JavaScript面向?qū)ο蟪绦蛟O(shè)計
本文會碰到的知識點:
原型滩报、原型鏈、函數(shù)對象、普通對象吮螺、繼承
讀完本文饶囚,可以學(xué)到
- 面向?qū)ο蟮幕靖拍?/li>
- JavaScript對象屬性
- 理解JavaScript中的函數(shù)對象與普通對象
- 理解
prototype
和proto
- 理解原型和原型鏈
- 詳解原型鏈相關(guān)的Object方法
- 了解如何用ES5模擬類帕翻,以及各種方式的優(yōu)缺點
- 了解如何用ES6實現(xiàn)面向?qū)ο?/li>
一鸠补、面向?qū)ο蟮幕靖拍?/h1>
面向?qū)ο笠布词荗OP,Object Oriented Programming嘀掸,是計算機(jī)的一種編程架構(gòu)紫岩,OOP的基本原則是計算機(jī)是由子程序作用的單個或者多個對象組合而成,包含屬性和方法的對象是類的實例睬塌,但是JavaScript中沒有類的概念泉蝌,而是直接使用對象來實現(xiàn)編程。
特性:
- 封裝:能夠?qū)⒁粋€實體的信息揩晴、功能勋陪、響應(yīng)都封裝到一個單獨對象中的特性。
由于JavaScript沒有public
硫兰、private
诅愚、protected
這些關(guān)鍵字,但是可以利用變量的作用域來模擬public和private封裝特性
var insObject = (function() {
var _name = 'hello'; // private
return {
getName: function() { // public
return _name;
}
}
})();
insObject._name; // undefined
insObject.getName(); // hello
這里只是實現(xiàn)了一個簡單的版本劫映,private比較好的實現(xiàn)方式可以參考深入理解ES6 145頁
,protected可以利用ES6的Symbol關(guān)鍵字來實現(xiàn)违孝,這里不展開,有興趣可以討論
繼承:在不改變源程序的基礎(chǔ)上進(jìn)行擴(kuò)充泳赋,原功能得以保存雌桑,并且對子程序進(jìn)行擴(kuò)展,避免重復(fù)代碼編寫祖今,后面的章節(jié)詳細(xì)描述
二校坑、JavaScript對象屬性
想弄懂面向?qū)ο螅遣皇窍瓤纯磳ο笫巧赌兀?br> 我們先看一個題目:
[] + {}; // "[object Object]"
{} + []; // 0
解釋:
在第一行中衅鹿,{}
出現(xiàn)在+
操作符的表達(dá)式中撒踪,因此被翻譯為一個實際的值(一個空object
)。而[]
被強(qiáng)制轉(zhuǎn)換為"",因此{}
也會被強(qiáng)制轉(zhuǎn)換為一個string:"[object Object]"
大渤。
但在第二行中制妄,{}
被翻譯為一個獨立的{}
空代碼塊兒(它什么也不做)。塊兒不需要分號來終結(jié)它們泵三,所以這里缺少分號不是一個問題耕捞。最終,+ []
是一個將[]
明確強(qiáng)制轉(zhuǎn)換 為number
的表達(dá)式烫幕,而它的值是0
俺抽。
2.1 屬性
對象的屬性
-
Object.prototype
Object 的原型對象,不是每個對象都有prototype
屬性 -
Object.prototype.proto
不是標(biāo)準(zhǔn)方法较曼,不鼓勵使用磷斧,每個對象都有proto
屬性,但是由于瀏覽器實現(xiàn)方式的不同,proto
屬性在chrome弛饭、firefox中實現(xiàn)了冕末,在IE中并不支持,替代的方法是Object.getPrototypeOf()
-
Object.prototype.constructor
:用于創(chuàng)建一個對象的原型侣颂,創(chuàng)建對象的構(gòu)造函數(shù)
可能大家會有一個疑問档桃,為什么上面那些屬性要加上prototype
在chrome中打印一下var a = { test: 'test' }
屬性描述符
數(shù)據(jù)屬性:
特性名稱 | 描述 | 默認(rèn)值 |
---|---|---|
value | 屬性的值 | undfined |
writable | 是否可以修改屬性的值,true表示可以憔晒,false表示不可以 | true |
enumerable | 屬性值是否可枚舉藻肄,true表示可枚舉for-in, false表示不可枚舉 | true |
configurable | 屬性的特性是否可配置,表示能否通過delete刪除屬性后重新定義屬性 | true |
例子:
[圖片上傳失敗...(image-7ebf49-1539227558825)]
訪問器屬性:
特性名稱 | 描述 | 默認(rèn)值 |
---|---|---|
set | 設(shè)置屬性時調(diào)用的函數(shù) | undefined |
get | 寫入屬性時調(diào)用的函數(shù) | undefined |
configurable | 表示能否通過delete刪除屬性后重新定義屬性 | true |
enumerable | 表示能否通過for-in循環(huán)返回屬性 | true |
訪問器屬性不能直接定義拒担,一般是通過Object.defineProperty()
方法來定義嘹屯,但是這個方法只支持IE9+, 以前一般用兩個非標(biāo)準(zhǔn)方法來實現(xiàn)__defineGetter__()
和?__defineSetter__()
例子:
var book = { _year: 2004, edition: 1 };
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004){
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition);
2.2 方法
-
Object.prototype.toString()
返回對象的字符串表示 -
Object.prototype.hasOwnProperty()
返回一個布爾值从撼,表示某個對象是否含有指定的屬性抚垄,而且此屬性非原型鏈繼承,也就是說不會檢查原型鏈上的屬性 -
Object.prototype.isPrototypeOf()
返回一個布爾值谋逻,表示指定的對象是否在本對象的原型鏈中 -
Object.prototype.propertyIsEnumerable()
判斷指定屬性是否可枚舉 -
Object.prototype.watch()
給對象的某個屬性增加監(jiān)聽 -
Object.prototype.unwatch()
移除對象某個屬性的監(jiān)聽 -
Object.prototype.valueOf()
返回指定對象的原始值 - 獲取和設(shè)置屬性
-
Object.defineProperty
定義單個屬性 -
Object.defineProperties
定義多個屬性 -
Object.getOwnPropertyDescriptor
獲取屬性
-
-
Object.assign()
拷貝可枚舉屬性 (ES6新增) -
Object.create()
創(chuàng)建對象 -
Object.entries()
返回一個包含由給定對象所有可枚舉屬性的屬性名和屬性值組成的 [屬性名呆馁,屬性值] 鍵值對的數(shù)組,數(shù)組中鍵值對的排列順序和使用for…in循環(huán)遍歷該對象時返回的順序一致 -
Object.freeze()
凍結(jié)一個對象毁兆,凍結(jié)指的是不能向這個對象添加新的屬性浙滤,不能修改其已有屬性的值,不能刪除已有屬性气堕,以及不能修改該對象已有屬性的可枚舉性纺腊、可配置性、可寫性茎芭。也就是說揖膜,這個對象永遠(yuǎn)是不可變的。該方法返回被凍結(jié)的對象 -
Object.getOwnPropertyNames()
返回指定對象的屬性名組成的數(shù)組 -
Object.getPrototypeOf
返回該對象的原型 -
Object.is(value1, value2)
判斷兩個值是否是同一個值 (ES6 新增) -
Object.keys()
返回一個由給定對象的所有可枚舉自身屬性的屬性名組成的數(shù)組梅桩,數(shù)組中屬性名的排列順序和使用for-in循環(huán)遍歷該對象時返回的順序一致 -
Object.setPrototypeOf(obj, prototype)
將一個指定的對象的原型設(shè)置為另一個對象或者null -
Object.values
返回一個包含指定對象所有的可枚舉屬性值的數(shù)組壹粟,數(shù)組中的值順序和使用for…in循環(huán)遍歷的順序一樣
2.3 應(yīng)用
如何檢測某個屬性是否在對象中?
- in運算符宿百,判斷對象是否包含某個屬性趁仙,會從對象的實例屬性、繼承屬性里進(jìn)行檢測
function Dogs(name) {
this.name = name
}
function BigDogs(size) {
this.size = size;
}
BigDogs.prototype = new Dogs();
var a = new BigDogs('big');
'size' in a;
'name' in a;
'age' in a;
- Object.hasOwnProperty()垦页,判斷一個對象是否有指定名稱的屬性雀费,不會檢查繼承屬性
a.hasOwnProperty('size');
a.hasOwnProperty('name');
a.hasOwnProperty('age');
-
Object.propertyIsEnumerable()
,判斷指定名稱的屬性是否為實例屬性并且是可枚舉的
// es6
var a = Object.create({}, {
name: {
value: 'hello',
enumerable: true,
},
age: {
value: 11,
enumerable: false,
}
});
// es5
var b = {};
Object.defineProperties(b, {
name: {
value: 'hello',
enumerable: true,
},
age: {
value: 11,
enumerable: false,
}
});
a.propertyIsEnumerable('name');
a.propertyIsEnumerable('age');
- 如何枚舉對象的屬性痊焊,并保證不同了瀏覽器中的行為是一致的盏袄?
for/in
語句忿峻,可以遍歷可枚舉的實例屬性和繼承屬性
var a = {
supername: 'super hello',
superage: 'super name',
}
var b = {};
Object.defineProperties(b, {
name: {
value: 'hello',
enumerable: true,
},
age: {
value: 11,
enumerable: false,
}
});
Object.setPrototypeOf(b, a); // 設(shè)置b的原型是a 等效的是b.__proto__ = a
for(pro in b) {
console.log(pro); // name, supername, superage
}
-
Object.keys()
, 返回一個數(shù)組辕羽,內(nèi)容是對象可枚舉的實例屬性名稱
var propertyArray = Object.keys(b); // name
-
Object.getOwnPropertyNames()
炭菌,返回一個數(shù)組,內(nèi)容是對象所有實例屬性逛漫,包括可枚舉和不可枚舉
var propertyArray = Object.getOwnPropertyNames(b); // name, age
- 如何判斷兩個對象是否相等?
我只想說赘艳,這個問題說簡單很簡單酌毡,說復(fù)雜也挺復(fù)雜的傳送門
我們看個簡單版的
function isEquivalent(a, b) {
var aProps = Object.getOwnPropertyNames(a);
var bProps = Object.getOwnPropertyNames(b);
if (aProps.length != bProps.length){
return false;
}
for (var i = 0; i < aProps.length; i++) {
var propName = aProps[i];
if (a[propName] !== b[propName]) {
return false;
}
}
return true;
}
// Outputs: true
console.log(isEquivalent({a:1},{a:1}));
上面這個函數(shù)還有啥問題呢
- 沒有對傳入?yún)?shù)進(jìn)行校驗,例如判斷是否是NaN蕾管,或者是其他內(nèi)置屬性
- 沒有判斷傳入對象的construct和prototype
- 時間算法復(fù)雜度是O(n2)
有同學(xué)可能會有疑問枷踏,能不能用Object.is
,答案是否定的掰曾,Object.is
簡單來說就是在===
的基礎(chǔ)上特別處理了NaN
旭蠕,+0
,-0
旷坦,保證了-0
和+0
不相同掏熬,Object.is(NaN, NaN)
返回true
。
- 對象的深拷貝和淺拷貝
其實如果大家理解了上面的那些方法秒梅,是很容易寫出深拷貝和淺拷貝的代碼的旗芬,我們先看一下這兩者的卻別。
淺拷貝僅僅是復(fù)制引用捆蜀,拷貝后a === b
疮丛, 注意Object.assign
方法實現(xiàn)的是淺復(fù)制(此處有深刻教訓(xùn)!A舅L鼙 )
深拷貝這是創(chuàng)建了一個新的對象,然后把舊的對象中的屬性和方法拷貝到新的對象中锰茉,拷貝后 a !== b
深拷貝的實現(xiàn)由很多例子呢蔫,例如jQuery
的extend
和lodash中的cloneDeep
, clone。jQuery可以使用$.extend(true, {}, ...)
來實現(xiàn)深拷貝, 但是jQuery無法復(fù)制JSON對象之外的對象飒筑,例如ES6引入的Map
咐刨、Set
等。而lodash加入的大量的代碼來實現(xiàn)ES6新引入的標(biāo)準(zhǔn)對象
三扬霜、對象分為函數(shù)對象和普通對象
** 什么是函數(shù)對象和普通對象定鸟?**
Object
、Function
著瓶、Array
联予、Date
等js的內(nèi)置對象都是函數(shù)對象
function a1 () {}
const a2 = function () {}
const a3 = new Function();
const b1 = {};
const b2 = new Object();
const c1 = [];
const c2 = new Array();
const d1 = new a1();
const d2 = new b1(); // ????
const d3 = new c1(); // ????
typeof a1;
typeof a2;
typeof a3;
typeof b1;
typeof b2;
typeof c1;
typeof c2;
typeof d1;
上面兩行報錯的原因,是因為構(gòu)造函數(shù)只能由函數(shù)來充當(dāng),而b1和c1不是Function的實例沸久,所以不能充當(dāng)構(gòu)造器
** 但是只有Function的實例都是函數(shù)對象季眷、其他的實例都是普通對象 **
我們延伸一下,在看個例子
const e1 = function *(){};
const e2 = new e1();
// Uncaught TypeError: e1 is not a constructor
console.log(e1.constructor) // 是有值的卷胯。子刮。。
// 規(guī)范里面就不能new
const e2 = e1();
GeneratorFunction
是一個特殊的函數(shù)對象
e1.__proto__.__proto__ === Function.prototype
e1
的原型實際上是一個生成器函數(shù)GeneratorFunction
窑睁,也就是說
e1.__proto__ === GeneratorFunction.prototype
這行代碼有問題么挺峡,啊哈哈哈,GeneratorFunction
這個關(guān)鍵字主流的JavaScript還木有暴露出來担钮,所以這個大家理解就好啦
雖然不能直接new e1
但是可以new e1.constructor();
哈哈哈哈
四橱赠、理解prototype和proto
| 對象類型 | prototype | proto |
| - | - |
| 函數(shù)對象 | Yes | Yes |
| 普通對象 | No | Yes |
- 只有函數(shù)對象具有
prototype
這個屬性 -
prototype
和__proto__
都是js在定義一個對象時的預(yù)定義屬性 -
prototype
是被實例的__proto__
指向 -
__proto__
指向構(gòu)造函數(shù)的prototype
const a = function(){}
const b = {}
typeof a // function
typeof b // object
typeof a.prototype // object
typeof a.__proto__ // function
typeof b.prototype // undefined
typeof b.__proto__ // object
a.__proto__ === Function.prototype
b.__proto__ === Object.prototype
理解了prototype
和__proto__
之后,我們來看看之前一直說的為什么JavaScript里面都是對象
const a = {}
const b = function () {}
const c = []
const d = new Date()
a.__proto__
a.__proto__ === Object.prototype
b.__proto__
b.__proto__ === Function.prototype
c.__proto__
c.__proto__ === Array.prototype
d.__proto__
d.__proto__ === Date.prototype
Object.prototype.__proto__ //null
Function.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype
Date.prototype.__proto__ === Object.prototype
延伸一個問題:如何判斷一個變量是否是數(shù)組箫津?
- typeof
我們上面已經(jīng)解釋了狭姨,這些都是普通對象,普通對象是沒有prototype
的苏遥,他們typeof
的值都是object
typeof []
typeof {}
從原型來看, 原理就是看Array是否在a的原型鏈中
a的原型鏈?zhǔn)?Array->Object
const a = [];
Array.prototype.isPrototypeOf(obj);
- instanceof
const a = [];
a instanceof Array
從構(gòu)造函數(shù)入手饼拍,但是這個方法和上面的方法都有一問題,不同的框架中創(chuàng)建的數(shù)組不會相互共享其prototype
屬性
根據(jù)對象的class屬性田炭,跨原型調(diào)用tostring
方法
const a = [];
Object.prototype.toString.call(a);
// [Object Array]
ES5 中所有內(nèi)置對象的[[Class]]屬性的值是由規(guī)范定義的惕耕,但是 ES6 中已經(jīng)沒有了[[Class]]屬性,取代它的是[[NativeBrand]]屬性诫肠,這個大家有興趣可以自行去查看規(guī)范
原理:
- 如果
this
的值為undefined
,則返回'[object Undefined]'
. - 如果
this
的值為null
,則返回[object Null]
. - 讓
O
成為調(diào)用ToObject(this)
的結(jié)果. - 讓
class
成為O
的內(nèi)部屬性[[Class]]
的值. - 返回三個字符串
'[object '
,'class'
, 以及']'
連接后的新字符串.
問題司澎?這個一定是正確的么?不正確為啥栋豫?
提示ES6的Symbol
屬性
Array.isArray()
部分瀏覽器中不兼容
五挤安、理解原型與原型鏈
其實上一節(jié)中的prototype
和proto
就是為了構(gòu)建原型鏈而存在的,之前也或多或少的說到了原型鏈這個概念丧鸯。
看下面的代碼:
const Dogs = function(name) {
this.name = name;
}
Dogs.prototype.getName = function() {
return this.name
}
const sijing = new Dogs('sijing');
console.log(sijing);
console.log(sijing.getName());
這段代碼的執(zhí)行過程
- 首先創(chuàng)建了一個構(gòu)造函數(shù)
Dogs
蛤铜,傳入一個參數(shù)name
,Dogs.prototype
也會自動創(chuàng)建 - 給對象
dogs
增加了一個方法 - 通過構(gòu)造函數(shù)
Dogs
實例化了一個對象sijing
- 輸出
sijing
的值,可以看到sijing
有兩個值name
和proto
,其中proto
指向Dogs.prototype
- 執(zhí)行
getName
方法時丛肢,在sijing
中找不到這個方法围肥,就會繼續(xù)向著原型鏈繼續(xù)往上找,也就是通過proto
蜂怎,然后就找到了getName
方法穆刻。
這個過程實際上就是原型繼承,實際上JavaScript的原型繼承就是利用了proto
并借助prototype
來實現(xiàn)的杠步。
sijing.__proto__ === Function.prototype
Dogs.prototype // 指向什么
Dogs.prototype.__proto__ // 指向什么
Dogs.prototype.__proto__.__proto__ // 指向什么
上面例子中getName
最終是查找到了氢伟,那么如果在原型鏈中一直沒查找到榜轿,會怎么樣?
例如console.log(sijing.age)
sijing // 是一個對象可以繼續(xù)
sijing.age // 不存在朵锣,繼續(xù)
sijing.__proto__ // 是一個對象可以繼續(xù)
sijing.__proto__.age // 不存在谬盐,繼續(xù)
sijing.__proto__.__proto__ // 是個對象可以繼續(xù)
sijing.__proto__.__proto__.age // 不存在,繼續(xù)
sijing.__proto__.__proto__.__proto__ null诚些,// 不是對象飞傀,到頭啦
原型鏈 的概念其實不重要,重要的是要理解诬烹,簡單來說砸烦,原型鏈就是利用原型讓一個引用類型繼承另一個應(yīng)用類型的屬性和方法。
還有三點需要注意的:
- 任何內(nèi)置函數(shù)對象(類)本身的
_proto_
都指向Function
的原型對象椅您; - 除了
Object
的原型對象的_proto_
指向null
,其他所有內(nèi)置函數(shù)對象的原型對象的_proto_
都指向object
寡键。 - 所有構(gòu)造函數(shù)的的
prototype
方法的proto
都指向Object.prototype
(除了….Object.prototype
自身)
如果理解了上面這些內(nèi)容掀泳,大家可以自行描述一下,構(gòu)造函數(shù)西轩、原型和實例之間的關(guān)系.
- 構(gòu)造函數(shù)首字母必須大寫员舵,用來區(qū)分普通函數(shù),內(nèi)部使用
this
指針藕畔,指向要生成的實例對象马僻,通過new
來生成實例對象。 - 實例就是通過
new
一個構(gòu)造函數(shù)產(chǎn)生的對象注服,它有一個屬性[[prototype]]
指向原型 - 原型中有一個屬性
[[constructor]]
韭邓,指向構(gòu)造函數(shù)
六、與原型鏈相關(guān)的方法
6.1 hasOwnProperty
Object.hasOwnProperty()
返回一個布爾值溶弟,表示某個對象的實例是否含有指定的屬性女淑,而且此屬性非原型鏈繼承。用來判斷屬性是來自實例屬性還是原型屬性辜御。類似還有in
操作符鸭你,in
操作符只要屬性存在,不管實在實例中還是原型中擒权,就會返回true
袱巨。同時使用in
和hasOwnProperty
就可以判斷屬性是在原型中還是在實例中
const Dogs = function (age) {
this.age = age
}
Dogs.prototype.getAge = function() {
return this.age;
}
const sijing = new Dogs(14);
sijing.hasOwnProperty('age');
6.2 isPrototypeOf
Object.prototype.isPrototypeOf()
返回一個布爾值,表示指定的對象是否在本對象的原型鏈中
const Dogs = function (age) {
this.age = age
}
Dogs.prototype.getAge = function() {
return this.age;
}
const sijing = new Dogs(11);
Object.prototype.isPrototypeOf(Dogs);
Dogs.prototype.isPrototypeOf(sijing);
6.3 getPrototypeOf
Object.getPrototypeOf
返回該對象的原型
const Dogs = function (age) {
this.age = age
}
Dogs.prototype.getAge = function() {
return this.age;
}
const sijing = new Dogs(11);
sijing.__proto__ === Object.getPrototypeOf(sijing)
七碳抄、ES5 對象繼承
7.1 原型繼承
原型繼承就是利用** 原型鏈 **來實現(xiàn)繼承
function SuperType() {
this.supername = 'super';
}
SuperType.prototype.getSuperName= function(){
return this.supername;
}
function SubType () {
this.subname='subname';
}
SubType.prototype = new SuperType();
SubType.prototype.getSubName = function (){
return this.subname;
}
var instance1 = new SubType();
console.log(instance1.getSubName());
console.log(instance1.getSuperName());
需要注意的地方:
實現(xiàn)原型繼承的時候不要使用對象字面量創(chuàng)建原型方法愉老,因為這樣做,會重寫原型鏈剖效。
function SuperType() {
this.supername = 'super';
}
SuperType.prototype.getSuperName= function(){
return this.supername;
}
function SubType () {
this.subname='subname';
}
SubType.prototype = new SuperType();
SubType.prototype = {
getSubName: function (){
return this.subname;
}
}
var instance1 = new SubType();
console.log(instance1.getSubName());
console.log(instance1.getSuperName()); // error
上面使用SubType.prototype = {...}
之后俺夕,SubType
的原型就是Object
了裳凸,而不是SuperType
了。
優(yōu)點:原型定義的屬性和方法可以復(fù)用
缺點:
- 引用類型的原型屬性會被所有實例共享
- 創(chuàng)建子對象時劝贸,不能向父對象的構(gòu)造函數(shù)中傳遞參數(shù)
7.2 構(gòu)造函數(shù)繼承
var a = {
name: 'a',
};
var name = 'window';
var getName = function(){
console.log(this.name);
}
getName() // window
getName.call(a) // a
執(zhí)行getName()
時姨谷,函數(shù)體的this
指向window
,而執(zhí)行getName.call(a)
時映九,函數(shù)體的this
指向的是a
對象梦湘,所以就可以理解啦。接下來我們看如何實現(xiàn)構(gòu)造函數(shù)繼承
function SuperType () {
this.colors = ['red', 'green'];
}
function SubType () {
// 繼承SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('blue');
console.log(instance1.colors); // red, green, blue
var instance2 = new SubType();
console.log(instance2.colors); // red, green
SuperType.call(this)
這一行代碼件甥,實際上意思是在SubType
的實例初始化過程中捌议,調(diào)用了SuperType
的構(gòu)造函數(shù),因此SubType
的每個實例都有colors
這個屬性
優(yōu)點:子對象可以傳遞參數(shù)給父對象引有。
function SuperType(name) {
this.name = name;
}
function SubType(name, age) {
name = name || 'hello';
SuperType.call(this, name);
this.age = age;
}
var instance1 = new SubType('scofield', 28);
console.log(instance1.name); //
console.log(instance1.age); //
需要注意的地方是在調(diào)用父對象的構(gòu)造函數(shù)之后瓣颅,再給子類型中的定義屬性,否則會被重寫譬正。
缺點:方法都需要在構(gòu)造函數(shù)中定義宫补,難以做到函數(shù)的復(fù)用,而且在父對象的原型上定義的方法曾我,對于子類型是不可見的粉怕。 ??? 為什么不可見
function SuperType(name) {
this.name = name;
}
SuperType.prototype.getName = function() {
return this.name;
}
SuperType.prototype.prefix = function() {
return 'prefix';
}
function SubType(name) {
SuperType.call(this, name);
}
var instance1 = new SubType('scofield');
console.log(instance1.name);
console.log(instance1.prefix);
console.log(instance1.getName()); // Uncaught TypeError: instance1.getName is not a function
7.3 組合式繼承
組合式繼承 顧名思義,就是組合兩種模式實現(xiàn)JavaScript的繼承抒巢,借助 原型鏈 和 構(gòu)造函數(shù) 來實現(xiàn)贫贝。這樣子在原型上定義方法實現(xiàn)了函數(shù)的復(fù)用,而且能夠保證每個實例都有自己的屬性蛉谜。
function SuperType (name) {
this.name = name;
this.con = [];
}
SuperType.prototype.getName = function() {
return this.name;
}
function SubType (name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.getAge = function() {
return this.age;
};
var instance1 = new SubType('li', 18);
instance1.con.push('test1');
console.log(instance1.con); // test1
console.log(instance1.getAge()); // 18
console.log(instance1.getName()); // li
var instance2 = new SubType('hang', 18);
console.log(instance1.con); // test1
console.log(instance1.getAge()); // 18
console.log(instance1.getName()); // hang
優(yōu)點:彌補了 原型繼承 和 構(gòu)造函數(shù) 的缺點
缺點:父類構(gòu)造函數(shù)調(diào)用了兩次
7.4 原型式繼承
原型式繼承并沒有使用嚴(yán)格意義上的構(gòu)造函數(shù)稚晚,借助原型可以基于已有的對象創(chuàng)建新的對象,例如:
function createObject(o) {
function newOrient () {};
newOrient.prototype = o;
return new newOrient();
}
簡單來說createObject
函數(shù)型诚,對傳入的o
對象進(jìn)行的一次淺拷貝蜈彼。在ES5中新增加了一個方法Object.create()
, 它的作用和createObject
是一樣的,但是只支持IE9+俺驶。
var Dogs = {
name: 'jingmao',
age: 1
}
var BigDogs = Object.create(Dogs);
BigDogs.name= 'bigjingmao';
BigDogs.size = 'big';
console.log(BigDogs.age);
其中Object.create
還支持傳入第二個參數(shù)幸逆,參數(shù)與Object.defineProperties()
方法的格式相同,并且會覆蓋原型上的同名屬性暮现。
7.5 寄生式繼承
寄生式繼承 其實和 原型式繼承 很類似还绘,區(qū)別在于,寄生式繼承 創(chuàng)建的一個函數(shù)把所有的事情做完了栖袋,例如給新的對象增加屬性和方法拍顷。
function createAnother(o) {
var clone = Object.create(o);
clone.size = 'big';
return clone;
}
var Dogs = {
name: 'jingmao',
age: 1
}
var BigDogs = createAnother(Dogs);
console.log(BigDogs.size);
7.6 寄生組合式繼承
到最后一個了,看看我們之前遺留的問題:
組合繼承 會調(diào)用兩次父對象的構(gòu)造函數(shù)塘幅,并且父類型的屬性存在兩組昔案,一組在實例上尿贫,一組在SubType的原型上。解決這個問題的方法就是 寄生組合式繼承踏揣。
function inheritPrototype(subType, superType){
// 繼承父類的原型
var prototype = Object.create(superType.prototype);
// 重寫被污染的construct
prototype.constructor = subType;
// 重寫子類的原型
subType.prototype = prototype;
}
這個函數(shù)就是 寄生組合式繼承 的最簡單的實現(xiàn)方式
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType('hello', 18);
instance1.__proto__.constructor == SubType
可以看到
- 子類繼承了父類的屬性和方法庆亡,同時屬性沒有創(chuàng)建在原型鏈上,因此多個子類不會共享同一個屬性捞稿。
- 子類可以動態(tài)傳遞參數(shù)給父類
- 父類構(gòu)造函數(shù)只執(zhí)行了一次
但是還有一個問題:
子類如果在原型上添加方法又谋,必須要在繼承之后添加,否則會覆蓋原來原型上的方法娱局。但是如果這兩個類是已存在的類彰亥,就不行了
優(yōu)化一下:
function inheritPrototype(subType, superType){
// 繼承父類的原型
var prototype = Object.create(superType.prototype);
// 重寫被污染的construct
prototype.constructor = subType;
// 重寫子類的原型
subType.prototype = Object.assign(prototype, subType.prototype);
}
雖然通過Object.assign
來進(jìn)行copy
解決了覆蓋原型類型的方法的問題,但是Object.assign
只能夠拷貝可枚舉的方法衰齐,而且如果子類本身就繼承了一個類任斋,這個辦法也不行。
八耻涛、ES6 實現(xiàn)繼承
我們知道了ES5中可以通過原型鏈來實現(xiàn)繼承废酷,ES6提供了extends關(guān)鍵字來實現(xiàn)繼承,這相對而言更加清晰和方便犬第,首先看看ES6 Class的語法锦积,此處參考http://es6.ruanyifeng.com/#docs/class