源码共读,我又来了——Vue2你到底做了什么
前言
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
本篇是源码共读第23期 | 为什么Vue2 this 能够直接获取到 data 和 methods,点击了解本期详情
准备
首先看下想要的实现结果:
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
const vm = new Vue({
data: {
name: '1122',
},
methods: {
sayName() {
console.log(this.name);
}
},
});
console.log(vm.name); // 1122
console.log(vm.sayName()); // 1122
看源码前的思考及自我实现
这是怎么实现的?如果让我完成这个需求,我该如何编写?
首先这个打印结果'1122'哪里来的,换句话说数据源是谁?从上面代码不难发现是name
,而vm.name
打印了这个数值,说明vm添加了name
这个属性并且值是data中的name。同样也添加了sayName
这个属性,值是一个方法,这个方法中打印了this.name
,vm.sayName()
显式绑定了里面的this,此时this也就是vm对象。好了,听上去这猜测还挺是那么回事的,自己动手试试:
function Vue({ data, methods }) {
for (key in data) {
this[key] = data[key]
}
for (fn in methods) {
this[fn] = methods[fn]
}
}
const vm = new Vue({
data: {
name: '我是小李',
},
methods: {
sayName() {
console.log(this.name);
this.showMessage()
},
showMessage() {
console.log('我' + this.age + '岁了')
}
},
});
console.log(vm.name) // 我是小李
vm.sayName() // 我是小李 我22岁了
咦~不光听起来有的可行,写起来也还蛮像那么回事的嘛。不过人家可是Vue源码,还能这么简单?但是我实在想不到了,就简单需求而言,确实可以做到。那么接下来看下源码吧
查看源码
注:我没有从官方拷贝过来代码,是直接从若川文章里Copy过来的
实不相瞒我开始都不晓得咋进入Vue构造函数内部😅在构造函数处打断点F11进不去,后来发现点击⬇️就可以,由此可以看出我不习惯打断点的坏习惯了😂
function Vue(options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
嗯?咋还用了instanceof?这个方法是判断一个对象是否属于特定的类或其继承链中的某个类(我个人理解就是这个类是否在对象的原型链上)在此处就是判断是否使用了new
关键词调用构造函数,因为构造函数也是函数,只要是函数那就有被调用的可能(个人是这么认为的,如有不对请指正)
_init初始化函数
// 代码有删减
function initMixin(Vue) {
Vue.prototype._init = function (options) {
var vm = this;
vm._uid = uid$3++;
vm._isVue = true;
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
// 初始化状态
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
};
}
initState初始化状态
function initState(vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
// 有传入 methods,初始化方法
if (opts.methods) { initMethods(vm, opts.methods); }
// 有传入 data,初始化 data
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
function initMethods(vm, methods) {
var props = vm.$options.props;
for (var key in methods) { // 遍历methods
{
if (typeof methods[key] !== 'function') { 如果每一项的value不是函数,警告
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,并且props已经存在了这个key, 警告
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
if ((key in vm) && isReserved(key)) {
// 如果每一项已经在Vue的实例对象vm上存在,且方法名是保留的 _ $,警告
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);
}
}
initData 初始化 data
function initData(vm) {
var data = vm.$options.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--) { // 遍历 data ,获取其中每一项:
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
// 如果和 methods 冲突了,警告
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
// 如果和 props 冲突了,警告。
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, true /* asRootData */); // 监测 data,使其成为响应式的数据
}
提取出的简化版实现
function noop(a, b, c) { }
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);
}
function initData(vm) {
const data = vm._data = vm.$options.data;
const keys = Object.keys(data);
var i = keys.length;
while (i--) {
var key = keys[i];
proxy(vm, '_data', key);
}
}
function initMethods(vm, methods) {
for (var key in methods) {
vm[key] = typeof methods[key] !== 'function' ? noop : methods[key].bind(vm);
}
}
function simpVue(options) {
let vm = this;
vm.$options = options;
var opts = vm.$options;
if (opts.data) {
initData(vm);
}
if (opts.methods) {
initMethods(vm, opts.methods)
}
}
😭可以看出差的不是一星半点啊
总结
我在考虑实现时没有特殊情况,而是直接设定了Vue构造函数是使用了new
关键词调用的,data传递的是对象,methods传递的是方法......
同样也只是将给出的示例实现而不是将Vue的该项功能实现,像代理,props等问题我就没有进行考虑😄
总体下来这篇源码阅读起来并没有那么难(没有那么难只是我想不到罢了😥)
转载自:https://juejin.cn/post/7252951745013825596