第23期:为什么 Vue2 this 能够直接获取到 data 和 methods ?
一、学习前言
1.1 本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与。
1.2 源码阅读辅助文档:juejin.cn/post/708298…
1.3 在线调试地址:github1s vue/vue/src/shared
二、学习目标
2.1 data中的数据为什么用this可以直接获取到
2.2 methods中的方法为什么用this可以直接获取到
三、调试环境准备(此处我用vscode进行调试)
3.1 先在vscode中下载一个插件,[Debugger for Chrome]
3.2 在代码行中左侧进行点击添加断点,然后按F5,开始调试。F11继续向下
四、源码解读
4.1 先建立一个html文件,然后引入vue.js。为了调试方便,我此处引入下载到本地的vue.js
在线vue.js自取unpkg.com/vue@2.6.14/…
<!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>vue中this直接获取data和method</title>
</head>
<body>
<script src="./vue.js"></script>
</body>
</html>
<script>
const vm = new Vue({
data: {
name: "小羊笑嘻嘻",
},
methods: {
sayName() {
console.log(this.name);
},
},
});
console.log(vm.name);
vm.sayName();
</script>
4.2 在new Vue行左侧点击添加断点,然后按F5可以进入到vscode的调试页面。如下图:
然后点击F11继续向下
4.3 断点进入vue函数
function Vue (options) {
if (!(this instanceof Vue) //判断是否是vue的实例,不是的话警告
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
然后再this._init(options)此行前添加断点,右击「运行到行」,按F11进入_init函数
4.4 init方法中我们关注下initState()方法
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++;
var startTag, endTag;
/* istanbul ignore if */
if (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 */
{
initProxy(vm);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm); // data和methods
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
/* istanbul ignore if */
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(("vue " + (vm._name) + " init"), startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
在initState行前点击添加断点,右击「运行到行」,按F11进入initState方法
4.5 initState方法中判断各属性若被定义进行各自的初始化
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); } // 初始化props
if (opts.methods) { initMethods(vm, opts.methods); } // 初始化方法
if (opts.data) {
initData(vm); //初始化data
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
在initMethods行前点击添加断点,右击「运行到行」,按F11进入方法内部。如图:
4.6 initMethods中主要是进行各项判断,然后改变原有方法的this指向,全部通过bind绑定到vm上
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
);
}
if (props && hasOwn(props, key)) { // 判断有没有和props冲突
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
if ((key in vm) && isReserved(key)) { // 是否函数重复,并且方法名和保留的_$冲突
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);// 将函数的指针变换到vm实例上
}
}
4.7 initData中主要涉及初始化data之前的系列判断,然后进行数据代理,数据监测。
function initData (vm) {
var data = vm.$options.data;
// 将data相关的值保_data,以备后用
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
if (!isPlainObject(data)) { // 如果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)) { // 判断data是否与method冲突
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) { // 判断是否与prop冲突
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) { // 不是内部私有的保留属性,做一层代理,代理到 _data 上。
proxy(vm, "_data", key);
}
}
// observe data // 最后监测 data,使之成为响应式的数据。
observe(data, true /* asRootData */);
}
4.8 proxy方法解读
var noop = function(a,b,c) {}
var sharedPropertyDefinition = {
enumerable: true, // 该属性在for in循环中是否会被枚举。
configurable: true, // 该属性是否可被删除。
get: noop, // 获取属性值时所调用的函数。
set: noop // 该属性的更新操作所调用的函数。
};
// 访问this.xxx 实际是访问的 this._data.xxx
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);
}
4.9 涉及到的一些基础工具类和方法,可以看24期vue工具类中有详解,此处不做赘述
五、跟着手写一遍vue中data和methods的简易实现
var noop = function (a, b, c) {};
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop,
};
class Vue {
constructor(options) {
let vm = this;
vm.$options = options;
const opts = vm.$options;
if (opts.data) {
this.initData(vm);
}
if (opts.methods) {
this.initMethods(vm);
}
}
initData(vm) {
const data = (vm._data = vm.$options.data);
const keys = Object.keys(data);
let i = keys.length;
while (i--) {
this.proxy(vm, "_data", keys[i]);
}
}
proxy(target, sourcesKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourcesKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
return (this[sourcesKey][key] = val);
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
initMethods(vm) {
const methods = vm.$options.methods;
for (var key in methods) {
vm[key] =
typeof methods[key] !== "function" ? noop : methods[key].bind(vm);
}
}
}
六、学习总结
6.1 data中的数据为什么用this可以直接获取到
vue构造函数中,先对data判断保证最后结果获取到的是纯object。其次对其各属性值进行判断,防止和props和methods重复。对不是vue保留关键词的属性通过Object.defineProperty代理到vue的_data。data中的属性最终存储到vue实例vm的_data中。访问this.xxx,实际访问的是this._data.xxx
6.2 methods中的方法为什么用this可以直接获取到
同样先对methods中的方法名进行校验,不重复且是一个function,将methods中方法的this指向通过bind转换为vue的实例vm上。
6.3 异常判断
源码中有针对各属性进行的类型、重复等判断。自己在开发过程中,有时候会忽略一些异常的判断,思路考虑的应该更全面一些。要有容错机制