本文2771字,阅读大约须要8分钟。
总括: 本文讲解了Javascript的做用域,做用域类型,做用域链等概念以及Javascript是如何去创建做用域链并寻找变量的。javascript
一花凋零,荒芜不了整个春天。前端
做用域和做用域链在Javascript和不少其它的编程语言中都是一种基础概念。但不少Javascript开发者并不真正理解它们,但这些概念对掌握Javascript相当重要。java
正确的去理解这个概念有利于你去写更好,更高效和更简洁的代码,让你成为一个更优秀的Javascript开发者。编程
所以,在本文中,我将会向你们解释清楚什么是做用域和做用域链,以及Javascript引擎在内部是如何经过它们操做和查找变量的。数组
事不宜迟,正文开始 :)安全
Javascript中的做用域说的是变量的可访问性和可见性。也就是说整个程序中哪些部分能够访问这个变量,或者说这个变量都在哪些地方可见。编程语言
Javascript中有三种做用域:函数
任何不在函数中或是大括号中声明的变量,都是在全局做用域下,全局做用域下声明的变量能够在程序的任意位置访问。例如:学习
// 全局变量 var greeting = 'Hello World!'; function greet() { console.log(greeting); } // 打印 'Hello World!' greet();
函数做用域也叫局部做用域,若是一个变量是在函数内部声明的它就在一个函数做用域下面。这些变量只能在函数内部访问,不能在函数之外去访问。例如:spa
function greet() { var greeting = 'Hello World!'; console.log(greeting); } // 打印 'Hello World!' greet(); // 报错: Uncaught ReferenceError: greeting is not defined console.log(greeting);
ES6引入了let
和const
关键字,和var
关键字不一样,在大括号中使用let
和const
声明的变量存在于块级做用域中。在大括号以外不能访问这些变量。看例子:
{ // 块级做用域中的变量 let greeting = 'Hello World!'; var lang = 'English'; console.log(greeting); // Prints 'Hello World!' } // 变量 'English' console.log(lang); // 报错:Uncaught ReferenceError: greeting is not defined console.log(greeting);
上面代码中能够看出,在大括号内使用var
声明的变量lang是能够在大括号以外访问的。使用var
声明的变量不存在块级做用域中。
像Javascript中函数能够在一个函数内部声明另外一个函数同样,做用域也能够嵌套在另外一个做用域中。请看例子:
var name = 'Peter'; function greet() { var greeting = 'Hello'; { let lang = 'English'; console.log(`${lang}: ${greeting} ${name}`); } } greet();
这里咱们有三层做用域嵌套,首先第一层是一个块级做用域(let
声明的),被嵌套在一个函数做用域(greet
函数)中,最外层做用域是全局做用域。
词法做用域(也叫静态做用域)从字面意义上看是说做用域在词法化阶段(一般是编译阶段)肯定而非执行阶段肯定的。看例子:
let number = 42; function printNumber() { console.log(number); } function log() { let number = 54; printNumber(); } // Prints 42 log();
上面代码能够看出不管printNumber()
在哪里调用console.log(number)
都会打印42
。动态做用域不一样,console.log(number)
这行代码打印什么取决于函数printNumber()
在哪里调用。
若是是动态做用域,上面console.log(number)
这行代码就会打印54
。
使用词法做用域,咱们能够仅仅看源代码就能够肯定一个变量的做用范围,但若是是动态做用域,代码执行以前咱们无法肯定变量的做用范围。
像C,C++,Java,Javascript等大多数编程语言都支持静态做用域。Perl 既支持动态做用域也支持静态做用域。
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前做用域下去寻找该变量,若是没找到,再到它的上层做用域寻找,以此类推直到找到该变量或是已经到了全局做用域。
若是在全局做用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。
例如:
let foo = 'foo'; function bar() { let baz = 'baz'; // 打印 'baz' console.log(baz); // 打印 'foo' console.log(foo); number = 42; console.log(number); // 打印 42 } bar();
当函数bar()
被调用,Javascript引擎首先在当前做用域下寻找变量baz
,而后寻找foo变量但发如今当前做用域下找不到,而后继续在外部做用域寻找找到了它(这里是在全局做用域找到的)。
而后将42
赋值给变量number
。Javascript引擎会在当前做用域以及外部做用域下一步步寻找number变量(没找到)。
若是是在非严格模式下,引擎会建立一个number
的全局变量并把42
赋值给它。但若是是严格模式下就会报错了。
结论:当使用一个变量的时候,Javascript引擎会循着做用域链一层一层往上找该变量,直到找到该变量为止。
以上内容已经讲解了做用域,做用域的类型,如今让咱们看下Javascript引擎是如何肯定变量的做用域链和如何去查找变量的。
要想理解Javascript是如何进行变量查找的,必需要了解Javascript中词法环境这个概念(请参考:理解Javascript中的执行上下文和执行栈)。
所谓词法环境就是一种标识符—变量映射的结构(这里的标识符指的是变量/函数的名字,变量是对实际对象[包含函数和数组类型的对象]或基础数据类型的引用)。
简单地说,词法环境是Javascript引擎用来存储变量和对象引用的地方。
注意:不要混淆了词法环境和词法做用域,词法做用域是在代码编译阶段肯定的做用域(译者注:一个抽象的概念),而词法环境是Javascript引擎用来存储变量和对象引用的地方(译者注:一个具象的概念)。
一个词法环境就像下面这样:
lexicalEnvironment = { a: 25, obj: <ref. to the object> }
只有当该做用域的代码被执行的时候,引擎才会为那个做用域建立一个新的词法环境。词法环境还会记录所引用的外部词法环境(即外部做用域)。例:
lexicalEnvironment = { a: 25, obj: <ref. to the object> outer: <outer lexical environemt> }
如今咱们已经知道了做用域,做用域链和词法环境的概念,如今让咱们看下Javascript引擎是如何利用词法环境来肯定做用域和做用域链的。
结合例子咱们来理解上面的这些概念:
let greeting = 'Hello'; function greet() { let name = 'Peter'; console.log(`${greeting} ${name}`); // Hello Peter } greet(); { let greeting = 'Hello World!' console.log(greeting); // Hello World! }
上述代码加载后,首先会建立一个全局词法环境,其中包含在全局范围内声明的变量和函数。像下面这样:
globalLexicalEnvironment = { greeting: 'Hello' greet: <ref. to greet function> outer: <null> }
这里的outer
字段(也就是外部词法环境)被设置为了null
,是由于全局词法环境已是最顶层的词法环境了。
而后,咱们调用了greet()
函数,而后一个新的词法环境会被被建立:
functionLexicalEnvironment = { name: 'Peter' outer: <globalLexicalEnvironment> }
这里的outer
字段被设置为了globalLexicalEnvironment
,是由于他的外部做用域就是全局做用域。
而后,执行console.log(`${greeting} ${name}`)这行代码,Javascript引擎首先在当前函数的词法环境中寻找变量greeting
和name
,但只找到了name
,没找到greeting
。而后继续在上层的词法环境中找greeting
(这里是全局做词法环境)。最后在全局词法环境中找到了greeting
。
紧接着执行那段在大括号里的代码,为这个块级建立一个新的词法环境。以下:
blockLexicalEnvironment = { greeting: 'Hello World', outer: <globalLexicalEnvironment> }
而后执行console.log(greeting)
这行代码,首先在本层词法环境中找greeting
,OK,找到,结束。此时就不会再去外部做用域(这里是全局做用域)寻找该变量了。
注意:只有let
和const
声明变量才会建立一个新的词法环境存储,使用var
声明的变量会被存储在当前块(大括号)所在的词法环境中(全局词法环境或是函数词法环境中)。
结论:当一个变量被使用时,Javascript引擎会首先在当前的词法环境中进行寻找,若是找不到就找上层词法环境中寻找,直到找到为止。
做用域就是一个变量可访问和可见的区域,和函数同样,Javascript的做用域也能够嵌套,Javascript引擎会层层遍历做用域来寻找用到的变量。
Javascript使用词法做用域,这意味着变量的做用在编译阶段就会被肯定。
Javascript引擎在程序执行期间使用词法环境来存储变量和函数。
做用域和做用域链是Javascript中的基础概念,理解做用域和做用域链能让你成为一个更优秀的Javascript开发者。
以上。
能力有限,水平通常,欢迎勘误,不胜感激。
订阅更多文章可关注公众号「前端进阶学习」,回复「666」,获取一揽子前端技术书籍