2020年11月20日
响应式原理(3)- vue数据响应式处理(极简版)
- 将模板生成带参数的虚拟dom树(vue生成的是AST)
- 将其dom树数据转化为响应式数据【示例中原数据为数组类型直接赋值对象类型数据,原数组不是一个响应式数据,所以赋值时出现问题。vue是用的是Watcher监听处理】
- 将虚拟dom树生成真实的dom(html)渲染到页面上【本示例是数据改变则全局刷新,vue其实是才用diff进行虚拟dom比较找出差异进行局部渲染】
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root" title="aaa"> <p>{{name}}:{{message.info}}</p> <p>{{name}}</p> <p>{{message.info}}</p> </div> <script> function JGVue(option){ this._data = option.data; this._el = document.querySelector(option.el); this._parent = this._el.parentNode; reactify(this._data, this); // 挂载 this.mount(); } JGVue.prototype.mount = function(){ this.render = this.createRenderFn(); this.mountComponent(); } JGVue.prototype.mountComponent = function () { // 执行 mountComponent() 函数 // 挂载组件(需使用发布订阅模式,渲染和计算的行为交给watcher完成) let mount = () => { this.update( this.render() ) } mount.call( this ); // 本质应该交给 watcher 来调用, 但是还没有讲到这里 } JGVue.prototype.createRenderFn = function(){ let ast = getVnode(this._el); return function render(){ // 带数据的 ast let _tmp = combine( ast, this._data ); return _tmp; } } JGVue.prototype.update = function (vnode) { // 简化, 直接生成 HTML DOM replaceChild 到页面中 // 父元素.replaceChild(新,旧) let newDom = parseDom (vnode); this._parent.replaceChild(newDom,document.querySelector('#root'));//会将页面的DOM全部替换,vue是使用diff算法进行判断增删改。因为每次全部替换即每次需重新去获取跟元素 } class VNode{ constructor(tag, attr, value, type){ this.tag = tag && tag.toLowerCase(); this.attr = attr; this.value = value; this.type = type; this.children = []; } getData(data){ this.children.push(data); } } function getVnode(el){ var type = el.nodeType; var tag,value; var attr = {}; var _vNode = null; if(type == 1){ // 元素节点 tag = el.nodeName; var attrs = el.attributes; for(var i = 0;i<attrs.length;i++){ attr[ attrs[i].nodeName ] = attrs[i].nodeValue; } _vNode = new VNode(tag, attr, value, type); var childNode = el.childNodes; for(var i=0;i<childNode.length;i++){ _vNode.getData( getVnode(childNode[i]) ); } }else if(type == 3){ // 文本节点 value = el.nodeValue; _vNode = new VNode(tag, attr, value, type); } return _vNode; } var rz = /\{\{(.+?)\}\}/g; function combine(vnode, data){ var tag = vnode.tag; var attr = vnode.attr; var value = vnode.value; var type = vnode.type; var children = vnode.children; var _vNode = null; if(type == 1){ // 元素节点 _vNode =new VNode(tag, attr, value, type); for(var i=0;i<children.length;i++){ _vNode.getData( combine(children[i], data) ) } }else if(type == 3){ // 文本节点 var res = value.trim().replace(rz, function(_,g){ return getValueByPath(data, g); }) _vNode =new VNode(tag, attr, res, type); } return _vNode; } function getValueByPath(obj, path){ var arr = path.split('.'); let res = obj; let prop; // arr.shift()取出数组的第一个值 while( prop = arr.shift() ){ res = res[ prop ]; } return res; } function parseDom (res){ let tag = res.tag; let type = res.type; let value = res.value; let attr = res.attr; let children = res.children; if(type == 1){ if(tag != undefined){ var ele = document.createElement(tag); } if(attr != undefined){ for(var i in attr){ ele.setAttribute(i,attr[i]); } } if(children.length != 0){ for(var i=0;i<children.length;i++){ val = ele.appendChild(parseDom(children[i])); } } }else if(type == 3){ var ele = document.createTextNode(value); } return ele; } // 数组方法拦截开始 let ARRAY_METHOD = ['push']; let array_methods = Object.create(Array.prototype); // 3 循环需扩展的数组方法名,并创建一个方法 ARRAY_METHOD.forEach(method => { array_methods[ method ] = function (){ console.log('调用拦截'); // 变为响应式数据 for(var i=0;i<arguments.length;i++){ reactify(arguments[i] )//有缺陷,wacher可解决 } // 调用原有的方式 let res = Array.prototype[ method ].apply(this , arguments); return res; } }) // 数组方法拦截结束 // 简化版本 function defineReactive(target, key, value, enumerable){ // value 为函数内部局部作用域(闭包,解决数据访问安全问题) var that = this; if(typeof value === 'object' && value!= null && !Array.isArray(value)){ reactify(value,that); } Object.defineProperty(target, key, { configurable: true,//设置属性是否可以被删除,属性是否可更改 // Writable:设置属性是否是可写的。Writable和set、get同时使用 enumerable: !!enumerable,//是否可枚举(使用for...in遍历是是否能访问到键) set(newValue){ if(typeof newValue === 'object' && newValue!= null && !Array.isArray(newValue)){ reactify(newValue); }else if(Array.isArray(newValue)){ for(var i=0;i<newValue.length;i++){ reactify(newValue[i]); } } value = newValue; // 实例化对象获取。vue中是watcher实现不存在获取不到实例化对象;现目前使用其他办法解决(调用时将实例化对象当参数传入) console.log(that) that.mountComponent() }, get(){ return value; } }) } //不完整 function reactify(data,vm){ let keys = Object.keys(data); for(var i=0;i<keys.length;i++){ let key = keys[i]; let val = data[key]; if(Array.isArray(val)){ // 数组类型 val.__proto__ = array_methods;//数组响应式方法调用 for(var j=0;j<val.length;j++){ reactify(val[j],vm); } }else{ // 引用类型 defineReactive.call(vm, data, key, val, true); } } } let app = new JGVue({ el: '#root', data: { name: '肉肉', message: { info: '还是嘎嘎' } } }) </script> </body> </html>