-
编译模板 —— 将模板中的
{{变量}}
替换成对应的数值。 -
例如:
<div> <h1>{{title}}</h1> <p>{{article}}</p> </div>
通过js进行操作将其中的
title
和article
替换为真正的值。
Compile(el, this);
/**
* el: 文本节点
* vm: 传入的数据 data
*
*/
function Compile(el, vm) {
// 获取dom
vm.$el = documentSelector(el)
// 将文本节点放在内存中处理
let fragment = document.createDocumentFragment()
let child;
whild(child = vm.$el.firstChild) {
fragment.appendChild(child)
}
// 执行替换方法
replace(fragment)
}
- 方法中
createDocumentFragment()
因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
-
使用正则找到每个dom节点,替换数据
/* * fragment 虚拟dom 树 */ replace(fragment) { Array.from(fragment).forEach(function(node) { let text = node.textContent; let reg = /\{\{(.*)\}\}/; // 找到node中符合{{key}}这样的模板 if (reg.test(text) && node.nodeType === 3) { let arr = RegExp.$1.splite('.') let res = vm arr.forEach(key => { res = res[key] }) node.textContent = text.replace(/\{\{(.*\}\}/, res) } // 如果节点下存在子节点 if(node.childNodes) { replace(node) } }) // 所有的节点替换完之后 将其挂在到树的父节点处 vm.$el.appendChild(fragment) }
-
附:
index.html
和vue.js
-
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MVVM 数据劫持</title> </head> <script src="./vue.js"></script> <body> <div id="app"> {{name}} <p>{{age}}</p> <span>微笑 </span> <div> <p>title: {{home.address}}</p> <p>number: {{home.num}}</p> </div> </div> </body> <script> let data = { name: 1, age: 23, home: { address: "aqweqwe", num: 333 } } let vue = new Vue({ el: '#app', data }) console.log(vue) </script> </html>
-
vue.js
function Vue(options = {}) { this.$options = options; let data = (this._data = this.$options.data); observe(data); for (let key in data) { Object.defineProperty(this, key, { enumerable: true, get () { return this._data[key] }, set (newVal) { this._data[key] = newVal } }) } new Compile(options.el, this) } function Compile (el, vm) { vm.$el = document.querySelector(el) console.log('el', vm.$el); console.log('el', vm.$el.firstChild); // 放在内存中的 html 片段 let fragment = document.createDocumentFragment() console.log(vm.$el.firstChild); let child; while (child = vm.$el.firstChild) { // 将app中的内容放在内存中 fragment.appendChild(child) } replace(fragment); function replace(fragment) { Array.from(fragment.childNodes).forEach(node => { let text = node.textContent // 匹配 {{ }} let reg = /\{\{(.*)\}\}/; let flag = reg.test(text); if(flag && node.nodeType === 3) { console.log(12, text); console.log(RegExp.$1); let arr = RegExp.$1.split('.'); let obj = vm; arr.forEach(function(k) { console.log(1111, k); console.log(obj[k]); obj = obj[k] }) node.textContent = text.replace(/\{\{(.*)\}\}/, obj) } if (node.childNodes) { replace(node) } }); } vm.$el.appendChild(fragment) } function observe(data) { if (typeof data !== Object) return return new Observe(data); } // 观察对象 给对象增加Object.defineproprety function Observe(data) { // 写主要的逻辑 // 业务是 将每个data上的属性都绑定 Object.defineproperty方法 Object.keys(data).forEach(key => { let val = data[key]; console.log('--------', val); observe(val); Object.defineProperty(data, key, { enumerable: true, set(newVal) { if (newVal == val) return val = newVal }, get() { return val } }) }) }
-
-
总结
- 创建一个新的空白的文档片段createDocumentFragment()
,也叫虚拟dom。
- 通过递归将这些node节点中的模板信息更改为data
中定义的值
-