摘要:操作符或調(diào)用函數(shù)時(shí)傳入?yún)?shù)的操作都會(huì)導(dǎo)致關(guān)聯(lián)作用域的賦值操作。此外可以使用和來(lái)設(shè)置對(duì)象及其屬性的不可變性級(jí)別。忽視這一點(diǎn)會(huì)導(dǎo)致許多問(wèn)題。使用調(diào)用函數(shù)時(shí)會(huì)把新對(duì)象的屬性關(guān)聯(lián)到其他對(duì)象。
前言
《你不知道的 javascript》是一個(gè)前端學(xué)習(xí)必讀的系列,讓不求甚解的JavaScript開(kāi)發(fā)者迎難而上,深入語(yǔ)言?xún)?nèi)部,弄清楚JavaScript每一個(gè)零部件的用途。本書(shū)介紹了該系列的兩個(gè)主題:“作用域和閉包”以及“this和對(duì)象原型”。這兩塊也是值得我們反復(fù)去學(xué)習(xí)琢磨的兩塊只是內(nèi)容,今天我們用思維導(dǎo)圖的方式來(lái)精讀一遍。(思維導(dǎo)圖圖片可能有點(diǎn)小,記得點(diǎn)開(kāi)看,你會(huì)有所收獲)
第一部分 作用域和閉包 作用域是什么作用域是一套規(guī)則,用于確定在何處以及如何查找變量(標(biāo)識(shí)符)。如果查找的目的是對(duì) 變量進(jìn)行賦值,那么就會(huì)使用 LHS 查詢(xún);如果目的是獲取變量的值,就會(huì)使用 RHS 查詢(xún)。賦值操作符會(huì)導(dǎo)致 LHS 查詢(xún)。 的賦值操作。 =操作符或調(diào)用函數(shù)時(shí)傳入?yún)?shù)的操作都會(huì)導(dǎo)致關(guān)聯(lián)作用域的賦值操作。
JavaScript 引擎首先會(huì)在代碼執(zhí)行前對(duì)其進(jìn)行編譯,在這個(gè)過(guò)程中,像 var a = 2 這樣的聲 明會(huì)被分解成兩個(gè)獨(dú)立的步驟:
首先, var a 在其作用域中聲明新變量。這會(huì)在最開(kāi)始的階段,也就是代碼執(zhí)行前進(jìn)行。
接下來(lái), a = 2 會(huì)查詢(xún)(LHS 查詢(xún))變量 a 并對(duì)其進(jìn)行賦值。
LHS 和 RHS 查詢(xún)都會(huì)在當(dāng)前執(zhí)行作用域中開(kāi)始,如果有需要(也就是說(shuō)它們沒(méi)有找到所 需的標(biāo)識(shí)符),就會(huì)向上級(jí)作用域繼續(xù)查找目標(biāo)標(biāo)識(shí)符,這樣每次上升一級(jí)作用域(一層 樓),最后抵達(dá)全局作用域(頂層),無(wú)論找到或沒(méi)找到都將停止。
不成功的RHS引用會(huì)導(dǎo)致拋出 ReferenceError 異常。不成功的 LHS 引用會(huì)導(dǎo)致自動(dòng)隱式地創(chuàng)建一個(gè)全局變量(非嚴(yán)格模式下),該變量使用 LHS 引用的目標(biāo)作為標(biāo)識(shí)符,或者拋 出 ReferenceError 異常(嚴(yán)格模式下)。
詞法作用域詞法作用域意味著作用域是由書(shū)寫(xiě)代碼時(shí)函數(shù)聲明的位置來(lái)決定的。編譯的詞法分析階段 基本能夠知道全部標(biāo)識(shí)符在哪里以及是如何聲明的,從而能夠預(yù)測(cè)在執(zhí)行過(guò)程中如何對(duì)它 們進(jìn)行查找。
JavaScript 中有兩個(gè)機(jī)制可以“欺騙”詞法作用域: eval(..) 和 with 。 前者可以對(duì)一段包 含一個(gè)或多個(gè)聲明的“代碼”字符串進(jìn)行演算,并借此來(lái)修改已經(jīng)存在的詞法作用域(在 運(yùn)行時(shí))。后者本質(zhì)上是通過(guò)將一個(gè)對(duì)象的引用 當(dāng)作 作用域來(lái)處理,將對(duì)象的屬性當(dāng)作作 用域中的標(biāo)識(shí)符來(lái)處理,從而創(chuàng)建了一個(gè)新的詞法作用域(同樣是在運(yùn)行時(shí))。
這兩個(gè)機(jī)制的副作用是引擎無(wú)法在編譯時(shí)對(duì)作用域查找進(jìn)行優(yōu)化,因?yàn)橐嬷荒苤?jǐn)慎地認(rèn) 為這樣的優(yōu)化是無(wú)效的。使用這其中任何一個(gè)機(jī)制都 將 導(dǎo)致代碼運(yùn)行變慢。 不要使用它們。
函數(shù)作用域和塊作用域函數(shù)是 JavaScript 中最常見(jiàn)的作用域單元。本質(zhì)上,聲明在一個(gè)函數(shù)內(nèi)部的變量或函數(shù)會(huì) 在所處的作用域中“隱藏”起來(lái),這是有意為之的良好軟件的設(shè)計(jì)原則。
但函數(shù)不是唯一的作用域單元。塊作用域指的是變量和函數(shù)不僅可以屬于所處的作用域, 也可以屬于某個(gè)代碼塊(通常指 { .. } 內(nèi)部)。
從 ES3 開(kāi)始, try/catch 結(jié)構(gòu)在 catch 分句中具有塊作用域。在 ES6 中引入了 let 關(guān)鍵字( var 關(guān)鍵字的表親), 用來(lái)在任意代碼塊中聲明變量。 if(..) { let a = 2; } 會(huì)聲明一個(gè)劫持了 if 的 { .. } 塊的變量,并且將變量添加到這個(gè)塊 中。
有些人認(rèn)為塊作用域不應(yīng)該完全作為函數(shù)作用域的替代方案。兩種功能應(yīng)該同時(shí)存在,開(kāi) 發(fā)者可以并且也應(yīng)該根據(jù)需要選擇使用何種作用域,創(chuàng)造可讀、可維護(hù)的優(yōu)良代碼。
提升我們習(xí)慣將 var a = 2; 看作一個(gè)聲明,而實(shí)際上 JavaScript 引擎并不這么認(rèn)為。它將 var a 和 a = 2 當(dāng)作兩個(gè)多帶帶的聲明,第一個(gè)是編譯階段的任務(wù),而第二個(gè)則是執(zhí)行階段的任務(wù)。
這意味著無(wú)論作用域中的聲明出現(xiàn)在什么地方,都將在代碼本身被執(zhí)行前 首先 進(jìn)行處理。 可以將這個(gè)過(guò)程形象地想象成所有的聲明(變量和函數(shù))都會(huì)被“移動(dòng)”到各自作用域的最頂端,這個(gè)過(guò)程被稱(chēng)為提升。
聲明本身會(huì)被提升,而包括函數(shù)表達(dá)式的賦值在內(nèi)的賦值操作并不會(huì)提升。
要注意避免重復(fù)聲明,特別是當(dāng)普通的 var 聲明和函數(shù)聲明混合在一起的時(shí)候,否則會(huì)引 起很多危險(xiǎn)的問(wèn)題!
作用域閉包閉包就好像從 JavaScript 中分離出來(lái)的一個(gè)充滿(mǎn)神秘色彩的未開(kāi)化世界,只有最勇敢的人 才能夠到達(dá)那里。但實(shí)際上它只是一個(gè)標(biāo)準(zhǔn),顯然就是關(guān)于如何在函數(shù)作為值按需傳遞的 詞法環(huán)境中書(shū)寫(xiě)代碼的。
當(dāng)函數(shù)可以記住并訪(fǎng)問(wèn)所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這時(shí) 就產(chǎn)生了閉包。
如果沒(méi)能認(rèn)出閉包,也不了解它的工作原理,在使用它的過(guò)程中就很容易犯錯(cuò),比如在循 環(huán)中。但同時(shí)閉包也是一個(gè)非常強(qiáng)大的工具,可以用多種形式來(lái)實(shí)現(xiàn) 模塊 等模式。模塊有兩個(gè)主要特征:
(1)為創(chuàng)建內(nèi)部作用域而調(diào)用了一個(gè)包裝函數(shù);
(2)包裝函數(shù)的返回 值必須至少包括一個(gè)對(duì)內(nèi)部函數(shù)的引用,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉 包。
現(xiàn)在我們會(huì)發(fā)現(xiàn)代碼中到處都有閉包存在,并且我們能夠識(shí)別閉包然后用它來(lái)做一些有用 的事!
第二部分 this 和對(duì)象原型 this 全面解析如果要判斷一個(gè)運(yùn)行中函數(shù)的 this 綁定,就需要找到這個(gè)函數(shù)的直接調(diào)用位置。找到之后 就可以順序應(yīng)用下面這四條規(guī)則來(lái)判斷 this 的綁定對(duì)象。
由 new 調(diào)用?綁定到新創(chuàng)建的對(duì)象。
由 call 或者 apply (或者 bind )調(diào)用?綁定到指定的對(duì)象。
由上下文對(duì)象調(diào)用?綁定到那個(gè)上下文對(duì)象。
默認(rèn):在嚴(yán)格模式下綁定到 undefined ,否則綁定到全局對(duì)象。
一定要注意,有些調(diào)用可能在無(wú)意中使用默認(rèn)綁定規(guī)則。如果想“更安全”地忽略 this 綁 定,你可以使用一個(gè) DMZ 對(duì)象,比如 ? = Object.create(null) ,以保護(hù)全局對(duì)象。ES6中的箭頭函數(shù)并不會(huì)使用四條標(biāo)準(zhǔn)的綁定規(guī)則, 而是根據(jù)當(dāng)前的詞法作用域來(lái)決定 this ,具體來(lái)說(shuō),箭頭函數(shù)會(huì)繼承外層函數(shù)調(diào)用的 this 綁定(無(wú)論 this 綁定到什么)。這 其實(shí)和 ES6 之前代碼中的 self = this 機(jī)制一樣。
對(duì)象JavaScript 中的對(duì)象有字面形式(比如 var a = { .. } )和構(gòu)造形式(比如 var a = new Array(..) )。字面形式更常用,不過(guò)有時(shí)候構(gòu)造形式可以提供更多選項(xiàng)。
許多人都以為“JavaScript 中萬(wàn)物都是對(duì)象”,這是錯(cuò)誤的。對(duì)象是 6 個(gè)(或者是 7 個(gè),取 決于你的觀點(diǎn))基礎(chǔ)類(lèi)型之一。對(duì)象有包括 function 在內(nèi)的子類(lèi)型,不同子類(lèi)型具有不同 的行為,比如內(nèi)部標(biāo)簽 [object Array] 表示這是對(duì)象的子類(lèi)型數(shù)組。
對(duì)象就是鍵 / 值對(duì)的集合。可以通過(guò) .propName 或者 ["propName"] 語(yǔ)法來(lái)獲取屬性值。訪(fǎng) 問(wèn)屬性時(shí), 引擎實(shí)際上會(huì)調(diào)用內(nèi)部的默認(rèn) [[Get]] 操作(在設(shè)置屬性值時(shí)是 [[Put]] ), [[Get]] 操作會(huì)檢查對(duì)象本身是否包含這個(gè)屬性,如果沒(méi)找到的話(huà)還會(huì)查找 [[Prototype]] 鏈(參見(jiàn)第 5 章)。
屬性的特性可以通過(guò)屬性描述符來(lái)控制,比如 writable 和 configurable 。此外,可以使用 Object.preventExtensions(..) 、 Object.seal(..) 和 Object.freeze(..) 來(lái)設(shè)置對(duì)象(及其 屬性)的不可變性級(jí)別。
屬性不一定包含值——它們可能是具備 getter/setter 的“訪(fǎng)問(wèn)描述符”。此外,屬性可以是 可枚舉或者不可枚舉的,這決定了它們是否會(huì)出現(xiàn)在 for..in 循環(huán)中。
你可以使用 ES6 的 for..of 語(yǔ)法來(lái)遍歷數(shù)據(jù)結(jié)構(gòu)(數(shù)組、對(duì)象, 等等)中的值, for..of 會(huì)尋找內(nèi)置或者自定義的 @@iterator 對(duì)象并調(diào)用它的 next() 方法來(lái)遍歷數(shù)據(jù)值。
混合對(duì)象"類(lèi)"類(lèi)是一種設(shè)計(jì)模式。 許多語(yǔ)言提供了對(duì)于面向類(lèi)軟件設(shè)計(jì)的原生語(yǔ)法。 JavaScript 也有類(lèi) 似的語(yǔ)法,但是和其他語(yǔ)言中的類(lèi)完全不同。
類(lèi)意味著復(fù)制。
傳統(tǒng)的類(lèi)被實(shí)例化時(shí),它的行為會(huì)被復(fù)制到實(shí)例中。類(lèi)被繼承時(shí),行為也會(huì)被復(fù)制到子類(lèi) 中。
多態(tài)(在繼承鏈的不同層次名稱(chēng)相同但是功能不同的函數(shù))看起來(lái)似乎是從子類(lèi)引用父 類(lèi),但是本質(zhì)上引用的其實(shí)是復(fù)制的結(jié)果。
JavaScript 并不會(huì)(像類(lèi)那樣)自動(dòng)創(chuàng)建對(duì)象的副本。
混入模式(無(wú)論顯式還是隱式)可以用來(lái)模擬類(lèi)的復(fù)制行為,但是通常會(huì)產(chǎn)生丑陋并且脆 弱的語(yǔ)法,比如顯式偽多態(tài)( OtherObj.methodName.call(this, ...) ),這會(huì)讓代碼更加難 懂并且難以維護(hù)。
此外, 顯式混入實(shí)際上無(wú)法完全模擬類(lèi)的復(fù)制行為, 因?yàn)閷?duì)象(和函數(shù)!別忘了函數(shù)也 是對(duì)象)只能復(fù)制引用, 無(wú)法復(fù)制被引用的對(duì)象或者函數(shù)本身。 忽視這一點(diǎn)會(huì)導(dǎo)致許多 問(wèn)題。
總地來(lái)說(shuō),在 JavaScript 中模擬類(lèi)是得不償失的,雖然能解決當(dāng)前的問(wèn)題,但是可能會(huì)埋下更多的隱患。
原型如果要訪(fǎng)問(wèn)對(duì)象中并不存在的一個(gè)屬性, [[Get]] 操作(參見(jiàn)第 3 章)就會(huì)查找對(duì)象內(nèi)部 [[Prototype]] 關(guān)聯(lián)的對(duì)象。這個(gè)關(guān)聯(lián)關(guān)系實(shí)際上定義了一條“原型鏈”(有點(diǎn)像嵌套的作用域鏈),在查找屬性時(shí)會(huì)對(duì)它進(jìn)行遍歷。
所有普通對(duì)象都有內(nèi)置的 Object.prototype ,指向原型鏈的頂端(比如說(shuō)全局作用域),如 果在原型鏈中找不到指定的屬性就會(huì)停止。 toString() 、 valueOf() 和其他一些通用的功能 都存在于 Object.prototype 對(duì)象上,因此語(yǔ)言中所有的對(duì)象都可以使用它們。
關(guān)聯(lián)兩個(gè)對(duì)象最常用的方法是使用 new 關(guān)鍵詞進(jìn)行函數(shù)調(diào)用, 在調(diào)用的 章)中會(huì)創(chuàng)建一個(gè)關(guān)聯(lián)其他對(duì)象的新對(duì)象。4個(gè)步驟(第2章)中會(huì)創(chuàng)建一個(gè)關(guān)聯(lián)其他對(duì)象的新對(duì)象。
使用 new 調(diào)用函數(shù)時(shí)會(huì)把新對(duì)象的 .prototype 屬性關(guān)聯(lián)到“其他對(duì)象”。帶 new 的函數(shù)調(diào)用 通常被稱(chēng)為“構(gòu)造函數(shù)調(diào)用”,盡管它們實(shí)際上和傳統(tǒng)面向類(lèi)語(yǔ)言中的 類(lèi)構(gòu)造函數(shù) 不一樣。
JavaScript 是 中的機(jī)制有一個(gè)核心區(qū)別, 那就是不會(huì)進(jìn)行復(fù)制, 對(duì)象之間是通過(guò)內(nèi)部的
雖然這些 機(jī)制和傳統(tǒng)面向類(lèi)語(yǔ)言中的“類(lèi)初始化”和“類(lèi)繼承”很相似, 但是 javascript 機(jī)制和傳統(tǒng)面向?qū)ο箢?lèi)語(yǔ)言中的“類(lèi)初始化”和“類(lèi)繼承”很相似但是 javascript 中的機(jī)制有一個(gè)核心區(qū)別,就是不會(huì)進(jìn)行復(fù)制,對(duì)象之間是通過(guò)內(nèi)部的 [[Prototype]] 鏈關(guān)聯(lián)的。
出于各種原因,以“繼承”結(jié)尾的術(shù)語(yǔ)(包括“原型繼承”)和其他面向?qū)ο蟮男g(shù)語(yǔ)都無(wú) 法幫助你理解 JavaScript 的 真實(shí) 機(jī)制(不僅僅是限制我們的思維模式)。
相比之下,“委托”是一個(gè)更合適的術(shù)語(yǔ),因?yàn)閷?duì)象之間的關(guān)系不是 復(fù)制 而是委托。
行為委托在軟件架構(gòu)中你可以 選擇是否 使用類(lèi)和繼承設(shè)計(jì)模式。大多數(shù)開(kāi)發(fā)者理所當(dāng)然地認(rèn)為類(lèi)是 唯一(合適)的代碼組織方式,但是本章中我們看到了另一種更少見(jiàn)但是更強(qiáng)大的設(shè)計(jì)模式: 行為委托 。
行為委托認(rèn)為對(duì)象之間是兄弟關(guān)系, 互相委托, 而不是父類(lèi)和子類(lèi)的關(guān)系。 JavaScript 的 [[Prototype]] 機(jī)制本質(zhì)上就是行為委托機(jī)制。也就是說(shuō),我們可以選擇在 JavaScript 中努 力實(shí)現(xiàn)類(lèi)機(jī)制(參見(jiàn)第 4 和第 5 章),也可以擁抱更自然的 [[Prototype]] 委托機(jī)制。
當(dāng)你只用對(duì)象來(lái)設(shè)計(jì)代碼時(shí),不僅可以讓語(yǔ)法更加簡(jiǎn)潔,而且可以讓代碼結(jié)構(gòu)更加清晰。
對(duì)象關(guān)聯(lián)(對(duì)象之前互相關(guān)聯(lián))是一種編碼風(fēng)格,它倡導(dǎo)的是直接創(chuàng)建和關(guān)聯(lián)對(duì)象,不把 它們抽象成類(lèi)。對(duì)象關(guān)聯(lián)可以用基于 [[Prototype]] 的行為委托非常自然地實(shí)現(xiàn)。
擴(kuò)展思維導(dǎo)圖能比較清晰的還原整本書(shū)的知識(shí)結(jié)構(gòu)體系,如果你還沒(méi)用看過(guò)這本書(shū),可以按照這個(gè)思維導(dǎo)圖的思路快速預(yù)習(xí)一遍,提高學(xué)習(xí)效率。學(xué)習(xí)新事物總?cè)菀走z忘,我比較喜歡在看書(shū)的時(shí)候用思維導(dǎo)圖做些記錄,便于自己后期復(fù)習(xí),如果你已經(jīng)看過(guò)了這本書(shū),也建議你收藏復(fù)習(xí)。如果你有神馬建議或則想法,歡迎留言或加我微信交流:646321933
你不知道的javascript上卷第二部分在線(xiàn)文檔
你不知道的 javascript(上卷)PDF 下載地址
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107785.html
摘要:文和,創(chuàng)意實(shí)驗(yàn)室創(chuàng)意技術(shù)專(zhuān)家在機(jī)器學(xué)習(xí)和計(jì)算機(jī)視覺(jué)領(lǐng)域,姿勢(shì)預(yù)測(cè)或根據(jù)圖像數(shù)據(jù)探測(cè)人體及其姿勢(shì)的能力,堪稱(chēng)最令人興奮而又最棘手的一個(gè)話(huà)題。使用,用戶(hù)可以直接在瀏覽器中運(yùn)行機(jī)器學(xué)習(xí)模型,無(wú)需服務(wù)器。 文 / ?Jane Friedhoff 和 Irene Alvarado,Google 創(chuàng)意實(shí)驗(yàn)室創(chuàng)意技術(shù)專(zhuān)家在機(jī)器學(xué)習(xí)和計(jì)算機(jī)視覺(jué)領(lǐng)域,姿勢(shì)預(yù)測(cè)或根據(jù)圖像數(shù)據(jù)探測(cè)人體及其姿勢(shì)的能力,堪稱(chēng)最令人興...
摘要:中科院自動(dòng)化所,中科院大學(xué)和南昌大學(xué)的一項(xiàng)合作研究,提出了雙路徑,通過(guò)單一側(cè)面照片合成正面人臉圖像,取得了當(dāng)前較好的結(jié)果。研究人員指出,這些合成的圖像有可能用于人臉?lè)治龅娜蝿?wù)?;謴?fù)的圖像的質(zhì)量嚴(yán)重依賴(lài)于訓(xùn)練過(guò)程中的先驗(yàn)或約束條件。 中科院自動(dòng)化所(CASIA),中科院大學(xué)和南昌大學(xué)的一項(xiàng)合作研究,提出了雙路徑 GAN(TP-GAN),通過(guò)單一側(cè)面照片合成正面人臉圖像,取得了當(dāng)前較好的結(jié)果。研...
摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開(kāi)發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開(kāi)發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡(jiǎn)的前端持續(xù)集成的開(kāi)發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥(niǎo),不論是面試求職,還是日...
摘要:在這些文件的下載執(zhí)行過(guò)程中,用戶(hù)看到的則是一片空白。頁(yè)面仍然必須等到所有代碼下載并執(zhí)行完畢才能繼續(xù)渲染。 前言 kyrieliuの《高性能JavaScript》讀書(shū)筆記。 script標(biāo)簽是一個(gè)很霸道的狠角色,它的每次出現(xiàn)都讓頁(yè)面等待腳本的解析和執(zhí)行。也就是說(shuō),不管當(dāng)前的javascript代碼是內(nèi)嵌還是包含在外鏈文件中,頁(yè)面的下載和渲染都必須停下來(lái)等待腳本執(zhí)行完成。 其實(shí),scri...
閱讀 3429·2021-11-15 11:39
閱讀 1573·2021-09-22 10:02
閱讀 1319·2021-08-27 16:24
閱讀 3606·2019-08-30 15:52
閱讀 3418·2019-08-29 16:20
閱讀 832·2019-08-28 18:12
閱讀 559·2019-08-26 18:27
閱讀 726·2019-08-26 13:32