1. 原理
1.1 defineReactive简单版
利用Object.defineProperty() 实现数据的响应,注意需要用一个temp把 值存起来
let temp;
let obj = {a:1}
defineReactive(obj,"a")
function defineReactive(obj,key) {
Object.defineProperty(obj,key,{
get () {
console.log("调用get方法,temp",temp)
// console.log("obj[key]",obj[key]) //为什么不直接用 obj[key] 因为又会死循环重复调用get
return temp
},
set (val) {
console.log("调用set方法,val",val)
temp = val
return
}
})
}
obj.a
obj.a = 2
obj.a
//调用get方法,temp undefined
//调用set方法,val 2
//调用get方法,temp 2
1.2 defineReactive闭包版
方法参数再方法体内被返回,形成闭包
let obj = {}
defineReactive(obj,"a",'1')
function defineReactive(obj,key,val) {
Object.defineProperty(obj,key,{
get () {
console.log("调用get方法,val",val)
return val //直接返回 参数val 形成闭包
},
set (newVal) {
console.log("调用set方法,newVal",newVal)
if(val === newVal) {
return
}
val = newVal
return
}
})
}
obj.a
obj.a = 2
obj.a
// 调用get方法,val 1
// 调用set方法,newVal 2
// 调用get方法,val 2
1.3. 简单的页面响应
简单html定时自动数据响应刷新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script>
let obj = {}
defineReactive(obj,"a",'1')
function defineReactive(obj,key,val) {
Object.defineProperty(obj,key,{
get () {
console.log("调用get方法,val",val)
return val //直接返回 参数val 形成闭包
},
set (newVal) {
if(val === newVal) {
return
}
console.log("调用set方法,newVal",newVal)
val = newVal
updateFn()
}
})
}
let app = document.getElementById("app")
function updateFn() {
app.innerHTML = obj.a
}
setInterval(() => {
obj.a = new Date().toLocaleTimeString();
}, 1000);
</script>
</body>
</html>
1.4. 遍历所有属性
//观察方法
function observe(obj) {
if (typeof obj !== "object" || obj == null) {
return
}
Object.keys(obj).forEach( k => {
defineReactive(obj,key,obj[key])
})
}
1.5. 解决嵌套对象问题
function defineReactive(obj, key, val) {
observe(val) //判断值是不是也是对象 递归调用
}
1.6. 插入值是对象处理
set(newVal) {
if (newVal !== val) {
observe(newVal) // 赋值的时候也调用一下
...
}
}
// vue的set方法就是这个原理
function set(obj, key, val) {
defineReactive(obj, key, val)
}
1.7 数组响应式处理
// 1.对象响应化:遍历每个key,定义getter、 setter
// 2.数组响应化:覆盖数组原型⽅法,额外增加通知逻辑
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(
method => {
arrayProto[method] = function () { //这里修改覆盖 新arrayProto对应的每一个方法
originalProto[method].apply(this, arguments)//originalProto[method] 调用原来的方法,如Array的push
notifyUpdate(arguments[0],method) //加入自己想要派发的信息
}
}
)
/*等价于
copyProto['push'] = function() {
orginalProto['push'].call(this,arguments)
notifyUpdate.log("test..")
}
*/
//观察方法 主入口
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
// 增加数组类型判断,若是数组则覆盖其原型
if (Array.isArray(obj)) {
Object.setPrototypeOf(obj, arrayProto) //等价于 obj.__proto__ = arrayProto
//处理数据每个元素响应式
for (let index = 0; index < obj.length; index++) {
observe(obj[index]);//继续递归处理
}
} else {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(obj, key, obj[key])
}
}
}
//响应式逻辑
function defineReactive(obj, key, val) {
observe(val) // 解决嵌套对象问题
Object.defineProperty(obj, key, {
get() {
return val
},
set(newVal) {
if (newVal !== val) {
observe(newVal) // 新值是对象的情况
val = newVal
notifyUpdate(val)
}
}
})
}
function notifyUpdate(val) {
console.log(val,'⻚⾯更新!')
}
//测试代码
var testObj = {name:"jason"}
observe(testObj)
testObj.name = "jason.yang"
var testArray = [{name:"jason1"},{name:"jason2"}]
observe(testArray)
testArray.push({name:"jason3"})
2. 实战
2.1官方vue分析
//myVue.html
<meta charset="UTF-8">
<div id="app">
<p>{{counter}}</p>
<p j-text="counter"></p>
<p j-html="desc"></p>
</div>
<script src="myVue.js"></script>
<script>
const app = new myVue({
el:'#app',
data: {
counter: 1,
desc: '<span style="color:red">测试</span>'
},
})
setInterval(() => {
app.counter++
}, 1000);
</script>
- 需要实现data数据响应
- 实现{{}} 符号解析
- 实现j-text和j-html标签解析
2.2代码实现
- 创建一个MyVue类 接受初始化参数
- 实现一个observe的方法完成数据响应 和 Observe类用类处理数据的类型处理,暂时只处理对象情况,数组后面优化
- 创建Compile类解析 特殊符号{{}}与j-开头标签
- 实现this.$data.a 等价于 this.a的数据代理
2.2.1 observe处理流程
2.3Compile解析流程
广度遍历,深度优先
- 遍历当前根节点下所有dom节点,判断当前节点是否为 需要解析的{{}}或者j-或者 @事件
- 如果还是节点继续深度递归解析,如果不是继续执行compile()解析
- 用正则匹配执行的类型,如对应的compileText方法,同时调用通用的update方法来创建new Watch和Dep
- 由于key 添加了对应Watch和Dep,就可以等待下次key的内容变化时候,触发set方法自动调用dep派发所有watch的notify更新
//myVue.js
// 实现myyVue构造函数
function defineReactive(obj, key, val) {
// 如果val是对象,需要递归处理之
observe(val)
// 管家创建
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
console.log('get', key);
// 依赖收集
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (val !== newVal) {
// 如果newVal是对象,也要做响应式处理
observe(newVal)
val = newVal
console.log('set', key, newVal);
// 通知更新
dep.notify()
}
}
})
}
// 遍历指定数据对象每个key,拦截他们
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj
}
// 每遇到一个对象,就创建一个Observer实例
// 创建一个Observer实例去做拦截操作
new Observer(obj)
}
// proxy代理函数:让用户可以直接访问data中的key
function proxy(vm, key) {
Object.keys(vm[key]).forEach(k => {
Object.defineProperty(vm, k, {
get() {
return vm[key][k]
},
set(v) {
vm[key][k] = v
}
})
})
}
// 根据传入value类型做不同操作
class Observer {
constructor(value) {
this.value = value
// 判断一下value类型
// 遍历对象
this.walk(value)
}
//目前只处理对象情况
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
class myVue {
constructor(options) {
// 0.保存options
this.$options = options
this.$data = options.data
// 1.将data做响应式处理
observe(this.$data)
// 2.为$data做代理
proxy(this, '$data')
// 3.编译模板
new Compile('#app', this)
}
}
class Compile {
// el-宿主元素,vm-myyVue实例
constructor(el, vm) {
this.$el = document.querySelector(el)
this.$vm = vm
// 解析模板
if (this.$el) {
// 编译
this.compile(this.$el)
}
}
compile(el) {
// el是宿主元素
// 遍历它,判断当前遍历元素的类型
el.childNodes.forEach(node => {
if (node.nodeType === 1) {
// console.log('编译元素', node.nodeName);
this.compileElement(node)
} else if (this.isInter(node)) {
// 文本, {{xxx}}
// console.log('编译文本', node.textContent, RegExp.$1);
this.compileText(node)
}
// 递归
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
// 判断插值表达式
isInter(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
// 编译文本
compileText(node) {
this.update(node, RegExp.$1, 'text')
}
// 编译元素:分析指令、@事件
compileElement(node) {
// 获取属性并遍历之
const nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach(attr => {
// 指令:j-xxx="yyy"
const attrName = attr.name // j-xxx
const exp = attr.value // yyy
if (this.isDirective(attrName)) {
const dir = attrName.substring(2) // xxx
// 指令实际操作方法
this[dir] && this[dir](node, exp)
}
})
}
isDirective(attr) {
return attr.indexOf('j-') === 0
}
// 执行text指令对应的更新函数
text(node, exp) {
this.update(node, exp, 'text')
}
// j-text对应操作函数
textUpdater(node, val) {
node.textContent = val
}
html(node, exp) {
this.update(node, exp, 'html')
}
htmlUpdater(node, val) {
node.innerHTML = val
}
// 提取update,初始化和更新函数创建,该方法是为了每次出发时候,都
update(node, exp, dir) {
const fn = this[dir + 'Updater']
// 初始化
fn && fn(node, this.$vm[exp])
// 更新
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val)
})
}
}
// Watcher: 小秘书,跟视图中依赖1:1
// const watchers = []
class Watcher {
constructor(vm, key, updaterFn) {
this.vm = vm
this.key = key
this.updaterFn = updaterFn
// 依赖收集触发
Dep.target = this
this.vm[this.key] // 触发上面的get
Dep.target = null
}
update() {
this.updaterFn.call(this.vm, this.vm[this.key])
}
}
// 管家:和某个key,一一对应,1对多
class Dep {
constructor() {
this.deps = []
}
addDep(watcher) {
this.deps.push(watcher)
}
notify() {
this.deps.forEach(watcher => watcher.update())
}
}
3.代码优化
3.1数组支持响应式
//拿到 原来的数组原型方法
const orginalProto = Array.prototype
//创建备份
const arrayProto = Object.create(orginalProto)
['push','pop','shift'].forEach( method => {
//保留原来操作
orginalProto[method].apply(this,arguments)
//新增切面操作
console.log("当前调用了方法:",method)
})
// 根据传入value类型做不同操作
class Observer {
constructor(value) {
this.value = value
this.walk(value)
}
walk(obj) {
if(Array.isArray(obj)) {//添加数组判断
//修改原型
obj.__proto__ = arrayProto
for (let index = 0; index < obj.length; index++) {
walk(obj[index]);//继续递归处理
}
}else {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
}
3.2新增@事件处理
// 编译元素:分析指令、@事件
compileElement(node) {
// 获取属性并遍历之
const nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach(attr => {
// 指令:k-xxx="yyy"
const attrName = attr.name // k-xxx
const exp = attr.value // yyy
if (this.isDirective(attrName)) {
const dir = attrName.substring(2) // xxx
// 指令实际操作方法
this[dir] && this[dir](node, exp)
}else if(this.isEvent(attrName)) { //处理事件
const dir = attrName.substring(1)
this.eventHandler(node,exp,dir)
}
// 处理事件
})
}
//新增事件判断
isEvent(attr) {
return attr.indexOf('@') === 0
}
eventHandler(node,exp,dir) {
//这里拿到的 是vm里面的 methods 键值关系对象
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
fn.bind(this.$vm) // 解决调用绑定问题
node.addEventLister(dir,fn)
}
3.3实现v-model
model(node,exp) {
this.update(node,exp,'model')
//增加事件
node.addEventListener('input', e => {
this.$vm[exp] = e.target.value
})
}
//直接节点赋值
modelUpdater(node,value) {
node.value = value
}