1、前言
- 本文参加了由 公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与
- 这是源码共读的第23期,链接: # 第23期 | 为什么 Vue2 this 能够直接获取到 data 和 methods
2、开始
2.1 创建新文件/开启服务
新建myVue文件夹并新建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
const vm = new Vue({
data() {
return {
name: '我是若川',
}
},
methods: {
sayName(){
console.log(this.name);
}
},
});
console.log(vm.name);
console.log(vm.sayName());
</script>
再全局安装npm i -g http-server启动服务
npm i -g http-server
http-server .
// 如果碰到端口被占用,也可以指定端口
http-server -p 8081 .
2.2 开始调试
在const vm = new Vue({这里打上断点,按F11进入构造函数
进入构造函数内部,在this._init打上断点,按下F11进入函数
function Vue (options) {
// 检查是否通过new 关键字调用 Vue
// instanceof 判断 Vue的原型对象是否在 this的原型链上
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
// 初始化
this._init(options);
}
这里只看initState函数,按F11进入
这里重点看 initMethods和 initData, 打上断点F11进入函数内部
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
initMethods 函数内部
通过for...in 遍历 methods并检查是否符合条件
遍历methods 通过bind把this指向为new Vue实例
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中的key跟methods中的key是否重复
if (props && hasOwn(props, key)) {
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
// 检查methods中的key是否与this实例中的关键字重复了
if ((key in vm) && isReserved(key)) {
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
// 把key值赋值到this上并且检查值是否非函数 并执行不同的逻辑
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
在methods中可以访问到this的关键函数 bind
根据浏览器对bind的兼容情况,一个正常使用bind一个通过 call和apply来实现bind
bind描述: bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
/* istanbul ignore next */
function polyfillBind (fn, ctx) {
// a 为函数调用时传的参数
function boundFn (a) {
var l = arguments.length;
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)
}
var bind = Function.prototype.bind
? nativeBind
: polyfillBind;
initData 函数
函数内部先检查 data 是否为function 是的话调用 getData 函数 返回 data对象
赋值给 _data
检查data对象是否为 object 不通过报错
然后遍历key值 检查key值是否与methods与props中的key是否重复 不通过则报错
最后通过 proxy函数进行代理
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];
{
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 {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();
}
}
proxy
函数接收三个参数 this _data key 在this上创建新的属性, 并对该属性进行修改
当访问该属性时, this[sourceKey][key] 其实时访问this._data中的属性值
当操作该属性时, this[sourceKey][key] = val; 操作的也是this._data 中的属性值
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
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);
}
2.3总结
为什么vue2中this可以直接获取到data与methods
methods 通过bind把methods函数的this改为new Vue实例 所以可以直接通过this访问
data 通过 Object.defineProperty() 函数对属性进行修改get以及set 当你this[key]调用的时候实际执行的时 this._data[key]