先介绍俩个关于this你应该知道的
1. this的运行机制
this是在运行时进行绑定的,不是在编写时绑定的,它的上下文关系取决于函数调用时的各种关系。this的绑定和函数声明的位置没有关系, 只取决于函数的调用方式。就是说this指向是动态绑定的,但是总是指向调用函数的那个对象,而不管函数是声明在哪里
2. this指向自身
知道了this的运行机制,就应该知道this并不是指向函数本身
看例子:
function foo(num){
console.log(num);
this.count++;//用来记录foo被调用的次数
}
foo.count=0;//给foo函数对象添加count属性
for(var i=0;i<5;i++){
foo(i);
}
console.log(foo.count);//输出0
为什么foo.count还是会输出0呢?明明foo函数被调用了5次。这就是this的指向问题了。this并没有指向自身,而是指向了window对象,foo(i);的执行其实是window.foo(i);如果还不明白,下面会有解释
要分析出this的指向,必须理解this是运行时绑定的,然后找出调用函数的对象
现在说说this的具体使用
1.默认绑定
这是最常用的函数调用类型:独立函数调用
var a=1;
function foo(){
foo.a=2;
console.log(this.a);
}
foo();//输出1
在没有任何对象调用时,默认函数中的this指向全局对象,所以输出1(其实就是window对象省略不写而已)
2.隐式绑定
当被某个对象包含或者拥有这个函数引用时,此时this会绑定到这个对象
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
obj.foo();//输出2
不过,还要注意,当有多层对象嵌套时,this只会指向最初的包含或者拥有这个函数引用的对象
function foo(){
console.log(this.a);
}
var obj1={
a:2,
foo:foo
}
var obj2={
a:3,
obj1:obj1
}
obj2.obj1.foo();//输出2
还有一点要注意,当把这个函数引用赋值给一个变量时,就可能会出现隐式丢失现象,就是this对象会指向全局对象,而不是被隐式绑定的对象,例如最开始的那个例子
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo } var bar=obj.foo; var a=20; bar();//20
bar引用的是foo函数的本身,此时foo函数中的this并没有指向谁(因为foo函数没有被调用,this就不会进行绑定),然后bar();就相当于是默认绑定,this就会默认绑定到全局对象中,因此会输出20
还有一个例子,也是类似于这种,也应该了解一下
function foo(){
console.log(this.a);
}
function doFoo(fn){
fn();
}
var obj={
a:2,
foo:foo
}
var a=1;
doFoo(obj.foo);//输出1
参数传递也是一种隐式赋值,所以也会出现隐式丢失现象
3.显示绑定
也就是使用call()和apply()方法
使用方法都类似,第一个参数都是传入一个this的绑定对象,剩下的参数有点区别,但作用都一样
function foo(){
console.log(this.a)
}
var obj={
a:2
}
var bar=function(){
foo.call(obj);
}
var a=1;
bar();//输出2
创建bar()函数,在其内部手动调用foo.call(obj),强制把foo的this指向了obj,之后无论如何调用bar()函数,都无法改变foo的this指向(这种方式也可以叫硬绑定 ,至于为什么叫硬绑定,可能是这种绑定比较命硬,难以更改,但也不绝对)
也可以使用apply方法创建一个简单的bind()方法,利用这方法来绑定对象(这也是硬绑定)
function foo(num){
return this.a+num
}
function bind(fn,obj){
return function(){
return fn.apply(obj,arguments);
}
}
var obj={
a:2
}
var bar=bind(foo,obj);
var x=bar(3);
console.log(x);//输出5
4.new绑定
当使用new关键字时,会自动执行下面的操作
1)创建一个全新的对象
2)这个新对象会执行[Prototype]连接
3)这个新对象会绑定到函数调用的this
4)如果函数没有返回其他对象,那么new表达式中的函数调用就会自动返回这个新对象
了解了这个过程就知道new关键字也可以改变this的指向
function foo(a){
this.a=a;
}
var bar=new foo(2);
console.log(bar.a);//输出 2
03
使用的优先级
如果一个函数同时使用这几种方式来修改this的指向,那么到底谁的优先级高呢?当然,首先排除默认绑定,它是优先级最低的
那么先来比价一下隐式和显示绑定的优先级
function foo(){
console.log(this.a);
}
var obj1={
a:1,
foo:foo
}
var obj2={
a:2,
foo:foo
}
obj1.foo();//1
obj2.foo();//2
obj1.foo.call(obj2);//2
obj2.foo.call(obj1);//1
很明显,显示绑定比隐式绑定的优先级要高,毕竟输出结果是2。如果是隐式绑定优先级高,那么即使进行了显示绑定也会无效
现在比较new关键字和隐式绑定的优先级
function foo(num){
this.a=num;
}
var obj1={
foo:foo
}
obj1.foo(2);
console.log(obj1.a);//2
var bar=new obj1.foo(5);
console.log(bar.a);//5
console.log(obj1.a);//2
很明显,当有new关键字时,会将新对象bar绑定到foo()函数中的this上,取代隐式绑定的obj1对象。
接下来再比较一下显示绑定和new关键字的优先级
function foo(num){
this.a=num;
}
var obj={};
var bar=foo.bind(obj);
bar(2);
console.log(obj.a);//2
var baz=new bar(5);
console.log(obj.a);//2
console.log(baz.a);//5
(注意,这里的bind()方法不是我们自己所创建的,而是使用了内置的bind方法)可以看出,原本是foo()中this显示绑定在了obj对象上。此时bar变量是对obj.foo()函数的引用,foo()函数中this指向的是obj。但是,当出现new关键字时,bar中的this指向变成了新对象baz。可见new关键字的优先级高于显示绑定,可以修改显示绑定的对象
那么这四种使用方式的优先级就可以很清楚的知道了
new关键字>显示绑定>隐式绑定>默认绑定
所以,判断this的指向,可以按照顺序来判断
-
是否在new中调用?是,就绑定到新创建的对象
-
是否是显示绑定?是,就绑定到指定的对象
-
是否在隐式绑定?是,就绑定到调用该函数的那个对象
-
以上都没有,那就是默认绑定,直接绑定到全局对象中(非严格模式)
04
绑定例外
凡事都会有例外,不可避免
比如说,间接引用(其实,我觉得就是隐式丢失的一种,但又有点不同)
function foo(){
console.log(this.a);
}
var a=2;
var obj={a:3,foo:foo};
var p={a:4};
(p.foo=obj.foo)();//输出2
是不是对于(p.foo=obj.foo)();这样一行代码有点迷惑?到底是什么意思?那么先不去看这行代码,先理解一个简单的赋值操作
var a=b=3;
var a=(b=3);
console.log(a,b);//3 3
是不是有点理解了?b=3是有返回值的,返回值3,然后再赋值给a。所以才能输出a=3。那么(p.foo=obj.foo)();这个也是同理
function foo(){
console.log(this.a);
}
var a=2;
var obj={a:3,foo:foo};
var p={a:4};
(p.foo=obj.foo)();//输出2
//==》等价于
(foo)();//立即执行函数,没有调用对象,默认指向全局对象
//再看一个表达式
(p.foo)();//输出4
//这里是有调用对象的,就是p ,所以指向p对象
再有就是软绑定,就是给默认绑定指定一个全局对象之外的值,既可以实现和硬绑定同样的效果,但是又可以保留隐式绑定或显示绑定修改this指向的能力。不像是硬绑定,除非有new关键字,否则不能进行修改
if(!Function.prototype.softBind){
Function.prototype.softBind=function(obj){
var fn=this;
var args=Array.prototype.slice.call(arguments,1);
var bound=function(){
return fn.apply(
(!this||this===(window||global))?obj:this,
args.concat.apply(args,arguments)
);
};
bound.prototype=Object.create(fn.prototype);
return bound;
};
}
主要就是能理解这几行代码就行,其他的暂时不用理解
var fn=this;//this就是指该函数当前绑定的对象
return fn.apply(
(!this||this===(window||global))?obj:this,
args.concat.apply(args,arguments)
);
/*判断这个this是否存在或者this是否指向window/global,如果是,那就把这个
*this的指向改变成为传入的Obj对象
*/
那么接下来就是使用了,看效果
function foo(){
console.log(this.a);
}
var obj1={a:1};
var obj2={a:2};
var obj3={a:3};
var fooBj=foo.softBind(obj1);
fooBj();//1
obj2.foo=foo.softBind(obj1);
obj2.foo();//2
fooBj.call(obj3);//3
setTimeout(obj2.foo,10);//1
先给foo()软绑定一个对象obj1,然后执行,输出为1。然后再隐式绑定到obj2中,执行输出为2。再显示绑定到obj3中,输出为3。最后,再来一个默认绑定(setTimeout()中this指向全局对象),输出为1。全都符合了对软绑定的定义。即指定一个除全局对象外的对象,但保留隐式绑定和显示绑定的能力
还有一个例子比较特别,那就是箭头函数。这个箭头函数中的this对象就是定义时所在的对象,不是运行时所在的对象。怎么理解呢?就是说箭头函数没有自己的this,只能使用外层代码的this。外层代码中的this指向谁,箭头函数中this就指向谁
function foo(){
setTimeout(function(){
console.log(this.a);
},1000);//输出1
setTimeout(()=>{
console.log("====");
console.log(this.a);
},2000);//输出2
}
var a=1;
var obj={a:2}
foo.call(obj);
可以看出,在箭头函数中的this是指向obj的,而普通函数是指向全局对象的。
还要注意就是,箭头函数没有this,所以不能当成构造函数来使用,就是不额能使用new关键字,否则会抛出一个错误
虽然我是自学的,但我有一颗全栈的心,哈哈
这是我的公众号,喜欢的朋友可以关注一下,每周都会有更新,篇篇都是干货