本文是 重溫基礎 系列文章的第十四篇。
這是第一個基礎系列的最後一篇,後面會開始複習一些中級的知識了,歡迎持續關注呀!接下來會統一整理到我的【Cute-JavaScript】的JavaScript基礎系列中。
今日感受:獨樂樂不如眾樂樂。
系列目錄:
- 【複習資料】ES6/ES7/ES8/ES9資料整理(個人整理)
- 【重溫基礎】1.語法和資料型別
- 【重溫基礎】2.流程控制和錯誤處理
- 【重溫基礎】3.迴圈和迭代
- 【重溫基礎】4.函式
- 【重溫基礎】5.表示式和運算子
- 【重溫基礎】6.數字
- 【重溫基礎】7.時間物件
- 【重溫基礎】8.字串
- 【重溫基礎】9.正規表示式
- 【重溫基礎】10.陣列
- 【重溫基礎】11.Map和Set物件
- 【重溫基礎】12.使用物件
- 【重溫基礎】13.迭代器和生成器
本章節複習的是JS中的超程式設計,涉及的更多的是ES6的新特性。
1. 概述
超程式設計,其實我是這麼理解的:讓程式碼自動寫程式碼,可以更改原始碼底層的功能。
元,是指程式本身。
有理解不到位,還請指點,具體詳細的介紹,可以檢視維基百科 超程式設計 。
從ES6開始,JavaScrip新增了對Proxy
和Reflect
物件的支援,允許我們連線並定義基本語言操作的自定義行為(如屬性查詢,賦值,列舉和函式呼叫等),從而實現JavaScrip的元級別程式設計。
Reflect
: 用於替換直接呼叫Object
的方法,並不是一個函式物件,也沒有constructor
方法,所以不能用new
操作符。Proxy
: 用於自定義物件的行為,如修改set
和get
方法,可以說是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
代理物件的set
和get
方法來進行攔截資料,像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 =>
10p.b = undefined;
// p.b =>
undefinedp.c;
// 預設值 30p.age = 100;
// p.age =>
100p.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 | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | github.com/pingan8787/… |
JS小冊 | js.pingan8787.com |