this
在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,变得有点随意。而在ES6中又引入了Arrow Function以及Class,它们对于this指针也带来了一定的影响。
Global Context(全局上下文)
在任何函数之外的全局环境中,不管在不在strict模式中,this指针往往指向一个全局变量。
console.log(this.document === document); // true
// In web browsers, the window object is also the global object:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
Function Context(函数上下文)
简单调用
在某个函数中,this的值取决于该函数的调用者。无论是用hello("world”)
还是call这种方式,都取决于传入该函数的对象。不过,在ES5的严格或者不严格模式下,同样的调用方式会有不同的结果。
function hello(thing) {
console.log("Hello " + thing);
}
// this:
hello("world")
// desugars to:
hello.call(window, "world");
而如果是strict模式下:
// this:
hello("world")
// desugars to:
hello.call(undefined, "world");
对象方法
另一种常用的调用函数的方法就是在object中调用:
function hello(thing) {
console.log(this + " says hello " + thing);
}
person = { name: "Brendan Eich" }
person.hello = hello;
person.hello("world") // still desugars to person.hello.call(person, "world") [object Object] says hello world
hello("world") // "[object DOMWindow]world"
bind
很多时候,需要为某个函数指定一个固定的this对象,最简单的方式即是使用闭包来获取一个不变的this对象。
var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this.name + " says hello " + thing);
}
}
var boundHello = function(thing) { return person.hello.call(person, thing); }
boundHello("world");
不过,这种方式仍然存在着一定的问题,ES5为Function对象引入了一个新的bind方法来解决这个问题:
var boundHello = person.hello.bind(person);
boundHello("world") // "Brendan Eich says hello world"
这种方式在设置回调函数中的this指针的时候会起到很大的作用,特别是在React中,为了保证指针的稳定性,往往需要为内置方法设置bind。
var person = {
name: "Alex Russell",
hello: function() { console.log(this.name + " says hello world"); }
}
$("#some-div").click(person.hello.bind(person));
// when the div is clicked, "Alex Russell says hello world" is printed
apply/call
在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:
function Point(x, y){
this.x = x;
this.y = y;
this.moveTo = function(x, y){
this.x = x;
this.y = y;
}
}
var p1 = new Point(0, 0);
var p2 = {x: 0, y: 0};
p1.moveTo(1, 1);
p1.moveTo.apply(p2, [10, 10]);
array.forEach
在这样一个回调函数中,回调函数的this指针是由调用者决定的,完整的forEach声明如下:array.forEach(callback[, thisArg]),这个传入的thisArg即是回调的调用者。
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
Arrow Function
Arrow Function是ES6新增的特性,很类似于Java或者C#中的Lambda表达式。Arrow函数中的this指针在创建时就被绑定到了闭合的作用域内,不会收到new、bind、call以及apply这些方法的影响。
var o = {
traditionalFunc: function () {
// Normal function, bound as expected
console.log('traditionalFunc this === o?', this === o);
},
arrowFunc: () => {
// Arrow function, bound to scope where it's created
console.log('arrowFunc this === o?', this === o);
console.log('arrowFunc this === window?', this === window);
}
};
o.traditionalFunc();
// traditionalFunc this === o? true
o.arrowFunc();
// arrowFunc this === o? false
// arrowFunc this === window? true
上述代码中的arrowFunc隶属于o对象,但是在window的作用域中被创建,因此,最终arrow函数中的this指针的值等于window对象。ES5中的对于this的控制已然非常复杂,特别是在处理异步代码中如何传入合适的this对象也是一件麻烦事,如下文所示:
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`
asyncFunction(o, function (param) {
// We made a mistake of thinking `this` is
// the instance of `o`.
console.log('param === this?', param === this);
});
}
};
o.doSomething(); // param === this? false
为了绑定上述代码中的this指针,一般来说有三个办法:
为this指针创建一个固定的引用。
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// Define a reference to `this` outside of the callback,
// but within the callback's lexical scope
var o = {
doSomething: function () {
var self = this;
// Here we pass `o` into the async function,
// expecting it back as `param`
asyncFunction(o, function (param) {
console.log('param === this?', param === self);
});
}
};
o.doSomething(); // param === this? true
使用bind方法绑定this
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// Here we control the context of the callback using
// `bind` ensuring `this` is correct
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`
asyncFunction(o, function (param) {
console.log('param === this?', param === this);
}.bind(this));
}
};
o.doSomething(); // param === this? true
使用Arrow Function在创建时绑定this指针。
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`.
//
// Because this arrow function is created within
// the scope of `doSomething` it is bound to this
// lexical scope.
asyncFunction(o, (param) => {
console.log('param === this?', param === this);
});
}
};
o.doSomething(); // param === this? true
DOM Event handler(Dom事件)
当某个函数作为事件监听器时,它的this值往往被设置为它的调用者。
// When called as a listener, turns the related element blue
function bluify(e){
// Always true
console.log(this === e.currentTarget);
// true when currentTarget and target are the same object
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// Get a list of every element in the document
var elements = document.getElementsByTagName('*');
// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
如果是行内的事件监听者,this指针会被设置为其所在的DOM元素:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。