封裝
繼承
多肽
原型鏈是面向對象的基礎,是很是重要的部分。有如下幾種知識:
var obj1 = {name:"江小白"} var obj2 = new Object(name:"江小白")
上面的兩種寫法,效果是同樣的。由於,第一種寫法,obj11會指向Object。
var M = function(name){ this.name = name; } var obj3 = new M("asd asd)
var p = {name:'lipiao'} var obj3 = Object.create(p); // 此方法建立的對象是原型鏈對象
第三種方法,這種方式裏,obj3是實例,p是obj3的原型(name是p原型裏的屬性),構造函數是Objecet 。
- 構造函數經過 new 生成實例
- 構造函數也是函數,構造函數的prototype指向原型。(全部的函數有prototype屬性,但實例沒有 prototype屬性)
- 原型對象中有 constructor,指向該原型的構造函數
- 實例的__proto__指向原型。也就是說,Foo.__proto__ === Foo.prototype。
聲明:全部的引用類型(數組、對象、函數)都有__proto__這個屬性。
Foo.__proto__ === Function.prototype的結果爲true,說明Foo這個普通的函數,是Function構造函數的一個實例。es6
原型鏈的基本原理:任何一個實例,經過原型鏈,找到它上面的原型,該原型對象中的方法和屬性,能夠被全部的原型實例共享。數組
Object對象是原型鏈的頂端。
原型能夠起到繼承的做用。原型裏的方法均可以被不一樣的實例共享:app
//給Foo的原型添加 say 函數 Foo.prototype.say = function () { console.log(''); }
原型鏈的關鍵:在訪問一個實例的時候,若是實例自己沒找到此方法或屬性,就往原型上找。若是仍是找不到,繼續往上一級的原型上找。函數
instanceof的做用:用於判斷實例屬於哪一個構造函數。
instanceof的原理:判斷實例對象的__proto__屬性,和構造函數的prototype屬性,是否爲同一個引用(是否指向同一個地址)。this
注意1:雖說,實例是由構造函數 new 出來的,可是實例的__proto__屬性引用的是構造函數的prototype。也就是說,實例的__proto__屬性與構造函數自己無關。 注意2:在原型鏈上,原型的上面可能還會有原型,以此類推往上走,繼續找__proto__屬性。這條鏈上若是能找到, instanceof 的返回結果也是 true。
好比說:es5
foo instance of Foo的結果爲true,由於foo.__proto__ === M.prototype爲true。
foo instance of Objecet的結果也爲true,爲Foo.prototype.__proto__ === Object.prototype爲true。
但咱們不能輕易的說:foo 必定是 由Object建立的實例`。這句話是錯誤的。咱們來看下一個問題就明白了。spa
問題:已知A繼承了B,B繼承了C。怎麼判斷 a 是由A直接生成的實例,仍是B直接生成的實例呢?仍是C直接生成的實例呢?
分析:這就要用到原型的constructor屬性了。prototype
foo.__proto__.constructor === M的結果爲true,可是 foo.__proto__.constructor === Object的結果爲false。
因此,用 consturctor判斷就比用 instanceof判斷,更爲嚴謹。
當new一個對象是發生了什麼
function Animal(){ this.name="xiaoqi";//經過this,代表這是一個構造函數 }
class Animal{ constructor(){//能夠在構造函數裏寫屬性 this.name = name; } }
類的實例化很簡單,直接 new 出來便可。code
new Animal();
繼承的本質是原型鏈,
繼承是面嚮對象語言的基礎概念,通常面嚮對象語言支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。ECMAScript中函數沒有簽名,所以沒法實現接口繼承。ECMAScript只支持實現繼承,而其實現繼承主要是靠原型鏈來實現。
原理:讓一個引用類型繼承另外一個引用類型的方法和屬性
具體實現以下:對象
// 父類 function Parent(){ this.name = "xiaoqi"; this.children = ["zhuzhu","chouzhu"] } Parent.prototype.getChildren = function(){ console.log(this.childen); } // 子類 function Child(){ } Child.prototype = new Parent() var child1 = new Child(); child1.children.push("hanhanzhu") console.log(child1.getChildren())// Array ["zhuzhu", "chouzhu", "hanhanzhu"] var child2 = new Child(); console.log(child2.getChildren())// Array ["hanhan", "chouzhu"]
優勢
缺點
原理:使用apply()和call()方法以新對象爲上下文執行構造函數,子類構造函數內部調用超類構造函數
具體實現以下:
//盜用構造函數 // 父類 function Parent(name){ this.name = name; this.colors = ["red","yellow"]; this.getName = function(){ return this.name; } } // 子類 function Child(name){ Parent.call(this,name); } var child1 = new Child("xiaoqi"); child1.colors.push("xiaopiao"); console.log(child.colors);//["red","yellow","xiaopiao"] var child2 = new Child("xiaopiao"); child2.colors.push("xiaoqi") console.log(child2.colors)//["red","yellow","xiaoqi"]
優勢
建立子類實例時,能夠向父類傳遞參數
能夠實現多繼承(call多個父類對象)
缺點
原理:經過借用構造函數實現對實例屬性的繼承。這樣,既可以保證可以經過原型定義的方法實現函數複用,又可以保證每一個實例有本身的屬性
具體實現以下:
// 組合繼承(原型鏈+借用構造函數的組合繼承) function Parent(name,age){ this.name= name; this.age = age; this.colors = ['red','green'] console.log("parent") } Parent.prototype.getColors = function(){ console.log(this.colors); } // 子類 function Child(name,age,grade){ Parent.call(this,name,age)//建立子類實列會折行一次 this.grade = grade; } Child.prototype = new Parent();//指定子類原型會執行一次 Child.prototype.constructor = Child;//校訂構造函數 Child.prototype.getName = function(){ console.log(this.name) } var c = new Child("xiaoqi",88,99); console.log(c.getName()); // 輸出:「Parent」,"Parent","xiaoqi"
優勢
缺點
原理:藉助原型能夠基於已有的對象建立新對象,同時還不比所以建立自定義類型
具體實現以下:
function object(o){ function F(){}; F.prototype = o; return new F(); }
在object()函數內部,先建立了一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回這個臨時類型的一個新實例。本質上object()就是完成了一次淺複製操做
var person ={ name:"xiaoqi", friends:["piaopiao","xiaopiao"] } var p1= object(person); p1.name="xiaopiao" p1.friends.push("heihei") var p2=object(person); p2.name = "xiaoqi" p2.friends.push("haha") console.log(p1.name) console.log(person.friends) //["piaopiao","xiaoxiao","heihei","haha"]
ECMAScript5經過新增Object.create()方法規範化了原型式繼承,這個方法接收兩個參數:一個用做新對象原型的對象和爲新對象定義屬性的對象
var person ={ name:"xiaoqi", friends:["piaopiao","xiaopiao"] } var p1= Object.create(person); p1.name="xiaopiao" p1.friends.push("heihei") var p2=Object.create(person); p2.name = "xiaoqi" p2.friends.push("haha") console.log(p1.name) console.log(person.friends) //["piaopiao","xiaoxiao","heihei","haha"]
寄生式繼承是與原型式繼承緊密相關的一種思路,即建立一個僅用於封裝繼承函數過程的函數,該函數在內部以某種方式來加強對象,最後返回對象
具體實現以下:
function object(obj) { function F(){}; F.prototype = obj; return new F(); } function createAnother(original) { var clone = object(original); // 建立新對象 clone.sayHi = function(){ console.log('hello, world'); // 加強對象,添加屬性或法,這裏致使方法難以複用問題 } return clone; // 返回新對象 } var person = { name: 'alice', friends: ['Sherly', 'Taissy', 'Vant'] } var p1 = createAnother(person); p1.sayHi(); > "hello, world"
合繼承是 JavaScript最經常使用的繼承模式,其最大的問題是無論在什麼狀況下都會調用兩次超類構造函數:一次是在建立子類原型時,一次是在子類型構造函數內部。子類型最終會包含超類的所有實例屬性。
所謂寄生組合式繼承,即經過構造函數來繼承屬性,經過原型鏈繼承方法,背後的基本思路是:沒必要爲了指定子類的原型而調用超類的構造函數,咱們所須要的無非就是超類原型的一個副本而已
具體實現以下:
function Parent(name,age){ this.name = name; this.age = age; console.log('parent') } Parent.prototype.getName = function(){ return this.name; } function Child(name,age,grade){ Parent.call(this,name,age); this.grade = grade; } // 寄生組合的方式 // 複製父類的原型對象 function create(original){ function F(){}; F.prototype = original; return new F(); } //建立父類的原型副本,改變子類的原型,同時糾正構造函數 function inherit(subClass,superClass){ var parent = create(superClass.prototype); parent.constructor = subClass; subClass.prototype = parent; } inherit(Child,Parent); var child = new Child("xiaoqi",99,99) // ‘parent’
寄生組合繼承的高效率在於它只調用了一次超類構造函數,同時還可以保持原型鏈不變,可以正常使用 instanceof 和 isPrototypeOf() 寄生組合繼承被廣泛認爲是引用類型最理想的繼承方式
寄生組合式繼承可以很完美地實現繼承,但也不是沒有缺點。inherit() 方法中複製了父類的原型,賦給子類,假如子類原型上有自定的方法,也會被覆蓋,所以能夠經過Object.defineProperty的方式,將子類原型上定義的屬性或方法添加到複製的原型對象上,如此,既能夠保留子類的原型對象的完整性,又可以複製父類原型
function Parent(name, age){ this.name = name; this.age = age; } Parent.prototype.getName = function(){ console.log(this.name) } function Child(name, age, grade){ Parent.call(this, name, age); this.grade = grade; } function inherit(child, parent){ let obj = parent.prototype; obj.constructor = child; for(let key in child.prototype){ Object.defineProperty(obj, key, { value: child.prototype[key] }) } child.prototype = obj; } inherit(Child,Parent); var child = new Child("xiaoqi",99,99) // ‘parent’
S6中引入了class關鍵字,class能夠經過extends關鍵字實現繼承,還能夠經過static關鍵字定義類的靜態方法,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。
ES5 的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修改this。
class Person { //調用類的構造方法 constructor(name, age) { this.name = name this.age = age } //定義通常的方法 showName() { console.log("調用父類的方法") console.log(this.name, this.age); } } let p1 = new Person('kobe', 39) console.log(p1) //定義一個子類 class Student extends Person { constructor(name, age, salary) { super(name, age)//經過super調用父類的構造方法 this.salary = salary } showName() {//在子類自身定義方法 console.log("調用子類的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Student('wade', 38, 1000000000) console.log(s1) s1.showName()
繼承方式 | 優勢 | 缺陷 |
---|---|---|
原型鏈繼承 | 可以實現函數複用 | 1.引用類型的屬性被全部實例共享;2.建立子類時不能向超類傳參 |
借用構造函數 | 1. 避免了引用類型的屬性被全部實例共享; 2. 能夠在子類中向超類傳參 | 方法都在構造函數中定義了,每次建立實例都會建立一遍方法,沒法實現函數複用 |
組合繼承 | 融合了原型鏈繼承和構造函數的優勢,是Javascript中最經常使用的繼承模式 | 建立子類會調用兩次超類的構造函數 |
原型繼承 | 在沒有必要興師動衆地建立構造函數,而只是想讓一個對象與另外一個對象保持相似的狀況下,原型式繼承徹底能夠勝任 | 引用類型的屬性會被全部實例共享 |
寄生式繼承 | 能夠加強對象 | 使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用形成效率下降,這一點與構造函數模式相似;同時存在引用類型的屬性被全部實例共享的缺陷 |
寄生組合繼承 | 使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用形成效率下降,這一點與構造函數模式相似;同時存在引用類型的屬性被全部實例共享的缺陷 |