首頁 > 軟體

Vue2為何能通過this存取到data與methods的屬性

2022-09-05 14:00:24

前言

在我沒接觸vue之前我不著調this是啥壓根就沒有接觸過,在我學過了vue之後我知道了this,那時候理解的this就是你要使用data中的屬性或呼叫methods中的方法等其他東西都要用this去呼叫,那時候其實我還是不知道this是啥,後面慢慢的才知道,當然我知道應該就是八股文背出來的,通過今天讀這個原始碼,讓我理解的更加深刻了,原來還可以這麼用。

一、vue的使用

看這一段程式碼我們能知道有個Vue的建構函式 ,然後我們使用new Vue建立了它的範例,並給它傳了一個物件引數,裡面有data和methods,那麼在這個Vue建構函式做了什麼才能讓我使用this可以直接存取裡面的屬性或者方法呢?

    //建立vue的範例
    const vm = new Vue({
        data: {
            desc: '為什麼this能夠直接存取data中的屬性',
        },
        methods: {
            sayName() {
                console.log(this.desc);
            }
        },
    });
    console.log(vm.name);
    console.log(vm.sayName());

二、Vue的建構函式

接收一個options引數

  • 使用 instanceof 判斷 this物件上是否出現了Vue的prototype,我們都知道this的指向是取決於誰呼叫
  • this._init(options) 證明在這呼叫要麼我們建立的範例上有_init方法要麼方法在Vue的prototype上,但是我們可以看到範例上並沒有_init方法 ,那麼肯定在一個地方給Vue的prototype上加上了_init方法 繼續往下看
 function Vue(options) {
        if (!(this instanceof Vue)
        ) {
            warn('Vue是一個建構函式,應使用「new」關鍵字呼叫');
        }
        this._init(options);
}
//Vue() //錯誤的呼叫方式 進入警告判斷 此時this指向window 然後window的 window.__proto__的指向的Window建構函式的prototype

三 初始化initMixin(Vue)

在原始碼中會看到很多初始化的函數在這我們initMixin()

這個函數就是在Vue的原型上增加了_init方法,方法接收一個引數,然後定義了vm變數,在我看的時候就想看看這個函數的this指向誰,其實也不難函數掛在Vue建構函式的原型上,呼叫還是在建構函式裡面使用this呼叫,建構函式的this指向Vue範例,根據this的指向規則 此時的vm就指向了Vue建構函式的範例。

使用this的存取規則如果範例上沒有就去原型上找

然後執行 initState(vm)

initMixin(Vue)
function initMixin(Vue) {
        //prototype上增加init方法
        Vue.prototype._init = function (options) {
            var vm = this; //Vue範例
          	vm.age = 30
          //程式碼進行了刪減
            initState(vm);
        }
}
//這裡只是舉例測試
const vm = new Vue({})
console.log(vm.age) //30

四 initState(vm)

這裡就是對我們傳入的data 或者methods進行不同的處理

 //initState方法程式碼進行了刪減
    function initState(vm) {
        vm._watchers = [];
        var opts = vm.$options; //這裡是我們在建立範例的時候傳的引數
        //如果傳了methods 則去呼叫
        if (opts.methods) { initMethods(vm, opts.methods); }
        if (opts.data) {
            initData(vm);
        } else {
            observe(vm._data = {}, true /* asRootData */);
        }
    }

五 initMethods(vm, opts.methods)

如果有methods則取呼叫initMethods方法

前面主要是判斷 methods中的值是不是函數,key有沒有跟props衝突等

最後一段程式碼就是在vm的範例上增加方法vm[key]=methods[key],在讀的時候我有這樣一個以為為什麼還要用bind改變this指向呢不本來就是寫在vm範例上的方法嗎 只能使用vm呼叫 那麼方法的this不就指向vm嗎?

/*
    vm:建構函式範例
    methods:我們傳的methods物件
    */
    function initMethods(vm, methods) {
        var props = vm.$options.props;
        //迴圈methods物件
        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
                    );
                }
                //判斷 methods 中的每一項是不是和 props 衝突了,如果是,警告。
                if (props && hasOwn(props, key)) {
                    warn(
                        ("Method "" + key + "" has already been defined as a prop."),
                        vm
                    );
                }
                //判斷 methods 中的每一項是不是已經在 new Vue範例 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中的方法 這樣其實我們就已經可以用vm存取 到methods中的方法了
            vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
        }
    }

問了群裡大佬之後原來這步操作時為了防止使用者改變this指向,專門做了個例子

在這我有定義了物件a裡面有個age屬性和fn,fn我賦值vm範例上的sayHi,然後a.fn()呼叫很明顯this的指向已經被改變了,使用bind之後則不會

  const vm = new Vue({
        methods: {
            sayHi() {
                console.log(this.age, 'hello-this')
            }
        }
    });
    let a = {
        age: 15,
        fn: vm.sayHi
    }
    console.log(a.fn(), 'vm') //列印15

六 initData(vm)

data是如何做到的使用this可以直接存取的,其實原理都一樣,

首先在vm範例上增加了_data,裡面存的我們傳入的data引數

 function initData(vm) {
        var data = vm.$options.data;
        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
        var keys = Object.keys(data);
        var props = vm.$options.props;
        var methods = vm.$options.methods;
        var i = keys.length;
        while (i--) {
            var key = keys[i];
            //判斷key值有沒有跟methods中的key重名
            {
                if (methods && hasOwn(methods, key)) {
                    warn(
                        ("Method "" + key + "" has already been defined as a data property."),
                        vm
                    );
                }
            }
            //判斷key值有沒有跟props中的key重名
            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 */);
    }

七 proxy(vm, "_data", key)

get 和 set 方法 注意裡面的this 指向vm範例物件,上面已經在vm範例物件上增加了_data 所有在獲取或者設定屬性值的時候 都是this._data[key] 也就是vm._data[key],

然後通過Object.defineProperty往範例物件上新增屬性,所以當我們存取vm[key] 也就是 vm._data[key]

  function proxy (target, sourceKey, key) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      };
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val;
      };
      Object.defineProperty(target, key, sharedPropertyDefinition);
  }
//建立vue建構函式
    function Vue(options) {
        if (!(this instanceof Vue)
        ) {
            warn('Vue是一個建構函式,應使用「new」關鍵字呼叫');
        }
        this._init(options);
    }
    //初始化
    initMixin(Vue);
    function initMixin(Vue) {
        //prototype上增加init方法
        Vue.prototype._init = function (options) {
            var vm = this; //Vue範例
            let methods = options.methods
            initState(vm);
        }
    }
    //initState方法程式碼進行了刪減
    function initState(vm) {
        vm._watchers = [];
        var opts = vm.$options; //這裡是我們在建立範例的時候傳的引數
        //如果傳了methods 則去呼叫
        if (opts.methods) { initMethods(vm, opts.methods); }
        if (opts.data) {
            initData(vm);
        } else {
            observe(vm._data = {}, true /* asRootData */);
        }
    }
    /*
    vm:建構函式範例
    methods:我們傳的methods物件
    */
    function initMethods(vm, methods) {
        var props = vm.$options.props;
        //迴圈methods物件
        for (var key in methods) {
            {
                //一些條件判斷
            }
            //給範例增加methods中的方法 這樣其實我們就已經可以用vm存取 到methods中的方法了
            vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
        }
    }
    const vm = new Vue({
        methods: {
            sayHi() {
                console.log('hello-this')
            }
        }
    });
    vm.sayHi() //hello-this

總結

其實看明白了Methods是怎麼做到直接用this可以直接存取的後面的都是差不多的,主要就是一個建構函式,然後建立一個範例,在範例上增加屬性或者方法,這樣我們就可以用範例物件直接存取了。原理就是那麼簡單。

到此這篇關於Vue2為何能通過this存取到data與methods的屬性的文章就介紹到這了,更多相關Vue2存取data內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com