JavaScript面向?qū)ο?/h1>

1 理解對(duì)象

1-1 屬性的類型

屬性分兩種:數(shù)據(jù)屬性和訪問器屬性

  1. 數(shù)據(jù)屬性

    數(shù)據(jù)屬性包含一個(gè)保存數(shù)據(jù)值的位置宾茂。值會(huì)從這個(gè)位置讀取,也會(huì)寫入到這個(gè)位置拴还,數(shù)據(jù)屬性有4個(gè)特性來描述它們的行為跨晴。

    • [[Configurable]]:表示屬性是否可以通過delete刪除并重新定義,是否可以修改它的特性片林,以及是否可以把它改為訪問器屬性端盆。默認(rèn)為true
    • [[Enumerable]]:表示屬性是否可枚舉(通過for-in循環(huán)返回),默認(rèn)為true
    • [[Writable]]:表示屬性的值是否可修改费封,默認(rèn)為true
    • [[Value]]:屬性實(shí)際的值焕妙,默認(rèn)是undefined

    注意點(diǎn):雖然可以對(duì)同一個(gè)屬性多次調(diào)用Object.defineProperty(),但在把configurable設(shè)為false之后就會(huì)受限制了

  2. 訪問器屬性

    訪問器屬性不包含數(shù)據(jù)值孝偎。相反访敌,它們包含一個(gè)獲取函數(shù)(getter)和一個(gè)設(shè)置函數(shù)(setter),訪問器屬性有4個(gè)特性來描述它們的行為

    • [[Configurable]]:表示屬性是否可以通過delete刪除并重新定義,是否可以修改它的特性衣盾,以及是否可以把它改為數(shù)據(jù)屬性寺旺。默認(rèn)為true
    • [[Enumberable]]:表示屬性是否可枚舉(通過for-in循環(huán)返回)爷抓,默認(rèn)為true
    • [[Get]]:獲取函數(shù),在讀取屬性時(shí)調(diào)用阻塑,默認(rèn)為undefined
    • [[Set]]:設(shè)置函數(shù)蓝撇,在寫入屬性時(shí)調(diào)用,默認(rèn)為undefined

1-2 定義多個(gè)屬性

在一個(gè)對(duì)象上同時(shí)定義多個(gè)屬性時(shí)陈莽,使用Object.defineProperties()方法渤昌,區(qū)別于Object.definedProperty()方法一次只能定義或修改多個(gè)屬性,具體看MDN文檔

1-3 讀取屬性的特性

使用Object.getOwnPropertyDescription(obj, prop)方法可以獲取指定屬性的屬性描述符走搁,也就是屬性的特性独柑。接收兩個(gè)參數(shù):屬性所在的對(duì)象和要取得其描述符的屬性名。

Object.getOwnPropertyDescriptions(obj)方法可用來獲取一個(gè)對(duì)象的所有屬性的屬性描述符私植。接收一個(gè)參數(shù):需要獲取的對(duì)象

1-4 合并對(duì)象

ES6中使用Object.assign(target, source)方法進(jìn)行對(duì)象的合并忌栅,返回值是目標(biāo)對(duì)象。這個(gè)方法實(shí)際上是對(duì)每個(gè)源對(duì)象執(zhí)行的是淺復(fù)制曲稼。

1-5 對(duì)象標(biāo)識(shí)及相等判定

為了解決 === 操作符判定特殊情況帶來的問題索绪,ES6新增了Object.is()

// === 
console.log(+0 === -0) // true
console.log(+0 === 0) // true
console.log(-0 === 0) // true
console.log(NaN === NaN) // false

// Object.is()
console.log(Object.is(+0 === -0)) // false
console.log(Object.is(+0 === 0)) // true
console.log(Object.is(-0 === 0)) // false
console.log(Object.is(NaN, NaN)) // true

2 創(chuàng)建對(duì)象

2-1 工廠模式

function createPerson(name, age, job) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name)
    }
    return o;
}
let person1 = createPerson('xiaoming', 10, 'student');
let person2 = createPerson('zhangsan', 20, 'doctor');
console.log(person1 instanceof Person); // false 不能識(shí)別對(duì)象的類型

弊端:這里,函數(shù)每次調(diào)用都會(huì)返回一個(gè)新的對(duì)象贫悄, 這種方法可以解決創(chuàng)建多個(gè)類似對(duì)象的問題瑞驱,但是沒有解決對(duì)象標(biāo)識(shí)問題(即新創(chuàng)建的對(duì)象是什么類型),構(gòu)造函數(shù)模式可以解決這個(gè)問題窄坦。

2-2 構(gòu)造函數(shù)模式

ECMAScript中的構(gòu)造函數(shù)是用于創(chuàng)建特定類型對(duì)象的唤反。

前面的例子使用構(gòu)造函數(shù)可以這么寫:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name)
    }
}
let person1 = new Person('xiaoming', 10, 'student');
let person2 = new Person('zhangsan', 20, 'doctor');

這里的代碼和前面使用工廠函數(shù)創(chuàng)建的例子基本是一樣的,只是有以下區(qū)別:

  • 沒有顯示地創(chuàng)建對(duì)象
  • 屬性和方法直接賦值給this
  • 沒有返回值

為什么鸭津?可以看到我們?cè)趧?chuàng)建實(shí)例地時(shí)候使用了new操作符拴袭。那使用new時(shí),內(nèi)部執(zhí)行了以下的操作:

  1. 創(chuàng)建一個(gè)新的空對(duì)象
  2. 這個(gè)對(duì)象內(nèi)部的__proto__屬性(這里應(yīng)該是[[Prototype]]特性曙博,具體為什么__proto__可以訪問到?下面會(huì)解釋到)指向構(gòu)造函數(shù)(即Person)的prototype屬性
  3. 構(gòu)造函數(shù)內(nèi)部的this指向這個(gè)新創(chuàng)建的空對(duì)象
  4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼怜瞒,也就是不斷地給this賦值父泳,不斷給this添加屬性
  5. 返回this對(duì)象(即新創(chuàng)建的對(duì)象)

instanceof操作符是用來確定對(duì)象類型最可靠的方式。相比于工廠模式吴汪,可識(shí)別對(duì)象的類型是一個(gè)很大的好處惠窄。

console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true

弊端:構(gòu)造函數(shù)內(nèi)部定義的方法會(huì)在每個(gè)實(shí)例上都創(chuàng)建一遍,上面的例子中漾橙,person1person2中都有名為sayName()的方法杆融,因?yàn)槭亲鐾患拢詻]必要?jiǎng)?chuàng)建兩次霜运。這個(gè)問題可以通過原型模式來解決脾歇。

2-3 原型模式

每個(gè)函數(shù)都會(huì)創(chuàng)建一個(gè)prototype屬性蒋腮,這個(gè)屬性是一個(gè)對(duì)象。

使用原型對(duì)象的好處是:在它上面定義的屬性和方法可以被實(shí)例共享藕各。原來在構(gòu)造函數(shù)中直接賦值給對(duì)象實(shí)例的值池摧,可以直接賦值給他們的原型,如下:

function Person() {}
Person.prototype.name = 'xiaoming';
Person.prototype.age = 10;
Person.prototype.job = 'student';
Person.prototype.sayName = function() {
    console.log(this.name);
}
let person1 = new Person();
let person2 = new Person();
console.log(person1.name) // xiaoming
console.log(person1.sayName == person2.sayName) // true

雖然構(gòu)造函數(shù)中什么都沒有激况,但是卻可以訪問得到相應(yīng)得屬性和方法作彤,而且使用定義在原型上的屬性和方法是共享給所有的實(shí)例的(即所有實(shí)例都可以訪問得到,也不會(huì)存在重復(fù)創(chuàng)建的問題)

  1. 理解原型

無論何時(shí)乌逐,只要?jiǎng)?chuàng)建一個(gè)函數(shù)竭讳,這個(gè)函數(shù)就存在一個(gè)prototype屬性(指向原型對(duì)象)。默認(rèn)情況下浙踢,所有原型對(duì)象都有一個(gè)名為constructor的屬性绢慢,指回對(duì)應(yīng)的構(gòu)造函數(shù)。比如上面的例子Person.prototype.constructor指回Person

每次調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例成黄,這個(gè)實(shí)例內(nèi)部存在一個(gè)[[Prototype]]特性呐芥,會(huì)指向構(gòu)造函數(shù)的原型對(duì)象。由于腳本中沒有訪問這個(gè)[[Prototype]]特性的標(biāo)準(zhǔn)方式奋岁,但Firefox思瘟,SafariChrome中會(huì)在每個(gè)對(duì)象上暴露__proto__屬性闻伶,通過這個(gè)屬性可以訪問實(shí)例對(duì)象的原型滨攻。(這也為上面將new操作符時(shí)說為什么可以通過__proto__訪問的到原型做了解釋)

關(guān)鍵在于理解這一點(diǎn):實(shí)例與構(gòu)造函數(shù)原型之間有直接的聯(lián)系,但實(shí)例與構(gòu)造函數(shù)之間沒有

看圖:

  1. 原型層級(jí)

在通過對(duì)象訪問屬性時(shí)蓝翰,會(huì)按照這個(gè)屬性的名稱開始搜索光绕。搜索開始于對(duì)象實(shí)例本身。如果在這個(gè)實(shí)例上發(fā)現(xiàn)了給定的名稱畜份,則返回該名稱對(duì)應(yīng)的值诞帐。如果沒有找到這個(gè)屬性,則搜索會(huì)沿著指針進(jìn)入原型對(duì)象爆雹,然后在原型對(duì)象上找到屬性后停蕉,再返回對(duì)應(yīng)的值。

雖然可以通過實(shí)例讀取原型對(duì)象上的值钙态,但不可能通過實(shí)例重寫這些值慧起。如果在實(shí)例上添加了一個(gè)與原型對(duì)象中同名的屬性,那就會(huì)在實(shí)例上創(chuàng)建這個(gè)屬性册倒,這個(gè)屬性會(huì)遮住原型對(duì)象上的屬性蚓挤。即使在實(shí)例上把這個(gè)屬性設(shè)置為null,也不會(huì)恢復(fù)它和原型的聯(lián)系,不過灿意,使用delete操作符可以完全刪除實(shí)例上的這個(gè)屬性估灿,從而讓標(biāo)識(shí)符解析過程能夠繼續(xù)搜索原型對(duì)象。

hasOwnProperty()方法用于確定某個(gè)屬性是在實(shí)例上還是在原型對(duì)象上脾歧。會(huì)在屬性存在于調(diào)用它的對(duì)象實(shí)例上時(shí)返回true甲捏,即如果該屬性是存在于實(shí)例上時(shí),返回true鞭执,反之返回false司顿。

function Person() {}
Person.prototype.name = 'xiaoming';
let person1 = new Person()
person1.name = 'lucy'
console.log(person1.hasOwnProperty('name')) // true
delete person1.name // 刪除實(shí)例上的name屬性
console.log(person1.hasOwnProperty('name')) // false
  1. 原型和in操作符

有兩種方式使用in操作符:

  • for-in循環(huán)中使用

    • for-in中使用in操作符時(shí),遍歷對(duì)象的所有可枚舉屬性
    • 要想獲得對(duì)象上所有可枚舉的實(shí)例屬性兄纺,可以使用Object.keys()方法大溜。(接收一個(gè)對(duì)象作為參數(shù),返回所有可枚舉屬性組成的字符串?dāng)?shù)組)
    • Object.getOwnPropertyNames()方法返回的是所有實(shí)例屬性估脆,無論是否可枚舉钦奋;Object.getOwnPropertySymbols()類似;
  • 單獨(dú)使用時(shí)疙赠,in操作符會(huì)在可以通過對(duì)象訪問指定屬性時(shí)返回true付材,無論該屬性是在實(shí)例上還是在原型上。

    • 如果要確定某個(gè)屬性是否存在于原型上圃阳,則可以像這樣同時(shí)使用hasOwnPropertyin操作符

      function hasPrototypeProperty(object, name) {
          return !Object.hasOwnProperty(name) && (name in Object)
      }
      
  1. 屬性枚舉順序
    • 順序不確定:for-in循環(huán)厌衔,Object.keys(),取決于JavaScript引擎捍岳,可能因?yàn)g覽器而異
    • 順序確定:Object.getOwnPropertyNames()富寿、Object.getOwnPropertySymbols()Object.assign()锣夹,先以升序枚舉數(shù)值鍵页徐,再按定義的順序插入枚舉字符串和符號(hào)鍵。(數(shù)字鍵優(yōu)先银萍,并且升序排列变勇,和定義屬性的順序無關(guān),次之是字符串和符號(hào)鍵贴唇,這兩種就按照定義屬性的順序來插入)

2-4 對(duì)象迭代

ESMAScript2017新增了兩個(gè)靜態(tài)方法贰锁。用于迭代對(duì)象,這兩個(gè)方法執(zhí)行對(duì)象的淺復(fù)制滤蝠,都會(huì)忽略符號(hào)屬性。

  • Object.values():返回的是對(duì)象 值的數(shù)組
  • Object.entries():返回的是 鍵/值對(duì)的數(shù)組
  1. 其他原型語法
function Person() {}
Person.prototype = {
    name: 'xiaoming',
    sayName() {
        console.log(this.name);
    }
}

看上面的代碼授嘀,在直接通過一個(gè)包含所有屬性和方法的對(duì)象來重寫原型時(shí)物咳,要注意,這樣重寫后蹄皱,Person.prototypeconstructor屬性就不指向Person了览闰,而是指向Object芯肤。如果我們想依靠constructor屬性來識(shí)別類型,那怎么辦压鉴?那就重新指定一下

function Person() {}
Person.prototype = {
    constructor: Person,
    name: 'xiaoming',
    sayName() {
        console.log(this.name)
    }
}

好了崖咨,但是有個(gè)問題,以這種方式恢復(fù)constructor屬性它是一個(gè)[[Enumerable]]true的屬性油吭,而原生的constructor屬性默認(rèn)是不可枚舉的击蹲。因此我們得用Object.definedProperty()方法來定義constructor屬性:

function Person() {}
Person.prototype = {
    name: 'xiaoming',
    sayName() {
        console.log(this.name)
    }
}
// 恢復(fù)constructor屬性
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
})

這樣就可以完美恢復(fù)constructor屬性了。

  1. 原型的動(dòng)態(tài)性

注意給原型添加屬性和方法重寫整個(gè)原型是完全兩回事婉宰。

先看個(gè)例子:給原型添加屬性和方法

let friend = new Person()
Person.prototype.sayHi = function() {
    console.log('Hi')
}
friend.sayHi() // Hi

雖然我們是在實(shí)例化之后才給原型添加sayHi()方法的歌豺,為什么實(shí)例可以直接訪問到該方法?

這是因?yàn)閚ew的時(shí)候?qū)嵗?code>[[Prototype]]指針就已經(jīng)指向Person.prototype了心包,所以無論我們后面怎么給原型對(duì)象添加屬性类咧,實(shí)例都能夠訪問得到。

再看看這個(gè)例子:重寫整個(gè)原型

let friend = new Person()
Person.prototype = {
    constructor: Person,
    name: 'xiaoming',
    sayName() {
        console.log(this.name)
    }
}
friend.sayName(); // 報(bào)錯(cuò)

為什么蟹腾?

這也是剛剛上面說的痕惋,實(shí)例的[[Prototype]]指針是在new的時(shí)候被賦值為Person.prototype的,而上面的代碼因?yàn)橹貙懥嗽屯拗常喈?dāng)于又創(chuàng)建了一個(gè)新的對(duì)象值戳, 而這時(shí)實(shí)例指向的還是最初的原型對(duì)象,上面并沒有sayName()方法珊随,所以報(bào)錯(cuò)

重寫構(gòu)造函數(shù)上的原型之后再創(chuàng)建的實(shí)例才會(huì)引用新的原型述寡。而在此之前創(chuàng)建的實(shí)例仍然會(huì)引用最 初的原型。

  1. 原生對(duì)象原型

盡管可以像修改自定義對(duì)象原型一樣修改原生對(duì)象原型叶洞,隨時(shí)添加方法鲫凶,但不推薦在產(chǎn)品環(huán)境中修改原生對(duì)象原型。這樣做很可能造成誤會(huì)衩辟,而且可能引發(fā)命名沖突(比如一個(gè)名稱在某個(gè)瀏覽器實(shí)現(xiàn)中不存在螟炫,在另一個(gè)實(shí)現(xiàn)中卻存在)。另外還有可能意外重寫原生的方法艺晴。推薦的做法是創(chuàng)建一個(gè)自定義的類昼钻,繼承原生類型

  1. 原型的問題

存在的問題:

  1. 弱化了向構(gòu)造函數(shù)傳遞初始化參數(shù)的能力封寞,會(huì)導(dǎo)致所有的實(shí)例默認(rèn)都取得相同的屬性值然评。
  2. 原型上的方法和屬性都是所有實(shí)例共享的,這對(duì)于方法來說比較合適狈究,但是對(duì)于屬性來說就不是特別好碗淌。如果屬性是原始類型,那還好,可以通過實(shí)例上添加同名屬性來覆蓋原型上地屬性亿眠。但是碎罚,如果屬性是引用類型,那么當(dāng)我們修改了某個(gè)實(shí)例上的該屬性纳像,(由于指針指向是相同的)那么這樣就影響了其他實(shí)例上的屬性荆烈,這是不合理的。

所以實(shí)際開發(fā)中通常不單獨(dú)使用原型模式竟趾。

3 繼承

繼承分為接口繼承和**實(shí)現(xiàn)繼承**憔购,實(shí)現(xiàn)繼承是`ECMAScript`唯一支持的繼承方式,而這主要是通過原型鏈實(shí)現(xiàn)的潭兽。

3-1 原型鏈繼承

原型鏈繼承就是 **使子類的原型指向父類的構(gòu)造出來的實(shí)例對(duì)象**
SubType.prototype = new SuperType()
  1. 默認(rèn)原型

任何函數(shù)的默認(rèn)原型都是Object的實(shí)例倦始,這意味著這個(gè)實(shí)例有一個(gè)內(nèi)部指針指向Object.prototype,所以自定義類型能夠繼承如toString(),valueOf()這些方法山卦。

  1. 原型與繼承的關(guān)系

原型與實(shí)例的關(guān)系可以通過兩種方式來確定:

  • instanceof操作符:(實(shí)例 instanceof 構(gòu)造函數(shù))如果一個(gè)實(shí)例的原型鏈中出現(xiàn)過相應(yīng)的構(gòu)造函數(shù)鞋邑,則返回true
  • isPrototypeOf()方法(構(gòu)造函數(shù).prototype.isPrototypeOf(需要檢測的實(shí)例對(duì)象))原型鏈中的每個(gè)原型都可以調(diào)用這個(gè)方法,用于檢測實(shí)例對(duì)象是否存在于另一個(gè)對(duì)象的原型鏈上账蓉,是則返回true

弊端:如果父類構(gòu)造函數(shù)中存在引用值會(huì)導(dǎo)致子類的原型中也存在著引用值(因?yàn)樽宇惖脑褪潜毁x值為父類的一個(gè)實(shí)例對(duì)象)枚碗,所以子類的所有實(shí)例都會(huì)共享存在的引用值。

3-2 盜用(借用)構(gòu)造函數(shù)繼承

為了解決原型包含引用值導(dǎo)致的繼承問題铸本,我們可以使用“盜用構(gòu)造函數(shù)繼承”

基本思路:在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)肮雨,可以使用call()apply()方法以新創(chuàng)建的對(duì)象為上下文執(zhí)行構(gòu)造函數(shù)。

function SuperType(name) {
   this.name = name
   this.colors = ['red', 'green']
   this.sayName = function() {
      console.log(this.name)
   }
}
function SubType() {
  SuperType.call(this, 'xiaoming') // 繼承SuperType并傳參
}
let instance1 = new SubType()
instance1.colors.push('blue')
console.log(instance1.colors) // ['red', 'green', 'blue']

let instance2 = new SubType()
console.log(instance2.colors) // ['red', 'green']
// 通過使用call()/apply()方法, SuperType構(gòu)造函數(shù)在SubType的實(shí)例創(chuàng)建的新對(duì)象的上下文中執(zhí)行了箱玷,相當(dāng)于新的SubType對(duì)象上運(yùn)行了SuperType函數(shù)中所有初始化代碼怨规。結(jié)果就是每個(gè)實(shí)例都會(huì)有自己的colors和name屬性。

優(yōu)點(diǎn):可以在子類構(gòu)造函數(shù)中向父類構(gòu)造函數(shù)傳參

缺點(diǎn):也是構(gòu)造函數(shù)模式的缺點(diǎn):就是必須在構(gòu)造函數(shù)中定義方法锡足,因此函數(shù)不能重用

3-3 組合繼承

組合繼承綜合了原型鏈和盜用(借用)構(gòu)造函數(shù)繼承波丰,將兩者的優(yōu)點(diǎn)集中了起來。

基本思路:使用原型鏈繼承原型上的屬性和方法舶得,而通過盜用構(gòu)造函數(shù)繼承實(shí)例屬性掰烟。

function SuperType(name) {
       this.name = name
       this.colors = ['red', 'blue']
}
SuperType.prototype.sayName = function() {
       console.log(this.name)
}

function SubType(name, age) {
       SuperType.call(this, name) // 借用構(gòu)造函數(shù)繼承 讓SubType的每個(gè)實(shí)例都擁有name 和 colors屬性,相互之間不受影響
       this.age = age
} 
SubType.prototype = new SuperType() // 原型鏈繼承父類
SubType.prototype.sayAge = function() {
       console.log(this.age)
}
let instance1 = new SubType('xiaoming', 10)
instance1.colors.push('yellow')
console.log(instance1.colors) // ['red', 'blue', 'yellow']
instance1.sayName() // xiaoming
instance1.sayAge() // 10

let instance2 = new SubType('lucy', 20)
console.log(instance2.colors) // ['red', 'blue']
instance2.sayName() // lucy
instance2.sayAge() // 20

優(yōu)點(diǎn):組合繼承彌補(bǔ)了原型鏈和盜用構(gòu)造函數(shù)的不足沐批,是JavaScript中使用最多的繼承模式纫骑,而且組合繼承也保留了instanceof操作符和isPrototypeOf()方法識(shí)別合成對(duì)象的能力

弊端:存在效率問題,就是父類構(gòu)造函數(shù)始終會(huì)被調(diào)用兩次:一次是在賦值給子類原型時(shí)調(diào)用九孩,另一次是在子類構(gòu)造函數(shù)中調(diào)用先馆。

3-4 原型式繼承

基本思路:即使不自定義類型也可以通過原型實(shí)現(xiàn)對(duì)象之間的信息共享

function object(o) {
       function F() {} // 創(chuàng)建一個(gè)臨時(shí)構(gòu)造函數(shù)F
       F.prototype = o // 構(gòu)造函數(shù)F的原型指向o,說明F的實(shí)例對(duì)象能夠訪問到o的屬性和方法
       return new F() // 返回構(gòu)造函數(shù)F的實(shí)例對(duì)象
}  
let person = { name: 'xiaoming', friends: ['xxx', 'yyy']}

let anotherPerson = object(person) // 返回一個(gè)對(duì)象躺彬,這個(gè)對(duì)象的[[Prototype]]指針指向o
anotherPerson.friends.push('zzz')
let yetAnotherPerson = object(person)
yetANotherPerson.friends.push('hhh')
console.log(person) // ['xxx', 'yyy', 'zzz', 'hhh'] 
// 實(shí)際上磨隘,object()是對(duì)傳入的對(duì)象執(zhí)行了一次淺復(fù)制

適用場景:

  • 你有一個(gè)對(duì)象缤底,想在它的基礎(chǔ)上再創(chuàng)建一個(gè)新對(duì)象。你需要先把這個(gè)對(duì)象傳入object()番捂,然后再對(duì)返回的對(duì)象做相應(yīng)的修改
  • 適合不需要單獨(dú)創(chuàng)建構(gòu)造函數(shù),但仍然需要在對(duì)象間共享信息的場合江解。

Object.create()方法將原型式繼承的概念規(guī)范化了设预。這個(gè)方法接收兩個(gè)參數(shù):第一個(gè)參數(shù):作為新對(duì)象原型的對(duì)象;第二個(gè)參數(shù)(可選):給新對(duì)象定義額外屬性的對(duì)象犁河。當(dāng)只有一個(gè)參數(shù)時(shí)鳖枕,Objcet.create()object()方法效果相同

Object.create()的第二個(gè)參數(shù)與Object.definedProperties()的第二個(gè)參數(shù)一樣:每個(gè)新增的屬性都通過各自的描述符來描述。以這種方式添加的屬性會(huì)遮蔽原型對(duì)象上的同名屬性桨螺。

弊端:屬性中包含的引用值類型始終會(huì)在各個(gè)實(shí)例之間共享宾符,跟適用原型模式是一樣的。

3-5 寄生式繼承

寄生式繼承與原型式繼承比較接近灭翔。

基本思路:創(chuàng)建一個(gè)實(shí)現(xiàn)繼承的函數(shù)魏烫,以某種方式增強(qiáng)對(duì)象,然后返回這個(gè)對(duì)象

function object(o) {
      function F() {}
       F.prototype = o
       return new F()
}
function createAnother(original) {
      let clone = object(original) // 通過調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
      clone.sayHi = function() {   // 以某種方式增強(qiáng)這個(gè)對(duì)象
              console.log('hi')       // 返回這個(gè)對(duì)象
      }
      return clone
}
let person = { name: 'xiaoming', friends: ['xxx', 'yyy'] }

let anotherPerson = createAnother(person)
anotherPerson.sayHi() // 'hi'

弊端:通過寄生式繼承給對(duì)象添加函數(shù)會(huì)導(dǎo)致函數(shù)難以復(fù)用肝箱,與構(gòu)造函數(shù)模式類似哄褒。(即每次創(chuàng)建實(shí)例都要重復(fù)創(chuàng)建方法)

3-6 寄生式組合繼承

前面說到組合繼承其實(shí)存在性能問題:父類構(gòu)造函數(shù)最終會(huì)被調(diào)用兩次。(第一次是在給子類原型賦值時(shí)調(diào)用煌张;第二次是在子類構(gòu)造函數(shù)里面調(diào)用)寄生式組合繼承可以解決這個(gè)問題呐赡。

繼承方法:組合繼承(原型鏈繼承+借用構(gòu)造函數(shù)繼承)+ 寄生式繼承

基本思路:不通過調(diào)用父類構(gòu)造函數(shù)來給子類原型賦值,而是通過取得父類原型的一個(gè)副本

function object(o) {
    function F() {}
    F.prototype = o // 這里由于直接用對(duì)象賦值的形式重寫原型對(duì)象骏融,所以constructor的指向發(fā)生改變链嘀,指向該對(duì)象o
    return new F()
}
function inheritPrototype(subType, superType) {
    let prototype = object(superType.prototype) // 返回父類構(gòu)造函數(shù)的一個(gè)副本
    prototype.constructor = subType   // 修改constructor的指向
    subType.prototype = prototype // 
}

function SuperType(name) {
    this.name = name
    this.colors = ['red', 'yellow']
}
SuperType.prototype.sayName = function() {
    console.log(this.name)
}
function SubType(name, age) {
    SuperType.call(this, name)
    this.age = age
}
// SubType.prototype = new SuperType()
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function() {
    console.log(this.age)
}

這樣的話就只調(diào)用一次父類構(gòu)造函數(shù),這樣效率更高档玻。而且原型鏈保持不變怀泊,因此instanceof操作符和isPrototypeOf()方法有效,所以寄生式組合繼承可以算是引用類型繼承的最佳方式窃肠。

4 類

ES6引入一個(gè)class關(guān)鍵字具有定義類的能力包个,是一個(gè)語法糖。class背后使用的仍然是原型和構(gòu)造函數(shù)的概念冤留。

4-1 類定義

定義類有兩種主要方式:類聲明和類表達(dá)式

類聲明:Class Person {}

類表達(dá)式:const Animal = class {}

類聲明不能提升

4-2 類構(gòu)造函數(shù)

constructor關(guān)鍵字用于在類定義塊的內(nèi)部創(chuàng)建類的構(gòu)造函數(shù)碧囊。方法名constructor會(huì)告訴解釋器在使用new操作符創(chuàng)建類的新實(shí)例時(shí),應(yīng)該調(diào)用這個(gè)函數(shù)纤怒。

  1. 實(shí)例化
  • 調(diào)用類構(gòu)造函數(shù)時(shí)必須使用new操作符糯而,否則會(huì)報(bào)錯(cuò)。而普通構(gòu)造函數(shù)如果不使用new泊窘,那就會(huì)以全局的this(通常是window)作為內(nèi)部對(duì)象

  • 類構(gòu)造函數(shù)實(shí)例化之后熄驼,它會(huì)變?yōu)槠胀ǖ膶?shí)例方法(但是它作為類構(gòu)造函數(shù)像寒,仍然需要使用new調(diào)用)

    class Person { // Person:類標(biāo)識(shí)符
        constructor() {}  // constructor:類構(gòu)造函數(shù)
    }
    let p1 = new Person()
    let p2 = new p1.constructor()
    

4-3 實(shí)例、原型和類成員

類的語法可以非常方便地定義應(yīng)該存在于實(shí)例上的成員瓜贾、應(yīng)該存在于原型上的成員诺祸,以及應(yīng)該存在于類本身的成員
  1. 實(shí)例成員

每次通過new調(diào)用類標(biāo)識(shí)符時(shí),都會(huì)執(zhí)行類構(gòu)造函數(shù)祭芦】瓯浚可以為新創(chuàng)建的實(shí)例(this)添加“自有”屬性。沒有限制是什么屬性龟劲。

構(gòu)造函數(shù)執(zhí)行完畢后地梨,仍然可以給實(shí)例繼續(xù)添加新成員胖腾。

每個(gè)實(shí)例都對(duì)應(yīng)一個(gè)唯一的成員對(duì)象,這意味著所有成員都不會(huì)在原型上共享。

  1. 原型方法與訪問器

為了在實(shí)例間共享方法腰素,類定義語法把在類塊中定義的方法作為原型方法匙监。

 class Person {
     constructor(name) {
         // 添加到this上面的所有內(nèi)容都會(huì)存在于不同的實(shí)例上面
         this.name = name
     }
     // 在類塊中定義的所有內(nèi)容都會(huì)定義在類的原型上
     locate() {
         console.log('prototype')
     }
 }
 let p1 = new Person('Jack')
 let p2 = new Person('May')
 console.log(p1.name) // Jack
 console.log(p2.name) // May
 
 p1.locate() // prototype
 p2.locate() // prototype

類方法等同于對(duì)象屬性琐馆,因此可以使用字符串瀑粥,符號(hào)或者計(jì)算的值作為鍵。

類定義也支持獲取和設(shè)置訪問器审胸。語法與行為跟普通對(duì)象一樣

class Person() {
     set name(newName) {
         this.name_ = newName
     }
     get name() {
         return this.name_
     }
 }
  1. 靜態(tài)類方法

可以在類上定義靜態(tài)方法亥宿,與原型成員類似,靜態(tài)成員每個(gè)類上只能有一個(gè)砂沛。使用static關(guān)鍵字作為前綴烫扼,this引用類自身。

class Person () {
     ... 省略代碼
     // 定義在類本身上
  static locate() {
         console.log('class')
     }
 }
  1. 非函數(shù)原型和類成員的添加

雖然類定義不顯示支持在原型上或類上添加成員數(shù)據(jù)碍庵,但在類定義的外部映企,可以通過手動(dòng)來添加。

  1. 迭代器與生成器方法

4-4 繼承

  1. 繼承基礎(chǔ)

ES6類支持單繼承静浴。使用extends關(guān)鍵字堰氓,不僅可以繼承一個(gè)類,也可以繼承普通的構(gòu)造函數(shù)

派生類都會(huì)通過原型鏈訪問到類和原型上定義的方法苹享。this的值會(huì)反映調(diào)用相應(yīng)方法的實(shí)例或者類双絮。

  1. 構(gòu)造函數(shù),HomeObject得问,super()

super關(guān)鍵字只能在派生類中使用囤攀,而且僅限于構(gòu)造函數(shù),實(shí)例方法和靜態(tài)方法內(nèi)部宫纬。

  • 在構(gòu)造函數(shù)中使用super可以調(diào)用繼承的父類的構(gòu)造函數(shù)

    class Vehicle {
        constructor() {
            this.hasEngine = true
        }
    }
    class Bus extends Vehicle {
        constructor() {
            super()  // 相當(dāng)于super.constructor()
            console.log(this.hasEngine) // true
            console.log(this) // Bus { hasEngine: true }
        }
    }
    new Bus()
    
  • 在靜態(tài)方法中使用super可以調(diào)用繼承的父類上定義的靜態(tài)方法

    class Vehicle {
        static identify() {
            console.log('vehicle')
        }
    }
    class Bus extends Vehicle {
        static identify() {
            super.identify()
        }
    }
    Bus.identify() // vehicle
    

使用super注意事項(xiàng):

  • super只能在派生類構(gòu)造函數(shù)和靜態(tài)方法中使用焚挠。

  • 不能單獨(dú)引用super關(guān)鍵字,要么用它調(diào)用構(gòu)造函數(shù)漓骚,要么用它引用靜態(tài)方法

  • 調(diào)用super()會(huì)調(diào)用父類構(gòu)造函數(shù)蝌衔,并將父類構(gòu)造函數(shù)中返回的實(shí)例賦值給子類中的this

class Father {
    constructor() {
        this.name = 'xiaoming'
    }
}
class Child extends Father {
    constructor() {
        super()
        console.log(this) // Child { name: 'xiaoming' }
    }
}
let c1 = new Child()
console.log(c1) // Child { name: 'xiaoming' }
  • super()的行為如同調(diào)用構(gòu)造函數(shù)榛泛,如果需要給父類構(gòu)造函數(shù)傳參噩斟,則需要手動(dòng)傳入曹锨。
class Father {
    constructor(name) {
        this.name = name
    }
}
class Child extends Father {
    constructor(name) {
        super(name)
    }
}
let c1 = new Child('Jack')
  • 如果沒有派生類中沒有定義類構(gòu)造函數(shù)佳遂,在實(shí)例化派生類時(shí)會(huì)自動(dòng)調(diào)用super()营袜,而且會(huì)自動(dòng)傳入所有傳給派生類的參數(shù)
class Father {
    constructor(name) {
        this.name = name
    }
}
class Child extends Father {}
let c1 = new Child('Jack')
  • 在派生類構(gòu)造函數(shù)中,不能在調(diào)用super()之前引用this

  • 如果在派生類中顯式定義了構(gòu)造函數(shù)丑罪,則要么必須在其中調(diào)用super()荚板,要么必須在其中返回一個(gè)對(duì)象

class Father {
    constructor() {
        this.name = 'Jack'
    }
}
class Child extends Father {
    constructor() { // 顯示定義了構(gòu)造函數(shù)
        super();
    }
}
// 或者
class Child extends Father {
    constructor() {
        return {}
    }
}

3.抽象基類

可供其他類繼承, 但本身不會(huì)被實(shí)例化吩屹」蛄恚可以通過new.target來實(shí)現(xiàn)。另外可以通過抽象基類構(gòu)造函數(shù)中進(jìn)行檢查煤搜,可以要求派生類必須定義某個(gè)方法免绿。

// 抽象基類
class Father {
    constructor() {
        if (new.target === Father) {
            throw new Error('Father cannot be directly instantiated')
        }
        if (!this.foo) {
            throw new Error('Inheriting class must define foo()')
        }
        console.log("success")
    }
}
// 派生類
class Child extends Father {
    foo() {}
}
new Child() // class Child {} // success
new Father() // class Father {}
// Error: Father cannot be directly instantiated
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者

  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市擦盾,隨后出現(xiàn)的幾起案子嘲驾,更是在濱河造成了極大的恐慌,老刑警劉巖迹卢,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辽故,死亡現(xiàn)場離奇詭異,居然都是意外死亡腐碱,警方通過查閱死者的電腦和手機(jī)誊垢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來症见,“玉大人喂走,你說我怎么就攤上這事⊥彩危” “怎么了缴啡?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瓷们。 經(jīng)常有香客問我业栅,道長秒咐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任碘裕,我火速辦了婚禮携取,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帮孔。我一直安慰自己雷滋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布文兢。 她就那樣靜靜地躺著晤斩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姆坚。 梳的紋絲不亂的頭發(fā)上澳泵,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音兼呵,去河邊找鬼兔辅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛击喂,可吹牛的內(nèi)容都是我干的维苔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼懂昂,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼介时!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忍法,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤潮尝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后饿序,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勉失,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年原探,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乱凿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咽弦,死狀恐怖徒蟆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情型型,我是刑警寧澤段审,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站闹蒜,受9級(jí)特大地震影響寺枉,放射性物質(zhì)發(fā)生泄漏抑淫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一姥闪、第九天 我趴在偏房一處隱蔽的房頂上張望始苇。 院中可真熱鬧,春花似錦筐喳、人聲如沸催式。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荣月。三九已至,卻和暖如春梳毙,著一層夾襖步出監(jiān)牢的瞬間喉童,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工顿天, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔑担。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓牌废,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啤握。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸟缕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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