本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
步骤
1. 进入Vue
const vm = new Vue({ //打上断点
data: {
name: 'liuyang',
},
methods: {
sayName(){
console.log(this.name);
}
},
});
2. 进入this._init(options);
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options); // 进入
}
3. 进入initState(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');
4. 分别进入initMethods() 和 initData()
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);
}
}
5.initMethods()
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
{
if (typeof methods[key] !== 'function') { // 如果method的key不是函数就报警告
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这个属性那么会报警告(方法xx已经在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); // 这里是this.方法名能获取到的关键
}
}
6. initData()
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm) // getData()
: 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); // data对象所有属性值组成的数组
var props = vm.$options.props;
var methods = vm.$options.methods; //
var i = keys.length; // 长度i
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); // proxy函数 重点
}
}
// observe data
observe(data, true /* asRootData */); //TODO 这里也是一个重点
}
调用vm.key的时候实际上是调用vm[_data][key] 我们打印Vue实例的时候也能看到_data对象
var sharedPropertyDefinition = {
enumerable: true, //为true时该属性才会出现在对象的枚举属性中,默认false
configurable: true,
get: noop, // noop(function) 啥也不返回
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); // 关于Object.defineProperty() 函数的详细介绍可以去看MDN
}
总结
Vue2中能获取到methods的原因: vue 将methods中的方法变成vm.xxx 然后使用bind方法改变了函数的指向,将其指向变成了vm 能获取到data的原因:vue创建了一个_data用来保存data中的数据 当this.data[key]的时候 调用的是this._data[key]
简单模拟一下
<!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>test</title>
</head>
<body>
<script>
let sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: null,
set: null
};
// 初始化data
function initData(vm) {
const keys = Object.keys(vm.$option.data)
let i = keys.length
while (i--) {
const key = keys[i]
proxy(vm, "_data", key)
}
}
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
return this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 初始化methods
function initMethods(vm, methods) {
for (const key in methods) {
vm[key] = methods[key].bind(vm)
}
}
function Person(option) {
const vm = this
vm.$option = option
vm._data = vm.$option.data
if (option.data) {
initData(vm)
}
if (option.methods) {
initMethods(vm, option.methods)
}
}
const p = new Person({
data: {
name: 'xxx'
},
methods: {
sayName() {
console.log(`in Function--${this.name}`);
}
}
})
console.log(p.name);
p.sayName()
</script>
</body>
</html>
getData 函数的作用其实就是获取真正的数据对象并返回,即:data.call(vm, vm),try..catch有错误发生那么则返回一个空对象。
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();
}
}
出现的vue2的一些工具函数
在文章最后复习一下 前不久刚看完vue2的工具函数
hasOwn检查一个对象是否具有某个属性
let hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(params) {
return hasOwnProperty.call(obj, key)
}
isReserved检查属性是否是保留的字符串 $ 或者 _
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F
}
bind有一些地方不支持bind方法 这里是兼容性处理
function polyfillBind (fn, ctx) {
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;