JavaScript总结分享之闭包

1. 什么是闭包

在JavaScript编程中,闭包是一个重要的概念,是指函数在定义时可以访问其所在词法作用域内的变量,即使在函数被调用(执行)时,仍然可以访问。换句话说,闭包就是将函数内部和函数外部连接起来的一座桥梁。

1.1 闭包的定义

JavaScript中的闭包是由函数和与其相关的引用环境组合而成的实体,即:在创建一个函数时,该函数可以访问到它所在的词法环境中的变量,而在执行该函数时,仍然可以访问到该变量。

1.2 闭包的作用

闭包的作用主要有两个,一是可以实现变量的私有化,二是可以实现对函数内部变量的保护。

对于第一个作用,闭包可以将函数内部的变量和函数外部的变量隔离开来,使得外部无法访问函数内部的变量,从而增强了程序的安全性。

对于第二个作用,闭包可以保护函数内部的变量不受外部的干扰,从而避免了变量的意外修改,造成程序出错的情况。

2. 闭包的实现

实现闭包的关键在于理解JavaScript中的作用域链机制。在JavaScript程序中,每个函数都有一个作用域链Scope Chain。作用域链是一条由当前执行环境(全局环境或函数环境)和所有外层环境中的变量对象(VO)构成的链。作用域链的作用在于保证变量的有序访问。

2.1 使用闭包实现变量私有化

使用闭包实现变量私有化的方法比较简单,就是在函数内部定义一个变量,然后再定义一个函数,该函数可以访问到内部定义的变量,并返回该函数。这样,外部就无法访问到函数内部的变量了。

function counter() {

var count = 0;

return function() {

count++;

console.log(count);

};

}

var c = counter();

c(); //1

c(); //2

c(); //3

上述示例中,函数counter内部定义了一个变量count,然后返回了一个函数,该函数可以访问到内部的变量count。在调用counter函数时,将返回的函数保存在变量c中,然后依次调用c函数,每次调用都会打印出变量count的值,并将count的值加1。

2.2 使用闭包保护函数内部变量

使用闭包保护函数内部变量的方法也比较简单,就是在函数内部定义一个函数,该函数可以访问到内部的变量,然后返回一个闭包函数,该闭包函数可以访问到内部函数的变量,但无法访问到外部函数的变量。

function createPerson(name) {

var age = 0;

function updateAge() {

age++;

}

return {

getName: function() {

return name;

},

getAge: function() {

return age;

},

celebrateBirthday: function() {

updateAge();

console.log('Happy birthday!');

}

};

}

var person = createPerson('Tom');

console.log(person.getName()); //"Tom"

console.log(person.getAge()); //0

person.celebrateBirthday(); //"Happy birthday!"

console.log(person.getAge()); //1

上述示例中,函数createPerson内部定义了三个闭包函数(getName、getAge和celebrateBirthday),这些闭包函数都可以访问到内部的变量(name和age),但无法访问到外部的变量。在调用createPerson函数时,将返回一个包含三个闭包函数的对象,并将该对象保存在变量person中,然后就可以通过调用对象的方法来访问到相应的变量了。

3. 闭包的注意事项

使用闭包要注意一些问题,否则可能会导致内存泄露或者其他问题。

3.1 循环引用

循环引用指的是两个对象互相引用对方,这种情况下就会形成循环链表,导致内存泄露。

function createNodes() {

var nodes = document.getElementsByTagName('div');

var i, node;

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

node = nodes[i];

node.onclick = function() {

console.log(node.innerHTML);

};

}

}

createNodes();

上述示例中,函数createNodes用于获取页面中所有的div元素,并为每个div元素绑定click事件。但由于每个click事件都会访问到外部函数的变量node,而node是循环遍历过程中定义的变量,因此每个事件都会访问到最后一个div元素。这就导致了循环引用,从而可能导致内存泄露。

为了避免循环引用的问题,可以通过定义一个内部函数,将需要访问的变量传递给该函数,并返回一个闭包函数,该闭包函数可以访问到传入的变量。这样,就可以避免循环引用的问题。

function createNodes() {

var nodes = document.getElementsByTagName('div');

var i, node;

function bindEvent(node) {

node.onclick = function() {

console.log(node.innerHTML);

};

}

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

node = nodes[i];

bindEvent(node);

}

}

createNodes();

上述示例中,将需要访问的变量node作为参数传递给内部函数bindEvent,并且将bindEvent返回的闭包函数赋值给node.onclick,这样就可以避免循环引用的问题了。

3.2 内存泄露

使用闭包可能会导致内存泄露,在使用闭包时要特别注意。

function createNodes() {

var nodes = document.getElementsByTagName('div');

var i, node;

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

node = nodes[i];

node.onclick = function() {

console.log(node.innerHTML);

};

}

}

createNodes();

上述示例中,如果将页面中的所有div元素删除,但是没有取消点击事件的绑定,就会导致内存泄露。

为了避免内存泄露的问题,可以对绑定的事件进行解绑。

function createNodes() {

var nodes = document.getElementsByTagName('div');

var i, node;

function bindEvent(node) {

node.onclick = function() {

console.log(node.innerHTML);

};

}

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

node = nodes[i];

bindEvent(node);

}

function clearEvents() {

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

node = nodes[i];

node.onclick = null;

}

}

return {

clearEvents: clearEvents

};

}

var obj = createNodes();

obj.clearEvents();

上述示例中,为绑定事件的元素定义了清除事件的方法,并返回了一个包含该方法的对象,这样就可以在需要清除事件时调用该方法了。

4. 总结

闭包是JavaScript中一个重要的概念,是将函数内部和函数外部连接起来的一座桥梁。它可以实现变量的私有化,保护函数内部变量不受外部干扰,从而增强了程序的安全性和稳定性。

使用闭包时要注意循环引用和内存泄漏的问题,对使用闭包时可能产生的问题进行充分的了解和预防,可以避免出现不必要的错误。