js面向?qū)ο髥?wèn)題匯總(原型對(duì)象 原型鏈)

\color{red}{創(chuàng)建對(duì)象的方法}

1.字面量方法(最原始):

// 1.創(chuàng)建對(duì)象的字面量
var person = {
    name: "Coderwhy",
    age: 18,
    height: 1.88,

    sayHello: function () {
        alert("My name is " + this.name)
    }
}

// 2.調(diào)用對(duì)象的方法
person.sayHello()

明顯的缺點(diǎn): 使用同一個(gè)接口創(chuàng)建很多對(duì)象, 會(huì)產(chǎn)生大量的重復(fù)代碼.

2.工廠模式創(chuàng)建對(duì)象:

// 創(chuàng)建工廠函數(shù)
function createPerson(name, age, height) {
    var o = new Object()
    o.name = name
    o.age = age
    o.height = height

    o.sayHello = function () {
        alert("Hello, My name is " + this.name)
    }
    return o
}

// 創(chuàng)建兩個(gè)對(duì)象
var person1 = createPerson("Coderwhy", 18, 1.88)
var person2 = createPerson("Kobe", 30, 1.98)
person1.sayHello() // Hello, My name is Coderwhy
person2.sayHello() // Hello, My name is Kobe

函數(shù)createPerson()能夠根據(jù)接受的參數(shù)來(lái)構(gòu)建一個(gè)包含所有必要信息的Person對(duì)象
可以無(wú)限調(diào)用創(chuàng)建函數(shù),但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)。

3.構(gòu)造函數(shù)模式

// 構(gòu)造函數(shù)
function Person(name, age, height) {
    this.name = name
    this.age = age
    this.height = height

    this.sayHello = function () {
        alert(this.name)
    }
}

// 使用構(gòu)造函數(shù)創(chuàng)建對(duì)象
var person1 = new Person("Coderwhy", 18, 1.88)
var person2 = new Person("Kobe", 30, 1.98)
person1.sayHello() // Coderwhy
person2.sayHello() // Kobe

在這個(gè)例子中,Person()函數(shù)取代了createPerson()函數(shù)。
我們會(huì)發(fā)現(xiàn)這個(gè)函數(shù)有一些不太一樣的地方:
1)沒(méi)有顯式地創(chuàng)建對(duì)象;(比如創(chuàng)建一個(gè)Object對(duì)象)
2)直接將屬性和方法賦給了this對(duì)象;
3)沒(méi)有return語(yǔ)句

還有, 我們?cè)谡{(diào)用函數(shù)時(shí), 不再只是簡(jiǎn)單的函數(shù)+(), 而是使用了new關(guān)鍵字
這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷以下4個(gè)步驟:
1)創(chuàng)建一個(gè)新對(duì)象, 這個(gè)新的對(duì)象類型其實(shí)就是Person類型.
2)將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象,也就是this綁定);
3)執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性和方法);
4)返回新對(duì)象, 但是是默認(rèn)返回的, 不需要使用return語(yǔ)句;
在前面例子的最后,person1和person2分別保存著Person的一個(gè)不同的實(shí)例。
這兩個(gè)對(duì)象都有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向Person.
后面我們會(huì)詳細(xì)說(shuō)道constructor到底從何而來(lái), 所以你需要特別知道一下這里有這個(gè)屬性.

使用構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。解決方式 使用原型鏈,

\color{red}{原型對(duì)象}

\color{blue}{基本介紹}

  • 我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性
  • 這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象
  • 而這個(gè)對(duì)象的作用是存放這個(gè)類型創(chuàng)建的所有實(shí)例共享的屬性和方法。
  • 指向的這個(gè)對(duì)象, 就是我們的所謂的原型對(duì)象.
    原型對(duì)象的作用:
  • 使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法。
  • 換句話說(shuō),不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息,而是可以將這些信息直接添加到原型對(duì)象中。
    原型對(duì)象的創(chuàng)建:
  • 無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。
    原型上的constructor屬性:
  • 默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。用我們上面的例子來(lái)說(shuō), Person.prototype.constructor指向Person。
  • 也就是原型對(duì)象自身來(lái)說(shuō), 只有一個(gè)constructor屬性, 而其他屬性可以由我們添加或者從Object中繼承.
    新的實(shí)例創(chuàng)建時(shí), 原型對(duì)象在哪里呢?
  • 當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)內(nèi)部屬性,該屬性的指針, 指向構(gòu)造函數(shù)的原型對(duì)象。
    這個(gè)屬性是proto
  • 簡(jiǎn)單說(shuō), 每個(gè)實(shí)例中, 其實(shí)也會(huì)有一個(gè)屬性, 該屬性是指向原型對(duì)象的.


    1.jpg

    上面的圖解析了Person構(gòu)造函數(shù)、Person的原型屬性以及Person現(xiàn)有的兩個(gè)實(shí)例之間的關(guān)系

  • Person.prototype指向原型對(duì)象, 而Person.prototype.constructor又指回了Person.
  • 原型對(duì)象中除了包含constructor屬性之外,還包括后來(lái)添加的其他屬性。
  • Person的每個(gè)實(shí)例——personl和person2都包含一個(gè)內(nèi)部屬性proto,該屬性也指向原型對(duì)象;
    可以通過(guò)proto來(lái)修改原型的值(通常不會(huì)這樣修改, 知道即可)
    person1修改了name后, person2也會(huì)修改:
person1.sayHello() // Coderwhy
person2.sayHello() // Coderwhy

person1.__proto__.name = "Kobe"

person1.sayHello() // Kobe
person2.sayHello() // Kobe

但是要注意下面的情況:
當(dāng)我們給person1.name進(jìn)行賦值時(shí), 其實(shí)在給person1實(shí)例添加一個(gè)name屬性.
這個(gè)時(shí)候再次訪問(wèn)時(shí), 就不會(huì)訪問(wèn)原型中的name屬性了.

\color{red}{創(chuàng)建對(duì)象的方法(續(xù))比較推薦的方法}

創(chuàng)建自定義類型的最常見(jiàn)方式,就是組合使用構(gòu)造函數(shù)模式與原型模式。
構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性。
結(jié)果,每個(gè)實(shí)例都會(huì)有自己的一份實(shí)例屬性的副本,但同時(shí)又共享著對(duì)方法的引用,最大限度地節(jié)省了內(nèi)存。
另外,這種混成模式還支持向構(gòu)造函數(shù)傳遞參數(shù);可謂是集兩種模式之長(zhǎng)。

// 創(chuàng)建Person構(gòu)造函數(shù)
function Person(name, age, height) {
    this.name = name
    this.age = age
    this.height = height
    this.hobby = ["Basketball", "Football"]
}

// 重新Peron的原型對(duì)象
Person.prototype = {
    constructor: Person,
    sayHello: function () {
        alert("Hello JavaScript")
    }
}

// 創(chuàng)建對(duì)象
var person1 = new Person("Coderwhy", 18, 1.88)
var person2 = new Person("Kobe", 30, 1.98)

// 測(cè)試是否共享了函數(shù)
alert(person1.sayHello == person2.sayHello) // true

// 測(cè)試引用類型是否存在問(wèn)題
person1.hobby.push("tennis")
alert(person1.hobby)
alert(person2.hobby)

\color{red}{原型鏈}

\color{blue}{基本使用}

思考如下情況:

  • 我們知道, 可以通過(guò)Person.prototype = {}的方式來(lái)重寫原型對(duì)象.
  • 假如, 我們后面賦值的不是一個(gè){}, 而是另外一個(gè)類型的實(shí)例, 結(jié)果會(huì)是怎么樣呢?
  • 顯然,此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)地,另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針。
  • 假如另一個(gè)原型又是另一個(gè)類型的實(shí)例,那么上述關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。這就是所謂原型鏈的基本概念。
// 1.創(chuàng)建Animal的構(gòu)造函數(shù)
function Animal() {
    this.animalProperty = "Animal"
}

// 2.給Animal的原型中添加一個(gè)方法
Animal.prototype.animalFunction = function () {
    alert(this.animalProperty)
}

// 3.創(chuàng)建Person的構(gòu)造函數(shù)
function Person() {
    this.personProperty = "Person"
}

// 4.給Person的原型對(duì)象重新賦值
Person.prototype = new Animal()

// 5.給Person添加屬于自己的方法
Person.prototype.personFunction = function () {
    alert(this.personProperty)
}

// 6.創(chuàng)建Person的實(shí)例
var person = new Person()
person.animalFunction()
person.personFunction()
  • 重點(diǎn)我們來(lái)看第4步代碼: 給Person.prototype賦值了一個(gè)Animal的實(shí)例. 也就是Person的原型變成了Animal的實(shí)例.
  • Animal實(shí)例本身有一個(gè)proto可以指向Animal的原型.
    那么, 我們來(lái)思考一個(gè)問(wèn)題: 如果現(xiàn)在搜索一個(gè)屬性或者方法, 這個(gè)時(shí)候會(huì)按照什么順序搜索呢?
  • 第一步, 在person實(shí)例中搜索, 搜索到直接返回或者調(diào)用函數(shù). 如果沒(méi)有執(zhí)行第二步.
  • 第二步, 在Person的原型中搜索, Person的原型是誰(shuí)? Animal的實(shí)例. 所以會(huì)在Animal的實(shí)例中搜索, 無(wú)論是屬性還是方法, 如果搜索到則直接返回或者執(zhí)行. 如果沒(méi)有, 執(zhí)行第三步.
  • 第三步, 在Animal的原型中搜索, 搜索到返回或者執(zhí)行, 如果沒(méi)有, 搜索結(jié)束. (當(dāng)然其實(shí)還有Object, 但是先不考慮)

原型鏈簡(jiǎn)單總結(jié):

  • 通過(guò)實(shí)現(xiàn)原型鏈,本質(zhì)上擴(kuò)展了本章前面介紹的原型搜索機(jī)制。
  • 當(dāng)以讀取模式訪問(wèn)一個(gè)實(shí)例屬性時(shí),首先會(huì)在實(shí)例中搜索該屬性。如果沒(méi)有找到該屬性,則會(huì)繼續(xù)搜索實(shí)例的原型。在通過(guò)原型鏈實(shí)現(xiàn)繼承的情況下,搜索過(guò)程就得以沿著原型鏈繼續(xù)向上。
  • 在找不到屬性或方法的情況下,搜索過(guò)程總是要一環(huán)一環(huán)地前行到原型鏈末端才會(huì)停下來(lái)。

\color{blue}{原型和實(shí)例的關(guān)系}

instanceof操作符:

// instanceof
alert(person instanceof Object) // true
alert(person instanceof Animal) // true
alert(person instanceof Person) // true

isPrototypeOf()函數(shù)

// isPrototypeOf函數(shù)
alert("isPrototypeOf函數(shù)函數(shù)")
alert(Object.prototype.isPrototypeOf(person)) // true
alert(Animal.prototype.isPrototypeOf(person)) // true
alert(Person.prototype.isPrototypeOf(person)) // true

\color{blue}{原型鏈的問(wèn)題}

原型鏈存在的問(wèn)題:

  • 原型鏈存在最大的問(wèn)題是關(guān)于引用類型的屬性.
  • 通過(guò)上面的原型實(shí)現(xiàn)了繼承后, 子類的person對(duì)象繼承了(可以訪問(wèn))Animal實(shí)例中的屬性(animalProperty).
    但是如果這個(gè)屬性是一個(gè)引用類型(比如數(shù)組或者其他引用類型), 就會(huì)出現(xiàn)問(wèn)題.
// 1.定義Animal的構(gòu)造函數(shù)
function Animal() {
    this.colors = ["red", "green"]
}

// 2.給Animal添加方法
Animal.prototype.animalFunction = function () {
    alert(this.colors)
}

// 3.定義Person的構(gòu)造函數(shù)
function Person() {
    this.personProperty = "Person"
}

// 4.給Person賦值新的原型對(duì)象
Person.prototype = new Animal()

// 5.給Person添加方法
Person.prototype.personFunction = function () {
    alert(this.personProperty)
}

// 6.創(chuàng)建Person對(duì)象, 并且調(diào)用方法
var person1 = new Person()
var person2 = new Person()

alert(person1.colors) // red,green
alert(person2.colors) // red,green

person1.colors.push("blue")

alert(person1.colors) // red,green,blue
alert(person2.colors) // red,green,blue

我們查看第6步的操作

  • 創(chuàng)建了兩個(gè)對(duì)象, 并且查看了它們的colors屬性
  • 修改了person1中的colors屬性, 添加了一個(gè)新的顏色blue
  • 再次查看兩個(gè)對(duì)象的colors屬性, 會(huì)發(fā)現(xiàn)person2的colors屬性也發(fā)生了變化
  • 兩個(gè)實(shí)例應(yīng)該是相互獨(dú)立的, 這樣的變化如果我們不制止將會(huì)在代碼中引發(fā)一些列問(wèn)題.
    原型鏈的其他問(wèn)題:
    在創(chuàng)建子類型的實(shí)例時(shí),不能向父類型的構(gòu)造函數(shù)中傳遞參數(shù)。
  • 實(shí)際上,應(yīng)該說(shuō)是沒(méi)有辦法在不影響所有對(duì)象實(shí)例的情況下,給父類型的構(gòu)造函數(shù)傳遞參數(shù)。
  • 從而可以修改父類型中屬性的值, 在創(chuàng)建構(gòu)造函數(shù)的時(shí)候就確定一個(gè)值.
    解決方法通過(guò)繼承的方式。

\color{red}{繼承的主要方法}

1.經(jīng)典繼承 2.組合繼承 3.原型式繼承 4.寄生式繼承 5.組合寄生式繼承(最終解決方案)

\color{blue}{借用構(gòu)造函數(shù)--經(jīng)典繼承}

經(jīng)典繼承的做法非常簡(jiǎn)單: 在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用父類型構(gòu)造函數(shù).

  • 因?yàn)楹瘮?shù)可以在任意的時(shí)刻被調(diào)用
  • 因此通過(guò)apply()和call()方法也可以在新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù).
// 創(chuàng)建Animal的構(gòu)造函數(shù)
function Animal() {
    this.colors = ["red", "green"]
}

// 創(chuàng)建Person的構(gòu)造函數(shù)
function Person() {
    // 繼承Animal的屬性
    Animal.call(this)

    // 給自己的屬性賦值
    this.name = "Coderwhy"
}

// 創(chuàng)建Person對(duì)象
var person1 = new Person()
var person2 = new Person()

alert(person1.colors) // red,greem
alert(person2.colors) // red,greem
person1.colors.push("blue")
alert(person1.colors) // red,green,blue
alert(person2.colors) // red,green

代碼解析:

  • 我們通過(guò)在Person構(gòu)造函數(shù)中, 使用call函數(shù), 將this傳遞進(jìn)去.
  • 這個(gè)時(shí)候, 當(dāng)Animal中有相關(guān)屬性初始化時(shí), 就會(huì)在this對(duì)象上進(jìn)行初始化操作.
  • 這樣就實(shí)現(xiàn)了類似于繼承Animal屬性的效果.

存在問(wèn)題

  • 對(duì)于經(jīng)典繼承理解比較深入, 你已經(jīng)能發(fā)現(xiàn): 經(jīng)典繼承只有屬性的繼承, 無(wú)法實(shí)現(xiàn)方法的繼承.
  • 因?yàn)檎{(diào)用call函數(shù), 將this傳遞進(jìn)去, 只能將父構(gòu)造函數(shù)中的屬性初始化到this中.
  • 但是如果函數(shù)存在于父構(gòu)造函數(shù)的原型對(duì)象中, this中是不會(huì)有對(duì)應(yīng)的方法的.
    回顧原型鏈和經(jīng)典繼承:
  • 原型鏈存在的問(wèn)題是引用類型問(wèn)題和無(wú)法傳遞參數(shù), 但是方法可以被繼承
  • 經(jīng)典繼承是引用類型沒(méi)有問(wèn)題, 也可以傳遞參數(shù), 但是方法無(wú)法被繼承.
    怎么辦呢? 將兩者結(jié)合起來(lái)怎么樣?

\color{blue}{組合繼承}

  • 組合繼承就是發(fā)揮原型鏈和經(jīng)典繼承各自的優(yōu)點(diǎn)來(lái)完成繼承的實(shí)現(xiàn).
  • 使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承.
  • 通過(guò)經(jīng)典繼承實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承, 以及可以在構(gòu)造函數(shù)中傳遞參數(shù).
// 1.創(chuàng)建構(gòu)造函數(shù)的階段
// 1.1.創(chuàng)建Animal的構(gòu)造函數(shù)
function Animal(age) {
    this.age = age
    this.colors = ["red", "green"]
}

// 1.2.給Animal添加方法
Animal.prototype.animalFunction = function () {
    alert("Hello Animal")
}

// 1.3.創(chuàng)建Person的構(gòu)造函數(shù)
function Person(name, age) {
    Animal.call(this, age)
    this.name = name
}

// 1.4.給Person的原型對(duì)象重新賦值
Person.prototype = new Animal(0)

// 1.5.給Person添加方法
Person.prototype.personFunction = function () {
    alert("Hello Person")
}

// 2.驗(yàn)證和使用的代碼
// 2.1.創(chuàng)建Person對(duì)象
var person1 = new Person("Coderwhy", 18)
var person2 = new Person("Kobe", 30)

// 2.2.驗(yàn)證屬性
alert(person1.name + "-" + person1.age) // Coderwhy,18
alert(person2.name + "-" + person2.age) // Kobe,30

// 2.3.驗(yàn)證方法的調(diào)用
person1.animalFunction() // Hello Animal
person1.personFunction() // Hello Person

// 2.4.驗(yàn)證引用屬性的問(wèn)題
person1.colors.push("blue")
alert(person1.colors) // red,green,blue
alert(person2.colors) // red,green

組合繼承存在什么問(wèn)題呢?

  • 組合繼承最大的問(wèn)題就是無(wú)論在什么情況下, 都會(huì)調(diào)用兩次父類構(gòu)造函數(shù).
    一次在創(chuàng)建子類原型的時(shí)候
    另一次在子類構(gòu)造函數(shù)內(nèi)部(也就是每次創(chuàng)建子類實(shí)例的時(shí)候).
  • 另外, 所有的子類實(shí)例事實(shí)上會(huì)擁有兩份父類的屬性
    一份在當(dāng)前的實(shí)例自己里面(也就是person本身的), 另一份在子類對(duì)應(yīng)的原型對(duì)象中(也就是person.proto里面)
    當(dāng)然, 這兩份屬性我們無(wú)需擔(dān)心訪問(wèn)出現(xiàn)問(wèn)題, 因?yàn)槟J(rèn)一定是訪問(wèn)實(shí)例本身這一部分的.

\color{blue}{原型式繼承}

原型式繼承的核心函數(shù):

// 封裝object()函數(shù)
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

代碼解析:
在object()函數(shù)內(nèi)部, 先創(chuàng)建一個(gè)臨時(shí)的構(gòu)造函數(shù).
然后將傳遞的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型
最后返回了這個(gè)臨時(shí)類型的一個(gè)新的實(shí)例.
事實(shí)上, object()對(duì)傳入的對(duì)象執(zhí)行了一次淺復(fù)制.

// 定義object函數(shù)
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

// 定義寄生式核心函數(shù)
function inhreitPrototype(subType, superType) {
    var prototype = object(superType.prototype)
    prototype.constructor = subType
    subType.prototype = prototype
}
// 定義Animal構(gòu)造函數(shù)
function Animal(age) {
    this.age = age
    this.colors = ["red", "green"]
}

// 給Animal添加方法
Animal.prototype.animalFunction = function () {
    alert("Hello Animal")
}

// 定義Person構(gòu)造函數(shù)
function Person(name, age) {
    Animal.call(this, age)
    this.name = name
}

// 使用寄生組合式核心函數(shù)
inhreitPrototype(Person, Animal)

// 給Person添加方法
Person.prototype.personFunction = function () {
    alert("Hello Person")
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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