ES6精通之Proxy(代理)个人笔记

2019-12-18 Proxy

目录

  • Proxy 的简介和特点
  • Proxy 的拦截操作
    • get()
    • set()
    • apply()
    • has()
    • construct()
    • deleteProperty()
    • defineProperty()
    • getOwnPropertyDescriptor()
    • getPrototypeOf()
    • isExtensible()
    • PreventExtensions()
    • ownKeys()
    • setPrototypeOf()
  • Proxy.revocable()一个可取消的Proxy
  • Proxy的this

Proxy 的简介和特点

  1. 定义
    用于修改某些操作的默认行为,可以理解在目标对象之前架设一层“拦截”,代理某些操作,这种代理是语言层面上的拦截
  2. 写法
var proxy = new Proxy(target, handler);

target:要拦截的目标对象
handler:要拦截的行为

实例:

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35
//拦截一个空的对象,在访问这个空对象之前首先访问这个代理

注意:如果想要Proxy代理起作用必须针对于Proxy这个实例,而不是针对于目标对象

3.如果没有对目标对象进行拦截,那么等同于直接指向目标对象

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

4.奇淫技巧
(1)可以将Proxy实例部署到对象的实例上,从而可以在Object上调用
(2)Proxy的实例也可以作为其他对象的原型对象

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);//继承了Proxy实例的get方法
obj.time // 35

Proxy 的拦截操作

get()
  1. 作用:拦截某个属性的读取
  2. 接收参数:目标对象,属性名,Proxy实例本身(可选)
  3. 写法:
var person = {
  name: "张三"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
    }
  }
});

proxy.name // "张三"
proxy.age // 抛出一个错误

4.注意
如果一个属性是不能配置并且不可写,则Proxy无法操作这个属性

const target = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  },
});

const handler = {
  get(target, propKey) {
    return 'abc';
  }
};

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed
proxy.a
//abc
  1. 应用扩展
    (1)get()方法可以继承
    (2)可以将读取属性的操作(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

(3)类似于虚拟DOM转换为真实的DOM

const dom = new Proxy({}, {
  get(target, property) {
    return function(attrs = {}, ...children) {
      const el = document.createElement(property);
      for (let prop of Object.keys(attrs)) {
        el.setAttribute(prop, attrs[prop]);
      }
      for (let child of children) {
        if (typeof child === 'string') {
          child = document.createTextNode(child);
        }
        el.appendChild(child);
      }
      return el;
    }
  }
});

const el = dom.div({},
  'Hello, my name is ',
  dom.a({href: '//example.com'}, 'Mark'),
  '. I like:',
  dom.ul({},
    dom.li({}, 'The web'),
    dom.li({}, 'Food'),
    dom.li({}, '…actually that\'s it')
  )
);

document.body.appendChild(el);
set()
  1. 作用:拦截某个属性的赋值操作
  2. 接收参数: 目标对象,属性名,属性值,Proxy实例本身(可选)
  3. 写法
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true
  1. 注意
    (1)如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar',
  writable: false,
});

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = 'baz';
  }
};

const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"

(2)严格模式下如果set代理没有返回true,就会报错
5. 应用扩展
(1)利用set方法可以进行数据绑定,每当对象发生变化的时候,会自动的更新DOM
(2)设置一些对象的内部属性(第一个字符下划线开头),不被外界访问

const handler = {
  get (target, key) {
    invariant(key, 'get');
    return target[key];
  },
  set (target, key, value) {
    invariant(key, 'set');
    target[key] = value;
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`Invalid attempt to ${action} private "${key}" property`);
  }
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
apply()
  1. 作用:拦截函数的调用,call,apply等操作
  2. 接收参数:目标对象,目标对象的上下文对象,目标对象的参数数组
  3. 写法
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方法拦截。
has()
  1. 作用:判断某个属性是否是对象身上的属性,in运算符
  2. 接收参数: 目标对象,要查询的属性
  3. 写法
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
  1. 注意
    如果原对象是不可配置,则报错
    只对in操作符生效,对for…in无效
construct()
  1. 作用:用于拦截new命令
  2. 接收参数:目标对象和构造函数的参数对象
  3. 写法
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
  1. 注意
    使用construct()必须返回一个对象,否则报错
deleteProperty()
  1. 作用:用于拦截delete属性操作
  2. 接收参数: 目标对象和要删除的属性
  3. 写法
var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    delete target[key];
    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
  1. 注意
    目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

剩下的不是很重要,暂时不更

getOwnPropertyDescriptor()
  1. 作用:用于拦截object.getOwnPropertyDescriptor(),返回一个属性的描述或undefined
  2. 接收参数: 目标对象和要查询的属性
  3. 写法
var handler = {
  getOwnPropertyDescriptor (target, key) {
    if (key[0] === '_') {
      return;
    }
    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')
getPrototypeOf()
  1. 作用:用于拦截delete属性操作
  2. 接收参数: 目标对象
  3. 写法
var proto = {};
var p = new Proxy({}, {
 getPrototypeOf(target) {
   return proto;
 }
});
Object.getPrototypeOf(p) === proto // true
  1. 注意
    主要拦截下面5个操作
  • Object.prototype.proto
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • Object.prototype.isPrototypeOf()
  • instanceof

isPrototypeOf、instanceof、hasOwnProperty函数介绍

isExtensible()
  1. 作用:用于拦截Object.isExtensible()(判断一个对象是否是可扩展的,返回一个boolean值)
  2. 接收参数: 目标对象
  3. 写法
var p = new Proxy({}, {
 isExtensible: function(target) {
   console.log("called");
   return true;
 }
});

Object.isExtensible(p)
// "called"
// true
  1. 这个方法有一个强制值,就是返回值必须和目标对象的Object.isExtensible()属性相同,否则就会报错
preventExtensions()
  1. 作用:用于拦截Object.perventExtentsible()(让一个对象不可扩展,就是永远无法添加新的属性)
  2. 接收参数: 目标对象
  3. 写法
var proxy = new Proxy({}, {
 preventExtensions: function(target) {
   console.log('called');
   Object.preventExtensions(target);
   return true;
 }
});

Object.preventExtensions(proxy)
// "called"
// Proxy {}
  1. 注意
    只有一个对象不可扩展时,Object.isExtensible()返回false,proxy.preventExtensible才能返回true,否则会报错
ownKeys()
  1. 作用:用于对象自身属性的读取操作操作
    Object.getOwnPropertyNames() 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组
    Object.getOwnPropertySymbols() 返回自身所有的Symbol属性数组
    Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组
    for…in循环 for…in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性
  2. 接收参数: 目标对
  3. 写法
let target = {
 a: 1,
 b: 2,
 c: 3
};

let handler = {
 ownKeys(target) {
   return ['a'];
 }
};

let proxy = new Proxy(target, handler);

Object.keys(proxy)
// [ 'a' ]

4.注意

  • 使用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']
const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
  ownKeys: function () {
    return ['a', 'b'];
  }
});

for (let key in proxy) {
  console.log(key); // 没有任何输出
}

上面代码中,ownkeys指定只返回a和b属性,由于obj没有这两个属性,因此for…in循环不会有任何输出

setPrototypeOf()
  1. 作用:用于拦截setPrototypeOf(obj, prototype)( 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null)
  2. 接收参数: 目标对象和要更改的原型
  3. 写法
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

4.注意

  • 该方法只能返回布尔值,否则会自动的返回布尔值
  • 如果目标对象不可扩展,则setProperty方法不得改变目标对象

Proxy的Proxy.revocable()

1.作用:返回一个可取消的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实例,就会抛出一个错误
2. 应用场景
Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

Proxy的this

1.目标对象内部的this关键字会指向 Proxy 代理

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

参考文献

  • 阮一峰官网(ES6 Promise对象)
  • isPrototypeOf、instanceof、hasOwnProperty函数介绍

你可能感兴趣的:(前端,ES6)