介绍
关于 Vue.js 的原理一直以来都是一个话题。经过几天的源码学习和资料介绍,我将一些个人理解的经验给写下来,希望能够与大家共勉。
附上GITHUB源码地址, 如果有任何不解 可以在 文章下面提出或者写下issue, 方便大家回答和学习, 有兴趣可以Star.
最后附上 LIVE DEMO
简单图解 Vue.js 内置对象
构造实例对象
应用创建时需要使用的构造函数对象, data
为我们的数据模型
const vm = new Vue({
data: {
foo: 'hello world'
}
});
数据双向绑定
被观察者 observe
被观察对象,data属性里的值的变化,get
时检查是否有新的观察员需要加入观察员集合, set
时通知观察员集合里的观察员更新视图,被观察者有一个观察员的集合对象。
观察员集合对象 Dep
一个观察员的收集器, depend()
负责将当前的 Dep.target
观察员加入观察员集合, data
中的每一项数据都会有对应的闭包dep
对象, 数据对象会有一个内置的dep
对象,用来通知嵌套的数据对象变化的情况。
观察员 watcher
由模板解析指令创建的观察员, 负责模板中的更新视图操作。保留旧的数据,以及设置钩子函数 update()
, 等待被观察者数据通知更新,对比新的value与旧数据, 从而更新视图。
数据代理 proxyData
我们的关注点在与创建后的vm
, 其 options.data
, 被挂载至vm._data
, 同时被代理至 vm
上, 以至于 vm._data.foo
等价于 vm.foo
, 代理函数代码如下:
const noop = function () {}
const 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)
}
// initState 时执行 initData
function initData () {
const keys = Object.keys(data)
let i = keys.length
while (i--) {
const key = keys[i]
// key不以 $ 或 _开头
if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// do something
}
数据劫持 defineProperty
/**
* Define a reactive property on an Object.
*/
function defineReactive(obj, key, val) {
// 观察员集合
const dep = new Dep();
// data 内属性描述
const property = Object.getOwnPropertyDescriptor(obj, key);
// 属性不可再次修改
if (property && property.configurable === false) {
return;
}
// 属性预定义 getter/setters
const getter = property && property.get;
const setter = property && property.set;
// 如果val为对象, 获取val的 被观察数据对象 __ob__
let childOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 被观察数据被使用时, 获取被观察员最新的数据
const value = getter ? getter.call(obj) : val
// 观察员在new时或使用 get()时, 注入给被观察员对象集合
if (Dep.target) {
// 将当前的 watcher 传递给 观察员
dep.depend();
if (childOb) {
// 将当前的 watcher 传递给子对象的 观察员
childOb.dep.depend();
}
}
return val;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
// 新value设置被观察者对象 __ob__
childOb = observe(newVal);
// 通知数据对象依赖的观察员, 更新 update()
dep.notify();
}
});
}
计算属性介绍
初始化执行过程
计算属性的定义和使用
var vm = new Vue({
data: {
firstname: 'li',
lastname: 'yanlong'
},
computed: {
fullname () {
return this.firstname + this.lastname;
}
}
});
console.log(vm.fullname);
核心代码解读
const computedWatcherOptions = {lazy: true};
function initComputed (vm, computedOptions) {
// 创建计算属性对应的观察员对象
// 获取计算属性时收集 内置数据对象的 dep
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
function defineComputed (target, key, userDef) {
// 如果不为服务端渲染,则使用缓存value
const shouldCache = !isServerRendering()
// sharedPropertyDefinition 共享属性配置
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop
}
// 给 vm对象定义计算属性
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 创建
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 计算属性的 watcher 有数据更新过, 重新计算
if (watcher.dirty) {
watcher.evaluate()
}
// 视图指令 使用了计算属性
// 将计算属性的watcher依赖传递给视图指令的 watcher
if (Dep.target) {
// 源码地址
// https://github.com/vuejs/vue/blob/master/src/core/observer/watcher.js#L210
watcher.depend()
}
return watcher.value
}
}
}
计算属性知识点介绍
1. 计算属性的 watcher
对象
计算属性函数在读取它本身的value
时, 使用一个watcher
观察员进行代理. 通过对原始数据的劫持, 将watcher
观察员添加到原始数据的dep
依赖集合中.
2. deps
的数据对象发生更新
举例,如果firstname
或者 lastname
任意一个更新,那么就会设置计算属性的watcher.dirty = true
, 而当其它指令或者函数使用,计算属性会重新计算值,如果是视图指令,还会重新该指令的watcher
的数据对象依赖。
Watcher 观察员种类
目前了解情况来看, 主要分三类
- 视图指令的
watcher
- 计算属性的
watcher
- 用户自定义的
watcher
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。