简化版本Vue总结我们都知道Vue
是一个渐进式
的框架 ,它有两大核心:(数据驱动
组件系统
),这里将提供数据驱动
的一些思路,组件系统
将在之后探讨。
思路我们都知道,Vue是一个MVVM的框架,这里所谓的MVVM,其实说的是M(model)-V(view)-VM(V–M) [数据模型 - 视图层 - 视图层之间的桥梁],其中最为重要的 VM
就是Vue
的核心——数据驱动
. 要实现这一点,我们就有一下几大难点:
监测值
(diff算法)监测值
也就是我们需要绑定的对象也或者是值,我们要如何去监测值是否发生了变化,我们或许有时候需要拦截这种改变,进行一些加工的操作。视图
虚拟Dom
节点
核心关键点Object.defineProperty
可以对对象的 getter
、setter
进行设置,所谓 getter
、setter
就是就是指获取对象的某个值的时候触发什么,设置某个值的时候自动触发什么,熟悉 java
的朋友应该知道这玩意。
其中 set
和 get
方法就起到了一个监测值改变的作用,这里单独拿出来分析一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Object .defineProperty (this .$data , key, { configurable : false , enumerable : false , get ( ) { return currentVal; }, set (changeVal ) { if (changeVal !== currentVal) { currentVal = changeVal; watcherObj.forEach ((element ) => { element.update (); }); }
再举一个例子(针对get
):
1 2 3 4 5 6 7 8 9 10 11 12 let obj = {}; Object .defineProperty ( obj , 'key' ,{ get ( ){ console .log ("key say 你在调动我!! " ); return "我来当做当前的值>(#'.')'>" ; } }) console .log (obj.key )
(针对 set
)
1 2 3 4 5 6 7 8 9 10 11 let obj = {}; Object .defineProperty ( obj , 'key' ,{ get (newVal ){ console .log ("key say 你在改变我>(#'.')'>" ); } }) obj.key = 12
描述符可拥有键值configurable
enumerable
value
writable
get
set
数据描述符 可以 可以 可以 可以 不可以 不可以 存取描述符 可以 可以 不可以 不可以 可以 可以
以上的含义:
1 2 3 4 5 6 7 8 let obj = {};Object .defineProperty ( obj , "newKey" , { get :function ( ){}|undefined , set :function ( ){}|undefined , configurable : true | false , enumable : true | false , writable : true | false } )
下面用一张图来表示
有了以上的基础我们可以开始为每一个子节点绑定上对应的属性点:也就是下面的observe部分:
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 observe ( ) { for (let key in this .$data ) { let currentVal = this .$data [key]; this .watcherObj [key] = []; let watcherObj = this .watcherObj [key]; Object .defineProperty (this .$data , key, { configurable : false , enumerable : false , get ( ) { return currentVal; }, set (changeVal ) { if (changeVal !== currentVal) { currentVal = changeVal; watcherObj.forEach ((element ) => { element.update (); }); } }, }); }
第二部分就是重构节点,这里只是对自定义组件进行一个探索,对于插值语法日后再论:
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 compile (el ) { let children = el.children ; for (let i = 0 ; i < children.length ; i++) { let child = children[i]; if (child.children .length > 0 ) { this .compile (child); } if (child.hasAttribute ("cVue-model" )) { let modelVal = child.getAttribute ("cVue-model" ); child.addEventListener ( "input" , ((i )=> { this .watcherObj [modelVal].push ( new viewUpdate (this , child, "value" , modelVal) ); return () => { this .$data [modelVal] = children[i].value ; }; })(i)); } } }
在compile中我们遍历了所有的子节点,然后对自定义事件属性的一个绑定,难度不是很高,
第三个部分(视图更新)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class viewUpdate { constructor (...args ) { this .vm = args[0 ]; this .el = args[1 ]; this .attr = args[2 ]; this .val = args[3 ]; this .update (); } update ( ) { if (this .vm .$data [this .val ] === true ) { this .el .style .display = "block" ; } else if (this .vm .$data [this .val ] === false ) { this .el .style .display = "none" ; } else { this .el [this .attr ] = this .vm .$data [this .val ]; } } }
这一部分的话,总的来说也没有什么关键点,就是对v-if的一个简单更新,当然Vue会比这个复杂的多
总结数据的绑定:Object.defineProperty 视图的更新,改变对应的值,触发 set 做出对应的改变 附上简化版本的 Vue ,当然(Vue
)会比这个复杂,这个只是在DOM下的一个做出响应这一部分的内容 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 class cVue { constructor (options ) { this .$el = document .querySelector (options.el ); this .$data = options.data ; this .$methods = options.methods ; this .watcherObj = {}; this .observe (); this .compile (this .$el ); } observe ( ) { for (let key in this .$data ) { let currentVal = this .$data [key]; this .watcherObj [key] = []; let watcherObj = this .watcherObj [key]; Object .defineProperty (this .$data , key, { configurable : false , enumerable : false , get ( ) { return currentVal; }, set (changeVal ) { if (changeVal !== currentVal) { currentVal = changeVal; watcherObj.forEach ((element ) => { element.update (); }); } }, }); } } compile (el ) { let children = el.children ; for (let i = 0 ; i < children.length ; i++) { let child = children[i]; if (child.children .length > 0 ) { this .compile (child); } if (child.hasAttribute ("cVue-on:click" )) { let eventVal = child.getAttribute ("cVue-on:click" ); child.addEventListener ( "click" , this .$methods [eventVal].bind (this .$data ) ); } if (child.hasAttribute ("cVue-if" )) { let ifVal = child.getAttribute ("cVue-if" ); this .watcherObj [ifVal].push (new viewUpdate (this , child, "" , ifVal)); } if (child.hasAttribute ("cVue-model" )) { let modelVal = child.getAttribute ("cVue-model" ); child.addEventListener ( "input" , ((i )=> { this .watcherObj [modelVal].push ( new viewUpdate (this , child, "value" , modelVal) ); return () => { this .$data [modelVal] = children[i].value ; }; })(i)); } if (child.hasAttribute ("cVue-text" )) { let textVal = child.getAttribute ("cVue-text" ); this .watcherObj [textVal].push ( new viewUpdate (this , child, "innerText" , textVal) ); } } } } class viewUpdate { constructor (...args ) { this .vm = args[0 ]; this .el = args[1 ]; this .attr = args[2 ]; this .val = args[3 ]; this .update (); } update ( ) { if (this .vm .$data [this .val ] === true ) { this .el .style .display = "block" ; } else if (this .vm .$data [this .val ] === false ) { this .el .style .display = "none" ; } else { this .el [this .attr ] = this .vm .$data [this .val ]; } } }