[TOC]
## 1.2 詞法作用域
詞法化(單詞化)的過程會對源代碼中的字符進行檢查,如果是有狀態的解析過程,還會賦予單詞語義。
### 1.2.1 詞法階段
**詞法作用域就是定義在詞法階段的作用域。**詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的,因此當詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)。
~~~
function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar( b * 3 );
}
foo( 2 ); // 2, 4, 12
~~~
作用域如

**作用域查找**會在找到第一個匹配的標識符時停止。在多層的嵌套作用域中可以定義同名的標識符,這叫作“遮蔽效應”(內部的標識符“遮蔽”了外部的標識符)。
全局變量會自動成為全局對象(比如瀏覽器中的window 對象)的屬性,可以不直接通過全局對象的詞法名稱,而是間接地通過對全局對象屬性的引用來對其進行訪問。
~~~
window.a
~~~
通過這種技術可以訪問那些被同名變量所遮蔽的全局變量。
無論**函數**在哪里被調用,也無論它如何被調用,它的詞法作用域都**只由函數被聲明時所處的位置決定。**
### 1.2.2 欺騙詞法(盡量不要使用)
JavaScript 中有**兩種機制**來實現在運行時修改詞法作用域。但在代碼中使用這兩種機制并不是什么好注意。**欺騙詞法作用域會導致性能下降。**
* `eval(..) `函數
可以接受一個字符串為參數,并將其中的內容視為好像在書寫時就存在于程序中這個位置的代碼。(在嚴格模式的程序中,`eval(..) `在運行時有其自己的詞法作用域,意味著其中的聲明無法修改所在的作用域。)
**總結:**`eval(...)`以對一段包含一個或多個聲明的“代碼”字符串進行演算,并借此來修改已經存在的詞法作用域(在運行時)。
~~~
function foo(str, a) {
eval( str ); // 欺騙!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
~~~
* `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; //實際上是LHS引用
}
}
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 被泄漏到全局作用域上了!
~~~
當我們傳遞o1 給with 時,with 所聲明的作用域是o1,而這個作用域中含有一個同o1.a 屬性相符的標識符。但當我們將o2 作為作用域時,其中并沒有a 標識符,因此進行了正常的LHS 標識符。查找o2 的作用域、foo(..) 的作用域和全局作用域中都沒有找到標識符a,因此當a=2 執行時,自動創建了一個全局變量(因為是非嚴格模式)。
`with` 可以將一個沒有或有多個屬性的對象處理為一個完全隔離的詞法作用域,因此這個對象的屬性也會被處理為定義在這個作用域中的詞法標識符。
### 1.2.3 性能
`eval(..) 和with `會在運行時修改或創建新的作用域,以此來欺騙其他在書寫時定義的詞法作用域。
JavaScript 引擎會在編譯階段進行數項的性能優化。其中有些**優化依賴**于能夠根據代碼的詞法進行**靜態分析**,并**預先確定**所有變量和函數的定義**位置**,才能在執行過程中快速找到標識符。
但如果引擎在代碼中發現了eval(..) 或with,它只能簡單地假設關于標識符位置的判斷都是無效的,因為無法在詞法分析階段明確知道eval(..) 會接收到什么代碼,這些代碼會如何對作用域進行修改,也無法知道傳遞給with 用來創建新詞法作用域的對象的內容到底是什么。甚至所有的優化可能都是無意義的,因此最簡單的做法就是完全不做任何優化。
- 前言
- 第一章 JavaScript簡介
- 第三章 基本概念
- 3.1-3.3 語法、關鍵字和變量
- 3.4 數據類型
- 3.5-3.6 操作符、流控制語句(暫略)
- 3.7函數
- 第四章 變量的值、作用域與內存問題
- 第五章 引用類型
- 5.1 Object類型
- 5.2 Array類型
- 5.3 Date類型
- 5.4 基本包裝類型
- 5.5 單體內置對象
- 第六章 面向對象的程序設計
- 6.1 理解對象
- 6.2 創建對象
- 6.3 繼承
- 第七章 函數
- 7.1 函數概述
- 7.2 閉包
- 7.3 私有變量
- 第八章 BOM
- 8.1 window對象
- 8.2 location對象
- 8.3 navigator、screen與history對象
- 第九章 DOM
- 9.1 節點層次
- 9.2 DOM操作技術
- 9.3 DOM擴展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件處理程序
- 10.3 事件對象
- 10.4 事件類型
- 第十一章 JSON
- 11.1-11.2 語法與序列化選項
- 第十二章 正則表達式
- 12.1 創建正則表達式
- 12.2-12.3 模式匹配與RegExp對象
- 第十三章 Ajax
- 13.1 XMLHttpRequest對象
- 你不知道的JavaScript
- 一、作用域與閉包
- 1.1 作用域
- 1.2 詞法作用域
- 1.3 函數作用域與塊作用域
- 1.4 提升
- 1.5 作用域閉包
- 二、this與對象原型
- 2.1 關于this
- 2.2 全面解析this
- 2.3 對象
- 2.4 混合對象“類”
- 2.5 原型
- 2.6 行為委托
- 三、類型與語法
- 3.1 類型
- 3.2 值
- 3.3 原生函數