中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue2?this能直接獲取到data和methods的原理是什么

發布時間:2022-06-24 13:48:06 來源:億速云 閱讀:128 作者:iii 欄目:開發技術

這篇文章主要講解了“Vue2 this能直接獲取到data和methods的原理是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Vue2 this能直接獲取到data和methods的原理是什么”吧!

    前言

    在平時使用vue來開發項目的時候,對于下面這一段代碼,我們可能每天都會見到:

    const vm = new Vue({
      data: {
          name: '我是pino',
      },
      methods: {
          print(){
              console.log(this.name);
          }
      },
    });
    console.log(vm.name); // 我是pino
    vm.print(); // 我是pino

    但是我們自己實現一個構造函數卻實現不了這種效果呢?

    function Super(options){}
    
    const p = new Super({
        data: {
            name: 'pino'
        },
        methods: {
            print(){
                console.log(this.name);
            }
        }
    });
    
    console.log(p.name); // undefined
    p.print(); // p.print is not a function

    那么vue2中是怎么實現這種調用方式的呢?

    源碼

    首先可以找到vue2的入口文件:

    src/core/instance/index

    function Vue (options) {
      if (process.env.NODE_ENV !== 'production' &&
        !(this instanceof Vue)
      ) {
        warn('Vue is a constructor and should be called with the `new` keyword')
      }
      this._init(options)
    }
    // 初始化操作是在這個函數完成的
    initMixin(Vue)
    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)
    export default Vue

    接下來看initMixin文件中是如何實現的

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }
        // a flag to avoid this being observed
        vm._isVue = true
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        // 初始化data/methods...
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')
      }
    }

    其實僅僅關注initState這個函數就好了,這個函數初始化了propsmethodswatchcomputed

    • 使用initProps初始化了props

    • 使用initMethods初始化了methods

    • 使用initData初始化了data

    • 使用initComputed初始化了computed

    • 使用initWatch初始化了watch

    function initState (vm) {
        vm._watchers = [];
        var opts = vm.$options;
        // 判斷props屬性是否存在,初始化props
        if (opts.props) { initProps(vm, opts.props); }
        // 有傳入 methods,初始化方法methods
        if (opts.methods) { initMethods(vm, opts.methods); }
        // 有傳入 data,初始化 data
        if (opts.data) {
          initData(vm);
        } else {
          observe(vm._data = {}, true /* asRootData */);
        }
        // 初始化computed
        if (opts.computed) { initComputed(vm, opts.computed); }
        // 初始化watch
        if (opts.watch && opts.watch !== nativeWatch) {
          initWatch(vm, opts.watch);
        }
    }

    在這里只關注initMethodsinitData

    initMethods

    function initMethods (vm, methods) {
        var props = vm.$options.props;
        for (var key in methods) {
          {
              // 判斷是否為函數
            if (typeof methods[key] !== 'function') {
              warn(
                "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
                "Did you reference the function correctly?",
                vm
              );
            }
            
            // 判斷props存在且props中是否有同名屬性
            if (props && hasOwn(props, key)) {
              warn(
                ("Method \"" + key + "\" has already been defined as a prop."),
                vm
              );
            }
            // 判斷實例中是否有同名屬性,而且是方法名是保留的 _ $ (在JS中一般指內部變量標識)開頭
            if ((key in vm) && isReserved(key)) {
              warn(
                "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
                "Avoid defining component methods that start with _ or $."
              );
            }
          }
          // 將methods中的每一項的this指向綁定至實例
          // bind的作用就是用于綁定指向,作用同js原生的bind
          vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
        }
    }

    其實整個initMethods方法核心就是將this綁定到了實例身上,因為methods里面都是函數,所以只需要遍歷將所有的函數在調用的時候將this指向實例就可以實現通過this直接調用的效果。

    其他的大部分代碼都是用于一些邊界條件的判斷:

    • 如果不為函數 -> 報錯

    • props存在且props中是否有同名屬性 -> 報錯

    • 實例中是否有同名屬性,而且是方法名是保留的 -> 報錯

    bind函數

    function polyfillBind (fn, ctx) {
        function boundFn (a) {
          var l = arguments.length;
          // 判斷參數的個數來分別使用call/apply進行調用
          return l
            ? l > 1
              ? fn.apply(ctx, arguments)
              : fn.call(ctx, a)
            : fn.call(ctx)
        }
        boundFn._length = fn.length;
        return boundFn
    }
    function nativeBind (fn, ctx) {
      return fn.bind(ctx)
    }
    // 判斷是否支持原生的bind方法
    var bind = Function.prototype.bind
      ? nativeBind
      : polyfillBind;

    bind函數中主要是做了兼容性的處理,如果不支持原生的bind函數,則根據參數個數的不同分別使用call/apply來進行this的綁定,而call/apply最大的區別就是傳入參數的不同,一個分別傳入參數,另一個接受一個數組。

    hasOwn 用于判斷是否為對象本身所擁有的對象,上文通過此函數來判斷是否在props中存在相同的屬性

    // 只判斷是否為本身擁有,不包含原型鏈查找
    var hasOwnProperty = Object.prototype.hasOwnProperty; 
    function hasOwn (obj, key) { 
        return hasOwnProperty.call(obj, key) 
    }
    hasOwn({}, 'toString') // false
    hasOwn({ name: 'pino' }, 'name') // true

    isReserved

    判斷是否為內部私有命名(以$_開頭)

    function isReserved (str) {
      var c = (str + '').charCodeAt(0);
      return c === 0x24 || c === 0x5F
    }
    isReserved('_data'); // true
    isReserved('data'); // false

    initData

    function initData (vm) {
        var data = vm.$options.data;
        // 判斷data是否為函數,如果是函數,在getData中執行函數
        data = vm._data = typeof data === 'function'
          ? getData(data, vm)
          : data || {};
        // 判斷是否為對象
        if (!isPlainObject(data)) {
          data = {};
          warn(
            'data functions should return an object:\n' +
            'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
            vm
          );
        }
        // proxy data on instance
        // 取值 props/methods/data的值
        var keys = Object.keys(data);
        var props = vm.$options.props;
        var methods = vm.$options.methods;
        var i = keys.length;
        // 判斷是否為props/methods存在的屬性
        while (i--) {
          var key = keys[i];
          {
            if (methods && hasOwn(methods, key)) {
              warn(
                ("Method \"" + key + "\" has already been defined as a data property."),
                vm
              );
            }
          }
          if (props && hasOwn(props, key)) {
            warn(
              "The data property \"" + key + "\" is already declared as a prop. " +
              "Use prop default value instead.",
              vm
            );
          } else if (!isReserved(key)) {
            // 代理攔截
            proxy(vm, "_data", key);
          }
        }
        // observe data
        // 監聽數據
        observe(data, true /* asRootData */);
    }

    getData

    如果data為函數時,調用此函數對data進行執行

    function getData (data, vm) {
        // #7573 disable dep collection when invoking data getters
        pushTarget();
        try {
          // 將this綁定至實例
          return data.call(vm, vm)
        } catch (e) {
          handleError(e, vm, "data()");
          return {}
        } finally {
          popTarget();
        }
    }

    proxy

    代理攔截,當使用this.xxx訪問某個屬性時,返回this.data.xxx

    // 一個純凈函數
    function noop (a, b, c) {}
    // 代理對象
    var sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get: noop,
        set: noop
    };
    function proxy (target, sourceKey, key) {
        // get攔截
        sharedPropertyDefinition.get = function proxyGetter () {
          return this[sourceKey][key]
        };
        // set攔截
        sharedPropertyDefinition.set = function proxySetter (val) {
          this[sourceKey][key] = val;
        };
        // 使用Object.defineProperty對對象進行攔截
        Object.defineProperty(target, key, sharedPropertyDefinition);
    }

    其實對data的處理就是將data中的屬性的key遍歷綁定至實例vm上,然后使用Object.defineProperty進行攔截,將真實的數據操作都轉發到this.data上。

    Object.defineProperty對象屬性

    • value:屬性的默認值。 

    • writable:該屬性是否可寫。 

    • enumerable:該屬性是否可被枚舉。 

    • configurable:該屬性是否可被刪除。 

    • set():該屬性的更新操作所調用的函數。 

    • get():獲取屬性值時所調用的函數。

    簡略實現

      function Person(options) {
          let vm = this
          vm.$options = options
          if(options.data) {
            initData(vm)
          } 
          if(options.methods) {
            initMethods(vm, options.methods)
          }
        }
        function initData(vm) {
          let data = vm._data = vm.$options.data
          let keys = Object.keys(data)
          let len = keys.length
          while(len--) {
            let key = keys[len]
            proxy(vm, "_data", key)
          }
        }
        var sharedPropertyDefinition = {
            enumerable: true,
            configurable: true,
            get: noop,
            set: noop
        };
        function proxy(target, sourceKeys, key) {
          sharedPropertyDefinition.get = function() {
            return this[sourceKeys][key]
          }
          sharedPropertyDefinition.set = function(val) {
            this[sourceKeys][key] = val
          }
          Object.defineProperty(target, key, sharedPropertyDefinition)
    
        }
        function noop(a, b, c) {}
        function initMethods(vm, methods) {
          for(let key in methods) {
            vm[key] = typeof methods[key] === 'function' ? methods[key].bind(vm) : noop
          }
        }
        let p1 = new Person({
          data: {
            name: 'pino',
            age: 18
          },
          methods: {
            sayName() {
              console.log('I am' + this.name)
            }
          }
        })
        console.log(p1.name) // pino
        p1.sayName() // 'I am pino'

    感謝各位的閱讀,以上就是“Vue2 this能直接獲取到data和methods的原理是什么”的內容了,經過本文的學習后,相信大家對Vue2 this能直接獲取到data和methods的原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    新晃| 永州市| 桂东县| 台南县| 凌源市| 山西省| 肃北| 呼伦贝尔市| 大石桥市| 柳州市| 卢龙县| 永宁县| 大渡口区| 托克托县| 玉林市| 武川县| 德州市| 喜德县| 涿鹿县| 莆田市| 新安县| 剑阁县| 镇坪县| 华蓥市| 绵阳市| 叶城县| 潢川县| 万载县| 绍兴县| 陵川县| 越西县| 潜江市| 宝应县| 彝良县| 神农架林区| 鸡东县| 龙陵县| 凤城市| 易门县| 三河市| 大洼县|