元編程:操作程序本身行為特性的編程
元編程關注以下幾點:代碼查看自身、代碼修改自身、代碼修改默認特性、影響其他代碼
#### 元屬性new.target
new.target總是訪問真正new的目標的上下文
```
class Parent {
constructor() {
if(new.target === parent) console.log('Parent instantiated');
else console.log('Child instantiated');
}
}
class Child extends Parent {}
var a = new Parent(); //Parent instantiated
var b = new Child(); //Child instantiated
```
#### 公開符號
symbol對外提供了一些公共的符號
`Symbol.iterator`
例如...、for...of都是自動使用Symbol.iterator的,它是對象上的一個專門屬性,它會自動查找對象上有沒有一個方法(可以構造一個迭代器來消耗這個對象的值)。你也可以重寫:
```
var arr = [1, 2, 3];
arr[Symbol.iterator] = function *() { ... }
```
`Symbol.toStringTag`和`Symbol.hasInstance`
```
function Foo() {}
var a = new Foo();
a.toString(); //[object Object]
a instanceOf Foo; //true
//Symbol.toStringTag和Symbol.hasInstance更改上述結果
function Foo() {}
Foo.prototype[Symbol.toStringTag] = "Foo";
Object.defineProperty(Foo, Symbol.hasInstance, { value: obj => false });
//必須使用Object.defineProperty來使用Symbol.hasInstance,因為Function.prototype上的那個的writable是false
//obj就是FOO的實例,比如是a、b
var a = new Foo();
var b = new Foo();
b[Symbol.toStringTag] = "cool";
a.toString; //[object Foo];
b.toString; //[object cool];
a instanceof Foo; //false
b instanceof Foo; //false
```
`Symbol.toPrimitive`
```
var arr = [1, 2, 3, 4, 5];
arr + 10; //1,2,3,4,510
arr[Symbol.toPrimitive] = function(hint) {
//默認期望的運算類型,在這里是default,也是number
//對于arr + 10來說,hint是default(number)
//對于arr * 10來說,hint是number
//對于String(arr)來說,hint是string
if(hint == "default" || hint == "number") {
return this.reduce(function(acc, curr) { return acc + curr }, 0);
}
};
arr + 10; //25
```
`Symbol.isConcatSpreadable`
```
var a = [1, 2, 3], b = [4, 5, 6];
[].concat(a, b); //[1, 2, 3, 4, 5, 6];
b[Symbol.isConcatSpreadable] = false;
[].concat(a, b); //[1, 2, 3, [4, 5, 6]]
```
#### 代理
代理是一種特殊的對象,它封裝了一個普通的對象,作為該普通對象的攔截器。
```
var obj = {a: 1};
var pobj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(target, key);
return Reflect.get(target, key, receiver);
}
});
//攔截obj的get方法做出一系列操作后通過Reflect.get轉發該操作(在這里是到默認操作上)
obj.a; //1
pobj.a; //{a: 1} a
//1
```
每一個代理trap都有一個與之同名的Reflect api,在這里是get
```
//列舉Proxy可用trap
1.get:在代理上訪問一個屬性:Reflect.get(obj)、obj[]、obj.
2.set:在代理上設置一個屬性:為對象賦值或解構等操作
3.deleteProperty:從代理對象上刪除一個屬性
4.Reflect.deleteProperty(obj)或delete obj
5.apply(如果目標是函數的話):將代理作為普通函數調用
6.Reflect.apply、Reflect.call、func.apply、func.call
7.defineProperty
8.getPrototypeOf
9.setPrototypeOf
10.has
```
可取消的代理
```
var obj = {a: 1};
var handlers = {
get(target, key, receiver) {
console.log("accessing: ", key);
return target[key];
}
};
//普通的代理是new Proxy()創建,而可取消代理只能這樣創建
var {proxy: pobj, revoke: prevoke} = Proxy.revocable(obj, handlers);
pobj.a; //accessing: a
//1
prevoke();
pobj.a; //TypeError
//一旦代理被取消,所有對代理的操作均會TypeError
```
使用代理
日常中,建議實際操作的是代理,而元對象被隱藏起來
```
//代理在先(首先與、主要或完全代理交互)
var message = [];
handlers = {
get(target, key) {
if(typeof target[key] === 'string') {
//過濾掉標點符號
return target[key].replace(/[^\w]/g, "");
}
return target[key];
},
set(target, key, val) {
if(typeof val === "string") {
val = val.toLowerCase();
if(target.indexOf(val) === -1) target.push(val.toLowerCase())
}
return true;
}
}
var message_proxy = new Proxy(messages, handlers);
message_proxy.push("hello...", 42, "wOrlD!!", "WoRlD!!");
//遍歷message_proxy
//hello world
//遍歷messages
//hello... world!!
```
```
//代理在后(代碼只能與主對象交互,實在不行了再與代理交互)
var handlers = {
get(target, key, receiver) {
return function() { receiver.speak(key + "!") }
}
};
var catchall = new Proxy({}, handlers);//換成greeter結果也一樣
var greeter = {speak(who) {console.log("hello", who)}};
Object.setPrototypeOf(greeter, catchall);
greeter.speak(); //hello undefined
greeter.speak("world"); //hello world
greeter.everyone(); //hello everyone!
//代理作為greeter的原型,用戶直接與greeter交流,而greeter沒有everyone方法,js會順著原型鏈找到代理查看代理有沒有這個屬性,此時會觸發代理的get
```
No such property or method
```
//有時候,我們期望只有屬性存在的時候才對該屬性進行操作
var obj = {a: 1, foo(){ console.log(this.a) }};
handlers = {
get(target, key, receiver) {
if(Reflect.has(target, key)) {
return Reflect.get(target, key, receiver);
}
else {
throw 'No such property/method';
}
},
set(target, key, receiver) {
if(Reflect.has(target, key)) {
return Reflect.set(target, key, receiver);
}
else {
throw 'No such property/method';
}
}
}
var pobj = new Proxy(obj, handlers);
pobj.a = 3;
pobj.foo(); //3
pobj.b = 4; //Error: no such property/method
pobj.bar(); //Error: no such property/method
//代理在后
var handlers = {
get() { throw 'No such property/method'; }
set() { throw 'No such property/method'; }
}
pobj = new Proxy({}, handlers);
Object.setProtytypeOf(obj, pobj);
pobj.a = 3;
pobj.foo(); //3
pobj.b = 4; //Error: no such property/method
pobj.bar(); //Error: no such property/method
```
代理[[proptotype]]鏈
```
//[[prototype]]機制的原理就是[[Get]]運算,當對象自身沒有某個屬性時,[[Get]]會自動把這個運算轉給[[prototype]]處理
//我們可以用代理做一個原型環
var handlers = {
get(target, key, receiver) {
if(Reflect.has(target, key)) {
return Reflect.get(target, key, receiver);
}
//直接對象沒有該屬性,則訪問我們自定義的原型
else {
return Reflect.get( target[Symbol("[[Prototype]]")], key, receiver );
}
}
}
var obj1 = new Proxy({
name: 'obj-1',
foo() { console.log('foo': this.name) }
}, handlers);
var obj2 = Object.assign(Object.create(obj1), {
name: 'obj-2',
bar() { console.log('bar: ', this.name); this.foo(); }
});
obj1[Symbol.for("[[Prototype]]")] = obj2;
//現在,obj2.prototype是obj1,而obj1的Symbol("[[Prototype]]")是obj2
//當obj1某個屬性沒有時,會代理到Symbol("[[Prototype]]")上,也就是obj2上
obj1.bar(); //bar: obj1
//foo: obj1
//因為obj1有name,所以訪問的是obj1的name
obj2.foo(); //foo: obj2
//因為obj12沒有foo,通過Prototype訪問obj1的foo
```
```
//不用原型環,應用:為一個對象創建多個“Prototype”
var obj1 = {
name: 'obj-1',
foo() { console.log('obj1.foo: ', this.name) }
}
var obj2 = {
name: 'obj-2',
foo() { console.log('obj2.foo', this.name) },
bar() { console.log('obj2.bar', this.name) }
}
var handlers = {
get(target, key, receiver) {
if(Reflect.has(target, key)) {
return Reflect.get(target, key, receiver);
}
else {
for(var p of target[Symbol.for("[[Prototype]]")]) {
if(Reflect.has(p, key)) {
return Reflect.get(p, key, receiver)
}
}
}
}
}
var obj3 = new Proxy({
name: 'obj-3',
baz() {
this.foo();
this.bar();
}
}, handlers)
obj3[Symbol.for("[[Prototype]]")] = [obj1, obj2]
obj3.baz(); //obj1.foo: obj-3
//obj2.bar: obj-3
```
#### Reflect API
設計初衷:
* 將Object對象的一些明顯屬于語言內部的方法(比如Object.defineProperty),放到Reflect對象上,未來所有操作內部api的事情全部交由Reflect來做,而不是Object.。
* 修改了某些Object方法的返回結果,讓其變得更合理。
Reflect的api與Proxy的trap一一對應,參考Proxy的那些trap。
Reflect API的第一個參數永遠是目標對象,如果它不是一個對象,則會報錯。
屬性排序
```
//一般的,對象的屬性列出順序是依賴于定義順序的
//而我們可以使用Reflect.ownKeys將列出順序改為es6的[[OwnPropertyKeys]]算法列出的順序
//Object.getOwnPropertyNames和Object.getOwnPropertySymbols也是按照該算法,只不過它們只能列出各自的
var o = {
[Symbol("c")]: "yay",
2: true,
1: true,
b: "awesome",
a: "cool"
}
Reflect.ownKeys(o); //[1, 2, "b", "a", Symbol(c)]
Object.getOwnPropertyNames(o); //[1, 2, "b", "a"]
Object.getOwnPropertySymbols(o); //[Symbol(c)]
//JSON.stringify(obj)不會考慮obj的原型鏈,它只會stringify obj自身的屬性并返回
```
#### 尾調用
尾調用的概念非常簡單,一句話就能說清楚,就是指某個函數的最后一步是調用另一個函數。
```
function f(x){
return g(x);
}
//函數f的最后一步是調用函數g,這就叫尾調用。
//下面的都不是尾調用
// 情況一
//調用g后,將其賦值給y然后再返回y
function f(x){
let y = g(x);
return y;
}
// 情況二
//調用g后,將結果+1再返回
function f(x){
return g(x) + 1;
}
```
尾調用不一定出現在函數尾部,只要是最后一步操作即可。
```
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
//上面代碼中,函數m和n都屬于尾調用,因為它們都是函數f的最后一步操作。
```
尾調用之所以與其他調用不同,就在于它的特殊的調用位置。
我們知道,函數調用會在內存形成一個"調用記錄",又稱"調用幀"(call frame),保存調用位置和內部變量等信息。如果在函數A的內部調用函數B,那么在A的調用記錄上方,還會形成一個B的調用記錄。等到B運行結束,將結果返回到A,B的調用記錄才會消失。如果函數B內部還調用函數C,那就還有一個C的調用記錄棧,以此類推。所有的調用記錄,就形成一個["調用棧"](http://zh.wikipedia.org/wiki/%E8%B0%83%E7%94%A8%E6%A0%88)(call stack)。
尾調用由于是函數的最后一步操作,所以不需要保留外層函數的調用記錄,因為調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用記錄,取代外層函數的調用記錄就可以了。
尾調用具體請參考[阮一峰的尾調用](http://www.ruanyifeng.com/blog/2015/04/tail-call.html)
EventLoop具體請參考[阮一峰的EventLoop](http://www.ruanyifeng.com/blog/2014/10/event-loop.html)
- 你不知道的JS上
- 第一部分 第三章 函數作用域和塊作用域
- 第一部分 第四章 提升
- 第一部分 第五章 閉包
- 第二部分 第一章 關于this
- 第二部分 第二章 this全面解析
- 第二部分 第三章 對象
- 第二部分 第五章 原型
- 第二部分 第六章 行為委托
- 你不知道的JS中
- 第一部分 第二章 值
- 第一部分 第三章 原生函數
- 第一部分 第四章 強制類型轉換
- 第一部分 第五章 語法
- 第二部分 第一章 異步
- 第二部分 第三章 Promise
- 第二部分 第四章 生成器
- 第二部分 第五章 性能
- 你不知道的JS下
- 第一部分 總結
- 第二部分 第二章 語法
- 第二部分 第三章 代碼組織
- 第二部分 第四章 Promise
- 第二部分 第五章 集合
- 第二部分 第六章 新增API
- 第二部分 第七章 元編程
- 第二部分 第八章 ES6之后