一彼棍、Reflect 反射
1. Reflect.get(target, name, receiver)
Reflect.get
方法查找并返回target
對(duì)象的name
屬性,如果沒有該屬性,則返回undefined
戴而。
注意:target
只能為一個(gè)對(duì)象
如果name
屬性部署了讀取函數(shù)(getter
)苹熏,則讀取函數(shù)的this
綁定receiver
。
注意:bas
是一個(gè)屬性而不是函數(shù)移盆,因此不能用call
來綁定receiver
悼院,只能用Reflect
來實(shí)現(xiàn)
普通函數(shù)的則可以直接用call
調(diào)用
總結(jié)Reflect.get()
的兩種用法
// 用法一
var obj = {
name: '阿偉',
age: '18'
}
Reflect.get(obj, 'name') // "阿偉"
// 用法二
var obj1 = {
a: 1,
b: 2,
get c() {
return this.a + this.b
}
}
obj1.c // 3
var obj2 = {
a: 3,
b: 4
}
Reflect.get(obj1, 'c', obj2) // 7
3. Reflect.set(target, name, value, receiver)
Reflect.set
方法設(shè)置target
對(duì)象的name
屬性等于value
如果name
屬性設(shè)置了賦值函數(shù),則賦值函數(shù)的this
綁定receiver
3. Reflect.apply(func, thisArg, args)
Reflect.apply
方法等同于Function.prototype.apply.call(func, thisArg, args)
咒循,用于綁定this
對(duì)象后執(zhí)行給定函數(shù)据途。
可將Reflect 的 set、get叙甸、apply 近似的理解為下面這樣
target.get('name') <===> Reflect.get(target, 'name', receiver) // target 為一個(gè)對(duì)象
target.set('name', 'xxx') <===> Reflect.set(target, 'name', 'xxx', receiver)
函數(shù).apply(this, args) <===> Reflect.apply(函數(shù), this, args)
4. Reflect.construct(target, args)
Reflect.construct
方法等同于new target(...args)
颖医,這提供了一種不使用new
,來調(diào)用構(gòu)造函數(shù)的方法裆蒸。
function Greeting(name) {
this.name = name;
}
// new 的寫法
const instance = new Greeting('張三');
// Reflect.construct 的寫法
const instance = Reflect.construct(Greeting, ['張三']);
注意:Reflect.construct()
方法的第一個(gè)參數(shù)target
必須是函數(shù)
二熔萧、Proxy 代理
Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”(中介),外界對(duì)該對(duì)象的訪問佛致,都必須先通過這層攔截贮缕,因此提供了一種機(jī)制,可以對(duì)外界的訪問進(jìn)行過濾和改寫俺榆。Proxy 這個(gè)詞的原意是代理感昼,用在這里表示由它來“代理”某些操作,可以譯為“代理器”罐脊。
ES6
原生提供Proxy
構(gòu)造函數(shù)定嗓,用來生成Proxy
實(shí)例。
var proxy = new Proxy(target, handler);
Proxy
對(duì)象的所有用法萍桌,都是上面這種形式宵溅,不同的只是handler
參數(shù)的寫法。其中梗夸,new Proxy()
表示生成一個(gè)Proxy實(shí)例层玲,target
參數(shù)表示所要攔截的目標(biāo)對(duì)象,handler
參數(shù)也是一個(gè)對(duì)象反症,用來定制攔截行為辛块。
用圖來理解Proxy
就是下面這樣
例子:使被代理對(duì)象中的lives
始終大于0
但這樣存在一個(gè)問題,用戶還是可以直接訪問這個(gè)變量并對(duì)其進(jìn)行修改
因此我們需要讓用戶無法訪問這個(gè)變量铅碍,有兩種做法
- 可以用閉包來隱藏
lives
- 不給這個(gè)被代理的對(duì)象名字润绵,即讓它為匿名對(duì)象(常見套路)
下面是另一個(gè)攔截讀取屬性行為的例子。
上面代碼中胞谈,作為構(gòu)造函數(shù)尘盼,Proxy
接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是所要代理的目標(biāo)對(duì)象(上例是一個(gè)空對(duì)象)烦绳,即如果沒有Proxy
的介入卿捎,操作原來要訪問的就是這個(gè)對(duì)象;第二個(gè)參數(shù)是一個(gè)配置對(duì)象径密,對(duì)于每一個(gè)被代理的操作午阵,需要提供一個(gè)對(duì)應(yīng)的處理函數(shù),該函數(shù)將攔截對(duì)應(yīng)的操作享扔。比如底桂,上面代碼中,配置對(duì)象有一個(gè)get
方法惧眠,用來攔截對(duì)目標(biāo)對(duì)象屬性的訪問請(qǐng)求籽懦。get
方法的兩個(gè)參數(shù)分別是目標(biāo)對(duì)象和所要訪問的屬性》湛可以看到暮顺,由于攔截函數(shù)總是返回35
厅篓,所以訪問任何屬性都得到35
。
注意:要使得Proxy
起作用拖云,必須針對(duì)Proxy
實(shí)例(上例是proxy
對(duì)象)進(jìn)行操作贷笛,而不是針對(duì)目標(biāo)對(duì)象(上例是空對(duì)象)進(jìn)行操作应又。
用代理實(shí)現(xiàn)“不可能實(shí)現(xiàn)的”自動(dòng)填充對(duì)象
創(chuàng)建一個(gè)Tree()函數(shù)來實(shí)現(xiàn)以下特性:
> var tree = Tree();
> tree
{ }
> tree.branch1.branch2.twig = "green";
> tree
{ branch1: { branch2: { twig: "green" } } }
> tree.branch1.branch3.twig = "yellow";
{ branch1: { branch2: { twig: "green" },
branch3: { twig: "yellow" }}}
普通對(duì)象是無法做到的
但是通過代理宙项,我們只用幾行代碼就可以輕松實(shí)現(xiàn),然后只需要接入tree.[[Get]]()
就可以了
function Tree() {
return new Proxy({}, handler);
}
var handler = {
get: function(target, key, receiver) {
if (!(key in target)) {
target[key] = Tree(); // 自動(dòng)創(chuàng)建一個(gè)子樹
}
return Reflect.get(target, key, receiver);
}
};
總結(jié)Proxy
的這種做法就是“如果不存在株扛,則先創(chuàng)建尤筐,再讀取”。
實(shí)例:Web 服務(wù)的客戶端
Proxy
對(duì)象可以攔截目標(biāo)對(duì)象的任意屬性洞就,這使得它很合適用來寫Web
服務(wù)的客戶端盆繁。
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
// ···
});
上面代碼新建了一個(gè)Web
服務(wù)的接口,這個(gè)接口返回各種數(shù)據(jù)旬蟋。Proxy
可以攔截這個(gè)對(duì)象的任意屬性油昂,所以不用為每一種數(shù)據(jù)寫一個(gè)適配方法,只要寫一個(gè)Proxy
攔截就可以了倾贰。
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl + '/' + propKey);
}
});
}
service.employees()
實(shí)際上是不存在的冕碟,當(dāng)讀取service.employees()
時(shí),就會(huì)執(zhí)行httpGet(baseUrl + '/' + propKey)
匆浙,因此service.employees()
就等價(jià)于httpGet('http://example.com/data/employees')
三安寺、Vue 3 將用 Proxy 改寫
<div id="app">
姓名:{{obj.name}}<br>
年齡:{{obj.age}}<br>
<button @click=createAge>創(chuàng)建年齡</button>
</div>
var app = new Vue({
el: '#app',
data: {
obj:{
name: '阿偉'
}
},
methods:{
createAge(){
this.obj.age = 18
console.log(this.obj)
}
}
})
我們會(huì)發(fā)現(xiàn)點(diǎn)擊按鈕后,頁面上并沒有顯示'18'首尼,但控制臺(tái)中卻打印出了age
為什么age
創(chuàng)建之后卻無法更新到頁面的DOM上呢挑庶?
這是因?yàn)閂ue是通過object.defineProperty
來實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式的
受現(xiàn)代JavaScript的限制 (而且
Object.observe
也已經(jīng)被廢棄),Vue 無法檢測(cè)到對(duì)象屬性的添加或刪除软能。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì)屬性執(zhí)行getter/setter
轉(zhuǎn)化迎捺,所以屬性必須在data
對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的
上面代碼中name
已經(jīng)存在,因此app.name
是響應(yīng)式的查排,而age
不存在凳枝,因此app.age
是非響應(yīng)式的。
解決:
- 在
data
加入age
即可
- 使用 Proxy 代理
然后刷新DOM
var proxy = new Proxy(data.obj, {// 注意Proxy只會(huì)代理當(dāng)前對(duì)象雹嗦,并不會(huì)代理子對(duì)象范舀,因此這里是data.obj
set(target, name, value){
// refreshDom()
return Reflect.set(target, name, value)
}
})
參考: