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

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

讀完本文,可以學到

  • 面向?qū)ο蟮幕靖拍?/li>
  • JavaScript對象屬性
  • 理解JavaScript中的函數(shù)對象與普通對象
  • 理解prototypeproto
  • 理解原型和原型鏈
  • 詳解原型鏈相關(guān)的Object方法
  • 了解如何用ES5模擬類,以及各種方式的優(yōu)缺點
  • 了解如何用ES6實現(xiàn)面向?qū)ο?/li>

一、面向?qū)ο蟮幕靖拍?/h1>

面向?qū)ο笠布词荗OP,Object Oriented Programming,是計算機的一種編程架構(gòu),OOP的基本原則是計算機是由子程序作用的單個或者多個對象組合而成,包含屬性和方法的對象是類的實例,但是JavaScript中沒有類的概念,而是直接使用對象來實現(xiàn)編程。
特性:

  • 封裝:能夠?qū)⒁粋€實體的信息、功能、響應都封裝到一個單獨對象中的特性。

由于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ǔ)上進行擴充,原功能得以保存,并且對子程序進行擴展,避免重復代碼編寫,后面的章節(jié)詳細描述

二、JavaScript對象屬性

想弄懂面向?qū)ο?,是不是先看看對象是啥呢?br> 我們先看一個題目:

[] + {}; // "[object Object]"
{} + []; // 0

解釋:
在第一行中,{}出現(xiàn)在+操作符的表達式中,因此被翻譯為一個實際的值(一個空object)。而[]被強制轉(zhuǎn)換為"",因此{}也會被強制轉(zhuǎn)換為一個string:"[object Object]"。
但在第二行中,{}被翻譯為一個獨立的{}空代碼塊兒(它什么也不做)。塊兒不需要分號來終結(jié)它們,所以這里缺少分號不是一個問題。最終,+ []是一個將[]明確強制轉(zhuǎn)換 為number的表達式,而它的值是0。

2.1 屬性

對象的屬性

  • Object.prototype Object 的原型對象,不是每個對象都有prototype屬性
  • Object.prototype.proto 不是標準方法,不鼓勵使用,每個對象都有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ù)屬性:

特性名稱 描述 默認值
value 屬性的值 undfined
writable 是否可以修改屬性的值,true表示可以,false表示不可以 true
enumerable 屬性值是否可枚舉,true表示可枚舉for-in, false表示不可枚舉 true
configurable 屬性的特性是否可配置,表示能否通過delete刪除屬性后重新定義屬性 true

例子:
[圖片上傳失敗...(image-7ebf49-1539227558825)]

訪問器屬性:

特性名稱 描述 默認值
set 設(shè)置屬性時調(diào)用的函數(shù) undefined
get 寫入屬性時調(diào)用的函數(shù) undefined
configurable 表示能否通過delete刪除屬性后重新定義屬性 true
enumerable 表示能否通過for-in循環(huán)返回屬性 true

訪問器屬性不能直接定義,一般是通過Object.defineProperty()方法來定義,但是這個方法只支持IE9+, 以前一般用兩個非標準方法來實現(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é)指的是不能向這個對象添加新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該對象已有屬性的可枚舉性、可配置性、可寫性。也就是說,這個對象永遠是不可變的。該方法返回被凍結(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 應用

如何檢測某個屬性是否在對象中?

  • in運算符,判斷對象是否包含某個屬性,會從對象的實例屬性、繼承屬性里進行檢測
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
  • 如何判斷兩個對象是否相等?

我只想說,這個問題說簡單很簡單,說復雜也挺復雜的傳送門
我們看個簡單版的

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ù)進行校驗,例如判斷是否是NaN,或者是其他內(nèi)置屬性
  • 沒有判斷傳入對象的construct和prototype
  • 時間算法復雜度是O(n2)

有同學可能會有疑問,能不能用Object.is,答案是否定的,Object.is簡單來說就是在===的基礎(chǔ)上特別處理了NaN,+0-0,保證了-0+0不相同,Object.is(NaN, NaN)返回true。

  • 對象的深拷貝和淺拷貝

其實如果大家理解了上面的那些方法,是很容易寫出深拷貝和淺拷貝的代碼的,我們先看一下這兩者的卻別。
淺拷貝僅僅是復制引用,拷貝后a === b, 注意Object.assign方法實現(xiàn)的是淺復制(此處有深刻教訓?。。。?br> 深拷貝這是創(chuàng)建了一個新的對象,然后把舊的對象中的屬性和方法拷貝到新的對象中,拷貝后 a !== b
深拷貝的實現(xiàn)由很多例子,例如jQueryextendlodash中的cloneDeep, clone。jQuery可以使用$.extend(true, {}, ...)來實現(xiàn)深拷貝, 但是jQuery無法復制JSON對象之外的對象,例如ES6引入的Map、Set等。而lodash加入的大量的代碼來實現(xiàn)ES6新引入的標準對象

三、對象分為函數(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ù)來充當,而b1和c1不是Function的實例,所以不能充當構(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在定義一個對象時的預定義屬性
  • 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的原型鏈是 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ù)name,Dogs.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,// 不是對象,到頭啦

原型鏈 的概念其實不重要,重要的是要理解,簡單來說,原型鏈就是利用原型讓一個引用類型繼承另一個應用類型的屬性和方法。

還有三點需要注意的:

  • 任何內(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)點:原型定義的屬性和方法可以復用
缺點:

  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ù)的復用,而且在父對象的原型上定義的方法,對于子類型是不可見的。 ??? 為什么不可見

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ù)的復用,而且能夠保證每個實例都有自己的屬性。

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 原型式繼承

原型式繼承并沒有使用嚴格意義上的構(gòu)造函數(shù),借助原型可以基于已有的對象創(chuàng)建新的對象,例如:

function createObject(o) {
  function newOrient () {};
  newOrient.prototype = o;
  return new newOrient();
}

簡單來說createObject函數(shù),對傳入的o對象進行的一次淺拷貝。在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來進行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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容