先问自己两个问题: 
    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来劫持对象属性的settergetter操作,并“种下”一个监听器,当数据发生变化的时候发出通知。

注意: 该方法每次只能设置一个属性,那么就需要遍历对象来完成其属性的配置:

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双向绑定原理(我尽量写的。简洁)的更多相关文章

  1. Vue双向绑定原理,教你一步一步实现双向绑定

    当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...

  2. vue双向绑定原理及实现

    vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...

  3. vue双向绑定原理分析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...

  4. Vue双向绑定原理(源码解析)---getter setter

       Vue双向绑定原理      大部分都知道Vue是采用的是对象的get 和set方法来实现数据的双向绑定的过程,本章将讨论他是怎么利用他实现的. vue双向绑定其实是采用的观察者模式,get和s ...

  5. vue 学习二 深入vue双向绑定原理

    vue双向绑定原理 请示总体来讲 就是为data的中的每个属性字段添加一个getter/seter属性 以此来追踪数据的变化,而执行这部操作,依赖的就是js的Object.defineProperty ...

  6. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  7. vue双向绑定原理

    要了解vue的双向绑定原理,首先得了解Object.defineProperty()方法,因为访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过 Object.definePrope ...

  8. Vue双向绑定原理及其实现

    在之前面试的时候被面试官问到是否了解Vue双向绑定的原理,其实自己之前看过双向绑定的原理,但也就是粗略的了解,但是没有深入.面试官当时让我手写一个原理,但是就蒙了

  9. 通俗易懂了解Vue双向绑定原理及实现

    看到一篇文章,觉得写得挺好的,拿过来给大家分享一下,刚好解答了一些困扰我的一些疑惑!!! 1. 前言 每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue内部通过Object.defi ...

随机推荐

  1. 大话区块链【Blockchain】

    最近这几天区块链又粉墨登场了,新闻媒体也一直在大量报道,宣称可能要在金融界掀起一番浪潮.甚至有人说很久之前中国就出现了区块链的产物——麻将.那么区块链到底是什么,麻将和区块链又有什么关系呢? 笔者这两 ...

  2. python 父类方法中使用不同的子类中的不同类对象

    # coding:utf-8 class Animal(object): def __init__(self): self._name = None self._f = None def eat(se ...

  3. Java编程基础——标识符和关键字

    Java编程基础——标识符和关键字 摘要:本文主要介绍标识符和关键字. 标识符 是什么 Java语言中,为各种变量.方法.类和包等起的名字,统统称之为Java标识符. 命名规则 ◆ 应以字母.下划线. ...

  4. 工作笔记--adb命令篇

    1.抓log方法 (bat文件) mkdir D:\logcatset /p miaoshu=请描述操作:adb logcat -v threadtime > D:\logcat\%miaosh ...

  5. Celery 异步队列

    Celery Celery是一个功能完备即插即用的异步任务队列系统.它适用于异步处理问题,当发送邮件.或者文件上传, 图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用 ...

  6. 删除Win10远程桌面中的无用的IP列表

    运行中,输入regedit,然后找到这个位置(也可在任务管理器的地址栏中,直接输入下面的地址),便可删除远程桌面中列出的一些无用的IP地址. 计算机\HKEY_CURRENT_USER\Softwar ...

  7. 构建maven项目,自定义目录结构方法

    构建maven项目 创建自定义的文件目录方法: 在项目名称右键-->Builder Path-->Configure Builder Path...Source菜单下的Add Folder ...

  8. Delphi Webbrowser使用方法详解

    1.webbroser介绍 该组件是一个浏览器组件,可以显示一个指定地址的网页.设置网页打开时的主页以及对网页进行相关的操作,同时也可以对HTML文件进行剪切.复制.粘贴.删除等操作.该 组件在Int ...

  9. js中this由来

    这篇文章主要是讲述js中的this是什么?是怎么来的? 我们首先创建一个person对象,如下: var person = { name: 'wyh', age: 22, sayHi: functio ...

  10. 【设计模式】Prototype

    前言 这篇讲设计模式的部分相对较少.Prototype设计模式,它提供一种复制对象的思路.使用Prototype就可以在不需要了解类结构的前提下,复制一个现有对象.写了一个代码片段,讲解使用Objec ...