← 返回首页
读和写的深度思考
发表时间:2024-01-06 14:35:53
读和写的深度思考

读和写的深度思考。

函数运行过程要和用到的响应式数据建立关联关系,包含以下三个方面:

下来咱们接上小节,进一步细化如何监听?

本小节的代码结构图如下:

1.监听的优化

首先监听这里我们必须要考虑一些边界问题,比如:传入的不是对象怎么办?传入的相同的对象还需要重复生成代理对象吗?等等。

代码优化如下:

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

2.读的优化

我们思考这么一种情况,如果对象的某个属性用到了其它属性,如下所示:

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();

3.写的优化

首先写属性分两种情况,如果这个属性存在那么就是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