js高级应用之闭包closure

本文深入解析JavaScript中的闭包概念,包括闭包的定义、产生条件、作用及常见问题。通过实例展示如何利用闭包保护变量、实现计数器和私有变量等功能,同时探讨闭包可能引发的内存泄漏等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、闭包的定义

【JavaScript高级程序设计】
闭包:指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式:在一个函数内部创建另一个函数。

【百度百科 】闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

总结: 在JavaScript中,在函数中定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。

闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。

二、产生闭包的条件

(1) 函数内部定义另一个函数
(2) 内部函数访问了外部函数的变量

function outer() {
    var num = 10;
    function inner() {
        console.log(num); // 产生
        console.log(20);  // 不产生
    }
    return inner;
}

outer()();

三、闭包的作用

(1) 保护变量数据(如全局变量污染问题)
(2) 将变量值保存在内存中
(3) 把变量作为自己的私有变量

  • 应用1: 计数器
    举个栗子:统计一个函数的调用次数
var count = 0;
function fn(){
  count++;
  console.log("调用次数是"+count);
}
fn();  // 调用次数是1
fn();  // 调用次数是2
fn();  // 调用次数是3

缺点:count是全局变量,不安全。==> 使用闭包

function outer(){
  var count = 0;
  function add(){
    count++;
    console.log("调用次数是"+count);
  }
  return add;
}
var result = outer();
result();   // 调用次数是1
result();   // 调用次数是2
result();   // 调用次数是3
  • 应用2: 私有变量
    举个栗子:使用闭包实现私有变量的读取和设置
function outer(){
  var num = 10;
  function set_num(n){
    num = n;
  }
  function get_num(){
    return num;
  }
  return {
    set_num: set_num,
    get_num: get_num
  }
}
var obj = outer();
console.log(obj.get_num());
obj.set_num(20);
console.log(obj.get_num());

四、闭包容易遇到的坑点

(1) 引用的变量可能发生变化
(2) this指向问题
(3) 内存泄露问题

  1. 引用的变量可能发生变化
function outer() {
  var result = [];
  for (var i = 0; i<10; i++){
    result[i] = function () {
      console.log(i);
    }
  }
  return result;
}

看样子result每个闭包函数打印对应数字,0,1,2,3,…,9。
实际不是!!!
因为每个闭包函数访问变量i是outer执行环境下的变量i,随着循环的结束,i已经变成10了,所以执行每个闭包函数,结果打印10, 10,10,10,…,10。

function outer() {
  var result = [];
  for(var i = 0; i<10; i++){
    result[i] = function (num) {
      return function() {
        console.log(num);
        // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
      }
    }(i);
 }
 return result;
}
  1. this指向问题

在闭包中使用 this 对象可能会导致一些问题。
匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

var name = "The Window";
var object = {
  name: 'My Object',
  getName: function() {
    return function() {
      console.log(this.name);
    }
 }
}
object.getName()();  // The Window

因为里面的闭包函数是在window作用域下执行的,也就是说,this指向window。

每个函数在被调用时都会自动取得两个特殊变量: this 和 arguments 。
内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

解决方法:要想访问到,可以使用that保存this值,arguments同理。

var name = "The Window";
var object = {
  name: "My Object",
  getName: function(){
    var that = this;
    return function(){
      console.log(that.name);
    };
  }
};
object.getName()(); // My Object
  1. 内存泄露问题

闭包占用的内存是不会被释放的,因此,如果滥用闭包,会造成内存泄漏的问题。

function  showId() {
  var el = document.getElementById("app");
  el.onclick = function(){
    alert(el.id);
    // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    console.log(el);
  }
}

释放闭包占用内存

function  showId() {
  var el = document.getElementById("app");
  var id = el.id;
  el.onclick = function(){
    alert(id);
    // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    console.log(el);
  }
  el = null; // 主动释放el
}

结论:闭包很强大,但是只有在必须使用闭包的时候才使用。

五、面试题

  1. 点击不同的按钮, 打印对应不同的下标: 0~9
var buttons = document.querySelectorAll('button');
console.log(buttons);

// 以为的答案 (no no no !!!)
for(var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function () {  
    console.log(i);
  }
}

// 第1种方式: 闭包
for (var i = 0; i < buttons.length; i++) {
  (function (i) {  
    buttons[i].onclick = function () {
      console.log(i);
    }
  })(i);
}

// 第2种方式: 闭包另一种方式
for(var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = (function (i) {  
    return function () {  
      console.log(i);
    }
  })(i);
}

// 第3种方式: let
for(let i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function () {  
    console.log(i);
  }
}

// 第4种方式: 存下标
for(var i = 0; i < buttons.length; i++) {
  buttons[i].index = i;
  buttons[i].onclick = function () {
    console.log(this.index);
  }
}

// 第5种方式: forEach(实质也是闭包)
buttons.forEach(function (v, i) {  
  v.onclick = function () {  
    console.log(i);  
  }
})

// 第6种方式: bind
for(var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = (function (i) {  
    console.log(i);
  }).bind(null, i);
}

// 第7种方式: with 块级作用域
for(var i = 0; i < buttons.length; i++) {
  with({i:i}) {
  	buttons[i].onclick = function () {  
      console.log(i);
    }
  }
}
  1. 每隔一秒钟打印一个数字: 1,2,3,4,5,6,7,8,9,10
// 第1种方式:闭包
for(var i = 1; i <= 10; i++) {
  (function(i) {
    setTimeout(function() {             
      console.log(i);             
    }, i*1000);        
  })(i); 
}

// 第2种方式: 在定时器里面使用闭包
for(var i = 1; i <= 10; i++) {  
  setTimeout((function(i) {  
    return function () {  
      console.log(i);  
    }  
  })(i), i*1000);
}

// 第3种方式: let
for(let i = 1; i <= 10; i++) {
  setTimeout(function() {   
    console.log(i);  
  }, i*1000);
}

// 第4种方式: 传参(其实也就是闭包)
var outer = function(i) {
  setTimeout(function() {   
    console.log(i); 
  }, i*1000);
}
for (var i = 1; i <= 10; i++) {
  outer(i);
}

// 第5种方式: forEach(实质也是闭包)
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.forEach(function(v, i) {  
  setTimeout(function() {   
    console.log(v); 
  }, v*1000);
})

// 第6种方式: bind
for (var i = 1; i <= 10; i++) {
  setTimeout((function(i) {             
    console.log(i);         
  }).bind(null, i), i*1000);   
}

// 第7种方式: with 块级作用域
for (var i = 1; i <= 10; i++) {
  with({i:i}) {
    setTimeout(function() {             
      console.log(i);         
    }, i*1000);
  }         
}

【核心思想:】不使用全局的 i ,使用自己作用域里的 i 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值