浅谈this指向问题,一次搞懂

先介绍俩个关于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();//1obj2.foo();//2
obj1.foo.call(obj2);//2obj2.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);//2console.log(baz.a);//5

(注意,这里的bind()方法不是我们自己所创建的,而是使用了内置的bind方法)可以看出,原本是foo()中this显示绑定在了obj对象上。此时bar变量是对obj.foo()函数的引用,foo()函数中this指向的是obj。但是,当出现new关键字时,bar中的this指向变成了新对象baz。可见new关键字的优先级高于显示绑定,可以修改显示绑定的对象

 

那么这四种使用方式的优先级就可以很清楚的知道了

new关键字>显示绑定>隐式绑定>默认绑定

 

所以,判断this的指向,可以按照顺序来判断

  1. 是否在new中调用?是,就绑定到新创建的对象

  2. 是否是显示绑定?是,就绑定到指定的对象

  3. 是否在隐式绑定?是,就绑定到调用该函数的那个对象

  4. 以上都没有,那就是默认绑定,直接绑定到全局对象中(非严格模式)

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();//2fooBj.call(obj3);//3setTimeout(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关键字,否则会抛出一个错误

 

虽然我是自学的,但我有一颗全栈的心,哈哈

这是我的公众号,喜欢的朋友可以关注一下,每周都会有更新,篇篇都是干货

        

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值