算是炒冷飯吧,最近看React源碼發現有一些原型與繼承方面的東西沒看太明白,便計劃花兩天重溫這方面的東西,以便以後有更好的腦回路。前端
prototype
顯式原型對象,每個函數(除了bind)在建立以後都會擁有一個名爲 prototype 的內部屬性,它指向函數的原型對象。用來實現基於原型的繼承與屬性的共享。__proto__
隱式原型對象,是對象的內部屬性, 任意對象都有一個內置屬性 [[prototype]]
,在ES5以前沒有標準的方法訪問這個內置屬性,可是大多數瀏覽器都支持經過 __proto__
訪問而且ES5能夠經過 Object.getPrototypeOf(target)
訪問。它指向建立這個對象的函數 constructor
的 prototype
, 對象依賴它構成原型鏈進行向上查詢。只有函數纔有顯示原型屬性
prototype
web
全部對象都有隱式原型屬性
__proto__
包括函數的原型Function.prototype === Object.__proto__
express
Function
,String
,Number
,Boolean
...(null 不算)全部對象都擁有 __proto__
屬性,因此才都具備對象的特色。全部這些原生構造函數的 __proto__
通通指向 Function.prototype
。而它的__proto__
又指向 Object.prototype
。因此才稱萬物皆對象。數組
一個對象的隱式原型指向構造該對象的構造函數的顯式原型對象。瀏覽器
var str = String('seven')
str.__proto__ === String.prototype // true
複製代碼
不管是經過字面量建立仍是構造器亦或者是new+構造器建立,js都會幫咱們自動裝箱完成實例對象的轉換。babel
Function 是一個比較獨特的對象,便是對象,也是函數。函數
// Foo 由 Function 構造
var Foo = Function('a','b','return a+b')
// 等同於
function Foo(a,b){ return a + b}
Foo.__proto__ === Function.prototype
// foo 由 Foo 構造
var foo = new Foo();
foo.__proto__ === Foo.prototype
複製代碼
字面量建立等同於調用構造器建立,但和 new
建立出來的 實例對象 又不一樣。牽扯到「裝箱」、「拆箱」...扯遠了...性能
好比上文中 Foo
也有 __proto__
屬性,前面提過,__proto__
指向的是 構造函數的顯式原型,Foo
由 Function
實例化而來,因此 Foo.__proto__
指向的是 Function.prototype
,不止是Function,Object和其餘類型都是同樣的道理。測試
函數不只能作對象能作的事情以外,還有個"特權"屬性 prototype
。這個屬性是一個指針,指向一個對象,這個對象的用途就是包含全部實例共享的屬性和方法ui
把上文中的 Foo.prototype
打印出來
{
constructor: ƒ Foo()
__proto__: Object
}
複製代碼
Foo
的顯式原型對象也是對象,原型對象的構造函數都是 Object
,它的 __proto__
屬性固然是指向 Object.prototype
。
Foo.prototype.__proto__ === Object.prototype
複製代碼
假如咱們想給數組原型添加一個去重排序方法 uniqueFlatWithSort
,讓全部數組均可以使用。應該都知道直接往Array.prototype
上加
Array.prototype.uniqueFlatWithSort = function() {
return [...new Set(this.flat(Infinity))].sort((a,b)=> a - b)
}
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
arr.uniqueFlatWithSort() // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
複製代碼
咱們都知道屬性是經過隱式原型__proto__
遞歸向上原型鏈查找的,而隱式原型指向的正是構造函數Array
的顯式原型prototype
。
仍是上面的例子
var str = String('seven')
str.padEnd(10,"$") // seven$$$$$
str.padEnd === str.__proto__.padEnd === String.prototype.padEnd
複製代碼
str
首先進行裝箱操做,轉化成字符串對象,String {"seven"}
。str
上找不到 padEnd
的屬性,開始進行原型鏈向上查找。str.__proto__
上找,也就是 String.prototype
,發現了String.prototype.padEnd
並返回...若是字符串 str
想調用 Object
的 hasOwnProperty
方法。
String {"seven"}
。hasOwnProperty
方法,發現沒有,開始進行原型鏈向上查找。str.__proto__
也就是 String.prototype
仍然沒有該屬性。str.__proto__.__proto__
上找,也就是 String.prototype
原型對象的構造函數 Object
顯式原型 prototype
上去找。Object.prototype.hasOwnProperty
並返回。這也就是爲何通常都會將實例方法建立前掛載在其顯式原型上,好讓子類的隱式原型經過進行原型鏈向上查找。instanceOf 即是這個原理遍歷原型鏈。
調用一個方法的時候,首先在對象自己屬性內查找,沒有則到 obj.__proto__
隱式原型內查找,若是尚未,就到obj.__proto__.__proto__
...。這條向上查找的鏈路就被稱爲原型鏈。
最終找到Object.prototype
,此時若是仍然沒有則返回 undefined
,由於再往上就是終點了。
Object.prototype.__proto__ === null
複製代碼
prototype 還有一個屬性 constructor
,它的指針指回構造函數。
foo.__proto__.constructor === Foo // true
複製代碼
當new一個函數的時候,執行的是原型鏈中的構造函數.
這張圖確定不會陌生,看完前面的就能明白。
按照先前的總結,Foo.prototype
是一個原型對象,它有兩個屬性:__proto__
和 constructor
,前者已經熟悉
function Foo(){}
Foo.prototype.constructor === Foo
複製代碼
這一步得出函數的顯式原型的構造函數指向 函數自身。
這個好理解,循環引用
Foo === Foo.prototype.constructor === Foo.prototype.constructor.prototype.constructor
複製代碼
全部構造器(函數)的__proto__都指向Function.prototype
Object.__proto__ === Function.prototype; // true
Function.__proto__ === Function.prototype; // true
Number.__proto__ === Function.prototype; // true
Boolean.__proto__ === Function.prototype; // true
String.__proto__ === Function.prototype; // true
Object.__proto__ === Function.prototype; // true
Array.__proto__ === Function.prototype; // true
RegExp.__proto__ === Function.prototype; // true
Error.__proto__ === Function.prototype; // true
Date.__proto__ === Function.prototype; // true
複製代碼
既然是構造函數,那他就是 Function
的實例,因此 原生構造函數.__proto__ === Function.prototype
。
也就有了
String.__proto__ === Boolean.__proto__
RegExp.__proto__ === Error.__proto__
Date.__proto__ === Number.__proto__
複製代碼
同理,函數原型的隱式原型都是對象,因此構造函數是 Object
,Function.prototype.__proto__ === Object.prototype
,也就是
Object.__proto__.__proto__ === Object.prototype; // true
Function.__proto__.__proto__ === Object.prototype; // true
Number.__proto__.__proto__ === Object.prototype; // true
Boolean.__proto__.__proto__ === Object.prototype; // true
String.__proto__.__proto__ === Object.prototype; // true
Object.__proto__.__proto__ === Object.prototype; // true
Array.__proto__.__proto__ === Object.prototype; // true
RegExp.__proto__.__proto__ === Object.prototype; // true
Error.__proto__.__proto__ === Object.prototype; // true
Date.__proto__.__proto__ === Object.prototype; // true
複製代碼
可是話又說回來了,既然全部對象都是經過構造器實例化出來的,可是構造器也是函數!究竟是先有 Function
仍是先有 Object
?
並且爲何函數的原型對象是個函數typeof Function.prototype === 'function'
?
而後爲何它是函數反而沒有 prototype
特權屬性: Function.prototype.prototype === undefined
按道理不該該是 Function.__proto__ === Object.prototype
?仍是說 Function
實際上是經過 Function.prototype
構造器實例化的,Function
自己只是個實例。或者說他們的關係就是一個僞命題
fn.__proto__ = obj.prototype
obj.__proto__ = fn.prototype
複製代碼
函數對象究竟是什麼?
雖然在winter
的重學前端專題中第8節對函數對象的定義是擁有瀏覽器內建call方法的對象。可是解釋這個JS版的雞生蛋蛋生雞的問題仍然有點勉強,或許等之後刨析V8源碼才能一探究竟(立下Flag)。關係圖以下:
Instanceof 一般用來判斷一個實例是否屬於某種類型。
好比
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo) // true
複製代碼
又亦如原型繼承的多層繼承關係。
function Bar(){}
function Foo(){}
Foo.prototype = new Bar();
var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Bar)//true
複製代碼
以爲很簡單?
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true
console.log(Array instanceof Object); // true
console.log(Array instanceof Function); // true
console.log(String instanceof Function); // true
console.log(String instanceof Object); // true
console.log(Number instanceof Number); // false
console.log(String instanceof String); // false
console.log(Boolean instanceof Boolean); // false
console.log(Array instanceof Array); // false
console.log(RegExp instanceof RegExp); // false
console.log(Symbol instanceof Symbol); // false
console.log(Error instanceof Error); // false
console.log(Foo instanceof Function); // true
console.log(Foo instanceof Foo); // false
複製代碼
關於 instanceof
運算符的定義,厚着臉皮把人家註釋好的粘過來。連接在底部。
11.8.6 The instanceof operator
The production RelationalExpression: RelationalExpression instanceof ShiftExpression is evaluated as follows:
1. Evaluate RelationalExpression.
2. Call GetValue(Result(1)).// 調用 GetValue 方法獲得 Result(1) 的值,設爲 Result(2)
3. Evaluate ShiftExpression.
4. Call GetValue(Result(3)).// 同理,這裏設爲 Result(4)
5. If Result(4) is not an object, throw a TypeError exception.// 若是 Result(4) 不是 object,拋出異常
/* 若是 Result(4) 沒有 [[HasInstance]] 方法,拋出異常。規範中的全部 [[...]] 方法或者屬性都是內部的, 在 JavaScript 中不能直接使用。而且規範中說明,只有 Function 對象實現了 [[HasInstance]] 方法。 因此這裏能夠簡單的理解爲:若是 Result(4) 不是 Function 對象,拋出異常 */
6. If Result(4) does not have a [[HasInstance]] method, throw a TypeError exception.
// 至關於這樣調用:Result(4).[[HasInstance]](Result(2))
7. Call the [[HasInstance]] method of Result(4) with parameter Result(2).
8. Return Result(7).
// 相關的 HasInstance 方法定義
15.3.5.3 [[HasInstance]] (V)
Assume F is a Function object.// 這裏 F 就是上面的 Result(4),V 是 Result(2)
When the [[HasInstance]] method of F is called with value V,the following steps are taken:
1. If V is not an object, return false.// 若是 V 不是 object,直接返回 false
2. Call the [[Get]] method of F with property name "prototype".// 用 [[Get]] 方法取 F 的 prototype 屬性
3. Let O be Result(2).//O = F.[[Get]]("prototype")
4. If O is not an object, throw a TypeError exception.
5. Let V be the value of the [[Prototype]] property of V.//V = V.[[Prototype]]
6. If V is null, return false. // 這裏是關鍵,若是 O 和 V 引用的是同一個對象,則返回 true;不然,到 Step 8 返回 Step 5 繼續循環
7. If O and V refer to the same object or if they refer to objects
joined to each other (section 13.1.2), return true.
8. Go to step 5.
複製代碼
看起來比較難以理解,邏輯最終經過左側 L.__proto__
隱式原型的向上查找。
function instance_of(L, R) { // L 爲 instanceof 左側,R爲右側
var Right = R.prototype;// 取 R 的顯示原型
L = L.__proto__; // 取 L 的隱式原型
while (true) {
if (L === null)
return false;
if (L === Right) // !!! 當 Right 等 L 時,返回 true
return true;
L = L.__proto__;
}
}
複製代碼
結合原型的關係一節,爲何 Function
與 Object
會有這麼奇怪的關係就懂了。
建立新對象一般有三種方式,new、字面量建立、Object.create()。在平常開發用的最可能是字面量建立,可是字面量是爲了方便開發人員而設置的語法糖,故只有兩種方法。而Object.create是ES5新增的方法。
當你想複用這條原型鏈的時候,能夠用 Object.create()
。
function Bar (){}
Bar.prototype.getOwner = function(){ return this.name}
Bar.prototype.name = 'Floyd'
var bar = new Bar()
var fiz = Object.create(bar.__proto__)
console.log(fiz.__proto__ === bar.__proto__) // true
console.log(fiz.getOwner()) // 'Floyd'
複製代碼
在早期開發,沒法經過直接訪問原型的方式複用原型鏈。
Object.create(proto,[propertiesObject])
接受兩個參數,proto
是新建立對象的原型對象,propertiesObject
可選屬性。要添加到新對象的可枚舉的屬性描述符以及相應的屬性名稱,須要注意的是,添加的屬性不會被掛載到原型鏈上去,僅僅做用於自己屬性。
好比
var opt = Object.prototype
var o = Object.create(opt,{
foo: {
writable:true,
configurable:true,
value: "hello"
}
})
o.__proto__ === opt.prototype // true
o.hasOwnProperty('foo') // true
複製代碼
看到這其實咱們很容易實現一個簡單版本的 polyfill.
var isObject = (obj) => obj && typeof obj === 'object' && !Array.isArray(obj)
function myCreate(proto, Properties){
function F() {}
F.prototype = proto;
var obj = new F();
if (isObject(Properties)) {
Object.defineProperties(obj, Properties);
}
return obj
}
// test
var o = myCreate(opt,{
foo: {
writable:true,
configurable:true,
value: "hello"
}
})
o.__proto__ === opt.prototype // true
o.hasOwnProperty('foo') // true
複製代碼
在函數內部建立一個臨時性的構造函數,將傳入的對象做爲這個構造函數的原型,最後返回臨時函數的新實例。
爲何要說是簡單版本,暫時還不支持傳 null
;
在Vue的源碼裏使用了大量的Object.create(null)
。這麼有什麼好處?
咱們分別打印下Object.create(null)
和 Object.create({})
的結果。
使用create
建立的對象,沒有任何屬性,顯式No properties
,咱們能夠制定一個很純淨的對象,全部的方法包括toString
、hasOwnProperty
等方法。沒有了"包袱"表明使用for in
能夠徹底避免遍歷原型鏈上的屬性,節約了性能損耗,而且也能夠當成一個乾淨的數據字典來使用。
知道了原理,咱們就好辦多了。
function myCreate(proto, Properties){
// 處理爲proto爲null
if(proto === null){
var pureObj = new Object({})
pureObj.__proto__ = null // 原型鏈必須使用null空指針,不能使用undefined
return pureObj
}
function F() {}
F.prototype = proto;
var obj = new F();
if (isObject(Properties)) {
Object.defineProperties(obj, Properties);
}
return obj
}
複製代碼
測試用例經過
在JS中,被繼承的函數稱爲超類型(父類,基類也行),繼承的函數稱爲子類型(子類,派生類)。
繼承也沒想象中那麼繞,那麼難以理解。只須要記住繼承的原則
複用原型對象這個都明白,無非是屬性與方法的複用;屬性隔離是表示相互不影響,a是A的實例,修改了a就不能影響到A;明確繼承關係則是:好比a是A的實例,那我就要有辦法知道a和A的關係。搞明白這三點,相信你就有了更好的腦回路去理解它。
繼承的方式有不少種,外界對此也沒有準確的認定到底有多少種方式,褒貶不一,主流一般有7種方式:
原型鏈繼承前面例子已經用到屢次。
function Foo(){
this.name = 'seven'
}
Foo.prototype.getName = function(){ return this.name }
var foo = new Foo();
foo.getName() // 'seven'
複製代碼
經過實例化一個新的函數,子類的原型指向了父類的實例,子類就能夠調用其父類原型對象上的私有屬性和公有方法。
仍是上面的例子,當咱們嘗試調用一個不存在的屬性
console.log(foo.getOwner) // undefined 原型鏈上沒有這個方法
複製代碼
原型鏈上沒有這個方法,去修改它的原型
// 建立一個新的構造函數
function Bar (){}
Bar.prototype.getOwner = function(){
return this.name
}
// 原型繼承
Foo.prototype = new Bar() //修改原型指向 Bar.prototype
console.log(foo.getOwner) // undefined 仍是沒有
console.log(foo.constructor) // ƒ Foo(){}
複製代碼
都已經替換原型了仍是沒有更新,表示原型鏈沒有實時性,再測試下新建
var fizz = new Foo()
console.log(fizz.getOwner()) // seven
console.log(fizz.constructor) // ƒ Bar()
console.log(fizz.__proto__) // { getOwner: ƒ, constructor: ƒ}
複製代碼
這時新建的對象能夠訪問更新後的原型,由於完整替換了 prototype
,構造函數又不對了,原本constructor
屬性應該指向Foo
,結果卻指向了Bar
(訪問了bar.__proto__.constructor
),這就是原型陷阱。完整的替換了原型對象致使訪問了新對象的構造函數。
咱們只須要從新指定bar
的構造函數便可。
var bar = new Bar()
bar.__proto__.constructor = Foo
Foo.prototype = bar.__proto__ //修改原型指向 = Bar.prototype
var fizz = new Foo()
console.log(fizz.getOwner()) // seven
console.log(fizz.constructor) // ƒ Foo()
複製代碼
如今就恢復正常了,此時原型鏈爲
.__proto__ |
.__proto__ |
.__proto__ |
.__proto__ |
---|---|---|---|
fizz |
fizz.__proto__ |
fizz.__proto__.__proto__ |
fizz.__proto__.__proto__.__proto__ |
Bar.prototype |
Bar.prototype.__proto__ |
Bar.prototype.__proto__.__proto__ |
|
Object.prototype |
null |
實際上最終不會訪問到Object.__proto__
,例如foo.freeze === undefined
。
搞明白原型陷阱以後,咱們複習一下,把原型繼承搞得稍微複雜一些
function Parent(name,age){
this.name = name;
this.age = age;
this.skill = ['cook','clean','run']
this.say = function(){ console.log(this.name) }
}
Parent.prototype.setName = function() {}
function Children (name){
this.children = name;
this.speak = function() {
console.log(this.childrenName)
}
}
Children.prototype = new Parent('Seven',24)
var c1 = new Children('c1')
var c2 = new Children('c2')
複製代碼
當調用 c1.skill.push('swimming')
的時候,引用類型的值被共享
若是父類的私有屬性中有引用類型的屬性,那它被子類繼承的時候會做爲公有屬性,這樣子類1操做這個屬性的時候,就會影響到子類2,違反了第二條:屬性隔離,實例之間互不影響
.
經過call將超類的this指向子類內部,從而達到隔離的效果。
function Parent(name){
this.name = name;
this.skill = ['cook','clean','run']
}
function Children(name){
Parent.call(this, name);
this.age = 24
}
var c1 = new Children('c1')
var c2 = new Children('c2')
c1.skill.push('swimming'); // ok
c1.skill // ["cook", "clean", "run", "swimming"]
c2.skill // ["cook", "clean", "run"]
c1 instanceof Parent // false
c1 instanceof Children // true
複製代碼
和借用構造函數相似,原理也是使用子類的this冒充父類的this執行其構造函數,因此把它概括在一塊兒。
function Parent(name){
this.name = name;
this.skill = ['cook']
this.getSkill = function(){
return this.skill
}
}
function Child(name, age){
this.c = Parent;
this.c(name,age);
delete this.c;
this.job = '廚師'
this.getAge = function(){
return this.age;
}
}
複製代碼
引用類型的問題是解決了,可是缺點也很明顯,只能繼承超類的屬性和方法,而沒法複用其原型上的屬性和方法。並且實例c1
不是 Parent
超類的子類。並且方法都在構造函數中定義,函數沒法達到複用,違反了第一條和第三條原則。
看完了前兩種方式,有聰明的小夥伴一會兒就能想到點什麼。原型繼承將父類實例做爲子類原型實現函數複用,主要針對原型鏈繼承;借用父類構造函數繼承父類屬性並保留傳參,同時針對屬性隔離,把兩種方式結合起來去其糟粕取其精華,豈不美哉?
這種模式就是組合繼承。
function Parent(name){
this.name = name;
this.skill = ['cook','clean','run']
}
Parent.prototype.getName = function(){
return this.name
}
function Children(name){
Parent.call(this, name);
this.age = 24
}
Children.prototype = new Parent('seven')
var c1 = new Children('c1')
var c2 = new Children('c2')
c1.hasOwnProperty('name') // true
c1.getName() // c1
c1.skill.push('swimming') // ok
c1.skill // ["cook", "clean", "run", "swimming"]
c2.skill // ["cook", "clean", "run"]
複製代碼
看起來彷佛沒有問題,可是它卻調用了2次構造函數,一次在子類構造函數內,另外一次是將子類的原型指向父類構造的實例,致使生成了2次name和skill,只不過實例屏蔽了原型上的。雖然達成了目的,卻不是咱們最想要的。
這個問題將在寄生組合式繼承裏獲得解決。
這種方式下子類和父類共享一個原型。
function Parent(){}
Parent.prototype.skill = ['cook']
function Children(name, age){
this.name = name;
this.age = age;
}
Children.prototype = Parent.prototype
var c1 = new Children("c1", 20)
var c2 = new Children("c2", 24)
c1.skill.push("run")
c1.skill // ["cook", "run"]
複製代碼
簡單
這種繼承方式廣泛用於基於當前已有對象建立新對象,在ES5以前實現方法:
function object(o){
function F(){}
F.prototype = o
return new F()
}
var obj = {
name: 'seven'
}
var o1 = object(obj)
obj.name // 'seven'
複製代碼
看完這段代碼,是否是以爲和上文Object.create
的 polyfill 雷同?是的,Object.create
的確ES5爲了規範原型式繼承。
寄生則是結合原型式繼承和工廠模式,將建立的邏輯進行封裝,邏輯上與原型式繼承沒有什麼區別。
function create(o){
var f = object(o);
f.getSkill = function () {
return this.skill;//一樣,會共享引用
};
return f;
}
var obj = {
name: 'seven',
skill: ['cook','clean','run']
}
var c1 = create(obj);
c1.name // 'seven'
複製代碼
簡單而言,寄生式繼承就是不用實例化父類了,直接實例化一個臨時副本實現了相同的原型鏈繼承。
沒啥優勢
原型式繼承有的缺點它都有,對此我很疑惑爲何外面裝個殼就是另外一種繼承模式了。
顧名思義,寄生式+組合(原型繼承+借用構造函數)式繼承。總結了上面的幾種方式,相信你已經明白了怎麼去實現一個寄生組合式繼承。
關於在 Babel loose模式下 inherit 的實現方法:
"use strict";
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
複製代碼
而在正常模式下
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
複製代碼
大同小異,都是子類的原型繼承自父類的原型,申明一個用於繼承原型的 inheritPrototype
方法,經過這個方法咱們可以將子類的原型指向超類的原型,從而避免超類二次實例化。
function object(o){
function F(){}
F.prototype = o
return new F()
}
function inheritPrototype(subType, suberType) {
var prototype = object(suberType.prototype); // 建立副本
prototype.constructor = subType; // 指定構造函數
subType.prototype = prototype; // 指定原型對象
}
function Parent(name){
this.name = name;
this.skill = ['cook', 'clean', 'run']
}
Parent.prototype.getSkill = function(){
return this.name
}
function Children(name){
Parent.call(this, name);
this.age = 24
}
inheritPrototype(Children, Parent)
var c1 = new Children('c1')
var c2 = new Children('c2')
複製代碼
能夠更簡短一些,inheritPrototype
原理上就是 Object.create
的實現。
function Parent(name){
this.name = name;
this.skill = ['cook', 'clean', 'run']
}
Parent.prototype.getSkill = function(){
return this.getSkill
}
function Children(name){
Parent.call(this, name);
this.age = 24
}
Children.prototype = Object.create(Parent.prototype)
Children.prototype.constructor = Children;
// 測試
var c1 = new Children('c1')
var c2 = new Children('c2')
console.log(c1 instanceof Children) // true
console.log(c1 instanceof Parent) // true
console.log(c1.constructor) // Children
console.log(Children.prototype.__proto__ === Parent.prototype) // true
console.log(Parent.prototype.__proto__ === Object.prototype) // true
c1.skill.push('swimming') // ok
c1.getSkill() // ["cook", "clean", "run", "swimming"]
c2.getSkill() // ["cook", "clean", "run"]
複製代碼
這也是目前最完美的繼承方案,也是以爲它與ES6的class的實現方式最爲接近。
堪稱完美
代碼多
ES6中,經過class關鍵字來定義類,子類能夠經過extends繼承父類。
class Parent{
constructor(name){
this.name = name;
this.skill = ['cook', 'clean', 'run']
}
getSkill(){
return this.skill
}
static getCurrent(){
console.log(this)
}
}
class Children extends Parent{
constructor(name){
super(name)
}
}
var c1 = new Children('c1')
var c2 = new Children('c2')
console.log(c1 instanceof Children) // true
console.log(c1 instanceof Parent) // true
複製代碼
constructor
爲構造函數,即便未定義也會自動建立。constructor,getSkill
都是原型方法。static
關鍵字定義的靜態方法都必須經過類名調用,其this指向調用者而並不是實例。extends
能夠繼承父類的全部原型屬性及 static
類方法,子類 constructor
調用 super
父類構造函數實現實例屬性和方法的繼承。最後咱們看下經過babel編譯後的代碼,也不是那麼難以理解了。
"use strict";
// loose模式相對比normal模式更易於理解。
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
var Parent = function () {
function Parent(name) {
this.name = name;
this.skill = ['cook', 'clean', 'run'];
}
var _proto = Parent.prototype;
_proto.getSkill = function getSkill() {
return this.skill;
};
Parent.getCurrent = function getCurrent() {
console.log(this);
};
return Parent;
}();
var Children = function (_Parent) {
_inheritsLoose(Children, _Parent);
function Children(name) {
return _Parent.call(this, name) || this;
}
return Children;
}(Parent);
複製代碼
文中爲了節約時間參考了一些文章,若是有哪寫的不對或者有更好的方式,請留言告訴我,洗耳恭聽。
本文連接:我的博客