作者:李旭光
引用请标明出处
前言
最近一直在追网课,说实话从业9年了,一直觉得前端发展非常快,而且一直充满着危机感,每天都要学习进步才有安稳的感觉,今天听了 vue 响应式原理实现的公开课,感觉还不错,做了如下笔记,帮助自己记忆,也希望能帮助大家。
Vue2 原理
什么是 defineProperty
defineProperty 其实是定义对象属性用的
defineProperty 其实并不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,只不过是属性里 get 和 set 实现了响应式。
属性名 |
默认值 |
value |
undefined |
get |
undefined |
set |
undefined |
writable |
false |
enumerable |
false |
configurable |
false |
1 2 3 4 5 6 7 8 9 10 11 12 13
| var ob = { a:1, b:2 }
Object.defineProperty(ob,'a',{ writable:false, enumerable:true, configurable:true, }) console.log(Object.getOwnPropertyDescriptor(ob,'a')) ob.a = 2 console.log(ob.a)
|
下面我们实现一下双向绑定
1 2 3 4 5 6 7 8 9 10 11 12 13
| Object.defineProperty(ob,'a',{ get:function(){ console.log('a is be get') return 999; }, set:function(){ console.log('a is be set') return 999; }, })
console.log(Object.getOwnPropertyDescriptor(ob,'a'))
|
改造代码实现双向绑定(存取值)
1 2 3 4 5 6 7 8 9 10 11 12
| var _val = obj.a; Object.defineProperty(ob,'a',{ get:function(){ console.log('a is be get') return _val; }, set:function(newVal){ _val = newVal console.log('a is be set') return _val; }, })
|
Vue 中从改变一个数据到发生改变的过程
- 改变数据触发 Set
- Set 部分触发 notify(更新)
- Get 部分收集依赖
- 更改对应的虚拟 Dom
- 重新 Render
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
function MyVue(){ this.$data = { a: { b:1 }, c:2 } this.el = document.getElementById('app'); this.virtualDom = ''; this.observer(this.$data); this.render(); } vue.property.observer = function(obj){ var _val, self = this; for(var key in obj){ _val = obj[key]; if(typeof _val === 'Object'){ this.observer(_val) }else{ Object.defineProperty(this.$data,key,{ get:function(){ return _val }, set:function(newVal){ _val = newVal self.render() } }) } } } vue.property.render = function(){ this.virtualDom = 'i am '+this.$data.b; this.el.innerHTML = this.virtualDom; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // index.html
<!DOCTYPE html> <html> <head> <title>自己实现Vue2数据双向绑定</title> </head> <body> <div id="app"></div> <script type="text/javascript" src='myVue.js'></script> <script type="text/javascript"> var mv = new MyVue(); setTimeout(function(){ console.log('changes'); console.log(mv.$data); mv.$data.b = 222; }) </script> </body> </html>
|
依赖收集:
- 我们的data里面的数据并不是所有地方都用到
- 如果我们直接更新整个视图,浪费资源
- 先收集依赖改变的数据的组件,再更新依赖了数据的组件(Dep depend notify)
格外注意的地方—数组怎么监听
definePropty 只能给对象进行 get set 绑定, 数组怎么办?
vue 中 使用了 装饰者模式
装饰者模式 Decorator模式(别名Wrapper):动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。
1 2 3 4 5 6 7 8 9 10
| var arraypro = Array.property; var arrob = Object.create(arraypro); var arr = ['push','pop','shift']; arr.forEach(function(method,index){ arrob[method]=function(){ var ret = arraypro[method].apply(this,arguments) dep.notify() } })
|
Vue3 实现双向绑定
Proxy 是什么?
Proxy 对象用于定义基本操作的自定义行为
和 definePropty 类似,功能几乎一样,只是用法上不同
- 不会污染原对象
- 直接给对象就可以了
- 不需要借助外部变量 _val
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var ob = { a:1, b:2 }
var newOb = new Proxy(ob,{ get(target,key,receiver){ console.log(target,key,receiver) return target[key] }, set(target,key,value,receiver){ return Reflect.set(target.key,value); } })
|
为什么改用 Proxy
- defineProperty 只能监听某个属性,不能全对象监听
- 可以省去
for in
循环提升代码执行效率
- 可以监听数组,不需要再为数组做特异性操作
- 不污染原对象
- 更优雅
我们用 Proxy 实现一下 observe 方法
1 2 3 4 5 6 7 8 9 10 11 12
| vue.property.observe = function(){ var self = this; this.$data = new Proxy(this.$data,{ get(target,key, receiver){ return target[key] }, set(target,key,newVal){ target[key] = newVal self.render() } }) }
|
还能用 Proxy 做什么
- 校验类型
- 真正的私有变量
校验类型
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
var valid = { name(value){ var reg=/^[\u4E00-\u9FAS]=$/ if(typeof value === 'string' && reg.test(value)){ return true; } return false; }, age(value){ if(typeof value === 'number' && value > 18){ return true; } return false; } } function Person(name,age){ this.name = name this.age = age return new Proxy(this,{ get(target,key){ return target[key] }, set(target,key,value){ if(valid[key](value)){ return Reflect.set(target,key,value) }else{ throw new Error(key+'is not valid') } } }) } new Person('name',19)
|
策略模式
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
真正的私有变量
vue-router 源码中,给 $router ,$route 用 defineProperty 定义 get 并返回本身,这样就不能修改属性了。
1 2 3 4 5 6 7 8 9 10 11 12 13
| Object.defineProperty(this,'$router',{ get(){ return this._root._router; } }) Object.defineProperty(this,'$route',{ get(){ return { current: this._root._router.history.current; } } })
|
虚拟Dom和diff算法
虚拟Dom是虚拟的,他只在概念里面存在,在AST语法树,下面进行解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div> <p>{{msg}}</p> <p>2</p> <p>3</p> </div> </template>
diff <div> props:{ id:2 } children:[ diff <p> props:{ id:xxx } children:[ ... ] ]
var virtual = { dom:'div', props:{ id:2 }, children:[ .... ] }
|
每层结构都是一样的,那么是如何进行 diff 比对的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
patchVnode(oldVnode,vnode){ const el = vnode.el = oldVnode.el; let i,oldCh = oldVnode.children ,ch = vnode.children if(oldVnode === vnode) return; if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text){ api.setTextContent(el,vonde.text) } else { updateEle(); if(oldCh&&ch&&oldCh!==ch){ updateChildren() } else if(ch){ createEl(vnode) } else if(oldCh){ api.removeChildren(el) } } }
|
源码要多看,以下必看 Vue/react/axios/vue-router/Redux/Vuex
为什么要看源码??
- 初级前端就会用vue或react — 从差不多水平的60%中挑出更好的人
- 提高思想–》看优秀的代码–》写优秀的代码
- 看源码能力,对高级前端是必备的。— 解决疑难杂症,看源码了解原理。
vue 性能优化
因为是公开课,所以时间上没来的及说完,以后自己在听别的有关的内容时再补上这块。
最后
只有不断学习才能进步,充分利用网络的便利性,找各种优质的教学资源,我相信,努力会有回报,加油!