重讀你不知道的JS (上) 第一節二章

你不知道的JS(上卷)筆記

你不知道的 JavaScript

JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具有許多複雜微妙技術的語言,即使是經驗豐富的 JavaScript 開發者,如果沒有認真學習的話也無法真正理解它們.

上捲包括倆節:

  • 作用域和閉包
  • this 和對象原型

作用域和閉包

希望 Kyle 對 JavaScript 工作原理每一個細節的批判性思 考會滲透到你的思考過程和日常工作中。知其然,也要知其所以然。

詞法作用域

作用域共有倆種主要的工作模型: 詞法作用域和動態作用域。

詞法階段

詞法化:大部分標準語言編譯器的第一個工作階段叫作詞法化(也叫單詞化)。
詞法化的過程會對源代碼中的字符進行檢查,如果是有狀態的解析過程,還會賦 予單詞語義。

詞法作用域

  1. 定義在詞法階段的作用域
  2. 由你在寫代碼時將變量和塊作用域寫在哪來決定的,因此當詞法分析器處理代碼時會保持作用域不變。

欺騙詞法作用域: 在詞法分析器處理過後依然可以修改作用域。

事實上,讓詞法作用域根據詞法關係保持書寫時的自然關係不變是一個非常好的最佳實踐。

“作用域氣泡法” 劃分作用域

查找

作用域氣泡的結構和互相之間的位置關係給引擎提供了足夠的位置信息,引擎用這些信息來查找標識符的位置。

  1. 作用域查找始終從運行時所處的最內部作用域開始,逐級向外或者向上進行。
  2. 作用域查找會在找到第一個匹配的標識符時停止,或者直至找到最後一個全局作用域處。
  3. window.a的方式可以訪問那些被同名變量遮蔽了的全局變量,但非全局變量如果被遮蔽,就無法訪問到了

遮蔽效應: 在多層的嵌套作用域中可以定義同名的標識符。

欺騙詞法

倆種欺騙手段:eval和with;
社區認爲使用這倆種機制並不是什麼好主意,因爲使用這倆種機制會導致性能下降
另外一個不推薦使用 eval(..) 和 with 的原因是會被嚴格模式所影響(限 制)。with 被完全禁止,而在保留核心功能的前提下,間接或非安全地使用 eval(..) 也被禁止了。

eval

JavaScript 中的 eval(..) 函數可以接受一個字符串爲參數,並將其中的內容視爲好像在書 寫時就存在於程序中這個位置的代碼。換句話說,可以在你寫的代碼中用程序生成代碼並 運行,就好像代碼是寫在那個位置的一樣。

function foo(str, a) {
  eval( str ); // 欺騙!
  console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

eval通常被用來執行動態創建的代碼
在這個例子中,爲了展示的方便和簡潔,我們傳遞進去的“代碼”字符串是 固定不變的。而在實際情況中,可以非常容易地根據程序邏輯動態地將字符 拼接在一起之後再傳遞進去。eval(..) 通常被用來執行動態創建的代碼,因 爲像例子中這樣動態地執行一段固定字符所組成的代碼,並沒有比直接將代 碼寫在那裏更有好處。

在嚴格模式的程序中,eval(..) 在運行時有其自己的詞法作用域,意味着其 中的聲明無法修改所在的作用域。

類似:setTimeout的第一個參數爲字符串時;new Function的最後一個字符串參數;等都不提倡,不要使用。

with

with 通常被當作重複引用同一個對象中的多個屬性的快捷方式,可以不需要重複引用對象 本身。

例如:

var obj = { 
  a: 1,
  b: 2,
  c: 3 
};
// 單調乏味的重複 "obj" obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡單的快捷方式
with (obj) {
  a = 3;
  b = 4;
  c = 5;
}
// 但實際上這不僅僅是爲了方便地訪問對象屬性。考慮如下代碼:
function foo(obj) {
  with (obj) {
    a = 2;
  }
}
var o1 = { a: 3 };
var o2 = { b: 3 };
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!

可以注意到一個奇怪的副作用,實際上 a = 2 賦值操作創建了一個全局的變量 a。這 是怎麼回事?

with 可以將一個沒有或有多個屬性的對象處理爲一個完全隔離的詞法作用域,因此這個對 象的屬性也會被處理爲定義在這個作用域中的詞法標識符。

儘管 with 塊可以將一個對象處理爲詞法作用域,但是這個塊內部正常的 var 聲明並不會被限制在這個塊的作用域中,而是被添加到 with 所處的函數作 用域中。

with 這種將對象及其屬性放進一個作用域並同時分配標識符的行爲很讓人費解。

性能

JavaScript 引擎會在編譯階段進行數項的性能優化。其中有些優化依賴於能夠根據代碼的 詞法進行靜態分析,並預先確定所有變量和函數的定義位置,才能在執行過程中快速找到 標識符。

小結

詞法作用域意味着作用域是由書寫代碼時函數聲明的位置來決定的。編譯的詞法分析階段 基本能夠知道全部標識符在哪裏以及是如何聲明的,從而能夠預測在執行過程中如何對它 們進行查找。

JavaScript 中有兩個機制可以“欺騙”詞法作用域:eval(..) 和 with。前者可以對一段包 含一個或多個聲明的“代碼”字符串進行演算,並藉此來修改已經存在的詞法作用域(在 運行時)。後者本質上是通過將一個對象的引用當作作用域來處理,將對象的屬性當作作 用域中的標識符來處理,從而創建了一個新的詞法作用域(同樣是在運行時)。

這兩個機制的副作用是引擎無法在編譯時對作用域查找進行優化,因爲引擎只能謹慎地認 爲這樣的優化是無效的。使用這其中任何一個機制都將導致代碼運行變慢。不要使用它們。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章