首页>>前端>>JavaScript->this绑定的基本原则(一)

this绑定的基本原则(一)

时间:2023-11-30 本站 点击:0

this绑定的基本原则

this绑定的基本原则大致上可以分成下列四种:

预设绑定 (Default Binding)

隐含式绑定 (Implicit Binding)

显式绑定 (Explicit Binding)

「new」关键字绑定

预设绑定 (Default Binding)

宣告在全域范畴 (global scope) 的变数,与同名的全域物件 (window 或 global) 的属性是一样的意思。\ 因为预设绑定的关系,当 function 是在普通、未经修饰的情况下被呼叫,也就是当 function 被呼叫的当下如果没有值或是在 func.call(null) 或 func.call(undefined) 此类的情况下,此时里面的 this 会自动指定至全域物件。

但若是加上 "use strict" 宣告成严格模式后,原本预设将 this 绑定至全域物件的行为,会转变成undefined

隐含式绑定 (Implicit Binding)

指的是,即使 function 被宣告的地方是在 global scope 中,只要它成为某个物件的参考属性 (reference property),在那个 function 被呼叫的当下,该 function 即被那个物件所包含。

白话说就是:✅this代表的是function 执行时"所属的物件",而不是 function "本身"

测试你真的懂了吗?

第一题:

var foo = function() {  this.count++;};foo.count = 0;for( var i = 0; i < 5; i++ ) {  foo();}

foo.count会是多少?

答案是0。我知道你可能不能接受,来听我解释。

前面讲过,this代表的是「function 执行时所属的物件」对吧?

在上面范例中,foo是 function,同时也是「全域变数」。相信已经看到 DAY 20 这篇的你,一定很清楚「全域变数」的定义吧!

复习一下,「全域变数」代表的是「全域物件的属性」。所以说,foo其实就是window.foo

所以说,当 foo() 在 for 回圈里面跑得很开心的时候,this.count++始终都是对 window.count 在做递增的处理,因为这个时候的 this 实际上就是window

而 window.count 理论上一开始会是undefined,在做了五次的 ++ 之后,你会得到一个 NaN 的结果,而 foo.count 依然是个0

记住,this代表的是function 执行时所属的物件,而不是 function 本身。

第二题:

var bar = function() {  console.log( this.a );};var foo = function() {  var a = 123;  this.bar();};foo();

相信经过前一个例题后,聪明的你应该知道 foo() 的执行结果应该是 undefined 了!

在这个范例中,foo()可以透过 this.bar 取得bar(),是因为 this.bar 实际上是指向window.bar。\ 而 bar() 的 this.a 并非是 foo 中的123,而是指向window.a,所以会得到 undefined 的结果。

第三题:

function func() {  console.log( this.a );}var obj = {  a: 2,  foo: func};func();       // undefinedobj.foo();    // 2

在上面的范例中可以看到,根据「预设绑定」的原则,直接呼叫 func() 的情况下,此时的 this.a 实际上会指向window.a,所以结果是undefined

而当我们在 obj 物件中,将 foo 这个属性指到 func() 的时候,再透过 obj 来呼叫 obj.foo() 的时候,虽然实际上仍是 func() 被呼叫, 但此时的 this 就会指向至 obj 这个 owner 的物件上,于是此时的 this.a 就会是 obj.a 也就是2

第四题:

理解了隐含式绑定的原则后,继续来看看这个变化过的版本:

function func() {  console.log( this.a );}var obj = {  a: 2,  foo: func};obj.foo();  // 2var func2 = obj.foo;func2();    // ??

在这个版本中,我们宣告另一个变数 func2 指向obj.foo,那么聪明的你是否可以猜到呼叫 func2() 的结果为何呢?

答案是undefined

虽然 func2 看起来是对 obj.foo 的参考,但实际上 func2 参考的对象是window.func

决定 this 的关键不在于它属于哪个物件,而是在于 function「呼叫的时机点」,当你透过物件呼叫某个方法 (method) 的时候,此时 this 就是那个物件 (owner object)。

补充测试:巢状回圈中的 this

var obj = {  func1: function(){    console.log( this === obj );    var func2 = function(){      // 这里的 this 跟上层不同!      console.log( this === obj );    };    func2();  }};obj.func1();

在这个范例当中,会有两次的console

在 obj.func1() 里面的 console.log( this === obj ); 会印出true,原因是因为 func1 是透过 obj 来呼叫的。

但 obj.func1() 里面的 func2() 在执行时的 console.log( this === obj ); 却会印出false

这里必须说明两个重点:

JavaScript 中,用来切分变数的最小作用范围 (scope),也就是我们说的有效范围的单位,就是function

当没有特定指明 this 的情况下,预设绑定 (Default Binding)this为「全域物件」,也就是window

换言之,在 func2 里头的this,若是没有特别透过call()apply()或是 bind() 来指定 this 的话,那么这里的 this 就是window

显式绑定(Explicit Binding)

相较于前两种,显式绑定就单纯许多,简单来说就是透过.bind().call()/.apply()这类直接指定 this 的 function 都可被归类至显式绑定的类型。

.bind()

延续上个范例,我们先看bind()。在前面范例中,我们用 that 这个变数来替代this,以便取得触发 click 事件的元素。

如果用 bind() 改写的话:

el.addEventListener("click", function(event) {  console.log( this.textContent );  // 透过 .bind(this) 来强制指定该 scope 的 this  $ajax('[URL]', function(res) {    console.log(this.textContent, res);  }.bind(this));}, false);

但要注意的是,无论是使用 'use strict' 或是再加上 .bind(xxx) 都无法改变 this 的内容,也不能作为物件建构子 (constructor) 来使用。箭头函式方便归方便,若是你的 function 内会有需要用到 this 的情况时,就需要特别小心你的 this 是不是在不知不觉中换了人来当。

.call().apply()

既然讲到了强制指定 this 的方式,看完了 bind() 与「箭头函式」,接下来就不能不讲到 call() 与apply()

假设今天有个 function 长这样:

function func( ){  // do something}

那么我们可以透过 func() 来呼叫它。

当然你也可以用 .call() 或是 .apply() 来呼叫它:

func.call( );func.apply( );

你可能会觉得奇怪,看起来没什么不同对吧,还要多打几个字岂不是自找麻烦。但如果遇上了需要带参数的时候,就又显得有些不同。

基本上 .call() 或是 .apply() 都是去呼叫执行这个 function ,并将这个 function 的 context 替换成第一个参数带入的物件。换句话说,就是强制指定某个物件作为该 function 执行时的this

而 .call() 与 .apply() 的作用完全一样,差别只在传入参数的方式有所不同:

function func( arg1, arg2, ... ){  // do something}func.call( context, arg1, arg2, ... );func.apply( context, [ arg1, arg2, ... ]);

.call()传入参数的方式是由「逗点」隔开,而 .apply() 则是传入整个阵列作为参数,除此之外没有明显的差别。

bind, call, apply 的差异

bind()让这个 function 在呼叫前先绑定某个物件,使它不管怎么被呼叫都能有固定的this。 尤其常用在像是 callback function 这种类型的场景,可以想像成是先绑定好 this,然后让 function 在需要时才被呼叫的类型。

而 .call() 与 .apply() 则是使用在 context 较常变动的场景,依照呼叫时的需要带入不同的物件作为该 function 的this。在呼叫的当下就立即执行。

new」关键字绑定

在建构式下会 new 一个新物件,此时的 this 会指向新的物件。建构式在后续的章节会介绍,此部分只要了解建构式的 this 也是指像物件本身即可。

function FamilyConstructor () {  this.mom = '老妈'}var myFamily = new FamilyConstructor();console.log(myFamily.mom);

这一个 this 不会是全域且可以在生成的物件上重新定义 (所以他指向的是该生成的物件)。

var bar = function() {  console.log( this.a );};var foo = function() {  var a = 123;  this.bar();};foo();0

事件中的“this”指的是「触发事件的元素」/event.currentTarget

范例: 像这样,当事件被触发时,此时 this 就会是触发事件的元素,也就是这个范例中的label

注意:this代表的会是「触发事件的目标」元素,也就是 event.currentTarget 而不是e.target

var bar = function() {  console.log( this.a );};var foo = function() {  var a = 123;  this.bar();};foo();1

然而,要是我们在事件的 callback function 加入 ajax 的请求,那么根据前面所说的,预设绑定 (Default Binding) 会把这个 callback function 的 this 指定给global object,也就是window

有个很简单的方式可以解决这个问题,那就是透过另一个变数来对目前的 this 做参考:

将事件内的 this 先用一个叫 that 的变数储存它的参考,那么在 ajax 的 callback function 就可以透过 that 来存取到原本事件中的 this 了。

(可以想像成某个 function 在执行的时候,「暂时」把它挂在某个物件下,以便透过 this 去取得该物件的 Context。)

var bar = function() {  console.log( this.a );};var foo = function() {  var a = 123;  this.bar();};foo();2

实务上除了 ajax 的 callback function 以外,另外像是setTimeoutsetInterval这类的 function,也是常见需要特别处理 this 的场景。

结论 What's "this" in JavaScript?

综合上述范例介绍,我们可以简单总结出一个结论:

这个 function 的呼叫,是透过 new 进行的吗?如果是,那 this 就是被建构出来的物件。

这个 function 是以 .call() 或 .apply() 的方式呼叫的吗?或是 function 透过 .bind() 指定?如果是,那 this 就是被指定的物件。

这个 function 被呼叫时,是否存在于某个物件?如果是,那 this 就是那个物件。

如果没有满足以上条件,则此 function 里的 this 就一定是全域物件,在严格模式下则是undefined

好啰嗦....下章讲个精简版的,就酱

原文:https://juejin.cn/post/7094811718013960206


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/3961.html