知识点
理解Vue设计思想
- MVVM模式

MVVM框架的三大要素,数据相应式,模版引擎,及渲染
数据响应式:监听数据变化,并在视图中更新
- object.defineproperty()
- proxy
模版引擎(提供描述视图的模版语法)
- 插值表达式{{}}
- 指令 v-bind v-on v-model v-for v-if
渲染(如何将模版转换成html)
- 模版=>虚拟dom=>dom
数据响应式原理
数据变更能够响应在视图中,就是数据响应式。vue2利用object.defineProperty()实现变更检测。
- 简单实现 ```vue const obj = {}
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
get(){
console.log(get,${key},${val},)
return val
},
set(newVal){
if(newVal !=val){
val = newVal;
console.log(set${key},${val})
}
}
})
}
defineReactive(obj,’foo’,’foo’); obj.foo; obj.foo = ‘joyce’
- 结合视图```vue<html><head></head><body><div id="app"></div><script>const obj = {}function defineReactive(obj,key,val){observe(val)// 解决嵌套参数问题 将嵌套对象变成响应式Ojbect.defineProperty(obj,key,{get(){console.log(`get ${key}${val}`)return val;},set(newVal){if(newVal!==val){observe(val) //解决赋值是对象的问题val = newVal//更新到视图update()}}})}defineReactive(obj,'foo','');obj.foo = new Date().toLocalTimeString();function update(){app.innerText = obj.foo;}setInterVal(()=>{obj.foo = new Date().toLocalTimeString();},1000)</script></body></html>
- 遍历需要响应化的对象 ```vue function observe(obj){ if(typeof obj !== object || obj == null){ return; } object.keys(obj).forEach(key=>{ defineReactive(obj,key,obj[key]); }) }
const obj = {foo:’foo’,bar:’bar’,baz:{a:1}};
observe(obj)
obj.baz.a = 10; //嵌套对象 no ok 需要在defineReactive方法中解决
obj.baz = {a:1};//解决赋值是对象的问题
obj.dong = ‘dong’ //添加属性/删除新属性无法检测
function set(obj,key,val){ defineReactive(obj,key,val); }
set(obj,’dong’,’dong’);
//需要注意的是defineProPerty()不支持数组; 思考如何解决数组的响应式问题?
<a name="VvPgn"></a>## vue中的数据响应化<a name="d6jSv"></a>### 原理分析1. 首先执行new Vue()执行初始化,对data数据做响应式处理,这个过程发生在observe中。1. 同时对模版进行编译,找到其中动态绑定的数据,从data中获取数据并初始化视图,这个过程发生在compile中1. 同时定义一个watcher和更新函数,将来数据发生变化时,就会调用watcher中的更新函数。1. 由于data中的某个key可能在视图中的多个地方出现,所以每个key都需要一个管家Dep来对应多个watcher1. 将来一旦数据发生变化,会首先找到对应的Dep,通知所有watcher执行更新函数<a name="A8yPA"></a>### 涉及类型介绍- KVue:框架构造函数- Obverser:执行数据响应化(需要区别对象 数组类型)- compile:编译模版,初始化视图,收集依赖(订阅数据变化,绑定更新函数,watcher创建)- watcher:执行更新函数(更新dom)- Dep:管理多个watcher,批量更新<a name="pnNuA"></a>### Kvue框架构造函数,执行初始化- 执行初始化,对data进行响应式处理Kvue.js```vuefunction observe(obj){if(typeof obj !== object && obj == null){return}new Observer(obj);}function defineReactive(){......}// 进行数据代理function proxy(vm){Object.keys(vm.$data).forEach(key={Object.defineProperty(vm,key,{get(){return vm.$data[key];},set(newVal){vm.data[key] = newVal;}})})}class KVue{constructor(options){this.$options = options;this.$data = options.data;observe(this.$data); //对数据进行响应式proxy(this, '$data');}}class Observer{construtor(value){this.$value = value;this.walk($this.value)}walk(obj){object.keys(obj).forEach(key=>{defineReactive(obj,key,obj[key]);})}}
编译-compile
初始化视图
根据节点类型进行编译,compile.js
class compile{constructor(el,vm){this.$vm = vm;this.$el = document.querySelector(el);if(this.$el){this.compile(this.$el)}}function compile(el){const childNodes = el.childNodes; //获取元素的子节点集合Array.from(childNodes).forEach(node=>{// 如果是元素节点if(this.isElement(node)){console.log('编译元素'+node.nodeName);// 插值文本}else if(this.this.isInterpolation(node)){this.textContent(node)console.log("编译插值文本" + node.textContent)}//如果还存在子节点 进行递归操作if(node.childNodes && node.childrenNodes.length>0){this.compile(node);}})}}function isElement(node){return node.nodeType == 1;}function isTerpolation(node){return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);}//编译插值function compileText(node){console.log(RegXep.$1)node.textContent = this.$vm[RegExp.$1];}// 编译元素function compileElement(node){let nodeAttrs = node.attributes;Array.from(nodeAttrs).forEach(attr=>{let attrName = attr.name;let exp = attr.value;if(this.isDirective(attrName){let dir = attrName.subString(2);this[dir] && this[dir](node,exp);})})}//是否存在指令function isDirective(attr){return attr.indexOf("k-") === '0';}//function text(node,exp){node.textContent = this.$vm[exp];}
依赖收集
视图中会用到data中某key,这称为依赖,同一个key可能出现多次,每次都需要收集出来用一个Watcher来维护它们,此过程依赖收集。多个watcher需要一个Dep来管理,需要更新时由Dep统一通知
new Vue({template:`<div><p>{{name1}}</p><p>{{name2}}</p><p>{{name1}}</p><div>`,data: {name1: 'name1',name2: 'name2'}});

实现思路
- defineReactive时为每一个key创建一个Dep实例2
- 初始化视图时读取某个key,例如name1,创建一个watcher1
- 由于触发name1的getters方法,便将watcher1添加到name1对应的dep1中
- 当name1更新时,触发set方法,便可以通过Dep通知其管理的所有watcher进行更新

创建watcher Kvue.js
watchers = []; //用于临时保存watcher测试用//监听器 负责更新视图class Watcher{constructor(vm,key,updateFn){//Kue实例this.vm = vm;this.key = key;//更新函数this.updateFn = updateFn//临时放入watcher数组watcher.push(this);}// 更新函数function update(){this.updateFn.call(this.vm,this.vm[this.key]);}}
编写更新函数,创建watcher
调用update函数为插值文本赋值compileText(node){// node.textContent = this.$vm[RegExp.$1]this.update(node,RegExp.$1,'text')}function text(node,exp){this.update(node,exp,'text')}function html(node,exp){this.update(node,exp,'html')}function update(node,exp,dir){const fn = this[dir+'updater'];fn && fn(node,this.$vm[exp]);// 创建Watcher 执行更新函数new Watcher(this.$vm,node,function(val){fn && fn(node,val);})}function textUpdater(node,val){node.textContent = val;}function htmlUpdater(node,val){node.innerHtml = val;}
声明Dep
class Dep{思路:创建Dep统一管理watcher,当数据更新时,触发set方法,通过Dep订阅通知对应watcher更新函数constructor(){this.deps = [];}function add(dep){this.deps.push(dep);}function notify(){this.deps.forEach(dep=>{dep.update()})}}
创建watcher时出发getter
class watcher{construtor(vm,key,updateFn){Dep.target = this;this.vm[this.key];Dep.target = null;}}
依赖收集,创建Dep实例
defineReactive(obj,key,val){this.observe(val);const dep = new Dep();Object.defineProperty(obj,key,{get(){Dep.target && dep.addDep(Dep.target);return val;},set(newVal){if(newVal !== val) return;dep.notify();}})}
