vue源码分析参考---1、准备工作
一、总结
一句话总结:
可以直接剖析 github 上某基友仿 vue 实现的 mvvm 库(https://github.com/DMQ/mvvm),用这个分析vue的原理更加简单,弄懂这个再去看vue源码会很简单
1、分析vue源码 策略?
可以直接剖析 github 上某基友仿 vue 实现的 mvvm 库(https://github.com/DMQ/mvvm),用这个分析vue的原理更加简单,弄懂这个再去看vue源码会很简单
2、[].slice.call(lis)?
将伪数组转换为真数组
//1. [].slice.call(lis): 根据伪数组生成对应的真数组 const lis = document.getElementsByTagName('li') // lis是伪数组(是一个特别的对象, length和数值下标属性) console.log(lis instanceof Object, lis instanceof Array) // 数组的slice()截取数组中指定部分的元素, 生成一个新的数组 [1, 3, 5, 7, 9], slice(0, 3) // slice2() Array.prototype.slice2 = function (start, end) { start = start || 0 end = start || this.length const arr = [] for (var i = start; i < end; i++) { arr.push(this[i]) } return arr } const lis2 = Array.prototype.slice.call(lis) // lis.slice() console.log(lis2 instanceof Object, lis2 instanceof Array) // lis2.forEach()
3、node.nodeType?
得到节点类型
//2. node.nodeType: 得到节点类型 const elementNode = document.getElementById('test') const attrNode = elementNode.getAttributeNode('id') const textNode = elementNode.firstChild console.log(elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
4、Object.defineProperty(obj, propName, {})?
给对象添加/修改属性(指定描述符) configurable: true/false 是否可以重新 define
enumerable: true/false 是否可以枚举(for..in / keys()) value: 指定初始值 writable: true/false value 是否可以修改 get: 回调函数, 用来得到当前属性值 set: 回调函数, 用来监视当前属性值的变化 //3. Object.defineProperty(obj, propertyName, {}): 给对象添加属性(指定描述符) const obj = { firstName: 'A', lastName: 'B' } //obj.fullName = 'A-B' Object.defineProperty(obj, 'fullName', { // 属性描述符: // 数据描述符 //访问描述符 // 当读取对象此属性值时自动调用, 将函数返回的值作为属性值, 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 Object.defineProperty(obj, 'fullName2', { configurable: false, //是否可以重新define enumerable: true, // 是否可以枚举(for..in / keys()) value: 'A-B', // 指定初始值 writable: false // value是否可以修改 }) console.log(obj.fullName2) // A-B obj.fullName2 = 'E-F' console.log(obj.fullName2) // A-B /*Object.defineProperty(obj, 'fullName2', { configurable: true, enumerable: true, value: 'G-H', writable: true })*/
5、Object.keys(obj)?
得到对象自身可枚举的属性名的数组
//4. Object.keys(obj): 得到对象自身可枚举属性组成的数组 const names = Object.keys(obj) console.log(names)
6、obj.hasOwnProperty(prop)?
判断 prop 是否是 obj 自身的属性
//5. obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性 console.log(obj.hasOwnProperty('fullName'), obj.hasOwnProperty('toString')) // true false
7、DocumentFragment?
文档碎片(高效批量更新多个节点)
//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 = 'atguigu' } }) // 4. 将fragment插入ul ul.appendChild(fragment)
8、使用DocumentFragment(文档碎片)高效批量更新多个节点 步骤?
1、创建fragment
2、取出ul中所有子节点取出保存到fragment
3、更新fragment中所有li的文本
4、将fragment插入ul
//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 = 'atguigu' } }) // 4. 将fragment插入ul ul.appendChild(fragment)
二、数据代理和模板解析
博客对应课程的视频位置:
1. 说明
1) 分析 vue 作为一个 MVVM 框架的基本实现原理数据代理
模板解析
数据绑定
2) 不直接看 vue.js 的源码
3) 剖析 github 上某基友仿 vue 实现的 mvvm 库
4) 地址: https://github.com/DMQ/mvvm
2. 准备知识
1) [].slice.call(lis): 将伪数组转换为真数组
2) node.nodeType: 得到节点类型
3) Object.defineProperty(obj, propName, {}): 给对象添加/修改属性(指定描述符) configurable: true/false 是否可以重新 define
enumerable: true/false 是否可以枚举(for..in / keys())
value: 指定初始值
writable: true/false value 是否可以修改
get: 回调函数, 用来得到当前属性值
set: 回调函数, 用来监视当前属性值的变化
4) Object.keys(obj): 得到对象自身可枚举的属性名的数组
5) DocumentFragment: 文档碎片(高效批量更新多个节点)
6) obj.hasOwnProperty(prop): 判断 prop 是否是 obj 自身的属性
3. 数据代理
1) 数据代理: 通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写)
2) vue 数据代理: 通过 vm 对象来代理 data 对象中所有属性的操作
3) 好处: 更方便的操作 data 中的数据
4) 基本实现流程
a. 通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符
b. 所有添加的属性都包含 getter/setter
c. getter/setter 内部去操作 data 中对应的属性数据
4. 模板解析
4.1. 模板解析的基本流程
1) 将 el 的所有子节点取出, 添加到一个新建的文档 fragment 对象中
2) 对 fragment 中的所有层次子节点递归进行编译解析处理
* 对大括号表达式文本节点进行解析
* 对元素节点的指令属性进行解析
* 事件指令解析
* 一般指令解析
3) 将解析后的 fragment 添加到 el 中显示
4.2. 模板解析(1): 大括号表达式解析
1) 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1 name
2) 从 data 中取出表达式对应的属性值
3) 将属性值设置为文本节点的 textContent
4.3. 模板解析(2): 事件指令解析
1) 从指令名中取出事件名
2) 根据指令的值(表达式)从 methods 中得到对应的事件处理函数对象
3) 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
4) 指令解析完后, 移除此指令属性
4.4. 模板解析(3): 一般指令解析
1) 得到指令名和指令值(表达式) text/html/class msg/myClass
2) 从 data 中根据表达式得到对应的值
3) 根据指令名确定需要操作元素节点的什么属性
* v-text---textContent 属性
* v-html---innerHTML 属性
* v-class--className 属性
4) 将得到的表达式的值设置到对应的属性上
5) 移除元素的指令属性