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 相关文章推荐
JS 获取span标签中的值的代码 支持ie与firefox
Aug 24 Javascript
jquery控制listbox中项的移动并排序的实现代码
Sep 28 Javascript
同时使用n个window onload加载实例介绍
Apr 25 Javascript
如何在JavaScript中实现私有属性的写类方式(一)
Dec 04 Javascript
javascript定义变量时加var与不加var的区别
Dec 22 Javascript
教你使用javascript简单写一个页面模板引擎
May 05 Javascript
vue.js轮播图组件使用方法详解
Jul 03 Javascript
js前端面试之同步与异步问题详解
Apr 03 Javascript
实现elementUI表单的全局验证的方法步骤
Apr 29 Javascript
使用typescript改造koa开发框架的实现
Feb 04 Javascript
Vue脚手架编写试卷页面功能
Mar 17 Javascript
Node Mongoose用法详解【Mongoose使用、Schema、对象、model文档等】
May 13 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基础知识:函数基础知识
2006/12/13 PHP
php 3行代码的分页算法(求起始页和结束页)
2009/10/21 PHP
常见的PHP五种设计模式小结
2011/03/23 PHP
PHP Yii框架之表单验证规则大全
2015/11/16 PHP
gearman中worker常驻后台,导致MySQL server has gone away的解决方法
2020/02/27 PHP
代码触发js事件(click、change)示例应用
2013/12/13 Javascript
Javascript玩转继承(三)
2014/05/08 Javascript
JavaScript indexOf方法入门实例(计算指定字符在字符串中首次出现的位置)
2014/10/17 Javascript
node网页分段渲染详解
2016/09/05 Javascript
基于jQuery中ajax的相关方法汇总(必看篇)
2017/11/08 jQuery
vue自定义全局共用函数详解
2018/09/18 Javascript
简单说说angular.json文件的使用
2018/10/29 Javascript
jQuery Ajax async=&gt;false异步改为同步时,解决导致浏览器假死的问题
2019/07/22 jQuery
Vue 中如何将函数作为 props 传递给组件的实现代码
2020/05/12 Javascript
Python set集合类型操作总结
2014/11/07 Python
Python操作Access数据库基本步骤分析
2016/09/19 Python
python导入csv文件出现SyntaxError问题分析
2017/12/15 Python
浅谈Python2、Python3相对路径、绝对路径导入方法
2018/06/22 Python
使用pycharm在本地开发并实时同步到服务器
2019/08/02 Python
Python使用百度翻译开发平台实现英文翻译为中文功能示例
2019/08/08 Python
使用Pyhton集合set()实现成果查漏的例子
2019/11/24 Python
django框架F&amp;Q 聚合与分组操作示例
2019/12/12 Python
如何基于Django实现上下文章跳转
2020/09/16 Python
python 装饰器的基本使用
2021/01/13 Python
Expedia加拿大官方网站:加拿大最大的在线旅游提供商
2017/12/31 全球购物
美国羊皮公司:Overland
2018/01/15 全球购物
阿玛尼美妆英国官网:Giorgio Armani Beauty英国
2019/03/28 全球购物
澳大利亚实惠时尚女装商店:Katies
2019/06/16 全球购物
承诺书范本
2015/01/21 职场文书
2016党员学习作风建设心得体会
2016/01/21 职场文书
JVM入门之类加载与字节码技术(类加载与类的加载器)
2021/06/15 Java/Android
Spring实现内置监听器
2021/07/09 Java/Android
HTML常用标签超详细整理
2022/03/19 HTML / CSS
漫改真人电影「萌系男友是燃燃的橘色」公开先导视觉图
2022/03/21 日漫
Nginx禁止ip访问或非法域名访问
2022/04/07 Servers
MySQL 数据 data 基本操作
2022/05/04 MySQL