在我沒接觸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],
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