闭包

一步一步重温 JavaScript 基础系列。
本章节将会理解JS的闭包、闭包为何会造成内存泄漏,以及闭包相关考题

闭包

概念

闭包,是指有权访问另一个函数作用域内的变量的函数。创建闭包的方式,常见如:在一个函数内部创建另外一个函数,并且将该函数作为返回值,同时该函数还引用了外部函数作用域内的变量。例如:

1
2
3
4
5
6
7
8
9
10
11
function foo1 () {
var n = 1;
function foo2 () {
alert(n)
}

return foo2;
}

var f = foo1();
f(); // 1

更广泛的含义

如果一个函数访问了此函数的父级或父级以上的作用域变量,该函数就可以称之为闭包。例如:

1
2
3
4
var num = 1;
(function(i) {
console.log(i)
})(num)

闭包产生的原因

在函数作用域外无法读取函数作用域内的变量,但是借助JS作用域链,函数内部的函数可以访问外层函数的变量。根据这一特性,就可以在函数内部创建并返回一个函数,将函数内部和函数外部作用域连接起来,由此便产生了闭包。

闭包的用途

闭包可以让函数外部访问函数内局部作用域的变量,并且可以让该变量始终保存在内存中, 例如:

1
2
3
4
5
6
7
8
9
10
11
function fn () {
var num = 3;
function fn1 () {
alert(++num);
}
return fn1
}

var fn2 = fn();
fn2() // 4
fn2() // 5

根据这一特性,可以解决实际场景中遇到的以下问题:

  • 引入的全局变量太多,极易产生全局变量命名冲突
    例如:(Jquery)

    1
    2
    3
    ;(function($){
    // 内部实现
    })(Jquery)
  • 在开发JS插件时,为了保护插件内部变量不被改变
    例如二(防止私有变量被污染)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    const CHANGE_FONT_SIZE='CHANGE_FONT_SIZE';

    // 设置一个闭包,把变量保护起来,通过返回值调用
    function createStore() {
    let appState={
    fontSize: '26px',
    color: 'red'
    }

    // 保护变量被修改,深克隆
    let getter=()=>JSON.parse(JSON.stringify(appState));

    // 改变变量的方法,action代表一个命令对象,就是一个普通的js对象,起码需要一个字段控制命令类型type
    let setter=(action)=>{
    switch(action.type){
    case CHANGE_FONT_SIZE:
    appState.fontSize = action.fontSize;
    default:
    return;
    }
    }

    //返回出去的修改和取值的接口
    return{
    getter,
    setter
    }
    }

    let store=createStore();

    // 取值函数
    store.getter().fontSize;

    // 修改函数
    store.setter({type:CHANGE_FONT_SIZE, fontSize: '30px'});

    如果需要对重要参数防止被篡改,可使用闭包规定变量的getter和setter
    以上示例参考,https://www.cnblogs.com/GoCircle/p/9804106.html

    例如三(利用闭包来完成对象的继承,做到函数的私有性和公开性):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    (function(){
    // Person模块
    var Person = {
    // 数据部分
    data: {
    name: "",
    age: ""
    },

    // 操作部分
    opt:{
    setName:function(name){
    Person.data.name=name;
    },
    setAge:function(age){
    Person.data.age=age;
    },
    getName:function(){
    return Person.data.name;
    },
    getAge:function(){
    return Person.data.age;
    },
    init:function(name,age){
    Person.opt.setName(name);
    Person.opt.setAge(age);
    }
    },

    // 公开的API
    openAPI:function(){
    return {
    init:Person.opt.init,
    getName:Person.opt.getName,
    getAge:Person.opt.getAge
    };
    }
    };

    window.Person = Person;
    })()

    var p = window.Person.openAPI();
    p.init("wx", "20");
    alert(p.getName());
    alert(p.getAge());

    简单说一下jQuery的源码,其实整个jQuery其实就是一个自调函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    (function(window){
    // jQuery是一个函数,一个对象,一个构造器函数
    var jQuery = function( selector, context ) {
    return new jQuery.fn.init( selector, context );
    },
    // 定义了jQuery的prototype的内容
    // fn = prototype
    // 加在jQuery的prototype上的方法可以通过选择器创建出来的对象调用
    // 对象方法
    jQuery.fn = jQuery.prototype = {
    ready: function(){},
    each: function(){}
    },

    // post方法和extend方法直接加在了jQuery对象上
    // 全局方法
    jQuery.post = function(){},
    jQuery.extend = function(){}

    // 程序员在用jQuery的时候,是window的一个属性
    //jQuery = $
    window.jQuery = window.$ = jQuery;
    $.fn = jQuery.prototype = window.$.prototype = window.$.fn = window.jQuery.fn = window.jQuery.prototype

    // 加在jQuery的prototype上的方法和加在jQuery对象上的方法称为jQuery的插件开发
    })(window);

    window.jQuery.fn=$.prototype=window.$.fn=jQuery.prototype=window.jQuery.prototype

    以上示例参考,https://blog.csdn.net/canot/article/details/50760804

    闭包的副作用

  • 内存泄漏
    如上例fn,fn1作为fn的返回值,形成闭包。fn1被赋值给全局变量fn2,导致fn1始终存在内存中,而fn1引用了父函数fn内的变量num,因此num也会一直在内存中,无法被垃圾回收机制(GC)回收。

闭包相关考题

  • 考点一:闭包函数调用时,this指向全局window

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 例①
    var name = "The Window";
     var object = {
      name : "My Object",
      getNameFunc : function(){
       return function(){
        return this.name;
       };
      }
     };

     alert(object.getNameFunc()()); // The Window

    // 例②
    var name = "The Window";
     var object = {
      name : "My Object",
      getNameFunc : function(){
    var that = this;
       return function(){
        return that.name;
        };
      }
     };

     alert(object.getNameFunc()()); // My Object

    题目引用自,http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

原创技术分享,您的支持将鼓励我继续创作