在文章 源码学习VUE之响应式原理我们大概描述了响应式的实现流程,主要写了observe,dep和wather的简易实现,以及推导思路。但相应代码逻辑并不完善,今天我们再来填之前的一些坑。

Observe

之前实现的observe函数只能处理一个对象的单个属性,但我们更多的数据是保存在对象中,为了抽象话,我们也封装一个对象Observe,只要传进一个参数,就可以把这个对象进行监听。

对现有所有属性进行监听

var obj = {
a: 1,
b: 2
}

比如一个对象有两个属性 a,b。我们可以尝试写出下面的实现类

class Observe{
constructor(value){
this.value = value //要监听的值。
this.walk();
} walk(){ //通过walk函数,依次处理
const keys = Object.keys(obj);
let self = this;
for (let i = 0; i < keys.length; i++) {
self.defineReactive(obj, keys[i])
}
} defineReactive (data, key, val) {
var dep = new Dep();
Object.defineProperty(obj, a, {
enumerable: true,
configurable: true,
get: function(){
if(Dep.target){
dep.addSub(Dep.target); // Dep.target是Watcher的实例
}
},
set: function(newVal){
if(val === newVal) return
val = newVal;
dep.notify();
}
})
}
}

当然,为了防止重复监听,我们可以给原object设置一个标识符以作辨别。

class Obsever(){
construct(){
this.value = value //要监听的值。
Object.defineProperty(value, "__ob__", {
value: this,
enumerable: false,
writable: true,
configurable: true
})
this.walk();
}
}

监听数组

虽然数组也是一个对象,但是我们队数组的操作却不会触发set,get方法。因此必须对数组特殊处理。
首先需要对操作数组的方法进行改写,如push,pop,shift

//首先拿到Array的原生原型链
const arrayProto = Arrary.prototype;
//为了保证修改不会影响原生方法,我们创建一个新对象
const arrayMethods = Object.create(arrayProto);
//要改写的方法
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
methodsToPatch.forEach(function (method) {
const original = arrayProto[method] // 先拿到原生方法
def(arrayMethods, method, function mutator (...args) {
// 改写后的方法,都是先拿到原生方法的计算结果
const result = original.apply(this, args)
const ob = this.__ob__
// 拿到插入的值。
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
//Observe插入的值
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})

其实逻辑很简单。对于可以改变array的方法我们都改写一下。只要调用了这些方法,除了返回正确的值,我们都通知观察对象,数据改变了,触发观察者update操作。同时,数组里面可能是个对象,我们不改变数组本身,但是改变数组里面的某个值,这也算是一种改变,因此,除了监听数组本身的改变,也要对数组每个值进行observe。
这涉及到两点,一是observe Array的时候,就要对每个值进行Observe。另外,插入数组的每个值也要observe.第二点就是上面代码中特别关注push,unshift,splice这三个可以插值方法的原因。

class Obsever(){
construct(){
this.value = value //要监听的值。
Object.defineProperty(value, "__ob__", {
value: this,
enumerable: false,
writable: true,
configurable: true
})
if(Array.isArray(value)){
this.observeArray();
}else{
this.walk();
}
}
observeArray(items){
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}, function observe (value) {
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 如果已经observe的对象就不再进行重复的observe操作
ob = value.__ob__
} else {
ob = new Observer(value)
}
return ob
}

优化

实际开发中我们经常会遇到一个很大的数据。如渲染tables时,table的数据很可能很大(一个多多维数组)。如果都进行observe无意会是很大的开销。关键是我们只是需要拿这些数据来渲染,并不关心数据内部的变化。因此可能就存在这种需求,可以不对array或object深层遍历observe。我们可以使用Object.freeze()将这个数据冻结起来。
因此对于冻结的数据我们就不再进行observe。上面的代码可以这么优化

function observe (value) {
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 如果已经observe的对象就不再进行重复的observe操作
ob = value.__ob__
} else if(Object.isExtensible(value)){// 如果数据被冻结,或者不可扩展,则不进行observe操作
ob = new Observer(value)
}
return ob
} defineReactive (data, key, val) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key)
// 如果数据被冻结,或者不可扩展,则改写set,get方法
if (property && property.configurable === false) {
return
}
//传进来的对象可能之前已经被定义了set,get方法,因此我们不能直接拿value
var getter = property && property.get
var setter = property && property.set
Object.defineProperty(obj, a, {
enumerable: true,
configurable: true,
get: function(){
var value = getter ? getter.call(obj) : val;
if(Dep.target){
dep.addSub(Dep.target); // Dep.target是Watcher的实例
}
return value
},
set: function(newVal){
if(val === newVal) return
val = newVal;
dep.notify();
}
})
}

最新文章

  1. Office文档在线预览
  2. SQL Server遍历表的几种方法
  3. linux中的内存申请函数的区别 kmalloc, vmalloc
  4. 黑马程序员_java08_多线程
  5. MAC中设置java环境变量和MAVEN
  6. java切换VPN让你像幽灵一样出现在全国各地
  7. Android activity_main.xml删除边缘距离,充满屏幕
  8. ti processor sdk linux am335x evm /bin/unshallow-repositories.sh hacking
  9. careercup-C和C++ 13.2
  10. 2014/08/24——升级stepbystep修复tc不刷新问题并加入杭电bc
  11. XAF-列表视图数据访问模式
  12. 使用RandomAccessFile在两个java进程之间传递数据
  13. 2018-2019-2 网络对抗技术 20165206 Exp2 后门原理与实践
  14. Asp.Net WebApi 使用OWIN架构后,出现 “没有 OWIN 身份验证管理器与此请求相关联(No OWIN authentication manager is associated with the request)” 异常的解决办法
  15. 游戏脚本编程 文本token解析
  16. ceph rgw multisite基本用法
  17. 如何让cxgrid自动调整列宽
  18. CentOS 7通过Firewall开放防火墙端口
  19. Introduction to the Optimizer --cbo
  20. gzip命令详解

热门文章

  1. docker-compose容器中redis权限问题
  2. MySQL入门,第八部分,多表查询(二)
  3. 二、Python2.7的安装并与Python3.8共存
  4. java异常处理:finally中不要return
  5. shell执行${var:m:n}报错Bad substitution解决办法
  6. JS数据结构与算法 - 剑指offer二叉树算法题汇总
  7. 搭建vue2.0开发环境及手动安装vue-devtools工具
  8. 06-移动web之flex布局
  9. 安装JDK后,未设置Path,也能执行java.exe的原因
  10. elasticsearch7.6.2实战(2)-es可视化及分析平台-kibana