Vue双向绑定原理(我尽量写的。简洁)
先问自己两个问题:
1.app.message修改数据的时候,Vue内部是如何监听message数据发生改变的
使用Object.defineProperty ->监听对象属性的改变
2.当数据发生改变后,Vue是如何知道 通知哪些 '人',界面发生刷新呢 (张三,李四,王五
发布订阅者模式
<div id='app'>
{{ message }} //张三
{{ message }} //李四
{{ message }} //王五 这里假设他们分别对应这三个message
{{ name }}
</div> <script src='./node_modules/vue/dist/vue.js'></script>
<script>
const app = new Vue({
el:'#app',
data:{//注意!这个是对象
message:'哈哈哈',
name:'kobe '
}
})
</script>
第一步肯定是创建Vue实例。 注意这里的data是 !对象
可以理解为我们把 这个obj对象传入Vue,Vue内部拿到的就是一个obj对象(data对象)
const obj = {
message:'哈哈哈',
name:'kobe '
}
拿到这个对象后,Vue先用Object.keys(obj)拿到一个包含obj对象的所有属性(message,name)的数组,然后进行forEach遍历,拿到每一个属性对应的value值
Object.keys(obj).forEach(key => {
let value = obj[key]
再进行‘数据劫持’ Object.defineProperty字面意思就是 给 obj对象重新 定义 属性。因为obj对象内的属性不容易监听
Object.defineProperty(obj, key, {
set(newValue){
console.log('监听' + key + '改变' ) !!!注意这里的监听
value = newValue
},
get(){
console.log('获取'+ key +'对应的值')
return value
}
})
})
此时我们在控制台给message重新赋值
app.message = '老詹'
就会触发set方法,打印出:
'监听message改变'
'老詹'
直接app.message则触发get
当我们设置或者访问对象的属性的时候,都会触发相应的函数,然后在这个函数里进行打印/返回/或者设置属性的值
既然如此,我们当然可以在触发函数的时候动一些手脚做点我们自己想做的事情,这也就是“劫持”操作。
------------------------------------------------------------------------------------------------------------
既然set内监听到数据发生了变化
set(newValue){
console.log('监听' + key + '改变' ) !!!注意这里的监听
value = newValue
},
那么监听到值改变后,告诉谁?谁在用呢?(记不记得一开始的 张三,李四,王五。让我们进行‘拟人’,更好理解)
谁在用其实是解析HTML代码,获取到哪些人有用到我们的属性
{{ message }} //张三
{{ message }} //李四
{{ message }} //王五
哎!!获取!获取!那么它肯定会调用一次message的get,那我就知道是张三,李四,王五你们在用这个message属性
那么到时候!一旦message属性的值发生变化set,那我再去通知你们三个。
---即发布者订阅者模式
class Dep{ //Dep 即 Depdency 依赖 存储所有对我这个属性有依赖的
constructor(){
this.subs = [] //用来记录现在是谁要订阅我们的属性的 subs 即subscribe订阅
}
}
const dep = new Dep() //这个Dep对象就可以用subs这个数组去记录所有的订阅者 (就是刚刚的张三,李四,王五啊)
那我怎么知道所有的订阅者在哪里呢 ,定义一个addSub方法,之后往里面传入sub形参,代表即将要传入进来的订阅者
class Dep {
constructor(){
// 这个数组是用来记录现在是谁要订阅我们的属性的
this.subs = []
}
addSub(){ //定义一个addSub方法,之后往里面传入sub形参,代表即将要传入进来的订阅者 为了拿到订阅者,我们得再创建一个类 class Watcher
}
}
为了拿到订阅者,我们得再创建一个类 class Watcher
class Dep {
constructor(){
// 这个数组是用来记录现在是谁要订阅我们的属性的
this.subs = []
}
addSub(){
}
}
// 监听观察
class Watcher{//订阅者
constructor(name){
this.name = name;
}
update(){
console.log(this.name + '发送update') //update 你细想,通知到张三,李四的时候,是不是需要他们自己更新一下,把界面更新一下
}
}
到时候我们创建一个watcher 实例,就可以实例化出来张三 w1对象 李四w2 对象, !!就可以把这些w1,w2实例对象放进dep实例的addSub内
addSub(watcher){ !形参
this.subs.push(watcher)
}
之后,谁用message属性了,我们就赶紧创建一个w1shiliduix
const w1 = new Watcher('张三') //意味着张三使用了一次
就把w1传进 dep.addSub(w1)
const obj = {
message:'哈哈哈',
name:'kobe '
}
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
set(newValue){
console.log('监听' + key + '改变' )
// 监听到值改变后告诉谁?谁在用呢?
// 解析HTML代码,获取到哪些人有用我们的属性 (获取一次--谁用-谁就调用一次get)
value = newValue
// dep.notify()//通知 !!!如果有一天,值发生改变了,我们拿到这个dep实例对象,调用notify
},
get(){
console.log('获取'+ key +'对应的值')
// 张三 get ->通知到就需要自己 update一下
// 李四 get -> update
// 王五 get -> update
return value
}
})
})
// 发布订阅者模式 Dependency subscribe订阅
class Dep {//发布者
// 存储所有对我这个属性有依赖的
constructor(){
// 这个数组是用来记录现在是谁要订阅我们的属性的
this.subs = []
}
addSub(watcher){
this.subs.push(watche r)
}
再定义一个notify方法
notify(){
this.subs.forEach( item => {
item.update() //拿到我们的subs,遍历找到里面所有的订阅者,让他去调用自己的update
})
}
}
// 监听观察
class Watcher{//订阅者
constructor(name){
this.name = name;
}
update(){
console.log(this.name + '发送update')
}
}
const dep = new Dep()
const w1 = new Watcher('张三')
dep.addSub(w1)
const w2 = new Watcher('李四')
dep.addSub(w2)
const w3 = new Watcher('王五')
dep.addSub(w3)
-------
最终代码一览
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!--
1.app.message修改数据,vue内部是如何监听message数据发生改变
Object.defineProperty ->监听对象属性的改变 2.当数据发生改变,Vue是如何知道通知哪些人,界面发生刷新呢
发布订阅者模式
--> <div id='app'>
{{ mess age }} //张三
{{ message }} //李四
{{ message }} //王五 {{ name }}
</div> <script>
const obj = {
message:'哈哈哈',
name:'kobe '
} Object.keys(obj).forEach(key => {
let value = obj[key] Object.defineProperty(obj, key, {
set(newValue){
console.log('监听' + key + '改变' )
// 监听到值改变后告诉谁?谁在用呢?
// 解析HTML代码,获取到哪些人有用我们的属性 (获取一次--谁用-谁就调用一次get)
value = newValue // dep.notify()//通知
},
get(){
console.log('获取'+ key +'对应的值')
// 张三 get ->通知到就需要自己 update一下
// 李四 get -> update
// 王五 get -> update return value
}
})
}) // 发布订阅者模式 Dependency subscribe订阅
class Dep {//发布者
// 存储所有对我这个属性有依赖的
constructor(){
// 这个数组是用来记录现在是谁要订阅我们的属性的
this.subs = []
} addSub(watcher){
this.subs.push(watche r)
} notify(){
this.subs.forEach( item => {
item.update()
})
}
} // 监听观察
class Watcher{//订阅者
constructor(name){
this.name = name;
} update(){
console.log(this.name + '发送update')
}
} const dep = new Dep() const w1 = new Watcher('张三')
dep.addSub(w1) const w2 = new Watcher('李四')
dep.addSub(w2) const w3 = new Watcher('王五')
dep.addSub(w3) dep.notify() </script> <script src='./node_modules/vue/dist/vue.js'></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'哈哈哈',
name:'kobe '
}
})
</script>
</body>
</html>
以下是copy来的,更为干练
数据双向绑定作为 Vue 核心功能之一,Vue 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定。
其中数据劫持是利用了 Object.defineProperty() 方法重新定义了对象获取属性值get和设置属性值set的操作来实现的;

劫持了数据之后,我们就需要一个监听器 Observer 来监听属性的变化。得知属性发生变化之后我们需要一个 Watcher 订阅者来更新视图,我们还需要一个 compile 指令解析器,用于解析我们的节点元素的指令与初始化视图。
- Observer 监听器:用来监听属性的变化通知订阅者
- Watcher 订阅者:收到属性的变化,然后更新视图(这个过程中我们可能会有很多个订阅者 Watcher 所以我们要创建一个容器 Dep 去做一个统一的管理)
- Compile 解析器:解析指令,初始化模版,绑定订阅者
当我们访问或设置对象的属性的时候,都会触发相对应的函数,然后在这个函数里返回或设置属性的值。既然如此,我们当然可以在触发函数的时候动一些手脚做点我们自己想做的事情,这也就是“劫持”操作。
在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器,当数据发生变化的时候发出通知。
注意: 该方法每次只能设置一个属性,那么就需要遍历对象来完成其属性的配置:
Object.keys(student).forEach(key => defineReactive(student, key))
另外还必须是一个具体的属性,这也非常的致命。假如后续需要扩展该对象,那么就必须手动为新属性设置 setter 和 getter 方法,这就是为什么不在 data 中声明的属性无法自动拥有双向绑定效果的原因 。这时需要调用 Vue.set() 手动设置。
针对 Array 类型的劫持
数组是一种特殊的对象,其下标实际上就是对象的属性,所以理论上是可以采用 Object.defineProperty() 方法处理数组对象。
但是 Vue 并没有采用上述方法劫持数组对象,原因分析:1、特殊的 length 属性,相比较对象的属性,数组下标变化地相对频繁,并且改变数组长度的方法也比较灵活,一旦数组的长度发生变化,那么在无法自动感知的情况下,开发者只能手动更新新增的数组下标,这可是一个很繁琐的工作。2、数组主要的操作场景还是遍历,而对于每一个元素都挂载一个 get 和 set 方法,恐怕也是不小的性能负担。
数组方法的劫持:最终 Vue 选择劫持一些常用的数组操作方法,从而知晓数组的变化情况:push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'。数组方法的劫持涉及到原型相关的知识,首先数组实例大部分方法都是来源于 Array.prototype 对象。顺便提一下,采用 Vue.set() 方法设置数组元素时,Vue 内部实际上是调用劫持后的 splice() 方法来触发更新。
总结:由上述内容可知,Vue 中的数据劫持分为两大部分:
针对 Object 类型,采用 Object.defineProperty() 方法劫持属性的读取和设置方法;
针对 Array 类型,采用原型相关的知识劫持常用的函数,从而知晓当前数组发生变化。
并且 Object.defineProperty() 方法存在以下缺陷:每次只能设置一个具体的属性,导致需要遍历对象来设置属性,同时也导致了无法探测新增属性;属性描述符 configurable 对其的影响是致命的。
*Object.keys(obj)
参数:要返回其枚举自身属性的对象
返回值:一个表示给定对象的所有可枚举属性的字符串数组
Vue双向绑定原理(我尽量写的。简洁)的更多相关文章
- Vue双向绑定原理,教你一步一步实现双向绑定
当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...
- vue双向绑定原理及实现
vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...
- vue双向绑定原理分析
当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...
- Vue双向绑定原理(源码解析)---getter setter
Vue双向绑定原理 大部分都知道Vue是采用的是对象的get 和set方法来实现数据的双向绑定的过程,本章将讨论他是怎么利用他实现的. vue双向绑定其实是采用的观察者模式,get和s ...
- vue 学习二 深入vue双向绑定原理
vue双向绑定原理 请示总体来讲 就是为data的中的每个属性字段添加一个getter/seter属性 以此来追踪数据的变化,而执行这部操作,依赖的就是js的Object.defineProperty ...
- [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅
有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...
- vue双向绑定原理
要了解vue的双向绑定原理,首先得了解Object.defineProperty()方法,因为访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过 Object.definePrope ...
- Vue双向绑定原理及其实现
在之前面试的时候被面试官问到是否了解Vue双向绑定的原理,其实自己之前看过双向绑定的原理,但也就是粗略的了解,但是没有深入.面试官当时让我手写一个原理,但是就蒙了
- 通俗易懂了解Vue双向绑定原理及实现
看到一篇文章,觉得写得挺好的,拿过来给大家分享一下,刚好解答了一些困扰我的一些疑惑!!! 1. 前言 每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue内部通过Object.defi ...
随机推荐
- 利用Chrome开发者工具功能进行网页整页截图的方法
第一步:打开chrome开发者工具. 打开你想截图的网页,然后按下 F12(macOS 是 option + command + i)调出开发者工具,接着按「Ctrl + Shift + P」(mac ...
- Java生鲜电商平台-商品价格的设计与架构
Java生鲜电商平台-商品价格的设计与架构 说明:Java开源生鲜电商平台-商品价格的设计与架构,主要是对商品的价格进行研究与系统架构. 一.常见的电商价格 市场价(List Price):这个价格仅 ...
- Windows下实现应用程序看门狗软件
有时候,我们需要确保一个应用程序长期稳定地运行,但是在Windows平台上由于种种原因,几乎不可能保障一个应用的绝对可靠运行.那么,我们就需要有一个机制,在应用死机﹑异常﹑系统重启等情况下自我恢复,而 ...
- 将多个sass文件合并到一个文件中
将多个sass文件合并到一个文件中 应用场景:制作angular npm包的时候,定义的一些全局样式,自定义主题色这类的情况下,多个scss文件会要合并成一个文件并写到dist文件里,发布到仓库中. ...
- ES6 入门系列 ArrayBuffer
由来 推荐在这里阅读 js操作二进制数据三兄弟 ArrayBuffer对象, TypeArray视图和DataView视图 它们都以数组的语法处理二进制数据,所以统称为二进制数组 ::: tip 二进 ...
- FCC-学习笔记 Sorted Union
FCC-学习笔记 Sorted Union 1>最近在学习和练习FCC的题目.这个真的比较的好,推荐给大家. 2>中文版的地址:https://www.freecodecamp.cn/; ...
- maven 学习---使用Maven运行单元测试
要通过Maven运行单元测试,发出此命令: mvn test 这会在你的项目中运行整个单元测试. 案例学习 创建两个单元测试,并通过 Maven 的运行它.参见一个简单的 Java 测试类: pack ...
- 谁有好的oracle数据库学习书籍,麻烦提供一下,感激不尽
作为一个IT人员,想深入学习一下oracle,以前都只是懂基本的语法,CRUD 数据库设计,数据库优化,底层完全不懂,哪位仁兄有好的书籍可以推荐一下,感激不尽.
- [TCP/IP] ping traceroute和TTL
1.Time To Live是生存时间的意思,就是说这个ping的数据包能在网络上存在多少时间.当我们对网络上的主机进行ping操作的时候,我们本地机器会发出一个数据包,数据包经过一定数量的路由器传送 ...
- 1-9 Python判断结构
判断结构¶ In [3]: tang=100 if tang>200: print('OK') print('test')##有缩进就不在就不在if条件结构中 test In [6]: ...
