根据前几节课,相信大家都明白的vue的基本原理 能够实现vue响应及渲染 这如果还不清楚的 请看上几篇文章

这节课 我们讲解vue中数据的响应实现 即vue中的观察模式 如果还不明白观察模式的 也请看我的文章详解js中观察模式和订阅发布模式的区别

Dep(Dependency)

功能

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

结构

下面是代码的基本实现

  // 要实现数据的响应机制 即数据变化 视图变化// 在vue的响应机制中 我们要使用观察模式来监听数据的变化 // 因此 在vue中我们要实现Dep和watcher  Dep的主要作用是收集依赖 在vue中的每一个响应属性 都会创建一个dep对象 负责手机依赖于该属性的所有依赖 即订阅者 并在数据更新时候发布通知  调用watcher对象中的update方法去更新视图 简单说明就是在数据劫持监听中的get去添加依赖 在set中去发布通知 class Dep {// 存储所有观察者constructor() {this.subs = []}// 添加观察者addSub(sub) {if (sub && sub.update) {this.subs.push(sub)}}// 发布通知notify() {this.subs.forEach(sub => {sub.update()})}}

在 compiler类 中收集依赖,发送通知

// defineReactive 中 // 创建 dep 对象收集依赖
const dep = new Dep()
// getter 中 // get 的过程中收集依赖 Dep.target && dep.addSub(Dep.target)
// setter 中 // 当数据变化之后,发送通知
dep.notify()

Watcher


功能

  • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
  • 自身实例化的时候往 dep 对象中添加自己

结构


```cpp
//具体代码实现如下
class Watcher {constructor(vm, key, cb) {this.vm = vm;// data中的属性名称this.key = key;// 回调函数 负责更新视图this.cb = cb;// 把watcher对象记录到Dep类的静态属性target中Dep.target = this;// 触发get方法 在get方法中调用addSubthis.oldValue = vm[key]Dep.target = null;}// 当数据发生变化的时候 更新视图update() {let newValue = this.vm[this.key];if (newValue === this.oldValue) {return}this.cb(newValue)}}

在 compiler类 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化

// 因为在 textUpdater等中要使用
this updaterFn && updaterFn.call(this, node, this.vm[key], key)
// v-text 指令的更新方法
textUpdater (node, value, key) { node.textContent = value }
// 每一个指令中创建一个 watcher,观察数据的变化
new Watcher(this.vm, key, value => { node.textContent = value }) }

附上完整的代码

    // 具体实现步骤// 1: 通过属性  保存选项的数据// 2: 把data中的成员 转换为getter和setter  注入到vue实例中 方便使用// 3:调用observer对象 监听数据变化// 4:调用compiler 解析指令和插值表达式class Vue {constructor(options) {// 通过属性  保存选项的数据this.$options = options || {};//如果我们在调用vue构造函数的时候 没有传入参数 我们初始化一个空对象this.$data = options.data || {};this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el;//如果我们是传入的选择器 则将选择器转换为dom对象// 把data中的成员 转换为getter和setter  注入到vue实例中 方便使用this._proxyData(this.$data)// 调用observer对象 监听数据变化new Observer(this.$data)// 调用compiler 解析指令和插值表达式 new Compiler(this)}_proxyData(data) {//vue传过来的参数 转换为getter和setter// 遍历data的所有属性Object.keys(data).forEach(key => {Object.defineProperty(this, key, {// 可遍历 可枚举enumerable: true,configurable: true,get() {return data[key]},set(newValue) {if (newValue === data[key]) {return} else {data[key] = newValue;}}})})// 把data中的属性 注入到vue实例中}}class Observer {constructor(data) {this.walk(data)}// 1. 判断数据是否是对象,如果不是对象返回 // 2. 如果是对象,遍历对象的所有属性,设置为 getter/setterwalk(data) {if (!data || typeof data != 'object') {return}Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key])})}//  定义响应式成员  即对data总的数据实现setter和getterdefineReactive(data, key, val) {//负责收集依赖 并发布通知let dep = new Dep()const that = this// 如果 val 是对象,继续设置它下面的成员为响应式数据 this.walk(val)Object.defineProperty(data, key, {enumerable: true,configurable: true,get() {// 收集依赖Dep.target && dep.addSub(Dep.target)return val;},set(newValue) {if (val === newValue) {return}// 如果 newValue 是对象,设置 newValue 的成员为响应式 that.walk(newValue)//这里不用this  因为在set方法中 在function的内部 会开启新的作用域 此时的this执行data对象   val = newValue;// 发布通知dep.notify()}})}}class Compiler {constructor(vm) {this.el = vm.$el;this.vm = vm;this.compile(this.el);}// 编译模板 处理文本节点和元素节点compile(el) {const nodes = el.childNodes;Array.from(nodes).forEach(node => {if (this.isElementNode(node)) {this.compileElement(node);} else if (this.isTextNode(node)) {this.compileText(node)}// 如果当前节点中还有子节点,递归编译if (node.childNodes && node.childNodes.length) {this.compile(node)}})}// 编译元素节点 处理指令compileElement(node) {// 暂时只涉及v-text和v-model//  逻辑思路 首先我们获取node节点所有的属性 查找到我们需要的指令 并用值替换// 下面我们打印一下node的所有属性 具体可切换google浏览器查看// console.log(node.attributes)// 遍历所有的属性节点Array.from(node.attributes).forEach(attr => {//Array.from 将伪数组转换为数组let attrName = attr.name;if (this.isDirective(attrName)) {// 在指令中 有v-text  v-model等各种各样的指令 我们不能用if语句判断 如果用if 在后期 如果增加了别的指令 则不便于维护 需要手动增加if判断// 因此 我们讲指令的v-去掉  只保留后面部分 即:v-text -> text  v-model ->modelattrName = attrName.substr(2)let key = attr.valuethis.update(node, key, attrName)}})// 判断是否是指令}// 通过update方法拼接处对应指令对应的方法 方便后序或者 如:v-text指令 则为textUpdater方法  v-model指令 则为modelUpdater方法 Updater字符串固定  因此 在后续 如果追加的不同的指令  执行增加指令名+Updater凭借的方法// 通过update方法拼接处对应指令对应的方法 方便后序或者 如:v-text指令 则为textUpdater方法  v-model指令 则为modelUpdater方法 Updater字符串固定  因此 在后续 如果追加的不同的指令  执行增加指令名+Updater凭借的方法update(node, key, attrName) {let updateFun = this[attrName + 'Updater']// updateFun && updateFun(node, this.vm[key])//将这里使用call改变this 引入这里的updateFun是直接调用 this对象不是compiler 为了在modelUpdater和textUpdater方法中的this指向是compiler而不是window 所以使用call将updateFun的this指向改为compiler// 增加key参数 是为了在textUpdater和modelUpdater方法中能有key使用updateFun && updateFun.call(this,node, this.vm[key], key)}textUpdater(node, value, key) {node.textContent = valuenew Watcher(this.vm, key, (newValue) => {node.textContent = newValue;})}modelUpdater(node, value, key) {node.value = valuenew Watcher(this.vm, key, (newValue) => {node.value = newValue;})}// 编译文本节点 处理插值表达式compileText(node) {// console.dir(node)// {{ msg }}  解析: 插值表达式 是双括号中奖有一个变量 变量的前后可能有空格 可能无空格 可能有多个空格  我们需要将变量提出来 因此用正则表达式// 详解{{ msg }}案例正则使用规则// 1:正则的使用规范 首先用//表示正则的开始和介绍// 2:插值表达式中有{{}},即前后两个大括号  因此这里的正则是 /{{}}/// 3: 犹豫{是特殊符号 因此需要转义符\ 故正则是 /\{\{\}\}/// 4: 匹配花括号 即{{}}中间的名字 而变量的名字前面都可能有空格 故:我们需要匹配任意的字符 使用点表示 即 '.' 点表示匹配任意的单个字符 ,不包括换行 后面跟上'+', 加号表示以前修饰的内容出现一次或者多次  因此 .+就可以匹配变量的名字 故 正则为: /\{\{.+\}\}/ // 5: 我们在.+后面跟上问好'?' 表示今早的来结束匹配 故 正则为:/\{\{.+?\}\}/// 6: 通过/\{\{.+?\}\}/ 我们可以匹配到变量的名字 现在我们需要将匹配的变量名字提取出来 也就是把".+?"的这个位置的内容提取出来 在正则表达式中 这个非常简单 我们只需要需要提取内容的位置加上小括号(),小括号在正则中有分组的含义 我们可以获取到分组中的结果 最终的正则为/\{\{(.+?)\}\}/let reg = /\{\{(.+?)\}\}/let value = node.textContent;//获取文本的内容if (reg.test(value)) {let key = RegExp.$1.trim();//这里使用正则对象的构造函数 来获取第一个分组的内容 这里$1表示第一个分组的内容 如果要获取第二个 则用$2 获取之后 可能有空格 因此使trim去掉空格node.textContent = value.replace(reg, this.vm[key]);//replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。new Watcher(this.vm, key, (newValue) => {node.textContent = newValue;})}}// 判断元素是否是指令isDirective(attrName) {return attrName.startsWith('v-')}// 判断元素是否是元素节点isElementNode(node) {return node.nodeType === 1;}// 判断元素是否是文本节点isTextNode(node) {return node.nodeType === 3;}}// 要实现数据的响应机制 即数据变化 视图变化// 在vue的响应机制中 我们要使用观察模式来监听数据的变化 // 因此 在vue中我们要实现Dep和watcher  Dep的主要作用是收集依赖 在vue中的每一个响应属性 都会创建一个dep对象 负责手机依赖于该属性的所有依赖 即订阅者 并在数据更新时候发布通知  调用watcher对象中的update方法去更新视图 简单说明就是在数据劫持监听中的get去添加依赖 在set中去发布通知 class Dep {// 存储所有观察者constructor() {this.subs = []}// 添加观察者addSub(sub) {if (sub && sub.update) {this.subs.push(sub)}}// 发布通知notify() {this.subs.forEach(sub => {sub.update()})}}//数据变化 watcher去更新视图// 当我们去创建一个watcher对象时 需要把自己添加到自己的主题对象中去class Watcher {constructor(vm, key, cb) {this.vm = vm;// data中的属性名称this.key = key;// 回调函数 负责更新视图this.cb = cb;// 把watcher对象记录到Dep类的静态属性target中Dep.target = this;// 触发get方法 在get方法中调用addSubthis.oldValue = vm[key]Dep.target = null;}// 当数据发生变化的时候 更新视图update() {let newValue = this.vm[this.key];if (newValue === this.oldValue) {return}this.cb(newValue)}}let vm = new Vue({el: '#app',data: {msg: 'hello',count: 123,person: {name: 'zs'}}})// vm.msg = { 'sex': 'ada' }

上一篇:详解vue原理中的编译compiler
下一篇:

详解vue原理之观察模式Dep->Watcher相关推荐

  1. 详解Vue响应式原理

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

  2. LVS原理详解(3种工作模式及8种调度算法)

    2017年1月12日, 星期四 LVS原理详解(3种工作模式及8种调度算法) LVS原理详解及部署之二:LVS原理详解(3种工作方式8种调度算法) 作者:woshiliwentong  发布日期:20 ...

  3. 图文详解 epoll 原理【Redis,Netty,Nginx实现高性能IO的核心原理】epoll 详解

    [Redis,Netty,Nginx 等实现高性能IO的核心原理] I/O 输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe).在li ...

  4. 实例化vue发生了什么?(详解vue生命周期)

    实例化vue发生了什么?(详解vue生命周期) 本文将对vue的生命周期进行详细的讲解,让你了解一个vue实例的诞生都经历了什么~ 我在Github上建立了一个存放vue笔记的仓库,以后会陆续更新一些 ...

  5. P2P技术详解(一):NAT详解——详细原理、P2P简介(转)

    这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层开发人员而言有很高的参考价值. <P2P技术详解>系列文章 ➊ 本 ...

  6. 015. P2P技术详解(一):NAT详解——详细原理、P2P简介

    http://www.52im.net/thread-50-1-1.html 这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层 ...

  7. vue单文件props写法_详解Vue 单文件组件的三种写法

    详解Vue 单文件组件的三种写法 JS构造选项写法 export defaul { data, methods, ...} JS class写法 @Component export default c ...

  8. vue 加载页面时触发时间_详解Vue.js在页面加载时执行某个方法

    详解Vue.js在页面加载时执行某个方法 jQuery中可以这样写 vue中,如果要达到相同效果,可以使用vue的生命周期函数,如create或者mounted 附上vue.js的生命周期函数执行流程 ...

  9. 思科考试注册流程详解----VUE考点现场演示-晁海江-专题视频课程

    思科考试注册流程详解----VUE考点现场演示-5201人已学习 课程介绍         更多课程,请百度搜索"晁海江". 备注:注册过程中,使用的姓名.电话.思科ID等,均是临 ...

  10. js定义全局变量 vue页面_详解Vue.js 定义全局变量的几种实现方式

    详解Vue.js 定义全局变量的几种实现方式 发布于 2020-8-11| 复制链接 本篇文章主要介绍了VUE 全局变量的几种实现方式,小妖觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小妖 ...

最新文章

  1. pandas读取多个文件内容为dataframe、并合并为一个dataframe、pandas创建仅有列标签而内容为空的dataframe
  2. 动手学深度学习(二)
  3. matlab自动给变量命名
  4. Java连接SQL Server 2012【查看自己电脑上的SQL Server端口号;附:jar包】
  5. Visual Studio Online 的 FAQ:iPad 支持、自托管环境、Azure 账号等
  6. 蓝屏分析_电脑突发蓝屏现象?教你如何快速修复
  7. 执行一次怎么会写入两次数据_Java进阶知识:一文详解缓存Redis的持久化机制,新手看完也会用
  8. ArrayList和LinkedList的插入删除性能差距到底有多大
  9. webRTC之智能指针std::unique_ptr::reset()使用(十四)
  10. 算法常用术语中英对照
  11. 网站制作笔记一域名购买与主机备案
  12. 内网,外网ip(路由器ip,公网ip)的分别以及如何查询
  13. Linux修改固定ip 地址,亲测有效
  14. HTML5射击鸭子小游戏
  15. 根据起始时间和流逝的时间计算出终止时间(C语言)
  16. 读河南干旱帖有感而发的一天(20191006)
  17. 官宣一一塔米狗企业并购图谱功能上线啦
  18. 【VUE】在vue中使用google地图
  19. 极客头条:5月25日科技要闻 | 华为自研操作系统已注册;大疆回应信息泄露;2019 新款 iPhone 曝光
  20. python自动获取微信公众号最新文章

热门文章

  1. log4j2配置详解(节点和输出格式)
  2. 怎么将CAD中的两条直线拉成弧形呢?
  3. idea运行web项目光标乱跳
  4. ValueError: operands could not be broadcast together with shapes、numpy广播错误
  5. netware显示没有首选服务器,NetWare下服务器配置几例
  6. 同步时钟与异步时钟介绍
  7. ios7下弹出新浪微博界面,一出现就消失的问题
  8. 华御上网行为管理FAQ
  9. 华为网络设备配置子接口
  10. 蒋清野《虚拟化、云计算、开放源代码及其他》