一、一个JavaScript闭包经典问题在正式讲解JavaScript闭包原理和应用之前,我们先来看一下JavaScript闭包常见的问题:在循环中使用闭包,具体应用包括添加DOM事件回调和setTimeout定时操作等,下面是一个循环添加DOM事件的例子:
function init(){
var items = document.getElementsByClassName("item");
for(var i = 0;
i <
items.length;
i++){
var item = items[i];
item.onclick = function (){
console.log(j);
}
}
}
目的是让每个item按钮点击分别输出1-6,这是一个经典的例子,不会有预期的输出,点击每个按钮都是输出6,为什么呢?
【JS进阶之JavaScript闭包原理和应用实例详解】这牵扯到闭包和作用域的问题,同时也涉及最基本的JavaScript执行上下文的运行原理,在理解闭包之前你需要对JS的这些概念有充分的了解,你可以参考JavaScript的执行上下文运行机制获取更多内容。
先解释上面的问题,var i是一个init作用域的变量,在该作用域中,又称为词法环境,词法环境是你在书写代码的确定的变量或函数的作用范围,这里i是属于init词法作用域,结合onclick函数形成闭包,闭包是由函数和词法环境组成的,这里我们可以称onclick函数是一个闭包,闭包就是封闭的包,什么是包?JS是没有包或块作用域的,但是可以使用函数模拟包。在这里onclick函数是一个函数变量,闭包函数一般是一个函数变量,多个click函数共享一个全局变量i,执行6次循环,但是函数并没有执行,等到函数入栈执行的时候,这个i变成了6(为什么不是5?因为i最后还加了一次再判断,然后离开循环),于是各个函数访问的都是同一个i即6。
如何解决?利用闭包,其实就是通过值传递的方式,如下:
function init(){
var items = document.getElementsByClassName("item");
for(var i = 0;
i <
items.length;
i++){
var item = items[i];
(function(j){
item.onclick = function (){
console.log(j);
}
})(i);
}
}
二、什么是闭包?
文章图片
闭包的英文是Closure,意思是关闭,包又是什么呢?在Java中有包的概念,在C++和PHP中有命名空间,它们都是为了解决变量作用域的问题,所以包实质就是作用域的意思,在JavaScript中函数就是作用域,闭包就是被关闭的包,即:在包中的包(在作用域中的作用域,在函数中的函数),其实就是这样的东西,关键是闭包结合执行上下文的机制会导致闭包所在的环境上下文在内存中长期驻留。
上面有推荐关于JavaScript执行上下文机制的文章,可以参考一下,里面有讲到函数执行会创建对应的执行上下文对象,包括创建变量对象和作用域链,这两个东西决定了访问标识符的路径。例如返回一个闭包函数,函数未被释放,也就是其作用域链中还持有上一个作用域的引用,所以导致整个上下文都还不能被释放,这也是使用闭包的一个性能问题,使用复杂的闭包会消耗过多的内存空间。
下面是一个JavaScript闭包的例子:
function run(message){ // run{}词法环境
var code = 200;
// run{}作用域的变量
return function(){ // 一个闭包
console.log(message + ": " + code);
// 持有run{}作用域的code变量
};
}
var r = run("json");
// 执行完成run方法,其中的数据还未被释放,在驻留在内存中
r();
// 执行闭包函数,但是同样run未被释放
r();
// 执行多次
三、JavaScript闭包有哪些应用?
文章图片
JavaScript的闭包只有一个作用:实现属性和方法的隐藏和封装,其实也就是面向对象编程,其它更具体的应用都可以从这里得到扩展,如何设计和使用?使用一个函数封装一个闭包函数?返回一个函数变量?我们不如从执行上下文的机制来看,使用闭包的最主要特点就是它让整个上下文驻留在内存了,为什么呢?因为它持有其词法环境的数据或函数,从这个角度看它和面向对象中的类十分相似,可以设计词法环境封装的数据或函数为私有,再通过提供闭包函数访问,也就和普通的类模板一样了,下面看完整的应用例子:
/**
* JavaScript闭包应用实例
*/
function User(){ // User{}词法环境,相当于一个类作用域
var username;
// 相当于类的私有属性
var password;
return {
getUsername(){ // 相当于类的方法
return username;
},
setUsername(username_){
username = username_;
},
getPassword(){
return password;
},
setPassword(password_){
password = password_;
}
};
}function UserDao(){ // 词法环境
var user;
function getUser(){ // 相当于类的私有方法
return user;
}
function setUser(user_){
user = user_;
}
return {
insert(user_){
setUser(user_);
console.log("inserted new user: " + user.getUsername());
},
delete(){
console.log("deleting user: " + user.getUsername());
setUser(null);
},
getByName(username_){
if(getUser().getUsername() === username_)
return getUser();
}
};
}var user = User();
user.setUsername("Pincle");
user.setPassword("123456");
var userDao = UserDao();
userDao.insert(user);
console.log(userDao.getByName("Pincle").getPassword());
userDao.delete();
推荐阅读
- JavaScript原型链和继承原理分析和实例详解(一)(原型和原型链)
- 进阶!这一次彻底理解JavaScript的执行上下文(Execution Context)
- 在vue.js中使用Ajax发送get和post请求
- vue.js混入(minxins)的使用说明
- 由于应用BundleID信息校正不通过,无法分享到微信的问题——已解决
- 谈谈JavaScript如何优化构造函数和对象创建
- CSS Color详细介绍
- SASS插值表达式用法介绍
- C#中的StringBuilder用法详细指南