Object.defineProperty-Proxy

getter    setter

1
2
3
4
5
6
7
8
9
let obj = {
get a(){
// todo
console.log("取值了")
return 1;
}
}
console.log(obj.a); // 1
// 这样设置和直接给obj.a赋值是一样的,不过可以在取值的时候todo
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
_a: '',
get a(){
// todo
console.log("取值了")
return this._a;
},
set a(value){
this._a = value;
}
}
obj.a = 100;
console.log(obj.a); // 取值了 100

示例

vue中的数据劫持

给每个对象都添加一个getter setter 当值变化可以实现更新视图的功能

1
2
3
4
5
6
7
8
9
let obj = {
a: 1,
b: 2
}
let updateView = () => {
console.log("更新");
}
obj.a = 100;
// 希望的是 a的值一改变就能触发updateView这个方法 先掌握下面的基础知识

Object.defineProperty

1
2
3
4
5
6
7
8
let obj = {};
Object.defineProperty(obj, 'a', {
value: 1, // 默认是不可枚举的
})
console.log(obj); // {}
for(let key in obj){
console.log(key); // {} value的值不能被循环输出
}
1
2
3
4
5
6
7
8
9
let obj = {};
Object.defineProperty(obj, 'a', {
value: 1, // 默认是不可枚举的
enumerable: true, //改成true就可以枚举了, 原型上的方法也是不可枚举的
})
console.log(obj); // { a: 1 }
for(let key in obj){
console.log(key); // a
}
1
2
3
4
5
6
7
8
9
let obj = {};
Object.defineProperty(obj, 'a', {
value: 1,
enumerable: true,
writable: true, // 是否可写 默认false 这里没有配置就不能更改他的值了 line8
configurable: true, // 是否可删除 默认false
})
obj.a = 100;
console.log(obj); // { a: 100 }

现在加入get set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {};
let val = '';
Object.defineProperty(obj, 'a', {
// value: 1, // 需要把这行隐藏 否则报错
enumerable: true,
//writable: true, // 需要把这行隐藏 否则报错
configurable: true,
get(){
return val;
},
set(value){
val = value;
}
})
console.log(obj); // { a: [Getter/Setter] }

现在我希望把obj上的ab循环 将他们的定义方式和上面一样 如下:

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
let obj = {
// a: 1,
a: {a:{a:1}},
b: 2
}
// 模拟下vue源码 缺陷:无法监控数组的变化
function observer(obj) { // obj对象里面可能还有对象line3 所以这里新起了一个函数用来递归
if(typeof obj !== 'object' || obj == null){
return ;
}
for(let key in obj){
defineReactive(obj, key, obj[key]);
}
}
let updateView = () => {
console.log("更新");
}
// 因为defineProperty需要一个公共的vaule去修改(供get、set)所以这里新起了一个defineReactive函数,这样这个value的作用域就是在下面这个函数中
function defineReactive(obj, key, value){ // 这里面这个value就在当前作用域下
observer(value); // value可能是对象,这里递归去赋值get、set
Object.defineProperty(obj, key, {
get(){
return value;
},
set(val){
updateView();
value = val;
}
})
}
observer(obj);
obj.a.a = 100;
console.log(obj, obj.a.a);
/*执行结果:
更新
{ a: [Getter/Setter], b: [Getter/Setter] } 100
*/

缺陷:现在我已近给a、b设置get、set了,但是如果我新加一个c,它是没有get、set的

1
2
obj.c = 200;
console.log(obj); // { a: [Getter/Setter], b: [Getter/Setter], c: 200 }

下面用proxy来实现:

Proxy

特点 兼容性差
代理 可以创造一个代理 帮我们干某些事

对象代理
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
a: 1,
b: 2
}
let proxy = new Proxy(obj, {
get(){

},
set(target, key, value){
console.log(target, key, value); // { a: 1, b: 2 } 'c' 100
}
})
proxy.c = 100;

现在给obj赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
a: 1,
b: 2
}
let proxy = new Proxy(obj, {
get(){

},
set(target, key, value){
target[key] = value; //这行不够优雅 可如下
}
})
proxy.c = 100;
console.log(obj);
// { a: 1, b: 2, c: 100 }

可以用Reflect 反射属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let obj = {
a: 1,
b: 2
}
let proxy = new Proxy(obj, {
get(target, key){
console.log("取值");
return Reflect.get(target, key);
},
set(target, key, value){
//target[key] = value;
console.log("更新");
return Reflect.set(target, key, value);
}
})
proxy.c = 100; // 更新
console.log(obj); // { a: 1, b: 2, c: 100 }
console.log(obj.c); // 100
console.log(proxy.c); // 取值 100
数组代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let obj = [1,2,3];
let proxy = new Proxy(obj, {
get(target, key){
console.log("取值");
return Reflect.get(target, key);
},
set(target, key, value){
//target[key] = value;
console.log(target, key, value);
console.log("更新");
return Reflect.set(target, key, value);
}
})
proxy.push(3);
/*执行结果
[ 1, 2, 3 ] '3' 3
更新
[ 1, 2, 3, 3 ] 'length' 4
更新
*/

这里把修改length长度也打印出来了,做如下优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let obj = [1,2,3];
let proxy = new Proxy(obj, {
get(target, key){
return Reflect.get(target, key);
},
set(target, key, value){
//target[key] = value;
if(key === 'length') return true;
console.log(target, key, value);
console.log("更新");
return Reflect.set(target, key, value);
}
})
proxy.push(3);
/*执行结果:
[ 1, 2, 3 ] '3' 3
更新
*/
console.log(obj, proxy); // [ 1, 2, 3, 3 ] [ 1, 2, 3, 3 ]

优点: 支持数组,可直接更改数组 达到拦截的目的
当obj中有多层对象时,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let obj = {
a: {b:{c:{d: 1}}},
b: 1
}
let proxy = new Proxy(obj, { //只代理当前对象obj 1层
get(target, key){
//console.log("取值");
return Reflect.get(target, key);
},
set(target, key, value){
//target[key] = value;
if(key === 'length') return true;
console.log(target, key, value);
console.log("更新");
return Reflect.set(target, key, value);
}
})
proxy.a.b = 100; // 这里没走“更新” 是因为proxy只代理这个对象obj 现在希望的是能走“更新”触发代理对象

所以要递归代理 这里要给 {b:{c:{d: 1}}} 这个对象加上代理,然后要把这个代理赋给a,这样就会触发obj的set ,同样 {c:{d: 1}}要给这个对象加上代理,就会触发{b:{c:{d: 1}}}这个对象的set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let obj = {
a: {b:{c:{d: 1}}},
b: 1
}
let proxy = new Proxy(obj, { //只代理当前对象 1层
get(target, key){
if(typeof target[key] === 'object' && target[key] !== null){
return new Proxy(target[key], handler); // 这里递归了这个代理的过程,将这部分抽离出来
}
return Reflect.get(target, key);
},
set(target, key, value){
if(key === 'length') return true;
console.log(target, key, value);
console.log("更新");
return Reflect.set(target, key, value);
}
})
proxy.a.b = 100;

将递归代理的这个过程抽离出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let obj = {
a: {b:{c:{d: 1}}},
b: 1
}
let handler = {
get(target, key){
console.log("get");
if(typeof target[key] === 'object'){
return new Proxy(target[key], handler); // 如果是对象就返回这个对象的代理
}
return Reflect.get(target, key);
},
set(target, key, value){
if(key === 'length') return true;
console.log("更新");
return Reflect.set(target, key, value);
}
}
let proxy = new Proxy(obj, handler)
proxy.a.b = 100; // get 更新
console.log(obj); // { a: { b: 100 }, b: 1 }