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 相关文章推荐
jquery实现背景墙聚光灯效果示例分享
Mar 02 Javascript
jQuery三级下拉列表导航菜单代码分享
Apr 15 Javascript
JS代码实现根据时间变换页面背景效果
Jun 16 Javascript
什么是JavaScript注入攻击?
Sep 14 Javascript
Node.js + Redis Sorted Set实现任务队列
Sep 19 Javascript
vue获取DOM元素并设置属性的两种实现方法
Sep 30 Javascript
老生常谈JavaScript面向对象基础与this指向问题
Oct 16 Javascript
微信小程序实现刷脸登录
May 25 Javascript
webpack+vue-cli项目中引入外部非模块格式js的方法
Sep 28 Javascript
vue实现日历备忘录功能
Sep 24 Javascript
高性能js数组去重(12种方法,史上最全)
Dec 21 Javascript
浅谈JavaScript浅拷贝和深拷贝
Nov 07 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 minixml详解
2008/07/19 PHP
php数据库抽象层 PDO
2011/05/07 PHP
用php实现选择排序的解决方法
2013/05/04 PHP
隐性调用php程序的方法
2015/06/13 PHP
浅析PHP关键词替换的类(避免重复替换,保留与还原原始链接)
2015/09/22 PHP
php版微信公众平台实现预约提交后发送email的方法
2016/09/26 PHP
利用php抓取蜘蛛爬虫痕迹的示例代码
2016/09/30 PHP
php中的单引号、双引号和转义字符详解
2017/02/16 PHP
YII2框架中使用RBAC对模块,控制器,方法的权限控制及规则的使用示例
2020/03/18 PHP
window.open 以post方式传递参数示例代码
2014/02/27 Javascript
jQuery中removeClass()方法用法实例
2015/01/05 Javascript
jquery实现手机号码选号的方法
2015/07/31 Javascript
Bootstrap表单使用方法详解
2017/02/17 Javascript
js判断用户是输入的地址请求的路径(实例讲解)
2017/07/18 Javascript
JS实现分页浏览横向图片(类轮播)实例代码
2017/11/06 Javascript
node.js基于fs模块对系统文件及目录进行读写操作的方法详解
2017/11/10 Javascript
electron中使用bootstrap的示例代码
2018/11/06 Javascript
Electron autoUpdater实现Windows安装包自动更新的方法
2018/12/24 Javascript
详解vue beforeEach 死循环问题解决方法
2020/02/25 Javascript
[03:04]DOTA2英雄基础教程 影魔
2013/12/11 DOTA
python基础教程之Hello World!
2014/08/29 Python
python实现unicode转中文及转换默认编码的方法
2017/04/29 Python
Python数据分析之双色球中蓝红球分析统计示例
2018/02/03 Python
python psutil监控进程实例
2019/12/17 Python
资生堂美国官网:Shiseido美国
2016/09/02 全球购物
英国袜子店:Sock Shop
2017/01/11 全球购物
法国在线购买汽车轮胎网站:123pneus.fr
2019/02/25 全球购物
CSS实现fullpage.js全屏滚动效果的示例代码
2021/03/24 HTML / CSS
实习生的自我鉴定范文欣赏
2013/11/20 职场文书
军训自我鉴定范文
2014/02/13 职场文书
2014年语文教研组工作总结
2014/12/06 职场文书
店长岗位职责
2015/02/11 职场文书
党支部工作总结2015
2015/04/01 职场文书
SpringBoot2零基础到精通之数据与页面响应
2022/03/22 Java/Android
Javascript的promise,async和await的区别详解
2022/03/24 Javascript
Redis如何使用乐观锁(CAS)保证数据一致性
2022/03/25 Redis