8.30vue响应式原理
- 编译模块
效果//姓名:{{name}},年龄:{{age}},居住:{{addr.province}} //返回 姓名:mon,年龄:17,居住:dd //obj = { 姓名:mon,年龄:17,addr:{ province:'等等', city:'ddd' },} //只有嵌套对象没有数组 function compile(template,obj) { let res = gettemplate(template) //把模版字符串中需要替换的{{}}找出来 for(let i = 0; i < res.length; i++) { template = replace (template,res[i],obj) //然后把{{}}替换对应的属性值 } return template }
function gettemplate(template) { return template ? template.match(/{{[^}]+}}/g) : [] // {{}}数组 }
function replace (template,subTemplate,obj){ let prop = subTemplate.replace('{{','').replace('}}','') //根据{{}}找出对应的属性 let value = getValue(obj,prop)//根据属性得到属性值 return template.replace(subTemplate,value) //把{{}}替换成属性值 }
function getValue(obj,prop) { let props = prop.split('.')//嵌套对象的属性数组 let value = obj for(let i = 0; i < props.length; i++) { value = value[props[i]] } return value } - 创建虚拟节点
function node(realDom,template) { //真实节点,模版字符串===节点文本 this.realDom = realDom this.template = template this.children = [] //子节点数组 } function createNode(realDom){ let template = '' if(realDom.nodeType === Node.TEXT_NODE) {//判断是否是文本节点 template = realDom.nodeValue } let vnode = node(realDom,template) for (let i = 0; i < realDom.childNodes.length; i++) { //子节点 let childnode = realDom.childNodes[i] let childVnode = createNode(childnode) node.children.push(childVnode) } return node }
虚拟节点
//node {realDom: div#app, template: "", children: Array(5)} - 渲染页面的文本
function render (node,obj) { //找到包含{{}}的文本节点 node.template = node.template.trim() if(node.template) { let newText = compile( node.template,obj) //如果有{{}}就得到编译后的字符串 if(newText !==node.realDom.nodeValue){//如果有更新就让节点更新 node.realDom.nodeValue = newText } }else { for (let i = 0; i < node.children.length; i++) { //没有{{}}就递归渲染子节点 render(node.children[i],obj) } } }
//<p>姓名:a</p><p>年龄:17</p><p>居住:等等</p> - 响应式
//Object.defineProperty //将原始对象obj的所有属性复制到targetobj,并且让targetobj属性都具有响应式,当改变时就运行回调函数 function createResponse((obj,targetobj,callback) { for(const key in obj) { clone(obj,key,targetobj,callback) } } function clone(obj,key,targetobj,callbac){ //克隆对象并且有每个属性有响应式 if(typeof obj[prop] === 'object') {//如果属性是对象 let newObj = {}//把属性对象又用新的对象这样克隆并且拥有响应式 createResponse((obj[prop],newObj,callback) Object.defineProperty(target,prop,{ get:function (params) { return newobj }, set:function (params) { obj[prop] = params //响应式 if (typeof params === 'object') { //如果修改的属性值是对象 又需要创建新的对象来让修改的属性拥有响应式 newobj = {} createResponse(params,newobj,callback) }else { newobj = params //不是对象就直接赋值 } callback && callback() } }) }else { //属性不是对象就直接defineProperty Object.defineProperty(target,prop,{ get:function(){ return obj[prop] //返回原来的属性 }, set:function(val){ obj[prop] = params //响应式 callback && callback() } }) } }
//vm.age = 10
10<p>年龄:10</p>
- 合并
export default function Vue(options) { //绑定实例 let el = options.el this.$data = options.data this.$el = document.querySelector(el) this.$node = createNode( this.$el) //虚拟节点 render(this.$node,this.$data) //渲染文本 createResponse(this.$data,this,() => { //响应式 render(this.$node,this.$data) }) }
- html
<div id="app"> <p>姓名:{{name}}</p> <p>年龄:{{age}}</p>
<p>居住:{{addr.province}}</p>
</div><button onclick='vm.age++'>+</button> let obj = { name:'moc', age:17, addr:{ province:'等等', city:'ddd' }, } let newobj = {} createResponse(obj,newobj,() => { console.log('有属性改变了') }) window.newobj = newobj window.vm = new Vue({ el:'#app', data:{ name:'a', age:17, addr:{ province:'等等', city:'ddd' }, } })