本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
1、问题-如何访vue2得到data与methods
function Person(options){
}
const p = new Person({
data: {
name: '若川'
},
methods: {
sayName(){
console.log(this.name);
}
}
});
console.log(p.name);
// undefined
console.log(p.sayName());
// Uncaught TypeError: p.sayName is not a function
2、准备
新建文件index.html,body中增加下面代码
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
const vm = new Vue({
data: {
name: 'haha'
},
methods: {
sayName() {
console.log(this.name)
}
}
})
console.log(vm.name)
console.log(vm.sayName())
</script>
调试:在 F12 打开调试,source 面板,在例子中const vm = new Vue({打上断点。F11进入函数,走到Vue构造函数。
3、源代码解释
1、Vue构造函数
function Vue (options) {
if (!(this instanceof Vue) //检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
if (!(this instanceof Vue)){} 判断是不是用了 new 关键词调用构造函数。因为Vue一个项目会new Vue()一次,所以平时开发不需要写。
继续在 this._init(options)打上断点,并F11进入。
2、_init(options) 初始化函数
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);
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);
}
};
}
3、 initState(vm) 初始化状态
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); } //初始化props
if (opts.methods) { initMethods(vm, opts.methods); } //初始化methods
if (opts.data) { //初始化data
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); } //初始化computed
if (opts.watch && opts.watch !== nativeWatch) { //初始化watch
initWatch(vm, opts.watch);
}
}
调试,进入initMethods方法
4、initMethods 初始化方法
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
{
if (typeof methods[key] !== 'function') { //判断methods每一项不是函数
warn(
"Method "" + key + "" has type "" + (typeof methods[key]) + "" in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
if (props && hasOwn(props, key)) { //判断methods每一项函数与props的函数冲突
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 $."
);
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}//使用bind 将this指向vm
}
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;
5、initData 初始化数据
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function' //先用 _data 赋值,后续使用
? 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)) { //每一项data与methods冲突
warn(
("Method "" + key + "" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)){ //每一项data与props名冲
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); //代理到_data上
}
}
// observe data
observe(data, true /* asRootData */); //检测,成为响应式的数据
}
1、getData 获取数据
如果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();
}
}
2、proxy 代理
function noop (a, b, c) {}
var sharedPropertyDefinition = {
enumerable: true, //可配置
configurable: true, //可删除
get: noop, //get方法
set: noop //set方法
};
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);
}
//es3方式 允许删除属性和改变属性值
var person = {};
person.legs = 2;
//es5方式 值默认是undefined 属性值默认为false
var person = {};
Object.defineProperty(person, 'legs', {
value: 2,
writable: true, //可写
configurable: true, //可删除
enumerable: true //可通过for...in 枚举属性
});
var person = {};
Object.defineProperty(person, 'heads', {value: 1});
person.heads = 0; // 0
person.heads; // 1 (改不了)
delete person.heads; // false
person.heads // 1 (删不掉)
6、hasOwn 对象本身是否拥有该属性
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
7、isReserved 是否是内部预留的字符串$或者_开头
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F
}
isReserved('_data'); // true
isReserved('$options'); // true
isReserved('data'); // false
isReserved('options'); // false
4、简易实现
vue data实现 将数据代理到_data上,observe实现响应式检测,通过get与set挂在vm上,获取与修改数据。
function Vue(options) {
let vm = this
vm.$options = options
let opt = vm.$options
if (opt.data) {
initData(vm, opt.data)
}
if (opt.methods) {
initMethods(vm, opt.methods)
}
}
function noop(a, b, c) {
return {}
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
function initData(vm, data) {
data = vm._data = typeof data === 'function' ? data.call(data, vm) : data || {}
let keys = Object.keys(data)
let len = keys.length
while (len--) {
proxy(vm, '_data', keys[len])
}
}
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return target[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter(val) {
target[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function initMethods(vm, methods) {
for (var key in methods) {
vm[key] = typeof methods[key] === 'function' ? methods[key].bind(vm) : noop()
}
}
vue实例对象