成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

《JavaScript 面向?qū)ο缶?閱讀摘要

raise_yang / 2659人閱讀

摘要:屬性表明該對象可以被執(zhí)行,由于僅函數(shù)擁有該屬性,定義操作符對任何具有屬性的對象返回。對象自動存在于函數(shù)中。注意對象不是一個數(shù)組的實(shí)例,其擁有的方法與數(shù)組不同,返回。所有函數(shù)作用域內(nèi)都有一個對象代表該函數(shù)的對象。

高程面向?qū)ο筮@塊內(nèi)容介紹的比較淺顯,個人覺得這本小書是高程的補(bǔ)充,看完之后覺得收獲匪淺,所以做了個筆記,以備后詢

1. 原始類型和引用類型

Js中兩種基本數(shù)據(jù)類型:原始類型(基本數(shù)據(jù)類型)和引用類型;
原始類型保存為簡單數(shù)據(jù)值,引用類型則保存為對象,其本質(zhì)是指向內(nèi)存位置的應(yīng)用。
其它編程語言用棧存儲原始類型,用堆存儲引用類型,而js則不同:它使用一個變量對象追蹤變量的生存期。原始值被直接保存在變量對象里,而引用值則作為一個指針保存在變量對象內(nèi),該指針指向?qū)嶋H對象在內(nèi)存中的存儲位置。

1.1 原始類型(基本數(shù)據(jù)類型)

Js中一共有5種原始類型:booleannumber、string、null、undefined,除了null類型,都可以用typeof來判斷
原始類型的變量直接保存原始值(而不是一個指向?qū)ο蟮闹羔槪?dāng)原始值被賦給一個變量,該值將被復(fù)制到變量中,每個變量有它自己的一份數(shù)據(jù)拷貝

var color1="red",color2=color1
console.log(color1)    // red
console.log(color2)    // red
color1="blue"
console.log(color2)    // red
1.2 引用類型

對象(引用值)是引用類型的實(shí)例。對象是屬性的無序列表,屬性包含鍵和值,如果一個屬性的值是函數(shù),它就被稱為方法
Js中的函數(shù)其實(shí)是引用值,除了函數(shù)可以運(yùn)行以外,一個包含數(shù)組的屬性和一個包含函數(shù)的屬性沒什么區(qū)別。
Js中的構(gòu)造函數(shù)用首字母大寫來跟非構(gòu)造函數(shù)區(qū)分:var object = new Object()
因?yàn)橐妙愋筒辉谧兞恐兄苯颖4鎸ο?,所?b>object變量實(shí)際上并不包含對象的實(shí)例,而是一個指向內(nèi)存中實(shí)際對象所在位置的指針。

var object1 = new Object()
var object2 = object1

一個變量賦值給另一個變量時,兩個變量各獲得一個指針的拷貝,并且指向同一個內(nèi)存中的對象實(shí)例。
對象不使用時可以將引用解除:object = null,內(nèi)存中的對象不再被引用時,垃圾收集器(GC)會把那塊內(nèi)存挪作他用,在大型項(xiàng)目中尤為重要

1.3 原始封裝類型

原始封裝類型共3種:StringNumber、Boolean,使用起來跟對象一樣方便,當(dāng)讀取這三種類型時,原始封裝類型將被自動創(chuàng)建:

var name = "Nicholas"
var fisrtChar = name.charAt(0)
console.log(firstChar)                        // N

背后發(fā)生的故事:

// what js engine does
var name = "Nicholas"
var temp = new String(name)            // 字符串對象
var firstChar = temp.charAt(0)
temp = null
console.log(firstChar)                         // N

Js引擎創(chuàng)建了一個字符串的實(shí)例讓charAt(0)可以工作,字符串對象的存在僅用于該語句并且在隨后被銷毀(一種被稱為自動打包的過程)??梢詼y試:

var name = "Nicholas"
name.last = "zakas"
console.log(name.last)                // undefined

原始封裝類型的屬性會消失是因?yàn)楸惶砑訉傩缘膶ο罅⒖叹捅讳N毀了。
背后的故事:

var name = "Nicholas"
var temp = new String(name)
temp.last = "zakas"
temp = null                                            // temp對象銷毀

var temp = new String(name)
console.log(temp.last)                           // undefined
temp = null

實(shí)際上是在一個立刻就被銷毀的臨時對象上而不是字符串上添加了新的屬性,之后試圖再訪問該屬性,另一個不同的臨時對象被創(chuàng)建,而新屬性并不存在。雖然原始封裝類型會被自動創(chuàng)建,在這些值上進(jìn)行的instanceof檢查對應(yīng)類型的返回值卻是false

var name = "Nicholas", count = 10, found = false
console.log(name instanceof String)                    // false
console.log(count instanceof Number)                // false
console.log(found instanceof Boolean)                // false

這是因?yàn)榕R時對象僅在值(屬性)被讀取時被創(chuàng)建,instanceof操作符并沒有真的讀取任何東西,也就沒有臨時對象的創(chuàng)建。
如果使用手動創(chuàng)建對象和原始封裝類型之間有一定區(qū)別,比如:

var found = new Boolean(false)
if (found) {
    console.log("Found")            // 執(zhí)行了,因?yàn)閷ο笤趇f條件判斷時總被認(rèn)為是true,無論該對象是不是false,所以盡量避免手動創(chuàng)建原始封裝類型
}
2. 函數(shù)

使函數(shù)不同于其它對象是函數(shù)存在一個[[Call]]的內(nèi)部屬性。內(nèi)部屬性無法通過代碼訪問而是定義了代碼執(zhí)行時的行為。ECMAScript為Js的對象定義了多種內(nèi)部屬性,這些內(nèi)部屬性都用[[ ]]來標(biāo)注。[[Call]]屬性表明該對象可以被執(zhí)行,由于僅函數(shù)擁有該屬性,ECMAScript定義typeof操作符對任何具有[[Call]]屬性的對象返回function。

2.1 函數(shù)聲明與函數(shù)表達(dá)式

函數(shù)有兩種字面形式,函數(shù)聲明函數(shù)表達(dá)式,兩者有個非常重要的區(qū)別,函數(shù)聲明會被提升至上下文的頂部(要么是函數(shù)聲明時所在函數(shù)的范圍,要么是全局范圍),這意味著可以先使用再聲明函數(shù)。

2.2 函數(shù)就是值

函數(shù)可以像使用對象一樣使用,可以將它們賦給變量,在對象中添加它們,將它們當(dāng)成參數(shù)傳遞給別的函數(shù),或從別的函數(shù)中返回,基本上只要是可以使用其它引用值的地方,就可以使用函數(shù)。

2.3 參數(shù)

函數(shù)的參數(shù)實(shí)際上被保存在一個arguments的數(shù)組中,arguments可以自由增長來包含任意個數(shù)的值,它的length屬性可以告訴當(dāng)前有多少個值。
arguments對象自動存在于函數(shù)中。也就是說函數(shù)的命名參數(shù)不過是為了方便,并不真的限制了函數(shù)可接受參數(shù)的個數(shù)。

注意: arguments對象不是一個數(shù)組的實(shí)例,其擁有的方法與數(shù)組不同,Array.isArray(arguments)返回false。

函數(shù)期望的參數(shù)個數(shù)保存在函數(shù)的length屬性中。

2.4 重載

Js中不存在簽名,因此也不存在重載,聲明的同名函數(shù)后一個會覆蓋前一個。
不過可以對arguments對象獲取的參數(shù)個數(shù)進(jìn)行判斷來決定怎么處理。

2.5 對象方法

可以像添加屬性那樣給對象添加方法,注意定義數(shù)據(jù)屬性和方法的語法完全相同。

var person = {
    name: "Nicholas",
    sayName: function () {
        console.log(person.name)
    }
}
2.5.1 this對象

之前的例子的sayName()直接引用了person.name,在方法和對象之間建立了緊耦合,這種緊耦合使得一個方法很難被不同對象使用。
Js所有函數(shù)作用域內(nèi)都有一個this對象代表該函數(shù)的對象。在全局作用域內(nèi),this代表全局對象window,當(dāng)一個函數(shù)作為對象的方法被調(diào)用時,默認(rèn)this的值等于那個對象。改寫:

var person = {
    name: "Nicholas",
    sayName: function () {
        console.log(this.name)  
    }
}

所以應(yīng)該在方法內(nèi)引用this而不是直接引用對象??梢暂p易改變變量名,或者將函數(shù)用在不同對象上,而不用大量改動代碼。

function sayNameForAll() {
    console.log(this.name)
}
var person1={
    name: "Nicholas",
    sayName: sayNameForAll
}
var person2={
    name: "Greg" ,
    sayName: sayNameForAll
}
var name = "Micheal"
person1.sayName()                            // Nicholas
person2.sayName()                            // Greg
sayNameForAll()                                // Micheal

this在函數(shù)被調(diào)用時才被設(shè)置,因此最后sayNameForAll函數(shù)執(zhí)行時的this為全局對象。

2.5.2 改變this

有3種方法可以改變this,函數(shù)是對象,而對象可以有方法,所以函數(shù)也有方法。

call()

第一個用于操作this的方法是call(),它以指定的this和參數(shù)來執(zhí)行函數(shù),第一個參數(shù)為函數(shù)執(zhí)行時的this的值,后面的參數(shù)為需要被傳入函數(shù)的參數(shù)。

function sayNameForAll (label) {
    console.log(label + ":" + this.name)
}
var person1 = {name: "Nicholas"}
var person2 = {name: "Greg"}
var name = "Micheal"
sayNameForAll.call(this,"global")                        // global:Micheal
sayNameForAll.call(person1, "person1")             // person1:Nicholas
sayNameForAll.call(person2,"person2")              // person2:Greg
apply()

第二個用于操作this的方法時apply(),其工作方式與call()完全一樣,但它只接受兩個參數(shù):this的值和一個數(shù)組或者類似數(shù)組的對象,內(nèi)含需要被傳入函數(shù)的參數(shù)(可以把arguments對象作為apply的第二個參數(shù))。

function sayNameForAll (label) {
    console.log(label + ":" + this.name)
}
var person1 =  {name:"Nicholas"}
var person2 = {name:"Greg"}
var name = "Micheal"
sayNameForAll.apply(this,["global"])                        // global:Micheal
sayNameForAll.apply(person1, ["person1"])             // person1:Nicholas
sayNameForAll.apply(person2,["person2"])              // person2:Greg

如果你已經(jīng)有個數(shù)組,那么推介使用apply(),如果你有的是多帶帶的變量,則用call()

bind()

改變this的第三個函數(shù)方法為bind()bind()的第一個參數(shù)是要傳給新函數(shù)的this的值,其他參數(shù)代表需要被永久設(shè)置在新函數(shù)中的命名參數(shù),可以在之后繼續(xù)設(shè)置任何非永久參數(shù)。

function sayNameForAll (label) {
    console.log(label + ":" + this.name)
}
var person1 =  {name:"Nicholas"}
var person2 = {name:"Greg"}

var sayNameForPerson1 = sayNameForAll.bind(person1)
sayNameForPerson1("person1")                                                // person1:Nicholas
var sayNameForPerson2 = sayNameForAll.bind(person2,"person2")
sayNameForPerson2()                                                                // person2:Greg
person2.sayName = sayNameForPerson1;
person2.sayName("person2")                                                    // person2:Nicholas

sayNameForPerson1()沒有綁定永久參數(shù),因此可以繼續(xù)傳入label參數(shù)輸出,sayNameForPerson2()不僅綁定了person2作為this,而且綁定了第一個參數(shù)為person2,因此可以使用sayNameForPerson2()而不用傳入額外參數(shù),但是也不能更改了。person2.sayName最后由于this的值在sayNameForPerson1的函數(shù)表達(dá)式中已經(jīng)綁定為person1了,所以雖然sayNameForPerson1現(xiàn)在是person2的方法,它依然輸出person1.name的值。

3. 理解對象

Js中的對象是動態(tài)的,可以在代碼執(zhí)行的任意時刻發(fā)生改變。

3.1 定義屬性

當(dāng)一個屬性第一次被添加給對象時,Js在對象上隱式調(diào)用一個名為[[Put]]的內(nèi)部方法,[[Put]]方法會在對象上創(chuàng)建一個新節(jié)點(diǎn)保存屬性,就像第一次在哈希表上添加一個鍵一樣。這個操作不僅指定了初試的值,也定義了屬性的一些特征。
調(diào)用[[Put]]的結(jié)果是在對象上創(chuàng)建了一個自有屬性,該屬性被直接保存在實(shí)例內(nèi),對該屬性的所有操作都必須通過該對象進(jìn)行。
當(dāng)一個已有的屬性被賦予一個新值時,調(diào)用的是一個名為[[Set]]的方法,該方法將屬性的當(dāng)前值替換為新值。

3.2 屬性探測

由于屬性可以在任何時候添加,因此有時候有必要檢查對象是否已有該屬性:

if(person1.age){            // 不可取
    // 執(zhí)行
}

問題在于Js的類型強(qiáng)制會影響輸出結(jié)果,如果if判斷的值為null、undefined、0、false、NaN或者空字符串時則判斷為假。由于一個對象屬性可以包含這些假值,上例代碼可能導(dǎo)致錯誤的判斷,更可靠的判斷是用in操作符。
in操作符是在給定對象上查找一個給定名稱的屬性,如果找到則返回true,另外in操作符在判斷的時候不會評估屬性的值:

var person1={
    name: "Nicholas",
    age: "111",
    sayName:function(){
        consloe.log(this.name)
    }
}
console.log("name" in person1)            // true
console.log("age" in person1)              // true
console.log("title" in person1)              // false
console.log("sayName" in person1)            // true    方法是值為函數(shù)的屬性,因此同樣可以用in判斷

但是in操作符會檢查自有屬性和原型屬性,因此在只想要自有屬性的時候使用hasOwnProperty()判斷一下,該方法在給定的屬性存在并且為自有屬性時返回true。

3.3 刪除屬性

正如屬性可以在任何時候被添加,也可以在任何時候被刪除。但是設(shè)置一個屬性值為null并不能將其從對象中刪除,只是調(diào)用[[Set]]將null替換了該屬性原來的值。徹底的刪除屬性值需要delete操作符。
delete操作符針對單個對象調(diào)用[[Delete]]的內(nèi)部方法,可以認(rèn)為該操作在哈希表中移除了一個鍵值對,當(dāng)delete操作符成功時,它返回true。

注意: 某些屬性無法被delete。
var person1= {name: "Nicholas"}
console.log("name" in person1)                // true
delete person.name
console.log("name" in person1)                // false
console.log(person1.name)                          // undefined
3.4 屬性枚舉

所有你添加的屬性默認(rèn)為可枚舉的,可以用for-in循環(huán)遍歷,可枚舉屬性的內(nèi)部特征[[Enumerable]]都被設(shè)置為true。for-in循環(huán)會枚舉一個對象中所有的可枚舉屬性并將屬性名賦給一個對象:

var property
for (property in object){
    console.log("name:" + property)
    console.log("value" + object[property])
}

如果只需要獲取一個對象的屬性列表,ES5引入了Object.keys()方法,它可以獲取可枚舉屬性的名字(key)的數(shù)組。

注意:Object.keys()只返回自有屬性不返回原型屬性。
var properties = Object.keys(object)
var i, len=properties.length
for (i=0; i

并不是每個屬性都是可枚舉的,可以使用propertyIsEnumerable()方法檢查一個屬性是否為可枚舉,每個對象都有該方法。

var person1= {name: "Nicholas"}
var properties = Object.keys(person1)
console.log("name" in person1)                                                // true
console.log(person1.propertyIsEnumerable("name"))            // true
console.log("length" in properties)                                            // true
console.log(properties.propertiesIsEnumerable("length"))            // false

這里name為可枚舉,因?yàn)樗?b>person1的自有屬性,而propertieslength為不可枚舉的,因?yàn)樗?b>Array.prototype的內(nèi)建屬性,你會發(fā)現(xiàn)很多原生屬性默認(rèn)都是不可枚舉的。

3.5 屬性類型

屬性有兩種類型數(shù)據(jù)屬性訪問器屬性;
數(shù)據(jù)屬性包含一個值,例如之前的name屬性,[[Put]]方法默認(rèn)行為是創(chuàng)建一個數(shù)據(jù)屬性。
訪問器屬性不包含值而是定義了一個當(dāng)屬性被讀取時調(diào)用的函數(shù)getter和一個當(dāng)屬性被寫入時調(diào)用的函數(shù)setter。

let person1 = {
    _name: "Nicholas" ,                        // 前置下劃線是約定俗成的,表示該屬性為私有的,實(shí)際上它是公開的
    get name() {
        console.log("reading me")
        return this._name
    },
    set name(val) {
        console.log(`setting name to ${val}`)
        this._name = val
    }
}
console.log(person1.name)                // reading me Nicholas
person1.name="greg"
console.log(person1.name)                // setting name to Greg

用于定義namegettersetter的語法看上去像函數(shù)但是沒有function關(guān)鍵字,注意getset之后的name需要跟被訪問的屬性名保持一致。
當(dāng)你希望賦值操作會觸發(fā)一些行為或者讀取的值需要通過計算所需的返回值得到時,訪問器屬性將會很有用。

注意: 不一定要同時定義gettersetter,可以選擇其中之一,如果只定義getter,那么屬性變?yōu)橹蛔x,在非嚴(yán)格下寫入將失敗,嚴(yán)格下寫入報錯,如果只定義setter,那么屬性為只寫,兩種模式下讀取都失敗
3.6 屬性特征

ES5之前無法訪問屬性的任何特征,也沒有辦法指定一個屬性是否為可枚舉,因此ES5引入多種方法與屬性特征互動,同時也引入新的特征來支持額外的功能,現(xiàn)在已經(jīng)可以創(chuàng)建出和Js內(nèi)建屬性一樣的自定義屬性。下面介紹數(shù)據(jù)屬性和訪問器屬性的特征。

3.6.1 通用特征

有兩個屬性時數(shù)據(jù)屬性和訪問器屬性共有的:
[[Enumerable]]決定你是否可以遍歷該屬性;
[[Configurable]]決定該屬性是否可配置;
你可以用delete刪除一個可配置的屬性,或者隨時改變它,也可以把可配置的屬性從數(shù)據(jù)屬性變?yōu)樵L問器屬性,反之亦可,所有自有屬性都是可枚舉和可配置的。

如果你想改變屬性特征,可以使用Object.defineProperty()方法,它接受三個參數(shù):擁有函數(shù)的對象、屬性名、包含需要設(shè)置的特征的屬性描述對象。屬性描述對象具有和內(nèi)部特征同名的屬性但名字中不包含中括號,所以可以使用enumerable屬性來設(shè)置[[Enumerable]]特征,用configurable屬性來設(shè)置[[Configurable]]特征。假如你想讓一個對象屬性變成不可枚舉且不可配置:

var person1 = { name: "Nicholas" }
var properties = Object.keys(person1)

Object.defineProperty(person1, "name", { enumerable: false })
console.log("name" in person1)                          // true
console.log(person1.propertyIsEnumerable("name"))      // false
console.log(properties.length)                            // 0
Object.defineProperty(person1, "name", { configurable: false })
delete person1.name                                            // 屬性設(shè)置為不可配置之后不能被delete,刪除失敗
console.log("name" in person1)                        // true
console.log(person1.name)                            // Nicholas
Object.defineProperty(person1, "name", { configurable: true })    // error!    設(shè)置為不可配置之后就不能再設(shè)置屬性特征了,包括[[Configurable]]
3.6.2 數(shù)據(jù)屬性特征

數(shù)據(jù)屬性額外擁有兩個訪問器屬性不具備的特征:
[[Value]]包含屬性的值,當(dāng)你在對象上創(chuàng)建屬性時該特征被自動賦值,所有屬性的值都保存在[[Value]]中,哪怕該值是一個函數(shù);
[[Writable]]是一個布爾值,指示該屬性是否可以寫入,所有屬性默認(rèn)都是可寫的,除非另外指定。
通過這兩個額外屬性,可以使用Object.defineProperty()完整定義一個數(shù)據(jù)屬性,即使該屬性還不存在。

var person1 = { name: "Nicholas" }                // 等同于
Object.defineProperty(person, "name",  {
    value: "Nicholas",
    enumerable: true,
    configurable: true,
    writable: true
}

當(dāng)Object.defineProperty()被調(diào)用時,它首先檢查屬性是否存在,如果不存在將根據(jù)屬性描述對象指定的特征創(chuàng)建。當(dāng)使用Object.defineProperty()定義新屬性時一定記得為所有的特征指定一個值,否則布爾型的特征會被默認(rèn)設(shè)置為false。

var person1 = {}
Object.defineProperty(person1, "name", { value: "Nicholas" })    // 由于沒有顯式指定特征,因此屬性為不可枚舉、不可配置、不可寫的
console.log("name" in person1)                          // true
console.log(person1.propertyIsEnumerable("name"))      // false
delete person1.name
console.log("name" in person1)                    // true
person1.name = "Greg"
console.log(person1.name)                          // Nicholas
在嚴(yán)格模式下視圖改變不可寫屬性會拋出錯誤,而在非嚴(yán)格模式下會失敗
3.6.3 訪問器屬性

訪問器屬性擁有兩個數(shù)據(jù)屬性不具備的特征,訪問器屬性不需要儲存值,因此也就沒有[[Value]][[Writable]],取而代之的是[[Get]][[Set]]屬性,內(nèi)含gettersetter函數(shù),同字面量形式一樣,只需要定義其中一個特征就可以創(chuàng)建一個訪問器屬性。

如果試圖創(chuàng)建一個同時具有數(shù)據(jù)屬性和訪問器屬性的屬性,會報錯

之前get set 例子可以被改寫為:

let person1 = { _name: "Nicholas" }
Object.defineProperty(person1, "name", {
    get: function() {
      console.log("reading me")
      return this._name
    },
    set: function(val) {
      console.log(`setting name to ${val}`)
      this._name = val
    },
    enumerable: true,
    configurable: true
  }
)
console.log(person1.name)               // reading me Nicholas
person1.name = "greg"
console.log(person1.name)                // setting name to Greg

注意Object.defineProperty()中的get和set關(guān)鍵字,它們是包含函數(shù)的數(shù)據(jù)屬性,這里不能使用字面量形式。

3.6.4 定義多重屬性

如果你使用Object.defineProperties()而不是Object.defineProperty()可以為一個對象同時定義多個屬性,這個方法接受兩個參數(shù):需要改變的對象、一個包含所有屬性信息的對象。后者可以背看成一個哈希表,鍵是屬性名,值是為該屬性定義特征的屬性描述對象。

var person1 = {}
Object.defineProperties(person1, {
  _name: {
    value: "Nicholas",
    enumerable: true,
    configurable: true,
    writable: true
  },
  name: {
    get: function() {
      console.log("reading me")
      return this._name
    },
    set: function(val) {
      console.log(`setting name to ${val}`)
      this._name = val
    },
    enumerable: true,
    configurable: true
  }
})
3.6.5 獲取屬性特征

如果需要獲取屬性的特征,Js中可以使用Object.getOwnPropertyDescriptor(),這個方法只可以用于自有屬性,它接受兩個參數(shù):對象、屬性名。如果屬性存在,它會返回一個屬性描述對象,內(nèi)含四個屬性:configurable、enumerable、另外兩個根據(jù)屬性類型決定。即使你從沒有為屬性顯式指定特征,你依然會得到包含全部這些特征值的屬性描述對象。

3.7 禁止修改對象

對象和屬性一樣具有指導(dǎo)行為的內(nèi)部特征,其中,[[Extensible]]是一個布爾值,它指明該對象本身是否可以被修改,你創(chuàng)建的所有對象默認(rèn)都是可擴(kuò)展的,新的屬性可以隨時被添加,設(shè)置[[Extensible]]為false則可以禁止新屬性的添加。
下面有三種方法可以用來鎖定對象屬性

3.7.1 禁止擴(kuò)展

第一種方法是Object.preventExtensions()創(chuàng)建一個不可擴(kuò)展的對象。該方法接受一個參數(shù):你希望擴(kuò)展的對象。一旦在一個對象上用這個方法,就永遠(yuǎn)不能再給它添加新的屬性了。

let person1 = { _name: "Nicholas" }
console.log(Object.isExtensible(person1))            // true
Object.preventExtensions(person1)
console.log(Object.isExtensible(person1))            // false
person1.sayName = function(){
    console.log(this.name)
}
console.log("sayName" in person1)                // false
在嚴(yán)格模式下試圖給一個不可擴(kuò)展對象添加屬性會拋出錯誤,而在非嚴(yán)格模式下會失敗。應(yīng)該對不可擴(kuò)展對象使用嚴(yán)格模式,這樣當(dāng)一個不可擴(kuò)展對象被錯誤使用時你就會知道
3.7.2 對象封印

一個被封印的對象是不可擴(kuò)展的且其所有屬性都不可配置,這意味著不僅不能給對象添加屬性,而且也不能刪除屬性或改變類型(從數(shù)據(jù)屬性改變成訪問屬性或者反之),如果一個對象被封印,那么只能讀寫它的屬性。
可以用Object.seal()方法來封印一個對象,該方法被調(diào)用時[[Extensible]]特征被設(shè)置為false,其所有屬性的[[Configurable]]特征被置為false,可以使用Object.isSealed()來判斷一個對象是否被封印。
這段代碼封印了person1,因此不能再person1上添加或者刪除屬性。所有的被封印對象都是不可擴(kuò)展的對象,此時對person1使用Object.isExtensible()方法將會返回false,且視圖添加sayName()會失敗。
而且雖然person.name被成功改變成一個新值,但是刪除它會失敗。

確保對被封印的對象使用嚴(yán)格模式,這樣當(dāng)有人誤用該對象時,會報錯
3.7.3 對象凍結(jié)

被凍結(jié)的對象不能添加或刪除屬性,不能修改屬性類型,也不能寫入任何數(shù)據(jù)屬性。簡言而之,被凍結(jié)對象是一個數(shù)據(jù)屬性都為只讀的被封印對象。
Object.freeze() 凍結(jié)對象。
Object.isFrozen() 判斷對象是否被凍結(jié)。

被凍結(jié)對象僅僅只是對象在某個時間點(diǎn)上的快照,用途有限且很少被使用
4. 構(gòu)造函數(shù)和原型對象 4.1 構(gòu)造函數(shù)

構(gòu)造函數(shù)就是用new創(chuàng)建對象時調(diào)用的函數(shù),使用構(gòu)造函數(shù)的好處在于所有用同一個構(gòu)造函數(shù)創(chuàng)建的對象都具有同樣的屬性和方法。
構(gòu)造函數(shù)也是函數(shù),定義的方式和普通函數(shù)一樣,唯一的區(qū)別是構(gòu)造函數(shù)名應(yīng)該首字母大寫,以此區(qū)分。

function Person(){}
var person1 = new Person                        // 如果沒有要傳遞給構(gòu)造函數(shù)的參數(shù),括號可以省略
console.log(person1 instanceof Person)        // true
console.log(person1.constructor === Person)        // true

即使Person構(gòu)造函數(shù)沒有顯式返回任何東西,person1也會被認(rèn)為是一個新的Person類型的對象,new操作符會自動創(chuàng)建給定類型的對象并返回它們。每個對象在創(chuàng)建時都會自動擁有一個構(gòu)造函數(shù)屬性,其中包含了一個指向其構(gòu)造函數(shù)的引用。那些通過字面量形式或者Object構(gòu)造函數(shù)創(chuàng)建出來的泛用對象,其構(gòu)造函數(shù)屬性constructer指向Object;那些通過自定義構(gòu)造函數(shù)創(chuàng)建出來的對象,其構(gòu)造函數(shù)屬性指向創(chuàng)建它的構(gòu)造函數(shù)。

雖然對象實(shí)例及其構(gòu)造函數(shù)之間存在這樣的關(guān)系,但是還是建議使用instanceof來檢查對象類型,這是因?yàn)闃?gòu)造函數(shù)屬性可以被覆蓋,并不一定完全準(zhǔn)確。
在構(gòu)造函數(shù)中只需簡單的給this添加任何想要的屬性即可:

function Person(name){
    this.name =  name
    this.sayName() = function(){
        console.log(this.name)
    }
}

在調(diào)用構(gòu)造函數(shù)時,new會自動創(chuàng)建this對象,且其類型就是構(gòu)造函數(shù)的類型,構(gòu)造函數(shù)本身不需要返回一個對象,new操作符會幫你返回。

function Person2(name){
    this.name=name
    this.sayName=function(){
        console.log(this.name)
    }
}
var person2=new Person2("sam") 
console.log(person2.name)                    // sam
person2.sayName()                                // sam

每個對象都有自己的name屬性值,所以sayName可以根據(jù)不同對象返回不同的值。

也可以在構(gòu)造函數(shù)中顯式調(diào)用return,如果返回的是一個對象,那么它會替代新創(chuàng)建的對象實(shí)例返回,如果返回的是一個原始類型,那么它將被忽略,新創(chuàng)建的對象實(shí)例將被返回。

構(gòu)造函數(shù)允許使用一致的方法初始化一個類型的實(shí)例,在使用對象前設(shè)置好所有的屬性,可以在構(gòu)造函數(shù)中使用Object.defineProperty()的方法來幫助初始化。

function Person(name) {
    Object.defineProperty(this, "name", {
        get: function() {
            return name
        },
        set: function(newName) {
            name = newName
        },
        enumerable: true,
        configurable: true
    })

    this.sayName = function() {
        console.log(this.name)
    }
}

var person1 =new Person("Nicholas")                // 始終確保使用了new操作符,否則就是冒著改變?nèi)謱ο蟮娘L(fēng)險
console.log(person1 instanceof Person)            // true   
console.log(typeof person1)                               // object
console.log(name)                                              // undefined

當(dāng)Person不是被new調(diào)用時候,構(gòu)造函數(shù)中的this指向全局對象,由于Person構(gòu)造函數(shù)依靠new提供返回值,person1變量為undefined。沒有new,Person只不過是一個沒有返回語句的函數(shù),對this.name的賦值實(shí)際上創(chuàng)建了一個全局對象name。

嚴(yán)格模式下,不通過new調(diào)用Person構(gòu)造函數(shù)會出現(xiàn)錯誤,這是因?yàn)閲?yán)格模式并沒有為全局對象設(shè)置this,this保持為undefined,而試圖給undefined添加屬性時都會出錯

構(gòu)造函數(shù)允許給對象配置同樣的屬性,當(dāng)構(gòu)造函數(shù)并沒有消除代碼冗余,每個對象都有自己的sayName()方法,這意味著100個對象實(shí)例就有100個函數(shù)做相同的事情,只是使用的數(shù)據(jù)不同。如果所有的對象實(shí)例共享同一個方法會更有效率,該方法可以使用this.name來訪問對應(yīng)的數(shù)據(jù),這就需要用到原型對象。

4.2 原型對象

原型對象可以看做對象的基類,幾乎所有函數(shù)(除了一下內(nèi)建函數(shù))都有一個名為prototype的屬性,該屬性是一個原型對象用來創(chuàng)建新的對象實(shí)例。
所有創(chuàng)建的對象實(shí)例共享該原型對象,且這些對象實(shí)例可以訪問原型對象的屬性。例如,hasOwnProperty()方法被定義在泛用對象Object的原型對象中,但卻可以被任何對象當(dāng)做自己的屬性訪問。

var book = {title: "the principles of object-oriented js"}
console.log("title" in book)
console.log(book.hasOwnProperty("title"))                        // true
console.log("hasOwnProperty" in book)                            // true
console.log(book.hasOwnProperty("hasOwnProperty"))             // false
console.log(Object.prototype.hasOwnProperty("hasOwnProperty"))            // true

即使book中沒有hasOwnProperty()方法的定義,但仍然可以通過book.hasOwnProperty()訪問該方法,這是因?yàn)樵摲椒ù嬖谟?b>Object.prototype中。
可以使用這樣一個方法來判斷一個屬性是否為原型屬性:

function hasPrototypeProperty(object, name){
    return name in object && !object.hasOwnProperty(name)
}
4.2.1 [[Prototype]]屬性

一個對象實(shí)例通過內(nèi)部屬性[[Prototype]]追蹤其原型對象,該 屬性時一個指向該實(shí)例使用的原型對象的指針。當(dāng)你使用new創(chuàng)建一個新的對象時,構(gòu)造函數(shù)的原型對象會被賦給該對象的[[Prototype]]屬性 (JS proto 探究.md )。你可以調(diào)用Object.getPropertyOf()方法讀取[[prototype]]屬性的值。

Object.prototype.__proto__ === null
var object={}
Object.getPrototypeOf(object) === Object.prototype                // true
Object.prototype.isPrototypeOf(object)                    // true

任何一個泛用對象(字面量形式或者new Object()),其[[Prototype]]對象始終指向Object.prototype。也可以用isPrototypeOf()方法檢查某個對象是否是另一個對象的原型對象,該方法被包含在所有對象中。

Note:大部分Js引擎在所有對象上都支持一個__proto__的屬性,該屬性使你可以直接讀寫[[Prototype]]屬性。包括Firefox、Safari、Chrome、Node.js

在讀取一個對象的屬性時,Js引擎會首先在對象的自有屬性中查找屬性名字,如果找到則返回,如果沒有則Js會搜索[[Prototype]]中的對象,如果找到則返回,找不到則返回undefined

var object = {}
console.log(object.toString())                    // [object Object]
object.toString = function() {return "[object Custom]"}
console.log(object.toString())                    // [object Custom]
delete object.toString
console.log(object.toString())                    // [object Object]
delete object.toString
console.log(object.toString())                    // [object Object]

上例可以看出,delete運(yùn)算符只對只有屬性起作用,無法刪除一個對象的原型屬性。并且也不可以給一個對象的原型屬性賦值,對.toString的賦值只是在對象上創(chuàng)建了一個新的自有屬性,而不是改變原型屬性。

4.2.2 在構(gòu)造函數(shù)中使用原型對象

原型對象的共享機(jī)制使得它們成為一次性為所有對象定義所有方法的理想手段,因?yàn)橐粋€方法對所有的對象實(shí)例做相同的事,沒理由每個實(shí)例都要有一份自己的方法。將方法放在原型對象中并使用this方法當(dāng)前實(shí)例是更有效的做法。

function Person(name) {this.name = name}
Person.prototype.sayName = function() {console.log(this.name)};
var person1 = new Person("Nicholas")
console.log(person1.name)                        // Nicholas
person1.sayName()                                // Nicholas

也可以在原型對象上存儲其他類型的數(shù)據(jù),但是在存儲引用值時要注意,因?yàn)檫@些引用值會被多個實(shí)例共享,可能大家不希望一個實(shí)例能夠改變另一個實(shí)例的值。

function Person(name) {this.name = name}
Person.prototype.favorites = []
var person1 = new Person("Nicholas")
var person2 = new Person("Greg")
person1.favorites.push("pizza")
person2.favorites.push("quinoa")

console.log(person1.favorites)                // ["pizza", "quinoa"]
console.log(person2.favorites)                // ["pizza", "quinoa"]

favorites屬性被定義到原型對象上,意味著person1.favoritesperson2.favorites指向同一個數(shù)組,你對任意Person對象的favorites插入的值都將成為原型對象上數(shù)組的元素。也可以使用字面量的形式替換原型對象:

function Person(name) {this.name=name}
Person.prototype= {
    sayName: function() {console.log(this.name)},
    toString: function(){return `[Person ${this.name} ]`}
}

雖然用這種字面量的形式定義原型非常簡潔,但是有個副作用需要注意。

var person1 = new Person("Nicholas")
console.log(person1 instanceof Person)                // true
console.log(person1.constructor === Person)                // false
console.log(person1.constructor === Object)                // true

使用字面量形式改寫原型對象改寫了構(gòu)造函數(shù)的屬性,因此現(xiàn)在指向Object而不是Person,這是因?yàn)樵蛯ο缶哂袀€constructor屬性,這是其他對象實(shí)例所沒有的。當(dāng)一個函數(shù)被創(chuàng)建時,其prototype屬性也被創(chuàng)建,且該原型對象的constructor屬性指向該函數(shù)自己,當(dāng)使用字面量形式改寫原型對象Person.prototype時,其constructor屬性將被復(fù)寫為泛用對象Object。為了避免這一點(diǎn),需要在改寫原型對象時手動重置其constructor屬性:

function Person(name) {this.name = name}
Person.prototype = {
    constructor: Person,             // 為了不忘記賦值,最好在第一個屬性就把constructor重置為自己
    sayName() {console.log(this.name)},
    toString() {return `[Person ${this.name} ]`}
}

var person1 = new Person("Nicholas")
console.log(person1 instanceof Person)                    // true
console.log(person1.constructor === Person)                // true
console.log(person1.constructor === Object)                // false

構(gòu)造函數(shù)、原型對象、對象實(shí)例之間:對象實(shí)例和構(gòu)造函數(shù)之間沒有直接聯(lián)系。不過對象實(shí)例和原型對象之間以及原型對象和構(gòu)造函數(shù)之間都有直接聯(lián)系。

這樣的連接關(guān)系也意味著,如果打斷對象實(shí)例和原型對象之間的聯(lián)系,那么也將打斷對象實(shí)例及其構(gòu)造函數(shù)之間的關(guān)系。

4.2.3 改變原型對象

給定類型的所有對象實(shí)例共享一個原型對象,所以可以一次性擴(kuò)充所有對象實(shí)例。[[Prototype]]屬性只是包含了一個指向原型對象的指針,任何對原型對象的改變都將你可反映到所有引用它的對象實(shí)例上。這意味著給原型對象添加的新成員都可以立刻被所有已經(jīng)存在的對象實(shí)例使用。

function Person(name) {this.name = name}
Person.prototype = {
    constructor: Person,
    sayName() {console.log(this.name)},
    toString() {return `[Person ${this.name} ]`}
}
var person1 = new Person("Nicholas")
var person2 = new Person("Greg")
console.log("sayHi" in person1)                // false
console.log("sayHi" in person2)                // false
Person.prototype.sayHi = () => console.log("Hi")
person1.sayHi()                // Hi
person2.sayHi()                // Hi

當(dāng)對一個對象使用Object.seal()Object.freeze()封印和凍結(jié)對象的時候是在操作對象的自有屬性,無法添加封印對象的自有屬性和更改凍結(jié)對象的自有屬性,但是仍然可以通過在原型對象上添加屬性來擴(kuò)展對象實(shí)例:

function Person(name) {this.name = name}
var person1 = new Person("Nicholas")
Object.freeze(person1)
Person.prototype.sayHi = function() {console.log("Hi")};
person1.sayHi()            // Hi

其實(shí),[[Prototype]]是實(shí)例對象的自有屬性,屬性本身person1.[[Prototype]]被凍結(jié),但是指向的值Person.prototype并沒有凍結(jié)。

4.2.4 內(nèi)建對象的原型對象

所有內(nèi)建對象都有構(gòu)造函數(shù),因此也都有原型對象可以去改變,例如要在數(shù)組上添加一個新的方法只需要改變Array.prototype即可

Array.prototype.sum = function() {
    return this.reduce((privious, current) => privious + current)
}
var numbers = [1, 2, 3, 4, 5, 6]
var result = numbers.sum()
console.log(result)                    // 21

sum()函數(shù)內(nèi)部,在調(diào)用時this指向數(shù)組的對象實(shí)例numbers,因此this也可以調(diào)用該數(shù)組的其他方法,比如reduce()。
改變原始封裝類型的原型對象,就可以給這些原始值添加更多功能,比如:

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.substring(1)
}
var message = "hello world!"
console.log(message.capitalize())            // Hello world!
5. 繼承 5.1 原型對象鏈和Object.prototype

Js內(nèi)建的繼承方法被稱為原型對象鏈,又稱為原型對象繼承。原型對象的屬性可以由對象實(shí)例訪問。實(shí)例對象集成了原型對象的屬性,因?yàn)樵蛯ο笠彩且粋€對象,它也有自己的原型對象并繼承其屬性。這就是原型繼承鏈:對象繼承其原型對象,而原型對象繼承它的原型對象,以此類推。
所有的對象,包括自定義的對象都繼承自Object,除非另有指定。更確切的說,所有對象都繼承自Object.prototype,任何以字面量形式定義的對象,其[[Prototype]]的值都被設(shè)為Object.prototype,這意味著它繼承Object.prototype的屬性。

var book = {title: "a book"}
console.log(Object.getPrototypeOf(book) === Object.prototype)            // true
5.1.1 繼承自O(shè)bject.prototype的方法

前幾張用到的幾個方法都是定義在Object.prototype上的,因此可以被其他對象繼承:

Methods Usage
hasOwnProperty() 檢查是否存在一個給定名字的自有屬性
propertyIsEnumerable() 檢查一個自有屬性是否為可枚舉
isPrototypeOf() 檢查一個對象是否是另一個對象的原型對象
valueOf() 返回一個對象的值表達(dá)
toString() 返回一個對象的字符串表達(dá)

這幾種方法由繼承出現(xiàn)在所有的對象中,當(dāng)你需要對象在Js中以一致的方式工作時,最后兩個尤為重要。

valueOf()
每當(dāng)一個操作符被用于一個對象時就會調(diào)用valueOf()方法,其默認(rèn)返回對象實(shí)例本身。原始封裝類型重寫了valueOf()以使得它對String返回一個字符串,對Boolean返回一個布爾,對Number返回一個數(shù)字;類似的,對Date對象的valueOf()返回一個epoch時間,單位是毫秒(正如Data.prototype.getTime())。

var now = new Date                // now.valueOf()  === 1505108676169
var earlier = new Date(2010,1,1)            // earlier.valueOf() === 1264953600000
console.log(now>earlier)                // true
console.log(now-earlier)                 // 240155076169

now是一個代表當(dāng)前時間的Date,而earlier是過去的時間,當(dāng)使用操作符>時,兩個對象上都調(diào)用了valueOf()方法,你甚至可以用兩個Date相減來獲得它們在epoch時間上的差值。如果你的對象也要這樣使用操作符,你可以定義自己的valueOf()方法,定義的時候你并沒有改變操作符的行為,僅僅應(yīng)了操作符默認(rèn)行為所使用的值。

toString()
一旦valueOf()返回的是一個引用而不是原始值的時候,就會回退調(diào)用toString()方法。另外,當(dāng)Js期望一個字符串時也會對原始值隱式調(diào)用toString()。例如當(dāng)加號操作符的一邊是一個字符串時,另一邊就會被自動轉(zhuǎn)換成字符串,如果另一邊是一個原始值,會自動轉(zhuǎn)換成一個字符串表達(dá)(true => "true"),如果另一邊是一個引用值,則會調(diào)用valueOf(),如果其返回一個引用值,則調(diào)用toString()

var book = {title: "a book"}
console.log("book = " + book)                // "book = [object Object]"

因?yàn)閎ook是一個對象,因此調(diào)用它的toString()方法,該方法繼承自O(shè)bject.prototype,大部分Js引擎返回默認(rèn)值[object Object],如果對這個值不滿意可以復(fù)寫,為此類字符串提供包含跟多信息。

var book = {title: "a book",
toString(){return `[Book = ${this.title} ]`}}
console.log("book = " + book)                 // book = [Book = a book ]

5.1.2 修改Object.prototype

所有的對象都默認(rèn)繼承自O(shè)bject.prototype,所以改變它會影響到所有的對象,這是非常危險的。
如果給Obejct.prototype添加一個方法,它是可枚舉的,可以粗現(xiàn)在for-in循環(huán)中,一個空對象依然會輸出一個之前添加的屬性。盡量不要修改Object.prototype。

5.2 對象繼承

對象字面量形式會隱式指定Object.prototype為其[[Prototype]],也可以用Object.create()方式顯示指定。Object.create()方法接受兩個參數(shù):需要被設(shè)置為新對象[[Prototype]]的對象、屬性描述對象,格式如在Object.defineProperties()中使用的一樣(第三章)。

var book = {title: "a book"}
// ↑ 等價于 ↓
var book = Object.create(Object.prototype, {
    title: {
        configurable: true,
        enumerable: true,
        value: "a book",
        writable: true
    }
})

第一種寫法中字面量形式定義的對象自動繼承Object.prototype且其屬性默認(rèn)設(shè)置為可配置、可寫、可枚舉。第二種寫法顯示使用Object.create()做了相同的操作,兩個book對象的行為完全一致。

var person = {
    name: "Jack",
    sayName: function(){
        console.log(this.name);
    }
}

var student = Object.create(person, {
    name:{value: "Ljc"},
    grade: {
        value: "fourth year of university",
        enumerable: true,
        configurable: true,
        writable: true
    }
});

person.sayName(); // "Jack"
student.sayName(); // "Ljc"

console.log(person.hasOwnProperty("sayName")); // true
console.log(person.isPrototypeOf(student)); // true
console.log(student.hasOwnProperty("sayName")); // false
console.log("sayName" in student); // true

console.log(student.__proto__===person)                                      // true
console.log(student.__proto__.__proto__===Object.prototype)      // true

對象person2繼承自person1,也就集成了person1的name和sayName(),然而又通過重寫name屬性定義了一個自有屬性,隱藏并替代了原型對象中的同名屬性。所以person1.sayName()輸出Nicholas而person2.sayName()輸出Greg。
在訪問一個對象的時候,Js引擎會執(zhí)行一個搜索過程,如果在對象實(shí)例上發(fā)現(xiàn)該屬性,該屬性值就會被使用,如果沒有發(fā)現(xiàn)則搜索[[Prototype]],如果仍然沒有發(fā)現(xiàn),則繼續(xù)搜索該原型對象的[[Prototype]],知道繼承鏈末端,末端通常是一個Object.prototype,其[[prototype]]為null。這就是原型鏈。
當(dāng)然也可以通過Object.create()創(chuàng)建[[Prototype]]為null的對象:var obj=Object.create(null)。該對象obj是一個沒有原型鏈的對象,這意味著toString()valueOf等存在于Object原型上的方法都不存在于該對象上。

5.3 構(gòu)造函數(shù)繼承

Js中的對象繼承也是構(gòu)造函數(shù)繼承的基礎(chǔ),第四章提到:幾乎所有的函數(shù)都有prototype屬性(通過Function.prototype.bind方法構(gòu)造出來的函數(shù)是個例外),它可以被替換和修改。該prototype屬性被自動設(shè)置為一個繼承自O(shè)bject.prototype的泛用對象,該對象有個自有屬性constructor

// 構(gòu)造函數(shù)
function YourConstructor() {}
// Js引擎在背后做的:
YourConstructor.prototype = Object.create(Object.prototype, {
    constructor: {
        configurable: true,
        enumerable: true,
        value: YourConstructor,
        writable: true
    }
})
console.log(YourConstructor.prototype.__proto__===Object.prototype)            // true

你不需要做額外工作,Js引擎幫你把構(gòu)造函數(shù)的prototype屬性設(shè)置為一個繼承自O(shè)bject.prototype的對象,這意味著YourConstructor創(chuàng)建出來的任何對象都繼承自O(shè)bject.prototype,YouConstructor是Object的子類。
由于prototype可寫,可以通過改寫它來改變原型鏈:

function Rectangle(length, width) {
    this.length = length
    this.width = width
}
Rectangle.prototype.getArea = function() {return this.length * this.width};
Rectangle.prototype.toString = function() {return `[ Rectangle ${this.length}x${this.width} ]`};

function Square(size) {
    this.length = size
    this.width = size
}
Square.prototype = new Rectangle()
Square.prototype.constructor = Square
Square.prototype.toString = function() {return `[ Square ${this.length}x${this.width} ]`}

var rect = new Rectangle(5, 10)
var squa = new Square(6)
console.log(rect instanceof Rectangle)        // true
console.log(rect instanceof Square)        // false
console.log(rect instanceof Object)        // true
console.log(squa instanceof Rectangle)        // true
console.log(squa instanceof Square)        // true
console.log(squa instanceof Object)        // true
MDN:instanceof 運(yùn)算符可以用來判斷某個構(gòu)造函數(shù)的 prototype 屬性是否存在另外一個要檢測對象的原型鏈上。

Square構(gòu)造函數(shù)的prototype屬性被改寫為Rectagle的一個實(shí)例,此時不需要給Rectangle的調(diào)用提供參數(shù),因?yàn)樗鼈儾恍枰皇褂茫胰绻峁┝?,那么所有的Square對象實(shí)例都會共享這樣的維度。如果用這種方式改寫原型鏈,需要確保構(gòu)造函數(shù)不會再參數(shù)缺失時拋出錯誤(很多構(gòu)造函數(shù)包含的初始化邏輯)且構(gòu)造函數(shù)不會改變?nèi)魏稳譅顟B(tài)。

// inherits from Rectangle
function Square(size){
    this.length = size;
    this.width = size;
}

Square.prototype = new Rectangle(); // 盡管是 Square.prototype 是指向了 Rectangle 的對象實(shí)例,即Square的實(shí)例對象也能訪問該實(shí)例的屬性(如果你提前聲明了該對象,且給該對象新增屬性)。
// Square.prototype = Rectangle.prototype; // 這種實(shí)現(xiàn)沒有上面這種好,因?yàn)镾quare.prototype 指向了 Rectangle.prototype,導(dǎo)致修改Square.prototype時,實(shí)際就是修改Rectangle.prototype。
console.log(Square.prototype.constructor); // 輸出 Rectangle 構(gòu)造函數(shù)

Square.prototype.constructor = Square; // 重置回 Square 構(gòu)造函數(shù)
console.log(Square.prototype.constructor); // 輸出 Square 構(gòu)造函數(shù)

Square.prototype.toString = function(){
    return "[Square " + this.length + "x" + this.width + "]";
}

var rect = new Rectangle(5, 10);
var square = new Square(6);

console.log(rect.getArea()); // 50
console.log(square.getArea()); // 36

console.log(rect.toString()); // "[Rectangle 5 * 10]", 但如果是Square.prototype = Rectangle.prototype,則這里會"[Square 5 * 10]"
console.log(square.toString()); // "[Square 6 * 6]"

console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
console.log(square instanceof Object); // true

Square.prototype 并不真的需要被改成為一個 Rectangle 對象。事實(shí)上,是 Square.prototype 需要指向 Rectangle.prototype 使得繼承得以實(shí)現(xiàn)。這意味著可以用 Object.create() 簡化例子。

// inherits from Rectangle
function Square(size){
    this.length = size;
    this.width = size;
}

Square.prototype= Object.create(Rectangle.prototype, {
    constructor: {
        configurable: true,
        enumerable: true,
        value: Square,
        writable: true
    }
})
在對原型對象添加屬性前要確保你已經(jīng)改寫了原型對象,否則在改寫時會丟失之前添加的方法(因?yàn)槔^承是將被繼承對象賦值給需要繼承的原型對象,相當(dāng)于重寫了需要繼承的原型對象)。
5.4 構(gòu)造函數(shù)竊取

由于JavaScript中的繼承是通過原型對象鏈來實(shí)現(xiàn)的,因此不需要調(diào)用對象的父類的構(gòu)造函數(shù)。如果確實(shí)需要在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù),那就可以在子類的構(gòu)造函數(shù)中利用 call、apply方法調(diào)用父類的構(gòu)造函數(shù)。

function Rectangle(length, width) {
    this.length = length
    this.width = width
}
Rectangle.prototype.getArea = function() {return this.length * this.width};
Rectangle.prototype.toString = function() {return `[ Rectangle ${this.length}x${this.width} ]`};

function Square(size) {Rectangle.call(this, size, size)}
Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value: Square,
        enumerable: true,
        configurable: true,
        writable: true
    }
})
Square.prototype.toString = function() {return `[ Square ${this.length}x${this.width} ]`}

var rect = new Rectangle(5, 10)
var squa = new Square(6)
console.log(rect.getArea())
console.log(rect.toString())
console.log(squa.getArea())
console.log(squa.toString())

一般來說,需要修改 prototype 來繼承方法并用構(gòu)造函數(shù)竊取來設(shè)置屬性,由于這種做法模仿了那些基于類的語言的類繼承,所以這通常被稱為偽類繼承。

5.5 訪問父類方法

其實(shí)也是通過指定 callapply 的子對象調(diào)用父類方法。

6. 對象模式

可以使用繼承或者混入等其他技術(shù)令對象間行為共享,也可以利用Js高級技巧阻止對象結(jié)構(gòu)被改變。

6.1 私有成員和特權(quán)成員 6.1.1 模塊模式

模塊模式是一種用于創(chuàng)建擁有私有數(shù)據(jù)的單件對象的模式。
基本做法是使用立即調(diào)用函數(shù)表達(dá)式(IIFE)來返回一個對象。原理是利用閉包。

var yourObj = (function(){
    // private data variables   
    return {
        // public methods and properties
    }
}());

模塊模式還有一個變種叫暴露模塊模式,它將所有的變量和方法都放在 IIFE 的頭部,然后將它們設(shè)置到需要被返回的對象上。

//  一般寫法
var yourObj = (function(){
    var age = 25;    
    return {
        name: "Ljc",      
        getAge: function(){
            return age 
        }
    }
}());

// 暴露模塊模式,保證所有變量和函數(shù)聲明都在同一個地方
var yourObj = (function(){
    var age = 25;                            // 私有變量,外部無法訪問
    function getAge(){
        return age
    };
    return {
        name: "Ljc",                          // 公共變量外部可以訪問
        getAge: getAge                    // 外部可以訪問的對象
    }
}());
6.1.2 構(gòu)造函數(shù)的私有成員

模塊模式在定義單個對象的私有屬性十分有效,但對于那些同樣需要私有屬性的自定義類型呢?你可以在構(gòu)造函數(shù)中使用類似的模式來創(chuàng)建每個實(shí)例的私有數(shù)據(jù)。

function Person(name){
    // define a variable only accessible inside of the Person constructor
    var age = 22;   
    this.name = name;
    this.getAge = function(){return age;};
    this.growOlder = function(){age++;}
}

var person = new Person("Ljc");
console.log(person.age);         // undefined
person.age = 100;
console.log(person.getAge());         // 22
person.growOlder();
console.log(person.getAge());         // 23

構(gòu)造函數(shù)在被new的時候創(chuàng)建了一個本地作用于并返回this對象。這里有個問題:如果你需要對象實(shí)例擁有私有數(shù)據(jù),就不能將相應(yīng)方法放在 prototype上。
如果你需要所有實(shí)例共享私有數(shù)據(jù)(就好像它被定義在原型對象里那樣),則可結(jié)合模塊模式和構(gòu)造函數(shù),如下:

var Person = (function(){
    var age = 22;
    function InnerPerson(name){this.name = name;}
    InnerPerson.prototype.getAge = function(){return age;}
    InnerPerson.prototype.growOlder = function(){age++;};
    return InnerPerson;
}());

var person1 = new Person("Nicholash");
var person2 = new Person("Greg");
console.log(person1.name); // "Nicholash"
console.log(person1.getAge()); // 22
console.log(person2.name); // "Greg"
console.log(person2.getAge()); // 22

person1.growOlder();
console.log(person1.getAge()); // 23
console.log(person2.getAge()); // 23
6.2 混入

這是一種偽繼承。一個對象在不改變原型對象鏈的情況下得到了另外一個對象的屬性被稱為“混入”。因此,和繼承不同,混入讓你在創(chuàng)建對象后無法檢查屬性來源。

function mixin(receiver, supplier){
    for(var property in supplier){
        if(supplier.hasOwnProperty(property)){
            receiver[property] = supplier[property];
        }
    }
}

這是淺拷貝,如果屬性的值是一個引用,那么兩者將指向同一個對象。
要注意一件事,使用這種方式,supplier的訪問器屬性會被復(fù)制為receiver的數(shù)據(jù)屬性。

function mixin(reciver, supplier) {
    if (Object.getOwnPropertyDescriptor) {                    // 檢查是否支持es5
        Object.keys(supplier).forEach(property => {
            var descriptor = Object.getOwnPropertyDescriptor(supplier, property)
            Object.defineProperty(reciver, property, descriptor)
        })
    } else {
        for (var property in supplier) {                        // 否則使用淺復(fù)制
            if (supplier.hasOwnProperty(property)) {
                reciver[property] = supplier[property]
            }
        }
    }
}
6.3 作用域安全的構(gòu)造函數(shù)

構(gòu)造函數(shù)也是函數(shù),所以不用 new 也能調(diào)用它們來改變 this 的值。在非嚴(yán)格模式下, this 被強(qiáng)制指向全局對象。而在嚴(yán)格模式下,構(gòu)造函數(shù)會拋出一個錯誤(因?yàn)閲?yán)格模式下沒有為全局對象設(shè)置 this,this 保持為 undefined)。
而很多內(nèi)建構(gòu)造函數(shù),例如 Array、RegExp 不需要 new 也能正常工作,這是因?yàn)樗鼈儽辉O(shè)計為作用域安全的構(gòu)造函數(shù)。
當(dāng)用 new 調(diào)用一個函數(shù)時,this 指向的新創(chuàng)建的對象已經(jīng)屬于該構(gòu)造函數(shù)所代表的自定義類型。因此,可在函數(shù)內(nèi)用 instanceof 檢查自己是否被 new 調(diào)用。

function Person(name){
    if(this instanceof Person){
        // called with "new"
    }else{
        // called without "new"
    }
}

具體案例:

function Person(name){
    if(this instanceof Person){
        this.name = name;
    }else{
        return new Person(name);
    }
}

PS:歡迎大家關(guān)注我的公眾號【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備注加群,我拉你入群~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91738.html

相關(guān)文章

  • Javascript面向對象精要讀書筆記

    摘要:面向?qū)ο缶x書筆記下面代碼的實(shí)際執(zhí)行過程是什么使用原始值和原始封裝類型是有區(qū)別的因?yàn)槭潜唤馕龀梢粋€對象的,所以肯定是真的函數(shù)是對象,函數(shù)有兩種字面形式,第一種是函數(shù)聲明,以關(guān)鍵字開頭后面跟函數(shù)名字。 Javascript面向?qū)ο缶x書筆記 1、下面代碼的實(shí)際執(zhí)行過程是什么? var name = fan var str = name.charAt(0) console.l...

    roadtogeek 評論0 收藏0
  • JavaScript面向對象精要(二)

    摘要:使用時,會自動創(chuàng)建對象,其類型為構(gòu)造函數(shù)類型,指向?qū)ο髮?shí)例缺少關(guān)鍵字,指向全局對象。構(gòu)造函數(shù)本身也具有屬性指向原型對象。 在JavaScript面向?qū)ο缶?一)中講解了一些與面向?qū)ο笙嚓P(guān)的概念和方法,這篇講講原型和繼承。 構(gòu)造函數(shù)和原型對象 構(gòu)造函數(shù)也是函數(shù),用new創(chuàng)建對象時調(diào)用的函數(shù),與普通函數(shù)的一個區(qū)別是,其首字母應(yīng)該大寫。但如果將構(gòu)造函數(shù)當(dāng)作普通函數(shù)調(diào)用(缺少new關(guān)鍵字...

    wayneli 評論0 收藏0
  • JavaScript面向對象精要(一)

    摘要:使函數(shù)不同于其他對象的決定性特性是函數(shù)存在一個被稱為的內(nèi)部屬性。其中,是一個布爾值,指明改對象本身是否可以被修改值為。注意凍結(jié)對象和封印對象均要在嚴(yán)格模式下使用。 數(shù)據(jù)類型 在JavaScript中,數(shù)據(jù)類型分為兩類: 原始類型:保存一些簡單數(shù)據(jù),如true,5等。JavaScript共有5中原始類型: boolean:布爾,值為true或false number:數(shù)字,值...

    hiYoHoo 評論0 收藏0
  • JavaScript面向對象精要》讀書筆記

    摘要:解除引用的最佳手段是將對象變量設(shè)置為。字面形式允許你在不需要使用操作符和構(gòu)造函數(shù)顯示創(chuàng)建對象的情況下生成引用值。函數(shù)就是值可以像使用對象一樣使用函數(shù)因?yàn)楹瘮?shù)本來就是對象,構(gòu)造函數(shù)更加容易說明。 JavaScript(ES5)的面向?qū)ο缶?標(biāo)簽: JavaScript 面向?qū)ο?讀書筆記 2016年1月16日-17日兩天看完了《JavaScript面向?qū)ο缶罚▍⒓赢惒缴鐓^(qū)的活動送...

    GitCafe 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<