JavaScript 作用域scope简单汇总


Posted in Javascript onOctober 23, 2019

什么是作用域

程序的执行,离不开作用域,也必须在作用域中才能将代码正确的执行。

所以作用域到底是什么,通俗的说,可以这样理解:作用域就是定义变量的位置,是变量和函数的可访问范围,控制着变量和函数的可见性和生命周期。

而JavaScript中的作用域,在ES6之前和ES6之后,有两种不同的情况。

ES6之前,JavaScript作用域有两种:函数作用域和全局作用域。

ES6之后,JavaScript新增了块级作用域。

作用域的特性

在JavaScript变量提升的讨论中,我们其实是缺少了一个作用域的概念的,变量提升其实也是针对在同一作用域中的代码来说的。

对编译器的了解,让我们明白,对于一段代码【var a = 10】变量的赋值操作,其实是包含了两个过程:

1、变量的声明和隐式赋值(var a = undefined),这个阶段在编译时

2、变量的赋值(a = 10),这个阶段在运行时

先看一下如下代码:

var flag = true;

if(flag) {
  var someStr = 'flag is true';
}

function doSomething() {
  var someStr = 'in doSomething';
  var otherStr = 'some other string';
  console.log(someStr);
  console.log(flag);
}

doSomething();

for(var i = 0; i < 10; i++) {
  console.log(i);
}

console.log(i);

{
  var place = 'i do not want to be visited';
}

那么这一些代码在编译之后,执行之前,根据变量提升的机制,我们可以知道应该是下面这个样子:

function doSomething() { // 函数优先提升
  // 提升隐式赋值
  var someStr = undefined; 
  var otherStr = undefined; 

  someStr = 'in doSomething';
  otherStr = 'some other string';

  console.log(someStr);
  console.log(flag);
}

// 隐式赋值和提升
var flag = undefined; 
var someStr = undefined;
var i = undefined;
var place = undefined;

flag = true;

if(flag) {
  someStr = 'flag is true';
}

for(i = 0; i < 10; i++) {
  console.log(i);
}

doSomething();

console.log(i);

{
  place = 'i do not want to be visited';
}

因为变量的提升特性,以及无块级作用域的概念,所以代码中在同一个作用域中变量和函数的定义,在编译阶段都会提升到顶部。

通过上述代码,我们大体上可以得出作用域的特性:

第一、内部作用域和外部作用域是嵌套关系。外部作用域完全包含内部作用域。

第二、内部作用域可访问外部作用域的变量,但是外部作用域不能访问内部作用域的变量,(链式继承,向上部作用域查找)。

第三、变量提升是在同一个作用域内部出现的。

第四、作用域用于编译器在编译代码时候,确定变量和函数声明的位置。

块级作用域

上述代码,在ES6+的环境中运行,也是和ES6之前是相同的结果,但是ES6不是引用了块级作用域吗,为什么大括号块内的代码还是会出现和之前一样的编译方式呢?

那么,ES6中的块级作用域到底是什么?

let & const

利用var定义的变量,具有提升的性质,可能会影响代码的执行结果。

这是var定义变量的缺陷,那么如何规避这种缺陷呢?在ES6中,设计出来了let和const来重新定变量。

但是,由于JavaScript标准定义的非常早,1995年5月JavaScript方案定义,1996年微软提供了JavaScript解决方案JScript。而网景公司为了同微软竞争,神情了JavaScript标准,于是,1997年6月第一个国际标准ECMA-262便颁布了。

C语言标准化的过程却是将近二十年后才颁布。

所以,我们以后设计的语言既要兼容var也要有自己的块级作用域,让var和let以及const在引擎做到兼容。

所以,我们定义块级作用域的标准,只能从定义变量的方式入手,而不是直接一个{}块就可以解决。

先让我们看一下下面代码:

var name = 'someName';

function doSomething(){
  console.log(name);
  if(true) {
    var name = 'otherName';
  }
}
doSomething();
结果:undefined

产生这个结果的原因是我们函数内部的变量提升,覆盖了外部作用域的变量,也就是说,其实打印出来的值是doSomething函数中的变量声明的值。

但是这样却并不符合块级作用域的预期,如果有许多类似代码,理解起来也会相当困难。如果将代码用ES6方式改写:

let name = 'someName';

function doSomething(){
  console.log(name);
  if(true) {
    let name = 'otherName';
  }
}

doSomething();

结果:'someName'

从运行结果看,我们真正的做到了块级作用域应该有的效果,那么let和const又是如何支持块作用域的呢?

执行上下文

先想想一下JavaScript中的一个作用域两个执行上下文中的编译过程中的环境:

变量环境:编译阶段var声明存放的位置(一个大对象)。

词法环境:我们代码书写的位置,也是let和const的初始化位置(代码按词法环境顺序执行,按照{}划分的栈结构)。

而在编译阶段,我们将var定义的变量全都在编译过程在变量环境初始化为undefined,但是用let和const定义的变量,其实他们并未在变量环境初始化,而是在词法环境初始化,也就是执行代码位置初始化。

词法环境的特点:按照{}划分的一个栈结构。

变量查找方式

JavaScript中变量查找的方式:沿着词法环境的栈顶向下查找,找不到的变量去变量环境中查找,这样就形成了先查找代码块中的变量,再查找提升之后的变量环境,这样就形成了块级作用域的概念。

上面的代码形成两种环境的情况如下:

一、全局环境的执行上下文

变量环境:函数声明function doSomething() { … }

词法环境栈:执行到let name = ‘someName';让语句name = ‘someName'入栈。

二、doSomething的执行上下文(被全局环境包裹)

变量环境:无

词法环境栈情况:执行到let name = ‘otherName',语句的时候,name = ‘other'才会入栈;

JavaScript代码执行方式

执行doSomething的时候,还未执行let name = ‘otherName',所以,此时doSomething的词法环境中并未有name = ‘otherName',这个时候查找,只能向外部作用域查找(全局作用域)

此时查找到全局作用域name = ‘someName'所以此时就打印了someName

代码接着执行到了if语句内部,才会将name = ‘otherName'入栈,但是此时因为语句已经执行完毕,所以也就无关痛痒了。

JavaScript也就通过这种方式,实现了块级别作用域。

总结

JavaScript中的作用域总的来说,分为块级作用域、函数作用域、全局作用域。

而每个作用域都会创建自身的执行上下文,每一个执行上下文又分为了变量环境和词法环境两部分。

块级作用域的实现,其实是根据定义的let和const声明的变量放置在词法环境栈中这一特性来实现。

这一特性被社区的人叫做‘暂时性死区',但是在JavaScript标准中并未有这个概念。

只有理解了作用域的概念,才能真正明白JavaScript的执行机制,才能减少我们因为变量定义等发生的错误。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript编程起步(第七课)
Feb 27 Javascript
弹出广告特效代码(一个IP只弹出一次)
May 11 Javascript
使用jQuery模板来展现json数据的代码
Oct 22 Javascript
js实现的四级左侧网站分类菜单实例
May 06 Javascript
自适应布局meta标签中viewport、content、width、initial-scale、minimum-scale、maximum-scale总结
Aug 18 Javascript
js使用xml数据载体实现城市省份二级联动效果
Nov 08 Javascript
在vue-cli搭建的项目中增加后台mock接口的方法
Apr 26 Javascript
element UI upload组件上传附件格式限制方法
Sep 04 Javascript
vue刷新页面时去闪烁提升用户体验效果的实现方法
Dec 10 Javascript
jQuery 查找元素操作实例小结
Oct 02 jQuery
vue excel上传预览和table内容下载到excel文件中
Dec 10 Javascript
一篇文章带你浅入webpack的DLL优化打包
Feb 20 Javascript
node.js使用fs读取文件出错的解决方案
Oct 23 #Javascript
jquery 键盘事件 keypress() keydown() keyup()用法总结
Oct 23 #jQuery
JavaScript提升机制Hoisting详解
Oct 23 #Javascript
使用p5.js实现动态GIF图片临摹重现
Oct 23 #Javascript
p5.js临摹动态图形的方法
Oct 23 #Javascript
Vue 实现点击空白处隐藏某节点的三种方式(指令、普通、遮罩)
Oct 23 #Javascript
p5.js实现动态图形临摹
Oct 23 #Javascript
You might like
PHP代码实现爬虫记录――超管用
2015/07/31 PHP
PHP 应用容器化以及部署方法
2018/02/12 PHP
laravel请求参数校验方法
2019/10/10 PHP
laravel框架语言包拓展实现方法分析
2019/11/22 PHP
在修改准备发的批量美化select+可修改select时,在非IE下发现了几个问题
2007/01/09 Javascript
在浏览器中获取当前执行的脚本文件名的代码
2011/07/19 Javascript
javascript中声明函数的方法及调用函数的返回值
2014/07/22 Javascript
JS实现网页Div层Clone拖拽效果
2015/09/26 Javascript
jQuery往返城市和日期查询实例讲解
2015/10/09 Javascript
jquery.validate 自定义验证方法及validate相关参数
2016/01/18 Javascript
解决wx.onMenuShareTimeline出现的问题
2016/08/16 Javascript
angular分页指令操作
2017/01/09 Javascript
js禁止浏览器的回退事件
2017/04/20 Javascript
JS手机端touch事件计算滑动距离的方法示例
2017/10/26 Javascript
vue和webpack打包项目相对路径修改的方法
2018/06/15 Javascript
Node.js中Koa2在控制台输出请求日志的方法示例
2019/05/02 Javascript
vue 解决遍历对象显示的顺序不对问题
2019/11/07 Javascript
JS 数组和对象的深拷贝操作示例
2020/06/06 Javascript
在vue项目中利用popstate处理页面返回的操作介绍
2020/08/06 Javascript
Python模拟登陆淘宝并统计淘宝消费情况的代码实例分享
2016/07/04 Python
Python 模块EasyGui详细介绍
2017/02/19 Python
Python实现excel转sqlite的方法
2017/07/17 Python
python使用SMTP发送qq或sina邮件
2017/10/21 Python
Linux 修改Python命令的方法示例
2018/12/03 Python
Python实现查找最小的k个数示例【两种解法】
2019/01/08 Python
django将数组传递给前台模板的方法
2019/08/06 Python
解决pytorch GPU 计算过程中出现内存耗尽的问题
2019/08/19 Python
Python有参函数使用代码实例
2020/01/06 Python
Python 操作 MySQL数据库
2020/09/18 Python
如何从csv文件构建Tensorflow的数据集
2020/09/21 Python
CSS3实现莲花绽放的动画效果
2020/11/06 HTML / CSS
大专自我鉴定范文
2013/10/23 职场文书
电子商务专业求职信
2014/07/10 职场文书
优秀员工事迹材料
2014/12/20 职场文书
致男子1500米运动员的广播稿
2019/11/08 职场文书
python3.9之你应该知道的新特性详解
2021/04/29 Python