總目錄
從C#到TypeScript - Reflect
在C#里如果想只通過名字來生成類實例、獲取屬性或執行方法可以使用反射,反射是基於元數據,現在很多流行語言都支持元數據,以此來提供更多便利的功能。
ES6和TypeScript也有Reflect,不過因為JavaScript本身是解釋型語言,很多操作如根據名字字符串獲取屬性,根據字符串執行函數這些原本就有支持,Reflect只是把這些操作歸結到一起。
下面來通過例子來看下TS Reflect常見的用法。
Reflect Get/Set
定義如下:
Reflect.get(target, name, receiver);
Reflect.set(target, name, value, receiver);
看上去也很好理解,和C#很類似:
target:操作的對象
name:名字字符串
value:要賦的值
receiver:這個比較怪,因為類里可以有getter/setter屬性,這兩種操作可以在代碼塊里使用this
,如果要用Reflect操作的話,receiver就會代替這個this
。
Reflect的操作即使是類的private變量也能獲取到。
class Test{
constructor(age: number){
this.age = age;
}
private _age: number;
get age(): number{
return this._age; // this 會被receiver代替
}
set age(value: number) {
this._age = value; // this 會被receiver代替
}
}
class Receiver{
_age: number = 2;
}
let t = new Test(1);
let r = new Receiver();
console.info(Reflect.get(t, "_age")); // 1, 獲取t的_age值
console.info(Reflect.get(t, "age")); // 1, 獲取t的age值
console.info(Reflect.set(t, "age", 3)); // true, 成功設置age值為3
console.info(Reflect.get(t, "age")); // 3, 再次獲取t的age值
console.info(Reflect.get(t, "age", r)); // 2, 表面上是t的age,但實際上獲取的是r的age
console.info(Reflect.set(t, "age", 3, r)); // true, 表面上是設置t的age, 實際上是設置r的age值為3
console.info(Reflect.get(r, "_age")); // 3, 直接獲取r的_age
apply
上面是屬性,還有方法,定義如下:
Reflect.apply(func, thisArg, args);
熟悉JS的朋友應該知道Function也有apply方法,fn.apply(obj, args)
,可以說是同樣的效果。
如果要通過函數名來調用函數,可以這樣做:
class Test{
add(a: number, b: number): number{
return a + b;
}
}
let t = new Test();
console.info(Reflect.apply(t["add"], t, [1, 2])); // 3, 雖然t["add"]可以直接執行,不過有時可能需要設置thisArg
define/delete property
define相比之前就真是簡單把Object替換成了Reflect,delete和delete obj[name]
效果一樣。
Reflect.defineProperty(target, propertyKey, attributes);
Reflect.deleteProperty(obj, name);
例子延用上面的對象t:
//define
Reflect.defineProperty(t, 'time', {
value: Date.now()
});
console.info(t.time); // 一串數字
//delete
let d = {time: 111};
console.info(d.time); // 111
Reflect.deleteProperty(d, 'time'); // 成功的話返回true,否則返回false
console.info(d.time); // undefined
可以看到define的參數attributes
是一個PropertyDescriptor對象,value
就是值,其他還有writable, enumerable, configurable
用來控制屬性的權限。
對於delete,需要注意的是deleteProperty對class的屬性是無效的。
has ownKeys
ownKeys
返回的是對象所有屬性,包括不可枚舉的,如Symbol之類。
has
用來判斷對象是否有某個屬性或方法,包括原型鏈上的。
class Test{
constructor(name: string){
this.name = name;
}
name: string;
flag: Symbol = Symbol('flag');
getName(): string{
return this.name;
}
}
let obj = new Test('123');
console.info(Reflect.has(obj, 'name')); // true
console.info(Reflect.has(obj, 'flag')); // true
console.info(Reflect.has(obj, 'get')); // true
console.info(Reflect.has(obj, 'toString')); // true
for(let p of Reflect.ownKeys(obj)){
console.info(p); // name, flag
}
其他
-
Reflect.construct(target,args)
實例化對象除了new之外,還可以用這個,有時候很有用,比如ORM框架里join的字段就可以在設置表時把關聯的類型傳給字段,使用時用該類型就可以創建出實例。
-
Reflect.getPrototypeOf(target) 和 Reflect.setPrototypeOf(target, prototype)
分別用於獲取和設置對象的原型
-
Reflect.getOwnPropertyDescriptor(target, name)
設置對象屬性的描述對象,如
configurable, writable, enumerable
。 -
Reflect.isExtensible(target)
分別用於判斷對象是否可擴展。
-
Reflect.preventExtensions(target)
讓一個對象變為不可擴展
Reflect基本上就是把之前Object的方法和一些命令如delete
in
之類聚到一起,相信ES6之后用Reflect來做這些操作將成為主流。