漫谈JavaScript中的作用域(scope)
什么是作用域
程序的执行,离不开作用域,也必须在作用域中才能将代码正确的执行。
所以作用域到底是什么,通俗的说,可以这样理解:作用域就是定义变量的位置,是变量和函数的可访问范围,控制着变量和函数的可见性和生命周期。
而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的执行机制,才能减少我们因为变量定义等发生的错误。
我的博客:http://www.gaoyunjiao.fun/?p=148
漫谈JavaScript中的作用域(scope)的更多相关文章
- 认识javascript中的作用域和上下文
javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的 ...
- 聊一下JS中的作用域scope和闭包closure
聊一下JS中的作用域scope和闭包closure scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了.我就被这个概念困扰了很久 ...
- 深入理解JavaScript中的作用域和上下文
介绍 JavaScript中有一个被称为作用域(Scope)的特性.虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,我会尽我所能用最简单的方式来解释作用域.理解作用域将使你的代码脱颖而出,减 ...
- JavaScript中的作用域
很多(JavaScript)开发者都在讨论"作用域",但它是什么?它们在JavaScript中的任何地方!我发现很多年轻的开发者不知道作用域是什么.他们中大多数人可以用jQuery ...
- 【翻译】JavaScript中的作用域和声明提前
原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译开始=== 你知道下面的JavaScript脚本执 ...
- JavaScript中的作用域和声明提前
[翻译]JavaScript中的作用域和声明提前 原文:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html ===翻译 ...
- 认识Javascript中的作用域和作用域链
作用域 只要写过java或者c#等语言的同学来说,相信一定能理解作用域的概念,在作用域的范围中,我们可以使用这个作用域的变量,对这个变量进行各种操作.可是,当使用Javascript的时候,相信很多的 ...
- 理解JavaScript中的作用域和上下文
JavaScript对于作用域(Scope)和上下文(Context)的实现是这门语言的一个非常独到的地方,部分归功于其独特的灵活性. 函数可以接收不同的的上下文和作用域.这些概念为JavaScrip ...
- 浅谈javascript中的作用域
首先说明一下:Js中的作用域不同于其他语言的作用域,要特别注意 JS中作用域的概念: 表示变量或函数起作用的区域,指代了它们在什么样的上下文中执行,亦即上下文执行环境.Javascript的作 ...
随机推荐
- 拉格朗日对偶性(Lagrange duality)
目录 拉格朗日对偶性(Lagrange duality) 1. 从原始问题到对偶问题 2. 弱对偶与强对偶 3. KKT条件 Reference: 拉格朗日对偶性(Lagrange duality) ...
- Hadoop 系列(一)—— 分布式文件系统 HDFS
一.介绍 HDFS (Hadoop Distributed File System)是 Hadoop 下的分布式文件系统,具有高容错.高吞吐量等特性,可以部署在低成本的硬件上. 二.HDFS 设计原理 ...
- Zabbix-agentd错误整理
一.无法启动 (一).当时环境 Firewalld与Selinux,Iptables都为关闭 配置环境 OS:CentOS Zabbix-server IP:10.18.43.71 Hostname: ...
- maven打jar包包括依赖包
<build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId& ...
- 【游记】NOIP2019复赛
声明 我的游记是一个完整的体系,如果没有阅读过往届文章,阅读可能会受到障碍. ~~~上一篇游记的传送门~~~ 前言 (编辑中)
- template.demo.js
<!DOCTYPE html><html><head> <title>index</title> <meta charset=&quo ...
- Promise 学习心得
当了这么久码农到今天没事才开始去深究 Promise 这个对象 什么是 Promise, Promise 有什么用? 在写代码的时候多多少少都有遇见过地狱式的回调 代码看起来没问题就是有点乱,Prom ...
- 对于微信UnionID在公众平台以及小程序里面的获取
首先介绍下UnionID的作用,在注册了微信开放平台(注意,这里是开放平台,不是微信公众平台)之后,同一个微信号在这个开放平台下的项目上面的UnionID都是统一的,通俗的说就是,小程序跟公众号项目在 ...
- 一文了解java异常机制
1.异常的概述 1.1什么是异常? 异常:程序在运行过程中发生由于外部问题导致的程序异常事件,发生的异常会中断程序的运行.(在Java等面向对象的编程语言中)异常本身是一个对象,产生异常就是产生了一个 ...
- vue过滤器微信小程序过滤器和百度智能小程序过滤器
因为最近写了微信小程序和百度小程序,用到了过滤器,感觉还挺好用的,所以就来总结一下,希望能帮到你们. 1. 微信小程序过滤器: 1.1:首先建一个单独的wxs后缀的文件,一般放在utils文件夹里面. ...