深入理解JavaScript的闭包

656 阅读3分钟
原文链接: github.com

是什么?

闭包是指有权访问另一个函数作用域中的变量的函数 创建闭包的常见方式,在一个函数内部创建另一个函数

闭包原理

在弄清楚闭包原理之前先得明白作用域链

作用域链

当函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域链,并把作用域链赋值给一个特殊的内部属性(scope)。然后,使用 this, arguments 和其它命名参数来初始化函数的活动对象(active object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象始终处于第三位,。。。直至作为作用域链终点的全局执行环境。

在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。

function compare(value1, value2){
	if(value1< value2){
		return -1;
	}else if(value1 > value2){
		return 1;
	}else{
		return 0
	}
}

var result = compare(5, 10)

当第一次调用 compare 时,会创建一个包含 this, arguments, value1, value2 的活动对象,全局的执行环境的变量对象(包含 this, result 和 compare)在 compare 的执行环境作用域链中处于第二位。
image

后台的执行环境都有一个变量对象,全局的变量对象一直存在,而 compare 的变量对象只在运行时才存在。

当 compare 被创建时,会有一个包含全局变量对象的作用域链包含在 Scope 属性中。当 compare 被调用时,会复制 Scope

属性中的值构建执行环境的作用域链,然后还会有一个活动对象被创建被推入作用域链的前端。最后,作用域链中就包含两个对象,compare 的活动对象和全局的变量对象。作用域链本质上是一个指向变量对象的指针列表

一般来讲,当函数执行完毕,局部活动对象就会被销毁,只保留全局的变量对象。但是,闭包的情况会不同。

函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中。

function createComparisonFunction(propertyName){
    return function(obj1, obj2){
        value1 = obj1[propertyName];
        value2 = obj2[propertyName];
        if(value1 < value2){
            return -1;
        }
        else if(value1 > value2){
            return 1;
        }
        else{
            return 0
        }
    }
}
var compare = createComparisonFunction("name")
var result = compare({name:"nicholas"},{name:"Greg"})

当匿名函数被返回后,作用域链初始化为 createComparisonFunction 的活动对象和全局变量对象。 createComparisonFunction 执行完毕后活动对象不会销毁,因为被内部函数的作用域链引用,直到内部函数被销毁后,活动变量才会销毁。

image

注意: 闭包会携带外部函数的作用域,比使用其它函数会占用更多的内存。

闭包与 this

var name = "window";
var object = {
	name: "object",
	getNameFunc: function(){
		return function(){
			return this.name;
		}
	}
}
console.log(object.getNameFunc()()));
// "window"

解析:
每个函数被调用时,活动对象会自动取得两个变量: this 和 arguments。 需要注意的是,内部函数在搜索这两个变量的值只会搜索到当前活动对象。
我改变了一下调用方式

console.log(object.getNameFunc().call(object));
// “object”

因为 call 改变了当前的执行环境,所以可以取到 object上的name值

闭包的用途

捕捉局部变量的值,并且保存下来 例如以下场景,一个表单,里面包含一个 input 数组,每个input都有自己的编号。在收集input的输入值的同时,也要记录下来input 的index。

optionInputs = this.state.dictionary_items.map((item,index)=>{
    return (
            <div key={`option-item-${index}`} className="option-item">
                <div>{ index+1 }</div>
                <Input defaultValue = {item.item_value} onChange = { } />
            </div>
        )
})

Input 组件的 onChange 函数回调函数的内部的逻辑是这样的

onChange(e){
    //...省略
    if(onChange){
        onChange(e);
    }
}

这样的话也就只能收集到input 的值,不能获取它的 index。这时候闭包该出场了。
因为闭包可以把引用外层的活动对象,所以可以把这个index 值存储下来。
用闭包改写之后的代码是这个样子。

import React, { Component } from 'react';
import Input from "ui/input";

class Form extends Component{
	constructor(props){
		super(props);	
		this.state = {
			dictionary_items:[
				{
					item_value: "姓名"
				},
				{
					item_value: "性别"
				},				
			]
		}
	}

	render(){
		optionInputs = this.state.dictionary_items.map((item,index)=>{
			return (
					<div key={`option-item-${index}`} className="option-item">
						<div>{ index+1 }</div>
						<Input defaultValue = {item.item_value} onChange = {function(e){ return that.onOptionChange(e, index)}} />
					</div>
				)
		})
		return (
				<div>
					{ optionInputs }
				</div>
			)
	}
}


export default Form

参考资料

  1. 《JavaScript高级程序设计》