深入理解JavaScript中变量作用域 - Go语言中文社区

深入理解JavaScript中变量作用域


理解JavaScript变量作用域:

------------------

变量作用域又叫做变量的可见性。在JavaScript中,变量的作用域是由函数限定的,它们要么是全局的,要么是局部的。·顾名思义,全局变量处处可以访问,局部变量只有在声明了它的地方才能访问。(PS:在JavaScript 1.7+ 版本中引入了一种全新的块级作用域构造器,使用 **let** 语句可以实现类似于C、C++、Java等语言中的块级作用域,不过并不是所有浏览器都实现了这一语法特性。使用示例:`let(v = 'block scope'){console.log(v)}; console.log(v);`)


变量作用域与其生命周期:

------------

变量的生命周期是指一个变量何时被创建和释放。变量作用域注重于“在形式上能操作一个变量的范围有多大”,变量的生命周期则注重于“在实现中一个变量创建和销毁的时机”。

在JavaScript中,一个变量的创建是在以下情况下发生的:

· 引擎做语法分析,发现显示生命时

· 引擎做代码执行,发现试图**写**(例如赋值操作)一个未被创建的变量时(**读取**一个未被创建的变量将出错!)

一个变量的释放则是发生在:

· 引擎执行到函数结束/退出操作时,将清除函数内未被引用的变量

· 引擎执行到全局的代码块终结或引擎卸载和重载入时,将清除全局的变量和数据的引用

所以,在JavaScript中的变量的生命周期只有两个:函数内的局部执行期间和函数外引擎的全局执行期间。这些是由JavaScript引擎的实现所选择的技术手段决定的,而不是语法和语义上的约定。


变量提升:

----  

在JavaScript中,当变量被声明时,声明会被提升到它所在的函数的顶部,并赋予undefined值(这一点我们可以通过浏览器自带的调试工具进行查看哦!)。这就使得在函数的任意位置声明的变量的作用域扩大到了整个函数中,尽管在赋值之前,它的值一直为 undefined 。

function hoisted(){ 	console.log(v);	var v = 123;}

上面的代码片段等价于:

function hoisted(){	var v;	console.log(v);	v = 123;}

记住:**JavaScript的变量声明会被提升到它们所在的函数顶部,而初始化的地方仍旧在原来的地方。JavaScript引擎并没有重写代码,每次调用函数时,声明都会重新提升。**

function demo(){	console.log(v); // undefined	var v = 123;	console.log(v); // 123}
因为变量声明总是被提升到函数作用域的顶部,所以在函数的顶部使用单一 var 声明变量总是最好的做法(这里与《代码大全》中推荐的“变量在哪里使用就在最近的地方进行变量声明”有一点点的“小冲突”。不过这是长久以来的编码习惯啦!)。


执行环境、执行环境对象和高级变量提升

------------------  

**提升**

在JavaScript中,语法解释与代码执行是分两个阶段完成的(或者说:“JavaScript引擎在进入作用域时,会对代码分两轮处理。第一轮时初始化变量;第二轮则是执行代码;”),变量的声明是在第一轮也就是语法解释阶段进行处理的。 在第一轮,JavaScript引擎分析代码,并做出以下3件事:

· 声明并初始化函数参数

· 声明局部变量,包括将匿名函数赋给一个局部变量,但并不初始化它们

· 声明并初始化函数

在第一轮执行的结果中,我们可以清晰的看见,局部变量并没有被赋值,因为它们最终的正确值可能需要在代码执行后才能确定,而第一轮语法解释阶段是不会执行代码的。但是函数的参数被赋值了,因为在向函数传递参数之前,任何决定参数值的代码都已经运行了。代码示例:

var v = 123; // 声明一个全局变量 function demo(){	console.log(v);  // 输出undefined,局部变量声明被提升,覆盖了全局变量。这里和变量作用域链有关,之后讨论。	var v = 456;}demo(); // 调用函数// 说明参数在第一轮就被赋值了 var v= 123;function demo(v){	console.log(v); // 输出789	var v = 456;}demo(789) // 调用函数,并将参数传入其中

**执行环境(execution context)**:

在JavaScript中,JavaScript引擎把变量作为属性保存在一个对象上,这个对象被称为“**执行环境对象**”。 每当函数被调用时就会产生一个新的执行环境。**执行环境是一种概念,是运行中的函数的意思,它不是对象**。执行环境定义了变量或函数有权访问的其它数据,决定了它们各自的行为。每个执行环境都有一个与之关联的**变量对象**(variable object),环境中定义的所有变量和函数都保存在这个对象中,这个对象我们编码人员是不能直接访问到的。(执行环境也依赖于JavaScript引擎的具体实现)。

所有在函数中定义的变量和函数都是执行环境的一部分,它们都被保存在执行环境对象中。如果变量在当前的执行环境中可以访问到,那么变量就在作用域内,换一种说法则是:“如果在函数运行时变量可以被访问到,那么该变量在作用域内”;

如上图所示:

(1)在<script>标签内的所有东西都在全局执行环境中

(2)调用first_function时,会在全局执行环境中创建一个新的执行环境。在first_function运行时,它有权限访问嵌套创建它的执行环境里面的变量。比如图中所示:first_function有权限访问在全局执行环境中定义的变量以first_function中定义的变量,我们称这些变量在作用域中。但是first_function没有权限访问嵌套创建在其执行环境中的second_function的执行环境中定义的变量。

(3)调用second_function时,会在first_function的执行环境中嵌套创建一个新的执行环境,这里的second_function有权限访问first_function的执行环境中的变量。

(4)再次调用second_function时,这次是在全局环境中调用的。这里的second_function没有权限访问在first_function的执行环境中定义的变量,因为second_function不是在first_function的执行环境中被调用的。也就是说second_function的执行环境不是在first_function中嵌套创建的。这里的second_function也没有权限访问先前调用的second_function中的变量,因为它们发生在不同的执行环境中。

JavaScript引擎在执行环境对象中访问作用域内的变量,查找的顺序叫做作用域链(scope chain),它和原型链一个,描述了JavaScript访问变量和属性的顺序。(PS:JavaScript中的函数的作用域是通过词法来划分的,也就是说:在定义函数的时候作用域链就固定了)。


作用域链(scope chain)

-----------------

作用域链的前端,始终都是当前执行的代码所在的执行环境的执行环境对象。更详细的内容可以参见维基百科。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/shenlei19911210/article/details/44312611
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-04-11 05:44:21
  • 阅读 ( 1258 )
  • 分类:Go深入理解

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢