深入理解 ES6中的 Reflect

龍恩0707發表於2019-01-19

閱讀目錄

Reflect物件是一個全域性的普通的物件。Reflect的原型就是Object.

我們首先來驗證下 看看Reflect的原型是否是Object, 基本程式碼如下:

let obj = {};
console.log(Reflect.__proto__ === Object.prototype); // true
console.log(obj.__proto__ === Reflect.__proto__); // true

let str = '111';

console.log(str.__proto__); // String {"", length: 0, constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}

Reflect是ES6為了操作物件而新增的API, 為什麼要新增Reflect物件呢?它這樣設計的目的是為了什麼?

1)將Object物件的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到Reflect物件上,那麼以後我們就可以從Reflect物件上可以拿到語言內部的方法。

2)在使用物件的 Object.defineProperty(obj, name, {})時,如果出現異常的話,會丟擲一個錯誤,需要使用try catch去捕獲,但是使用 Reflect.defineProperty(obj, name, desc) 則會返回false。

比如 舊的寫法如下:

try {
  Object.defineProperty(target, property, attributes);
} catch(e) {
  // 失敗
}

// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

等等這些考慮,所以就新增了這個靜態物件。

Reflect物件一共有13個靜態方法。

一:Reflect.get(target, name, receiver)

該方法是用來讀取一個物件的屬性。
引數如下解析:
target: 目標物件
name: 是我們要讀取的屬性。
receiver(可選): 可以理解為上下文this物件。

先看如下demo來理解下 Reflect中的get方法的使用如下:

const obj = {
  name: 'kongzhi',
  age: 30,
  get xxx() {
    console.log(this.name); 
    console.log('-------');
  }
};

console.log(Reflect.get(obj, 'name')); // kongzhi
 
console.log(Reflect.get(obj, 'yyy'));  // undefined

/* 
 先執行 xxx 方法 列印 kongzhi 和 ----, 
 然後在列印undefined, 因為該xxx()函式沒有返回值
*/
console.log(Reflect.get(obj, 'xxx')); 

/* 
 會執行 xxx() 方法,列印 happy, 因此第三個引數指向上下文
 就指向了這個物件,然後列印 ----- ,最後列印undefined
 因為該函式沒有返回值
*/
console.log(Reflect.get(obj, 'xxx', {name: 'happy'})); 

/*
 會執行 xxx() 方法,列印 undefined, 因此第三個引數指向上下文
 就指向了這個物件,而這個物件裡面又沒有name屬性,因此會列印undefined
 然後列印 ----- ,最後列印undefined. 因為該函式沒有返回值
*/
console.log(Reflect.get(obj, 'xxx', {age: 'happy'}));

const obj2 = {
  name: 'kongzhi2',
  age: 30,
  get xxx() {
    console.log(this.name); 
    console.log('----xxxx---');
    return 0;
  }
};
/*
  先執行 obj2 該物件中的 xxx 方法,指定了第三個引數作為該上下文物件,
  因此會列印 happy2, 然後繼續列印 ----xxxx---, 最後我們可以看到
  有返回值為0,因此列印0了 
*/
console.log(Reflect.get(obj2, 'xxx', {name: 'happy2'})); 

二:Reflect.set(target,name,value,receiver)

上面的get方法是獲取物件中的值,那麼set就是設定該物件的屬性值了,引數解析簡單如下:
target: 我們需要操作的物件。
name: 我們需要設定該物件的屬性名。
value: 我們要設定的屬性值。
receiver: 可以理解為上下文this物件。如果我們在設定值的時候遇到setter函式,該引數就指向與setter中上下文this物件。
該函式會返回一個Boolean的值,代表在目標物件上設定屬性是否成功。

如下程式碼演示:

const obj = {
  age: 30,
  set name(name) {
    console.log(this); 
    console.log('-------');
  }
};

const res = Reflect.set(obj, 'age', 31);
console.log(res); // true
console.log(obj); // {age: 31, set name:function} 這樣的

console.log(obj.age); // 列印 31

/*
 如下程式碼,設定 obj物件中的name屬性,因此列印 console.log(this)
 返回 {age: 31, set name:function} 這樣的, console.log(res2)返回true,設定成功
*/
const res2 = Reflect.set(obj, 'name', 'xxxx');
console.log(res2); // true

/*
 先執行 set 中的name方法,列印 console.log(this);this就指向了第四個引數 {test: 'test'}
 然後會列印 '-----'; 
*/
const r2 = Reflect.set(obj, 'name', 'dreamapple', {test: 'test'}); // this: -->  { test: 'test' }
console.log(r2); // true
console.log(obj); // { name: [Setter], age: 31 }

三:Reflect.apply(target,thisArg,args)

該方法的含義是:通過指定的引數列表對該目標函式的呼叫。該方法類似於我們之前的 Function.prototype.apply 方法的。

引數解析如下:
target: 我們的目標函式.
thisArg: target函式呼叫的時候繫結的this物件。
args: 就是函式引數列表。

如下程式碼demo演示:

// 查詢陣列裡面最小的元素值

const arrs = [1, 2, 3, 4];
// ES6 的語法如下
const min = Reflect.apply(Math.min, arrs, arrs);

console.log(min); // 1

// ES5的語法如下:

const min2 = Math.min.apply(arrs, arrs);
console.log(min2); // 1

// 或者我們使用 Finction.prototype 程式碼如下演示

const min3 = Function.prototype.apply.call(Math.min, arrs, arrs);
console.log(min3); // 1

// 下面是擷取字串的方法演示下 

const strs = 'kongzhi';

// 使用ES6的語法 程式碼演示如下:

const str1 = Reflect.apply(String.prototype.slice, strs, [0, 3]);
console.log(str1); // 列印 kon

// 使用 ES5的語法 
const str2 = strs.slice(0, 3);
console.log(str2); // 列印 kon

// 或者我們使用 String.prototype 程式碼如下演示
const str3 = String.prototype.slice.apply(strs, [0, 3]);
console.log(str3); // kon

四:Reflect.construct(target,args[, newTarget])

該方法的作用和 new AAA() 建立一個實列方法作用類似,那麼使用該方法,我們就可以提供一種不使用new來呼叫建構函式的方法,
引數含義如下:
target: 被執行的目標函式。
args: 呼叫建構函式傳遞的引數陣列或偽陣列。
newTarget: 也是建構函式,表示使用 Reflect.construct後生成的實列物件是誰的實列。如果沒有該引數,預設生成的實列物件就和target建構函式是一樣的。

程式碼演示如下:

function XXXX(name) {
  this.name = name;
}

XXXX.prototype.getName = function() {
  return this.name;
}

function YYYY(age) {
  this.age = age;
}

YYYY.prototype.getAge = function() {
  return this.age || 31;
}

// 使用 XXXX函式作為建構函式, 那麼建構函式就指向了 XXXX函式
const xxxx = Reflect.construct(XXXX, ['xx']);
console.log(xxxx);  // 列印 XXXX {name: xx}
console.log(xxxx.getName()); // 列印 xx

如下圖所示:

// 使用 YYYY 函式作為建構函式,那麼建構函式就指向了 YYYY函式
const yyyy = Reflect.construct(XXXX, ['30'], YYYY);

console.log(yyyy); // 列印 YYYY {name: 30}
console.log(yyyy.name); // 30
console.log(yyyy.age); // undefined
console.log(yyyy instanceof YYYY); // true
console.log(yyyy instanceof XXXX);  // false
console.log(yyyy.getAge()); // 31

如上demo所示:當const xxxx = Reflect.construct(XXXX, ['xx']); 沒有第三個引數的時候,那麼建構函式指向了 XXXX 函式。
我們繼續看第二個demo,const yyyy = Reflect.construct(XXXX, ['30'], YYYY); 有第三個引數,因此 yyyy的實列指向了 YYYY.
如上程式碼列印的資訊看到 console.log(yyyy instanceof YYYY); 返回true, console.log(yyyy instanceof XXXX); 返回false.
但是呢 console.log(yyyy.getAge()); 返回的是 31. 如果我們沒有預設的 31值的話,那麼就應該返回undefined了,可以看到,請看下面的注意總結:

注意:如果有第三個引數的話,那麼我們的實列由兩部分組成,實列的屬性部分由第一部分建構函式生成。實列的方法由第三個引數物件生成。

比如上面列印的 console.log(yyyy); // 列印 YYYY {name: 30} 看到只返回了 XXXX中的name屬性,XXXX中的getName方法並沒有拿到。
同理如上 console.log(yyyy.age); 為undefined, console.log(yyyy.getAge()); 返回了31.  如下圖所示:

五:Reflect.defineProperty(target,name,desc)

該方法與Object.defineProperty方法類似的,不過唯一的區別是 Reflect.defineProperty返回值是一個Boolean的值。
比如如下基本的程式碼比較:

const obj = {};
// 使用 Object.defineProperty
try {
  Object.defineProperty(obj, 'a', {
    value: 22
  })
} catch(e) {
  console.log('define property failed');
}

// 使用 Reflect.defineProperty

const res = Reflect.defineProperty(obj, 'b', {
  configurable: true,
  enumerable: true
});

console.log(res); // true

既然兩者的用法是一樣的,那配置項也是一樣的,那這邊就不多介紹了,只是返回值不一樣而已,那麼Object.defineProperty 的具體用法,
請看我上一篇文章(https://www.cnblogs.com/tugenhua0707/p/10261170.html)。

因此總結一下:如果使用Object.defineProperty的屬性定義失敗了,就會丟擲一個錯誤,成功的話就會返回這個物件;
Reflect.defineProperty如果定義屬性失敗的話就會返回false,如果成功定義的話,就會返回true。
但是如果使用Reflect.defineProperty函式,它的第一個引數不是物件的話,也會丟擲錯誤。

六:Reflect.deleteProperty(target,name)

該方法用於刪除一個物件上的屬性,它和delete操作符類似的。
引數如下:
target: 表示要操作的物件。
name: 表示要刪除該物件上的屬性。
該函式返回值是一個Boolean的值,如果成功的話,返回true,失敗的話返回false。比如如下demo演示:

const obj = {
  name: 'kongzhi',
  age: 30
};

let test1 = Reflect.deleteProperty(obj, 'name');
console.log(test1);  // true
console.log(obj); // {age: 30}

// 如果刪除物件上不存在的屬性的話,也是返回true的
let test2 = Reflect.deleteProperty(obj, 'xx');
console.log(test2); // true
console.log(obj); // {age: 30}

let test3 = Reflect.deleteProperty(obj, 'age');
console.log(test3); // true
console.log(obj); // {}

七:Reflect.has(target,name)

該方法的含義是:檢查一個物件上是否含有特定的屬性。相當於es5中的in操作符。
那麼引數 target: 就是改物件哦,name的含義是:該物件上的屬性。
具體的demo演示如下:

// 一般的物件

const obj = {
  name: 'kongzhi',
  age: 30
};

console.log(Reflect.has(obj, 'name')); // true
console.log(Reflect.has(obj, 'username')); // 該物件上沒有 username屬性  返回false
console.log(Reflect.has(obj, 'age')); // true

// 函式的實列

function Obj(name) {
  this.name = name;
}

Obj.prototype.getName = function() {
  return this.name;
}

const test = new Obj();

// 使用in操作符測試
console.log('name' in test); // true
console.log('getName' in test); // true

// 使用Reflect.has 測試
console.log(Reflect.has(test, 'name')); // true
console.log(Reflect.has(test, 'getName')); // true

八:Reflect.ownKeys(target)

該函式的作用是:返回由目標物件自身的屬性鍵組成的陣列。如果這個目標物件不是一個物件的話,那麼該函式就會丟擲一個異常。
target引數:它是一個物件。如下程式碼演示:

const obj = {
  name: 'kongzhi',
  age: 30
};

console.log(Reflect.ownKeys(obj)); // ['name', 'age'];

九:Reflect.preventExtensions(target)

該方法的作用是 阻止新的屬性新增到物件中去。target引數必須是一個物件,否則的話會丟擲一個異常。
如下程式碼演示:

const obj = {};
// 判斷該物件是否可以擴充套件,使用 Reflect.isExtensible 該方法
const t1 = Reflect.isExtensible(obj);
console.log(t1);  // true

// 使用 Reflect.preventExtensions 來阻止該物件擴充套件

Reflect.preventExtensions(obj);

// 再來擴充套件下該物件,看是否可以
const t2 = Reflect.isExtensible(obj);

console.log(t2); // false

十:Reflect.isExtensible(target)

該方法的作用是檢查一個物件是否可以擴充套件的,也就是說物件裡面是否可以新增新的屬性或方法。
target參數列示目標物件。如果該目標物件不是一個物件的話,那麼函式會丟擲一個異常。
該函式會返回一個Boolean值,如果為true的話,說明該物件可以擴充套件,否則的話返回false,表示該物件不可以擴充套件。
如下demo來演示下:

const obj = {};
// 判斷該物件是否可以擴充套件,使用 Reflect.isExtensible 該方法
const t1 = Reflect.isExtensible(obj);
console.log(t1);  // true

// 使用 Reflect.preventExtensions 來阻止該物件擴充套件
Reflect.preventExtensions(obj);

// 再來擴充套件下該物件,看是否可以
const t2 = Reflect.isExtensible(obj);

console.log(t2); // false

十一:Reflect.getOwnPropertyDescriptor(target, name)

該方法的引數如下解析:
target: 表示的是目標物件。
name: 表示目標物件的屬性
該方法的具體含義是:如果目標物件中的屬性描述符存在的話,就返回這個屬性描述符,如果不存在,就返回undefined。
如下demo演示:

const obj = {};

Reflect.defineProperty(obj, 'name', {
  configurable: true,
  enumerable: true,
  writable: true,
  value: '30'
});

const test1 = Reflect.getOwnPropertyDescriptor(obj, 'name');
/*
 列印值如下:
 {
  configurable: true
  enumerable: true
  value: "30"
  writable: true
 }
*/
console.log(test1);

const test2 = Reflect.getOwnPropertyDescriptor(obj, 'age');
console.log(test2); // undefined

// 如果第一個引數不是物件
const test3 = Object.getOwnPropertyDescriptor('kkkk', 'name');
console.log(test3); // undefined

// 使用 try catch 包圍,會執行 catch方法內部程式碼
try {
    const test4 = Reflect.getOwnPropertyDescriptor('kkkk', 'name');
    console.log(test4);
} catch (e) {
    console.log('error');
} 

十二:Reflect.getPrototypeOf(target)

 該方法是返回一個物件的原型的,也就是說內部的 [[Prototype]] 屬性的值。來看如下程式碼:

function testA() {};

testA.prototype.xxx = function() {};

const a = new testA();

console.log(Object.getPrototypeOf(a));

列印 如下圖所示:

十三:Reflect.setPrototypeOf(target, prototype) 

該方法的作用是設定一個物件的原型。如果設定成功的話,這個物件就返回一個true,如果設定失敗的話,這個物件就返回一個false。
比如如下程式碼:

const obj = {};
const test1 = Reflect.setPrototypeOf(obj, Object.prototype);
console.log(test1); // true

let test2 = Reflect.setPrototypeOf(Object.freeze({}), null);
console.log(test2); // false

相關文章