欢迎光临
我们一直在努力

Javascript中关于this绑定机制的解析

前言

《你不知道的JavaScript》这本书里面关于this绑定机制的部分讲的特别好,很清晰,这部分对我们js的使用也是相当关键的,并且这也是一个面试的高频考点,所以整理一篇文章分享一下这部分的内容,相信看本文的解析,你一定会有所收获的,如果喜欢的话可以点波赞/关注,支持一下。

个人博客了解一下:obkoro1.com


为什么要用this:

<code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">identify</span>() </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello,I'm "</span> + <span class="hljs-keyword">this</span>.name);
}
<span class="hljs-keyword">let</span> me = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Kyle"</span>
};
<span class="hljs-keyword">let</span> you = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Reader"</span>
};
identify.call(me); <span class="hljs-comment">// Hello,I'm Kyle</span>
identify.call(you); <span class="hljs-comment">// Hello,I'm Reader</span>
</code>

这个简单的栗子,可以在不同的对象中复用函数identify,不用针对每个对象编写一个新函数。

this解决的问题:

this提供了一种更优雅的方法来隐式’传递’一个对象的引用,因此可以将API设计得更加简洁并且易于复用

this的四种绑定规则:

默认绑定:

规则:在非严格模式下,默认绑定的this指向全局对象,严格模式下this指向undefined

<code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>() </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.a); <span class="hljs-comment">// this指向全局对象</span>
}
<span class="hljs-keyword">var</span> a = <span class="hljs-number">2</span>;
foo(); <span class="hljs-comment">// 2</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo2</span>() </span>{
<span class="hljs-meta">  "use strict"</span>; <span class="hljs-comment">// 严格模式this绑定到undefined</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.a); 
}
foo2(); <span class="hljs-comment">// TypeError:a undefined</span>
</code>

默认绑定规则如上述栗子,书中还提到了一个微妙的细节:

<code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>() </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.a); <span class="hljs-comment">// foo函数不是严格模式 默认绑定全局对象</span>
}
<span class="hljs-keyword">var</span> a = <span class="hljs-number">2</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo2</span>()</span>{
<span class="hljs-meta">  "use strict"</span>;
  foo(); <span class="hljs-comment">// 严格模式下调用其他函数,不影响默认绑定</span>
}
foo2(); <span class="hljs-comment">// 2</span>
</code>

所以:对于默认绑定来说,决定this绑定对象的是函数体是否处于严格模式,严格指向undefined,非严格指向全局对象。

通常不会在代码中混用严格模式和非严格模式,所以这种情况很罕见,知道一下就可以了,避免某些变态的面试题挖坑。

隐式绑定:

规则:函数在调用位置,是否有上下文对象,如果有,那么this就会隐式绑定到这个对象上。

<code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>() </span>{
      <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.a);
    }
    <span class="hljs-keyword">var</span> a = <span class="hljs-string">"Oops, global"</span>;
    <span class="hljs-keyword">let</span> obj2 = {
      <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>,
      <span class="hljs-attr">foo</span>: foo
    };
    <span class="hljs-keyword">let</span> obj1 = {
      <span class="hljs-attr">a</span>: <span class="hljs-number">22</span>,
      <span class="hljs-attr">obj2</span>: obj2
    };
    obj2.foo(); <span class="hljs-comment">// 2 this指向调用函数的对象</span>
    obj1.obj2.foo(); <span class="hljs-comment">// 2 this指向最后一层调用函数的对象</span>
    
    <span class="hljs-comment">// 隐式绑定丢失</span>
    <span class="hljs-keyword">let</span> bar = obj2.foo; <span class="hljs-comment">// bar只是一个函数别名 是obj2.foo的一个引用</span>
    bar(); <span class="hljs-comment">// "Oops, global" - 指向全局</span>
</code>

隐式绑定丢失:

隐式绑定丢失的问题:实际上就是函数调用时,并没有上下文对象,只是对函数的引用,所以会导致隐式绑定丢失。

同样的问题,还发生在传入回调函数中,这种情况更加常见,并且隐蔽,类似:

<code>    test<span class="hljs-comment">(obj2.foo)</span>; <span class="hljs-comment">// 传入函数的引用,调用时也是没有上下文对象。</span>
</code>

显式绑定:

就像我们上面看到的,如果单纯使用隐式绑定肯定没有办法得到期望的绑定,幸好我们还可以在某个对象上强制调用函数,从而将this绑定在这个函数上

规则:我们可以通过applycallbind将函数中的this绑定到指定对象上。

<code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>() </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.a);
}
<span class="hljs-keyword">let</span> obj = {
    <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>
};
foo.call(obj); <span class="hljs-comment">// 2</span>
</code>

传入的不是对象:

如果你传入了一个原始值(字符串,布尔类型,数字类型),来当做this的绑定对象,这个原始值转换成它的对象形式。

如果你把null或者undefined作为this的绑定对象传入call/apply/bind,这些值会在调用时被忽略,实际应用的是默认绑定规则。

new绑定:

书中提到:在js中,实际上并不存在所谓的’构造函数’,只有对于函数的’构造调用’。

new的时候会做哪些事情:

  1. 创建一个全新的对象
  2. 这个新对象会被执行 [[Prototype]] 连接。
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

规则:使用构造调用的时候,this会自动绑定在new期间创建的对象上。

<code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params">a</span>) </span>{
  <span class="hljs-keyword">this</span>.a = a; <span class="hljs-comment">// this绑定到bar上</span>
}
<span class="hljs-keyword">let</span> bar = <span class="hljs-keyword">new</span> foo(<span class="hljs-number">2</span>);
<span class="hljs-built_in">console</span>.log(bar.a); <span class="hljs-comment">// 2</span>
</code>

this四种绑定规则的优先级

如果在某个调用位置应用了多条规则,如何确定哪条规则生效?

<code>    obj.foo.call(obj2); <span class="hljs-regexp">//</span> <span class="hljs-keyword">this</span>指向obj2 显式绑定比隐式绑定优先级高。
    <span class="hljs-keyword">new</span> obj.foo(); <span class="hljs-regexp">//</span> thsi指向<span class="hljs-keyword">new</span>新创建的对象 <span class="hljs-keyword">new</span>绑定比隐式绑定优先级高。
</code>

显式绑定和隐式绑定无法直接比较(会报错),默认绑定是不应用其他规则之后的兜底绑定所以优先级最低,最后的结果是:

显式绑定 > 隐式绑定 > 默认绑定

new绑定 > 隐式绑定 > 默认绑定

箭头函数的this指向不会使用上述的四条规则:

<code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>() </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">this</span>.a);
  };
}
<span class="hljs-keyword">let</span> obj1 = {
  <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>
};
<span class="hljs-keyword">let</span> obj2 = {
  <span class="hljs-attr">a</span>: <span class="hljs-number">22</span>
};
<span class="hljs-keyword">let</span> bar = foo.call(obj1); <span class="hljs-comment">// foo this指向obj1</span>
bar.call(obj2); <span class="hljs-comment">// 输出2 这里执行箭头函数 并试图绑定this指向到obj2</span>
</code>

从上述栗子可以得出,箭头函数的this规则:

  1. 箭头函数中的this继承于它外面第一个不是箭头函数的函数的this指向
  2. 箭头函数的 this 一旦绑定了上下文,就不会被任何代码改变

结语

认真看完的话,相信你已经get到this的用法了,最后推荐一下《你不知道的JavaScript》,这本书真的很好,写的也很有趣,没看过的小伙伴抓紧入手了。

赞(6) 打赏
未经允许不得转载:前端范 » Javascript中关于this绑定机制的解析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

更专业 更方便

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏