Es6 对象Proxy

proxy [代理]

Es6中,新出现了一种新的引用类型–Proxy对象类型

创建一个Proxy 很简单,如下代码即可


var oproxy = new Proxy(target , handle);

Proxy 有什么用呢?

Proxy 英语意思是代理,其作用也是一种代理的效果

Proxy 是对于其他引用类型而言的,Proxy 的效果是给引用类型中间添加一层代理,当我们操控被代理的引用类型对象的时候,会先执行Proxy 代理部分的代码,由Proxy 代理部分的代码来决定具体的操作

栗子:

let obj = {
    a: 1
}

let oproxy = new Proxy(obj , {
    set(target, key) {
        target[key] = 1+100;
    }
});

oproxy.a = 2; 

console.log(obj.a) // 1+100 === 101

obj.a = 3;

console.log(obj.a) // 3

由上面的例子我们可以看到,当我们设置obj.a 的值的时候,可以选择通过代理操作,也可以选择直接操控对象

如果选择使用代理操控,会先走代理中的 set控制器, 而具体设置的值,便由控制器来决定了,所以最后设置的值是 101 而不是 2

而直接操控对象,就相当于直接赋值了,得到的值为3

如果代理中,没有任何的操作,则直接操作被代理的引用对象。

Proxy 控制器

关于Proxy的一大亮点就是Proxy的控制器,不同的控制器功能,决定了代理功能的不同

get

当我们通过代理来访问一个引用类型的属性的时候,就会激发get控制器

let arr = [100,777,345];

let oproxy = new Proxy(arr , {
    get(target , key) {
        console.log(`您访问了数组 的 第 ${~~key+1} 位成员 它的值是 ${target[key]} `);
        return target[key];

        // 需要注意的是,get控制器需要 显式的 return 获得的值,不然会默认返回underfined
    }
});

console.log(oproxy[1]);

// 您访问了数组 的 第 2 位成员 它的值是 777 
// 777

当我们通过Proxy 操作了数组的时候,就会激发,proxy get的操控器的代码。

get(target , key) {
    
}

// get 控制器传入两个参数,首参是被代理的引用对象,key是引用对象的成员索引 (数组里面就是下标)

set

当我们设置被代理对象的值的时候,就会激发set控制器


set(target , key , value) {
    
}

前两个参数和get相同,value 即是被设置的新值

我们可用set 进行传入数据的筛选工作

例如,在各大高校已经被讲烂的学生成绩系统…


let student = {
    name: "Owen",
    id: 10001
}

let oproxy = new Proxy(student , {
    set(target , key , value) {
        if (key === "score") {
            switch(true) {
                case value <= 100 && value>=85: 
                    target["grade"] = "优秀";
                    break;
                case value >=60 && value <85:
                    target["grade"] = "中等";
                    break;
                case value <60 && value >=0:
                    target["grade"] = "不及格";
                    break;
            }
        }
        target[key] = value;  // 显式的赋值
    }
})

oproxy.score = 88;
console.log(student)

//Object {
//  name: "Owen", 
//  id: 10001, 
//  grade: "优秀", 
//  score: 88}

值得注意的是,使用了set控制器的时候,需要显式的为被代理的对象赋值

has

has 可以拦截 in 操作符的使用

用起来也是小白级操作

let obj = {
    name: "Zyz",
    age : 18,
    score: 81 
}

let obj2 = {
    name: "Owen",
    age: 19,
    score: 59
}
let handler = {
   has(target , key) {
       if(key === "score" && target[key] <= 60) {
        console.log("偷偷的考砸的分数藏起来");
        return false;
       }

       return key in target;  // 需要显式的返回遍历的内容
   } 
}

let oproxy1 = new Proxy(obj , handler);
let oproxy2 = new Proxy(obj2 , handler);

for(let a in oproxy1) {
    console.log(oproxy1[a]);
}

for(let b in oproxy2) {
    console.log(oproxy2[b])
}

in 操作符的另外一个作用就是检查成员是否在对象中(实例或原型) 如果,实例中成员存在,就会忽略原型中成员的存在

我们也可以使用has 激发此次操作

var handler = {
  has (target,key) {
    if(key in target) {
        console.log(`${key} 确实存在,值是${target[key]}`);
    } else {
        console.log(`${key} 并不存在于被代理的对象中`);
    }
  }
};

var target = { prop: 'foo', _bar: 'baz', _prop: 'foo' };
var proxy = new Proxy(target, handler);

"_bar" in proxy;
"+++prop" in proxy;

apply

对于函数类型的引用对象,我们可以选用apply的控制器

let count = 0;

let func = function () {
    return "i am Owen";
}

let oproxy = new Proxy(func , {
    apply(target) {
        return "i am zyz";
    }    
});

console.log(oproxy());

由上面的栗子,可以看出 apply 可以左右函数的执行

apply 控制器可传入三个参数

apply(target , ctx , args) {
    
}

target 即目标函数

ctx 传入的是执行的上下文环境

args 函数的形参

值得注意的是,ctx 需要显式的传入,所以我们使用 apply , call , bind 这些可以改变函数上下文的函数时,我们就可以接收到ctx

let func = function() {
    console.log("i am zyz");
}

let oproxy = new Proxy(func, {
    apply(target , ctx) {
        console.log(ctx);
    }
});

oproxy.call(this); // window

args 是传入参数的集合,该集合并不是 arguments 这样的类数组对象,而是真真切切的数组(很欣慰)


let func = function (name , age) {
        
}

let oproxy = new Proxy(func , {
    apply(target , ctx , args) {
        console.log(...args);
    }   
});

oproxy("Owen" , 18);

construct

construct 控制器,监听的是new 关键字

当咱用new 构造一个对象的时候,construct 控制器就会起作用

function I (name , age , gender , from ) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.from = from;
}


let oproxy = new Proxy(I , {
    construct(target , args) {
        console.log(args);
        return new target(...args) // 一定要显式的返回一个构造对象
    }
})

let Owen = new oproxy("Owen" , 18 , "male" , "ShenZhen");

console.log(Owen);

值得注意的是,如果没有显式的返回一个创建的对象,那么就会报错~!

construct(target , args) {
    
}

target 即被代理的目标

args 即参与被构造的对象参数集合

deleteProperty

这个控制器和 delete 关键字有关,

当我们使用 delete 来删除变量的时候,就会激发这个控制器

let obj = {__a:1,b:2}

let oproxy = new Proxy(obj , {
    deleteProperty(target , key , x) {
        if (key === "__a") {
            console.log(`${key} 属性不能被删除!`);
        } else {
            delete target[key];
        }
    }
});


delete oproxy.a;
delete oproxy.b;

console.log(obj) // Object {__a: 1}

我们可以使用这个控制器来让带有__ 的属性禁止被删除

和上面一样,需要在代理中来操控原对象

如果代理对象 return false 或 throw Error 了,就不能完成删除操作

getOwnPropertyDescriptor

getOwnPropertyDescriptor 是当程序执行了Object.getOwnPropertyDescriptor 的时候会被激发的

栗子如下:

let obj = {
    a:1,
    b:2,
    c:3
}

let oproxy = new Proxy(obj , {
    getOwnPropertyDescriptor(target,key) {
        console.log("我被执行了");
        return Object.getOwnPropertyDescriptor(target,key);
    }
});



let objProperty = Object.getOwnPropertyDescriptor(oproxy,"a");

console.log(objProperty);

由于博主才学疏浅,其具体用途暂时没能想到,(肯定有其妙用)

getPrototypeOf

直接看栗子

let Person = function (name,age) {
    this.name = name;
    this.age = age;
}

Person.prototype = {
    sayHello() {
        console.log("Hello World");
    }
}

let Owen = new Person("Owen" , 20);

let oproxy = new Proxy(Owen , {
    getPrototypeOf(target) {
        console.log(`Owen 实例的原型被获取了`);
        return Object.getPrototypeOf(Owen);
    }
});

let proto = Object.getPrototypeOf(oproxy);
console.log(proto);

isExtensible

Object.isExtensible 即对象是否可以被扩展,如果这个对象没有被对象三大封印咒(preventExtensions seal freeze) 给封印(中二犯了哈哈)

其中:

Object.preventExtensions 是魔域血咒 拿来封印恶魔,防止其继续增强力量(原有力量不变)

Object.seal 是五行大法 充其量就是拿来压压孙猴子

Object.freeze 是须弥大法 可以封印天地灵气,荡涤生灵

那么探测法术isExtensible 就会返回true,不然就是false 说明该对象已经被封印了

那么代理控制器isExtensible,就是当探测法术发起的时候被激发咯

来看几个栗子

Object.preventExtensions 封印了恶魔对象,恶魔对象只能运用仅存的力量,而不能获取新的力量

let obj = {
    a: 1
}

Object.preventExtensions(obj);

obj.a = 2;
obj.b = 4;  // 无法新增力量

Object.defineProperty(obj , "a" ,{
    writable: false
});

console.log(obj);
console.log(Object.getOwnPropertyDescriptor(obj,"a"));
// Object {value: 2, writable: false, enumerable: true, configurable: true}

但是恶魔能自己自爆,强行把自己的力量释放

let obj = {
    a: 1
}

Object.preventExtensions(obj);

delete obj.a;

console.log(obj);

Object.seal 的力量能把齐天大圣压在五指山下,同样也能封印恶魔的力量

但是,seal 的强大力量,还能防止孙猴子和恶魔自爆

let obj = {
    a: 1
}

Object.seal(obj);

delete obj.a; // 无法被释放

console.log(obj);

而更加可怕的freeze 须弥大法将天地生灵完全冻结,剥夺各种生物对自身力量的控制

let obj = {
    a: 1
}

Object.freeze(obj);

obj.a = 2;  // 无法操控力量
obj.b = 4;  // 无法新增力量

console.log(obj);
console.log(Object.getOwnPropertyDescriptor(obj,"a"));
// Object {value: 2, writable: false, enumerable: true, configurable: false}


Object.defineProperty(obj , "a" ,{
    writable: true
});
// Cannot redefine property: a

freeze大法强烈的折磨天地生灵,让其求死不能

let obj = {
    a: 1
}

Object.freeze(obj);

delete obj.a
console.log(obj)

列个表格吧

  可增添 可修改 可删除
方法      
preventExtensions false true true
seal false true false
freeze false false false

上面的内容算是复习了OOP 对象的密封方法,防止组件使用者擅自修改你的对象

isExtensible 控制器很简单,即当使用Object.isExtensible 方法的时候会被激发

let obj = {
    a: 1
}

Object.preventExtensions(obj);

let proxy = new Proxy(obj , {
    isExtensible(target) {
        console.log(`探测了这个对象是否可以被扩展`)
        return Object.isExtensible(target)
    }
});

console.log(Object.isExtensible(proxy));

ownKeys

见栗子


function createObj(a,b) {
    this.a = a;
    this.b = b;
}

let obj =new createObj(132 , 456);

let oproxy = new Proxy(obj , {
    ownKeys(target) {
        console.log(`探测到了该对象的keys 值`);
        return Object.keys(target)
    }
});

console.log(Object.keys(oproxy));

从上文中我们可以看到,我们使用keys 的时候就会激发该代理控制器

preventExtensions

setPrototypeOf

剩下也是如同字面解释 分别是来拦截,Object.preventExtensions 和 Object.setPrototypeOf 的

如感兴趣的可以自己实现栗子

Proxy.revocable

当我们需要一个可随时取消的代理对象的时候,我们就可以调用这个方法

let target = {};
let handler = {};

let revocableProxy = Proxy.revocable(target, handler);

使用这个方法 可以产生一个对象


Object {
    proxy: Object
    revoke: function()
}

这个对象包含两个成员,一个成员就是设置的代理对象

另外一个revoke 就是删除函数

我们可以使用对象的解构法来获取这两个对象成员

let obj = {
    a: 1
};

let handler = {
    set(target , key, value) {
        console.log(`我被设置了一个新值 ${value}`);
        target[key] = value;
    }    
};

let {proxy , revoke} = Proxy.revocable(obj , handler);

proxy.a = 2;

revoke();

proxy.a = 3;

// Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked

Summery

代理对象是es6 新增加的用于操控对象的api,让我们更加的方便的操作和使用oop编程

感谢

es ruanyf

- CATALOG -