与其他语言相比,函数的this
关键字在JavaScript中的行为略有不同。并且它在严格模式和非严格模式之间也有一些区别。
在绝大多数情况下,函数的调用方式决定了this
的值。this
不能在执行期间被赋值,在每次函数被调用时this
的值也可能会不同。ES5引入了bind
方法来设置函数的this
值,而不用考虑函数如何被调用的。
先来做几道面试题:
第一道:
var name = "caibaojian.com";
var person = {
name: "kang",
pro: {
name: "Michael",
getName: function() {
return this.name;
}
}
};
console.log(person.pro.getName()); // Michael
var pepole = person.pro.getName;
console.log(pepole()); // caibaojian.com
第二道:
var name = "caibaojian.com";
var person = {
name: "kang",
pro: {
name: "Michael",
getName: function() {
console.log(this);
return this.name;
}
}
};
console.log(person.pro.getName()); // Object { name: "...", getName: () }, Michael
var pepole = person.pro.getName;
console.log(pepole()); // Window, caibaojian.com
第三道:
'use strict';
var name = "caibaojian.com";
var person = {
name: "kang",
pro: {
name: "Michael",
getName: function() {
console.log(this);
return this.name;
}
}
};
console.log(person.pro.getName()); // Object { name: "...", getName: () }, Michael
var pepole = person.pro.getName;
console.log(pepole()); // undefined
第四道:
var name = "caibaojian.com",
person = {
name : "kang",
getName : function(){
return function(){
return this.name;
};
}
};
console.log(person.getName()()); // caibaojian.com
下面一起看看关于this
的用法:
一、全局上下文
在全局运行上下文中(在任何函数体外部),this
指代全局对象,无论是否在严格模式下。在浏览器中,全局对象为window
对象。
console.log(this.document === document); // true
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
二、函数上下文
在函数内部,this
的值取决于函数是如何调用的。
1、直接调用
在非严格模式下,this
的值不会在函数执行时被设置,此时的this
的值会默认设置为全局对象。
function foo(){
return this;
}
foo() === window; // true
在严格模式下,this
将保持他进入执行环境时的值,所以下面的this
将会默认为undefined
function foo(){
"use strict"; // 严格模式
return this;
}
foo() === undefined; // true
在严格模式下,如果this
未被执行的上下文环境定义,那么它将会默认为undefined
。
2、对象方法中的this
当以对象里的方法的方式调用函数时,它们的this
是调用该函数的对象。
下面的例子中,当obj.f()
被调用时,函数内的this
将绑定到obj
对象。
var obj = {
prop: 37,
foo: function() {
return this.prop;
}
};
console.log(obj.foo()); // 37
注意,在何处或者如何定义调用函数完全不会影响到this
的行为。
在上一个例子中,我们在定义obj
的时候为其成员foo
定义了一个匿名函数。但是,我们也可以首先定义函数然后再将其附属到obj.foo
。这样做this
的取值也和上面一致:
var obj = { prop: 37 };
function independent() {
return this.prop;
}
obj.foo = independent;
console.log(obj.foo()); // 37
这说明this
的值只与函数foo
作为obj
的成员被调用有关系。
类似的,this
的绑定只受最靠近的成员引用的影响。
在下面的这个例子中,我们把一个方法g
当作对象obj.b
的函数调用。在这次执行期间,函数中的this
将指向obj.b
。事实上,这与对象本身的成员没有多大关系,最靠近的引用才是最重要的。
var obj = { prop: 37 };
function independent() {
return this.prop;
}
obj.b = {
g: independent,
prop: 42
};
console.log(obj.b.g()); // 42
3、原型链中的this
相同的概念在定义在原型链中的方法也是一致的。如果该方法存在于一个对象的原型链上,那么this
指向的是调用这个方法的对象,表现得好像是这个方法就存在于这个对象上一样。
var obj = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(obj);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
在这个例子中,对象p
没有属于它自己的f
属性,它的f
属性继承自它的原型。但是这对于最终在obj
中找到f
属性的查找过程来说没有关系;查找过程首先从p.f
的引用开始,所以函数中的this
指向p
。也就是说,因为f
是作为p
的方法调用的,所以它的this
指向了p
。这是JavaScript的原型继承中的一个有趣的特性。
4、getter
与setter
中的this
相同的概念也适用时的函数作为一个getter
或者setter
调用。作为getter
或setter
函数都会绑定this
到从设置属性或得到属性的那个对象。
function modulus(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var obj = {
re: 1,
im: -1,
get phase(){
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(obj, 'modulus', {
get: modulus,
enumerable: true,
configurable: true
});
console.log(obj.phase, obj.modulus); // -0.785 1.414
5、构造函数中的this
当一个函数被作为一个构造函数来使用(使用new
关键字),它的this
与即将被创建的新对象绑定。
注意:当构造器返回的默认值是一个this
引用的对象时,可以手动设置返回其他的对象,如果返回值不是一个对象,返回this
。
function Fn(){
this.a = 37;
}
var obj = new Fn();
console.log(obj.a); // 37
function Foo(){
this.a = 37;
return { a: 38 };
}
obj = new Foo();
console.log(obj.a); // 38
6、call
和apply
当一个函数的函数体中使用了this
关键字时,通过所有函数都从Function
对象的原型中继承的call()
方法和apply()
方法调用时,它的值可以绑定到一个指定的对象上。
function add(c, d){
return console.log(this.a + this.b + c + d);
}
var obj = {
a: 1,
b: 3
};
add.call(obj, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(obj, [10, 20]); // 1 + 3 + 10 + 20 = 34
使用call
和apply
函数的时候要注意,如果传递的this
值不是一个对象,JavaScript将会尝试使用内部 ToObject
操作将其转换为对象。因此,如果传递的值是一个原始值比如7
或foo
,那么就会使用相关构造函数将它转换为对象,所以原始值7
通过new Number(7)
被转换为对象,而字符串foo
使用new String('foo')
转化为对象,例如:
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
7、bind()
方法
ECMAScript 5引入了Function.prototype.bind
。调用fn.bind(someObject)
会创建一个与fn
具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
的第一个参数,无论这个函数是如何被调用的。
function fn(){
return this.a;
}
var g = fn.bind({ a: "azerty" });
console.log(g()); // "azerty"
var obj = {
a: 37,
foo: fn,
go: g
};
console.log(obj.foo(), obj.go()); // 37, "azerty"
8、DOM事件处理函数中的this
当函数被用作事件处理函数时,它的this
指向触发事件的元素(一些浏览器在动态添加监听器时不遵守这个约定,除非使用addEventListener
)。
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
for(var i = 0; i < elements.length; i++){
// 当元素被点击时,就会变成蓝色
elements[i].addEventListener('click', function (e) {
e.stopPropagation();
console.log(this);
console.log(e.currentTarget);
console.log(e.target);
// 上面3个值是一样的
this.style.backgroundColor = '#A5D9F3';
});
}
9、内联事件处理函数中的this
当代码被内联处理函数调用时,它的this
指向监听器所在的DOM元素:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
上面的alert
会显示button
。注意只有外层代码中的this
是这样设置的:
<button onclick="alert((function(){return this})());">
Show inner this
</button>
在这种情况下,没有设置内部函数的this
,所以它指向global/window
对象(即非严格模式下调用的函数未设置this
时指向的默认对象)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。