读和写的深度思考。
函数运行过程要和用到的响应式数据建立关联关系,包含以下三个方面:
下来咱们接上小节,进一步细化如何监听?
本小节的代码结构图如下:

首先监听这里我们必须要考虑一些边界问题,比如:传入的不是对象怎么办?传入的相同的对象还需要重复生成代理对象吗?等等。
代码优化如下:
1).创建utils.js 工具模块
//判断是否是对象
export function isObject(value){
return typeof value === 'object' && value !==null;
}
2).剥离出来handler对象。
import {track, trigger} from "./effect.js";
export const handlers = {
get(target, key, value) {
//依赖收集
track(target, key);
return target[key];
},
set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
trigger(target, key);
return Reflect.set(target, key, value);
}
}
3).在reactive.js增加对是否是对象和是否是同一个对象的判断。这里使用了WeakMap实现代理对象的缓存。
import {track, trigger} from "./effect.js";
import {isObject} from './utils.js'
import {handlers} from "./handlers.js";
//注意:这里一定要使用WeakMap,因为里面是弱引用,方便垃圾回收器回收不用的对象。
const targetsMap = new WeakMap();
export function reactive(target) {
if (!isObject(target)) {
return target; //如果不是对象直接返回原始对。
}
if (targetsMap.has(target)) {
//说明已经代理过了,直接返回缓存里的代理对象。
return targetsMap.get(target);
}
const proxy = new Proxy(target, handlers)
targetsMap.set(target, proxy);
return proxy;
}
4).index.js 针对同一个对象重复生成代理。
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20
}
const state1 = reactive(obj);
const state2 = reactive(obj);
console.log(state1===state2); //true
我们思考这么一种情况,如果对象的某个属性用到了其它属性,如下所示:
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
get introduce(){
return 'hello,my name is '+this.name+" ,"+this.age+'years old!'
}
}
const state = reactive(obj);
function fn(){
state.introduce;
}
fn();
运行结果:我们发现仅仅依赖收集了introduce,并没触发name和age的依赖收集。其原因是this指向的是obj对象而非代理对象。
依赖收集:introduce
我们必须使用Reflect.get返回代理对象。因此,handlers.js修改如下:
import {track, trigger} from "./effect.js";
export const handlers = {
get(target, key, receiver) {
//依赖收集
track(target, key);
//return target[key];
return Reflect.get(target,key,receiver); //receiver其实就是代理对象
},
set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
trigger(target, key);
return Reflect.set(target, key, value);
}
}
再次运行:
依赖收集:introduce
依赖收集:name
依赖收集:age
下来我们再看另一种情况,index.js代码修改如下:
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
state.car.brand;
}
fn();
运行结果:
依赖收集:car
我们发现并没有对brand属性触发依赖收集。这是因为car属性本身也是一个对象,因此返回是car对象本身,并不是一个代理对象。
因此,handlers.js修改如下:
import {track, trigger} from "./effect.js";
import {isObject} from "./utils.js";
import {reactive} from "./reactive.js";
export const handlers = {
get(target, key, receiver) {
//依赖收集
track(target, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target,key,receiver);
if(isObject(result)){
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
},
set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
trigger(target, key);
return Reflect.set(target, key, value);
}
}
再次运行:
依赖收集:car
依赖收集:brand
除了读属性外,如果是判断某个属性是否存在,能否触发依赖收集呢?比如:index.js修改如下:
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
'car' in state;
}
fn();
我们发现没有任何输出结果。这种情况我们也必须触发依赖收集,因为这个car属性有可能是程序运行过程中动态添加的,如果添加了car属性就会影响fn这个函数的执行结果。
通过查看官方文档,我们得知使用in来判断某个属性是否存在,内部是通过调用了has方法实现的。因此我们在handers.js添加has拦截函数即可。
import {track, trigger} from "./effect.js";
import {isObject} from "./utils.js";
import {reactive} from "./reactive.js";
export const handlers = {
get(target, key, receiver) {
//依赖收集
track(target, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target, key, receiver);
if (isObject(result)) {
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
},
set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
trigger(target, key);
return Reflect.set(target, key, value);
},
has(target, key) {
track(target, key);
return Reflect.has(target, key);
}
}
再次运行:
依赖收集:car
这样看起来似乎很完美了,但是我们没有考虑到属性读和改的具体动作,例如:index.js代码修改如下:
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
'car' in state;
}
fn();
state.car.brand = 'BENZ'; //这里的修改属性按理说不会影响判断car属性是否存在。
运行结果:
依赖收集:car
依赖收集:car
派发更新:brand
我们创建一个operations.js用来封装属性读和改的具体动作的类型。
//读属性的操作类型
export const TrackTypes = {
GET: 'get',
HAS: 'has'
}
//更改属性的操作类型
export const TriggerTypes = {
SET: 'set',
ADD: 'add',
DELETE: 'delete'
}
handlers.js代码优化如下:
import {track, trigger} from "./effect.js";
import {isObject} from "./utils.js";
import {reactive} from "./reactive.js";
import {TrackTypes, TriggerTypes} from "./operations.js";
function get(target, key, receiver) {
//依赖收集
track(target, TrackTypes.GET, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target, key, receiver);
if (isObject(result)) {
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
}
function set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
//TODO: 判断操作类型
trigger(target, TriggerTypes.SET,key);
return Reflect.set(target, key, value);
}
function has(target, key) {
track(target,TrackTypes.HAS, key);
return Reflect.has(target, key);
}
export const handlers = {
get,
set,
has
}
运行结果:
依赖收集:[has], car
依赖收集:[get], car
派发更新:[set], brand
还有一种情况,如果使用for in 遍历一个对象的属性,是否能够出发依赖收集呢?index.js修改如下:
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
//'car' in state;
for(const key in state ){
}
}
fn();
我们发现没有任何的输出结果,其道理和 in 判断属性是否存在一样,使用 for in 遍历属性内部都是通过 ownKeys()这个函数判断的。因此在handlers.js添加ownKeys 拦截函数即可。
首先在operations.js里增加一个迭代操作类型。
export const TrackTypes = {
GET: 'get',
HAS: 'has',
ITERATE: 'iterate'
}
export const TriggerTypes = {
SET: 'set',
ADD: 'add',
DELETE: 'delete'
}
在handlers.js添加ownKeys 拦截函数。
import {track, trigger} from "./effect.js";
import {isObject} from "./utils.js";
import {reactive} from "./reactive.js";
import {TrackTypes, TriggerTypes} from "./operations.js";
function get(target, key, receiver) {
//依赖收集
track(target, TrackTypes.GET, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target, key, receiver);
if (isObject(result)) {
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
}
function set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
//TODO: 判断操作类型
trigger(target, TriggerTypes.SET,key);
return Reflect.set(target, key, value);
}
function has(target, key) {
track(target,TrackTypes.HAS, key);
return Reflect.has(target, key);
}
function ownKeys(target){
track(target,TrackTypes.ITERATE);
return Reflect.ownKeys(target);
}
export const handlers = {
get,
set,
has,
ownKeys
}
运行结果:
依赖收集:[iterate]
同理,Object.keys(obj)也会触发TrackTypes.ITERATE操作的依赖收集。
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
//'car' in state;
/*
for(const key in state ){
}*/
Object.keys(state);
}
fn();
首先写属性分两种情况,如果这个属性存在那么就是TriggerTypes.SET操作,如果不存在就是TriggerTypes.ADD操作。
修改handlers.js如下:
import {track, trigger} from "./effect.js";
import {isObject} from "./utils.js";
import {reactive} from "./reactive.js";
import {TrackTypes, TriggerTypes} from "./operations.js";
function get(target, key, receiver) {
//依赖收集
track(target, TrackTypes.GET, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target, key, receiver);
if (isObject(result)) {
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
}
function set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
const type = target.hasOwnProperty(key)? TriggerTypes.SET: TriggerTypes.ADD;
trigger(target, type,key);
return Reflect.set(target, key, value);
}
function has(target, key) {
track(target,TrackTypes.HAS, key);
return Reflect.has(target, key);
}
function ownKeys(target){
track(target,TrackTypes.ITERATE);
return Reflect.ownKeys(target);
}
function deleteProperty(target, key) {
trigger(target,TriggerTypes.DELETE,key);
return Reflect.deleteProperty(target, key);
}
export const handlers = {
get,
set,
has,
ownKeys,
deleteProperty
}
index.js测试派发更新。
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
state.car.brand = 'BENZ'; //修改属性
state.car.price = 200000; //增加属性
delete state.age; //删除属性
delete state.abc; //删除一个不存在的属性
}
fn();
运行结果:
依赖收集:[get], car
派发更新:[set], brand
依赖收集:[get], car
派发更新:[add], price
派发更新:[delete], age
派发更新:[delete], abc
但是我们发现删除一个不存在的属性也触发派发更新了。handlers.js修改如下:
import {track, trigger} from "./effect.js";
import {isObject} from "./utils.js";
import {reactive} from "./reactive.js";
import {TrackTypes, TriggerTypes} from "./operations.js";
function get(target, key, receiver) {
//依赖收集
track(target, TrackTypes.GET, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target, key, receiver);
if (isObject(result)) {
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
}
function set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
//派发更新
const type = target.hasOwnProperty(key) ? TriggerTypes.SET : TriggerTypes.ADD;
trigger(target, type, key);
return Reflect.set(target, key, value);
}
function has(target, key) {
track(target, TrackTypes.HAS, key);
return Reflect.has(target, key);
}
function ownKeys(target) {
track(target, TrackTypes.ITERATE);
return Reflect.ownKeys(target);
}
function deleteProperty(target, key) {
const hasKey = target.hasOwnProperty(key); //存在要删除的属性
const result = Reflect.deleteProperty(target, key); //并且删除成功了。
if(hasKey && result){
trigger(target,TriggerTypes.DELETE,key);
}
return result;
}
export const handlers = {
get,
set,
has,
ownKeys,
deleteProperty
}
再次运行:
依赖收集:[get], car
派发更新:[set], brand
依赖收集:[get], car
派发更新:[add], price
派发更新:[delete], age
还有一种情况,如果修改的属性值和原来的值一样,同样也会触发派发更新。index.js如下:
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
state.car.brand = 'BMW'; //更新的值和原来的值一样。
}
fn();
运行结果:
依赖收集:[get], car
派发更新:[set], brand
这里涉及到如何判断属性发送了改变,我们需要使用Ojbect.is方法来严格判断,在utils.js中添加一个判断是否发生改变的方法。
export function isObject(value){
return typeof value === 'object' && value !==null;
}
export function hasChanged(oldValue,newValue){
return !Object.is(oldValue,newValue)
}
修改handlers.js如下:
import {track, trigger} from "./effect.js";
import {hasChanged, isObject} from "./utils.js";
import {reactive} from "./reactive.js";
import {TrackTypes, TriggerTypes} from "./operations.js";
function get(target, key, receiver) {
//依赖收集
track(target, TrackTypes.GET, key);
//return target[key];
//return Reflect.get(target,key,receiver); //receiver其实就是代理对象
const result = Reflect.get(target, key, receiver);
if (isObject(result)) {
//如果是对象需要再次调用reactive返回代理对象。
return reactive(result);
}
return result;
}
function set(target, key, value) {
//target[key]=value;
//return true; //不要忘了这里要返回一个true;表示赋值是否成功, 推荐使用反射Reflect,它本身返回就是布尔值。
const oldValue = target[key];//获取旧的值。
//派发更新
const type = target.hasOwnProperty(key) ? TriggerTypes.SET : TriggerTypes.ADD;//获取操作类型
const result = Reflect.set(target, key, value);//获取更新结果。
if (!result) { //如果更新失败,直接返回。
return result;
}
if (hasChanged(oldValue, value || type === TriggerTypes.ADD)) {
trigger(target, type, key);
}
return result;
}
function has(target, key) {
track(target, TrackTypes.HAS, key);
return Reflect.has(target, key);
}
function ownKeys(target) {
track(target, TrackTypes.ITERATE);
return Reflect.ownKeys(target);
}
function deleteProperty(target, key) {
const hasKey = target.hasOwnProperty(key); //存在要删除的属性
const result = Reflect.deleteProperty(target, key); //并且删除成功了。
if (hasKey && result) {
trigger(target, TriggerTypes.DELETE, key);
}
return result;
}
export const handlers = {
get,
set,
has,
ownKeys,
deleteProperty
}
index.js测试派发更新。
import {reactive} from './reactive.js'
const obj = {
name: 'zhangsan',
age: 20,
car:{
brand: 'BMW'
}
}
const state = reactive(obj);
function fn(){
state.car.brand = 'BMW'; //更新的值和原来的值一样;
state.car.price = 200000; //新增属性
}
fn();
运行结果:
依赖收集:[get], car
依赖收集:[get], car
派发更新:[add], price