JavaScript面向?qū)ο蟪绦蛟O(shè)計

JavaScript面向?qū)ο蟪绦蛟O(shè)計
本文會碰到的知識點:
原型滩报、原型鏈、函數(shù)對象、普通對象吮螺、繼承

讀完本文饶囚,可以學(xué)到

  • 面向?qū)ο蟮幕靖拍?/li>
  • JavaScript對象屬性
  • 理解JavaScript中的函數(shù)對象與普通對象
  • 理解prototypeproto
  • 理解原型和原型鏈
  • 詳解原型鏈相關(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)由很多例子呢蔫,例如jQueryextendlodash中的cloneDeep, clone。jQuery可以使用$.extend(true, {}, ...)來實現(xiàn)深拷貝, 但是jQuery無法復(fù)制JSON對象之外的對象飒筑,例如ES6引入的Map咐刨、Set等。而lodash加入的大量的代碼來實現(xiàn)ES6新引入的標(biāo)準(zhǔn)對象

三扬霜、對象分為函數(shù)對象和普通對象

** 什么是函數(shù)對象和普通對象定鸟?**
ObjectFunction著瓶、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ī)范
原理:

  1. 如果this的值為undefined,則返回'[object Undefined]'.
  2. 如果this的值為null,則返回[object Null].
  3. O成為調(diào)用ToObject(this)的結(jié)果.
  4. class成為O的內(nèi)部屬性[[Class]]的值.
  5. 返回三個字符串'[object ', 'class', 以及 ']'連接后的新字符串.

問題司澎?這個一定是正確的么?不正確為啥栋豫?
提示ES6的Symbol屬性
Array.isArray()
部分瀏覽器中不兼容

五挤安、理解原型與原型鏈

其實上一節(jié)中的prototypeproto就是為了構(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í)行過程

  1. 首先創(chuàng)建了一個構(gòu)造函數(shù)Dogs蛤铜,傳入一個參數(shù)nameDogs.prototype也會自動創(chuàng)建
  2. 給對象dogs增加了一個方法
  3. 通過構(gòu)造函數(shù)Dogs實例化了一個對象sijing
  4. 輸出sijing的值,可以看到sijing有兩個值nameproto,其中proto指向Dogs.prototype
  5. 執(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袱巨。同時使用inhasOwnProperty就可以判斷屬性是在原型中還是在實例中

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ù)用
缺點:

  1. 引用類型的原型屬性會被所有實例共享
  2. 創(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

可以看到

  1. 子類繼承了父類的屬性和方法庆亡,同時屬性沒有創(chuàng)建在原型鏈上,因此多個子類不會共享同一個屬性捞稿。
  2. 子類可以動態(tài)傳遞參數(shù)給父類
  3. 父類構(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芒帕,一起剝皮案震驚了整個濱河市歉嗓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌背蟆,老刑警劉巖鉴分,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異带膀,居然都是意外死亡志珍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門垛叨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伦糯,“玉大人,你說我怎么就攤上這事嗽元×哺伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵剂癌,是天一觀的道長淤翔。 經(jīng)常有香客問我,道長佩谷,這世上最難降的妖魔是什么旁壮? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任监嗜,我火速辦了婚禮,結(jié)果婚禮上抡谐,老公的妹妹穿的比我還像新娘裁奇。我一直安慰自己,他們只是感情好童叠,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布框喳。 她就那樣靜靜地躺著,像睡著了一般厦坛。 火紅的嫁衣襯著肌膚如雪五垮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天杜秸,我揣著相機(jī)與錄音放仗,去河邊找鬼。 笑死撬碟,一個胖子當(dāng)著我的面吹牛诞挨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呢蛤,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惶傻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了其障?” 一聲冷哼從身側(cè)響起银室,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎励翼,沒想到半個月后蜈敢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體差牛,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡甜攀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年指厌,在試婚紗的時候發(fā)現(xiàn)自己被綠了附较。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骗污。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡锦溪,死狀恐怖躏精,靈堂內(nèi)的尸體忽然破棺而出李丰,到底是詐尸還是另有隱情惭蟋,我是刑警寧澤苗桂,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站敞葛,受9級特大地震影響誉察,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惹谐,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一持偏、第九天 我趴在偏房一處隱蔽的房頂上張望驼卖。 院中可真熱鬧,春花似錦鸿秆、人聲如沸酌畜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桥胞。三九已至,卻和暖如春考婴,著一層夾襖步出監(jiān)牢的瞬間贩虾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工沥阱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留缎罢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓考杉,卻偏偏與公主長得像策精,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崇棠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容