`

Javascript学习笔记 What is "this"

阅读更多
在JavaScript中,每当我们使用函数或者阅读已经写好的函数时,我们经常会看到里面用到this关键字的情况。

对于一个JS的初学者(尤其是对于有“主流”语言编程经验的人),会觉得this是让人凌乱的东西。

为了理解this,我们需要理解函数的调用方法。

在《Javascript Good Parts》一书中,将函数分为了四种调用方式:
方法调用;
函数式调用;
构造器调用;
apply(call)调用。
通读《Javascript Good Parts》的4.3节(2009年4月第一版)后,我对调用方式做了一个简短的总结。

当每个函数被调用的时候,他将会收到两个参数:this和arguments。为了方便起见,我们先写一个函数。

// 例1
var f = function(x, y) {
      var count = x + y + this.z;
      this.total = count;
      console.log(this.total);
};

当函数被调用的时候,就会得到this和arguments。

一、arguments是什么?
arguments是比较好理解的,它是一个传入实际参数的数组。这里之所以用“实际参数”,是为了和this相对应,在我们的例子中,arguments就是x和y。

由于有arguments的存在,所以js可以不必在函数定义的时候规定参数,比如上一个函数完全可以写成。

// 例2
var f = function() {
      var count = arguments[0] + arguments[1] + this.z;
      this.total = count;
      console.log(this.total);
};

虽然上面的函数可读性变差了,但是它和例1的功能是一样的。

基于以上原理,JS并不会支持如Java一样重载,如果有这样的代码:
// 例3
var o = {
    f: function(){ console.log(1); },
    f: function(){ console.log(2); },
};
//注意,这段代码没有错误的,JS真是自由的国度。
但是o只会承认第二的方法,即,后面的芳芳会覆盖前面的。

二、开始this吧

下面到了this,其实上面提到的四种调用方式的区别,从this的角度看,只是this所代表的地方不同。

2.1 方法式调用。
这要求函数必须在一个对象里面,比如:
// 例4
var o = {
    f: function() {
        console.log(this); //
    }
};
当我们使用o.f()的时候,打印出来的就是对象o。
可以看出,函数作为方法调用中,函数使用的this就是函数所在的对象。

2.2 函数式调用。
// 例5
var f = function() {
    console.log(this);
};
f()执行的时候,打印出来的是全局对象。如果你使用浏览器的开发者工具(比如打开Chrome,按F12后用Console界面)输入例子,你会得到Window对象。

2.3 构造器式调用。
// 例6
var Fun = function() {
    console.log(this);
};
Fun.prototype.x = function() {
    return 1;
};
Fun.prototype.y = 2;
对于例6中的函数,当我们执行var f = new Fun();的时候,this是什么呢?是Fun.prototype,即Fun的原型链。

2.4 apply/call式调用
我们先了解一下apply和call调用函数的方式:
Fun.apply(Obj, [x, y]);
Fun.call(Obj, x, y);
以上两个方式,都是以Obj为对象,以x和y为参数,调用方法Fun。所不同的是apply要求参数是数组,call要求参数挨个传入。
这样我们就可以方法式调用来理解apply式的调用。

我们大致可以了解this是什么了,this就是当前函数的执行环境。那么以下几个理解或许有助于我们记忆。
如例5中函数式调用,可以把f()看作是window.f();
例6中new的调用方式可以看成Fun.call(Fun.prototype, null),当然最后还有需要把生成结果返回给f;
再进一步,所有的前三种调用方式都可以用call来模拟。如例4中o.f()可以看做f.call(o, null),而例5中f()可以看做f.call(window, null);
而把this和arguments并列理解,也就可以解释为什么函数调用时,为什么要function.apply(object, arguments)了。

三、还有几个问题
以上的解释可以理解函数调用中大部分this的问题,但是我们要注意几个问题。
3.1 匿名函数的调用。
(function() { console.log(this); })();
JS很自由,它允许函数没有名字,这种情况下,this仍然指向全局对象(浏览器中为window)。

3.2 方法中函数式的调用。
这个说法很拗口,但是确有存在。
// 例7
var o = {
    f: function() {
        var innerF = function() {
            console.log(this);
        };
        innerF();
    }
};
当我们使用o.f();的时候,innerF也被顺便调用了,但是打印出来的结果仍然是全局对象。
这很让人头疼,如果说例5中f()打印出来的是全局对象是因为函数f就写在window下,这里innerF距离window隔了好几层。
但是现实如此,我们只能接受。具体原因估计要查ECMAScript最初开发者的交流邮件!

这里就引入了一个问题,如果innerF想调用对象o的代码怎么办?
好在可以“曲线救国”:
var o = {
    f: function() {
        var that = this;
        var innerF = function() {
            console.log(that);
        };
        innerF();
    }
};
当使用o.f();的时候,打印出来的就是对象o,而不是全局对象。

3.3 回调函数中的this。
// 例8
var f = function(cb) {
    cb();
};
var o = {
    callback: function() {
        console.log(this);
    }
};

f(o.callback);的结果仍然是window,可见函数的调用方式决定着this,把this和arguments作为参数理解,就更容易理解了。
当然回调函数被调用的方式是由原函数决定的。
对例8稍作修改,就可以得到不一样的结果。
// 例9
var f = function(callback) {
var innerO = {
x: 1,
cb: callback
};
innerO.cb();
};

var o = {
callback: function() {
console.log(this);
}
};

f(o.callback);
var f = function(callback) {
    var innerO = {
        x: 1,
        cb: callback
    };
    innerO.cb();
  
};
var o = {
    callback: function() {
        console.log(this);
    }
};
执行例9的f(o.callback);,打印结果是innerO对象。所以回调函数的使用就要看原函数的作者的意图了。

以上对于this的理解只是表象的总结,深入的了解需要查阅资料和了解浏览器的解析器

诚所谓,路漫漫其修远兮,吾将上下而求索。
var f = function(cb) {
cb();
};

var o = {
callback: function() {
console.log(this);
}
};

f(o.callback);
var f = function(cb) {
cb();
};

var o = {
callback: function() {
console.log(this);
}
};

f(o.callback);
var f = function(cb) {
cb();
};

var o = {
callback: function() {
console.log(this);
}
};

f(o.callback);
var f = function(cb) {
cb();
};

var o = {
callback: function() {
console.log(this);
}
};

f(o.callback);
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics