问题一:MVVM框架中编译数据到视图
-
编译数据到视图的原理(JS原生方法理解)
-
创建可实例化的对象(对象内部接受指定的参数,及自身的方法)
-
视图当中采用指定的形式进行对象的创建以及模板语法
-
实例化对象当中实现以下方法(函数)功能:
- 模板语法的查找与解构
- 数据的解析与定向赋值
<body> <div id="app"> {{message}} <div> {{message}} </div> </div> </body> <script> let vm = new Vue({ el: "#app", data: { message: "测试数据" } }) </script>class Vue { constructor(opts) { this.opts = opts; this._data = this.opts.data; this.compile(); } // 标签循下的文本采用递归查找与替换的方法进行数据的渲染 compile() { let ele = document.querySelector(this.opts.el); let childNodes = ele.childNodes; //将查找和插入分离出来,构写新的的方法,以便后续的方法自身递归调用 this.compileNode(childNodes); } compileNode(childNodes) { childNodes.forEach((node) => { if (node.nodeType === 3) { //获取该文本节点下的内容数据 let textContent = node.textContent; //使用正则匹配进行文本数据的拆分,获取相应的字段信息 let reg = /{{\s*([^{}\s*]+)\s*}}/g; if (reg.test(textContent)) { let $1 = RegExp.$1; console.log($1); //将获取到的属性进行值的替换 node.textContent = node.textContent.replace(reg, this._data[$1]); } } else if (node.nodeType === 1) {//如果当前节点为标签节点 if (node.childNodes.length > 0) { //判断子节点是否存在 this.compileNode(node.childNodes); //存在则进行函数递归查询,重新执行直到解析完毕 } } }); } } -
问题二:实现数据驱动视图更新的底层对象Object的defineProperty方法(原理解读)
-
Object.defineProperty()能够做什么或有什么作用与特性
-
直接在一个对象上定义一个新属性
-
能够修改一个对象的现有属性,并返回此对象
应当直接在Object构造器对象上调用此方法,而不是在任意一个Object类型的实例上调用
//修改一个已定义的对象现有属性的方法(代码实践) const object1 = {}; Object.defineProperty(object1,"name",{ value: 42, writable: false //是否可修改 }) object1.name = 77; //writable为false时,不能改变其值 console.lob(object1.name) //42该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for……in或Object.keys方法),可以改变属性值、删除属性。
默认情况下,使用Object.defineProperty()添加的属性值是不可修改(immutable)的
对象里目前存在有:数据描述符(具有值的属性,可写可不写)和存取描述符(由getter和setter两个函数所描述的属性) 。描述符(对象形式)只能两者选其一
-
-
共享以下可选键值: configurable(值为true时,该属性描述符除
value和writable特性外的其他特性能够被改变,该属性能从对应的对象上被删除)、enumerable(值为true时,该属性会在出现的对象的枚举属性中)、 -
数据描述符还具有以下可选键值: value、writable(值为true时,value能被赋值运算符改变
-
存取描述符还具有以下可选键值: get(属性的getter函数,没有则为undefined。访问该属性,调用此函数。执行时会传入this对象。返回值会被用作属性的值。)、set(属性的setter函数,没有则为undefined。同上……)
如果一个描述符不具有value、writable、get和set中的任意一个键,则被认为是一个数据描述符,一项选项不一定是自身属性,也可能来自继承的属性,在设置之前可使用Object.prototype进行冻结。
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则 enumerable 及其他属性值均为 true
-
实现数据驱动视图时Object.defineProperty()的作用:
在进行数据的获取和数据的更改时会触发Object.defineProperty()方法的get和set属性
//更改对象的类型,实现数据的更改 Object.defineProperty(obj, "name", { configurable: true, enumerable: true, get() { console.log("获取name时采用get??"); return "李四" }, set(newValue) { console.log("触发set方法进行值修改", newValue); } }) // console.log(obj); console.log(obj.name); obj.name = "王五" //改变name的值是否调用set方法 // 总结:当一个对象是Object.defineProperty形式创建的时候, // 进行值获取和值改变的时候会触发get和set方法 -
通过Object.defineProperty()+extends EventTarget(自定义事件)实现数据驱动视图更新
<body> <div id="app"> {{message}} </div> </body> <script> let vm = new Vue({ el: "#app", data: { message: "测试数据" } }) console.log(vm._data); setTimeout(function () { console.log("修改了数据"); vm._data.message = "修改了数据" }, 1000) </script>class Vue extends EventTarget { //添加事件基础 extends EventTarget constructor(opts) { super(); //事件继承关联 this.opts = opts; this._data = this.opts.data; // 调用observe方法 this.observe(this._data); this.compile(); } // 创建一个具备get和set属性方法的对象,触发事件进行数据的获取与改变、视图的重新渲染 observe(data) { for (let key in data) { let _this = this; //将this提前保存 let value = data[key]; //提起将枚举到的data[key]值进行保存,防止在内部的递归 //构造Object对象,调用defineProperty方法 Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { console.log('get……'); return value; }, set(newValue) { console.log('set……'); //当数据放生更改的时候希望能够同时更新视图,调用方法(自定义事件) // 触发自定义事件更新 let event = new CustomEvent(key, { detail: newValue, //detail是干什么的 }); // this.dispatchEvent(event); //报错,this指向发生改变 _this.dispatchEvent(event); value = newValue; }, }); } } // 标签循下的文本采用递归查找与替换的方法进行数据的渲染 compile() { let ele = document.querySelector(this.opts.el); let childNodes = ele.childNodes; this.compileNode(childNodes); } compileNode(childNodes) { childNodes.forEach((node) => { if (node.nodeType === 3) { let textContent = node.textContent; let reg = /\{\{\s*([^\{\}\s*]+)\s*\}\}/g; if (reg.test(textContent)) { let $1 = RegExp.$1; // console.log($1); node.textContent = node.textContent.replace(reg, this._data[$1]); //绑定自定义事件 关键字 key this.addEventListener($1, (e) => { // console.log('触发了更新', e); //获取自定义事件e对象的返回值,detail进行的值存取 //自定义事件触发后,进行视图数据更新 let newValue = e.detail; let oldValue = this._data[$1]; let updateReg = new RegExp(oldValue); node.textContent = node.textContent.replace(updateReg, newValue); }); } } else if (node.nodeType === 1) { if (node.childNodes.length > 0) { this.compileNode(node.childNodes); } } }); } }