[每日一題]面試官問:談談你對ES6的proxy的理解?

[每日一題]面試官問:談談你對ES6的proxy的理解?

關注「松寶寫代碼」,精選好文,每日一題

作者:saucxs | songEagle

一、前言

2020.12.23 日剛立的 flag,每日一題,題目類型不限制,可以是:算法題,面試題,闡述題等等。

本文是「每日一題」第 8 題:[每日一題]面試官問:談談你對ES6的proxy的理解

每日一題

往期「每日一題」:

二、什麼是Proxy?

Proxy,代理,是ES6新增的功能,可以理解為代理器(即由它代理某些操作)。

Proxy 對象用於定義或修改某些操作的自定義行為,可以在外界對目標對象進行訪問前,對外界的訪問進行改寫。

1、Proxy定義

var proxy = new Proxy(target, handler)

new Proxy()表示生成一個 Proxy 實例

  • target:目標對象
  • handler:一個對象,其屬性是當執行一個操作時定義代理的行為的函數。

注意:要實現攔截操作,必須是對 Proxy 實例進行操作,而不是針對目標對象 target 進行操作。

2、proxy攔截get和set操作

我們先來看一下proxy攔截get和set操作,示例代碼如下:

let handler = {
    get: function(target, key, receiver) {
        console.log(`getter ${key}!`)
        return Reflect.get(target, key, receiver)
    },
    set: function(target, key, value, receiver) {
    	console.log(`setter ${key}=${value}`)
		return Reflect.set(target, key, value, receiver)
	}
}
var obj = new Proxy({}, handler)
obj.a = 1          // setter a=1
obj.b = undefined  // setter b=undefined

console.log(obj.a, obj.b) 
// getter a!
// getter b!
// 1 undefined

console.log('c' in obj, obj.c)	
// getter c!
// false undefined

3、proxy覆蓋組件的原始行為

我們來看一下,示例代碼如下:

let handler = {
    get: function(target, key, receiver) {
        return 1
    },
  	set: function (target, key, value, receiver) {
    	console.log(`setting ${key}!`);
    	return Reflect.set(target, key, value, receiver);
  	}
}
var obj = new Proxy({}, handler)
obj.a = 5       // setting 5!
console.log(obj.a);    // 1

由上面代碼看出:Proxy 不僅是攔截了行為,更是用自己定義的行為覆蓋了組件的原始行為。

若handler = {},則代表 Proxy 沒有做任何攔截,訪問 Proxy 實例就相當於訪問 target 目標對象。

三、Proxy handle方法

  • 1、get(target, key, receiver):攔截 target 屬性的讀取
  • 2、set(target, key, value, receiver):攔截 target 屬性的設置
  • 3、has(target, key):攔截 key in proxy 的操作,並返回是否存在(boolean值)
  • 4、deleteProperty(target, key):攔截 delete proxy[key]的操作,並返回結果(boolean值)
  • 5、ownKeys(target):攔截Object.getOwnPropertyName(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for … in循環。並返回目標對象所有自身屬性的屬性名數組。注意:Object.keys()的返回結果數組中只包含目標對象自身的可遍歷屬性
  • 6、getOwnPropertyDescriptor(target, key):攔截 Object.getOwnPropertyDescriptor(proxy, key),返回屬性的描述對象
  • 7、defineProperty(target, key, desc):攔截Object.defineProperty(proxy, key, desc)、Object.defineProperties(proxy, descs),返回一個 boolean 值
  • 8、preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個 boolean 值
  • 9、getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象
  • 10、isExtensible(target):攔截Object.isExtensible(proxy),返回一個 boolean 值
  • 11、setPrototypeOf(target, key):攔截Object.setPrototypeOf(proxy, key),返回一個 boolean 值。如果目標對象是函數,則還有兩種額外操作可以被攔截
  • 12、apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)
  • 13、construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如new proxy(…args)

總共 13 個攔截方法,下面進行簡要舉例說明,更多可見阮一峰老師的 《ECMAScript 6 入門》(//es6.ruanyifeng.com/#docs/proxy)

1、get方法和set方法

get方法用於攔截某個屬性的讀取操作,可以接受三個參數,依次為目標對象、屬性名和 proxy 實例本身(嚴格地說,是操作行為所針對的對象),其中最後一個參數可選。

set攔截 target 屬性的設置,可以接受四個參數,依次為目標對象、屬性名、value和 proxy 實例本身(嚴格地說,是操作行為所針對的對象),其中最後一個參數可選。

let target = {foo: 1}
let proxy = new Proxy(target, {
    get(target, key, receiver) {
        console.log(`getter ${key}!`)
        return target[key]
    },
    set: function(target, key, value, receiver) {
        console.log(`setter ${key}!`)
        target[key] = value;
    }
})

let obj = Object.create(proxy)
console.log(obj.foo) 
// getter foo!
// 1

2、has方法

攔截 propKey in proxy 的操作,返回一個布爾值。

// 使用 has 方法隱藏某些屬性,不被 in 運算符發現
var handler = {
    has (target, key) {
        if (key.startsWith('_')) {
            return false;
        }
        return key in target;
    }
};
var foo = { _name: 'saucxs', name: 'saucxs' };
var proxy = new Proxy(foo, handler);
console.log('_name' in proxy); // false
console.log('name' in proxy); // true

3、ownKeys方法

攔截自身屬性的讀取操作。並返回目標對象所有自身屬性的屬性名數組。具體返回結果可結合 MDN 屬性的可枚舉性和所有權

  • Object.getOwnPropertyName(proxy)
  • Object.getOwnPropertySymbols(proxy)
  • Object.keys(proxy)
  • for … in循環
let target = {
  _foo: 'foo',
  _bar: 'bar',
  name: 'saucxs'
};

let handler = {
  ownKeys (target) {
    return Reflect.ownKeys(target).filter(key => key.startsWith('_'));
  }
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "saucxs"

4、apply方法

apply 攔截 Proxy 實例作為函數調用的操作,比如函數的調用(proxy(…args))、call(proxy.call(object, …args))、apply(proxy.apply(…))等。

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the saucxs proxy';
  }
};

var proxy = new Proxy(target, handler);

proxy();
// "I am the saucxs proxy"

更多可見阮一峰老師的 《ECMAScript 6 入門》(//es6.ruanyifeng.com/#docs/proxy)

謝謝支持

1、文章喜歡的話可以「分享,點贊,在看」三連哦。

2、作者昵稱:saucxs,songEagle,松寶寫代碼。「松寶寫代碼」公眾號作者,每日一題,實驗室等。一個愛好折騰,致力於全棧,正在努力成長的位元組跳動工程師,星辰大海,未來可期。內推位元組跳動各個部門各個崗位。

3、長按下面圖片,關注「松寶寫代碼」,是獲取開發知識體系構建,精選文章,項目實戰,實驗室,每日一道面試題,進階學習,思考職業發展,涉及到JavaScript,Node,Vue,React,瀏覽器,http等領域,希望可以幫助到你,我們一起成長~

松寶寫代碼

位元組內推福利

  • 回復「校招」獲取內推碼
  • 回復「社招」獲取內推
  • 回復「實習生」獲取內推

後續會有更多福利

學習資料福利

回復「算法」獲取算法學習資料

往期「每日一題」

1、JavaScript && ES6

2、瀏覽器

3、Vue

4、算法

5、Http