作者:Naice
https://segmentfault.com/a/1190000023196603
这篇文章将带大家全面理解vue
的watcher
、computed
和user watcher
,其实computed
和user watcher
都是基于Watcher
来实现的,我们通过一个一个功能点去敲代码,让大家全面理解其中的实现原理和核心思想。所以这篇文章将实现以下这些功能点:
- 实现数据响应式
- 基于渲染
wather
实现首次数据渲染到界面上 - 数据依赖收集和更新
- 实现数据更新触发渲染
watcher
执行,从而更新ui界面 - 基于
watcher
实现computed
- 基于
watcher
实现user watcher
废话不要多说,先看下面的最终例子。
例子看完之后我们就直接开工了。
准备工作
首先我们准备了一个index.html
文件和一个vue.js
文件,先看看index.html
的代码
html>
"en">
"UTF-8">
全面理解vue的渲染watcher、computed和user atcher
"root">
index.html
里面分别有一个id是root的div节点,这是跟节点,然后在script标签里面,引入了vue.js
,里面提供了Vue构造函数,然后就是实例化Vue,参数是一个对象,对象里面分别有data 和 render 函数。然后我们看看vue.js
的代码:
function Vue (options) {
this._init(options) // 初始化
this.$mount() // 执行render函数
}
Vue.prototype._init = function (options) {
const vm = this
vm.$options = options // 把options挂载到this上
if (options.data) {
initState(vm) // 数据响应式
}
if (options.computed) {
initComputed(vm) // 初始化计算属性
}
if (options.watch) {
initWatch(vm) // 初始化watch
}
}
vue.js
代码里面就是执行this._init()
和this.$mount()
,this._init
的方法就是对我们的传进来的配置进行各种初始化,包括数据初始化initState(vm)
、计算属性初始化initComputed(vm)
、自定义watch初始化initWatch(vm)
。this.$mount
方法把render
函数渲染到页面中去、这些方法我们后面都写到,先让让大家了解整个代码结构。下面我们正式去填满我们上面写的这些方法。
实现数据响应式
要实现这些watcher
首先去实现数据响应式,也就是要实现上面的initState(vm)
这个函数。相信大家都很熟悉响应式这些代码,下面我直接贴上来。
function initState(vm) {
let data = vm.$options.data; // 拿到配置的data属性值
// 判断data 是函数还是别的类型
data = vm._data = typeof data === 'function' ? data.call(vm, vm) : data || {};
const keys = Object.keys(data);
let i = keys.length;
while(i--) {
// 从this上读取的数据全部拦截到this._data到里面读取
// 例如 this.name 等同于 this._data.name
proxy(vm, '_data', keys[i]);
}
observe(data); // 数据观察
}
// 数据观察函数
function observe(data) {
if (typeof data !== 'object' && data != null) {
return;
}
return new Observer(data)
}
// 从this上读取的数据全部拦截到this._data到里面读取
// 例如 this.name 等同于 this._data.name
function proxy(vm, source, key) {
Object.defineProperty(vm, key, {
get() {
return vm[source][key] // this.name 等同于 this._data.name
},
set(newValue) {
return vm[source][key] = newValue
}
})
}
class Observer{
constructor(value) {
this.walk(value) // 给每一个属性都设置get set
}
walk(data) {
let keys = Object.keys(data);
for (let i = 0, len = keys.length; i let key = keys[i]
let value = data[key]
defineReactive(data, key, value) // 给对象设置get set
}
}
}
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
if (newValue == value) return
observe(newValue) // 给新的值设置响应式
value = newValue
}
})
observe(value); // 递归给数据设置get set
}
重要的点都在注释里面,主要核心就是给递归给data
里面的数据设置get
和set
,然后设置数据代理,让 this.name
等同于 this._data.name
。设置完数据观察,我们就可以看到如下图的数据了。