详解JavaScript中的作用域链与闭包

目录
  • 作用域链
  • 词法作用域
  • 闭包

作用域链

首先来看看这段代码:

var a = '喜羊羊';
function A(){
    console.log(a);
    a = '美羊羊';
    function B(){
        console.log(a);
    }
    B();
}
A();

在这里毫无疑问结果肯定是我们想到的先打印喜羊羊,再打印美羊羊。因为作用域链嘛,如果当前层没找到,那么就去当前层的上一级找。

那么再看这道

function bar() {
    console.log(myName)
}
function foo() {
    var myName = "极客邦"
    bar()
}
var myName = "极客时间"
foo()

是不是感觉是打印极客邦?如果是的话,那么恭喜你,掉坑里了。(还不赶快爬起来,补一补作用域链的知识)。

为什么打印不是极客邦而是极客时间呢?

既然问题出现在了对作用域链的理解上,那么就再回到作用域链的定义上吧。

其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。

比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找

为了直观理解,你可以看下面这张图:

看到这张图我猜你又纳闷了,为什么bar函数创建的执行上下文中的outer会指向全局??

哈哈哈,这里就要涉及到了词法作用域了

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

这么讲可能不太好理解,你可以看下面这张图:

从图中可以看出,词法作用域就是根据代码的位置来决定的,其中 main 函数包含了 bar 函数,bar 函数中包含了 foo 函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。

明白了词法作用域,那么我们再回到刚刚的问题。

为什么bar函数创建的执行上下文中的outer会指向全局

这是因为根据词法作用域,而词法作用域又是根据代码的位置,而bar函数代码的位置就是包裹在全局下,而喜羊羊那个例子中的B函数是在A函数的环境下,所以会造成它们的词法作用域链不同,也就导致函数作用域链不同了。

所以我们才有那句话词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

也就是只和代码位置有关,和函数直接如何调用没关系

闭包

老生常谈的问题,这次再从一个更深入的角度来理解一下。

看下面这段代码:

function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = {
        getName:function(){
            console.log(test1)
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())

这段代码乍一看没有什么问题,但是这里有一个细节很多人会忽视。

在foo()执行完将返回值给bar时,这里foo函数会从调用栈中弹出,变量都会被回收。既然变量都被回收了,那么bar.setName()这些调用方法从何而来??

foo执行完后的情况可以参考下图:

从上图可以看出,foo 函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setNamegetName 方法中使用了 foo 函数内部的变量 myNametest1,所以这两个变量依然保存在内存中。这像极了 setNamegetName 方法背的一个专属背包,无论在哪里调用了 setName getName 方法,它们都会背着这个foo函数的专属背包。

之所以是专属背包,是因为除了 setNamegetName 函数之外,其他任何地方都是无法访问该背包的,我们就可以把这个背包称为 foo 函数的闭包。

好了,现在我们终于可以给闭包一个正式的定义了。在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包 比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。

用一句话概括就是

能够访问其他函数内部变量的函数,被称为 闭包

(我们理解可以这么理解,但是和面试官说的当然可以把这个例子说一下,这直接上升到了一个理解什么是闭包的新高度了)

到此这篇关于详解JavaScript中的作用域链与闭包的文章就介绍到这了,更多相关JavaScript作用域链 闭包内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript闭包与作用域链实例分析

    本文实例讲述了JavaScript闭包与作用域链.分享给大家供大家参考,具体如下: 闭包定义 闭包指的是有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数A内部创建另一个函数B,那么函数B就是一个闭包,可以访问函数A作用域中的所有变量. JavaScript的闭包与作用域链密不可分,因此本文可以和JavaScript的作用域链相对照分析,一定可以对JavaScript的闭包和作用域链有更深的理解. 下面我们仍然以createComparisonFunction为例进行闭包

  • JavaScript闭包和作用域链的定义实现

    目录 引言 闭包的定义和实现 作用域链 闭包和作用域链的关系 使用闭包的注意事项 结论 引言 在JavaScript中,每个函数都有自己的作用域.作用域规定了哪些变量和函数可以在当前函数内部访问.当我们在函数中定义一个新的变量时,这个变量只能在该函数内部使用.同样地,当我们在函数内部定义一个新的函数时,这个函数也只能在该函数内部使用. 但是,在JavaScript中,函数还具有另外一个特性:它们可以访问其定义范围内的变量和函数,即使这个函数在其他地方被调用.这种行为就是闭包. 闭包的定义和实现

  • 深入理解Javascript中的作用域链和闭包

    首先我们回顾下之前一篇关于介绍数组遍历的文章: 请先看上一篇中提到的for循环代码: var array = []; array.length = 10000000;//(一千万) for(var i=0,length=array.length;i<length;i++){ array[i] = 'hi'; } var t1 = +new Date(); for(var i=0,length=array.length;i<length;i++){ } var t2 = +new Date();

  • 详解JavaScript作用域、作用域链和闭包的用法

    1. 作用域 作用域是指可访问的变量和函数的集合. 作用域可分为全局作用域和局部作用域. 1.1 全局作用域 全局作用域是指最外层函数外面定义的变量和函数的集合. 换言之,这些最外层函数外面定义的变量和函数在任何地方都能访问. 举个例子: // 最外层定义变量 var a = 1; console.log(a); // 最外层可以访问 function fnOne() { // 最外层函数 console.log(a); // 函数内可以访问 function fnTwo() { // 子函数

  • Javascript的作用域、作用域链以及闭包详解

    一.javascript中的作用域 ①全局变量-函数体外部进行声明 ②局部变量-函数体内部进行声明 1)函数级作用域 javascript语言中局部变量不同于C#.Java等高级语言,在这些高级语言内部,采用的块级作用域中会声明新的变量,这些变量不会影响到外部作用域. 而javascript则采用的是函数级作用域,也就是说js创建作用域的单位是函数. 例如: 在C#当中我们写如下代码: static void Main(string[] args) { for (var x = 1; x < 1

  • JavaScript中的作用域链和闭包

    作用域 全局作用域 局部作用域 作用域链 执行上下文 活动对象 闭包 闭包优化 JavaScript中出现了一个以前没学过的概念--闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥有全局作用域,以下几种情形拥有全局作

  • 详解JavaScript中的链式调用

    链模式 链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧. 描述 链式调用在JavaScript语言中很常见,如jQuery.Promise等,都是使用的链式调用,当我们在调用同一对象多次其属性或方法的时候,我们需要多次书写对象进行.或()操作,链式调用是一种简化此过程的一种编码方式,使代码简洁.易读. 链式调用通常有以下几种实现方式,但是本质上相似,都是通过返回对象供之后进行调用. this的作用域链,jQuery的实现方式,通常链式调用

  • 详解JavaScript原型与原型链

    正如一些面向对象语言中所实现的那样,在JavaScript中我们有时也需要创建一个拥有公共函数与属性的类作为父类来减少代码重复.实现类型检查与实现更加清晰地代码结构.在JavaScript中,继承是通过原型链实现的.了解JavaScript的继承与原型链之前首先需要了解JavaScript中对象创建的方式. 在JavaScript中创建对象 JavaScript中对象创建的方式有两种:工厂方法(Factory Functions).构造器方法(Constructor Functions) . 工

  • 详解JavaScript中的执行上下文及调用堆栈

    一.执行上下文是什么 代码运行是在一定的环境之中运行的,这个运行环境我们就成为执行环境,也就是执行上下文,按照执行环境不同,我们可以分为三类: 全局执行环境:代码首次执行时候的默认环境 函数执行环境:每当执行流程进入到一个函数体内部的时候 Eval执行环境:当eval函数内部的文本执行的时候 二.执行上下文栈是什么 既然是'栈',那就得符合'栈'的特性,即数据结构是先进后出.下面我们看一段代码: function cat(a){ if(a<0){ return false; } console.

  • 详解Javascript中prototype属性(推荐)

    在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于'类的',而是通过构造函数(constructor)和原型链(prototype chains)实现的.但是在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板.通过class关键字,可以定义类.基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能

  • 详解Javascript 中的 class、构造函数、工厂函数

    到了ES6时代,我们创建对象的手段又增加了,在不同的场景下我们可以选择不同的方法来建立.现在就主要有三种方法来构建对象,class关键字,构造函数,工厂函数.他们都是创建对象的手段,但是却又有不同的地方,平时开发时,也需要针对这不同来选择. 首先我们来看一下,这三种方法是怎样的 // class 关键字,ES6新特性 class ClassCar { drive () { console.log('Vroom!'); } } const car1 = new ClassCar(); consol

  • 详解JavaScript中new操作符的解析和实现

    前言 new 运算符是我们在用构造函数创建实例的时候使用的,本文来说一下 new 运算符的执行过程和如何自己实现一个类似 new 运算符的函数. new 运算符的运行过程 new 运算符的主要目的就是为我们创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例(比如箭头函数就没有构造函数,所以是不能 new 的).new 操作符的执行大概有以下几个步骤: 创建一个新的空对象 把新对象的 __proto__ 链接到构造函数的 prototype 对象(每一个用户定义函数都有一个 proto

  • 详解JavaScript中的数据类型,以及检测数据类型的方法

    一.js中的数据类型有哪些? 在js中,基本数据类型有五种,分别是 string.number.boolean.null.undefined,不过在ES6中新增加的了一种基本数据类型Symbol(表示独一无二的值),其作用主要是从根本上防止属性名的冲突而设定的. 除了基本数据类型之外,还有引用数据类型object,也有人称之为复杂数据类型,包含了我们常见的Array.Object.Function等. 所以现在js中的数据类型共有七种. PS: Symbol数据类型通过Symbol函数生成.也就

  • 详解JavaScript中的箭头函数的使用

    目录 前言 箭头函数语法 无圆括号语法 隐式返回 注意隐式返回错误 无法命名箭头函数 如何处理this关键字 匿名箭头函数 不正常工作的情况 箭头函数作为对象方法 箭头函数与第三方库 箭头函数没有arguments对象 总结 前言 本文可以让你了解所有有关JavaScript箭头函数的信息.我们将告诉你如何使用ES6的箭头语法,以及在代码中使用箭头函数时需要注意的一些常见错误.你会看到很多例子来说明它们是如何工作的. JavaScript的箭头函数随着ECMAScript 2015的发布而到来,

随机推荐