决定跟着黄轶老师的 vue2 源码课程好好学习下vue2的源码,学习过程中,尽量输出自己的所得,提高学习效率,水平有限,不对的话请指正~
将vue 的源码clone 到本地,切换到分支2.6。
Introduction
前面说了,vue 是怎么实现初始化数据渲染和组件化的,但当数据变动的时候,dom 又是怎么更新的呢。
先看个 demo
数据变更:
<div id="app">hello</div>
假设希望点击之后能将hello变成hello world的话,寻常怎么操作。
// 1.修改数据
const text = "hello world";
// 2.获取dom,监听事件
document.querySelector("#app").onclick = () => {
// 3.手动操作dom重新渲染
app.innerHTML = text;
};
如果需要多次修改的话,就不得不每次都手动操作 dom,重新渲染。
而 vue,去掉了手动操作dom,根据数据变化自动操作 dom。
<div id="app" @click="changeMsg">{{ message }}</div>
<script src="/Users/zhm/mygit/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
message: "Hello Vue!",
},
methods: {
changeMsg() {
// 这边只是 改变了数据,其余的由vue本身去更新dom,重新渲染
this.message = "Hello World!";
},
},
});
</script>
响应式对象
其实Object.defineProperty应该都听过了,嗯,主要就是用这个属性。
普通获取对象属性,设置对象属性都可以用这个,但是比较麻烦,所以一般分场景使用。
/** 普通创建属性的方式: **/
var obj = { a: 1 };
/** defineProperty创建、获取和设置属性的方式: **/
var obj = {};
// !注意需要另设一个变量,存储属性的值。
let value = 1;
Object.defineProperty(obj, "a", {
get() {
console.log("get");
return value;
},
set(newValue) {
console.log("set");
value = newValue;
},
});
/* 当然获取属性和设置的话,是一样的 */
// 获取
console.log(obj.a);
// 设置
obj.a = 2;
Object.defineProperty的核心就是get和set,因为是函数,所以能做很多事。
所谓的劫持,每次获取/设置属性的时候,都会执行get/set函数,既然是函数,自然能做一些别的事情。
所谓的响应式对象,当对象的某属性有
get/set,就称为响应式对象。
简版的响应式Vue
先写个简版的响应式Vue,大致有个印象,真实的源码处理的情景比较多,看懂简版的,再看源码,不容易迷路。。。
主要做了以下几件事:
- 用
vm._data指向options.data - 将
vm._data上面的属性都代理到vm上 vm._data.__ob__指向Observer实例,而这个实例的value是data的响应式
const vm = new Vue({ data: { a: 1 } });
console.dir(vm);
function Vue(options) {
this.vm = this;
this.$options = options;
/* this._init() initState()开始 */
initData(this);
/* this._init() initState()结束 */
}
function initData(vm) {
let data = vm.$options.data;
vm._data = data;
// 将data上面的属性直接挂在vm上
Object.keys(data).forEach((key) => {
/* proxy(vm, "_data", key) 开始 */
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get() {
return vm._data[key];
},
set(newValue) {
vm.data[key] = newValue;
},
});
/* proxy(vm, "_data", key) 结束 */
});
/* observe(data) 开始 */
data.__ob__ = data.__ob__ || new Observer(data);
/* observe(data) 结束 */
}
function Observer(data) {
this.value = data;
// this.dep = new Dep();
data.__ob__ = this;
Object.keys(data).forEach((key) => {
defineReactive(data, key);
});
}
// defineReactive将 obj.x 这种定义属性的方式 变成Object.defineProperty(obj, 'x' , {get(){}}
function defineReactive(data, key) {
// const dep = new Dep();
let value = data[key];
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// dep.depend();
return value;
},
set: function reactiveSetter(newValue) {
// dep.notify();
value = newValue;
},
});
}
initState
Vue 的构造函数,开始就是this._init(..),而Vue.prototype._init = function(){initState(this)}。
这里的initState就是让Vue的实例变成响应式对象的关键,这个方法就是对options进行各种初始化的操作,而本文的重点是对data/props的处理。
// src/core/instance/state.js
export function initState(vm: Component) {
vm._watchers = [];
const 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);
}
}
props 的处理
先看initProps,主要其实就是
- 遍历
options.props - 每个 prop 变成响应式(set/get),且每个 prop,也同步到
vm._props.xx - 通过
proxy把vm._props.xxx的访问代理到vm.xxx上
// src/core/instance/state.js
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {};
const props = (vm._props = {});
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = (vm.$options._propKeys = []);
const isRoot = !vm.$parent;
// root instance props should be converted
if (!isRoot) {
toggleObserving(false);
}
for (const key in propsOptions) {
keys.push(key);
const value = validateProp(key, propsOptions, propsData, vm);
defineReactive(props, key, value);
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key);
}
}
toggleObserving(true);
}
data 的处理
data 的初始化也做了两件事:
- 遍历
data对象,每一个vm._data.xx通过proxy代理到vm._data - 调用
observe观察data变化,将其也变成响应式
// src/core/instance/state.js
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
if (!isPlainObject(data)) {
data = {};
}
// proxy data on instance
const keys = Object.keys(data);
const props = vm.$options.props;
const methods = vm.$options.methods;
let i = keys.length;
while (i--) {
const key = keys[i];
if (!isReserved(key)) {
proxy(vm, `_data`, key);
}
}
// observe data
observe(data, true /* asRootData */);
}
proxy 代理
初始化 data 和 props 一个关键操作,就是让他们变成响应式,然后代理到vm上。
代理?其实就是就是一个中介。本身只是一个线索,会连接到真实的资源。
举个例子,看下面的obj,有个data属性,现在想obj.a也可以访问到 a 属性,怎么办?
此时obj.a就是一个中介,其真实连接的资源是obj.data.a。
const obj = { data: { a: 1 } };
其实还是利用上面的Object.defineProperty
Object.defineProperty(obj, "a", {
get() {
// 这里的obj.data.a就相当于存储变量
return obj.data.a;
},
set(newValue) {
obj.data.a = newValue;
},
});
// 1
console.log(obj.a);
现在看源码里,对于proxy的实现:
// src/core/instance/state.js
// const noop = function empty(){}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop,
};
// target相当于obj,sourceKey相当于data,key相当于a
// proxy就是可以让vm.x 代理到 vm.data.x
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
observe:给增加一个 Observer 实例
observe 的功能就是用来监测数据的变化,给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer 对象实例。
// src/core/observer/index.js
export function observe(value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return;
}
let ob: Observer | void;
if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob;
}
Observer 类:给对象的每个属性添加 getter 和 setter
Observer 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor(value: any) {
this.value = value;
// 实例化 Dep 对象
this.dep = new Dep();
this.vmCount = 0;
def(value, "__ob__", this);
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk(obj: Object) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
}
}
Observer 的构造函数逻辑很简单:
- 实例化
Dep对象, - 通过执行
def函数把自身实例添加到数据对象value的__ob__属性上
def就是给一个对象,定义一个属性,设置一个属性值,默认此属性是不可遍历的。
// def的定义
// src/core/util/lang.js
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
});
}
def相当于Object.defineProperty的简单封装。
defineReactive:给对象的某个属性动态添加 getter 和 setter
defineReactive 的功能就是给对象动态添加 getter 和 setter,让对象成为响应式对象:
// src/core/observer/index.js
/**
* Define a reactive property on an Object.
*/
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 初始化 Dep 对象的实例
const dep = new Dep();
// 拿到 obj 的属性描述符
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== "production" && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
},
});
}
defineReactive 函数:
- 最开始初始化
Dep对象的实例, - 接着拿到
obj的属性描述符, - 然后对子对象递归调用
observe方法
这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,
这样我们访问或修改 obj 中一个嵌套较深的属性,也能触发 getter 和 setter。
最后利用 Object.defineProperty 去给 obj 的属性 key 添加 getter 和 setter。
总结
响应式对象,核心就是利用 Object.defineProperty 给数据添加了 getter 和 setter。
这样在访问数据以及写数据的时候能自动执行一些逻辑:
getter做的事情是依赖收集setter做的事情是派发更新