专业编程基础技术教程

网站首页 > 基础教程 正文

区分初级和高级JavaScript程序员:是否理解JavaScript闭包(Closure)

ccvgpt 2024-11-06 16:49:58 基础教程 39 ℃

闭包是functional language里面的核心概念。也是常见的JS面试题,是否理解闭包是一个简单的区分JS初级和高级程序员的判例。

区分初级和高级JavaScript程序员:是否理解JavaScript闭包(Closure)

几乎每个JS程序员都在使用闭包,有意或无意间。比如编写一个jQuery鼠标点击处理函数:

$(function() {

var option;

$(".scssbox").click(function() { // 闭包,该闭包同时也是一个匿名函数

option = scss; // 闭包可以访问和改变其外部函数(包含函数)中的变量

});

}

什么是闭包

“闭包”实际上就是一个函数,该函数被定义在一个包容(外部)函数的内部,并能够访问外部函数的变量,即使外部函数已返回。

这种外部变量访问能力不是通过数值拷贝传递,而是通过引用(Reference)传递的,因此闭包可以读取并改变外部变量的取值。

function Name (firstName, lastName) {

var nameIntro = "Your name is ";

// 该内部函数能访问外部函数中定义的变量,以及参数

function makeFullName () {

return nameIntro + firstName + " " + lastName;

}

return makeFullName ();

}

console.log( Name("Michael", "Jordan") ); // Your name is Michael Jordan

注意:上述代码中的闭包不能被公开函数(public function)所调用,另外闭包不能访问外部函数的arguments变量。所谓公开函数指的是通过原型(prototype)扩展来定义的对象函数。

为什么需要闭包

闭包的好处是可以方便的完成外部函数对象的某些特定功能。我们可以类比C++等其他面向对象的语言,访问控制是面向对象语言的一个基本特征,而通过闭包,我们可以在JS语言中实现对象私有成员函数(private member function)和授权函数的功能。

闭包的陷阱

由于闭包能够修改外部函数变量,如果不小心,可能会产生一些比较隐蔽的问题:

function Member(users) {

var i;

var uniqueID = 100;

for (i = 0; i < users.length; i++) {

users[i]["id"] = function() { return uniqueID + i;}

}

return users;

}

var users = [{name: "Ryan",id: 0},

{ name: "Mike",id: 0},

{name: "Mark",id: 0}];

var members = Member(users);

var rid = members[0];

console.log(rid.id()); // 103

上面的代码本来是想给3个用户分别分配唯一成员编号100、101、102,但实际上都是103。

原因就是在调用Member函数并返回后,i已经被修改为3,那么当再次调用闭包函数获取唯一编号时就已经是103。

实例分析

看下面代码:

function f1(){

var n=999;

nAdd=function(){n+=1}

function f2(){alert(n);}

return f2;

}

var result=f1();

result(); // 999

nAdd();

result(); // 1000

在这段代码中,result()它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。为什么会这样呢?尤其是第二次,为何输出的不是999呢? nAdd=function(){n+=1}又起到了什么作用呢?

首先要说的是,闭包是functional language里面的核心概念。当出现高阶嵌套函数的时候,编译器会做closure convention闭包变换,核心就是变量不在分配在stack上,而是分配在heap上。这就是为什么f1已经返回,但是n还能被+1的原因。上面这段代码,实际上就是一个高阶嵌套函数。

  1. 因为在函数里面有定义的函数,这是嵌套。pascal也是允许嵌套函数。

  2. 高阶的原因是,函数可以所谓参数传递和返回,像我们熟悉的C语言。

但是当高阶和嵌套同时出现,就会造成麻烦,所以pascal和C都只能支持其中的一个。

我来分析一下这个程序的执行流。

  1. var result=f1(); 返回了一个函数f2, 因此result为f2。这个高阶函数特性,参考C语言函数指针。

  2. result(); 调用f2,显然输出999.

  3. nAdd(); 这里需要注意,这个nAdd实际上在定义的时候是一个lambda,是一个匿名函数,功能是n+=1。定义时将这个函数赋值给nAdd。所以在此时,实际上是调用了n+=1.为什么能找到n?因为n在堆里面。

  4. result(); 调用f2,显然输出1000.

最后一点,n在堆上如何被销毁,这个工作是垃圾收集器负责。当n不在被任何闭包的env引用的时候,会被回收。

Tags:

最近发表
标签列表