第一个参数即所要代理的目标对象,target是被代

2020-01-04 14:24栏目:美高梅开户
TAG:

  1. 概述

美高梅开户送58元官网 1

defineProperty()

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种『元编程』即对编程语言进行编程。

Proxy 对象

Proxy 用来修改某些默认操作,等同于在语言层面做出修改。所以属于一种元编程(meta programming), 即对编程语言进行编程。字面理解为Proxy代理了某些默认的操作。
其使用格式如下:

var proxy = new Proxy(target, handler);

target是被代理的目标对象,handler也是个对象,用来定制拦截行为,内部定义每个被代理的行为。
注意:

  • 如果希望这个代理有效,需要在 proxy 对象上调用属性方法,而不是在 target 上调用
  • 如果指定 handler 为空对象,那么得到对象和原对象一样
  • 得到的 proxy 是 target 的引用,如果没有代理,在 proxy 上的修改和在 target 上的修改等同

看一个简单的实例

var proxy = new Proxy({},{
  get: function(target, key){
    return 35;
  }
});
console.log(proxy.time);    //35
console.log(proxy.name);    //35
console.log(proxy.title);    //35
//被代理的对象无论输入什么属性都返回35

实际上,proxy 对象也可以被继承:

var proxy = new Proxy({},{
  get: function(target, key){
    return 35;
  }
});
var obj = Object.create(proxy);
obj.time = 20;
console.log(obj.time);    //20
console.log(obj.name);    //35

感受一下它的威力:

var obj = new Proxy({}, {
  get: function(target, key, receiver){
    console.log(`getting ${key} ...`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver){
    console.log(`setting ${key} ...`);
    return Reflect.set(target, key, value, receiver);
  }
});

obj.count = 1;            //setting count ...
++obj.count;              //getting count ...
                          //setting count ...
console.log(obj.count);   //getting count ...
                          //2

可以看出来,handler对象中 get 方法表示属性的访问请求,set 方法表示属性的写入请求。
当然不仅仅 get 和 set, 我们可以定义以下拦截函数:

  • get(target, propKey, receiver = target)
    拦截对象的读取属性。当 target 对象设置了 propKey 属性的 get 函数时,receiver 绑定 get 函数的 this。返回值任意
  • set(target, propKey, value, receiver = target)
    拦截对象的写入属性。返回一个布尔值
  • has(target, propKey)
    拦截 propKey in proxy 操作符,返回一个布尔值
  • deleteProperty(target, propKey)
    拦截 delete proxy[propKey] 操作符,返回一个布尔值
  • enumerate(target)
    拦截 for(let i in proxy) 遍历器,返回一个遍历器
  • hasOwn(target, propKey)
    拦截 proxy.hasOwnProperty('foo'),返回一个布尔值
  • ownKeys(target)
    拦截 Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy),返回一个数组。该方法返回对象所有自身属性,包括不可遍历属性,不包括 Symble属性,但是Object.keys(proxy)不应该包括不可遍历属性
  • getOwnPropertyDescriptor(target, propKey)
    拦截 Object.getOwnPropertyDescriptor(proxy, propKey),返回其属性描述符
  • defineProperty(target, propKey, propDesc)
    拦截 Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDesc),返回一个布尔值
  • preventExtensions(target)
    拦截 Object.preventExtensions(proxy),返回一个布尔值
  • getPrototypeOf(target)
    拦截 Object.getPrototypeOf(proxy),返回一个对象
  • isExtensible(target)
    拦截 Object.isExtensible(proxy),返回一个布尔值
  • setPrototypeOf(target, proto)
    拦截 Object.setPrototypeOf(proxy, proto),返回一个布尔值
  • apply(target, object, args)
    拦截对 proxy 实例的函数操作,包括 proxy(...args),proxy.call(object, ...args),proxy.apply(object, args)
  • construct(target, args, proxy)
    拦截用 new 调用 proxy 函数的操作,construct()返回的不是对象会报错

以下列举一些 Proxy 的实例

访问对象不存在的属性报错

var obj = new Proxy({}, {
  get: function(target, key){
    if(key in target){
      return Reflect.get(target, key);
    } else {
      throw new ReferenceError(`"${key}" is not in object`);
    }
  }
});
obj.look = "picture";
console.log(obj.look);     //"picture"
console.log(obj.sleep);    //ReferenceError: "sleep" is not in object

数组索引为负时返回倒数位置的值

var origin = [10,20];
var arr = new Proxy(origin, {
  get(target, key){
    let index = parseInt(key);
    if(index < 0){
      index = target.length + index;
      if(index < 0) return undefined;
    }
    return Reflect.get(target, index);
  }
});
console.log(arr[0]);     //10
console.log(arr[1]);     //20
console.log(arr[2]);     //undefined
console.log(arr[-1]);    //20
console.log(arr[-4]);    //undefined

保护对象内以 "_" 开头的属性为私有属性:

var o = {
  "_name": "Bob",
  "age": 13,
  "_fun": function(){
    console.log("_fun is called");
  }
};
var obj = new Proxy(o, {
  get(target, key){
    if(key.charAt(0) === '_'){
      return undefined;
    }
    return Reflect.get(target, key);
  },
  set(target, key, value){
    if(key.charAt(0) === '_'){
      throw new Error('Cannot define a property begin with "_"');
    }
    return  Reflect.set(target, key, value);
  },
  has(target,key){
    if(key.charAt(0) === '_'){
      return false;
    }
    return Reflect.has(target, key);
  },
  deleteProperty(target,key){
    if(key.charAt(0) === '_'){
      return false;
    } else {
      Reflect.deleteProperty(..arguments);
    }
  },
  apply(target,ctx,args){
    if(target.name.charAt(0) === '_'){
      throw new TypeError(`${target.name} is not defined`);
    } else {
      Reflect apply(...arguments);
    }
  },
  defineProperty(target,key,desc){
    if(key.charAt(0) === '_'){
      return new Error(`cannot define property begin with "_"`);
    } else {
      Reflect.defineProperty(..arguments);
    }
  },
  setPrototypeOf(target,proto){
    throw new TypeError(`Cannot change the proto of ${target}`);
  },
  construct(target,ctx,args){
    if(target.name.charAt(0) === '_'){
      throw new TypeError(`${target.name} is not defined`);
    } else {
      Reflect construct(...arguments);
    }
  }
});

console.log(obj.age);    //13
obj.age = 20;
console.log(obj.age);    //20
console.log(obj._name);  //undefined
obj._hobby = "Coding";   //Error: Cannot define a property begin with "_"
_name in key             //false
delete obj._name;
Object.defineProperty(obj,"_hobby",{
  value: "Coding"
});
Object.defineProperties(obj,{
  '_hobby': {
    value: "Coding"
  }
});
obj._fun();
var a = new obj._fun();
obj.__proto__ = {};     //Cannot define a property begin with "_"
Object.setPrototypeOf(obj,{})    //Cannot change the proto of obj

当然不是所有 proxy 代理都不可取消,下面方法设置的代理是可以通过定义代理时返回的revoke函数取消:

var a = {
  name:"Bob"
};
var {proxy, revoke} = Proxy.revocable(a, {
  get(target,key){
    return undefined;
  }
});
proxy.name;   //undefined;
revoke();
proxy.name;   //TypeError: Cannot perform 'get' on a proxy that has been revoked

学习书籍《ECMAScript 6 入门 》

1.1 理解

Reflect 对象

Reflect 对象有一下作用:

  1. 将 Object对象的一些明显属于语言层面的方法部署在 Reflect 上
  2. 修改某些 Object 对象的方法使其更合理。比如 Object.defineProperty 遇到无法定义属性时会抛出错误,而 Reflect.defineProperty 会返回 false
  3. 把所以 object 的操作都替换成函数行为,比如用 Reflect.has(obj,name) 替换 name in obj
  4. 保证只要是 Proxy 有的方法就一定可以在 Reflect 上找到相同的方法,这样可以在实现 proxy 时方便的完成默认行为。换言之,无论 proxy 怎么修改默认行为,你总可以在 Reflect 上找到真正默认的行为

代理在添加额外的功能时,利用 Reflect 保证了原始功能的实现。举个例子:

var loggedObj = new Proxy({}, {
  get(target,propKey){
    console.log(`getting ${target}.${propKey}`);  //当然你最好把操作记录到一个 log 中
    return Reflect.get(target,propKey);
  }
});

Reflect有以下方法:

  • Reflect.getOwnPropertyDescriptor(target, propKey)
    等同于 ObjectgetOwnPropertyDescriptor(target, propKey)
  • Reflect.defineProperty(target,propKey,desc)
    等同于 Object.defineProperty(target,propKey,desc)
  • Reflect.getOwnPropertyNames(target)
    等同于 Object.getOwnPropertyNames(target)
  • Reflect.getPrototypeOf(target)
    等同于 Object.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, proto)
    等同于 Object.setPrototypeOf(target, proto)
  • Reflect.deleteProperty(target, propKey)
    等同于 delete target.propKey
  • Reflect.enumerate(target)
    等同于 for ... in target
  • Reflect.freeze(target)
    等同于 Object.freeze(target)
  • Reflect.seal(target)
    等同于 Object.seal(target)
  • Reflect.preventExtensions(target)
    等同于 Object.preventExtensions(target)
  • Reflect.isFrozen(target)
    等同于 Object.isFrozen(target)
  • Reflect.isSealed(target)
    等同于 Object.isSealed(target)
  • Reflect.isExtensible(target)
    等同于 Object.isExtensible(target)
  • Reflect.has(target, propKey)
    等同于 propkey in object
  • Reflect.hasOwn(target, propKey)
    等同于 target.hasOwnProperty(propKey)
  • Reflect.ownKeys(target)
    遍历得到target自身所有属性,包括不可枚举属性,不包括 Symbol 属性
  • Reflect.get(target,propKey, receiver = target)
    如果 propKey 是个读取器,则读取器中的 this 绑定到 receiver
var per = {
  bar: function(){console.log("per-bar")}
}
var obj = {
  get foo(){ this.bar(); },
  bar: function (){console.log("obj-bar")}
};
Reflect.get(obj, "foo", per);    //"per-bar"
  • Reflect.set(target,propKey, value, receiver = target)
    如果 propKey 是个读取器,则读取器中的 this 绑定到 receiver
  • Reflect.apply(target, thisArg, args)
    等同于 Function.prototype.apply.call(target, thisArg, args)thisArg.target(args)
  • Reflect.construct(target,args)
    等同于 new target(...args)

注意以上方法中,Reflect.set(), Reflect.defineProperty(), Reflect.freeze(), Reflect.seal(), Reflect.preventExtensions() 在成功时返回 true, 失败时返回 false。对应的 Object 方法失败时会抛出错误。

Proxy


Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

Proxy 构造函数

var proxy = new Proxy(target,handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

实例代码:

var handler = {

    get: function(target, name) {

        if (name === 'prototype') {

            return Object.prototype;

        }

        return 'Hello, ' + name;

    },

    apply: function(target, thisBinding, args) {

        return args[0];

    },

   construct: function(target, args) {

        return {value: args[1]};

    }

};

var fproxy = new Proxy(function(x, y) {

    return x + y;

  }, handler);

fproxy(1, 2)  // 1

new fproxy(1,2) // {value: 2}

fproxy.prototype === Object.prototype // true

fproxy.foo // "Hello, foo"

下面是 Proxy 支持的拦截操作一览。

(1)get(target, propKey, receiver)

拦截对象属性的读取,比如proxy.fooproxy['foo']

最后一个参数receiver是一个对象,可选,参见下面Reflect.get的部分。

(2)set(target, propKey, value, receiver)

拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。

(3)has(target, propKey)

拦截propKey in proxy的操作,返回一个布尔值。

(4)deleteProperty(target, propKey)

拦截delete proxy[propKey]的操作,返回一个布尔值。

(5)ownKeys(target)

拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

(6)getOwnPropertyDescriptor(target, propKey)

拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

(7)defineProperty(target, propKey, propDesc)

拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。

(8)preventExtensions(target)

拦截Object.preventExtensions(proxy),返回一个布尔值。

(9)getPrototypeOf(target)

拦截Object.getPrototypeOf(proxy),返回一个对象。

(10)isExtensible(target)

拦截Object.isExtensible(proxy),返回一个布尔值。

(11)setPrototypeOf(target, proto)

拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。

如果目标对象是函数,那么还有两种额外操作可以拦截。

(12)apply(target, object, args)

拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)*proxy.apply(...)。*

(13)construct(target, args)

拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

get()

下面的例子使用get拦截,实现数组读取负数的索引。


function createArray(...elements) {

    let handler = {

        get(target, propKey, receiver) {

            let index = Number(propKey);

            if (index < 0) {

                propKey = String(target.length + index);

            }

             return Reflect.get(target, propKey, receiver);

        }

    };

    let target = [];

    target.push(...elements);

    return new Proxy(target, handler);

}

let arr = createArray('a', 'b', 'c');

arr[-1] // c

该例子,通过判定index来对数组的角标返回值进行拦截过滤

if(index<0){

    propKey=String(target.length+index);

}

即当index小于0时,propkey也就是 arr[-1]  传入的-1的index,对其进行修改,让 propkey的值变为 (target.length+index)= 3+(-1)=2;

然后执行反射函数 Reflect;

Reflect.get(target, propKey, receiver)

通过字面意思翻译以及上面代码的返回值 可猜测到  Refect为反射函数;

而该句的意思为:用过Reflect 调用 拦截对象 本身的get方法执行;


利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。


var pipe = (function () {

    return function (value) {

        var funcStack = [];

       var oproxy = new Proxy({} , {

            get : function (pipeObject, fnName) {

                if (fnName === 'get') {

                   return funcStack.reduce(function (val, fn) {

                        return fn(val);

                   },value);

                 }

                funcStack.push(window[fnName]);

                return oproxy;

          }

       });

        return oproxy;

      }

    }());

var double = n => n * 2;

var pow    = n => n * n;

var reverseInt = n => n.toString().split("").reverse().join("") | 0;

pipe(3).double.pow.reverseInt.get; // 63

该例子用到了一个没见过的方法也就是reduce();通过百度知道了 这是Array 数组里面的一个方法;

reduce()对数组中的所有元素调用指定的回调函数。该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。

美高梅开户送58元官网 2

reduce()

也就是说reduce方法的第一个参数是回调函数,函数格式如下

美高梅开户送58元官网 3

reduce方法的回调函数

而回调函数的第一个参数为上次回调的值,如果首次调用该函数,则参数的值为initalValue.

所以

funcStack.reduce(function (val, fn) {

return fn(val);

},value);

该行代码的执行结果为: 

  第一次调用  val = value = 3  ;  fn =  n => n * 2  ;   return 值 : 3*2 = 6;

  第二次调用  val = 6 ; value = 3  ;  fn =  n => n * n ;  return 值 : 6*6 = 36;

  第三次调用  val = 36 ; value = 3  ;  fn =  n =>n.toString().split("").reverse().join("") | 0  ;  

                       return 值 :把36变成字符串 然后分割 反转 变成 '63' ,在转换成 数字  63;

因此,最后的执行结果为 63 .


set()

set方法用来拦截某个属性的赋值操作。

假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的属性值符合要求。

let validator = {

    set: function(obj, prop, value) {

        if (prop === 'age') {

           if (!Number.isInteger(value)) {

               throw new TypeError('The age is not an integer');

           }

           if (value > 200) {

             throw new RangeError('The age seems invalid');

           }

        }

        // 对于age以外的属性,直接保存

        obj[prop] = value;

      }

  };

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100

person.age = 'young' // 报错

person.age = 300 // 报错


apply()

apply方法拦截函数的调用、callapply操作。

apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

var handler = {

    apply (target, ctx, args) {

        return Reflect.apply(...arguments);

    }

};

下面是一个例子。

var target = function () {  return 'I am the target';  };

var handler = {

    apply: function () {

        return 'I am the proxy';

    }

};

var p = new Proxy(target, handler);

p()

// "I am the proxy"

上面代码中,变量p是 Proxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。

下面是另外一个例子。

var twice = {

    apply (target, ctx, args) {

        return Reflect.apply(...arguments) * 2;

    }

};

function sum (left, right) {

    return left + right;

};

var proxy = new Proxy(sum, twice);

proxy(1, 2) // 6

proxy.call(null, 5, 6) // 22

proxy.apply(null, [7, 8]) // 30

上面代码中,每当执行proxy函数(直接调用或call和apply调用),就会被apply方法拦截。

另外,直接调用Reflect.apply方法,也会被拦截。

Reflect.apply(proxy,null,[9,10])// 38


has()

has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。

下面的例子使用has方法隐藏某些属性,不被in运算符发现。

var handler = {

    has (target, key) {

        if (key[0] === '_') {

            return false;

        }

       return key in target;

    }

};

var target = { _prop: 'foo', prop: 'foo' };

var proxy = new Proxy(target, handler);

'_prop' in proxy // false

上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has就会返回false,从而不会被in运算符发现。

如果原对象不可配置或者禁止扩展,这时has拦截会报错。

var obj = { a: 10 };

Object.preventExtensions(obj);

var p = new Proxy(obj, {

    has: function(target, prop) {

        return false;

    }

});

'a' in p // TypeError is thrown

上面代码中,obj对象禁止扩展,结果使用has拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则has方法就不得“隐藏”(即返回false)目标对象的该属性。

值得注意的是,has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性。

另外,虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效。

let stu1 = {name: '张三', score: 59};

let stu2 = {name: '李四', score: 99};

let handler = {

    has(target, prop) {

        if (prop === 'score' && target[prop] < 60) {

            console.log(`${target.name} 不及格`);

            return false;

        }

        return prop in target;

    }

};

let oproxy1 = new Proxy(stu1, handler);

let oproxy2 = new Proxy(stu2, handler);

'score' in oproxy1

// 张三 不及格

// false

'score' in oproxy2

// true

for (let a in oproxy1) {

console.log(oproxy1[a]);

}

// 张三

// 59

for (let b in oproxy2) {

console.log(oproxy2[b]);

}

// 李四

// 99

上面代码中,has拦截只对in循环生效,对for...in循环不生效,导致不符合要求的属性没有被排除在for...in循环之外。


construct()

construct方法用于拦截new命令,下面是拦截对象的写法。

var handler = {

    construct (target, args, newTarget) {

        return new target(...args);

    }

};

construct方法返回的必须是一个对象,否则会报错。

construct方法可以接受两个参数。

    --target: 目标对象

    --args:构建函数的参数对象

var p = new Proxy(function () {}, {

    construct: function(target, args) {

        console.log('called: ' + args.join(', '));

        return { value: args[0] * 10 };

    }

});

(new p(1)).value

// "called: 1"

// 10


deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

var handler = {

    deleteProperty (target, key) {

        invariant(key, 'delete');

        return true;

    }

};

function invariant (key, action) {

    if (key[0] === '_') {

        throw new Error(`Invalid attempt to ${action} private "${key}" property`);

    }

}

var target = { _prop: 'foo' };

var proxy = new Proxy(target, handler);

delete proxy._prop

// Error: Invalid attempt to delete private "_prop" property

上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。


defineProperty()

defineProperty方法拦截了Object.defineProperty操作。

var handler = {

    defineProperty (target, key, descriptor) {

        return false;

    }

};

var target = {};

var proxy = new Proxy(target, handler);

proxy.foo = 'bar'

// TypeError: proxy defineProperty handler returned false for property '"foo"'

上面代码中,defineProperty方法返回false,导致添加新属性会抛出错误。

注意,如果目标对象不可扩展(extensible),则defineProperty不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty方法不得改变这两个设置。


getOwnPropertyDescriptor()

getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor*,返回一个属性描述对象或者*undefined。

var handler = {

    getOwnPropertyDescriptor (target, key) {

        if (key[0] === '_') {

        return;

美高梅开户送58元官网,        }

    return Object.getOwnPropertyDescriptor(target, key);

   }

};

var target = { _foo: 'bar', baz: 'tar' };

var proxy = new Proxy(target, handler);

Object.getOwnPropertyDescriptor(proxy, 'wat')

// undefined

Object.getOwnPropertyDescriptor(proxy, '_foo')

// undefined

Object.getOwnPropertyDescriptor(proxy, 'baz')

// { value: 'tar', writable: true, enumerable: true, configurable: true }

上面代码中,handler.getOwnPropertyDescriptor方法对于第一个字符为下划线的属性名会返回undefined


getPrototypeOf()

getPrototypeOf方法主要用来拦截Object.getPrototypeOf()运算符,以及其他一些操作。

    --Object.prototype.__proto__

    --Object.prototype.isPrototypeOf()

    --Object.getPrototypeOf()

    --Reflect.getPrototypeOf()

    --instanceof运算符

var proto = {};

var p = new Proxy({}, {

    getPrototypeOf(target) {

        return proto;

    }

});

Object.getPrototypeOf(p) === proto // true

上面代码中,getPrototypeOf方法拦截Object.getPrototypeOf(),返回proto对象。

注意,getPrototypeOf方法的返回值必须是对象或者null,否则报错。另外,如果目标对象不可扩展(extensible),getPrototypeOf方法必须返回目标对象的原型对象。


isExtensible() 

isExtensible方法拦截Object.isExtensible操作。

var p = new Proxy({}, {

    isExtensible: function(target) {

        console.log("called");

        return true;

    }

});

Object.isExtensible(p)

// "called"

// true

上面代码设置了isExtensible方法,在调用Object.isExtensible时会输出called

注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值。

这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。


ownKeys()

ownKeys方法用来拦截以下操作。

    --Object.getOwnPropertyNames()

    --Object.getOwnPropertySymbols()

    --Object.keys()

注意,使用Object.keys方法时,有三类属性会被ownKeys方法自动过滤,不会返回。

    --目标对象上不存在的属性

    --属性名为 Symbol 值

    --不可遍历(enumerable)的属性

let target = {

    a: 1,

    b: 2,

    c: 3,

    [Symbol.for('secret')]: '4',

};

Object.defineProperty(target, 'key', {

    enumerable: false,

    configurable: true,

    writable: true,

    value: 'static'

});

let handler = {

    ownKeys(target) {

        return ['a', 'd', Symbol.for('secret'), 'key'];

    }

};

let proxy = new Proxy(target, handler);

Object.keys(proxy)

// ['a']

上面代码中,ownKeys方法之中,显式返回不存在的属性(d)、Symbol 值(Symbol.for('secret'))、不可遍历的属性(key),结果都被自动过滤掉。


preventExtensions()

preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。

这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)false),proxy.preventExtensions才能返回true,否则会报错。

var p = new Proxy({}, {

    preventExtensions: function(target) {

        return true;

    }

});

Object.preventExtensions(p) // 报错

上面代码中,proxy.preventExtensions方法返回true,但这时Object.isExtensible(proxy)会返回true,因此报错。

为了防止出现这个问题,通常要在proxy.preventExtensions方法里面,调用一次Object.preventExtensions

var p = new Proxy({}, {

    preventExtensions: function(target) {

         console.log('called');

        Object.preventExtensions(target);

        return true;

    }

});

Object.preventExtensions(p)

// "called"

// true


setPrototypeOf()

setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。

var handler = {

    setPrototypeOf (target, proto) {

        throw new Error('Changing the prototype is forbidden');

   }

};

var proto = {};

var target = function () {};

var proxy = new Proxy(target, handler);

Object.setPrototypeOf(proxy, proto);

// Error: Changing the prototype is forbidden

上面代码中,只要修改target的原型对象,就会报错。

注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(extensible),setPrototypeOf方法不得改变目标对象的原型。


Proxy.revocable()

Proxy.revocable方法返回一个可取消的 Proxy 实例。

let target = {};

let handler = {};

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

proxy.foo = 123;

proxy.foo // 123

revoke();

proxy.foo // TypeError: Revoked

Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。


this 问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {

    m: function () {

        console.log(this === proxy);

    }

};

const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false

proxy.m()  // true

上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target


实例:Web 服务的客户端

Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。

const service = createWebService ('');

service.employees().then(json => {

    const employees = JSON.parse(json);

    // ···

});

上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。

function createWebService(baseUrl) {

    return new Proxy({}, {

        get(target, propKey, receiver) {

         return () => httpGet(baseUrl+'/' + propKey);

        }

    });

}

同理,Proxy 也可以用来实现数据库的 ORM 层。

Proxy 是在目标对象之前架设一层『拦截』,外部对对象的访问,都需要经过该层拦截。因此在拦截中对外界的访问进行过滤和改写。

在Es6 中 提供了原生的 Proxy 构造函数,可以用来生成 Proxy实例。

let proxy = new Proxy(target, handler)

Proxy 对象的所有用法,都是上面的形式,不同的只是handler参数的写法。其中new Proxy() 表示生成一个 Proxy实例,target 参数表示所有拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为。如下:

let proxy = new Proxy({}, { get: function(target, property) { return 35 }})proxy.time // 35proxy.name // 35proxy.title // 35

解读:

在上面中,作为构造函数,Proxy接受两个参数。第一个参数即所要代理的目标对象,如果没有 Proxy的介入,操作原来要访问的就是这个对象。第二个参数是一个配置对象,用来对每个代理对象的操作,提供具体的函数和拦截操作。上述代码中有一个 get 函数,用来拦截对目标对象属性的访问请求。

另外,要使 Proxy起作用,必须针对 Proxy 实例进行操作,而不是针对目标对象进行操作。

如果 handler 没有设置任何拦截,那就等同于直接通向原对象。如下:

let target = {}let handler = {}let proxy = new Proxy(target, handler)proxy.a = 'b'target.a = 'b'

对于上面的例子,我们可以讲Proxy对象,设置到 object.proxy属性,从而可以在object对象上调用。

版权声明:本文由美高梅开户送58元官网发布于美高梅开户,转载请注明出处:第一个参数即所要代理的目标对象,target是被代