【重溫基礎】14.超程式設計

pingan8787發表於2019-01-07

本文是 重溫基礎 系列文章的第十四篇。
這是第一個基礎系列的最後一篇,後面會開始複習一些中級的知識了,歡迎持續關注呀!接下來會統一整理到我的【Cute-JavaScript】JavaScript基礎系列中。

今日感受:獨樂樂不如眾樂樂。

系列目錄:

本章節複習的是JS中的超程式設計,涉及的更多的是ES6的新特性。

1. 概述

超程式設計,其實我是這麼理解的:讓程式碼自動寫程式碼,可以更改原始碼底層的功能
元,是指程式本身。
有理解不到位,還請指點,具體詳細的介紹,可以檢視維基百科 超程式設計
從ES6開始,JavaScrip新增了對ProxyReflect物件的支援,允許我們連線並定義基本語言操作的自定義行為(如屬性查詢,賦值,列舉和函式呼叫等),從而實現JavaScrip的元級別程式設計。

  • Reflect: 用於替換直接呼叫Object的方法,並不是一個函式物件,也沒有constructor方法,所以不能用new操作符。
  • Proxy: 用於自定義物件的行為,如修改setget方法,可以說是ES5中Object.defineProperty()方法的ES6升級版。
  • 兩者聯絡: API完全一致,但Reflect一般在Proxy需要處理預設行為的時候使用。

參考資料

名稱 地址
Reflect MDN Reflect
Proxy MDN Proxy
超程式設計 MDN 超程式設計

本文主要從Proxy介紹,還會有幾個案例,實際看下怎麼使用。

2. Proxy介紹

proxy 用於修改某些操作的預設行為,可以理解為一種攔截外界對目標物件訪問的一種機制,從而對外界的訪問進行過濾和修改,即代理某些操作,也稱“代理器”。

2.1 基礎使用

基本語法:

let p = new Proxy(target, handler);
複製程式碼

proxy例項化需要傳入兩個引數,target參數列示所要攔截的目標物件,handler引數也是一個物件,用來定製攔截行為。

let p = new Proxy({
}, {
get: function (target, handler){
return 'leo';

}
})p.name;
// leop.age;
// leop.abcd;
// leo複製程式碼

上述a例項中,在第二個引數中定義了get方法,來攔截外界訪問,並且get方法接收兩個引數,分別是目標物件所要訪問的屬性,所以不管外部訪問物件中任何屬性都會執行get方法返回leo

注意

  • 只能使用Proxy例項的物件才能使用這些操作。
  • 如果handler沒有設定攔截,則直接返回原物件。
let target = {
};
let handler = {
};
let p = new Proxy(target, handler);
p.a = 'leo';
target.a;
// 'leo'複製程式碼

同個攔截器函式,設定多個攔截操作

let p = new Proxy(function(a, b){ 
return a + b;

},{
get:function(){
return 'get方法';

}, apply:function(){
return 'apply方法';

}
})複製程式碼

這裡還有一個簡單的案例:

let handler = { 
get : function (target, name){
return name in target ? target[name] : 16;

}
}let p = new Proxy ({
}, handler);
p.a = 1;
console.log(p.a , p.b);
// 1 16複製程式碼

這裡因為 p.a = 1 定義了p中的a屬性,值為1,而沒有定義b屬性,所以p.a會得到1,而p.b會得到undefined從而使用name in target ? target[name] : 16;
返回的預設值16

Proxy支援的13種攔截操作
13種攔截操作的詳細介紹:開啟阮一峰老師的連結

  • get(target, propKey, receiver):攔截物件屬性的讀取,比如proxy.foo和proxy[‘foo’]。

  • set(target, propKey, value, receiver):攔截物件屬性的設定,比如proxy.foo = v或proxy[‘foo’] = v,返回一個布林值。

  • has(target, propKey):攔截propKey in proxy的操作,返回一個布林值。

  • deleteProperty(target, propKey):攔截delete proxy[propKey]的操作,返回一個布林值。

  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in迴圈,返回一個陣列。該方法返回目標物件所有自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標物件自身的可遍歷屬性。

  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述物件。

  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布林值。

  • 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(…)。

  • construct(target, args):攔截 Proxy 例項作為建構函式呼叫的操作,比如new proxy(…args)。

2.2 取消Proxy例項

使用Proxy.revocable方法取消Proxy例項。

let a = {
};
let b = {
get: function(target, name) {
return "[[" + name + "]]";

}
};
let revoke = Proxy.revocable(a, b);
let proxy = revoke.proxy;
proxy.age;
// "[[age]]"revoke.revoke();
proxy.age;
// Uncaught TypeError: Cannot perform 'get' on a proxy that has been revokedproxy.age = 10;
// Uncaught TypeError: Cannot perform 'set' on a proxy that has been revokeddelete proxy.age;
// Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revokedtypeof proxy;
// "object"複製程式碼

2.3 實現 Web服務的客戶端

const service = createWebService('http://le.com/data');
service.employees().than(json =>
{
const employees = JSON.parse(json);

})function createWebService(url){
return new Proxy({
}, {
get(target, propKey, receiver{
return () =>
httpGet(url+'/'+propKey);

})
})
}複製程式碼

3. Proxy實踐

3.1 資料攔截驗證

通過Proxy代理物件的setget方法來進行攔截資料,像Vue就是用資料攔截來實現資料繫結。

let handler = { 
// 攔截並處理get方法 // 可以理解為設定get方法返回的預設值 get : function (target, key){
return key in target ? target[key] : 30;

}, // 攔截並處理set方法 // 可以理解為設定set方法的預設行為 set : function (target, key, value){
if(key === "age"){
if (!Number.isInteger(value)){
throw new TypeError("age不是一個整數!");

} if (value >
200){
throw new TypeError("age不能大於200!");

}
} // 儲存預設行為 target[key] = value;

}
}let p = new Proxy({
}, handler);
p.a = 10;
// p.a =>
10
p.b = undefined;
// p.b =>
undefined
p.c;
// 預設值 30p.age = 100;
// p.age =>
100
p.age = 300;
// Uncaught TypeError: age不能大於200!p.age = "leo";
// Uncaught TypeError: age不是一個整數!複製程式碼

3.2 函式節流

通過攔截handler.apply()方法的呼叫,實現函式只能在1秒之後才能再次被呼叫,經常可以用在防止重複事件的觸發。

let p = (fun, time) =>
{
// 獲取最後點選時間 let last = Date.now() - time;
return new Proxy (fun, {
apply(target, context, args){
if(Date.now() - last >
= time){
fun.bind(target)(args);
// 重複設定當前時間 last = Date.now();

}
}
})
}let p1 = () =>
{
console.log("點選觸發");

}let time = 1000;
// 設定時間let proxyObj = p(p1, time);
// 監聽滾動事件document.addEventListener('scroll', proxyObj);
複製程式碼

3.3 實現單例模式

通過攔截construct方法,讓不同例項指向相同的constructer,實現單例模式。

let p = function(fun){ 
let instance;
let handler = {
// 攔截construct方法 construct: function(targer, args){
if(!instance){
instance = new fun();

} return instance;

}
} return new Proxy(fun, handler);

}// 建立一個construct案例function Cons (){
this.value = 0;

}// 建立例項let p1 = new Cons();
let p2 = new Cons();
// 操作p1.value = 100;
// p1.value =>
100 , p2.value =>
0
// 因為不是相同例項// 通過Proxy實現單例let singleton = p(Cons);
let p3 = new singleton();
let p4 = new singleton();
p3.value = 130;
// p1.value =>
130 , p2.value =>
130
// 現在是相同例項複製程式碼

參考資料

1. MDN 超程式設計
2. ES6中的超程式設計-Proxy &
Reflect

本部分內容到這結束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 github.com/pingan8787/…
JS小冊 js.pingan8787.com

來源:https://juejin.im/post/5c337217e51d45520a766376

相關文章