知识点
理解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来对应多个watcher
1. 将来一旦数据发生变化,会首先找到对应的Dep,通知所有watcher执行更新函数
![WechatIMG6.png](https://cdn.nlark.com/yuque/0/2021/png/21734057/1622107982501-d322c14a-2968-42dc-a363-aef2f2e282c3.png#clientId=u1cc31653-48c5-4&from=ui&id=u862bd760&margin=%5Bobject%20Object%5D&name=WechatIMG6.png&originHeight=1056&originWidth=2104&originalType=binary&size=429447&status=done&style=none&taskId=u00ed31a4-4a3f-45fb-885f-91cac5729b0)
<a name="A8yPA"></a>
### 涉及类型介绍
- KVue:框架构造函数
- Obverser:执行数据响应化(需要区别对象 数组类型)
- compile:编译模版,初始化视图,收集依赖(订阅数据变化,绑定更新函数,watcher创建)
- watcher:执行更新函数(更新dom)
- Dep:管理多个watcher,批量更新
<a name="pnNuA"></a>
### Kvue
框架构造函数,执行初始化
- 执行初始化,对data进行响应式处理Kvue.js
```vue
function 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();
}
})
}