JS中的作用域链,闭包,this指向全解析

文章目录

    • 执行环境与作用域
      • 作用域链
      • 对比一下原型链与作用域链:
    • 闭包
      • 什么是闭包?
      • 闭包会引起的问题
        • 引用的变量发生变化
        • 内存泄露问题
      • this指向问题
    • 闭包的其他用途
      • 利用闭包约束块级作用域

在不久之前,将网上总结的关于闭包的博文大概看了一下,却仍然是云里雾里。事实证明还是自己直接啃书比较好,对照着红宝书,从作用域到闭包在到this,把闭包的原理和应用以及各种坑通过知识点和网上的博文串起来,才总算弄懂闭包的大概。本篇博文是按照自己的理解所写,如果有不正确的地方,麻烦批评指正。

要知道闭包,必须先了解JS中的执行环境与作用域。

执行环境与作用域

在JS中,执行环境分为两类:

  1. 全局执行环境,即window对象
    • 到应用程序退出,(关闭网页或浏览器)才会被销毁
  2. 执行函数时函数自己的执行环境
    • 函数执行完之后就被销毁

作用域链

我们都知道,JS中有全局变量,局部变量,那JS是怎么判断哪个是全局变量,哪个是局部变量呢?若全局和局部环境中都存在一个同名变量,怎么确定要用的是哪个?这就是作用域链的用途了。

看一个红宝书上的例子

var color = 'blue'

function changeColor () {
    var anotherColor = 'red';

    function swapColors() {
        var temp = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors()
}

changeColor();

上述例子中有三个执行环境, 当代码由上往下进行解析时,依次会进入一下执行环境:

  1. 全局环境
  2. changeColor函数的执行环境
  3. swapColors函数的执行环境

很容易理解,在swapColors里,是能访问到其上面的两个执行环境中的变量和函数的,而它外面的执行环境,却无法访问其内部的变量与函数。
这些,都是因为有作用域链的存在,上述例子的作用域可以用下图表示

window
    |--color
    |
    |--changeColor()
        |
        |--anotherColor
        |
        |--swapColors()
            |
            |--tempColor

JS在执行代码时,会进入不同的作用域,每进到一个作用域内,都会向上搜索作用域链,查询变量与函数。即刚开始会在自己的执行对象中搜索,如果搜索不到,再沿着作用域链往上搜索。和原型链中对象属性的搜索方式非常像。

如果想在局部执行环境内访问全局对象怎么办?用window.dataName即可。

对比一下原型链与作用域链:

  • 创建对象会产生原型链,其作用在于管理各个对象之间的关系。读取对象属性时,先搜索实例对象,再沿着原型链往上搜索
  • 在js执行过程中也会产生作用域链,其用途是对执行环境中的变量和函数进行有序访问。 。执行时先在自己所在执行对象中搜索,再沿着作用域链往上搜索

闭包

什么是闭包?

红宝书上就一句话:闭包是指有权访问另一个函数作用于中的变量的函数。
这不是跟上面的那个例子一样吗? 没错,闭包正是作用域链所引起的。根据作用域链,就能解释为什么闭包函数能够访问其他函数的作用域。

闭包会引起的问题

引用的变量发生变化

缘由:闭包只能取得包含函数中任何变量的最后一个值

function createFunction() {
    var result = new Array()

    for(var i = 0; i < 10; i++) {
        result[i] = function () {
            return i;
        }
    }

    return result//都返回10
}

为什么都返回10?
对于里面那个匿名函数来说,访问的变量i是createFunction执行环境中的变量,当进入到自己的执行环境时,i已经结束循环,变成10了。

怎么解决?

function createFunction() {
    var result = new Array()

    for(var i = 0; i < 10; i++) {
        result[i] = function(num) {
            return function() {
                return num // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
             }
        }(i)
    }

    return result//依次返回1到10
    //result数组中的每个函数都有自己num变量的一个副本
}

内存泄露问题

产生缘由:闭包会引用包含函数的整个活动对象

function assignHandler() {
    var el = document.getElementById("some");

    element.onClick = function () {
        alert(element.id)
    }
}

这里面的匿名函数只要存在,就会保存对assignHandler的活动对象的引用,所以即使函数已经执行完,assignHandler占用的内存也永远不会被回收。

怎么解决?

function assignHandler() {
    var el = document.getElementById("some");
    var id = el.id

    element.onClick = function () {
        alert(id)
    }

    el = null;
}

this指向问题

产生缘由:每个函数在调用时都会自动取得thisargument这两个特殊变量。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,而不会沿着作用域链向上搜索

var name = 'window'

var object = {
    name: 'My object',
    getName: function() {
        return function() {
            return this.name;
        }
    }
}
console.log(object.getName()())    // window

因为匿名函数的执行环境具有全局性,因此其this队形通常指向window,在这个例子中里面的闭包函数是在window作用域下执行的,也就是说,this指向window。

怎么解决?

var name = 'window'

var object = {
    name: 'My object',
    getName: function() {
        var that = this;
        return function() {
            return that.name;
        }
    }
}
console.log(object.getName()())    // My object

熟悉的var that = this;
原理:因为闭包函数可以访问that,而此时that作为object作用域中this指向的副本,that同样也指向object。

(发现好像都是通过建立副本来解决闭包带来的各种问题)

闭包的其他用途

利用闭包约束块级作用域

与C语言不一样,在JS中是没有块级作用域的,即在if语句中声明的变量,会将其添加到当前的执行环境中。利用闭包,可以约束块级作用域。在ES6中,也可以利用let和const解决这一问题。

for(var i=0; i<10; i++){
    console.info(i)
}
alert(i)  // 变量提升,弹出10

//为了避免i的提升可以这样做
(function () {
    for(var i=0; i<10; i++){
         console.info(i)
    }
})()
alert(i)   // underfined   因为i随着闭包函数的退出,执行环境销毁,变量回收

参考资料:

  1. 《JavaScript高级程序设计》
  2. 彻底搞懂JS闭包各种坑

你可能感兴趣的:(JS,前端)