Vue 之数据双向绑定

    xiaoxiao2022-07-02  98

    基础概念 先打点基础 方便后面理解

    [].slice.call(lis): 将伪数组转换为真数组 node.nodeType: 得到节点类型 Object.defineProperty(obj, propertyName, {}): 给对象添加属性(指定描述符) Object.keys(obj): 得到对象自身可枚举属性组成的数组 obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性 DocumentFragment: 文档碎片(高效批量更新多个节点)

     理解第二条 node.nodeType

     

    第3条  Object.defineProperty(obj, propertyName, {}): 给对象添加属性(指定描述符)

    //3. Object.defineProperty(obj, propertyName, {}): 给对象添加属性(指定描述符) const obj = { firstName: 'A', lastName: 'B' } //obj.fullName = 'A-B' Object.defineProperty(obj, 'fullName', { // 属性描述符: // configurable 是否可以重新定义 // emumerable 是否可以枚举 // value 初始值 // writable 是否可以修改属性值 // 数据描述符 //访问描述符 // 当读取对象此属性值时自动调用, 将函数返回的值作为属性值, this为obj get () { return this.firstName + "-" + this.lastName }, // 当修改了对象的当前属性值时自动调用, 监视当前属性值的变化, 修改相关的属性, this为obj set (value) { const names = value.split('-') this.firstName = names[0] this.lastName = names[1] } }) console.log(obj.fullName) // A-B obj.fullName = 'C-D' console.log(obj.firstName, obj.lastName) // C D

     

    //5. obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性 console.log(obj.hasOwnProperty('fullName'), obj.hasOwnProperty('toString')) // true false

    批量更新节点的方法

    //6. DocumentFragment: 文档碎片(高效批量更新多个节点) // document: 对应显示的页面, 包含n个elment 一旦更新document内部的某个元素界面更新 // documentFragment: 内存中保存n个element的容器对象(不与界面关联), 如果更新framgnet中的某个element, 界面不变 /* <ul id="fragment_test"> <li>test1</li> <li>test2</li> <li>test3</li> </ul> */ const ul = document.getElementById('fragment_test') // 1. 创建fragment const fragment = document.createDocumentFragment() // 2. 取出ul中所有子节点取出保存到fragment let child while(child=ul.firstChild) { // 一个节点只能有一个父亲 fragment.appendChild(child) // 先将child从ul中移除, 添加为fragment子节点 } // 3. 更新fragment中所有li的文本 Array.prototype.slice.call(fragment.childNodes).forEach(node => { if (node.nodeType===1) { // 元素节点 <li> 因为空格也算节点 但是我们并不需要 node.textContent = 'hello world' } }) // 4. 将fragment插入ul ul.appendChild(fragment) //此方法接受一个node 参数 而node是接口

    首先我们下载  github 上某基友仿 vue 实现的 mvvm 库  地址:https://github.com/DMQ/mvvm 

     1 数据代理

    1. vue数据代理: data对象的所有属性的操作(读/写)由vm对象来代理操作 2. 好处: 通过vm对象就可以方便的操作data中的数据 3. 实现: 1). 通过Object.defineProperty(vm, key, {})给vm添加与data对象的属性对应的属性 2). 所有添加的属性都包含get/set方法 3). 在get/set方法中去操作data中对应的属性 const vm = new MVVM({ el: "#test", data: { name: '张三2' } }) console.log(vm.name) //张三2 读取的是data中的name, vm代理对data的读操作 vm.name = '李四2' // 数据保存到data中的name上, vm代理对data的写操作 console.log(vm.name, vm._data.name)//李四2 李四2 */ function MVVM(options) { // 将选项对象保存到vm this.$options = options; // 将data对象保存到vm和datq变量中 var data = this._data = this.$options.data; //将vm保存在me变量中 var me = this; // 遍历data中所有属性 Object.keys(data).forEach(function (key) { // 属性名: name // 对指定属性实现代理 me._proxy(key); }); } // 对指定属性实现代理 _proxy: function (key) { // 保存vm var me = this; // 给vm添加指定属性名的属性(使用属性描述) Object.defineProperty(me, key, { configurable: false, // 不能再重新定义 enumerable: true, // 可以枚举 // 当通过vm.name读取属性值时自动调用 get: function proxyGetter() { // 读取data中对应属性值返回(实现代理读操作) return me._data[key]; }, // 当通过vm.name = 'xxx'时自动调用 set: function proxySetter(newVal) { // 将最新的值保存到data中对应的属性上(实现代理写操作) me._data[key] = newVal; } }); }

     2 模板解析

    function MVVM(options) { // 数据代理 // 将选项对象保存到vm this.$options = options; // 将data对象保存到vm和datq变量中 var data = this._data = this.$options.data; //将vm保存在me变量中 var me = this; // 遍历data中所有属性 Object.keys(data).forEach(function (key) { // 属性名: name // 对指定属性实现代理 me._proxy(key); }); // 对data进行监视 observe(data, this); // 模板解析 // 创建一个用来编译模板的compile对象 this.$compile = new Compile(options.el || document.body, this) }

    我们来看一下Compile这个模板解析对象,首先获取到传入的el元素 

    function Compile(el, vm) { // 保存vm this.$vm = vm; // 保存el元素 this.$el = this.isElementNode(el) ? el : document.querySelector(el); // 如果el元素存在 if (this.$el) { // 1. 取出el中所有子节点, 封装在一个framgment对象中 this.$fragment = this.node2Fragment(this.$el); // 2. 编译fragment中所有层次子节点 this.init(); // 3. 将fragment添加到el中 this.$el.appendChild(this.$fragment); } } node2Fragment: function (el) { var fragment = document.createDocumentFragment(), child; // 将原生节点拷贝到fragment while (child = el.firstChild) { fragment.appendChild(child); } return fragment; } init: function () { // 编译fragment this.compileElement(this.$fragment); }, compileElement: function (el) { // 得到所有子节点 var childNodes = el.childNodes, // 保存compile对象 me = this; // 遍历所有子节点 [].slice.call(childNodes).forEach(function (node) { // 得到节点的文本内容 var text = node.textContent; // 正则对象(匹配大括号表达式) var reg = /\{\{(.*)\}\}/; // {{name}} // 如果是元素节点 if (me.isElementNode(node)) { // 编译元素节点的指令属性 me.compile(node); // 如果是一个大括号表达式格式的文本节点 } else if (me.isTextNode(node) && reg.test(text)) { // 编译大括号表达式格式的文本节点 me.compileText(node, RegExp.$1); // RegExp.$1: 表达式 name } // 如果子节点还有子节点 if (node.childNodes && node.childNodes.length) { // 递归调用实现所有层次节点的编译 me.compileElement(node); } }); }

    判断是否是指令属性 还是普通属性 进行编译

    compile: function (node) { // 得到所有标签属性节点 var nodeAttrs = node.attributes, me = this; // 遍历所有属性 [].slice.call(nodeAttrs).forEach(function (attr) { // 得到属性名: v-on:click var attrName = attr.name; // 判断是否是指令属性 if (me.isDirective(attrName)) { // 得到表达式(属性值): test var exp = attr.value; // 得到指令名: on:click var dir = attrName.substring(2); // 事件指令 if (me.isEventDirective(dir)) { // 解析事件指令 compileUtil.eventHandler(node, me.$vm, exp, dir); // 普通指令 } else { // 解析普通指令 compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); } // 移除指令属性 node.removeAttribute(attrName); } }); }

    compileUtil 

    // 指令处理集合 var compileUtil = { // 解析: v-text/{{}} text: function (node, vm, exp) { this.bind(node, vm, exp, 'text'); }, // 解析: v-html html: function (node, vm, exp) { this.bind(node, vm, exp, 'html'); }, // 解析: v-model model: function (node, vm, exp) { this.bind(node, vm, exp, 'model'); var me = this, val = this._getVMVal(vm, exp); node.addEventListener('input', function (e) { var newValue = e.target.value; if (val === newValue) { return; } me._setVMVal(vm, exp, newValue); val = newValue; }); }

     

    // 真正用于解析指令的方法 bind: function (node, vm, exp, dir) { /*实现初始化显示*/ // 根据指令名(text)得到对应的更新节点函数 var updaterFn = updater[dir + 'Updater']; // 如果存在调用来更新节点 updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 创建表达式对应的watcher对象 new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/ // 当对应的属性值发生了变化时, 自动调用, 更新对应的节点 updaterFn && updaterFn(node, value, oldValue); }); } // 包含多个用于更新节点方法的对象 var updater = { // 更新节点的textContent textUpdater: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }, // 更新节点的innerHTML htmlUpdater: function (node, value) { node.innerHTML = typeof value == 'undefined' ? '' : value; }, // 更新节点的className classUpdater: function (node, value, oldValue) { var className = node.className; className = className.replace(oldValue, '').replace(/\s$/, ''); var space = className && String(value) ? ' ' : ''; node.className = className + space + value; }, // 更新节点的value modelUpdater: function (node, value, oldValue) { node.value = typeof value == 'undefined' ? '' : value; } }

     数据双向绑定

     

     

     

     

     

     

    最新回复(0)