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 一个函数对同一元素的多个事件响应
Jul 25 Javascript
JS获得浏览器版本和操作系统版本的例子
May 13 Javascript
js格式化输入框内金额、银行卡号
Feb 01 Javascript
js实现文字截断功能
Sep 14 Javascript
Javascript 获取鼠标当前的位置实现方法
Oct 27 Javascript
Javascript中字符串replace方法的第二个参数探究
Dec 05 Javascript
利用Three.js如何实现阴影效果实例代码
Sep 26 Javascript
JS设计模式之单例模式(一)
Sep 29 Javascript
javaScript 连接打印机,打印小票的实例
Dec 29 Javascript
在 React、Vue项目中使用SVG的方法
Feb 09 Javascript
9102了,你还不会移动端真机调试吗
Mar 25 Javascript
15个值得收藏的JavaScript函数
Sep 15 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开发环境配置记录
2011/01/14 PHP
解析php中die(),exit(),return的区别
2013/06/20 PHP
CodeIgniter实现从网站抓取图片并自动下载到文件夹里的方法
2015/06/17 PHP
php使用json_decode后数字对象转换成了科学计数法的解决方法
2017/02/20 PHP
PDO::errorInfo讲解
2019/01/28 PHP
基于PHP实现用户在线状态检测
2020/11/10 PHP
JavaScript 继承详解(一)
2009/07/13 Javascript
js数组依据下标删除元素
2015/04/14 Javascript
IE8下jQuery改变png图片透明度时出现的黑边
2015/08/30 Javascript
微信小程序本作用域下调用全局JS详解及实例
2017/02/22 Javascript
详解webpack分离css单独打包
2017/06/21 Javascript
基于Vue实现图书管理功能
2017/10/17 Javascript
基于datepicker定义自己的angular时间组件的示例
2018/03/14 Javascript
vue-cli 引入、配置axios的方法
2018/05/08 Javascript
nodejs中用npm初始化来创建package.json的实例讲解
2018/10/10 NodeJs
详解基于webpack&amp;gettext的前端多语言方案
2019/01/29 Javascript
微信小程序五子棋游戏的棋盘,重置,对弈实现方法【附demo源码下载】
2019/02/20 Javascript
vue基础之模板和过滤器用法实例分析
2019/03/12 Javascript
js代码编写无缝轮播图
2020/09/13 Javascript
[01:16:01]VGJ.S vs Mski Supermajor小组赛C组 BO3 第一场 6.3
2018/06/04 DOTA
Python pip install如何修改默认下载路径
2020/04/29 Python
python语言的优势是什么
2020/06/17 Python
python 用Matplotlib作图中有多个Y轴
2020/11/28 Python
使用css如何制作时间ICON方法实践
2012/11/12 HTML / CSS
自荐信不宜过于夸大
2013/11/06 职场文书
函授自我鉴定
2013/11/06 职场文书
公司开业庆典策划方案
2014/06/04 职场文书
教书育人演讲稿
2014/09/11 职场文书
机关干部三严三实心得体会
2014/10/13 职场文书
2015年学校综合治理工作总结
2015/07/20 职场文书
本地通过nginx配置反向代理的全过程记录
2021/03/31 Servers
Python控制台输出俄罗斯方块的方法实例
2021/04/17 Python
Python实现简繁体转换
2021/06/07 Python
关于python爬虫应用urllib库作用分析
2021/09/04 Python
SQL Server数据库查询出现阻塞之性能调优
2022/04/10 SQL Server
nginx设置资源请求目录的方式详解
2022/05/30 Servers