闭包是JavaScript中一个重要的特性,其最大的作用在于保存函数运行过程中的信息。在JavaScript中,闭包的诸多特性源自函数调用过程中的作用域链上。
函数调用对象与变量的作用域链
对于JavaScript中的每一次函数调用,JavaScript都会创建一个局部对象以储存在该函数中定义的局部变量;如果在该函数内部还有一个嵌套定义的函数(nested function),那么JavaScript会在已经定义的局部对象之上再定义一个嵌套局部对象。对于一个函数,其内部有多少层的嵌套函数定义,也就有多少层的嵌套局部对象。该局部对象称为“函数调用对象”(ECMAScript 3中的“call object”,ECMAScript 5中改名为“declarative environment record”,但个人认为还是ECMAScript 3中的名称更容易理解一些)。以下面的函数调用为例:
function f(x){
var a = 10;
return a*x;
}
console.log(f(6));//60
在这个简单的例子中,当调用f()函数时,JavaScript会创建一个f()函数的调用对象(姑且称之为f_invokeObj),在f_invokeObj对象内部有两个属性:a和x;运行f()时,a值为10而x值为6,因此最后的返回结果为60。图示如下:
当存在函数嵌套时,JavaScript将创建多个函数调用对象:
function f(x){
var a = 10;
return a*g(x);
function g(b){
return b*b;
}
}
console.log(f(6));//360
在这个例子中,当调用f()函数时,JavaScript会创建一个f()函数的调用对象(f_invokeObj),其内部有两个属性a和x,a值为10而x值为6;运行f()时,JavaScript会对f()函数中的g()函数进行解析定义,并创建g()的调用对象(g_invokeObj),其内部有一个属性b,b值与传入参数x相同为6,因此最后的返回结果为360。图示如下:
可以看到,函数调用对象形成了一条链。当内嵌函数g()运行,需要获取变量值的时候,会从最近的函数调用对象中开始进行搜索,如果无法搜索到,则沿函数调用对象链在更远的调用对象中进行搜寻,此即所谓的“变量的作用域链”。如果两个函数调用对象中出现相同的变量,则函数会取离自己最近的那个调用对象中的变量值:
function f(x){
var a = 10;
return a*g(x);
function g(b){
var a = 1;
return b*b*a;
}
}
console.log(f(6));//360, not 3600
在上面的例子中,g()函数的调用对象(g_invokeObj)和f()函数的调用对象(f_invokeObj)中均存在变量a且a的值不同,当运行g()函数时,在g()函数内部所使用的a值为1,而在g()函数外部所使用的a值则为10。图示此时的函数调用对象链如下:
什么是闭包?
在JavaScript中所有的函数(function)都是对象,而定义函数时都会产生相应的函数调用对象链,一次函数定义对应一个函数调用对象链。只要函数对象存在,相应的函数调用对象就存在;一旦某函数不再被使用,相应的函数调用对象就会被垃圾回收掉;而这种函数对象和函数调用对象链之间的一一组合,就称之为“闭包”。在上面f()函数和g()函数的例子中,就存在两个闭包:f()函数对象和f_invokeObj对象组成了一个闭包,而g()函数对象和g_invokeObj-f_invokeObj对象链一起组成了第二个闭包。当g()函数执行完毕后,由于g()函数不再被使用,因此g()闭包被垃圾回收了;之后,当f()函数执行完毕后,由于同样的原因,f()闭包也被垃圾回收了。
从闭包的定义可以得出结论:所有的JavaScript函数在定义后都是闭包 – 因为所有的函数都是对象,所有的函数在执行后也都有其对应的调用对象链。
不过,令闭包真正发挥作用的是嵌套函数的情况。由于内嵌函数是在外部函数运行的时候才开始定义的,因此内嵌函数的闭包中所保存的变量值(尤其是外部函数的局部变量值)是这次运行过程中的值。只要内嵌函数对象依然存在,那么其闭包就依然存在(闭包中的变量值不会发生任何改变),从而也就实现了保存函数运行过程的信息这个目的。考虑以下这个例子:
var a = "outside";
function f(){
var a = "inside";
function g(){return a;}
return g;
}
var result = f();
console.log(result());//inside
在这个例子中,当运行f()函数时,g()函数被定义,同时创建了g()函数的闭包,g()闭包包含了g_invokeObj-f_invokeObj对象链,因此保存了f()函数执行过程中的变量a的值。当执行console.log()语句时,由于g函数对象仍然存在,因此g()闭包也依然存在;当运行这个仍然存在的g函数对象时,JavaScript会使用依然存在的g()闭包并从中获取变量a的值(“inside”)。

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

记事本++7.3.1
好用且免费的代码编辑器

Atom编辑器mac版下载
最流行的的开源编辑器