投资建设集团网站首页,微信小程序外联网站,宁波网站建设服务服务商,网站模版配置数据库目录一、MVVM模型二、内在1. 深入响应式原理2. Object.entries3. 底层搭建一、MVVM模型
MVVM#xff0c;即Model 、View、ViewModel。
Model data数据
view 视图#xff08;vue模板#xff09;
ViewModel vm vue 返回的实例 控制中心, 负责监听…
目录一、MVVM模型二、内在1. 深入响应式原理2. Object.entries3. 底层搭建一、MVVM模型
MVVM即Model 、View、ViewModel。
Model data数据
view 视图vue模板
ViewModel vm vue 返回的实例 控制中心, 负责监听Model的数据进行改变并且控制视图的更新 vue渲染流程 1. vue 拿到挂载中的所有节点 2. vue 取data中的数据插入到带有vue指令特殊的vue符号中 3. vue 将数据插入成功的元素放回到页面中
二、内在
1. 深入响应式原理
如何追踪变化 当把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项Vue 将遍历此对象所有的 property并使用 Object.defineProperty把这些 property 全部转为 getter/setter。
例监听对象的变化
script
// 方法Object.defineProperty(监听对象,对象的属性名,{配置对象})
let data {age:38,
}
// 定义一个变量
let _data null;
Object.defineProperty(data,age,{get(){console.log(get--data.age取值的时候触发);return _data},set(val){console.log(set--设置data.age的时候触发);_data val}
})
/script使用Object.defineProperty方法函数内部把属性转化为get/setget()函数在函数取值时触发set()函数在设值时触发通过在外部定义变量在get()函数内部返回该变量在set()内部将设的值赋值给该外部变量从而实现监听。 对象之间的关联原对象与代理对象
script// 关联代理 let data {age: 38,}// 定义一个变量let _data {};Object.defineProperty(_data, age, {get() {return data.age},set(val) {data.age val}})
/script如上述代码监听的对象是外部定义的对象监听的属性名是另一个对象中的属性。在get()执行时将data.age的值返回给监听对象_data那么 _data中就会生成一个属性值age: 38同理在set()执行时也会将设置的值返回给监听对象 _data从而修改 _data中的属性值。两个对象data与 _data之间是代理的关系。 vue2底层数据代理 通过一个对象代理另一个对象的中的属性操作(读/写)
2. Object.entries
Object.entries将一级对象处理成键值对的形式。
let data {name:Evan You,age:36,sex:man
}然后通过循环遍历使用Object.defineProperty方法把属性转化为get/set最后代理给_data。
// 原对象
let data {name:Evan You,age:36,sex:man
}
// 代理对象
let _data {}
// 处理键值对
Object.entries(data).forEach(([key, val]) {// 获取data对象的键值对交给_data代理 createObj(_data, key, val)
})
// 代理
function createObj(_data, key, val){console.log(_data, key, val);Object.defineProperty(_data, key, {// 对data的每一个属性keyget获取值set将值赋给属性最后将属性及其对应值赋给监听对象_dataget(){return val;},set(value){val value;}})
}修改_data的值但data的值不会受影响。 3. 底层搭建
创建一个class类Test返回一个constructor对象实例化Test。在constructor输出arguments可获得节点和数据。
class Test{constructor(){console.log(arguments);}
}
let vm new Test({el:#root,data(){return {num:32,name:Jordan,country:Ame,work:basketball}}
}) 操作
div idroot{{name}/brinput typetext v-modelname /br{{work}}
/divvue底层
vue 拿到挂载中的所有节点vue 拿data中的数据, 插入到带有vue指令,特殊的vue符合中。Object.defineProperty监听data数据vue 将数据插入成功的元素放回到页面中。
class Test {constructor({el, data}) { // 解构赋值// 获取节点this.el document.querySelector(el);this._data data;// 调用方法getDom(this.el, this._data)}
}
// 获取节点
function getDom(node, _data){console.log(node, _data);
}可以通过firstChild和nextSibling获取每一个节点 getDom函数
function getDom(node, _data){// console.log(node, _data);// 文本代码拼凑,创建一个新的空白的文档片段let frame document.createDocumentFragment();let child;console.log(node.firstChild);// 空循环将赋值给childwhile(child node.firstChild){// 插入到frameframe.append(child);console.log(child);// child获得节点}return frame;
}执行结果页面上的节点被剪切。 循环过程每执行一次append操作root的第一个节点就会被剪切掉当所有节点被剪切掉时为null循环结束。 接下来我们在进入循环之后先调用另一个函数getDom2判断节点的类型并把原对象的数据取出来赋值给这些节点。
function getDom(node, _data){let frame document.createDocumentFragment();while(child node.firstChild){getDom2(child, _data)frame.append(child); }// 返回操作后的节点return frame;
}function getDom2(node, _data){console.log(node, node.nodeType);//正则 匹配插值符号{{}}let reg /\{\{(.*)\}\}/// 节点// if(noede.nodeType 1){ // 元素节点nodeType 属性返回 1 // }if(node.nodeType 3){ //文本节点nodeType 属性返回 3// 如果该文本节点匹配到{{}}返回trueif(reg.test(node.nodeValue)){ // 取出节点值匹配{{}}console.log(reg.test(node.nodeValue)); // 文本节点返回trueconsole.dir(RegExp);// $1为RegExp的属性获取{{}}里面的值- name,worklet arg RegExp.$1.trim();// 获取{{}}里面的值- name,workconsole.log(arg);// 将原对象的namework及其值赋给页面中的{{name}},{{work}}node.nodeValue _data[arg];// 节点分别为Jordan、basketballconsole.log(node);// 将数据(节点 data)存储下来 new watcher(_data, node, arg)}}
}class Test {constructor({el, data}) {this.el document.querySelector(el);this._datadata;// 获取节点this.dom getDom(this.el, this._data)// 将返回的节点插入页面this.el.append(this.dom)}
}输出说明 **说明**为了方便html的div中将用于换行的两个删去。
对于元素节点进行下述处理。
if(node.nodeType 1){ // 元素节点nodeType 属性返回 1 console.log(node, node.nodeType);// 获取元素节点上的属性节点console.log(node.attributes);[...node.attributes].forEach((item) { // 遍历属性节点if(item.nodeName v-model){console.log(item.nodeName);// 获取v-model的属性值- namelet arg item.nodeValue;// 双向数据绑定通过页面修改原对象node.addEventListener(input,(ev){_data[arg] ev.target.value})console.log(arg);// 将原对象data中的name赋值(代理)到v-model的namenode.value _data[arg];console.log(node.value);console.log(node);// 将数据(节点 data)存储下来 new watcher(_data, node, arg)}})
}从vue底层原理可知在获取节点以及渲染之前应该先进行数据监听。
数据监听第一步是启动订阅(Dep类)然后调用Object.defineProperty所有属性转为get/set在get中调用Dep类的addSub方法存储数据在set中调用Dep类的notify方法修改数据。此时还未获取节点属于在后端修改数据。
在获取节点的分类节点并插入页面后调用watcher类该类先保存数据并传送数据给Dep类也进行数据的获取和修改。此时以获取节点属于在页面修改数据。
以上两部分即是双向数据绑定。
// 订阅发布在数据变动时,发给订阅者,触发对应的函数
class Dep {constructor() {// 保留数据this.sub []}addSub(val) {this.sub.push(val)}notify() {this.sub.map((item) {item.update()})}
}//观察者 保存数据以便后期进行修改
class watcher {constructor(_data, node, arg) {// 数据也给Dep一份以便Dep订阅数据是否变化Dep.target this// 保存数据this._data _data;this.node node;this.arg arg;this.init()}init() {// 用于后期进行数据修改this.update()// 修改数据后清空Dep留存的数据Dep.target null}update() {// 用于获取数据this.get()// 修改数据this.node.value this.node.nodeValue this.value}get() {// 获取数据数据代理this.value this._data[this.arg]}
}//处理数据监听
function setdefineProperty(data, _data) { // data_dataObject.entries(data).forEach(([key, val]) {createObj(_data, key, val)});
}//监听数据
function createObj(_data, key, val) {//启动 订阅发布let dep new Dep()// 所有属性转为get/setObject.defineProperty(_data, key, {get() {// 如果Dep.target 有数据if (Dep.target) { //没有数据不就要放进去 dep.addSub(Dep.target)}return val},set(value) {// 数据相同不作改变if (val value) {return}// 更改数据val value;// 设置的时候修改页面数据dep.notify()}})
}双向数据绑定需在获取输入框节点的代码中加入
// 双向数据绑定通过页面修改原对象
node.addEventListener(input, (ev) {_data[arg] ev.target.value
})最后最好将多余的属性和样式删除。如删除v-model。
// 删除属性
node.removeAttribute(v-model)