06-javascript基础----js闭包

标签: javascript知识  js  javascript  前端  函数闭包

一、闭包

1、理解

看这段代码:

function foo() {

  var i = 1

  function show() {
    i += 1
    console.log(i)
  }

  return show
}

var a = foo()
a()
  • 正常情况,函数定义时,开辟一个内存空间,使用结束后,该变量取消指向内存地址,内存地址准备被销毁

在这里插入图片描述

  • 如果内部函数引用了外部函数的值,那么就会形成闭包

在这里插入图片描述

2、 概念

  • 闭包是一个存在内部函数 的引用关系

  • 该引用指向的是 外部函数局部变量对象

3、产生条件

  1. 函数嵌套
  2. 内部函数引用外部函数的局部变量
  3. 内部函数被使用

为什么内部函数一定要被使用?

因为函数变量提升的时候,如果内部函数没用被使用,在预解析的过程中,不会被定义

4、优缺点

优点:

  1. 延长外部函数变量对象的生命周期
  2. 间接使得函数外部可以操作(读写)到 函数内部的数据(变量/函数)

浏览器为了性能,后期将外部函数中,不被内部函数使用的变量清除了

缺点:

  • 延长外部函数变量对象的生命周期**(占内存,如果不及时清除,容易造成内存溢出、泄露)**

5、使用闭包的注意点

  1. 及时清除闭包
  2. 让内部的函数成为垃圾对象

6、生命周期

  1. 产生:在嵌套内部函数定义指向完就产生了
  2. 死亡:在嵌套内部函数成为垃圾对象时
function fn1() {
    // 预解析阶段,闭包产生
	var a = 2
    
    function fn2() {
		a++
        console.log(a)
    }
    
    return fn2
}
var f = fn1()
f()

f = null // f指向内部函数发生改变,内部函数成为 垃圾对象,闭包死亡

7、常见的闭包

  1. 将函数作为另一个函数的返回值

    function fn1() {
    	var a = 2
        
        function fn2() {
    		a++
            console.log(a)
        }
        
        return fn2
    }
    var f = fn1()
    f() // 3
    f() // 4
    
  2. 将函数作为实参传递给另一个函数调用

    function showMsgDelay(msg, time) {
      setTimeout(() => {
        console.log(msg)
      }, time);
    }
    
    showMsgDelay('hello', 1000)
    

二、闭包的应用

作为模块

这种使用方式,就跟module的使用方式很相似

作用:定义JS模块

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只想外暴露一个包含 n 个方法的对象或函数
  • 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
// js模块
function mathModule() {
	var result = 0
    
    function add(x) {
        return result += x
    }
    function subtract(x) {
		return result -= x
    }
    
    return {
        add,
        subtract
    }
}

使用:

<script type="text/javascript" src="mathModule.js"></script>
<script>
  var mathObj = mathModule()
  console.log(mathObj.add(1)) // 1
  console.log(mathObj.subtract(2)) // -1  
</script>

三、闭包的面试题

1、第一道(附解析)

var name = 'The Window'
var obj = {
  name: 'My Object',
  getName: function() {
    return function() {
      return this.name
    }
  }
}

console.log(obj.getName()())

答案: The Window

解析:

/* obj.getName()() 可以分解为以下代码 */

var result = obj.getName() // 1. 返回为: function(){ return this.name }

result() // 2. 就是执行 function(){ return this.name }
		 // 3. result() ==  window.result()
	     // 4. 所以此时,this为 window
		 // this上面有一个变量,name = The Window,在全局作用域就有了
         // 所以输出 The Window

2、第二道(附解析)

var name = 'The Window'
var obj = {
   name: 'My Object',
  getName: function() {
    var that = this  // 缓存this,这样可以保证this的指向
    return function() {
      return that.name
    }
  }
}

console.log(obj.getName()())

答案:My Object

解析:

/* obj.getName()() 可以分解为以下代码 */

var result = obj.getName() // 1. 返回为: function(){ return that.name }

result() // 2. 就是执行 function(){ return that.name }
		 // 3. 由于此时的that,闭包对象指定了that = this,而在getName的this为obj 
	     // 4. 所以此时,that为 obj
		 // obj上面有一个变量,name = My Object,在局部作用域就有了
         // 所以输出 My Object

3、第三道(最难的)

明白这道题,闭包就可以出师了

function fun(n, o) {
  console.log(o)

  return {
    fun: function(m) {
      return fun(m, n)
    }
  }
}

var a = fun(0) 
a.fun(1) 
a.fun(2) 
a.fun(3) 

var b = fun(0).fun(1).fun(2).fun(3)
      
var c = fun(0).fun(1)
c.fun(2)
c.fun(3)

答案:

undefined
0
0
0

undefined
0
1
2

undefined
0
1
1

解析:

解析之前,首先要明确两个语法:

  1. var a = fun(0)、var b = fun(0)

    这两个并没有共享同一个 闭包对象,而是分别创建两个不同的闭包对象,也就是对于a、b来说,最开始的console.log(o),都是undefined

  2. a.fun(1)、a.fun(2) 和 fun(1).fun(2)

    这两个并不是等同的,第一个a.fun(1)、a.fun(2)都只是使用同一个函数;第二个fun(1).fun(2)等同于fun(2)在使用fun(1)基础上的函数

    /* 如果fun有一个变量是 1, 调用一次就会 +1 */
    
    a,fun(1) //那么这两个都是1
    a.fun(2)
    
    fun(1).fun(2) // fun(1) 的变量是 1, fun(2) 的变量 就会改变,为2
    

那么,明确之后,现在正式开始解析

function fun(n, o) {
  console.log(o)

  return {// 满足产生闭包的条件,产生了一个闭包引用->外部函数fun的变量对象{n:0,o: undefined}
    fun: function(m) {
      return fun(m, n)
    }
  }
}
// 意思是:fun调用后,返回一个对象,对象内有一个函数,函数内返回的是这个外部函数fun
// 即 第一个是 window.fun(n, o),第二个是 对象.fun(m),这个返回 window.fun(n, o)
  • a的调用方式:

    /* 
        开拓一个变量对象,此时:
        window.fun的 n = 0, o = undefined
        a.fun 的 o = 0 
    */
    var a = fun(0)  // 输出 undefined
    
    /*
    	以下都是调用同一个 a.fun,上面可得知,a.fun 的 o = 0
    */
    a.fun(1) // 输出 0
    a.fun(2) // 输出 0
    a.fun(3) // 输出 0
    
  • b的调用方式:

    var b = fun(0).fun(1).fun(2).fun(3)
    /*
    	以上可以分解为:
    	var b0 = fun(0)
    	var b1 = b0.fun(1)
    	var b2 = b1.fun(2)
    	var b3 = b2.fun(3)
    */
    
    /*
    	开拓一个变量对象,此时:
        window.fun的 n = 0, o = undefined
        b0.fun 的 o = 0 
    */
    var b0 = fun(0) // 输出 undefined
    
    /*
    	开拓一个变量对象,此时:
        b0.fun 的 o = 0 
        b1.fun 的 o = 1
    */
    var b1 = b0.fun(1) // 输出 0
    
    /*
    	开拓一个变量对象,此时:
        b0.fun 的 o = 0 
        b1.fun 的 o = 1
    */
    var b2 = b1.fun(2) // 输出 1
    
    /*
    	开拓一个变量对象,此时:
        b0.fun 的 o = 0 
        b1.fun 的 o = 1
    */
    var b3 = b2.fun(3) // 输出 2
    
  • c的调用方式

    var c = fun(0).fun(1)
    c.fun(2)
    c.fun(3)
    
    /*
    	以上可以分解为:
    	var c0 = fun(0)
    	var c1 = c0.fun(1)
    	c1.fun(2)
    	c1.fun(3)
    */
    
    /*
    	开拓一个变量对象,此时:
        window.fun的 n = 0, o = undefined
        c0.fun 的 o = 0 
    */
    var c0 = fun(0) // 输出 undefined
    
    /*
    	开拓一个变量对象,此时:
        c0.fun 的 o = 0 
        c1.fun 的 o = 1
    */
    var c1 = c0.fun(1) // 输出 0
    
    /*
    	以下都是调用同一个 c1.fun,上面可得知,c1.fun 的 o = 1
    */
    c1.fun(2) // 输出 1
    c1.fun(3) // 输出 1
    
版权声明:本文为pig_is_duck原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/pig_is_duck/article/details/108934122