this 是什么?


在 JavaScript 中,函数是对象,是Function的实例,可以在变量间任意指定,可以传给函数的参数参考,当然,要新增为对象的特性也是可以的。例如:

function toString() {
    return '[' + this.name + ',' + this.age + ']';
}

var p1 = {
    name     : 'Justin', 
    age      : 35,
    toString : toString
};

var p2 = {
    name     : 'momor', 
    age      : 32,
    toString : toString
};

console.log(p1.toString());  // [Justin,35] 
console.log(p2.toString());  // [momor,32]

在上例中定义了一个toString函数,并分别设定为p1p2toString来参考,透过p1调用时,toString就像是p1的方法(Method),透过p2调用时,toString就像是p2的方法。

在上例中,toString函数中使用了this,在调用函数时,每个函数都会有个this,然而this参考至哪个对象,其实依调用方式而有所不同。以上例而言,透过p1调用时,toString中的this会参考至p1所参考的对象,也因此显示p1对象的nameage值,透过p2调用时,toString中的this则会参考至p2所参考的对象。

如果调用函数时是透过对象与点运算符的方式调用,则this会参考至点运算符左边的对象。

在 JavaScript 中,函数是Function的实例,Function都会有个call方法,可以让你决定this的参考对象。举例来说,你可以如下调用:

function toString() {
    return '[' + this.name + ',' + this.age + ']';
}

var p1 = {
    name : 'Justin', 
    age  : 35,
};

var p2 = {
    name     : 'momor', 
    age      : 32,
};

console.log(toString.call(p1));  // [Justin,35] 
console.log(toString.call(p2));  // [momor,32]

这次并没有将toString指定为对象的特性,而是直接使用call方法来调用函数,call方法的第一个参数就是用来指定函数中的this所参考的对象。如果函数原本具有参数,则可接续在第一个参数之后。例如:

function add(num1, num2) {
    return this.num + num1 + num2;
}

var o = {num : 10};

console.log(add.call(o, 20, 30)); // 60

Function也有个apply方法,作用与call方法相同,也可让你在第一个参数指定this所参考的对象,不过apply方法指定后续实参时,必须将实参收集为一个数组,如果你有一组实参,必须在多次调用时共用,就可以使用apply方法。例如:

function add(num1, num2) {
    return this.num + num1 + num2;
}

var o1 = {num : 10};
var o2 = {num : 100};
var args = [20, 30];

console.log(add.apply(o1, args)); // 60
console.log(add.apply(o2, args)); // 150

所以,this实际参考的对象,是以调用方式而定,而不是它是否附属在哪个对象而定。例如就算函数是附属在函数上的某个特性,也可以这么改变this所参考的对象:

function toString() {
    return this.name;
}

var p1 = {
    name     : 'Justin', 
    toString : toString
};

var p2 = {
    name     : 'momor', 
    toString : toString
};

console.log(p1.toString());        // Justin
console.log(p2.toString());        // momor
console.log(p1.toString.call(p2)); // momor

在最后一个测试中,是以p1.toString.call(p2)的调用方式,所以虽然toStringp1的特性,但call指定this是参考至p2,结果当然也是返回p2name

在用对象字面量创建对象时,也可以直接指定函数作为特性。例如:

var o = {
    name : 'Justin',
    toString : function() {
        return this.name;
    }
};

console.log(o.toString()); // Justin

如果调用函数时,无法透过.运算、callapply等方法确定this的对象,在严格模式下this会是undefined

全局对象是 JavaScript 执行时期全局可见的对象,在不同的环境中想要获取全局对象,会透过不同的名称,像是 Node.js 中可以使用global,浏览器中可以透过window或在全局范围使用this

因此,如果你想统一全局对象的变量名称,例如统一使用global,可以透过类似以下的方式:

var global = global || (function() {
    return this;
})();

严格模式下,当一个内部函数直接被调用时,无法确定this对象时,this会是undefined

function func() {
    function inner() {
        return this;
    }
    return inner();
}

console.log(func());    // undefined

底下也会是undefined

'use strict'

 (function() {
     return this;
 })();  // undefined

在严格模式下,如果真的想获取全局对象,可以透过两个方式,第一个是直接创建Function实例:

var global = global || Function('return this')();

第二个方式是间接参考eval函数:

var get = eval;
var global = global || get('this');

详情可以参考How to get the global object in JavaScript?,如果不想多个get名称,那也可以写为:

var global = global || (0, eval)('this');

这个有趣的语法在于,逗号运算符会从左而右运算每个运算数,然后返回最后一个运算数,可参考MDN:Comma operator的说明。

在 JavaScript 执行过程中,搞清楚this是谁有时非常重要,this的决定方式是在于调用,而非定义的方式。

举个例子来说,如果你想要自行实现ArrayforEach方法,则可以如下:

var obj = {
    '0' : 100,
    '1' : 200,
    '2' : 300,
    length : 3,
    forEach : function(callback) {
        for(var i = 0; i < this.length; i++) {
            callback(this[i]);
        }
    }
};

obj.forEach(function(elem) {
    console.log(elem);
});

在上例中,由于调用forEach时,obj参考的对象就是this所参考的对象,因而可以获取length等特性,函数是对象,所以自然可以丢给forEach作为实参。

在 ECMAScript 5 中,函数实例有个bind方法,执行结果返回一个新函数,这个新函数的this绑定对象固定为你调用bind时指定的对象。例如:

function forEach(callback) {
    for(var i = 0; i < this.length; i++) {
        callback(this[i]);
    }
}

var obj1 = {
    '0' : 100,
    '1' : 200,
    '2' : 300,
    length : 3,
};

var f1 = forEach.bind(obj1);

f1(function(elem) {
    console.log(elem);  // 100 200 300
});

var obj2 = {
    '0' : 10,
    '1' : 20,
    '2' : 30,
    length : 3,
    forEach : f1
};

obj2.forEach(function(elem) {
    console.log(elem);  // 100 200 300
});

在上面这个例子中,即使后来透过obj2.forEach(...)bind返回的函数,this都是绑定为obj1参考的对象。

bind可以指定实参,如果给的实参不全,返回的函数之后只要补上不全的实参就可以了。例如,这可以用来达到一些语言内置的部份函数(Partial function)效果:

function plus(a, b) {
    return a + b;
}

var addTwo = plus.bind(undefined, 2);
console.log(addTwo(10));    // 12
console.log(addTwo(5));     // 7




展开阅读全文