理解第二条 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
我们来看一下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; } }