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 ...
随机推荐
- Winform 窗体皮肤美化_IrisSkin
1 先把IrisSkin2.dll文件添加到当前项目引用(解决方案资源管理器->当前项目->引用->右键->添加引用,找到IrisSkin2.dll文件.....之后就不用我说 ...
- Linux入门——初识Linux
Linux入门——初识Linux 摘要:本文主要说明了Linux是什么,Linux发展历史,以及同Linux系统有关的一些基本知识. 简介 操作系统 Linux系统同Windows系统.Mac系统一样 ...
- ES6 入门系列 ArrayBuffer
由来 推荐在这里阅读 js操作二进制数据三兄弟 ArrayBuffer对象, TypeArray视图和DataView视图 它们都以数组的语法处理二进制数据,所以统称为二进制数组 ::: tip 二进 ...
- Java的 Annotation 新特性
对于软件程序的开发经过了三个发展过程: —— 将所有配置相关的内容直接写到代码之中 —— 将配置与代码程序独立,将程序运行的时候根据配置文件进行操作 —— 配置信息对用户透明且无用,将配置信息写回代码 ...
- 【已采纳】新项目第一次怎么上传到github里面
言归正传,最近学习了怎么将新创建的本地代码上传到github上,这里简单的记录一下,我喜欢使用命令行,这里全用命令行来实现,不了解git命令的可以去了解下. 第一步:建立git仓库 cd到你的本地 ...
- SQL 触发器 新建时删除相同数据
--create alter trigger [dbo].[trigger_sqsj] on [dbo].[lctnrcrd] after INSERT as BEGIN ) id ),dlr,) d ...
- angular6 使用信息提示框toast
angular6 可以使用的toast插件有好多个,在目前来看ngx-toastr在过去一年时间的使用量和受欢迎程度可以说是一骑绝尘,如下图: 我也就选择了ngx-toastr这个插件,使用步骤如下: ...
- 002-Zabbix 前端配置
Zabbix 前端配置 [基于此文章的环境]点我快速打开文章 [官方地址]点我快速打开文章 第 1 步 在浏览器中,打开 Zabbix URL:http:// <server_ip_or_nam ...
- Rust中的所有权,引用和借用
这个有意思,指针解释获新生!!! fn main() { let mut s = String::from("hello"); s.push_str(", world!& ...
- re 模块详解
1.re 模块 regex 1.查找 :findall 意思"匹配所有,每一项都是列表的组成元素" 有返回值 import re ret=re.findall("\d+& ...