你不知道的JS(上卷)

作用域是什么

1.1編譯原理

JavaScript引擎編譯的步驟與傳統(tǒng)的編譯語(yǔ)言類似。程序中的一段源代碼在執(zhí)行前會(huì)經(jīng)歷三個(gè)步驟。
分詞/語(yǔ)法分析:將字符串分解成有意義的代碼塊。這些代碼塊被稱為詞法單元。
解析/語(yǔ)法分析:將詞法單元流(數(shù)組)轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語(yǔ)法結(jié)構(gòu)的樹。
代碼生成:將樹轉(zhuǎn)換成可執(zhí)行代碼的過程。

1.2理解作用域

變量的賦值操作:如果之前沒聲明過這個(gè)變量,編譯器則在當(dāng)前作用域中聲明一個(gè)變量,然后運(yùn)行時(shí),引擎會(huì)在作用域中查找該變量,如果能夠找到則對(duì)它賦值。

function foo(a){
  var b = a;
  return a + b;
}
var c = foo(2);
//找到其中所有LHS查詢
//找到其中所有RHS查詢

RHS查詢:得到某某的值。LHS查詢:找到變量容器的本身。
上題答案:
LHS:c = .. 、a = 2 、b = ..
RHS: foo(2.. 、= a 、a .. 、..b

1.3作用域嵌套

當(dāng)一個(gè)塊或函數(shù)嵌套在另一個(gè)塊或函數(shù)中,就發(fā)生了作用域的嵌套。因此在當(dāng)前域中無(wú)法找到某個(gè)變量時(shí),引擎就會(huì)在外層嵌套的作用域中繼續(xù)查找,直到找到該變量。

1.4異常

在變量還沒有聲明的情況下,這兩種查詢的行為不一樣。
如果RHS查詢?cè)谒星短椎淖饔糜蛑姓也坏剿枳兞?,引擎?huì)拋出ReferenceError異常。
如果LHS查詢?cè)谒星短椎淖饔糜蛑姓也坏剿枳兞?,就?huì)在全局作用域中創(chuàng)建一個(gè)具有該名稱的變量,并將其返還給引擎,前提是在非嚴(yán)格模式下。
ReferenceError同作用域判別失敗相關(guān),而TypeError則代表作用域判別成功了,但是對(duì)結(jié)果的操作是不合理的。

詞法作用域

2.1詞法階段

詞法作用域就是定義在詞法階段的作用域。
作用域查找會(huì)在找到第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止。在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符。這叫做“遮蔽作用”。

2.2欺騙詞法

欺騙詞法作用域會(huì)導(dǎo)致性能下降。

使用eval

eval函數(shù)可以接受一個(gè)字符串為參數(shù),將其中的內(nèi)容視為好像在書寫時(shí)就存在于程序中這個(gè)位置的代碼。如果eval函數(shù)的參數(shù)是一個(gè)賦值語(yǔ)句,就相當(dāng)于欺騙詞法,真的在那里創(chuàng)建一樣,如果外作用域有相同的變量則永遠(yuǎn)不會(huì)被找到。在嚴(yán)格模式中,eval有自己的作用域,意味著其無(wú)法修改所在的作用域。

使用with

with通常被當(dāng)做重復(fù)引用同一個(gè)對(duì)象中的多個(gè)屬性的快捷方式。下面代碼在o2作用域中查找不到a變量,一直到全局也查找不到,所以LHS查詢會(huì)在全局作用域創(chuàng)建一個(gè)a變量。

function foo(obj){
  with(obj){
    a = 2;
  }
}
var o1 = {
  a:3
}
var o2 = {
  b:3
}
foo(o1);
console.log(o1.a); // 2

foo(02);
console.log(02.a); // undefined
console.log(a); // 泄漏到全局作用域

性能

出現(xiàn)eval()或者with時(shí),引擎只能簡(jiǎn)單地假設(shè)關(guān)于標(biāo)識(shí)符位置的判斷都是無(wú)效的。因?yàn)椴恢罆?huì)有什么代碼會(huì)傳進(jìn)eval,也不知道傳遞給with用來(lái)創(chuàng)建新詞法作用域的對(duì)象內(nèi)容到底是什么。

函數(shù)作用域和塊作用域

函數(shù)中的作用域

屬于這個(gè)函數(shù)的全部變量都可以在整個(gè)函數(shù)的范圍內(nèi)使用及復(fù)用。

隱藏內(nèi)部實(shí)現(xiàn)

最小限度的暴露必要內(nèi)容,而將其他內(nèi)容都“隱藏”起來(lái)。
可以避免同名標(biāo)識(shí)符之間的沖突。
避免沖突的方法:
1.全局命名空間。通常在全局作用域中聲明一個(gè)名字足夠獨(dú)特的變量,通常是一個(gè)對(duì)象。這個(gè)對(duì)象被用作命名空間,所有需要暴露給外界的功能都會(huì)成為這個(gè)對(duì)象的屬性,而不是將自己的標(biāo)識(shí)符暴露在頂級(jí)的詞法作用域中。
2.模塊管理。

函數(shù)作用域

在任意代碼片段外部添加包裝函數(shù),可以將內(nèi)部的變量和函數(shù)定義隱藏起來(lái)。但是這樣必須需要一個(gè)具名函數(shù),意味著這個(gè)名稱本身污染了所在作用域,其次,必須顯式的通過函數(shù)名來(lái)調(diào)用這個(gè)函數(shù)來(lái)運(yùn)行代碼。

可使用函數(shù)表達(dá)式來(lái)解決,區(qū)分函數(shù)聲明和函數(shù)表達(dá)式最簡(jiǎn)單方法就是看function關(guān)鍵字出現(xiàn)在聲明中的位置,如果function是第一個(gè)詞就是函數(shù)聲明,否則就是函數(shù)表達(dá)式。在函數(shù)表達(dá)式后加()可立即執(zhí)行。

立即執(zhí)行函數(shù)表達(dá)式進(jìn)階用法是把它們當(dāng)做函數(shù)調(diào)用并傳遞參數(shù)進(jìn)去。

塊作用域

用with從對(duì)象中創(chuàng)建出的作用域僅在with聲明中而非外部作用域中有效。
用try/catch的catch分句會(huì)創(chuàng)建一個(gè)塊作用域。
使用let,為塊作用域顯式地創(chuàng)建塊可以部分解決代碼混亂問題。使附屬關(guān)系變得更清晰。
使用const,定義常量,在之后如果修改此變量則會(huì)報(bào)錯(cuò)。

使用塊作用域?qū)τ陂]包及回收內(nèi)存垃圾的回收機(jī)制有關(guān)。

提升

只有聲明本身會(huì)被提升,而賦值或其他運(yùn)行邏輯會(huì)留在原地。函數(shù)聲明會(huì)被提升,但是函數(shù)表達(dá)式不會(huì)被提升。

函數(shù)優(yōu)先

函數(shù)首先被提升,其次是變量。

作用域和閉包

當(dāng)函數(shù)可以記住并訪問所在的詞法作用域,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。將函數(shù)當(dāng)作第一級(jí)的值進(jìn)行傳遞,就是閉包在這些函數(shù)中的應(yīng)用,回調(diào)函數(shù),實(shí)際上就是在使用閉包。

循環(huán)和閉包

for (var i = 1; i <= 5 ; i++){
  setTimeout(function timer() {
    console.log( i );
  },1000*i)
}

正常情況下,我們對(duì)這段代碼的預(yù)期是分別輸出1~5,每秒一次,每次一個(gè)。但實(shí)際上,這段代碼在運(yùn)行時(shí)會(huì)以每秒一次的頻率輸出五次6。因?yàn)檠舆t函數(shù)的回調(diào)會(huì)在循環(huán)結(jié)束時(shí)才執(zhí)行。因此需要用函數(shù)表達(dá)式自動(dòng)執(zhí)行來(lái)解決。

for (var i = 1; i <= 5 ; i++){
  (function(j){
    setTimeout(function timer() {
      console.log( j );
    },1000*j)
  })(i)
}

使用let在循環(huán)頭部定義變量,這個(gè)行為指出變量在循環(huán)過程中不止被聲明一次,每次迭代都會(huì)聲明。隨后的每個(gè)迭代都會(huì)使用上一個(gè)迭代結(jié)束時(shí)的值來(lái)初始化這個(gè)變量。

模塊

需要具備兩個(gè)條件:
1.必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)
2.封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
模塊模式用法:
命名將要作為API返回的對(duì)象。通過在模塊實(shí)例的內(nèi)部保存對(duì)公共API對(duì)象的內(nèi)部引用,可以從內(nèi)部對(duì)模塊實(shí)例進(jìn)行修改,包括添加或刪除方法和屬性,以及修改它們的值。
在ES6中可以使用import將一個(gè)模塊中的一個(gè)或多個(gè)API導(dǎo)入到當(dāng)前作用域中,并分別綁定在一個(gè)變量上。module會(huì)將整哥模塊的API導(dǎo)入并綁定到一個(gè)變量上。export會(huì)將當(dāng)前模塊的一個(gè)標(biāo)識(shí)符導(dǎo)出為公共API。這些操作可以在模塊定義中根據(jù)需要使用任意多次。

關(guān)于this

this提供了一種更優(yōu)雅的方式來(lái)隱式傳遞一個(gè)對(duì)象的引用,因此可以將API設(shè)計(jì)的更加簡(jiǎn)潔并且易于復(fù)用。
this的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式。

this的綁定對(duì)象規(guī)則

1.默認(rèn)綁定

// 獨(dú)立函數(shù)調(diào)用
function foo(){
  console.log( this.a );
}
var a = 2;
foo(); // 2

在代碼中,foo()是直接使用不帶任何修飾的函數(shù)引用進(jìn)行調(diào)用的,因此只能使用默認(rèn)綁定,無(wú)法應(yīng)用其他規(guī)則。如果使用嚴(yán)格模式,則不能將全局對(duì)象用于默認(rèn)綁定。
2.隱式綁定
調(diào)用位置是否有上下文對(duì)象,或者說是否被某個(gè)對(duì)象擁有或者包含,不過這種說法可能會(huì)造成一些誤導(dǎo)。

function foo(){
  console.log( this.a );
}
var obj = {
  a: 2,
  foo: foo
};
obj.foo(); // 2

當(dāng)函數(shù)引用有上下文對(duì)象時(shí),隱式綁定規(guī)則會(huì)把函數(shù)調(diào)用中的this綁定到這個(gè)上下文對(duì)象。因?yàn)檎{(diào)用foo()時(shí)this被綁定到obj,因此this.a和obj.a是一樣的。
3.顯示綁定
使用call()和apply()方法,它們的第一個(gè)參數(shù)是一個(gè)對(duì)象,是給this準(zhǔn)備的,接著在調(diào)用函數(shù)時(shí)將其綁定到this。

function foo(){
  console.log( this.a );
}
var obj = {
  a:2
}
foo.call( obj ); // 2

通過foo.call(),我們可以在調(diào)用foo時(shí)強(qiáng)制把它的this綁定到obj上。
4.new綁定
構(gòu)造函數(shù)只是一些使用new操作符時(shí)被調(diào)用的函數(shù)。它們不會(huì)屬于某個(gè)類,也不會(huì)實(shí)例化一個(gè)類。
使用new來(lái)調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時(shí),會(huì)自動(dòng)執(zhí)行下面的操作:

  • 創(chuàng)建一個(gè)全新的對(duì)象。
  • 這個(gè)新對(duì)象會(huì)被執(zhí)行[[Prototype]]連接。
  • 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this。
  • 如果函數(shù)沒有返回其它對(duì)象,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。

優(yōu)先級(jí)

  1. 函數(shù)是否在new中調(diào)用(new綁定)?如果是的話this綁定的是新創(chuàng)建的對(duì)象。
  2. 函數(shù)是否通過call、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話this綁定的是指定的對(duì)象。
  3. 函數(shù)是否在某個(gè)上下文對(duì)象中調(diào)用(隱式綁定)?如果是的話this綁定的是哪個(gè)上下文對(duì)象。
  4. 如果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就愛綁定到undefined,否則綁定到全局對(duì)象。

綁定例外

如果你把null或者undefined作為this的綁定對(duì)象傳入call、apply或者bind,這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)際采用的是默認(rèn)綁定規(guī)則。但是默認(rèn)綁定會(huì)把this綁定到全局對(duì)象,可能會(huì)修改全局對(duì)象。
可以創(chuàng)建一個(gè)空的非委托的對(duì)象var DMZ = Object.create( null );

this語(yǔ)法

箭頭函數(shù)不使用this的四種標(biāo)準(zhǔn)規(guī)則,而是根據(jù)外層(函數(shù)或者全局)作用域來(lái)決定this。箭頭函數(shù)的綁定無(wú)法被修改,new也不行。

對(duì)象

語(yǔ)法

對(duì)象可以通過兩種形式定義:聲明形式和構(gòu)造形式。

//聲明形式
var obj = {
  key:value
  // ...
}
// 構(gòu)造形式
var obj = new Object();
obj.key = value;

構(gòu)造形式和文字形式生成的對(duì)象是一樣的。唯一的區(qū)別是在文字聲明中你可以添加多個(gè)鍵值對(duì),但是在構(gòu)造形式中你必須逐個(gè)添加屬性。

類型

對(duì)象是JavaScript的基礎(chǔ)。一共有六種主要類型。

  • string
  • number
  • boolean
  • null
  • undefined
  • object
    注意簡(jiǎn)單基本類型(除object外)本身并不是對(duì)象。null有時(shí)會(huì)被當(dāng)作一種對(duì)象類型,但是這其實(shí)只是語(yǔ)言本身的一個(gè)bug,即對(duì)null執(zhí)行typeof null時(shí)會(huì)返回object,實(shí)際上,null本身是基本類型。

內(nèi)置對(duì)象

JavaScript中還有一些對(duì)象子類型,通常被稱為內(nèi)置對(duì)象。

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
    在JavaScript中,它們實(shí)際上只是一些內(nèi)置函數(shù)。這些內(nèi)置函數(shù)可以當(dāng)做構(gòu)造函數(shù)(由new產(chǎn)生的函數(shù)調(diào)用)來(lái)使用,從而可以構(gòu)造一個(gè)對(duì)應(yīng)子類型的新對(duì)象。

內(nèi)容

對(duì)象的內(nèi)容是由一些存儲(chǔ)在特定命名位置的值組成的,我們稱為屬性。在對(duì)象中,屬性名永遠(yuǎn)都是字符串。

可計(jì)算屬性名

ES6增加了可計(jì)算屬性名,可以在文字形式中使用[]包裹一個(gè)表達(dá)式來(lái)當(dāng)作屬性名。

var a = "abc";
var obj = {
  [a + 'cjx'] : "hello",
  [b + 'lcc'] : "world"
};
obj["abccjx"]; // hello
obj["abclcc"]; // world

數(shù)組

數(shù)組也支持[]訪問,數(shù)組期望的是數(shù)值下標(biāo)(存儲(chǔ)的位置),數(shù)組也是對(duì)象,可以為數(shù)組添加屬性。

復(fù)制對(duì)象

淺復(fù)制:復(fù)制出的新對(duì)象變量的值會(huì)復(fù)制舊對(duì)象中變量的值,但是在新對(duì)象中,屬性如果是引用,它們和舊對(duì)象中引用的對(duì)象是一樣的。ES6定義了Object.assign()方法來(lái)實(shí)現(xiàn)淺復(fù)制。
深復(fù)制:除了復(fù)制對(duì)象之外,還會(huì)復(fù)制對(duì)象的引用的對(duì)象。

屬性描述符

普通的對(duì)象屬性對(duì)應(yīng)的屬性描述符,除了包含value還包含另外第三個(gè)特性:writable(可寫)、enumerable(可枚舉)、configurable(可配置)

在創(chuàng)建普通屬性使,屬性描述符會(huì)使用默認(rèn)值,我們可以使用Object.defineProperty(...)來(lái)添加一個(gè)新屬性或修改一個(gè)已有屬性(如果是可配置的)并對(duì)特性進(jìn)行設(shè)置。

  • Writable
    決定是否可以修改屬性的值
  • Configurable
    只要屬性是可配置的,就可以使用Object.defineProperty(...)方法來(lái)修改屬性描述符。
  • Enumerable
    屬性是否會(huì)出現(xiàn)在對(duì)象的屬性枚舉中,如果你不想某些特殊屬性出現(xiàn)在枚舉中,那就把它設(shè)置為enumerable:false

不變性

  • 對(duì)象常量
    結(jié)合writable:falseconfigurable:false就可以創(chuàng)建一個(gè)真正的常量屬性。
  • 禁止擴(kuò)展
    如果想禁止一個(gè)對(duì)象添加新屬性并且保留已有屬性,使用Object.preventExtensions(..)
  • 密封
    Object.seal(..)會(huì)創(chuàng)建一個(gè)“密封”的對(duì)象,這個(gè)方法會(huì)在一個(gè)現(xiàn)有對(duì)象上調(diào)用Object.preventExtensions(..)并把所有現(xiàn)有屬性標(biāo)記為configurable:false。
  • 凍結(jié)
    Object.freeze(...)會(huì)創(chuàng)建一個(gè)凍結(jié)對(duì)象,這個(gè)方法實(shí)際上會(huì)在一個(gè)現(xiàn)有對(duì)象上調(diào)用Object.seal(..)并把所有“數(shù)據(jù)訪問”屬性標(biāo)記為writable:false

[[Get]]

var myObject = {
  a : 2
};
myObject.a; // 2

myObject.a是一次屬性訪問,但是這條語(yǔ)句并不僅僅是在myObject中查找名字為a的屬性。在語(yǔ)言規(guī)范中,myObject.a在myObject上實(shí)際上是實(shí)現(xiàn)了[[GET]]操作(有點(diǎn)像函數(shù)調(diào)用)。對(duì)象默認(rèn)的內(nèi)置[[GET]]操作首先在對(duì)象中查找是否有名稱相同的屬性。如果沒有找到,會(huì)遍歷可能存在的[[Prototype]]鏈。

[[Put]]

[[Put]]被觸發(fā)時(shí),實(shí)際的行為取決于許多因素,包括對(duì)象中是否已經(jīng)存在這個(gè)屬性。
如果已經(jīng)存在這個(gè)屬性,[[Put]]算法大致會(huì)檢查下面這些內(nèi)容:

  • 屬性是否是訪問描述符?如果是并且存在setter就調(diào)用setter。
  • 屬性的數(shù)據(jù)描述符中writable是否是false?如果是在非嚴(yán)格模式下靜默失敗,在嚴(yán)格模式下拋出TypeError
  • 如果都不是,將該值設(shè)置為屬性的值。

Getter和Setter

對(duì)象默認(rèn)的[[Put]]和[[Get]]操作分別可以控制屬性值的設(shè)置和獲取。
當(dāng)你給一個(gè)屬性定義getter、setter或者兩者都有時(shí),這個(gè)屬性會(huì)被定義為“訪問描述符”(和數(shù)據(jù)描述符相對(duì))。對(duì)于訪問描述符來(lái)說,JavaScript會(huì)忽略他們的value和writable特性。

存在性

我們可以在不訪問屬性值的情況下判斷對(duì)象中是否存在這個(gè)屬性:

var myObject = {
  a:2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("b"); // false

in操作符會(huì)檢查屬性是否在對(duì)象及其[[Prototype]]原型鏈中。hasOwnProperty(...)只會(huì)檢查屬性是否在myObject對(duì)象中,不會(huì)檢查原型鏈。

枚舉

enumerable屬性描述符,表示可枚舉性。當(dāng)設(shè)置為false時(shí),可以訪問值但是卻不會(huì)出現(xiàn)在for...in循環(huán)中,“可枚舉”就相當(dāng)于“可以出現(xiàn)在對(duì)象屬性的遍歷中”。
propertyIsEnumerable(..)會(huì)檢查給定的屬性名是否直接存在于對(duì)象中(而不是原型鏈中)并且滿足enumerable:true
Object.keys(..)會(huì)返回一個(gè)數(shù)組,包含所有可枚舉屬性。Object.getOwnPropertyNames(..)會(huì)返回一個(gè)數(shù)組包含所有屬性,無(wú)論他們是否可枚舉。
inhasOwnProperty(..)的區(qū)別在于是否查找[[Prototype]]鏈,然而Object.keys(..)Object.getOwnPropertyNames(..)都只會(huì)查找對(duì)象直接包含的屬性。

遍歷

forEach(..)會(huì)遍歷數(shù)組中的所有值并忽略回調(diào)函數(shù)的返回值。every(..)會(huì)一直運(yùn)行直到回調(diào)函數(shù)返回false。some(..)會(huì)一直運(yùn)行直到回調(diào)函數(shù)返回true。
for...of循環(huán)語(yǔ)法,首先回想被訪問對(duì)象請(qǐng)求一個(gè)迭代器對(duì)象,然后通過調(diào)用迭代器對(duì)象的next()方法來(lái)遍歷所有返回值。數(shù)組內(nèi)有內(nèi)置的@@iterator,因此for..of可以直接應(yīng)用在數(shù)組上。我們使用內(nèi)置的@@iterator來(lái)手動(dòng)遍歷數(shù)組。

var myArray = [1,2,3];
var it = myArray[Symbol.iterator]();

it.next();  // { value:1, done:false }
it.next(); // {value:2, done:false}
it.next(); // {value:3, done:false}
it.next();// {done:true}

調(diào)用迭代器next()方法會(huì)返回形式為{value: .. , done: ..}的值,value是當(dāng)前的遍歷值,done表示是否還有可以遍歷的值。

混合對(duì)象“類”

構(gòu)造函數(shù)

類實(shí)例是由一個(gè)特殊的類方法構(gòu)造的,這個(gè)方法名通常和類名相同,被稱為構(gòu)造函數(shù)。

類的繼承

定義好一個(gè)子類之后,相對(duì)于父類來(lái)說它就是一個(gè)獨(dú)立并且完全不同的類。子類會(huì)包含父類行為的原始副本,但是也可以重寫所有繼承的行為甚至定義新行為。

多態(tài)

任何方法都可以引用繼承層次中高層的方法(無(wú)論高層的方法名和當(dāng)前方法名是否相同)。在繼承鏈的不同層次中一個(gè)方法名可以被多次定義,當(dāng)調(diào)用方法時(shí)會(huì)自動(dòng)選擇合適的定義。

原型

[[Prototype]]

JavaScript中的對(duì)象有一個(gè)特殊的[[Prototype]]內(nèi)置屬性,其實(shí)就是對(duì)于其他對(duì)象的引用。

Object.prototype

所有普通的[[Prototype]]鏈最終都會(huì)指向內(nèi)置的Object.prototype。

屬性設(shè)置和屏蔽

如果一個(gè)屬性既出現(xiàn)在對(duì)象中頁(yè)出現(xiàn)在對(duì)象的[[Prototype]]鏈上層,那么就會(huì)發(fā)生屏蔽,對(duì)象包含的屬性總會(huì)屏蔽掉原型鏈上層的屬性,因?yàn)檫x擇屬性總是會(huì)選擇原型鏈中最底層的屬性。

“類”

所有的函數(shù)默認(rèn)都會(huì)擁有一個(gè)名為prototype的公有并且不可枚舉的屬性,它會(huì)指向另一個(gè)對(duì)象。

function Foo(){
  //...
}
Foo.prototype; // { }

這個(gè)對(duì)象通常被稱為Foo的原型。

function Foo(){
  //...
}
var a = new Foo();

Object.getPrototypeOf(a) === Foo.prototype; // true

調(diào)用new Foo()時(shí)會(huì)創(chuàng)建a,其中一步就是將a內(nèi)部的[[Prototype]]鏈接到Foo.prototype所指向的對(duì)象。

構(gòu)造函數(shù)

對(duì)構(gòu)造函數(shù)最準(zhǔn)確的解釋是,所有帶new的函數(shù)調(diào)用。函數(shù)不是構(gòu)造函數(shù),但是當(dāng)且僅當(dāng)使用new時(shí),函數(shù)調(diào)用會(huì)變成構(gòu)造函數(shù)調(diào)用。

對(duì)象關(guān)聯(lián)

[[Prototype]]機(jī)制就是存在于對(duì)象中的一個(gè)內(nèi)部鏈接,它會(huì)引用其他對(duì)象。作用是:如果在對(duì)象上沒有找到需要的屬性或者方法引用,引擎就會(huì)繼續(xù)在[[Prototype]]關(guān)聯(lián)的對(duì)象上進(jìn)行查找。同理,如果在后者中也沒有找到需要的引用就會(huì)繼續(xù)查找它的[[Prototype]],以此類推。這一系列對(duì)象的鏈接被稱為原型鏈。

行為委托

Task = {
    setID: function(ID) { this.id = ID; },
    outputID: function() { console.log( this.id ); }
};
// 讓XYZ委托Task
XYZ = Object.create(Task);

XYZ.prepareTask = function(ID,Label){
    this.setID(ID);
    this.Label = Label;
};

XYZ.outputTaskDetails = function() {
    this.outputID();
    console.log( this.Label );
};
  • 在上面代碼中,id和label數(shù)據(jù)成員都是直接儲(chǔ)存在XYZ上(而不是Task)。通常來(lái)說,在[[Prototype]]委托中最好把狀態(tài)保存在委托者(XYZ)而不是委托目標(biāo)(Task)上。
  • 在類設(shè)計(jì)模式中,我們故意讓父類和子類都有相同方法,這樣就可以利用重寫的優(yōu)勢(shì)。在委托行為中則恰好相反:我們會(huì)盡量避免在[[Prototype]]鏈的不同級(jí)別中使用相同的命名,否則就需要使用笨拙并且脆弱的語(yǔ)法來(lái)消除引用歧義。
  • this.setID(ID);XYZ中的方法首先會(huì)尋找XYZ自身是否有setID(..),但是XYZ中并沒有這個(gè)方法名,因此會(huì)通過[[Prototype]]委托關(guān)聯(lián)到Task繼續(xù)尋找,這時(shí)就可以找到setID(..)方法。
    委托行為意味著某些對(duì)象在找不到屬性或者方法引用時(shí)會(huì)把這個(gè)請(qǐng)求委托給另一個(gè)對(duì)象(Task)。

更好的語(yǔ)法

  • 在ES6中我們可以在任意對(duì)象的字面形式中使用簡(jiǎn)潔方法聲明,所以對(duì)象關(guān)聯(lián)風(fēng)格的對(duì)象可以不使用function(和class的語(yǔ)法糖一樣)唯一的區(qū)別就是對(duì)象的字面形式仍然需要使用“,”來(lái)分割元素,而class語(yǔ)法不需要。
  • 在ES6中可以使用對(duì)象的字面形式(這樣就可以使用簡(jiǎn)潔方法定義)來(lái)改寫之前繁瑣的屬性賦值語(yǔ)法,然后用Object.setPrototypeOf(..)來(lái)修改它的[[Prototype]]
最后編輯于
?著作權(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)容