javascirpt沒有"子類"和"父類"的概念,也沒有"類"(class)和"實例"(instance)的區分,全靠一種很奇特的"原型鏈"(prototype chain)模式,來實現繼承。繼承意味着複製操做,然而 JavaScript 默認並不會複製對象的屬性,相反,JavaScript 只是在兩個對象之間建立一個關聯,這樣,一個對象就能夠經過委託訪問另外一個對象的屬性和函數,因此與其叫繼承,委託的說法反而更準確些。javascript
constructor
屬性指向關聯的構造函數。function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); //true;
function Person() {
}
var person = new Person();
console.log(Person === Person.prototype.constructor); //true
//其實 person 中並無constructor 屬性,當不能讀取到constructor屬性時,會從 person 的原型也就是 Person.prototype中讀取,正好原型中有該屬性
console.log(person.constructor === Person); // true
複製代碼
JavaScript中全部的對象都是由它的原型對象繼承而來。而原型對象自身也是一個對象,它也有本身的原型對象,這樣層層上溯,就造成了一個相似鏈表的結構,這就是原型鏈(prototype chain)。全部原型鏈的終點都是Object函數的prototype屬性,由於在JavaScript中的對象都默認由Object()構造。Objec.prototype指向的原型對象一樣擁有原型,不過它的原型是null,而null則沒有原型。java
function Super(){
this.val = 1;
this.arr = [1];
}
function Sub(){
// ...
}
Sub.prototype = new Super(); // 核心
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
alert(sub1.val); // 2
alert(sub2.val); // 1
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1, 2
// 優勢: 簡單,易於實現
// 缺點: 1. 修改sub1.arr後sub2.arr也變了,由於來自原型對象的引用屬性是全部實例共享的。 2. 建立子類實例時,沒法向父類構造函數傳參
複製代碼
function Super(val){
this.val = val;
this.arr = [1];
this.fun = function(){
// ...
}
}
function Sub(val){
Super.call(this, val); // 核心
// ...
}
var sub1 = new Sub(1);
var sub2 = new Sub(2);
sub1.arr.push(2);
alert(sub1.val); // 1
alert(sub2.val); // 2
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1
alert(sub1.fun === sub2.fun); // false
// 優勢: 1. 解決了子類實例共享父類引用屬性的問題 2. 建立子類實例時,能夠向父類構造函數傳參
// 缺點: 沒法實現函數複用,每一個子類實例都持有一個新的fun函數,太多了就會影響性能,內存爆炸。。
複製代碼
// 經過Super.call(this);繼承父類的基本屬性和引用屬性並保留能傳參的優勢;經過Sub.prototype = new Super();繼承父類函數,實現函數複用
function Super(){
// 只在此處聲明基本屬性和引用屬性
this.val = 1;
this.arr = [1];
}
// 在此處聲明函數
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
Sub.prototype = new Super(); // 核心
var sub1 = new Sub(1);
var sub2 = new Sub(2);
alert(sub1.fun === sub2.fun); // true
// 優勢: 1. 不存在引用屬性共享問題 2. 可傳參 3. 函數可複用
// 缺點:(一點小瑕疵)子類原型上有一份多餘的父類實例屬性,由於父類構造函數被調用了兩次,生成了兩份,而子類實例上的那一份屏蔽了子類原型上的。。。又是內存浪費,比剛纔狀況好點,不過確實是瑕疵
複製代碼
function beget(obj){ // 生孩子函數 beget:龍beget龍,鳳beget鳳。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
// 只在此處聲明基本屬性和引用屬性
this.val = 1;
this.arr = [1];
}
// 在此處聲明函數
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
// 用beget(Super.prototype);切掉了原型對象上多餘的那份父類實例屬性
var proto = beget(Super.prototype); // 核心
proto.constructor = Sub; // 核心
Sub.prototype = proto; // 核心
var sub = new Sub();
alert(sub.val);
alert(sub.arr);
複製代碼
function beget(obj){ // 生孩子函數 beget:龍beget龍,鳳beget鳳。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
// 拿到父類對象
var sup = new Super();
// 生孩子
var sub = beget(sup); // 核心
// 加強
sub.attr1 = 1;
sub.attr2 = 2;
//sub.attr3...
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
//ES5提供了Object.create()函數,內部就是原型式繼承,IE9+支持
// 優勢:從已有對象衍生新對象,不須要建立自定義類型(更像是對象複製,而不是繼承。。
// 缺點: 1. 原型引用屬性會被全部實例共享,由於是用整個父類對象來充當了子類原型對象,因此這個缺陷無可避免 2. 沒法實現代碼複用(新對象是現取的,屬性是現添的,都沒用函數封裝,怎麼複用)
複製代碼
// 有缺陷的寄生式繼承 + 不完美的組合繼承 = 完美的寄生組合式繼承
function beget(obj){ // 生孩子函數 beget:龍beget龍,鳳beget鳳。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
function getSubObject(obj){
// 建立新對象
var clone = beget(obj); // 核心
// 加強
clone.attr1 = 1;
clone.attr2 = 2;
//clone.attr3...
return clone;
}
var sub = getSubObject(new Super());
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
// 優勢: 仍是不須要建立自定義類型
// 缺點: 沒法實現函數複用(沒用到原型,固然不行)
複製代碼