【注:如下TypeScript簡稱爲TS】javascript
咱們從一段實現類繼承的代碼開始(出自TS):java
var Point3D = (function (_super) {
__extends(Point3D, _super);
function Point3D(x, y, z) {
_super.call(this, x, y);
this.z = z;
} P
Point3D.prototype.add = function (point) {
var point2D = _super.prototype.add.call(this, point);
return new Point3D(point2D.x, point2D.y, this.z + point.z);
};
return Point3D;
})(Point);
複製代碼
這種被叫作當即調用函數表達式(IIFE [Immediately-Invoked Function Expression])git
__extends實現以下(這段代碼也是TS裏實現類繼承的簡化版本):typescript
var __extends = this.__extends || function (Child, Parent) {
for (var p in Parent) if (Parent.hasOwnProperty(p)) Child[p] = Parent[p];
function __() { this.constructor = Child; }
__.prototype = Parent.prototype;
Child.prototype = new __();
};
複製代碼
for (var p in Parent) if (Parent.hasOwnProperty(p)) Child[p] = Parent[p];
這裏的靜態屬性是指直接用hasOwnProperty可以訪問到的屬性,而並不是原型鏈上的屬性。Child.prototype.__proto__ = Parent.prototype
,而後爲啥一句話就能搞定,可是TS要寫3句話,那是由於__proto__這個屬性有兼容性問題,有些瀏覽器叫法不一樣,或者有些根本不讓你去訪問到,但確確實實在其內部存在這麼一個東西,接下去咱們先來把Child.prototype.__proto__ = Parent.prototype
這個解釋通,而後再來講下剩下的3行代碼首先先來名詞解釋一下瀏覽器
__proto__
在JS裏,全部實例對象都有一個__proto__(拋開瀏覽器兼容性,只說概念),做用是:若是訪問某個對象的屬性obj.abc沒有的話,那就會去obj.__proto__
.abc去找,若是仍是沒有找到,那就去obj.__proto__
.__proto__
.abc找,以此類推,一直到__proto__爲null結束,若是還沒找到,那就真的沒有了。這個就是js支持的原型繼承。舉例:函數
var foo = {}
// setup on foo as well as foo.__proto__
foo.bar = 123;
foo.__proto__.bar = 456;
console.log(foo.bar); // 123
delete foo.bar; // remove from object
console.log(foo.bar); // 456
delete foo.__proto__.bar; // remove from foo.__proto__
console.log(foo.bar); // undefined
複製代碼
prototype
__proto__
是給實例用的,而prototype
是給函數用的,每一個JS裏的function都有個prototype
(這個貌似沒有兼容性問題,比較統一),而後prototype
有一個成員叫constructor
,這個constructor
又反過來指向函數本身。代碼以下:ui
function Foo() { }
console.log(Foo.prototype); // {} i.e. it exists and is not undefined
console.log(Foo.prototype.constructor === Foo); // Has a member called `constructor` pointing
複製代碼
this
在函數裏指向了當前這個被新建立出來的對象實例,通常咱們會在構造函數裏用this
來改一下這個實例的初始值等。this
function Foo() {
this.bar = 123;
}
// call with the new operator
var newFoo = new Foo();
console.log(newFoo.bar); // 123
複製代碼
我以爲能夠這麼認爲,用了new關鍵字來調用函數後,不但建立了一個新對象,並且在函數最後強制return了裏面的this,從而可以賦值給左邊那個變量(newFoo)spa
prototype
和__proto__
作了些啥調用了new以後,會把這個函數的prototype
賦值給這個新對象實例的__proto__
屬性。舉例:prototype
function Foo() { }
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // True!
複製代碼
1 function __() { this.constructor = Child; }
2 __.prototype = Parent.prototype;
3 Child.prototype = new __();
複製代碼
咱們倒過來看,
第3行 Child.prototype = new __()
意思實際上是: Child.prototype = {__proto__ : __.prototype}
(上面第4點)
而後結合第2行代碼 __.prototype = Parent.prototype
, 其實就變成了 Child.prototype = {__proto__ : Parent.prototype}
到這裏,其實咱們已經完成目標Child.prototype.__proto__ = Parent.prototype
而後爲了和普通函數擁有相同的定義,參考第1點,咱們須要再復原一下Child.prototype.constructor
,這也就是第一行function __() {this.constructor = Child; }
作的事情,最後就變成了Child.prototype = {__proto__ : Parent.prototype , constructor : Child}
(上面第2點)
而後疑問來了,咱們爲啥要復原這個constructor
,其實沒有這個constructor
,咱們大部分繼承功能都能使用,具體能夠參考一下這裏
大概理解爲,這個玩意只有在你本身手動調用的時候纔有用,好比有一個var ball = new Football();
,你用着用着,發現忘了這個ball是Football仍是Basketball構造出來的,你確實又想要一個和ball同類型的,那你能夠這樣來 var ball2 = ball.constructor();
,這樣ball2和ball就同樣了,不過好像用不太到就是。。。若是有小夥伴知道更多的理解,麻煩評論給我哈。。。。
這篇文章是看了TypeScript Deep Dive關於TS類轉成JS實現那部分以後總結的,若有不對的地方還但願你們評論給我。