在模拟最小的vue之前,先复习一下,发布订阅模式和观察者模式

对两种模式有了了解之后,对Vue2.0和Vue3.0的数据响应式核心原理

1.Vue2.0和Vue3.0的数据响应式核心原理

(1).  Vue2.0是采用Object.defineProperty的方式,对数据进行get,set方法设置的, 具体可以详见Object.defineProperty的介绍

浏览器兼容 IE8 以上(不兼容 IE8)
<script>
// 模拟 Vue 中的 data 选项
let data = {
msg: 'hello'
} // 模拟 Vue 的实例
let vm = {} // 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作
Object.defineProperty(vm, 'msg', {
// 可枚举(可遍历)
enumerable: true,
// 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)
configurable: true,
// 当获取值的时候执行
get () {
console.log('get: ', data.msg)
return data.msg
},
// 当设置值的时候执行
set (newValue) {
console.log('set: ', newValue)
if (newValue === data.msg) {
return
}
data.msg = newValue
// 数据更改,更新 DOM 的值
document.querySelector('#app').textContent = data.msg
}
}) // 测试
vm.msg = 'Hello World'
console.log(vm.msg)
</script>

如果,vm里的属性是对象如何处理,可以,对其遍历,在进行Object.defineProperty

<script>
// 模拟 Vue 中的 data 选项
let data = {
msg: 'hello',
count: 10,
person: {name: 'zhangsan'}
} // 模拟 Vue 的实例
let vm = {} proxyData(data) function proxyData(data) {
// 遍历 data 对象的所有属性
Object.keys(data).forEach(key => {
// 把 data 中的属性,转换成 vm 的 setter/setter
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get () {
console.log('get: ', key, data[key])
return data[key]
},
set (newValue) {
console.log('set: ', key, newValue)
if (newValue === data[key]) {
return
}
data[key] = newValue
// 数据更改,更新 DOM 的值
document.querySelector('#app').textContent = data[key]
}
})
})
} // 测试
vm.msg = 'Hello World'
console.log(vm.msg)
</script>

(2). Vue3.x是采用proxy代理的方式实现, 直接监听对象,而非属性。ES 6中新增,IE 不支持,性能由浏览器优化,具体可以详见MDN - Proxy

<script>
// 模拟 Vue 中的 data 选项
let data = {
msg: 'hello',
count: 0
} // 模拟 Vue 实例
let vm = new Proxy(data, {
// 执行代理行为的函数
// 当访问 vm 的成员会执行
get (target, key) {
console.log('get, key: ', key, target[key])
return target[key]
},
// 当设置 vm 的成员会执行
set (target, key, newValue) {
console.log('set, key: ', key, newValue)
if (target[key] === newValue) {
return
}
target[key] = newValue
document.querySelector('#app').textContent = target[key]
}
}) // 测试
vm.msg = 'Hello World'
console.log(vm.msg)
</script>

2.Vue 响应式原理模拟

看图,整体分析

 Vue
  • 把 data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter
Observer
  • 能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep
Compiler
  • 解析每个元素中的指令/插值表达式,并替换成相应的数据
Dep
  • 添加观察者(watcher),当数据变化通知所有观察者
Watcher
  • 数据变化更新视图

(1) Vue

功能
  • 负责接收初始化的参数(选项)
  • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
  • 负责调用 observer 监听 data 中所有属性的变化
  • 负责调用 compiler 解析指令/插值表达式
class Vue {
constructor (options) {
//1.通过属性保存选项的数据
this.$options = options || {}
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
this.$data = options.data || {}
//2.把data中的成员转换成getter和setter方法,注入到vue实例中
this._proxyData(this.$data)
//3.调用observer对象,监听数据变化
new Observer(this.$data)
//4.调用compiler对象, 解析指令和差值表达式
new Compiler(this)
} _proxyData (data) {
//遍历data中的所有属性
Object.keys(data).forEach( key => {
//把data的属性注入到vue实例中
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get () {
return data[key]
},
set (newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue
} })
}) } }

(2)Observer

功能
  • 负责把 data 选项中的属性转换成响应式数据
  • data 中的某个属性也是对象,把该属性转换成响应式数据
  • 数据变化发送通知
class Observer {
constructor (data) {
this.walk(data)
}
//1.
walk (data) {
//1.判断data是不是对象
if (!data || typeof data !== 'object') {
return
}
//遍历data对象里的所有属性
Object.keys(data).forEach( key => {
this.definedReactive(data, key, data[key])
})
} definedReactive (obj, key, value) {
let that = this
//负责收集依赖(观察者), 并发送通知
let dep = new Dep() this.walk(value)//如果data里的属性是对象,对象里面的属性也得是响应式的,所以得判断一下 Object.defineProperty (obj, key, {
enumerable: true,
configurable: true,
get () {
//收集依赖
Dep.target && dep.addSubs(Dep.target)
return value
// return obj[key]//这么写会引起堆栈溢出
},
set (newValue) {
if (newValue === value) {
return
} value = newValue
that.walk(newValue)//如果赋值为对象,对象里面的属性得是响应式数据 //数据变换 ,发送通知给watcher的update ,在渲染视图里的数据
dep.notify()
} })
} }

(3).Compiler

功能
  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染
  • 当数据变化后重新渲染视图
class Compiler {

    constructor (vm) {//传个vue实例
this.el = vm.$el
this.vm = vm
this.compile(this.el)
} //编译模板, 处理文本节点和元素节点
compile (el) { let childNodes = el.childNodes //获取子节点 伪数组
console.dir(el.childNodes)
Array.from(childNodes).forEach( node => {
if (this.isTextNode(node)) { //是文本节点
this.compileText(node)
} else if (this.isElementNode(node)) {//是元素节点
this.compileElement(node)
} if (node.childNodes && node.childNodes.length) { //子节点里面还有节点,递归遍历获取
this.compile(node)
}
})
} //编译元素节点, 处理指令
compileElement (node) {
//console.log(node.attributes) Array.from(node.attributes).forEach( attr => { //判断是不是指令
let attrName = attr.name //<div v-text="msg"></div> 里的v-text
if (this.isDirective(attrName)) {
//v-text --> text
attrName = attrName.substr(2)
let key = attr.value //<div v-text="msg"></div> 里的msg
this.update(node , key, attrName)
}
})
} update (node, key, attrName) {
let updateFn = this[attrName + 'Updater']
updateFn && updateFn.call(this, node, this.vm[key], key)//call方法改变this指向
}
//处理v-text 命令
textUpdater (node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
//v-model
modelUpdater (node, value, key) {
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
}) //双向绑定,视图改变,数据也会更新
node.addEventListener('input', () => {
this.vm[key] = node.value
})
} //编译文本节点,处理差值表达式
compileText (node) {
//console.dir(node)
// {{ msg }}
let reg = /\{\{(.+?)\}\}/
let value = node.textContent //里面的内容, 也可以是nodeValue
if (reg.test(value)) {
let key = RegExp.$1.trim() //匹配到的第一个
node.textContent = value.replace(reg, this.vm[key]) //创建watcher对象, 当数据改变更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
} //判断元素属性是否是指令
isDirective (attrName) {
return attrName.startsWith('v-')
} //判断节点是否是文本节点
isTextNode (node) {
return node.nodeType === 3
} //判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}

(4).Dep(Dependency)

功能

  • 收集依赖,添加观察者(watcher)
  • 通知所有观察者
class Dep {

    constructor () {
//收集观察者
this.subs = []
} //添加观察者
addSubs (watcher) {
if (watcher && watcher.update) {
this.subs.push(watcher)
}
}
//数据变换,就调watcher的update方法
notify () {
this.subs.forEach(watcher => {
watcher.update()
});
}
}

(5).Watcher

功能

  • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
  • 自身实例化的时候往 dep 对象中添加自己
class Watcher {
constructor (vm, key, callback) {
this.vm = vm
//data中的属性名
this.key = key
this.callback = callback
//将watcher对象记录在Dep的静态属性target
Dep.target = this
//触发get方法,触发get里的addsubs方法,添加watcher
this.oldValue = vm[key]
Dep.target = null
} //当数据变化的时候,更新视图
update () {
let newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
this.callback(newValue)
}
}

总结:

Vue

  • 记录传入的选项,设置 $data/$el
  • 把 data 的成员注入到 Vue 实例
  • 负责调用 Observer 实现数据响应式处理(数据劫持)
  • 负责调用 Compiler 编译指令/插值表达式等
Observer
  • 数据劫持
  • 负责把 data 中的成员转换成 getter/setter
  • 负责把多层属性转换成 getter/setter
  • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
  • 添加 Dep 和 Watcher 的依赖关系
  • 数据变化发送通知
Compiler
  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染过程
  • 当数据变化后重新渲染
Dep
  • 收集依赖,添加订阅者(watcher)
  • 通知所有订阅者
Watcher
  • 自身实例化的时候往dep对象中添加自己
  • 当数据变化dep通知所有的 Watcher 实例更新视图

Vue 响应式原理模拟以及最小版本的 Vue的模拟的更多相关文章

  1. 浅谈vue响应式原理及发布订阅模式和观察者模式

    一.Vue响应式原理 首先要了解几个概念: 数据响应式:数据模型仅仅是普通的Javascript对象,而我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率. 双向绑定:数据改变,视图 ...

  2. Vue源码--解读vue响应式原理

    原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...

  3. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  4. 深入解析vue响应式原理

    摘要:本文主要通过结合vue官方文档及源码,对vue响应式原理进行深入分析. 1.定义 作为vue最独特的特性,响应式可以说是vue的灵魂了,表面上看就是数据发生变化后,对应的界面会重新渲染,那么响应 ...

  5. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  6. vue响应式原理,去掉优化,只看核心

    Vue响应式原理 作为写业务的码农,几乎不必知道原理.但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行.所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理. 核心: / ...

  7. 深入Vue响应式原理

    深入Vue.js响应式原理 一.创建一个Vue应用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h =& ...

  8. vue响应式原理解析

    # Vue响应式原理解析 首先定义了四个核心的js文件 - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中 - 2. watcher ...

  9. 浅析Vue响应式原理(三)

    Vue响应式原理之defineReactive defineReactive 不论如何,最终响应式数据都要通过defineReactive来实现,实际要借助ES5新增的Object.definePro ...

  10. Vue响应式原理及总结

    Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 ...

随机推荐

  1. 通过 Docker 部署 Redis 6.x 集群

    要点步骤总结: # 这里演示使用同一台主机上 # 创建各节点存储路径 mkdir -p /opt/redis/{7000,7001,7002,7003,7004,7005} # 创建各节点配置文件 c ...

  2. 企业使用erp系统的好处及解决了什么问题?

    不是所有的企业使用ERP都能带来好处的,尤其是对于一些小微企业,带来的可能是灾难,而实施不适用的系统同样也会带来意想不到的后果,所以在ERP的使用方面得根据自己企业实际做决定.不同规模的企业选用不同的 ...

  3. ERP系统都能给企业带来什么好处?

    ERP系统但如果用得好,自然可以提高企业内部资源的计划和控制能力,提质增效降成本,提升企业竞争力,加速数字化转型步伐,但不是所有的企业使用ERP都能带来好处的,尤其是对于一些小微企业,带来的可能是灾难 ...

  4. 集合框架——LinkedList集合源码分析

    目录 示例代码 底层代码 第1步(初始化集合) 第2步(往集合中添加一个元素) 第3步(往集合中添加第二个元素) 第4步(往集合中添加第三个元素) LinkedList添加元素流程示意图 第5步(删除 ...

  5. 支付宝沙箱服务 (结合springboot实现,这里对接的是easy版本,工具用的是IDEA,WebStrom)

    一:打开支付宝开发平台,登录,然后点击控制台 https://open.alipay.com/ 二:滚动到底部,选着沙箱服务 三:获取到对接要用的appId和公钥私钥 四:打开IDEA导入所需的xml ...

  6. 洛谷P3810 陌上花开 (cdq)

    最近才学了cdq,所以用cdq写的代码(这道题也是cdq的模板题) 这道题是个三维偏序问题,先对第一维排序,然后去掉重复的,然后cdq分治即可. 为什么要去掉重复的呢?因为相同的元素互相之间都能贡献, ...

  7. AlexNet-文献阅读笔记

    论文介绍 ImageNet Classification with Deep Convolutional Neural Networks- Alex Krizhevsky, Ilya Sutskeve ...

  8. python基础作业1

    目录 附加练习题(提示:一步步拆解) 1.想办法打印出jason 2.想办法打印出大宝贝 3.想办法打印出run 4.获取用户输入并打印成下列格式 5 根据用户输入内容打印其权限 6 编写用户登录程序 ...

  9. 【杂谈】2021-CSP退役记

    Part1:复赛前一周 感觉复赛来的好快...... 我还没 颓够 准备好就来了QAQ 根据模拟赛 爆零 的光辉事迹,这次复赛我特别慌,虽然但是还是不想复习 但无所谓了,复赛一下子就只剩一天了 Par ...

  10. 驱动开发:内核测试模式过DSE签名

    微软在x64系统中推出了DSE保护机制,DSE全称(Driver Signature Enforcement),该保护机制的核心就是任何驱动程序或者是第三方驱动如果想要在正常模式下被加载则必须要经过微 ...