运行环境
JavaScript代码主要有两种运行环境,一种是常用的浏览器环境,还有一种就是node环境,本文中如无特殊说明,均指浏览器环境。
this的含义
首先,this关键字总是返回一个对象。
其次,this的指向是动态的,即this的指向是可以发生变化的,this对象是在运行时基于函数的执行环境绑定的,即只有在运行的时候才能知道this的值(bind函数除外)。
this的使用场景
this的使用场景主要有以下这五个使用场景。
- 全局对象
- 构造函数
- 对象的方法
- DOM事件
- call/apply/bind
下面,我将详细阐述这五个使用场景。
其中,call/apply/bind单独作为一个单元来讲。
1. 全局对象中的this
在全局环境中使用this,无论是直接运行还是在函数内部运行,它指向的就是顶层对象window。
this === window; // true
(function(){console.log(this === window)})(); // true
function f() {console.log(this === window)}; f(); // true
通过上例,我们可以看到在浏览器上,以上三段代码均返回true。但在node中并不完全一致。
// 以下代码请在node环境中执行
console.log(this === global); // false
(function(){console.log(this === global)})(); // true
function f() {console.log(this === global)}; f(); // true
在node环境下,只有在函数中,this 才会指向顶层对象 global。
2. 构造函数中的this
1.构造函数中的 this 指向实例对象。
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
return this.name;
}
var p = new Person('kylin');
console.log(p.name); // kylin
p.sayName(); // kylin
2.原型继承中的this
var Parent = function() {
this.get = function() {
return this;
}
}
var Son = function() {
}
Son.prototype = new Parent();
var son = new Son();
son.get(); // son{}
这个例子也说明了,this 对象和调用方法的对象有关,即使在继承中也不例外。
3. 对象方法中的this
1.直接使用对象调用对象中的方法,其方法中的 this 指向该对象。
var person = {
name: 'kylin',
getName: function() {
return this.name;
}
}
person.getName(); // kylin
2.需要注意的是,当A对象的方法被赋值给了B对象时,方法中的 this 指向就从A对象变成了B对象。
var personA = {
name: 'kylin',
getName: function() {
return this.name;
}
}
var personB = {
name: 'lover'
}
personB.getName = personA.getName;
personB.getName(); // lover
3.如果某个方法位于多层对象的内层,这时 this 也只会指向当前这一层的对象,而不会继承更上面的层的对象。
var outerObj = {
a: 'hello',
b: {
// a: 'world',
c: function() {
return this.a;
}
}
};
outerObj.b.c(); // undefined
如果将注释掉的内容恢复,就会返回 world。
4.如果将对象的方法赋值给一个变量,那么 this 指向会变成 window 对象。
var name = 'the window';
var obj = {
name: 'kylin',
getName: function() {
return this.name;
}
}
var f = obj.getName;
f(); // the window
5.方法内部定义的函数,一般而言其执行环境具有全局性,即 this 指向顶层对象window。
var name = 'the window';
var obj = {
name: 'my ojb',
getNameFunc: function() {
return function() {
console.log(this.name); // the window
}
}
};
obj.getNameFunc()();
var obj2 = {
name: 'my ojb',
getNameFunc: function() {
function f() {
console.log(this.name); // the window
console.log(this === window); // true
}
f();
}
};
obj2.getNameFunc();
实际开发中,我们确实有在方法中定义新的函数的需求,一般而言,我们会使用闭包将 this 传入内部函数。
var name = 'the window';
var obj = {
name: 'my ojb',
getNameFunc: function() {
return function f() {
console.log(this.name); // the window
console.log(this === window); // true
}
}
};
obj.getNameFunc()();
var obj2 = {
name: 'my ojb',
getNameFunc: function() {
var _this = this;
return function f() {
console.log(_this.name); // my obj
console.log(_this === window); // false
}
}
};
obj2.getNameFunc()();
6.比较特殊的,在方法中处理数组
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
};
o.f();
// undefined a1
// undefined a2
与上面的示例一样,数组处理方法中的 this 也是指向顶层对象window。
7.对象属性中的this
var obj = {
name: 'outer',
self: this,
getName: function() {
console.log(this.name); // outer
console.log(this.self === window); // true
},
inner: {
name: 'inner',
self: this,
getName: function() {
console.log(this.name); // inner
console.log(this.self === window); // true
},
}
}
obj.getName();
obj.inner.getName();
具体JavaScript为什么这么设计,我也不太明白,有知道的同学麻烦告诉我。
8.函数数组中的this
function f1() {return this};
function f2() {return this};
var arr = [f1, f2];
arr[0](); // arr[]
其实这个并不难理解,arr数组其实就是arr对象,[]运算符可以理解为对象中的 . 运算符,只不过在对象中凡是能用 . 运算符的时候都能用 [] 运算符,但是在某些特殊情况下只能用 [] 运算符。调用可以这样理解
// 仅仅作为辅助理解作用,语法上并不合适
arr.0()
很明显,会输出 arr 对象嘛。
9.下面来看几个比较奇怪的例子
var name = 'the window';
var obj = {
name: 'kylin',
getName: function() {
console.log(this.name);
}
};
obj.getName(); // kylin
(obj.getName)(); // kylin
(obj.getName = obj.getName)(); // the window
(true && obj.getName)(); // the window
(false || obj.getName)(); // the window
(1, obj.getName)(); // the window
相信实际开发中不会有人这么写,仅作为开拓视野的部分,不再详细展开。
其基本原理是一致的,最后四个代码片段,第一个小括号最后都会返回一个函数并且在全局作用域下执行。
4. DOM事件中的this
DOM事件处理程序中的 this ,一般都是指向绑定事件的Element节点。
但里面有两个特殊的事件处理程序的 this 对象指向 window 对象。
// HTML级事件处理程序
<div id="root" onclick="showMSG()"/>
function showMSG() {
console.log(this === window); // true
}
// IE事件处理程序
<div id="root"/>
var rootEle = document.getElementById('root');
rootEle.attachEvent('onclick', function () {
console.log(this === window); // true
});
有对DOM事件还不太了解的同学可以看我的这篇文章DOM事件;
绑定this
1. function.prototype.call()
1.call 方法接受多个参数,其中第一个参数为要绑定的对象,其他的参数为函数调用需要的参数。
var name = 'the window';
var obj = {
name: 'kylin',
};
var f = function() {
console.log(this.name);
}
f(); // the window
f.call(obj); // kylin
2.call 方法中的参数,如果参数为空、null、undefined,则默认传入 window 对象。
var name = 'the window';
var obj = {
name: 'kylin',
getName: function() {
console.log(this.name);
}
};
var obj2 = {
name: 'obj'
};
obj.getName.call(obj2); // obj
obj.getName.call(); // the window
obj.getName.call(null); // the window
obj.getName.call(undefined); // the window
3.如果 call 方法中的参数是原始值,那么将传入其包装类型。
var name = 'the window';
var obj = {
name: 'kylin',
getName: function() {
console.log(this);
}
};
var obj2 = {
name: 'obj'
};
obj.getName.call(obj2); // obj{}
obj.getName.call(1); // Number{}
obj.getName.call('string'); // String{}
obj.getName.call(false); // Boolean{}
4.call 方法的一个经典实用场景是调用对象的原生方法。
var str = 'my str';
console.log(str.toString()); // my str
Object.prototype.toString.call(str); // [object String]
2. function.prototype.apply()
apply方法和call方法类似,唯一的不同是其接受数组作为函数执行时的参数。
var name = 'the window';
var obj = {
name: 'kylin',
};
var f = function() {
console.log(this.name);
}
f(); // the window
f.apply(obj); // kylin
3. function.prototype.bind()
1.bind 函数用于将函数体内的 this 绑定到某个对象上,然后返回一个新的函数。
var obj = {
count: 0,
inc: function() {
console.log(++this.count);
}
};
var f1 = obj.inc;
f1(); // NaN
var f2 = obj.inc.bind(obj);
f2(); // 1
2.bind 函数在绑定 this 的同时,还可以绑定原函数的参数。
var add = function(x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 3
}
var newAdd = add.bind(obj, 5);
newAdd(10); // 40
3.如果 bind 函数的第一个参数为null、undefined,则默认绑定顶层对象。
var m = 5,
n = 10;
var add = function(x, y) {
console.log(x * this.m + y * this.n);
}
var newAdd1 = add.bind();
newAdd1(5, 10); // 125
var newAdd2 = add.bind(null, 5);
newAdd2(10); // 125
var newAdd3 = add.bind(undefined, 5);
newAdd3(10); // 125
4.请注意,bind函数每次都返回一个新函数。
var obj = {name: 'kylin'};
var f = function() {return this}
var f1 = f.bind(obj);
var f2 = f.bind(obj);
f1 === f2; // false
f1() === f2(); // true
5. 箭头函数
一般而言,this 指向和执行时的对象有关,而和定义时的对象无关,但是对于箭头函数而言并不是这样。
var name = 'the window';
var obj = {
name: 'kylin',
getNameFunc: function(){
return () => {
return this.name;
}
}
};
obj.getNameFunc()(); // kylin
var f = obj.getNameFunc();
f(); // kylin
var obj2 = {
name: 'kylin',
getNameFunc: function(){
return function() {
return this.name;
}
}
};
obj2.getNameFunc()(); // the window
var f2 = obj2.getNameFunc();
f2(); // the window
致谢
本文主要参考了阮一峰老师的文章this 关键字和《JavaScript高级程序设计》,谨在此向阮一峰老师和JavaScript的作者译者表示感谢。