- 原文地址:javascriptissexy.com/understandi…
- 原文做者:Dan Wellman
- Markdown 地址:github.com/Yangfan2016…
- 译者:Yangfan2016
ES2015 最大的特性之一就是有了一个全新的做用域。在这个章节里,咱们将开始学习什么是做用域。咱们将继续学习如何建立新的做用域类型,以及给咱们代码带来的好处javascript
做用域描述为一个变量,函数,标识符能够被访问的区域。JavaScript 传统上有两种做用域类型:全局做用域和函数做用域,你定义变量的位置会影响其余代码是否能够访问。让咱们来看一个简单的例子来阐述做用域的概念。想象一下,你的 JavaScript 文件只包含如下代码:java
var globalVariable = 'This is global';
function globalFunction1() {
var innerVariable1 = 'Non-global variable 1';
}
function globalFunction2() {
var innerVariable2 = 'Non-global variable 2';
}
复制代码
在上面的代码中,咱们首先声明了一个变量 globalVariable
。这个语句不在函数内部,因此会自动存到全局做用域中。浏览器用 window
对象建立了一个全局做用域,除了能够用 globalVariable
访问,咱们还能够经过挂在 window
对象上的 window.globalVariable
访问。咱们能够在文件的任何地方访问这个变量,这两个函数的以前或以后,甚至是在函数的内部(这就是为何咱们说全局变量是 “隐藏的”,咱们能够在任何地方正确的访问他们),甚至是在附在同一页面的其余 JavaScript 文件git
在全局做用域里,咱们定义了两个函数,globalFunction1
和 globalFunction2
,就像全局变量同样,他们是 “可见的” 而且能够在这个文件的任何地方调用,也能够被同一页面的其余 JavaScript 文件调用。然而,当 JavaScript 引擎解析这些函数时,会分别建立他们本身的做用域。因吹斯听,这两个新的函数做用域被嵌套在全局做用域下,成为子做用域。这也就意味着函数内的代码能够访问全局变量,就像是和在函数 “内部的” 定义变量同样github
当咱们试图访问 JavaScript 里的标识符时,浏览器会首先在当前做用域中查找。若是没有找到,浏览器会在当前做用域的父做用域中查找,而且继续向上查找,直到找到这个变量,或者到达全局做用域为止。若是这个变量在全局做用域里依旧没有找到的话,那么浏览器会抛出一个 ReferenceError
错误。这种嵌套的做用域被称做做用域链,而这个检查当前做用域和父做用域的过程被称做变量查找。这种查找只会向上查找做用域链,它永远不会在它的子做用域里查找编程
在上面的做用域链查找方向咱们得知,例子中的 innerVariable1
变量只能在 globalFunction1
函数内部被访问,innerVariable2
变量只能在 globalFunction2
函数内部被访问。innerVariable1
变量不能在 globalFunction2
函数内部或全局做用域内被访问,innerVariable2
变量也不能在 globalFunction1
函数内部或全局做用域内被访问数组
下面的图片是上面代码中做用域的抽象表示:浏览器
全局做用域包含了
globalVariable
以及两个内嵌的函数做用域。每一个内嵌的函数做用域又包含本身的变量,可是这些变量不能被全局做用域访问。虚线表示的是做用域链的查找方向
让咱们来看下另外一个简短的代码示例,完全的了解下到目前为止咱们所介绍到的做用域概念。假设 JavaScript 文件只包含以下代码:编程语言
function outer() {
var variable1;
function inner() {
var variable2;
}
}
复制代码
在这段代码里,咱们在全局做用域里声明了一个叫 outer
的函数。由于它是一个函数,因此它建立了一个函数做用域,嵌套在全局做用域下。在这个做用域下,咱们又声明了一个叫 variable1
的变量和 一个叫 inner
的函数。由于 inner
也是一个函数,因此一个新的做用域又被建立了,嵌套在 outer
函数的做用域下函数
在 inner
函数中,咱们既能够访问 variable2
也能够访问 variable1
。当咱们在 inner
函数中访问 variable1
时,浏览器首先会在它的做用域里查找这个变量;当这个变量没有被找到时,会继续向上在父做用域里查找(也就是 outer
函数的做用域)。代码里做用域以下图所示:学习
函数做用域能够嵌套在其余的函数做用域里,可是做用域链查找规则是同样的,所以在
inner
做用域下能够访问到variable1
和variable2
,可是在outer
做用域下只能访问variable1
这个示例中的做用域链比较长,从 inner
函数延伸到 outer
函数,直到全局对象 window
在 JavaScript 中,一个块是由一个或多个语句用大括号包裹起来的。诸如 if
,for
,while
的条件表达式,都是用块基于特定的条件来执行块语句
其余流行的常见的编程语言都有块做用域,JavaScript 做用域中,直到现在却只有全局做用域和函数做用域,所以使咱们变得很困惑。ES2015 在 JavaScript 新增了块做用域,对于咱们的代码来讲有很大的影响,而且对于那些熟悉其余编程语言的开发者来讲变得更直观
块做用域意味着一个块能够建立它本身的做用域,而不是简单的存在于它最近到父级函数做用域或全局做用域下。让咱们在认识块做用域是如何工做的以前,先来了解下传统上块里的 JavaScript 是如何工做的:
function fn() {
var x = 'function scope';
if (true) {
var y = 'not block scope';
}
function innerFn() {
console.log(x, y); // function scope not block scope
}
innerFn();
}
复制代码
var
语句是不可以建立块做用域的,即便是在块里,所以 console.log
语句能够访问到 x
和 y
变量。 fn
函数建立了一个函数做用域并且 x
和 y
变量都是能够经过做用域内的做用域链访问到
理解提高的概念是理解 JavaScript 如何工做的基础。JavaScript 有两个阶段:解析阶段(JavaScript 引擎读取全部的代码)、执行阶段(执行已解析的代码)。大多数的事情都发生在第二阶段;例如,当你使用 console.log
语句时,实际的日志消息会在执行阶段打印到控制台
然而,一些重要的事情也会在解析阶段发生,包括变量的内存分配、做用域建立。提高这个术语指的是 JavaScript 引擎在遇到标识符,如变量、函数声明时所发生到事情;当发生声明提高时,它的行为就像是把它定义的字面量提高到当前做用域的顶部。鉴于此,上面到代码示例实际会变成以下状况:
function fn() {
var x;
var y;
x = 'function scope';
if (true) {
y = 'not block scope';
}
function innerFn() {
console.log(x, y); // function scope not block scope
}
innerFn();
}
复制代码
只有变量到声明会提高到它的做用域的顶部;在这个例子的 if
语句中,变量赋值依然发生在咱们所赋值的地方。固然,咱们到变量并不会移动,而是引擎行为表现如此,所以这样能够更好的帮助咱们理解代码
除了变量,函数声明也会被提高。结果就是,从 JavaScript 引擎到角度来看,代码实际上看起来是这样的:
function fn() {
var x;
var y;
function innerFn() {
console.log(x, y); // function scope not block scope
}
x = 'function scope';
if (true) {
y = 'not block scope';
}
innerFn();
}
复制代码
innerFn
的声明也被提高到了它的做用域的顶部。可是,记住它仅仅是函数声明被提高了,函数调用没有被提高。上面的代码并不会报任何错,由于 innerFn
在 x
和 y
赋值以前并无被调用
let
即便使用了 ES2015,var
声明也不会建立块做用域。为了建立块做用域,咱们须要在块里使用 let
或 const
声明。咱们一会再看 const
,首先来看下 let
表面上,let
和 var
(咱们用它来声明变量)的行为很类似:
function fn() {
var variable1;
let variable2;
}
复制代码
在这个简单的例子中,var
和 let
声明都作了相同的事情(在 fn
建立的做用域下初始化了一个新的变量)。为了建立一个新的块做用域,咱们须要在块里使用 let
:
function fn() {
var variable1 = 'function scope';
if (true) {
let variable2 = 'block scope';
}
console.log(variable1, variable2); // Uncaught ReferenceError: variable2 is not defined
}
fn();
复制代码
在这个代码示例中,抛出了一个引用错误(reference error);让咱们来探索下为何会这样。fn
函数建立了一个新做用域,里面声明了变量 variable1
。而后咱们在 if
语句的块里,声明了变量 variable2
。然而,由于咱们在块里使用了 let
声明,所以一个新的块做用域在 fn
的做用域下被建立了
若是 console.log
语句也在 if
块中的话,那么它就和 variable2
在相同的做用域下了,也可以经过做用域链找到 variable1
。可是由于 console.log
在外头,所以它不能访问 variable2
,因此会抛出一个引用错误
块做用域和函数做用域的行为相同,可是他们是为块建立的,而不是函数
当一个用 var
声明的常规变量被建立时,会被提高到它的做用域的顶部,而后并初始化一个 undefined
值,这样就容许咱们可以在它赋值以前引用一个常规变量
console.log(x); // undefined
var x = 10;
复制代码
记住,因为存在声明提高,代码实际看起来是这样的:
var x = undefined;
console.log(x); // undefined
x = 10;
复制代码
这个行为会阻止抛出引用错误 ReferenceError
用 let
声明的变量也被提高了,但重要的是,他们并不会自动初始化值 undefined
,所以意味着下面的代码会产生一个错误:
console.log(x); // Uncaught ReferenceError: x is not defined
let x = 10;
复制代码
这个错误是由暂时性死区(TDZ)引发的。TDZ 存在于做用域初始化到变量声明期间。为了修复这个错误(ReferenceError
),咱们须要在访问它前声明它:
译者注:TDZ
let x;
console.log(x); // undefined
x = 10;
复制代码
TDZ 这样设计是为了使开发更容易(试图引用一个还没声明的变量一般视为一个错误,而不是故意为之),所以这个错误能够当即提醒咱们
const
新的 const
被用来声明一个不可再次赋值的变量。它和 let
的在 TDZ 的行为很是类似,可是,const
变量必须初始化一个值
const VAR1 = 'constant';
复制代码
从如今开始, 变量 VAR1
的值将永远是 “constant” 这个字符串。若是咱们试图再次对它赋值,咱们会获得一个错误:
TypeError: Assignment to
constant
variable
若是咱们试图建立一个没有初始化的 const
变量,咱们将看到一个语法错误:
SyntaxError: Missing initializer in
const
declaration
类似地,一个 const
变量不能被再次声明。若是咱们试图再次用 const
声明一个相同变量时,咱们将获得一个不一样类型的语法错误
SyntaxError: Identifier ‘VAR1′ has already been declared
和其余编程语言同样,常量是被用来保存咱们的程序在生命周期里不但愿改变的值
记住 let
和 const
都是 JavaScript 的保留词,所以在严格模式下,是不能被用做标识符名称的(变量名,函数名等)。随着 ES2015 愈来愈广泛,let
和 const
优于 var
已造成一个共识,由于变量建立的做用域更与其余现代编程语言看齐,而且代码的行为也更好预测。 所以,在大多数状况下尽量的避免使用 var
用 const
声明的变量不能被再次赋值的,可是 const
声明的变量并非彻底不可变的。若是咱们用对象或数组初始化了一个 const
变量,咱们依然能够修改对象的属性和增长删除数组的元素
for
循环里用 let
来初始化计数器变量const
的错误:const VAR1 = 'constant';
const VAR1 = 'constant2';
const VAR2;
VAR2 = 'constant';
复制代码
成功是经过不断的练习和知识的积累,而非智力
- 本文仅表明原做者我的观点,译者不发表任何观点
- Markdown 文件由译者手动整理,若有勘误,欢迎指正
- 译文和原文采用同样协议,侵删