1. this
的诞生
假设我们有一个speak
函数,通过this
的运行机制,当使用不同的方法调用它时,我们可以灵活的输出不同的name。
var me = {name: "me"};
function speak() {
console.log(this.name);
}
speak.call(me) //me
但是如果没有this
, 这时我们需要显示的传递上下文给该函数。这时必须硬性的指定上下文,代码的复杂度增加,灵活性也欠缺。
function speak(context) {
console.log(context.name);
}
2. this
的运行机制
2.1 运行原理
When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function's execution.
当函数被调用时, 函数会创建一个activation object(执行上下文), 这个对象包括了函数在哪里被调用(调用栈),函数的调用方式,传入的参数,以及this值。
因此,我们可以看到,this
值是在函数调用时赋值的,而不是在声明的时候。是动态的。
2.2 运行规则
根据this
的运作原理,我们可以看到,this
的值和调用栈(通过哪些函数的调用运行到调用当前函数的过程)以及如何被调用有关。
2.2.1 Default Binding(默认绑定)
当函数是被独立调用时,this
值在非严格模式下为全局对象, 严格模式下为undefined
.
var a = 1;
function foo() {
var a = 2;
console.log(this.a);
}
function bar() {
debuuger;
foo();
}
bar();
打开chrome devtool可以看到,在调用foo
时,函数的调用栈为bar -> foo
,调用方式是独立调用,且是在非严格模式下,此时this
值指向window
,输出1。
2.2.2 Implicit Binding(隐式绑定)
var = 1;
function foo() {
debugger;
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
obj.foo(); //2
此时,调用foo
时,函数前加上了对obj
这个对象的引用,输出obj.a
。
因此,如果有上下文对象引用了函数,隐式绑定规则会指定this
值为该引用对象。
但是我们再看看下面这种情况。要注意的是,bar
的值是对函数foo
的引用,因此此时foo
的调用并没有上下文对象的引用,因此应用的是default binding, 输出1。要注意这种赋值的情况。
var a = 1;
function foo() {
debugger;
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo;
bar(); //1
2.2.3 Explicit Binding(显式绑定)
上面两种情况,要么this
值为全局对象(非严格模式),要么通过对象方法调用,this
指向调用的对象。
那我想不通过对象调用,而是独立调用时又能指定this
值为某个对象呢?这时,call
,apply
就诞生了。它的第一个参数是this
值,帮助我们明确指定函数调用时this
的值。
var a = 1;
function foo() {
debugger;
console.log(this.a);
}
var obj = {
a: 2
}
foo.call(obj); //2
通过call, apply
,我们可以在调用时明确指定this
值。还有一种情况是,有时候我们希望this
值绑定在我们给定的对象上,而函数只需要接受一些参数。特别是在第三方库中,它会提供一种方法,接收方法需要的参数,但是不希望你意外的修改了方法的this
值,这时它可能会采用bind
这种硬性绑定的方法明确的指出this
值。
在ES5中提供了Function.prototype.bind
,它的应用场景就是帮助你predicable的绑定this
值。常用的应用场景为包裹函数、事件绑定函数、setTimeout
中绑定this
:
//包裹函数,用来接受参数
function multiple(num) {
console.log(this.pen, num);
return this.pen * num;
}
var priceMapping = {
pen: 10
}
function calTotalPrices() {
return multiple.apply(priceMapping, arguments);
}
var total = calTotalPrices(3);
console.log(total); //30
//事件绑定
var states = {
clickCount: 0
}
function clickHandler() {
this.clickCount++;
console.log(this.clickCount);
}
button.addEventListener('click', clickHandler.bind(states));
注意:当使用显示绑定时,如果第一个参数是null, undefined
,则应用默认绑定规则。为避免传入null, undefined
时错误的改变了全局值,最好创建一个空对象代替null, undefined
。
var ø = Object.create(null);
foo.call(ø);
2.2.4 new Binding(new绑定)
明白new
的运作原理:
创建一个新对象;
对象链接到[[prototype]]上;
将
this
绑定到这个新对象上;有显式的
return
,返回return
,否则返回这个新对象。
2.2.5 优先级
new > 显示绑定(call,apply,bind) > 隐式绑定(方法调用) > 默认绑定(独立函数调用)
function foo(sth) {
this.b = sth;
console.log("a:", this.a, "b:", this.b);
}
var a = "window";
var obj1 = {
a: "obj1",
foo: foo,
}
var obj2 = {
a: "obj2",
foo: foo,
}
obj1.foo("obj1"); //a: obj1 b: obj1
obj1.foo.call(obj2, "obj2"); //a: obj2 b: obj2; 显示 > 隐式
var bar = foo.bind(obj1);
new bar("new"); //new > 显示
4. 箭头函数
箭头函数中的this
并不适用于以上四种规则。因为这里的this
不是使用的传统this
机制,而是使用的词法作用域,根据外层的作用域来决定this
。应用机制不一样,该this
也不能通过显示绑定来修改。
5. 总结
下一次再看到this
的时候,我们问自己两个问题:
where to call: 函数的调用位置是?
how to call: 函数的调用方法是?应用的规则是?
-
应用规则4条(按优先级排序):
new (新创建的对象)
显式绑定 (绑定到指定对象,
call, apply, bind
:可预测的this
);隐式绑定 (调用的上下文对象,注意间接引用的错误);
默认绑定 (全局对象或
undefined
注意函数体是否为严格模式);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。