赞
踩
数据变化:
1、Object.defineProperty数据劫持:
每次访问或修改对象某个属性时,都能够被get或set捕捉到,从而能够进行响应式的处理
Object.defineProperty(obj, 'a', {
// value: 3, 不能同时指定get当有value/writable属性,set同理
get() { 当访问obj对象的a属性时,会进入到getter中,相当于被劫持了
return 4;
},
set(newValue){
}
})
弊端:
虽然能数据劫持,但是修改属性时,set并不能完成对数据的修改,调用this.a来修改会因为循环进入set而死循环,最终返回的值依旧是get中设置的值,相当于修改无效
解决方式:在外部定义一个变量,set中将新值赋给变量,get中返回变量
2、包装原始数据劫持:
使用闭包,将上述的变量保存起来 function defineReactive(obj,key,val) { if(arguments.length==2){ 未传入第三个参数,赋值属性本身的值 val=obj[key]; } Object.defineProperty(obj, key, { get() { return val; }, set(newVal) { if (newVal == val) { return; } val = newVal; }, enumerable: true, configurable:true }) } defineReactive(obj, 'a', 0); console.log(obj.a); obj.a = 5; console.log(obj.a); 弊端:无法劫持更深层次的属性,如obj.a.b
3、相互引用来实现递归遍历对象每层属性,将所有属性转换成响应式(调用能够被劫持):
循环调用顺序observe->Observer->defineReactive->observe...
observe方法:将属性对象交给Observer,属性不为对象则返回空,为对象则返回对应的Observer实例
Observer类:将属性对象添加__ob__属性并在__ob__上挂载一个Observer实例,并调用实例的walk方法遍历对象属性并调用defineReactive传递属性
def:Observer类内部调用,给属性添加__ob__属性并赋值当前Observer实例,并将__ob__变成不可枚举的一个方法
defineReactive方法:若属性不为对象,将属性变为响应式,否则继续交给observe递归调用
observe.js:
import Observer from './observer'; //辅助判别函数,将传入的对象添加__ob__属性,并将对象每个层级属性转换成响应式的 export default function observe(value) { if (typeof value != 'object') { //属性值是基础类型直接返回 return; } var ob; if (typeof value.__ob__!=='undefined') { //vue中, __ob__会指向一个Observer对象,每个被双向绑定的对象元素(数组也是对象)都会有一个__ob__ ,而且是单例的 ob = value.__ob__; } else { ob = new Observer(value); //将传入的对象转换为每个层级都是响应式的(可以被数据劫持侦测的)的对象 } return ob; }
Observer类:
import { def } from './util' import defineReactive from './defineReactive' export default class Observer{ //将传入的对象转换为每个层级都是响应式的(可以被数据劫持侦测的)的对象 constructor(value) { console.log('observer'); //给传入的对象添加不可枚举属性__ob__,避免遍历时获取到,并将将__ob__属性值设置为当前实例this def(value, '__ob__', this, false); this.walk(value); } walk(value) { //遍历 for (let k in value) { defineReactive(value, k); //将对象属性转换成可被数据劫持的对象 } } }
def方法:
export const def = function (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable,
writable: true,
configurable:true
})
}
defineReactive:
import observe from './observe'; //辅助判别函数,将传入的对象添加__ob__属性,并将对象每个层级属性转换成响应式的 export default function defineReactive(obj, key, value) { if (arguments.length == 2) { value = obj[key]; } //会产生递归的效果,会将当前属性值中的内层属性遍历,内层属性为了变成响应式又会调用defineReactive方法,直到当前属性内层的属性不是个对象为止,才会走后续的操作,然后递归进行返回走之后的操作 //循环调用顺序observe->Observer->defineReactive->observe... //observe:将属性对象交给Observer,属性不为对象返回 //Observer:将属性对象添加__ob__属性并在__ob__上挂载一个Observer实例,并调用实例的walk方法遍历对象属性调用defineReactive //defineReactive:若属性不为对象,将属性变为响应式,否则继续交给observe递归调用 let childOb=observe(value); //返回属性对象对应的Observer实例 Object.defineProperty(obj, key, { get() { console.log('访问obj的'+key+'属性') return value; }, set(newValue) { console.log('修改obj的' +key+'属性为'+newValue) if (newValue == value) { return; } value = newValue; //如果赋的值是一个对象,则赋值后对象内部属性也应该是响应式的 childOb=observe(newValue); }, enumerable: true, configurable:true }) }
将传入的对象每层属性都变成响应式,index.js:
import observe from './observe'; var obj = { a: { m: { n: 5 } }, b:4 }; observe(obj); console.log(obj); obj.a.m.n++;
将会打印:
实现数组的响应式:
1、通过上一部分,能够将数组变成响应式,即访问修改能够侦测到
2、但通过.push方法添加元素,因为并没有进行赋值的操作,所以没有触发set,不能响应式侦测
解决方法:
修改数组的方法调用,使得调用时能够被侦测
(1)定义一个方法,若属性是一个数组,则调用该方法,将修改后的'push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'等方法挂载到数组原型上,使得数组调用对应方法时,调用这些方法
1、通过Object.create可以创造一个原型指向原生数组原型的对象,即新对象具有原生数组的所有方法
2、为新对象添加'push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'对应的属性,并重写方法以及设置成不可枚举
使用上一部分中的def方法
3、因为新对象会被挂载到数组原型上,所以this会指向数组,数组调用上述方法时会优先调用新对象中重写的方法
因此可通过arguments获取到调用时的参数,意味着可通过调用原生的对应方法实现和原生方法相同效果,且因为为自定义方法,使得调用时能被我们知道,即被侦测
4、因为数组对象上绑定了__ob__属性,即Observer实例,就可调用实例上的observeArray方法,并将添加的参数传入,使得参数中的数组和对象变成响应式的
observeArray方法:遍历数组的元素,将元素传入observe中,使得对象元素被侦测,数组元素的原型方法修改为自定义的七种方法,普通元素不处理,实现响应式
(2)修改Observer类,添加如果传入的是数组判断,如果是数组则修改数组的原型为修改后的对象,并调用observeArray方法,遍历数组元素,将元素传入observe中,使得对象元素被侦测,数组元素的原型方法修改为自定义的七种方法,普通元素不处理,实现响应式
Object.setPrototypeOf能够修改对象原型的指向
解释了为什么vue中,数组[下标]=值视图不能刷新,但若数组[下标]为对象,数组[下标]=值/或数组[下标].属性=值能够被响应,数组.push等七种指定方法能够被追踪刷新视图
array.js,暴露修改了七种方法的对象,将被挂载到数组对象的原型上:
import {def} from './util' //被改写的七个数组方法 const methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'] //获取原生数组原型链方法 const arrayPrototype = Array.prototype; //创造一个原型链为原生数组原型链的对象,不能直接使用原型对象赋值,否则将直接修改原生原型链上的方法 //这样的目的是,如果使用改写的方法,则直接调用对象 //并将其暴露,在Obsever类中,修改数组的原型指向 export const arrayMethods = Object.create(arrayPrototype); methods.forEach(method => { //备份原来的方法 const original = arrayPrototype[method]; //直接执行原函数,会因为无上下文而将内部的this指向window而报错 //取出数组身上的在observe方法调用后添加的__ob__属性,即Observer实例 // console.log(this); //定义新的方法 def(arrayMethods, method, function () { //因为this指向arrayMethods对象,而arrayMethods是数组对象的原型,在调用时是数组在调用该方法,所以this又指向了数组本身 //取出数组身上的在observe方法调用后添加的__ob__属性,即Observer实例 const ob = this.__ob__; //将类数组变成数组,使其具有数组的方法 let transArguments = Array.from(arguments); //能够添加新元素的三种方法push、spilce、unshift,添加的元素都应该是响应式的 let inserted = []; switch (method) { case "push": //push、unshift参数都相同 case "unshift": inserted = transArguments; break; case "splice": inserted = transArguments.slice(2); //splice从第二参数之后才是添加的元素 break; } //将新元素变成响应式 if (inserted.length) { ob.observeArray(inserted); } //arguments为调用该方法传入的参数 let res=original.apply(this, arguments); //调用原数组方法,使得功能未改变 console.log('new me') return res; }, false); })
Observer类,添加了判断属性是数组的清空:
添加了能够将数组原型指向修改后的对象上,和遍历素组元素,递归检测的方法
import { def } from './util' import defineReactive from './defineReactive' import {arrayMethods} from './array' import observe from './observe'; export default class Observer{ //将传入的对象转换为每个层级都是响应式的(可以被数据劫持侦测的)的对象 constructor(value) { //给传入的对象添加不可枚举属性__ob__,避免遍历时获取到,并将将__ob__属性值设置为当前实例this def(value, '__ob__', this, false); //检查属性是否是数组 if (Array.isArray(value)) { //是数组,修改数组的原型为修改后的对象 Object.setPrototypeOf(value, arrayMethods); //使得调用数组的七种指定方法能够被侦测 //遍历数组元素调用observe方法,只将数组中的对象变成响应式的,数组中的数组调用七种指定方法能够侦测,普通值不响应 //解释了为什么vue中,数组[下标]=值视图不能刷新,但若数组[下标]为对象,数组[下标]=值/或数组[下标].属性=值能够被响应,数组.push等七种指定方法能够被追踪刷新视图 this.observeArray(value); } else { //如果是对象 this.walk(value); } } walk(value) { //遍历 for (let k in value) { defineReactive(value, k); //将对象属性转换成可被数据劫持的对象 } } observeArray(arr) { for (let i = 0,l=arr.length; i < l; i++){ //循环检测每一项是否是数组或对象,普通值会退出 observe(arr[i]); } } }
在setter外部实现依赖改变监听(依赖:状态数据),即Vue中的watch函数/计算属性等依赖监听实现
收集监听器需要当监听器开启时(即 new Watcher后)再进行,所以需要一个全局标识,这里往Dep类上添加一个target属性,Dep.target默认为null,当new Watcher后赋值为Watcher实例作为标识
1、创建一个Dep类,用来保存watcher监听器实例,并提供一个方法来添加监听器,一个方法来通知监听器再次获取值
当非数组的普通值元素(包括数组本身)进行修改和访问时,都会进入set和get内,所以在get中收集监听器依赖,在set中通知收集的监听器再次获取要监听的属性值
当数组调用如.push方法时,因为不会进入get,而是进入之前修改后的自定义push中(在array.js中),所以在自定义的方法中通知收集的监听器再次获取要监听的属性值
而因为自定义修改的方法能通过this.__ob__访问到Observer实例,而在set之前也会调用observe来返回对应的Observer实例,所以在set方法中,对于数组对象,将监听器存放在对应的Observer实例的dep属性上,dep属性也是一个Dep实例,
因此就能通过调用Observer实例上的dep属性来通知存储的监听器来再次获取要监听的属性值
2、创建一个Watcher监听器类,传入要监听的对象、要监听的对象的值的属性链(如:"a.b.c",a为属性)、对象值修改后的回调
当首次实例化监听器时,就会将对象上的值存在监听器实例上,又因为会根据属性链依次访问属性的get,所以每次访问都会将该监听器实例存储进dep中
Watcher还会提供一个update方法,用来再次获取要监听的属性值,会在Dep通知监听器后触发
Dep类:
var uid = 0; export default class Dep{ constructor() { this.id = uid++; //唯一id //订阅的是watcher的实例 this.subs = []; } addSub(sub) { //添加订阅 // console.log(sub); this.subs.push(sub); } depend() { //添加依赖 //自己指定的开始收集监听器依赖的全局标识(new Watcher后),这里指定为指定了属性链的Watcher实例 if (Dep.target) { this.addSub(Dep.target); } } notify() { //通知更新 console.log('notify'); //浅克隆 const subs = this.subs.slice(); console.log(subs); for (let i = 0, l = subs.length; i < l; i++){ subs[i].update(); } } }
Watcher监听器类:
import Dep from "./dep"; var uid = 0; export default class Watcher{ constructor(target,expression,callback) { //target:要监听的对象,expression:'a.b.c'属性调用链,callback:c属性改变时的回调 this.id = uid++; //id标识 this.target = target; this.getter = parsePath(expression);//传入"a.b.c",解析返回{a:{b:{c:4}}}中c的值 this.callback = callback; this.value = this.get();//实例化时,就会获取一次属性调用链指定的属性值,会进入每个属性调用链上的属性的get中,所以如a.b.c,当c之前的任意属性更改,都能触发wather的更新update函数,重新获取值 } update() { //重新获取指定expression下的对象的属性值 this.run() } run() { this.getAndInvoke(this.callback); } getAndInvoke(callback) { //重新调用get方法,获取指定expression下的对象的属性值 const value = this.get(); if (value !== this.value || typeof value == 'object') { //当重新获取的值和上一次值不相同时,更新值,并触发回调 const oldValue = this.value; this.value = value; callback.call(this.target, value, oldValue);//调用对象为要监听的对象,value为新值,oldValue为之前的值 } } get() {//获取指定expression下的对象的属性值 //进入依赖收集,即全局Dep.target设置为watcher实例,能调用其中方法 Dep.target = this; const obj = this.target; let value; try { value =this.getter(obj); //会进入每个属性调用链上的属性的get中,所以如a.b.c,当c之前的任意属性更改,都能触发wather的更新update函数,重新获取值 } catch (e) { } finally { Dep.target = null; //退出依赖收集,让给别的watcher } return value; } } function parsePath(str) { //传入"a.b.c",返回一个函数用来解析返回{a:{b:{c:4}}}中c的值,最后返回的函数的返回值为c的值 let segments = str.split('.'); return (obj) => { for (let i = 0; i < segments.length; i++){ if (!obj) { return; } obj = obj[segments[i]]; } return obj; } }
修改Observer类,添加dep实例属性,因为每个数组对象都有__ob__属性,即Observer实例,所以将监听器放在添加的dep属性上,dep是个Dep实例,当数组调用.push方法等,会进入修改后的方法,修改后的方法能获取到Observer实例,从而获取到dep上存储的监听器,从而通知监听器再次获取指定属性的值:
import { def } from './util' import defineReactive from './defineReactive' import {arrayMethods} from './array' import observe from './observe'; import Dep from './dep'; export default class Observer{ //将传入的对象转换为每个层级都是响应式的(可以被数据劫持侦测的)的对象 constructor(value) { //会为每个对象绑定一个对应的__ob__属性,即Observer实例,故在此收集的监听器可以提供给修改后的数组方法中能够访问 this.dep = new Dep(); //给传入的对象添加不可枚举属性__ob__,避免遍历时获取到,并将将__ob__属性值设置为当前实例this def(value, '__ob__', this, false); //检查属性是否是数组 if (Array.isArray(value)) { //是数组,修改数组的原型为修改后的对象 Object.setPrototypeOf(value, arrayMethods); //使得调用数组的七种指定方法能够被侦测 //遍历数组元素调用observe方法,只将数组中的对象变成响应式的,数组中的数组调用七种指定方法能够侦测,普通值不响应 //解释了为什么vue中,数组[下标]=值视图不能刷新,但若数组[下标]为对象,数组[下标]=值/或数组[下标].属性=值能够被响应,数组.push等七种指定方法能够被追踪刷新视图 this.observeArray(value); } else { //如果是对象 this.walk(value); } } walk(value) { //遍历 for (let k in value) { defineReactive(value, k); //将对象属性转换成可被数据劫持的对象 } } observeArray(arr) { for (let i = 0,l=arr.length; i < l; i++){ //循环检测每一项是否是数组或对象,普通值会退出 observe(arr[i]); } } }
修改defineReactive,使得非数组的普通值元素每次get/set都能触发收集监听器/触发监听器再次更新,以及将监听器收集在Observer实例的dep上,便于.push等自定义函数中获取到
import observe from './observe'; //辅助判别函数,将传入的对象添加__ob__属性,并将对象每个层级属性转换成响应式的 import Dep from './dep'; export default function defineReactive(obj, key, value) { const dep = new Dep(); if (arguments.length == 2) { value = obj[key]; } //会产生递归的效果,会将当前属性值中的内层属性遍历,内层属性为了变成响应式又会调用defineReactive方法,直到当前属性内层的属性不是个对象为止,才会走后续的操作,然后递归进行返回走之后的操作 //循环调用顺序observe->Observer->defineReactive->observe... //observe:将属性对象交给Observer,属性不为对象返回 //Observer:将属性对象添加__ob__属性并在__ob__上挂载一个Observer实例,并调用实例的walk方法遍历对象属性调用defineReactive //defineReactive:若属性不为对象,将属性变为响应式,否则继续交给observe递归调用 let childOb=observe(value); //返回属性对象对应的Observer实例 Object.defineProperty(obj, key, { get() { console.log('访问obj的' + key + '属性') if (Dep.target) { //如果处于依赖收集阶段,即new Watcher了,因为会先调用new Watcher,其中的get方法,会根据属性链(如:"a.b.c"),挨个进入getter,并在各自的闭包中保存watcher实例 //使得"a.b.c",若监听的是.c,则c之前任意一个属性变化,都会触发 dep.depend(); //将对应的watcher实例保存在当前属性的set闭包中 if (childOb) { //如果当前属性是个数组,则不会触发set,为了在.push等方法中触发监听,所以将watcher实例保存在,数组对象的Observer实例的dep属性上,在修改后的数组的方法(array.js),如push,又能通过this.__ob__获取到Observer实例,继而能够通过Observer实例获取到dep实例,然后调用dep实例上的方法通知watcher来更新监听 childOb.dep.depend();//将watcher实例保存在当前对象属性的Observer实例的dep实例属性中 } } return value; }, set(newValue) { console.log('修改obj的' +key+'属性为'+newValue) if (newValue == value) { return; } value = newValue; //如果赋的值是一个对象,则赋值后对象内部属性也应该是响应式的 childOb = observe(newValue); //发布订阅模式,当对象属性改变时,通知dep改变,这里无法通知到数组.push等调用的改变,需要在array.js中,重写的push等方法中进行通知 dep.notify() }, enumerable: true, configurable:true }) }
修改array.js,因为数组的push等方法会进入这里,所以在这里获取Observer实例上保存的dep属性上保存的监听器,并通知监听器再次获取值
import {def} from './util' //被改写的七个数组方法 const methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'] //获取原生数组原型链方法 const arrayPrototype = Array.prototype; //创造一个原型链为原生数组原型链的对象,不能直接使用原型对象赋值,否则将直接修改原生原型链上的方法 //这样的目的是,如果使用改写的方法,则直接调用对象 //并将其暴露,在Obsever类中,修改数组的原型指向 export const arrayMethods = Object.create(arrayPrototype); methods.forEach(method => { //备份原来的方法 const original = arrayPrototype[method]; //直接执行原函数,会因为无上下文而将内部的this指向window而报错 //取出数组身上的在observe方法调用后添加的__ob__属性,即Observer实例 // console.log(this); //定义新的方法 def(arrayMethods, method, function () { //因为this指向arrayMethods对象,而arrayMethods是数组对象的原型,在调用时是数组在调用该方法,所以this又指向了数组本身 //取出数组身上的在observe方法调用后添加的__ob__属性,即Observer实例 const ob = this.__ob__; //将类数组变成数组,使其具有数组的方法 let transArguments = Array.from(arguments); //能够添加新元素的三种方法push、spilce、unshift,添加的元素都应该是响应式的 let inserted = []; switch (method) { case "push": //push、unshift参数都相同 case "unshift": inserted = transArguments; break; case "splice": inserted = transArguments.slice(2); //splice从第二参数之后才是添加的元素 break; } //将新元素变成响应式 if (inserted.length) { ob.observeArray(inserted); } //当数组调用了修改后的方法时,也通知改变 ob.dep.notify(); console.log('new me') //arguments为调用该方法传入的参数 let res = original.apply(this, arguments); //调用原数组方法,使得功能未改变 return res; }, false); })
index.js调用监听器:
import defineReactive from './defineReactive'; import observe from './observe'; import Watcher from './watcher'; var obj = { a: { m: { n: 5, g:7 } }, b: 4, c:[1,2,3] }; observe(obj); new Watcher(obj, 'a.m.n', (val) => { console.log("***watcher监听***",val); }) obj.a.m.n= 4; // obj.a.m.g = 9; // obj.b = 5;
会打印输出监听修改的结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。