javascript closure 闭包

一,前言

听过很多大道理,却依然过不好这一生。看过很多闭包解释,却依然不知道到底什么意思。在查阅相关资料后,本文通过记笔记的方式,来从新理一下,我所理解的javascript闭包。为了简化起见,全文“闭包”代指“javascript 闭包”,因为尚不确定其它语言的闭包是否同javascript类似。

二,何为闭包

一切都要从访问范围说起,global variale访问范围太大,整个页面都可以访问。如:

var a=0;
function add() {
    var b = a + 1;
}

function中的local variable太小,只局限在方法中。如:

function add() {
    var a = 0;
    var b = a + 1;
}
alert(a);//在方法外访问a,由于超出a的范围,要报未定义异常。

在某些情况下,我们希望有一种介于两者之间的variable scope,他虽然在方法内定义,但却可以在方法外被引用和访问,且保留被修改的状态。javascript提供了两种方式来达到该目的。

  • 方式1:将变量定义在prototype上,这样保证了变量可以被对象在任意位置访问,但同时只能通过对象访问。如:

    function add() {
        this.a = 0;
        this.plus = function () {
            return this.a += 1;
        };
    
    
    }
    var obj = new add();
    alert(obj.plus());
    alert(obj.plus());
    

a虽然写在function add内(严格来说不叫定义在function内),但是由于其通过this定义在了add的prototype上,所以add的对象obj可以通过plus方法,实现对a累加。第一次输入1,第二次输出2。

  • 方式2:使用闭包暴露接口,来操作function内部变量,实现累加。如:

    function add() {
        var a = 0;
        return function plus() {
            return a += 1;
        };
    
    
    }
    var addInvoke = add();
    alert(addInvoke());
    alert(addInvoke());
    

    上述代码第一个alert输出1,第二个alert输出2。可以看到,我们通过执行addInvoke(),能够实现对a的累加计算,即便a定义在function add中。

三,如何实现闭包

根据上述闭包的实现代码来拆解闭包的实现步骤为:

  • 在方法add中定义变量a,再在该方法中定义一个嵌套的方法plus来访问该变量a,然后将plus方法以方法或对象的形式返回(上例为以方法的方式返回)。
  • 执行一次方法add,将其执行结果赋给一个变量addInvoke(这样该变量实际上存储的是plus方法的引用)
  • 通过变量addInvoke来执行plus方法:addInvoke(),从而实现对a的累加。

闭包优化

可以看到上述步骤,为了拿到对plus方法的引用,需要执行一次外部方法add,实际上这步可以通过使用self-invoking function来省略。如:

var add = (function()  {
    var a = 0;
    return function plus() {
        return a += 1;
    };
})();
alert(add());
alert(add());

由于self-invoking定义后即执行,所以上例add中存储的便是plus方法的引用。

以对象的方式返回的闭包

操作local varible的方法不光可以以方法的方式返回,还可以以对象的方式返回。如:

    var add = (function()  {
        var a = 0;
        return {
            plus:function (){
                return a += 1;
            }
        };
    })();
    alert(add.plus());
    alert(add.plus());

四,闭包的使用场景

说到这里差不多讲明白了闭包是什么和怎么实现。那它具体的用处呢?目前我了解有以下三种。

隐藏状态

比如代码:

function greet(message) {
  console.log(message);
}

function greeter(name, age) {
  return name + ", who is " + age + " years old, says hi!";
}
// Generate the message
var message = greeter("Bob", 47);

// Pass it explicitly to greet
greet(message);

该代码有哪里不好呢?通过greeter构造的信息,需要使用message变量存放,然后再传给greet方法。由于这个message暴露在global,其它代码可能会操作从而造成value污染。可以通过闭包将中间过程message隐藏,从而避免问题,如下:

 function greeter(name, age) {
  var message = name + ", who is " + age + " years old, says hi!";

  return function greet() {
    console.log(message);
  };
}

// Generate the closure
var bobGreeter = greeter("Bob", 47);

// Use the closure
bobGreeter();

实现OO编程

传统的OO编程方式如下:

// Define the constructor
function Person(name, age) {

  // Store the message in internal state
  this.message = name + ", who is " + age + " years old, says hi!";

};

// Define a sync method
Person.prototype.greet = function greet() {
  console.log(this.message);
};

var p1 = new Person('Tom',100);
p1.greet();

可以看到我们需要使用new来创建对象,然后再访问greet()。使用闭包来实现类似结构如下:

function newPerson(name, age) {

  // Store the message in a closure
  var message = name + ", who is " + age + " years old, says hi!";

  return {

    // Define a sync function
    greet: function greet() {
      console.log(message);
    }

  };
}

var p1 = newPerson("Tom",111);
p1.greet();

闭包能更好的实现回调

哎哟不写了,好累。

参考链接