From 04f582da7867e2a23a8e69e00ea7ad9c1f9545cb Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 24 Dec 2016 20:56:36 +0800 Subject: [PATCH] docs(proxy): edit proxy --- docs/proxy.md | 76 +++++++++++++++++++++++++++++++++++++++++++++---- docs/reflect.md | 2 ++ js/ditto.js | 2 +- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index 7ea0525..26d7dd6 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -317,6 +317,29 @@ const el = dom.div({}, document.body.appendChild(el); ``` +如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。 + +```javascript +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 +``` + ### set() `set`方法用来拦截某个属性的赋值操作。 @@ -380,6 +403,8 @@ proxy._prop = 'c' 上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。 +注意,如果目标对象自身的某个属性,不可写也不可配置,那么`set`不得改变这个属性的值,只能返回同样的值,否则报错。 + ### apply() `apply`方法拦截函数的调用、`call`和`apply`操作。 @@ -474,7 +499,7 @@ var p = new Proxy(obj, { 'a' in p // TypeError is thrown ``` -上面代码中,`obj`对象禁止扩展,结果使用`has`拦截就会报错。 +上面代码中,`obj`对象禁止扩展,结果使用`has`拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则`has`方法就不得“隐藏”(即返回`false`)目标对象的该属性。 值得注意的是,`has`方法拦截的是`HasProperty`操作,而不是`HasOwnProperty`操作,即`has`方法不判断一个属性是对象自身的属性,还是继承的属性。 @@ -588,6 +613,8 @@ delete proxy._prop 上面代码中,`deleteProperty`方法拦截了`delete`操作符,删除第一个字符为下划线的属性会报错。 +注意,目标对象自身的不可配置(configurable)的属性,不能被`deleteProperty`方法删除,否则报错。 + ### defineProperty() `defineProperty`方法拦截了`Object.defineProperty`操作。 @@ -606,6 +633,8 @@ proxy.foo = 'bar' 上面代码中,`defineProperty`方法返回`false`,导致添加新属性会抛出错误。 +注意,如果目标对象不可扩展(extensible),则`defineProperty`不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则`defineProperty`方法不得改变这两个设置。 + ### getOwnPropertyDescriptor() `getOwnPropertyDescriptor`方法拦截`Object.getOwnPropertyDescriptor`,返回一个属性描述对象或者`undefined`。 @@ -655,6 +684,8 @@ Object.getPrototypeOf(p) === proto // true 上面代码中,`getPrototypeOf`方法拦截`Object.getPrototypeOf()`,返回`proto`对象。 +注意,`getPrototypeOf`方法的返回值必须是对象或者`null`,否则报错。另外,如果目标对象不可扩展(extensible), `getPrototypeOf`方法必须返回目标对象的原型对象。 + ### isExtensible() `isExtensible`方法拦截`Object.isExtensible`操作。 @@ -674,7 +705,9 @@ Object.isExtensible(p) 上面代码设置了`isExtensible`方法,在调用`Object.isExtensible`时会输出`called`。 -这个方法有一个强限制,如果不能满足下面的条件,就会抛出错误。 +注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值。 + +这个方法有一个强限制,它的返回值必须与目标对象的`isExtensible`属性保持一致,否则就会抛出错误。 ```javascript Object.isExtensible(proxy) === Object.isExtensible(target) @@ -855,9 +888,9 @@ Object.getOwnPropertyNames(p) ### preventExtensions() -`preventExtensions`方法拦截`Object.preventExtensions()`。该方法必须返回一个布尔值。 +`preventExtensions`方法拦截`Object.preventExtensions()`。该方法必须返回一个布尔值,否则会被自动转为布尔值。 -这个方法有一个限制,只有当`Object.isExtensible(proxy)`为`false`(即不可扩展)时,`proxy.preventExtensions`才能返回`true`,否则会报错。 +这个方法有一个限制,只有目标对象不可扩展时(即`Object.isExtensible(proxy)`为`false`),`proxy.preventExtensions`才能返回`true`,否则会报错。 ```javascript var p = new Proxy({}, { @@ -876,7 +909,7 @@ Object.preventExtensions(p) // 报错 ```javascript var p = new Proxy({}, { preventExtensions: function(target) { - console.log("called"); + console.log('called'); Object.preventExtensions(target); return true; } @@ -908,6 +941,8 @@ proxy.setPrototypeOf(proxy, proto); 上面代码中,只要修改`target`的原型对象,就会报错。 +注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(extensible),`setPrototypeOf`方法不得改变目标对象的原型。 + ## Proxy.revocable() `Proxy.revocable`方法返回一个可取消的 Proxy 实例。 @@ -927,6 +962,8 @@ proxy.foo // TypeError: Revoked `Proxy.revocable`方法返回一个对象,该对象的`proxy`属性是`Proxy`实例,`revoke`属性是一个函数,可以取消`Proxy`实例。上面代码中,当执行`revoke`函数之后,再访问`Proxy`实例,就会抛出一个错误。 +`Proxy.revocable`的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。 + ## this 问题 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的`this`关键字会指向 Proxy 代理。 @@ -998,3 +1035,32 @@ const proxy = new Proxy(target, handler); proxy.getDate() // 1 ``` +## 应用实例 + +### Web 服务器的客户端 + +Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。 + +```javascript +const service = createWebService('http://example.com/data'); + +service.employees().then(json => { + const employees = JSON.parse(json); + // ··· +}); +``` + +上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。 + +```javascript +function createWebService(baseUrl) { + return new Proxy({}, { + get(target, propKey, receiver) { + return () => httpGet(baseUrl+'/' + propKey); + } + }); +} +``` + +同理,Proxy 也可以用来实现数据库的 ORM 层。 + diff --git a/docs/reflect.md b/docs/reflect.md index 0bf67e7..c6129ef 100644 --- a/docs/reflect.md +++ b/docs/reflect.md @@ -225,6 +225,8 @@ delete myObj.foo; Reflect.deleteProperty(myObj, 'foo'); ``` +该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回`true`;删除失败,被删除的属性依然存在,返回`false`。 + ### Reflect.construct(target, args) `Reflect.construct`方法等同于`new target(...args)`,这提供了一种不使用`new`,来调用构造函数的方法。 diff --git a/js/ditto.js b/js/ditto.js index 3e133f7..7247ee3 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -139,7 +139,7 @@ function init_back_to_top_button() { function goTop(e) { if(e) e.preventDefault(); - $('html body').animate({ + $('html, body').animate({ scrollTop: 0 }, 200); history.pushState(null, null, '#' + location.hash.split('#')[1]);