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)建一遍。解決方式 使用原型鏈,
- 我們創(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屬性了.
創(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)
思考如下情況:
- 我們知道, 可以通過(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)。
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
原型鏈存在的問(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ò)繼承的方式。
1.經(jīng)典繼承 2.組合繼承 3.原型式繼承 4.寄生式繼承 5.組合寄生式繼承(最終解決方案)
經(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)怎么樣?
- 組合繼承就是發(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í)例本身這一部分的.
原型式繼承的核心函數(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")
}
