我以前介绍过vue1.0如何实现observer
和watcher
。本想继续写下去,但是vue2.0横空出世..因此
直接看vue2.0吧。这篇文章在公司分享过,终于写出来了。咱们采用用最精简的代码,还原vue2.0响应式架构实现
之前写的那篇 vue 源码分析之如何实现 observer 和 watcher能够做为本次分享的参考。
不过不看也不要紧,可是最好了解下Object.definePropertyjavascript
理解vue2.0的响应式架构,就是下面这张图html
顺带介绍他比react快的其中一个缘由vue
const demo = new Vue({ data: { text: "before", }, //对应的template 为 <div><span>{{text}}</span></div> render(h){ return h('div', {}, [ h('span', {}, [this.__toString__(this.text)]) ]) } }) setTimeout(function(){ demo.text = "after" }, 3000)
对应的虚拟dom会从<div><span>before</span></div>
变为 <div><span>after</span></div>
好,开始吧!!!java
来来来先看代码吧node
class Vue { constructor(options) { this.$options = options this._data = options.data observer(options.data, this._update) this._update() } _update(){ this.$options.render() } } function observer(value, cb){ Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) } function defineReactive(obj, key, val, cb) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{}, set:newVal=> { cb() } }) } var demo = new Vue({ el: '#demo', data: { text: 123, }, render(){ console.log("我要render了") } }) setTimeout(function(){ demo._data.text = 444 }, 3000)
为了好演示咱们只考虑最简单的状况,若是看了vue 源码分析之如何实现 observer 和 watcher可能就会很好理解,不过不要紧,咱们三言两语再说说,这段代码要实现的功能就是将react
var demo = new Vue({ el: '#demo', data: { text: 123, }, render(){ console.log("我要render了") } })
中data
里面全部的属性置于 observer,而后data
里面的属性,好比 text
以改变,就引发_update()
函数调用进而从新渲染,是怎样作到的呢,咱们知道其实就是赋值的时候就要改变对吧,当我给data
下面的text
赋值的时候 set
函数就会触发,这个时候 调用 _update
就ok了,可是git
setTimeout(function(){ demo._data.text = 444 }, 3000)
demo._data.text
没有demo.text
用着爽,不要紧,咱们加一个代理github
_proxy(key) { const self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) }
而后在Vue
的constructor
加上下面这句segmentfault
Object.keys(options.data).forEach(key => this._proxy(key))
第一步先说到这里,咱们会发现一个问题,data
中任何一个属性的值改变,都会引发_update
的触发进而从新渲染,属性这显然不够精准啊数组
好比考虑下面代码
new Vue({ template: ` <div> <section> <span>name:</span> {{name}} </section> <section> <span>age:</span> {{age}} </section> <div>`, data: { name: 'js', age: 24, height: 180 } }) setTimeout(function(){ demo.height = 181 }, 3000)
template
里面只用到了data
上的两个属性name
和age
,可是当我改变height
的时候,用第一步的代码,会不会触发从新渲染?会!,但其实不须要触发从新渲染,这就是问题所在!!
首先,template最后都是编译成render函数的(具体怎么作,就不展开说了,之后我会说的),而后render 函数执行完就会获得一个虚拟DOM,为了好理解咱们写写最简单的虚拟DOM
function VNode(tag, data, children, text) { return { tag: tag, data: data, children: children, text: text } } class Vue { constructor(options) { this.$options = options const vdom = this._update() console.log(vdom) } _update() { return this._render.call(this) } _render() { const vnode = this.$options.render.call(this) return vnode } __h__(tag, attr, children) { return VNode(tag, attr, children.map((child)=>{ if(typeof child === 'string'){ return VNode(undefined, undefined, undefined, child) }else{ return child } })) } __toString__(val) { return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val); } } var demo = new Vue({ el: '#demo', data: { text: "before", }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } })
咱们运行一下,他会输出
{ tag: 'div', data: {}, children:[ { tag: 'span', data: {}, children: [ { children: undefined, data: undefined, tag: undefined, text: '' // 正常状况为 字符串 before,由于咱们为了演示就不写代理的代码,因此这里为空 } ] } ] }
这就是 虚拟最简单虚拟DOM
,tag
是html
标签名,data
是包含诸如 class
和 style
这些标签上的属性,childen
就是子节点,关于虚拟DOM就不展开说了。
回到开始的问题,也就是说,我得知道,render
函数里面依赖了vue实例里面哪些变量(只考虑render 就能够,由于template 也会是帮你编译成render)。叙述有点拗口,仍是看代码吧
var demo = new Vue({ el: '#demo', data: { text: "before", name: "123", age: 23 }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } })
就像这段代码,render
函数里其实只依赖text
,并无依赖 name
和 age
,因此,咱们只要text
改变的时候
咱们自动触发 render
函数 让它生成一个虚拟DOM
就ok了(剩下的就是这个虚拟DOM和上个虚拟DOM
作比对,而后操做真实DOM
,只能之后再说了),那么咱们正式考虑一下怎么作
回到最上面那张图,咱们知道data
上的属性设置defineReactive
后,修改data 上的值会触发 set
。
那么咱们取data
上值是会触发 get
了。
对,咱们能够在上面作作手脚,咱们先执行一下render
,咱们看看data
上哪些属性触发了get
,咱们岂不是就能够知道 render
会依赖data
上哪些变量了。
而后我么把这些变量作些手脚,每次这些变量变的时候,咱们就触发render
。
上面这些步骤简单用四个子归纳就是 计算依赖。
(其实不只是render
,任何一个变量的改别,是由于别的变量改变引发,均可以用上述方法,也就是computed
和 watch
的原理,也是mobx的核心)
咱们写一个依赖收集的类,每个data
上的对象都有可能被render
函数依赖,因此每一个属性在defineReactive
时候就初始化它,简单来讲就是这个样子的
class Dep { constructor() { this.subs = [] } add(cb) { this.subs.push(cb) } notify() { console.log(this.subs); this.subs.forEach((cb) => cb()) } } function defineReactive(obj, key, val, cb) { const dep = new Dep() Object.defineProperty(obj, key, { // 省略 }) }
而后,当执行render
函数去'touch'依赖的时候,依赖到的变量get就会被执行,而后咱们就能够把这个 render
函数加到 subs
里面去了。
当咱们,set
的时候 咱们就执行 notify
将全部的subs数组里的函数执行,其中就包含render
的执行。
至此就完成了整个图,好咱们将全部的代码展现出来
function VNode(tag, data, children, text) { return { tag: tag, data: data, children: children, text: text } } class Vue { constructor(options) { this.$options = options this._data = options.data Object.keys(options.data).forEach(key => this._proxy(key)) observer(options.data) const vdom = watch(this, this._render.bind(this), this._update.bind(this)) console.log(vdom) } _proxy(key) { const self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data.text = val } }) } _update() { console.log("我须要更新"); const vdom = this._render.call(this) console.log(vdom); } _render() { return this.$options.render.call(this) } __h__(tag, attr, children) { return VNode(tag, attr, children.map((child)=>{ if(typeof child === 'string'){ return VNode(undefined, undefined, undefined, child) }else{ return child } })) } __toString__(val) { return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val); } } function observer(value, cb){ Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) } function defineReactive(obj, key, val, cb) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{ if(Dep.target){ dep.add(Dep.target) } return val }, set: newVal => { if(newVal === val) return val = newVal dep.notify() } }) } function watch(vm, exp, cb){ Dep.target = cb return exp() } class Dep { constructor() { this.subs = [] } add(cb) { this.subs.push(cb) } notify() { this.subs.forEach((cb) => cb()) } } Dep.target = null var demo = new Vue({ el: '#demo', data: { text: "before", }, render(){ return this.__h__('div', {}, [ this.__h__('span', {}, [this.__toString__(this.text)]) ]) } }) setTimeout(function(){ demo.text = "after" }, 3000)
咱们看一下运行结果
好咱们解释一下 Dep.target
由于咱们得区分是,普通的get
,仍是在查找依赖的时候的get
,
全部咱们在查找依赖时候,咱们将
function watch(vm, exp, cb){ Dep.target = cb return exp() }
Dep.target
赋值,至关于 flag 一下,而后 get
的时候
get: () => { if (Dep.target) { dep.add(Dep.target) } return val },
判断一下,就行了。
到如今为止,咱们再看那张图是否是就清楚不少了?
我很是喜欢,vue2.0 以上代码为了好展现,都采用最简单的方式呈现。
不过整个代码执行过程,甚至是命名方式都和vue2.0同样
对比react,vue2.0 自动帮你监测依赖,自动帮你从新渲染,而
react 要实现性能最大化,要作大量工做,好比我之前分享的
react如何性能达到最大化(前传),暨react为啥非得使用immutable.js
react 实现pure render的时候,bind(this)隐患。
而 vue2.0 自然帮你作到了最优,并且对于像万年不变的 如标签上静态的class
属性,
vue2.0 在从新渲染后作diff 的时候是不比较的,vue2.0比 达到性能最大化的react 还要快的一个缘由
而后源码在此,喜欢的记得给个 star 哦? 后续,我会简单聊聊,vue2.0的diff。 若是有疑问,能够在评论区留言哈