解析依赖收集

知识点:

  • Object.defineProperty
  • Proxy
  • Reflect

介绍

依赖收集,是希望收集函数依赖的数据,当数据发生修改的时候,重新执行与其绑定的函数。实现依赖收集的基础就是监控数据修改。

监控数据修改

#Object.defineProperty MDN

Object.defineProperty(obj, prop, descriptor)

方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

Object.defineProperty

ECMAScript 5 ✔ 96.29% ◒ 2.68%
IE ✘ 5.5+ ✘ 8+⁴ ◒ 9+² ✔ 10+ Edge ✔ Firefox ◒ 2+ ◒ 4+¹ ✔ 21+ Chrome ◒ 4+ ◒ 19+¹ ✔ 23+ Safari
◒ 3.1+ ✔ 6+ Opera ◒ 9+ ◒ 12.1+¹ ✔ 15+

Object.defineProperty(obj, prop, descriptor)

方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

参数

  • obj 要在其上定义属性的对象。
  • prop 要定义或修改的属性的名称。
  • descriptor 将被定义或修改的属性描述符value, enumerable, configurable, writable, set, get

返回值

  • 被传递给函数的对象。

Object.defineProperty 可以精确添加一个新的属性,并对其设置。要对数据实现监控,我们需要理解属性描述符:

描述符 介绍
value 设置该属性的属性值
enumerable 是否可以枚举
configurable 是否可以操作 (表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改。)
writable 是否可写
get getter 提供方法的
set setter 提供方法的

如果一个描述符同时具有 (value / writable) 和 (get / set) 关键字,将会产生异常

在 VUE 中就是把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。

示例

创建属性

1
2
3
4
5
6
7
8
9
10
let people = {};
Object.defineProperty(people, "name", {
value: "小明",
enumerable: false,
writable: false,
configurable: false
});
console.log(people.name); // 小明
people.name = "小白";
console.log(people.name); // 小白

需要注意的是,描述对象(description)中的属性是具有继承性的,如果没有覆盖其 prototype 上的这些属性,可能存在风险;我们可以通过 Object.create(null) 创建一个空的对象的方式避免这个问题

1
2
3
4
5
let people = {};
let desObj = Object.create(null);
desObj.value = "小明";
Object.defineProperty(people, "name", desObj);
console.log(people.name); //小明

这些描述符都具有默认值:

当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是 false。value,get 和 set 字段的默认值为 undefined。一个没有 get/set/value/writable 定义的属性被称为“通用的”,并被“键入”为一个数据描述符。

MDN
  1. Getter && Setter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    function PeopleGroup() {
    var peoples = [];
    var peoplesName = [];
    function setPeoples(value) {
    if (!value.name) {
    console.log("The name cant empty");
    return false;
    }
    if (peoplesName.includes(value.name)) {
    console.log(value.name + " Is exist");
    return false;
    }

    peoples.push(value);
    peoplesName.push(value.name);
    console.log(value.name + " 信息设置为 " + JSON.stringify(value));
    return true;
    }
    Object.defineProperty(this, "people", {
    get() {
    return peoplesName;
    },
    set(val) {
    setPeoples(val);
    }
    });
    this.getPeoplesInfo = () => {
    return peoples;
    };
    }
    let peoples = new PeopleGroup();
    peoples.people = {
    name: "小明",
    age: "12"
    };
    /* 小明 信息设置为 {"name":"小明","age":"12"} */
    peoples.people = {
    name: "小白",
    age: "12"
    };
    /* 小白 信息设置为 {"name":"小白","age":"12"} */
    peoples.people = {
    name: "小明",
    age: "12"
    };
    /* 小明 Is exist */
    console.log(peoples.people);
    /* ["小明", "小白"] */
    console.log(JSON.stringify(peoples.getPeoplesInfo()));
    /* [{"name":"小明","age":"12"},{"name":"小白","age":"12"}] */
  2. Array

    对于数组的修改比较特殊,通常我们会使用 push 方法来修改数组,如果需要对修改进行监控我们需要使用描述符 value 重写 push

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    let people = {
    name: "小明",
    friends: ["小白", "小红"]
    };

    Object.defineProperty(people.friends, "push", {
    value(name, age) {
    this[this.length] = arguments[0];
    console.log(
    "这里添加了一个新的朋友:" +
    arguments[0] +
    " 年龄:" +
    arguments[1]
    );
    }
    });

    people.friends.push("小黑", 12);
    // 这里添加了一个新的朋友:小黑 年龄:12

    注:示例中严重修改了 push 容易造成误解,不建议这样使用;

Proxy

new Proxy(target, handler)

20190419115116.png

使用描述对象(handle)来包装目标对象(target),用于定义基本操作的自定义行为。最终返回包装后的对象

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 目的:当访问对象中不存在的属性名时,返回 false
const handler = {
get: function(target, name) {
return name in target ? target[name] : new Proxy({}, handle);
}
};
let p = new Proxy(
{
name: "小明"
},
handler
);
console.log(p.name); // 小明
console.log(p.age); // false

使用描述对象(handler) 还可以改写 set 行为,这种方式可以对添加的属性进行验证。

  1. 利用 Proxy 我们可以改写 defineProperty 中的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    function PeopleGroup() {
    var peoples = [];
    var peoplesName = [];

    const handler = {
    set(obj, prop, value) {
    if (prop == "people") {
    if (!value.name) {
    console.log("The name cant empty");
    return false;
    }
    if (peoplesName.includes(value.name)) {
    console.log(value.name + " Is exist");
    return false;
    }
    peoples.push(value);
    peoplesName.push(value.name);
    console.log(
    value.name + " 信息设置为 " + JSON.stringify(value)
    );
    } else {
    obj[prop] = value;
    // 添加了其它属性
    }
    },
    get(obj, prop) {
    if (prop == "people") {
    return peoplesName;
    } else {
    return obj[prop];
    }
    }
    };
    let new_this = new Proxy(this, handler);

    new_this.getPeoplesInfo = () => {
    return peoples;
    };
    return new_this;
    }
    let peoples = new PeopleGroup();
    peoples.people = {
    name: "小明",
    age: "12"
    };
    /* 小明 信息设置为 {"name":"小明","age":"12"} */
    peoples.people = {
    name: "小白",
    age: "12"
    };
    /* 小白 信息设置为 {"name":"小白","age":"12"} */
    peoples.people = {
    name: "小明",
    age: "12"
    };
    /* 小明 Is exist */
    console.log(peoples.people);
    /* ["小明", "小白"] */
    console.log(JSON.stringify(peoples.getPeoplesInfo()));
    /* [{"name":"小明","age":"12"},{"name":"小白","age":"12"}] */
  2. Array

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    let people = {
    name: "小明",
    friends: ["小白", "小红"]
    };

    const handler = {
    set(target, props, value) {
    if (props === "length") return true;
    console.log(JSON.stringify(target) + " 修改了 ");
    target[props] = value;
    console.log("修改结果:" + JSON.stringify(target));
    return true;
    }
    };

    var proxy_people_friends = new Proxy(people.friends, handler);
    proxy_people_friends.push("小黑");
    // ["小白","小红"] 修改了
    // 修改结果:["小白","小红","小黑"]

    注:这里排除了 length 是因为使用 push 的时候不仅会修改 people.friends 还有修改其 length 属性,会触发两次行为

使用 Proxy 存在一个问题 – 我们如何在劫持的行为中调用 target 本身的行为,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let people = {
name: "小明",
age: 22
};

let handle = {
get(target, key) {
if (key === "info") {
return target.name + "今年" + target.age;
} else if (key === "name") {
return "小白";
} else {
return target[key];
}
}
};

let proxy_people = new Proxy(people, handle);

console.log(proxy_people.info); // 小明今年22

这里我们希望获得的是代理后的 name,如何操作呢?下面是操作示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let people = {
name: "小明",
age: 22
};

let handle = {
get(target, key, receiver) {
if (key === "info") {
return receiver.name + "今年" + target.age;
} else if (key === "name") {
return "小白";
} else {
return target[key];
}
}
};

let proxy_people = new Proxy(people, handle);

console.log(proxy_people.info); // 小白今年22

我们再看个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let people = {
get info() {
return this.name + "今年:" + this.age;
},
name: "小明",
age: 12
};

let handle = {
get(target, key, receiver) {
if (key === "name") return "小白";
console.log("receiver[key]", receiver[key]);
console.log("target[key]", target[key]);
return target[key];
}
};

let proxy_people = new Proxy(people, handle);

console.log(proxy_people.info); // RangeError: Maximum call stack size exceeded

可以看到如果继续使用,receiver 获取代理后的属性,就会出现重复调用,这个时候我们就需要使用 Reflect 来解决这个问题了

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象(handler)的方法相同。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

  1. Reflect 相对于 Proxy 来说,提供了操作被代理对象的行为:

    Reflect.set()

    Reflect.set(target, propertyKey, value[, receiver])

    拦截对象(target)设置值(value)的属性(propertyKey)。最终将会返回一个 boolean 表示是否成功。receiver 表示直接操作的对象

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    let people = {
    get info() {
    return this.name + "今年:" + this.age;
    },
    name: "小明",
    age: 12
    };
    console.log(people.info); // 小明今年:12

    let handle = {
    get(target, key, receiver) {
    if (key === "age") return 22;

    console.log("Reflect:", Reflect.get(target, key, receiver));
    console.log("target[key]:", target[key]);
    /* :two: console.log("receiver[key]:", receiver[key]); */

    return target[key];
    }
    };

    let proxyPeople = new Proxy(people, handle);

    /* :one: */
    console.log(proxyPeople.info);

    // Reflect: 小明
    // target[key]: 小明
    // Reflect: 小明今年:22
    // target[key]: 小明今年:12
    // 小明今年:12
    • 1️⃣ 这里使用的是 proxyPeople.info 直接操作的对象是 proxyPeople
    • 2️⃣ 如果这里对 receiver 进行操作,就会重复调用 get 变成死循环

    可以看出使用 Reflect 可以使得在被代理的对象上使用被代理的方法;

  2. Reflect 使我们对于函数的操作变成了函数操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let people = {
    get info() {
    return this.name + "今年:" + this.age;
    },
    name: "小明",
    age: 12
    };

    let handle = {
    get(target, key, receiver) {
    if (key === "name") return "小白";
    return Reflect.get(target, key, receiver);
    }
    };

    let proxy_people = new Proxy(people, handle);

    console.log(proxy_people.info); // 小白今年:12
  3. 更好的返回值
    使用 Reflect 可以的到更好的返回值:true / false

  4. 更可靠的 apply
    我们通常使用 fn.apply(obj, args) 的方式来绑定函数,但是 apply 可能会被修改。这个时候我们可以使用 Function.prototype.apply.call(f, obj, args) 来解决问题,有了 Reflect 后我们可以更加方便的解决这个问题:Reflect.apply(obj, args)

进阶

Proxy

new Proxy(target, handler)

handler 可以拦截的方法有:

  1. handler.has():在判断代理对象是否拥有某个属性时触发该操作,比如在执行 “foo” in proxy 时。
  2. handler.get():在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
  3. handler.set():在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
  4. handler.defineProperty():在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, “foo”, {}) 时
  5. handler.deleteProperty():在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
  6. handler.construct():在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行 new proxy() 时。
  7. handler.ownKeys():在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
  8. handler.apply():当目标对象为函数,且被调用时触发。
  9. handler.getPrototypeOf():在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
  10. handler.setPrototypeOf():在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
  11. handler.getOwnPropertyDescriptor():在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, “foo”) 时。
  12. handler.isExtensible():在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
  13. handler.preventExtensions():在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。

Reflect

Reflect 能够代理的方法和 handle 相同

实践

实现观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
let people = {
name: "小明"
};

function set_proxy(target, observable_fn = function() {}) {
let observable_arrays = new Set();
observable_arrays.add(function(target, key, value, receive) {
console.log(
JSON.stringify(target) +
"发生了修改,其属性:" +
key +
",改变成:" +
value
);
});
observable_arrays.add(observable_fn);
let handle = {
set(target, key, value, receive) {
observable_arrays.forEach(fn => {
fn(target, key, value, receive);
});
}
};
return new Proxy(target, handle);
}

let proxy_people = set_proxy(people, function(target, name, value) {
console.log("欢迎" + value);
});

proxy_people.name = "小白";

使用 proxy & reflect 实现 TODO_List

实现依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// 实现依赖收集

class Dependence_manager {
constructor() {
// 存放依赖项
this.dependence_array = new Set();
}

// 添加依赖
depend() {
if (Dependence_manager.target) {
this.dependence_array.add(Dependence_manager.target);
}
}

// 添加发布行为
notify() {
this.dependence_array.forEach(function(dependence) {
dependence();
});
}
}

class Observable {
constructor(obj) {
const keys = Object.keys(obj);
keys.forEach(key => {
this.define_reactive(obj, key, obj[key]);
});
return obj;
}
define_reactive(obj, key, val) {
const dependence_manager = new Dependence_manager();

Object.defineProperty(obj, key, {
get() {
dependence_manager.depend();
console.log("get", key);
return val;
}
});
if (Array.isArray(val)) {
Object.defineProperty(val, "push", {
value() {
this[this.length] = arguments[0];
dependence_manager.notify(arguments[0]);
}
});
} else {
Object.defineProperty(obj, key, {
set(new_value) {
val = new_value;
dependence_manager.notify();
}
});
}
}
}

class Watcher {
constructor(obj, key, cb, on_computed_update) {
this.obj = obj;
this.key = key;
this.cb = cb;
this.on_computed_update = on_computed_update;
this.define_computed();
}
define_computed() {
const self = this;
let result;
Dependence_manager.target = () => {
result = self.cb();
this.on_computed_update(result);
};
result = self.cb();

Object.defineProperty(self.obj, self.key, {
get() {
Dependence_manager.target = null;
return result;
},
set() {
console.error("计算属性无法被赋值!");
}
});
}
}

let observable_people = new Observable({
name: "小明",
age: 22,
info: ""
});

new Watcher(
observable_people,
"info",
function() {
let info = observable_people.name + "今年:" + observable_people.age;
console.log("info 发生了修改:", info);
return info;
},
function(new_value) {
console.log("complete:", new_value);
}
);

observable_people.name = "小白";
  1. 使用 Proxy 的依赖收集

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    // 实现依赖收集

    class Dependence_manager {
    constructor() {
    // 存放依赖项
    this.dependence_array = new Set();
    }

    // 添加依赖
    depend(key) {
    if (Dependence_manager.target) {
    this.dependence_array.add({
    key: key,
    target: Dependence_manager.target
    });
    }
    }

    // 添加发布行为
    notify(key) {
    this.dependence_array.forEach(function(dependence) {
    if (dependence.key == key) {
    dependence.target();
    }
    });
    }
    }

    class Observable {
    constructor(obj) {
    return this.define_reactive(obj);
    }
    define_reactive(obj) {
    const dependence_manager = new Dependence_manager();
    const handler = {
    get(target, key, receiver) {
    dependence_manager.depend(key);
    return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
    console.log(`我的${key}属性被修改为${value}了!`);
    Reflect.set(target, key, value, receiver);
    dependence_manager.notify(key);
    }
    };
    return new Proxy(obj, handler);
    }
    }

    class Watcher {
    constructor(obj, key, cb, on_computed_update) {
    this.obj = obj;
    this.key = key;
    this.cb = cb;
    this.on_computed_update = on_computed_update;
    return this.define_computed();
    }
    define_computed() {
    const self = this;
    let result;
    Dependence_manager.target = () => {
    result = self.cb();
    this.on_computed_update(result);
    };
    result = self.cb();
    Dependence_manager.target = null;

    return new Proxy(self.obj, {
    get(target, key, receiver) {
    console.log("2");
    if (key === self.key) {
    return result;
    } else {
    return Reflect.get(target, key, receiver);
    }
    },
    set(target, key, value, receiver) {
    if (key === self.key) {
    console.error("计算属性无法被赋值!");
    } else {
    return Reflect.set(target, key, value, receiver);
    }
    }
    });
    }
    }

    let observable_people = new Observable({
    name: "小明",
    age: 22,
    job: "student",
    info: ""
    });

    let observable_info_people = new Watcher(
    observable_people,
    "info",
    function() {
    let info =
    observable_people.name + "今年:" + observable_people.age;
    console.log("info 发生了修改:", info);
    return info;
    },
    function(new_value) {
    console.log("complete:", new_value);
    }
    );
    let observable_job_people = new Watcher(
    observable_people,
    "age",
    function() {
    return observable_people.age < 18 ? "student" : "programmer";
    },
    function(new_value) {
    console.log(observable_people.name + "的职业是:", new_value);
    }
    );

    observable_people.name = "小白";
    observable_people.age = 10;
    observable_people.age = 15;
    observable_people.age = 18;
    observable_people.age = 20;

    // info 发生了修改: 小明今年:22
    // 我的name属性被修改为小白了!
    // info 发生了修改: 小白今年:22
    // complete: 小白今年:22
    // 我的age属性被修改为10了!
    // info 发生了修改: 小白今年:10
    // complete: 小白今年:10
    // 小白的职业是: student
    // 我的age属性被修改为15了!
    // info 发生了修改: 小白今年:15
    // complete: 小白今年:15
    // 小白的职业是: student
    // 我的age属性被修改为18了!
    // info 发生了修改: 小白今年:18

    注:这里可以看到,每一次修改都会执行回调函数,这会代指在实际使用中会造成性能损耗或者页面重复刷新页面的问题

  2. 使用异步优化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    // 实现依赖收集

    class Dependence_manager {
    constructor() {
    // 存放依赖项

    this.dependence_array = new Set();
    }

    // 添加依赖

    depend(key) {
    if (Dependence_manager.target) {
    this.dependence_array.add({
    key: key,
    target: Dependence_manager.target
    });
    Dependence_manager.target = null;
    }
    }

    // 添加发布行为

    async notify(key) {
    this.dependence_array.forEach(function(dependence) {
    if (dependence.key == key) {
    dependence.target(key);
    }
    });
    await Dependence_manager.target_array.clear();
    }
    }

    Dependence_manager.target = null;
    Dependence_manager.target_array = new Set();

    class Observable {
    constructor(obj) {
    return this.define_reactive(obj);
    }
    define_reactive(obj) {
    const dependence_manager = new Dependence_manager();
    const handler = {
    get(target, key, receiver) {
    dependence_manager.depend(key);
    return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
    console.log(`我的${key}属性被修改为${value}了!`);
    Reflect.set(target, key, value, receiver);
    dependence_manager.notify(key);
    }
    };
    return new Proxy(obj, handler);
    }
    }

    class Watcher {
    constructor(obj, key, cb, on_computed_update) {
    this.obj = obj;
    this.key = key;
    this.cb = cb;
    this.on_computed_update = on_computed_update;
    return this.define_computed();
    }
    define_computed() {
    const self = this;
    let result;
    Dependence_manager.target = async key => {
    await console.log("watch");
    // console.log(Dependence_manager.target_array.keys(), key);

    if (!Dependence_manager.target_array.has(key)) {
    Dependence_manager.target_array.add(key);
    result = self.cb();
    self.on_computed_update(result);
    }
    };
    result = self.cb();

    return new Proxy(self.obj, {
    get(target, key, receiver) {
    if (key === self.key) {
    return result;
    } else {
    return Reflect.get(target, key, receiver);
    }
    },
    set(target, key, value, receiver) {
    if (key === self.key) {
    console.error("计算属性无法被赋值!");
    } else {
    return Reflect.set(target, key, value, receiver);
    }
    }
    });
    }
    }

    let observable_people = new Observable({
    name: "小明",
    age: 22,
    job: "student",
    info: ""
    });

    let observable_info_people = new Watcher(
    observable_people,
    "info",
    function() {
    let info =
    observable_people.name + "今年:" + observable_people.age;
    console.log("callback:", info);
    return info;
    },
    function(new_value) {
    console.log("complete:", new_value);
    }
    );
    let observable_job_people = new Watcher(
    observable_people,
    "job",
    function() {
    return observable_people.age < 18 ? "student" : "programmer";
    },
    function(new_value) {
    console.log(observable_people.name + "的职业是:", new_value);
    }
    );

    observable_people.name = "小白";
    observable_people.age = 10;
    observable_people.age = 15;
    observable_people.age = 18;
    observable_people.age = 20;

    // callback: 小明今年:22
    // 我的name属性被修改为小白了!
    // watch
    // 我的age属性被修改为10了!
    // watch
    // 我的age属性被修改为15了!
    // watch
    // 我的age属性被修改为18了!
    // watch
    // 我的age属性被修改为20了!
    // watch
    // callback: 小白今年:20
    // complete: 小白今年:20
    // 小白的职业是: programmer

参考文章:

  • 实战解析依赖收集
  • 深入实践 ES6 Proxy & Reflect
  • MDN
  • TechBridge 技術共筆部落格