找回密码
 立即注册
查看: 117|回复: 9

Vue2 Observer 构造函数 - 对象响应的实现

[复制链接]

9

主题

37

回帖

151

积分

注册会员

积分
151
发表于 2022-9-19 21:54:54 | 显示全部楼层 |阅读模式
本帖最后由 13824597405 于 2022-9-19 22:05 编辑

Observer 构造函数的主要作用是把每个要被观察的数据添加到响应式系统, 将该数据的属性转为getter/setters, 收集依赖和派发更新
Observer 构造函数的代码结构很简单
  • value 属性 要将入响应式系统的数据
  • dep 属性 依赖收集
  • vmCount 属性 统计响应式数据
  • walk 原型上的方法
  • observeArray 原型上的方法

回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 21:55:13 | 显示全部楼层
代码
  1. var Observer = function Observer(value) {
  2.     /* 获取到传入过来的value值 */
  3.     this.value = value;
  4.     /* 依赖收集 */
  5.     this.dep = new Dep();
  6.     /*统计被观察的数据的个数*/
  7.     this.vmCount = 0;
  8.     /* 给value添加 __ob__的数据, 目的是让__ob__变成不可枚举的属性 */
  9.     def(value, '__ob__', this);
  10.     /*value不仅是数组,还可以是对象*/
  11.     if (Array.isArray(value)) {
  12.         if (hasProto) {
  13.             protoAugment(value, arrayMethods);
  14.         } else {
  15.             copyAugment(value, arrayMethods, arrayKeys);
  16.         }
  17.         this.observeArray(value);
  18.     } else {
  19.         this.walk(value);
  20.     }
  21. };
  22. /*将对象类型的属性转为getter/setters*/
  23. Observer.prototype.walk = function walk(obj) {
  24.    var keys = Object.keys(obj);
  25.    for (var i = 0; i < keys.length; i++) {
  26.        defineReactive$$1(obj, keys[i]);
  27.    }
  28. };
  29. Observer.prototype.observeArray = function observeArray(items) {
  30.      for (var i = 0, l = items.length; i < l; i++) {
  31.         observe(items[i]);
  32.      }
  33. };
复制代码

回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 21:56:03 | 显示全部楼层
在 Observer 构造函数里面, 调用了 def(value, '__ob__', this) 函数

  1. /* def(value, '__ob__', this) */
  2. function def(obj, key, val, enumerable) {
  3.     Object.defineProperty(obj, key, {
  4.         value: val,
  5.         enumerable: !!enumerable,
  6.         writable: true,
  7.         configurable: true
  8.     });
  9. }
复制代码

def 函数主要是配置 Object.defineProperty 里的 enumerable. 在 Observer 里面调用了 def,没传递 enumerable, 那么就是undefined, !!enumerable两次取反最终为false. 通过 for in 操作不可枚举出 __ob__ 属性
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 21:56:59 | 显示全部楼层
对象加入到响应式系统
经过 if...else...的判断, 如果是对象, 将调用

  1.    this.walk(value);
复制代码

通过实例调用, walk() 是定义在原型上的

  1. Observer.prototype.walk = function walk(obj) {
  2.     /* 获取到key的集合 */
  3.    var keys = Object.keys(obj);
  4.    /* 进行遍历 */
  5.    for (var i = 0; i < keys.length; i++) {
  6.         /*defineReactive$$1函数调用多少次, 取决于数据对象的key的个数*/
  7.         /*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
  8.        defineReactive$$1(obj, keys[i]);
  9.    }
  10. };
复制代码
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 21:57:56 | 显示全部楼层
walk 函数通过 Object.keys 收集 obj 的 key,对 keys 里面的每个key进行遍历, 调用了 defineReactive$$1() 函数.

  1. function defineReactive$$1( obj, key, val, customSetter, shallow ) {
  2.         /* dep是回调列表, 用来收集依赖 */
  3.         var dep = new Dep();
  4.         /* 获取属性描述对象, 获取用户是否已经添加了描述对象 */
  5.         var property = Object.getOwnPropertyDescriptor(obj, key);
  6.         /* 该属性的描述对象如果存在, 并且是不可以配置, 直接返回了 */
  7.         if (property && property.configurable === false) {
  8.             return
  9.         }

  10.         // cater for pre-defined getter/setters
  11.         /*获取 get 钩子函数*/
  12.         var getter = property && property.get;
  13.         /*获取 set 钩子函数*/
  14.         var setter = property && property.set;
  15.         if ((!getter || setter) && arguments.length === 2) {
  16.             val = obj[key];
  17.         }
  18.         /* 深度的观测 */
  19.         var childOb = !shallow && observe(val);
  20.         Object.defineProperty(obj, key, {
  21.             enumerable: true,
  22.             configurable: true,
  23.             /* 进行依赖的收集 */
  24.             get: function reactiveGetter() {
  25.                 ...
  26.                 return value
  27.             },
  28.             /* 调用依赖项 */
  29.             set: function reactiveSetter(newVal) {
  30.                ...
  31.             }
  32.         });
  33.     }
复制代码

defineReactive$$1 函数是整个响应式系统的核心, 将数据对象的属性转换为访问器属性, 给属性添加getter/setter
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 21:59:20 | 显示全部楼层
接下里分析 defineReactive$$ 1函数

  1. var property = Object.getOwnPropertyDescriptor(obj, key);
  2. if (property && property.configurable === false) {
  3.     return
  4. }
复制代码

Object.getOwnPropertyDescriptor 获取该属性的描述对象, 判断是否有 property, 并且判断该对象是否可以进行配置. 如果不能进行配置, 也就不能为改属性的定义
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 22:00:14 | 显示全部楼层
继续分析

  1. // cater for pre-defined getter/setters
  2. /*获取 get 钩子函数*/
  3. var getter = property && property.get;
  4. /*获取 set 钩子函数*/
  5. var setter = property && property.set;
  6. if ((!getter || setter) && arguments.length === 2) {
  7.    val = obj[key];
  8. }
  9. /* 深度的观测  */
  10. var childOb = !shallow && observe(val);
复制代码

用了 && 逻辑运算符, 如果 property 存在, property.get 也存在, getter 是 get 的钩子函数. 反之是 false. setter 也是同样的原理

可以这样想: 一个属性, 已经存在了 get 或者 set 钩子函数. if 的判断保证了响应系统数据的一致性
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 22:00:56 | 显示全部楼层
  1. var childOb = !shallow && observe(val);
复制代码

在调用 defineReactive$$1 的时候, 没有传递 shallow 形参, 是undefined, 为false . 进行取反. 此时的 val = obj[key] 的属性的值, 可能还是一个对象, 调用 observe 函数,进行深度观测 进行数据过滤和加入响应式系统
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 22:02:04 | 显示全部楼层
get 钩子函数如下:

  1. get: function reactiveGetter() {
  2.     var value = getter ? getter.call(obj) : val;
  3.     if (Dep.target) {
  4.         dep.depend();
  5.         if (childOb) {
  6.             childOb.dep.depend();
  7.             if (Array.isArray(value)) {
  8.                 dependArray(value);
  9.             }
  10.         }
  11.     }
  12.     return value
  13. }
复制代码

检测 getter 的钩子函数是否存在, 如果存在就直接调用, 获取返回值. 最终返回 到此开始进行依赖的收集, 进行 if 判断, Dep.target 存在, 将调用 dep.depend() 函数收集依赖. 如果 childOb 存在, 进行深度的依赖收集. 如果是一个数组 在另一篇帖子中会讲到
回复

使用道具 举报

9

主题

37

回帖

151

积分

注册会员

积分
151
 楼主| 发表于 2022-9-19 22:03:39 | 显示全部楼层
set钩子函数

  1. set: function reactiveSetter(newVal) {
  2.         var value = getter ? getter.call(obj) : val;
  3.         /* eslint-disable no-self-compare */
  4.         if (newVal === value || (newVal !== newVal && value !== value)) {
  5.             return
  6.         }
  7.         /* eslint-enable no-self-compare */
  8.         if (customSetter) {
  9.             customSetter();
  10.         }
  11.         // #7981: for accessor properties without setter
  12.         if (getter && !setter) {
  13.             return
  14.         }
  15.         if (setter) {
  16.             setter.call(obj, newVal);
  17.         } else {
  18.             val = newVal;
  19.         }
  20.         childOb = !shallow && observe(newVal);
  21.         dep.notify();
  22.     }
  23. });
复制代码

set 钩子函数在数据发生变化, 进行了依赖的触发.

  1. if (newVal === value || (newVal !== newVal && value !== value)) {
  2.     return
  3. }
复制代码

if 判断进行了性能的优化, 如果改变的值 newVal 和 value 相等, 没有必要进行依赖的触发. newVal !== newVal 自身与自身不相等, 只有NaN

  1. childOb = !shallow && observe(newVal);
复制代码

如果给当前的依赖进行设置newVal, 是一个引用类型, 继续进行 observe, 进行数据监测, 加入到响应式系统

  1. dep.notify();
复制代码

进行依赖的触发
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|粤嵌技术交流空间

GMT+8, 2025-7-7 22:32 , Processed in 0.677131 second(s), 18 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表