图解作用域,作用域链,闭包

2,404 阅读3分钟

前言

作用域的概念是理解js的关键所在,但是大部分刚入们的同学,都像曾经的我一样,对其一知半解,甚至从未听闻。不管从性能角度还是从功能角度,作用域都非常关键。

一、什么是作用域和作用域链

1、首先我们要清楚,每一个js函数都表示为一个对象,他们都是Function对象的一个实例。Function对象同其他对象一样,拥有可以编程的访问属性,其中有一个不能通过代码直接访问的内部属性[[scope]]。[[scope]]包含了被创建的作用域中的对象的集合,这个集合被称为函数的作用域链。它决定了哪些对象能被函数访问。

2、创建一个函数fn

function fn(n1, n2){
	let sum = n1 + n2;
    return sum;
}

当函数被创建时,它的作用域链中插入了一个对象变量,包含了所有的全局对象。 3、当函数fn被执行时,会创建一个称为“执行环境”的内部对象。它有以下特点:

a.函数每执行一次都会创建一个独一无二的执行环境;

b.每一个执行环境都有自己的作用域链;

c.每一个执行环境会创建一个被称为“活动对象”的新对象,里面包含了所有的局部变量,命名参数,参数集合以及this。并被推入作用域链的最前端;

d、函数执行完毕,执行环境被销毁,活动对象也随之被销毁。

理解执行环境有助于理解闭包。
const total = fn(1,2);

函数在执行过程中,每遇到一个变量,就会经历一次标识符的解析以决定从哪里获取或存储数据。该过程从头部开始搜索执行环境的作用域链。该搜索过程将耗费性能,因此建议在函数内部尽可能的使用局部变量,以减少搜索时间。

二、闭包原理

闭包是js最强大的特性之一,它允许函数访问局部作用域之外的数据。关于闭包的使用到处都能找到,这里不做赘述,只简单介绍闭包的底层原理。

举个例子:

function f1(){
	var name = 'xiaohong';
    function sub(){
    	console.log('my name is'+name);
    }
    return sub;
}

f1函数内的sub函数就是一个闭包。它在f1执行时被创建,并且能访问所属作用域的name变量。为了让这个闭包访问name,必须创建一个特定的作用域链。

当闭包代码执行时,也会创建一个自己的执行环境,它的[[scope]]中会引用父函数的活动对象,以及全局对象,并加入到作用域链中。闭包自己的活动对象会添加到闭包作用域链的最前端。

由于闭包的[[scope]]和f1的[[scope]]引用了相同的活动对象,因此在使用闭包时会产生强烈的副作用。通常情况下,闭包会被其他属性引用,因此导致激活的活动对象无法被销毁,因而产生更大的内存开销。因此闭包会导致内存泄漏。