下面都是干货,涉及JavaScript高级开发基础知识。如果有理解上的错误,欢迎指出。

最初级的闭包

下面是一个简单的计数器。

function Counter(initNum){
    var num = parseInt(initNum);
    return function() { num += 1; return num; };
}
var counter = new Counter(12);
console.log(counter());//12 + 1 = 13
console.log(counter());//13 + 1 = 14
console.log(counter());//14 + 1 = 15
console.log(counter());//15 + 1 = 16

我们可以看到储存当前次数的变量藏在函数Counter内;函数Counter内部返回一个匿名函数,每一次调用counter()都会执行一次这个匿名函数,其作用域在Counter下,实现对计数器counter2数字加1的功能。

稍作改进的计数器

function Counter2(initNum){
    var num = parseInt(initNum);
    this.get = function() { return num; }; //获取当前次数
    this.add = function(){
        return function() { num += 1; return num; }}; //num++
}

var counter2 = new Counter2(0); //initial
console.log(counter2.add()()); //1
console.log(counter2.get()); //1
console.log(counter2.add()()); //2
console.log(counter2.add()()); //3
console.log(counter2.get()); //3

我们可以看到,Counter2下的方法add返回一个匿名函数,匿名函数内部同第一个例子,num为Counter2内的局部变量,匿名函数的作用域还是在counter2下,所以一样实现了对计数器counter2数字加1的功能。

计数器中作用域的差异

下面的内容涉及this的差异。请看Rebecca Murphey关于this关键字的阐述:

In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked. The value of this is determined using a simple series of steps:

  • If the function is invoked using Function.call or Function.apply, this will be set to the first argument passed to call/apply. If the first argument passed to call/apply is null or undefined, this will refer to the global object (which is the window object in Web browsers).
  • If the function being invoked was created using Function.bind, this will be the first argument that was passed to bind at the time the function was created.
  • If the function is being invoked as a method of an object, this will refer to that object.
  • Otherwise, the function is being invoked as a standalone function not attached to any object, and this will refer to the global object.

简单来说,就是this与执行环境有关,跟它在哪里定义并没有什么关系

var num = -999;
var Counter3 = {
    num: 0,
    add: function(){
        return function() { this.num += 1; return this.num; }
    }
};
console.log(Counter3.add()());

var Counter4 = {
    num: 0,
    add: function(){
        var that = this;
        return function() { that.num += 1; return that.num; }
    }
};
console.log(Counter4.add()());

同是计数器,在浏览器中看看为啥效果不一样?这就涉及this的作用域问题。

Counter3add里面的this执行环境为浏览器的Window对象。因为console.log(Counter3.add()());相当于console.log(function() { this.num += 1; return this.num; });console所在域为Window,所以this指向Window,故访问到的是全局变量num。简而言之,this<=>Window。得结果-998

Counter4add里面的that指的是执行是Counter4本身。因为执行Counter4.add()时先Counter4add方法的作用域,定义thatthis执行环境为Counter4自身。返回的因为闭包的可以共用return时所处环境的局部变量,所以匿名函数中访问的thatvar that = this;中的that。简而言之,this<=>that<=>Counter4。故得结果1

By the way,如果在Node.js中,Counter3得不到这个结果,因为在这里this指的是global对象,只需要把that去掉就可以在Node.js里面用啦。说白了就是Node.jsJavaScriptthis规则并不完全一样造成的。

#搭配Timer的计数器

第一款

采用function构造的对象的计数器。

function Counter1(initNum){
    var num = parseInt(initNum);
    this.get = function() { return num; };
    this.add = function(){ num += 1; return num; };
}

var counter1 = new Counter1(0);
setInterval(function(){
    console.log(counter1.add());
}, 1000);

第二款

采用{}构造的对象的计数器,跟闭包没什么关系。

var counter2 = {
    num: 0,
    add: function(){ this.num += 1; return this.num; }
};
setInterval(function(){
    console.log(counter2.add());
}, 1000);

第三款

采用{}构造的闭包同款的对象的计数器

var counter3 = {
    num: 0,
    add: function(){
        var that = this;
        return function(){
            that.num += 1; return that.num;
        }
    }
};
setInterval(function(){
    console.log(counter3.add()());
}, 1000);

链式调用

看了JavaScript 中的函数式编程实践这篇五年前的文章有感而发。随便写了个计算器,符合函数式编程的要求。

function Num(x){
    this.add = function (b){  x = x + b; return this; };
    this.sub = function (b){  x = x - b; return this; };
    this.mul = function (b){  x = x * b; return this; };
    this.div = function (b){  x = x / b; return this; };
    this.rem = function (b){  x = x % b; return this; };
    this.inc = function (){  x = x + 1; return this; };
    this.dec = function (){  x = x - 1; return this; };
    this.get = function (){ return x; };
    this.equal = function (b){  return x == b; };
    this.great = function (b){  return x > b; };
    this.less = function (b){  return x < b; };
}

//链式操作
// (10 + (24 / 8) - 23) / 6 * 3 - 1
console.log(new Num(10).add(new Num(24).div(8).get()).sub(23).mul(6).div(3).dec().get());

// 10 % (24 / 8) == 3
console.log(new Num(10).rem(new Num(24).div(8).get()).equal(3));

看上去是挺复杂。这里也涉及到this的作用域问题,前面两个例子没有斟酌出来的话可以看看这个。

除非注明,麦麦小家文章均为原创,转载请以链接形式标明本文地址。

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

本文地址:https://blog.micblo.com/2015/06/20/JavaScript闭包和链式调用的那些事/