首先原理的分析:
1.new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observer中
2.同时对模板进行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个发生在Compile中
3.同时定义一个更新函数和watcher,将来会对应数据变化时wather会调用更新函数。
4.由于data中的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个watcher
5.将来data中的数据发生变化时,首先会找到对应的Dep,通知所有的watcher执行更新函数。
框架构造函数:执行初始化
执行初始化,对data执行响应化处理,Kvue.js
// KVue:解析选项,响应式、编译等等
class LVue{
constructor(options){ //此处获取到的是LVue对象,包括数据和函数
this.$options = options
console.log(options)
this.$data = options.data
console.log(this.data)
console.log(this) //此处的this是KVue的实例
// 对data选项做响应式处理
observe(this.$data)
//代理
proxy(this)
new Compile(options.el,this)
}
}
function observe(obj){
if(typeof obj !=='object'||obj===null){
return
}
new Observer(obj)
}
class Observer{ //创建响应类,判断时村对象还是数组进而进行不同的更新
constructor(value){
this.value = value
if(Array.isArray(value)){ //如果是数组执行数组的响应式
}else{
this.walk(value)
}
}
walk (obj) { //对象响应式,将对象的每一个属性进行响应式处理
Object.keys(obj).forEach(key=>defineReactive(obj,key,obj[key]))
}
}
通过proxy为data做代理
// 代理函数:可以将$data代理到KVue的实例
// vm是KVue实例
function proxy(vm){
Object.keys(vm.$data).forEach(key=>{
//此处为当前的实例做代理,建立data与key对应的关系
Object.defineProperty(vm,key,{
get(){
return vm.$data[key]
},
set(newVal){
if(newVal){
vm.$data[key] = newVal
}
}
})
})
}
关于proxy代理的问题我之前也一直不理解具体是干嘛的,查了一些资料,发现有一篇文章介绍的很白话,通俗易懂,附上地址链接: Proxy 对象是做什么用的?.
// Observer: 辨别当前对象类型是纯对象还是数组,从而做不同响应式操作
class Observer {
constructor(value) {
this.value = value
// 辨别类型
if (Array.isArray(value)) { //数组
// todo
} else { //对象
this.walk(value)
}
}
walk(obj) {
// 对象响应式
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
}
}
编译模板中vue模板的特殊语法,初始化视图,更新视图。
获取DOM->遍历子元素->编译节点/编译文本->对于节点遍历属性->分辨是事件还是元素
// Compile: 遍历视图模板,解析其中的特殊模板语法为更新函数
// new Compile(el, vm)
class Compile {
constructor(el, vm) {
// el:宿主元素选择器
// vm:KVue的实例
this.$vm = vm;
this.$el = document.querySelector(el)
// 执行编译
this.compile(this.$el)
}
compile(el) {
// 遍历子元素,判断他们类型并做响应处理
el.childNodes.forEach(node => {
// 判断类型
if (node.nodeType === 1) {
// 元素节点
// console.log('编译元素', node.nodeName);
this.compileElement(node)
} else if (this.isInter(node)) {
// 文本节点
// console.log('文本节点', node.textContent);
// double kill
this.compileText(node)
}
// 递归子节点
if (node.childNodes) {
this.compile(node)
}
})
}
// 是否插值绑定
isInter(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
// 绑定表达式解析
compileText(node) {
// 获取匹配表达式 RegExp.$1,比如counter, vm['counter']
// node.textContent = this.$vm[RegExp.$1]
this.update(node, RegExp.$1, 'text')
}
// 编译元素节点:判断指令和事件
compileElement(node) {
let _this = this
// 获取属性
const attrs = node.attributes
Array.from(attrs).forEach(attr => {
// k-text="counter"
// attr是一个对象{name:'k-text', value: 'counter'}
const { name, value } = attr
// 判断是否是指令
if (name.indexOf('k-') === 0) {
// 截取指令
const dir = name.substring(2)
// 指令指令
this[dir] && this[dir](node, value)
}
// 判断是否是事件 @
else if (name.indexOf('@') === 0) {
node.onclick = (function(){
const handle = name.substring(1)
//此处不知道如何获取绑定的函数名
_this.$vm.$options.methods.handleclick()
})()
console.log(node)
// var attrVal = node.getAttribute('@')
// console.log(attrVal)
}
})
}
视图中会用到data中某key,这成为依赖。同一个key可能出现多次,每次都需要一个watcher来维护他们,这就是依赖收集。
每一个key对应一个Dep,但是会出现一个key的数据有多个用到的地方,所以就会有多个watcher需要一个Dep来管理,需要更新时由Dep同意通知。
根据案例,整理watcher与Dep直接按的关系:
new Vue({
template:
`
{{name1}}
{{name2}}
{{name3}}
`,
data:{
name1:'aaaa',
name2:'bbbbb'
}
})
具体实现思路:
1.defineReactive中为每一个key值创建一个Dep实例
2.初始化视图的时候,读取某个key值,如name1,创建一个Watcher1
3.由于触发name1的getter方法,便将Watcher1添加到name1对应的Dep中
4.当name1更新,setter触发时,便可通过对应Dep通知其管理所有的Watcher更新
// Watcher: 管理依赖,执行更新
// const watchers = []
class Watcher {
// vm是KVue实例
// key是data中对应的key名称
// fn是更新函数,他知道怎么更新dom
constructor(vm, key, fn) {
this.vm = vm
this.key = key
this.fn = fn
// watchers.push(this)
// 建立dep和watcher之间的关系
Dep.target = this
this.vm[this.key] // 读一下key的值触发其getter
Dep.target = null
}
// Dep: 管理多个watcher实例,当对应key发生变化时,通知他们更新
class Dep {
constructor() {
this.deps = []
}
addDep(dep) {
// 添加订阅者,dep就是watcher实例
this.deps.push(dep)
}
// 通知更新
notify() {
this.deps.forEach(w => w.update())
}
}
function defineReactive(obj, key, val) {
// val可能还是对象,此时我们需要递归
observe(val)
// 创建Dep实例,他和key一对一对应关系
const dep = new Dep()
// 参数3是描述对象
Object.defineProperty(obj, key, {
get() {
// console.log('get', key);
// 依赖收集:Dep.target就是当前新创建Watcher实例
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (newVal !== val) {
console.log('set', key);
// 防止newVal是对象,提前做一次observe
observe(newVal)
val = newVal
// 通知更新
dep.notify()
}
}
})
}
这里的watcher与Dep的关系,是vue1中用到的,就是每一个变量都会对应一个Watcher,而Dep与Watcher的对应关系也是一对多。但在vue2进行了改进,Watcher对应的是一个组件,而不是一个变量,跟Dep之间的关系也是多个Dep对应多个Watcher,后面整理好了再分享。