A-A+

ES6 Proxy怎么使用全剖析

2018年11月01日 javascript 暂无评论 阅读 38 views 次

ES6 Proxy

proxy者,代理也。我的理解,就是加壳,就是绿林、就是收费站。

假设一个函数,好比一条高速公路,可以从A地到B地。具备的功能就是联通。具备的属性就是名字、长度、几车道  等。如果没有代理,什么也不做,那么你不会对从他上面 通过的那些川流不息的车有任何影响。

如果代理(收费站)介入,将产生如下变化:

1.基础篇

通过收费站,才能上这条公路,收费站同时记录了你从A到B,还是从B到A 的信息。

定义这条高速公路

const road={name:"AB高速",width:8,length:300, direction:function(x,y){return x+"->"+y}}

建立代理

const roadhandler = { get(road, key, proxy) { const today = new Date(); console.log(`你访问了属性 ${key} 在 ${today}`); return Reflect.get(road, key, proxy); } }; 
const roadproxy = new Proxy(road, roadhandler);

试执行

roadproxy.name
VM263:1 你访问了属性 name 在 Thu Nov 01 2018 10:48:09 GMT+0800 (中国标准时间)
"AB高速"
roadproxy.direction("a","b")
VM263:1 你访问了属性 direction 在 Thu Nov 01 2018 10:53:47 GMT+0800 (中国标准时间)
"a->b"

roadhandler  就是那个收费站,要想用这条路,就得先经过他,然后才能上。

例子一就是通过他看了看,这条路是不是你想要走的

例子二就是上去走,你要从a走到b.

2.抽离校验模块

我申请了一笔经费,要把这条路的宽度改一下,此时各家就要上报,改成的具体宽是多少,但也有不按约定报的,我们为了防止误报上要校验。

let numericDataStore = { width: 8 }; numericDataStore = new Proxy(numericDataStore, { set(target, key, value, proxy) { if (typeof value !== 'number') { throw Error("只能上报数字类型,别的不接受。"); } return Reflect.set(target, key, value, proxy); } });

试用一下:

numericDataStore.width="1"
VM314:1 Uncaught Error: 只能上报数字类型,别的不接受。
    at Object.set (<anonymous>:1:159)
    at <anonymous>:1:23
set @ VM314:1
(anonymous) @ VM336:1
numericDataStore.width=12
12

更复杂的校验

function createValidator(target, validator) {
    return new Proxy(target, {
        _validator: validator,
        set(target, key, value, proxy) {
            if (target.hasOwnProperty(key)) {
                let validator = this._validator[key];
                if ( !! validator(value)) {
                    return Reflect.set(target, key, value, proxy);
                } else {
                    throw Error(`Cannot set $ {
                        key
                    }
                    to $ {
                        value
                    }.Invalid.`);
                }
            } else {
                throw Error(`$ {
                    key
                }
                is not a valid property`)
            }
        }
    });
}
const personValidators = {
    name(val) {
        return typeof val === 'string';
    },
    age(val) {
        return typeof age === 'number' && age > 18;
    }
}
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
        return createValidator(this, personValidators);
    }
}
const bill = new Person('Bill', 25);

3.私有属性  使对象的私有属性不能被外部访问到

在 JavaScript 或其他语言中,大家会约定俗成地在变量名之前添加下划线 _ 来表明这是一个私有属性(并不是真正的私有),但我们无法保证真的没人会去访问或修改它。在下面的代码中,我们声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey:


var api = {
    _apiKey: '123abc456def',
    /* mock methods that use this._apiKey */
    getUsers: function() {},
    getUser: function(userId) {},
    setUser: function(userId, config) {}
}; // logs '123abc456def'; console.log("An apiKey we want to keep private", api._apiKey); // get and mutate _apiKeys as desired 
var apiKey = api._apiKey; 
api._apiKey = '987654321';

很显然,约定俗成是没有束缚力的。使用 ES6 Proxy 我们就可以实现真实的私有变量了,下面针对不同的读取方式演示两个不同的私有化方法。第一种方法是使用 set / get 拦截读写请求并返回 undefined:

let api = {
    _apiKey: '123abc456def',
    getUsers: function() {},
    getUser: function(userId) {},
    setUser: function(userId, config) {}
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
    get(target, key, proxy) {
        if (RESTRICTED.indexOf(key) > -1) {
            throw Error(`$ {
                key
            }
            is restricted.Please see api documentation
            for further info.`);
        }
        return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if (RESTRICTED.indexOf(key) > -1) {
            throw Error(`$ {
                key
            }
            is restricted.Please see api documentation
            for further info.`);
        }
        return Reflect.get(target, key, value, proxy);
    }
}); // 以下操作都会抛出错误
 console.log(api._apiKey); 
api._apiKey = '987654321'; 
var api = {
    _apiKey: '123abc456def',
    getUsers: function() {},
    getUser: function(userId) {},
    setUser: function(userId, config) {}
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
    has(target, key) {
        return (RESTRICTED.indexOf(key) > -1) ? false: Reflect.has(target, key);
    }
}); // these log false, and `for in` iterators will ignore _apiKey 
console.log("_apiKey" in api);
for (var key in api) {
    if (api.hasOwnProperty(key) && key === "_apiKey") {
        console.log("This will never be logged because the proxy obscures _apiKey...")
    }
}

4.访问日志  这个很好理解,就是记录一下你访问了对象的哪些属性

对于那些调用频繁、运行缓慢或占用执行环境资源较多的属性或接口,开发者会希望记录它们的使用情况或性能表现,这个时候就可以使用 Proxy 充当中间件的角色,轻而易举实现日志功能:

let api = {
    _apiKey: '123abc456def',
    getUsers: function() {
        /* ... */
    },
    getUser: function(userId) {
        /* ... */
    },
    setUser: function(userId, config) {
        /* ... */
    }
};
function logMethodAsync(timestamp, method) {
    setTimeout(function() {
        console.log(`$ {
            timestamp
        } - Logging $ {
            method
        }
        request asynchronously.`);
    },
    0)
}
api = new Proxy(api, {
    get: function(target, key, proxy) {
        var value = target[key];
        return function(...arguments) {
            logMethodAsync(new Date(), key);
            return Reflect.apply(value, target, arguments);
        };
    }
});
api.getUsers();

5.预警和拦截

假设你不想让其他开发者删除 noDelete 属性,还想让调用 oldMethod 的开发者了解到这个方法已经被废弃了,或者告诉开发者不要修改 doNotChange 属性,那么就可以使用 Proxy 来实现:

let dataStore = {
    noDelete: 1235,
    oldMethod: function() {
        /*...*/
    },
    doNotChange: "tried and true"
};
const NODELETE = ['noDelete'];
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];
dataStore = new Proxy(dataStore, {
    set(target, key, value, proxy) {
        if (NOCHANGE.includes(key)) {
            throw Error(`Error ! $ {
                key
            }
            is immutable.`);
        }
        return Reflect.set(target, key, value, proxy);
    },
    deleteProperty(target, key) {
        if (NODELETE.includes(key)) {
            throw Error(`Error ! $ {
                key
            }
            cannot be deleted.`);
        }
        return Reflect.deleteProperty(target, key);
    },
    get(target, key, proxy) {
        if (DEPRECATED.includes(key)) {
            console.warn(`Warning ! $ {
                key
            }
            is deprecated.`);
        }
        var val = target[key];
        return typeof val === 'function' ?
        function(...args) {
            Reflect.apply(target[key], target, args);
        }: val;
    }
}); // these will throw errors or log warnings, respectively 
dataStore.doNotChange = "foo"; 
delete dataStore.noDelete; 
dataStore.oldMethod();

6. 过滤操作

某些操作会非常占用资源,比如传输大文件,这个时候如果文件已经在分块发送了,就不需要在对新的请求作出相应(非绝对),这个时候就可以使用 Proxy 对当请求进行特征检测,并根据特征过滤出哪些是不需要响应的,哪些是需要响应的。下面的代码简单演示了过滤特征的方式,并不是完整代码,相信大家会理解其中的妙处:

let obj = {
    getGiantFile: function(fileId) {
        /*...*/
    }
};
obj = new Proxy(obj, {
    get(target, key, proxy) {
        return function(...args) {
            const id = args[0];
            let isEnroute = checkEnroute(id);
            let isDownloading = checkStatus(id);
            let cached = getCached(id);
            if (isEnroute || isDownloading) {
                return false;
            }
            if (cached) {
                return cached;
            }
            return Reflect.apply(target[key], target, args);
        }
    }
});

7.中断代理

Proxy 支持随时取消对 target 的代理,这一操作常用于完全封闭对数据或接口的访问。在下面的示例中,我们使用了 Proxy.revocable 方法创建了可撤销代理的代理对象:

let sensitiveData = {
    username: 'devbryce'
};
const {
    sensitiveData,
    revokeAccess
} = Proxy.revocable(sensitiveData, handler);
function handleSuspectedHack() {
    revokeAccess();
} // logs 'devbryce' console.log(sensitiveData.username);
 handleSuspectedHack(); // TypeError: Revoked 
console.log(sensitiveData.username);

打赏作者
如果文章对您有所帮助请打赏支持本站发展。

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

标签:

给我留言

您必须 登录 才能发表留言!

Copyright © 前端技术分享休闲玩耍去处分享-大一网 保留所有权利.   Theme  Ality

用户登录 ⁄ 注册

分享到: