this关键字之三板斧
1.第一板斧——this的原则
this代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。随着函数使用场合的不同,this的值会发生变化。但是有一个原则是不变的:this指向调用函数的对象。
2.第二板斧——this的四种绑定形式
- 默认绑定
function foo(){
console.log(this.a);
}
var a=2;
foo();//2
这是我们最常见的函数调用类型:独立函数调用。foo()是直接使用不带任何修饰的函数引用进行调用的,只能使用默认绑定(看完下面的三条规则,再来理解这句话),无法应用其他规则。此时this指向全局对象,如果使用严格模式,this会绑定到undefined。 - 隐式绑定
function foo(){
console.log(this.a);
}
var obj={a:2,foo:foo};
obj.foo();//2
像这种函数调用是属于函数引用有上下文对象,这种是最好理解的,因为高级语言比如C#,java就是采用这种机制。这时隐式绑定规则会把函数调用中的this绑定到上下文对象。
- 显式绑定
再讲这个绑定之前,我们应该知道JavaScript提供的绝大多数函数以及你自己创建的函数都有call()和apply()方法,至于这两个函数的差别,自己去查资料,这不是本文的重点。
这两个函数的第一个参数是一个对象,在函数调用时会将这个对象绑定到this。因为你可以直接指定this的绑定对象,称之为显式绑定;此外ES5中还提供了bind()方法,这个函数与上面两个函数的不同之处在于,bind()会返回一个新的函数,并把你指定的参数设置为this。
function foo(){
console.log(this.a);
}
var obj={a:2};
foo.call(obj);//2
- new关键字绑定
function foo(a){
this.a=a;
}
var bar=new foo(2);
console.log(bar.a);
我们首先来了解一下,我们使用new来调用函数,会执行下面的操作:
1.创建一个全新的对象。
2.这个新对象会被执行Prototype连接。
3.这个新对象会绑定到this
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个对象。
这是我们会看到this绑定到新创建的对象上了,我们称之为new绑定。
3.第三板斧——代码分析
最近《画江湖之不良人》真人版莫名其妙的火了,看完之后感觉挺不错,片中的女演员挺漂亮。期待《画江湖之灵主》也能拍成真人版。这里我先给大家剧透一些《灵主》中的一些情节——男猪脚的武功最后练到九重山,但是我们理解this关键字也有九重山,让我们开始修炼吧。
结合1和2的原则和绑定形式,我们来分析一些代码中易混淆的this指向。
(1) 一重山
var a=1;
function test(){
var a=2;
console.log(this.a); //1
}
test();
这段代码在浏览器中是比较常见,我们结合四种绑定规则的特点,我们发现这种情况适合默认绑定——this指向全局对象window,所以这里打印的是“1”。
(2)二重山
var a=1;
function test(){
console.log(this.a);
}
var obj={
a:2,
fun:test
};
obj.fun(); // 2
var fun1=obj.fun;
fun1(); // 1
不知道你看到这段代码有没有晕,没有晕的话,且听我慢慢道来。obj.fun()这种调用形式,我们查看四种规则,发现符合第二种规则——隐式绑定,this指向obj,打印的是obj中的a。
再看fun1()这种调用形式,我们发现它符合四种规则的第一种,但是fun1=obj.fun()正好又符合第二种规则隐式绑定。我们仔细发现,fun1和obj.fun都是函数test的引用,因此fun1()等价于test(),所以第一种规则——默认绑定胜出。
(3)三重山
var a=1;
function test(){
console.log(this.a); // 2
function test2(){
console.log(this.a); //1
}
test2();
}
var obj={a:2,fun:test};
obj.fun();
看完这段代码你能坚定的答出,test2中打印的是“1”吗?其实只要坚持四种规则,并且相信自己,你应该大声的说出是他,就是他,我们的小伙伴——“1”;其实这种明显符合第一种规则,但是obj.fun()给我们造成了迷惑,但是只要弄清四种绑定规则的特点,就应该毫无疑问的认为就是第一种情况。
(4)四重山
var a=2;
var obj={
a:1,
foo:function(){
setTimeout(function(){
console.log(this.a);
},1000);
}
};
obj.foo(); //2
首先,我们还是用四种规则来去适配这段代码,发现仍然是第二种规则–隐式绑定符合,但是事实是这样吗?这段代码里面有个特殊的函数setTimeout,传递了一个回调函数,这个回调函数1秒之后 会被调用。这里我们可以模拟一下setTimeout的函数实现:
function setTimeout(fn,delay){
//等待delay毫秒
fn();
}
通过观察这段代码我们就会发现,其实这种情况我们三重山中的例子相似,this指向全局变量,所以打印的结果是 2。
(5)五重山
var a=1;
var obj={
a:2,
foo:function(){
console.log(this.a);
}
};
var obj2={a:3};
obj.foo.call(); //1
obj.foo.call(obj2); //3
首先看obj.foo().call(),当无参数时,this
这里适用第三种规则–显式绑定,this指向obj2,所以打印的结果是3,而不是2。
(6)六重山
function Person(name){
this.name=__name;
}
Person.prototype.show=function(){
console.log(this.name);
}
var stu=new Person('Bob');
stu.show(); //Bob
这种适用第四种规则——new关键字绑定,这有点类似高级语言java、c#的用法,这点比较好理解。
(7)七重山——四种规则的优先级
毫无疑问,默认绑定的优先级是最低的。其次,显示绑定的优先级比隐式优先级高,我们可以找段代码试一下:
function foo(){
console.log(this.a);
}
var obj1={a:2,foo:foo};
var obj2={a:3,foo:foo};
obj1.foo(); //2
obj2.foo();//3
obj1.foo.call(obj2); //3
obj2.foo.call(obj1); //2
从最后两端代码中可以看出,显示绑定的优先级要高于隐式绑定。
然后还有new关键字来打擂台,当然先找个级别较低的——隐式绑定来比较,谁的武功比较高强,当然还是写代码来比较:
function foo(something){
this.a=something;
}
var obj1={};
obj1.foo(2);
console.log(obj1.a); //2
var bar=new obj1.foo(4);
console.log(obj1.a); //2
console.log(bar.a); //4
可以看到new的武功比较高,隐式绑定的优先较低。
那么new的武功和显式绑定比较呢,来,进入下一回合:
function foo(something){
this.a=something;
}
var obj1={};
var bar=foo.bind(obj1);
bar(2);
console.log(bar.a); //2
var baz=new bar(3);
console.log(obj1.a); //2
console.log(baz.a); //3
从上面的代码中可以看到,new确实修改了this的绑定,new关键字绑定又胜一局。显示绑定落败,优先级比new较低。接下来我们来看两个变态等级:
(8)八重山——eval函数
var a=3;
function foo(){
eval(“console.log(this.a)”);
}
var obj={a:2,foo:foo);
obj.foo(); //2
var test=obj.foo;
test(); //3
其实我们套用四种规则来判定this的指向也行,this绑定到当前作用域的对象上。
(9)九重山——箭头函数
箭头函数不使用this的四种绑定规则,而是根据外层作用域来决定this。
function foo(){
return (a)=>{
console.log(this.a);
}
}
var obj1={a:2};
var obj2={a:3};
var bar =foo.call(obj1);
bar.call(obj2); // 2
foo()内部创建的箭头函数会捕获调用时foo()的this,由于this绑定到obj1,bar的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行)
说到这里,this的关键字算是告一段落。