相信大家在入门JavaScript这门语言时对作用域、作用域链、变量声明提升这些概念肯定会稀里糊涂,下面就来说说这几个

Javascript 作用域

在 Javascript 中,只有局部作用域和全局作用域。而只有函数可以创建局部作用域,像 if,for 或者 while 这种块语句是没办法创建作用域的。 (当然 ES6 提供了 let 关键字可以创建块作用域。)

Javascript 的这种特性导致 for 循环里面创建闭包时会产生让人意想不到的结果。比如下面这个例子:

var i = 20;
var makeLogger = function() {
var funcs = [];
for(var i = 0; i < 10; ++i){
funcs[i] = function() {
console.log(i);
}
}
return funcs;
} var loggers = makeLogger();
for(var i = 0; i < 10; ++i){
loggers[i]();
}

RESULTS:

上面的输出结果,大致原因就是 for 循环里面的变量的作用域是整个函数的,循环内部创建的一系列闭包引用的是同一个变量 i,而在 for 循环结束后,这个 i 的值变成了 10。所以当我们调用这些内部函数的时候,就会输出 10 了。

现在这样讲可能还是不够清楚,在我们了解作用域链和 Javascript 的执行原理后,就更容易理解了。

Javascript 作用域链

  1. 当 Js 里面 声明 一个函数的时候,会给该函数对象创建一个 scope 属性,该属性指向当前作用域链对象。
  2. 当 Js 里面 调用 一个函数的时候,会创建一个执行上下文,这个执行上下文定义了函数解释执行时的环境信息。每个执行上下文都有自己的作用域链,主要用于变量标识符的解析。
  3. 在 Js 引擎运行一个函数的时候,它首先会把该函数的 scope 属性添加到执行上下文的作用域链上面,然后再创建一个 活动对象 添加到此作用域顶端共同组成了新的作用域链。活动对象包含了该函数的所有的形参,arguments 对象,所有的局变变量等信息。
  4. 当解释执行函数的每一条语句的时,会依据这个执行上下文的作用域链来查找标识符,如果在一个作用域对象上面没有找到标识符,则会沿着作用链一直向上查找,这一点类似于 Js 的原型继承的属性查找机制。

让我们来看几个具体的例子:

var name = 'zilongshanren';
function echo() {
console.log(name);
var name = 'hello';
console.log(name);
} echo();

RESULTS:

undefined
hello

要理解上面的代码的输出结果,我们可以按照上面提到的 4 点来解释:

  • 在声明 echo 函数时,此时的作用域链是(我们假设 scope chain 是一个作用域对象数组)
[[scope chain]] = [
{
global Object: {
name: 'zilongshanren'
...
}
}
]

echo 函数的作用域属性指向此 scope chain 对象。

  • 当调用 echo 函数时,会创建一个执行上下文,同时把 echo 的作用域添加到执行上下文的作用域链上。同时创建一个活动对象并添加到该作用域链的顶端。此时的作用域链是:
[[scope chain]] = [
{
Active Object {
name: undefined,
arguments: ...
...
},
global Object: {
name: 'zilongshanren'
...
}
}
]
  • 当解释执行函数的第一条语句的时候,查找 name 变量,在活动对象中找到了,于是输出 undefined。然后执行 var name = 'hello',此时变量 name 的值为 hello。最后解释执行 console.log(name)的时候就输出了 hello.

这个例子可能比较简单,因为它没有使用闭包。

我们接下来分解一下本文开头的例子。

  • 当定义 makeLogger 函数时,makeLogger 函数的作用域为:
[[scope chain]] = [
{
global Object: {
i: 20,
...
}
}
]
  • 在 for 循环里面定义闭包函数的时候,此时的作用域链是:
[[scope chain]] = [
{
makeLogger local scope object : {
i: undefined,
funcs: [],
},
global Object: {
i: 20,
...
}
}
]

并且此时 funcs…funcs的 scope 都指向该 scope chain。

  • 当调用 makeLogger 函数的时候,创建一个执行上下文。把 makeLogger 函数的作用域链加到执行上下文中,并且创建一个活动对象添加到作用域链的顶端,此时的 scope chain 为:
[[scope chain]] = [
{
makeLogger active object: {
funcs: undefined,
i: undefined,
arguments: ...
},
global Object: {
i: 20,
...
}
}
]
  • 当执行完 makeLogger 函数的时候,此时的作用域对象变成了:
[[scope chain]] = [
{
makeLogger local scope object : {
i: undefined,
funcs: [function object ...],
},
global Object: {
i: 20,
...
}
}
]

这里的 funcs 函数还会生成闭包对象,它包含了 makeLogger 局部作用域的变量的值,即 i=10.

下图是 V8 引擎中 funcs 函数及其闭包的截图:

  • 最后遍历执行所有的 loggers 的时候,会依次为每一个 loggers 函数创建一个执行上下文,每一个执行上下文的作用域链为:
[[scope chain]] = [
{
loggers function active object : {
arguments: ...
},
makeLogger local scope object : {
i: 10,
funcs: [function object ...],
},
global Object: {
i: 20,
...
}
}
]

当执行 loggers 函数的 console.log(i)的时候,它会沿着此时的作用域链进行变量查找,于是找到了 i=10. 所以我们输出的结果就是 10.

变量提升

我们看一个例子:

var name = 'zilongshanren';
function echo() {
name = "hello";
console.log(name);
var name;
console.log(name);
}
console.log(name); echo();

+RESULTS:

zilongshanren
hello
hello
undefined

调用 echo 函数的第一行 name = "hello"时并不是对全局变量 name 进行重新赋值,而是对函数内部声明的变量 name 进行赋值。所以,在 echo 函数声明之后,调用 console.log(name)输出的还是 zilongshanren。

echo 函数内部的 name 变量“使用在前,而声明在后”,这就是所谓的变量提升。

如果从我们前面提到的变量作用域和作用域链来解释这个行为肯定是更容易理解的。

正因为函数内部的变量声明会发生“提升”副作用,所以,最好的做法就是把函数需要用到的局部变量都放在函数开头进行声明,避免产生不必要的混淆。

小结

JavaScript 中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。理解作用域和作用域链对于理解闭包和变量提升这种奇葩特性非常有帮助。 本文可能有些地方讲的还不是非常清楚,读者可以读一读后面的参考链接,相信会有助于理解。

JavaScript作用域及作用域链详解、声明提升的更多相关文章

  1. JS作用域,作用域,作用链详解

    前言   通过本文,你大概明白作用域,作用域链是什么,毕竟这也算JS中的基本概念. 一.作用域(scope) 什么是作用域,你可以理解为你所声明变量的可用范围,我在某个范围内申明了一个变量,且这个变量 ...

  2. 《前端之路》之 JavaScript原型及原型链详解

    05:JS 原型链 在 JavaScript 的世界中,万物皆对象! 但是这各种各样的对象其实具体来划分的话就 2 种. 一种是 函数对象,剩下的就是 普通对象.其中 Function 和 Objec ...

  3. javascript 原型及原型链详解

    我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个原型对象,而这个原型对象中拥有的属性和方法可以被所以实例共享. function Person(){ } Pe ...

  4. 你不知道的JavaScript--Item15 prototype原型和原型链详解

    用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了, ...

  5. “全栈2019”Java异常第十五章:异常链详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  6. 高性能JavaScript模板引擎实现原理详解

    这篇文章主要介绍了JavaScript模板引擎实现原理详解,本文着重讲解artTemplate模板的实现原理,它采用预编译方式让性能有了质的飞跃,是其它知名模板引擎的25.32 倍,需要的朋友可以参考 ...

  7. JavaScript对象的property属性详解

    JavaScript对象的property属性详解:https://www.jb51.net/article/48594.htm JS原型与原型链终极详解_proto_.prototype及const ...

  8. javascript中=、==、===区别详解

    javascript中=.==.===区别详解今天在项目开发过中发现在一个小问题.在判断n==""结果当n=0时 n==""结果也返回了true.虽然是个小问题 ...

  9. Javascript 调试利器 Firebug使用详解

    Javascript 调试利器 Firebug使用详解 有时候,为了更清楚方便的查看输出信息,我们可能需要将一些调试信息进行分组输出,那么可以使用console.group来对信息进行分组,在组信息输 ...

随机推荐

  1. android -------- Eclipse下的NDK配置环境

    NDK 全称是Native Development Kit,是一个让开发人员在Android应用中嵌入使用本地代码编写的组件的工具集 原生开发工具包 (NDK) 是一组可让您在 Android 应用中 ...

  2. yii框架中使用gii的用法

    首先在config文件中的 main-local.php中添加一句 'allowedIPs' => ['*'],如下图所示:

  3. blog项目知识点梳理

    1.获取图片验证码: def get_validCode_img(request): # 方式1: # import os # path= os.path.join(settings.BASE_DIR ...

  4. python-前20天的着重知识点

    1.CPU存在两种工作状态:一种是内核态,操作系统在运行--可以操作硬件: 另一种是用户态,是应用软件在运行--不可以操作硬件. 应用软件要控制硬件,就要从用户态切换成内核态 2.多道技术:(多道指的 ...

  5. JQ 实现监测input中值的变化并绑定到另个input

                    $('#input').bind('input propertychange', function () {                     $('#myDiv ...

  6. C++笔试题总结

    1.C和C++的特点与区别? 答:(1)C语言特点:1.作为一种面向过程的结构化语言,易于调试和维护: 2.表现能力和处理能力极强,可以直接访问内存的物理地址: 3.C语言实现了对硬件的编程操作,也适 ...

  7. System.Web.Optimization 找不到引用,教你如何解决?

    在vs 2017 创建 BundleConfig 时添加引用 using System.Web.Optimization 是报错 提示未找到 解决方法: 在最下端窗口中写入:Install-Packa ...

  8. Beta阶段——第6篇 Scrum 冲刺博客

    Beta阶段--第6篇 Scrum 冲刺博客 标签:软件工程 一.站立式会议照片 二.每个人的工作 (有work item 的ID) 昨日已完成的工作 人员 工作 林羽晴 完成了函数的编写,提供报表数 ...

  9. ActiveMQ 集群和主从

    举例说明:假设有 3 个 broker 节点,分别是61616,61618, 61620,其中 61616 和 61618 组成主.从节点,而 61616(或61618)和 61620 构成集群.61 ...

  10. ActiveMQ Advisory Message

    http://activemq.apache.org/advisory-message.html ActiveMQ broker 内部维持了一些 topic,保存了一些系统信息,客户端可以订阅这些 t ...