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 ...
随机推荐
- 修复dtcms5.0后台管理编辑器上传视频和图片被过滤问题
1.原程序代码调用上传接口文件路径更改为父节点相对路径: 2.修复ueditor.config.js配置: img: ['src', 'alt', 'title', 'width', 'height' ...
- c# 如何获取当前方法的调用堆栈
c# 调试程序时常常需要借助 call stack 查看调用堆栈,实际上通过code也可以获取: class Program { static void Main(string[] args) { T ...
- 面向对象的六大原则之 接口隔离原则——ISP
ISP = Interface Segregation Principle ISP的定义如下: 1.客户端不应该依赖他不需要的接口 2.一个类对另外一个类的依赖性应该是建立在最小的接口上 3.不应 ...
- 基础系列(2)--- css1
css组成 css语法组成 选择器 和 声明 (多个声明用分号隔开) 声明包括 属性和属性值(多个属性值用空格隔开) 语法: 选择器{ 属性: 属性值; 属性: 属性值1 属性值2; } css样式表 ...
- 四.Windows基础
系统目录 Windows Program files 用户 Perflogs:是Windows7的日志信息,如磁盘扫描错误信息,删掉可以,但不建议删,删掉反而降低系统速度,perflogs是系统自动生 ...
- weblogic 安全漏洞问题解决
1 weblogic控制台地址暴露 ² 整改方法: 禁用weblogic控制台.在weblogic域(sguap-domain和base-domain)的config下的config.xml中 &l ...
- sparkSQL中的example学习(1)
SparkSQLDemo.scala import org.apache.spark.sql.{Row, SparkSession} import org.apache.spark.sql.types ...
- python实现LRU热点缓存
基于列表+Hash的LRU算法实现. 访问某个热点时,先将其从原来的位置删除,再将其插入列表的表头 为使读取及删除操作的时间复杂度为O(1),使用hash存储热点的信息的键值 class LRUCac ...
- Phoenix 无法启动报错: java.net.BindException: Address already in use
一.问题描述 i. 登录Ambari发现有一个节点的 Phoenix 无法启动 ii. 在Ambari上点击“Start”,监控 Phoenix 日志文件 iii. Phoenix 日志如下: [ro ...
- linux 进程通信之 信号
一,管道PIPE 二,FIFO通信 三,mmap通信 四,信号的概念 信号的特点:简单,但不能携带大量的信息,满足特定条件就会发生 信号的机制:进程B发送信号给进程A.信号是由内核来处理的. 信号的产 ...