在 JavaScripts
的世界中,有不少神奇的 "魔法" ,像使人琢磨不透的原型链,也有隐晦的闭包。这篇是关于(《你不知道的JavaScript》上卷中this
)的学习笔记,经过总结和反思让咱们真正掌握复杂而又神奇的机制 —— this
。javascript
this
被定义在全部函数的做用域中,对于传统的高级语言,它们有各自的定义,而在 JavaScirpts
中又该如何准确的判断出这个 this
到底指向谁或者说跟谁绑定,这彷佛是咱们这次讨论的重点。可是,this
之因此这么让人迷惑大体出于 ——" 动态做用域",咱们先来看一段代码。java
var a = 2; function bar() { console.log(a); } function foo() { var a = 3; bar(); } foo(); // 2
首先,经过输出的结果来看 foo
输出的并非 2 而不是 3。有人可能会这么想:当执行 bar()
因为找不到 a 变量的定义时便经过调用栈顺着做用域链在 foo
方法中找, 这时候发现定义了 a = 3
所以这时候便会输出 3。若是存在 "动态做用域" 就可以很好的解释这个误觉得输出为 3 的缘由。可是,结果不会骗人,骗人的是这种嵌套的写法。闭包
JavaScripts
不存在这种 "动态做用域" 机制,它只有词法做用域,词法做用域让 bar
在定义的时候,经过做用域的提高机制引用到了全局(window)对象上定义的变量 a = 2
。所以,当调用 bar
的时候,即使当前处于函数 bar
中,此时的做用域是全局对象,跟代码中的嵌套无关。app
::: tip
词法做用域是一套解释引擎如何查找变量以及在什么地方找到该变量的规则。词法做用域在书写代码的时候或者定义变量或定义函数的时候就肯定了;不管函数在哪里被调用,也不论它如何调用,它的词法做用域都只由被声明时所处的位置所决定。
:::wordpress
可是话又说回来,怎么让它输出 3 呢? 咱们经过上述分析以后,获得的结论是因为词法做用域的机制,使得变量a
处于全局做用域下。所以,若是咱们改变 bar
的做用域,让它处于 foo
中就好了。来看一下以下代码:函数
var a = 2; function foo() { var a = 3; function bar() { console.log(a); } bar(); } foo(); // 3
没错,利用 闭包
机制来访问 foo
做用域。可是又有人会感到疑惑,谁是闭包?或者这不就是利用了词法做用域提高的机制将 bar
所处的做用域提高到了 foo
中了么。其实,拿闭包或者利用做用域的查找规则来解释这段代码都不为过,利用做用域的查找规则来查找 a 的引用也是闭包的一部分,虽然闭包不是咱们这次讲解的重点。学习
咱们换一种更为通俗的写法:测试
var a = 2; function foo() { var a = 3; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // 3
咱们先来看下什么是闭包?this
当函数能够记住并访问所在的词法做用域,即使函数是在它当前的词法做用域以外被执行,这时候就会产生闭包。
prototype
foo
函数就是一个包装函数,它的返回值是一个内部函数(也就是bar),而后将内部函数的引用赋值给baz
,同时内部函数持有外层函数做用域中的变量(a)的引用 ,这个时候bar便持有能够访问覆盖整个 foo
函数内部做用域的引用,这个引用就是闭包。因此 baz
在被调用的时候,其实是执行 foo
上下文环境的 bar
,这时候输出的变量天然是当前做用域下的 a = 3
。
::: tip
这里咱们提到持有该做用域的 引用
,既然提到引用必然跟对象有关联。实际上,JavaScirpts
的引擎内部有它自已的一套规则,做用域跟对象相似,可见的操做符都是它的属性,只不过该做用域 "对象" 只定义在引擎内部。
:::
上面为了帮助咱们理解词法做用域引出了闭包的概念。固然,具体关于闭包的介绍不是咱们讨论的重点。另外说关于 this
还有一个不得不说的就是 () =>
箭头函数 。
固然,箭头函数的引入不仅仅是为了简写 function
而引入的,更为有意义的是它可以 "继承" 外层函数的this
绑定,让 this
在某些场合变得更加 "单纯" 一些,咱们来看几个简单的例子:
var name = "hello~~"; var obj = { name: "kkxiao", show: function() { console.log(this.name); } } // 第一种调用方式 obj.show(); // kkxiao // 第二种调用方式 setTimeout(obj.show, 200); // hello~~ var obj = { name: "kkxiao", show: () => { console.log(this.name) } } // 改写后第三种调用方式 setTimeout(obj.show, 200); // hello~~ var obj = { name: "kkxiao", show: function() { setTimeout(function() { console.log(this.name); }, 200) } } // 改写后第四种调用方式 obj.show(); // hello~~ var obj = { name: "kkxiao", show: function() { setTimeout(() => { console.log(this.name); }, 200) } } // 改写后第五种调用方式 obj.show(); // kkxiao var obj = { name: "kkxiao", show: function() { setTimeout(function() { console.log(this.name); }.bind(this), 200) } } // 第六种调用方式 obj.show(); // kkxiao
这里有个很容易让人疑惑,稍不留神可能就会出现错误(第二种和第四种)。这里遇到的问题能够详见隐式绑定,但这个例子咱们想要说明的是 () =>
箭头函数能够放弃普通 this
的绑定规则,而且能够继承它外层的 this
绑定。
::: tip
这也不是意味着箭头函数能胜任各类状况,因为它是匿名的,因此在一些场景下它并不比具名函数更有使用的价值。具体来说,具名函数拥有以下的优势:
debug
模式下,因为没有合适的名称,调试起来可能不那么方便addEventListener
),须要解绑注册函数的时候具名函数就很重要了因此合理的使用它,让它发挥出最大的用途。
:::
::: warning
箭头函数虽然能够继承父级做用域,可是它一旦被绑定后就没法更改,稍后咱们会讲到。
:::
以上这些彷佛都没法解释 this
的机制,咱们也没弄懂它到底如何工做。不过不要着急,前面只是一些铺垫,理解 this
首先要理解词法做用域。
若是不理解词法做用域,咱们可能会对 this
产生错误的理解:
1、错觉得 this
指向自身:
function timer() { this.count++; } timer.count = 0; for(let i = 0; i < 10; i++) { if (i % 2 === 0) { timer(); } } console.log(timer.count); // 0
这彷佛并不像 this
字面量那样指向 timer
函数自身,但为何 count
会是 0,或着说 this.count++
没被执行呢?
前面咱们有讲过,执行 timer
的时候会检查当前词法做用域中是否存在 count
变量,没有的话会发生做用域提高,也就是说会检查全局做用域中是否存在 count
。然而依旧不存在,因此执行完 timer
以后会在全局对象window
下建立 count
属性并自增,最后的到 NaN
。
既然是执行函数的时候当前上下文属于全局对象 window
,手动让其引用自身:
function timer() { timer.count++; } timer.count = 0; for(let i = 0; i < 10; i++) { if (i % 2 === 0) { timer(); } } console.log(timer.count); // 5
咱们手动将其引用自身的属性 count
,这也验证了刚刚说起的具名函数的优势(能够引用自身)。这样在调用函数的时候即使当前的调用位置是 window
对象,也不影响函数自身建立的属性。或者,咱们使用 call
来改变当前上下文对象:
function timer() { this.count++; } timer.count = 0; for(let i = 0; i < 10; i++) { if (i % 2 === 0) { timer.call(timer); } } console.log(timer.count);
2、错觉得 this
指向函数的词法做用域:
function showName() { var name = "abc"; this.say() } function say() { console.log(name) } showName(); // undefined
这里咱们稍后会讲 this
的具绑定规则,首先明确调用 showName
的时候 this
使用默认绑定,此时的 this
指向 window
,不要错觉得在 this
指向 showName
的词法做用域,进而会觉得在 say
中输出 abc
,say
函数的上下文对象依然是 window
。
实际上函数在被调用的时候,会建立上下文对象(context),这个 context
对象里面记录着函数的调用栈(哪里调用的)、调用方式、入参信息以及 this
绑定的对象。
所以this
既不指向函数自身,也不指向函数的此法做用域,而是经过调用位置的上下文对象来判断 this
的指向 。
刚刚咱们提到,this
的绑定是在函数执行时才确认的,而执行时会建立 context
,而 context
中的 this
则是根据当前执行上下文的词法做用域来确认的。因此,找到函数的 调用位置
就显得很重要。
即使有如上的分析,可是有的时候函数的调用位置会迷惑咱们。接下来咱们就来具体分析 this
在绑定过程当中的规则,主要有以下四点。
咱们首先来介绍最多见的函数调用:独立函数调用。这也是四类 this
绑定规则的默认规则:
function intro() { console.log(this.name); } var name = "kkxiao"; intro(); // kkxiao
你们能够看到 intro
被调用时是不带任何修饰的函数引用进行调用的 ,咱们都知道当前的调用位置是在全局做用域中,进而直接输出全局对象中的 name
属性,相似与这样的独立函数调用即是应用了默认绑定规则。或者咱们能够理解为也是使用了的修饰的函数引用调用的,只不过是经过 window.intro()
调用罢了。所以 this
绑定到了全局对象当中。
在严格模式下会报异常错误 TypeError
,而在普通模式下正常。
function intro() { "use strict"; console.log(this.name); } var name = "kkxiao"; intro(); // TypeError: Cannot read property 'name' of undefined
这里有个小细节须要另外关注:
function intro() { console.log(this.name); } var name = "kkxiao"; (function() { "use strict"; foo(); // kkxiao })()
::: warning
对于默认绑定来讲,决定 this
绑定对象的并非调用位置是否处于严格模式,而是函数体是否处于严格模式。正如上述代码输出的结果:若是函数体处于严格模式下,this
会被绑定到 undefined
;不然,this
会绑定到全局对象。
:::
应用该规则的函数调用位置一般存在 上下文
对象,可是这里面会有陷阱:
var obj = { name: "kkxiao", say: showName } function showName() { console.log(this.name) } obj.say() // kkxiao // 或者 var obj = { name: "kkxiao", say: function() { console.log(this.name) } } obj.say() // kkxiao
咱们观察它的调用方式:obj.say()
;调用的时候 obj
对象包裹着 say
方法,或者说是上下文环境的 this
指向 obj
,所以这种方式的调用 this
会自动绑定到上下午对象上。
此外,经过使用 obj.say
这种方式调用,被调用函数前面带着 obj
引用;若是对象属性引用链有不止一层的话,那么只有最后一层引用会绑定到 this
上:
function showName() { console.log(this.name) } // 注意: 须要先声明 obj2,不然 obj2 会被声明为 undefined, 进而致使 TypeError var obj2 = { name: "kkxiao2", say: showName } var obj1 = { name: "kkxiao1", ref: obj2 } obj1.ref.say() // kkxiao2
::: warning
注意:这里有一个很容易致使隐式丢失的问题,那就是无论是先声明具名 function
再将该方法关联到对象属性上也好,仍是直接在对象上定义 function
也罢,该方法其实不是真正属于这个对象。致使隐式丢失 this
也基本上跟这个问题有关,那就是引用在传递后原来绑定在上下午对象可能会改变或丢失。
:::
刚刚咱们已经提到关于隐士绑定会出现很是常见的问题 —— 隐式丢失。一旦先前绑定的对象(在运行时经过上下文确认)丢失,那它极可能会绑定到全局 window
或者 undefined
(严格模式下)上。咱们经过几个例子来分析一下:
第一种:引用经过显示的赋值给某一变量
function showName() { console.log(this.name) } var obj = { name: "kkxiao", say: showName } var toSay = obj.say; var name = "hello~~"; toSay(); // hello~~
致使这个缘由是将 obj.say
引用赋值给 toSay
,但 obj.say
引用的是 showName
,因此最后经过 soSay()
调用至关于全局做用域下调用 window.toSay()
,只不过这里的上下文环境是 window
或者说使用了默认绑定规则。
第二种:使用回调函数
function showName() { console.log(this.name) } function toSay(fn) { // 这里 this 指向 window fn(); // <-- 调用位置 } var obj = { name: "kkxiao", say: showName } // 或者函数直接声明在对象上 // var obj = { // name: "kkxiao", // say: function() { // console.log(this.name) // } //} var name = "hello~~" toSay(obj.say); // hello~~
首先,fn
是经过 toSay
方法的参数进行隐式传递,前面咱们在讲默认绑定的时候,函数经过不带任何修饰的函数引用进行调用或者说经过独立函数调用的时候,this
默认绑定全局对象(window)。因此,showName
方法的上下文对象是 window
。可是咱们看到为何 toSay
方法的上下文对象也是指向 window
? 同理,调用 toSay
方法的时候也是独立函数调用呀。
或许有的小伙伴还有疑问:那若是强制改变toSay
上下午环境对象会怎么样?咱们知道 call
、apply
、bind
能够改变上下午对象指向,这个其实属于另一种 this
绑定规则 —— 显示绑定,稍后咱们会讲到。可是,为了说明如今遇到的问题,咱们先来使用 call
测试一下:
function showName() { console.log(this.name) } function toSay(fn) { // 此时 this 指向 obj fn(); // <-- 调用位置 } var obj = { name: "kkxiao", say: showName } var name = "hello~~"; toSay.call(obj, obj.say); // hello~~ //或者 toSay.bind(obj, obj.say)(); // hello~~
是否是以为会输出kkxiao
? 咱们来分析一下: 使用 call
后如今的 toSay
的上下文对象变成了 obj
,可是输出结果依旧没有变化。这个缘由以前咱们已经提到过 this
的指向既不指向函数自身也不指向函数的词法做用域(函数toSay
的词法做用域是window
),经过 call
得知此时的上下文虽然指向 obj
,可是真正执行 fn
的时候是不带任何修饰函数的引用调用的(独立函数调用)。因此,这时的 this
绑定依然是使用默认规则即fn
的this
指向 window
。
咱们来看一下使用 call
或者 bind
后怎么才能让它输出咱们想要的结果:
function toSay(fn) { // 此时 this 指向 obj this.say(); // <-- 调用位置 }
其实只须要输出固然上下文对象的 say
方法便可,由于上下文对象已经改变。
除此以外,对于内置函数的 callback
调用也是如此,像 setTimeout
、setInterval
等:
function showName() { console.log(this.name) } var obj = { name: "kkxiao", say: showName } var name = "hello~~"; setTimeout(obj.say, 200); // hello~~
想这些经过回调函数调用的例子,很容易出现 this
隐式丢失的问题。setTimeout
定时器跟咱们写的 toSay
方法里执行 fn
是同样的,最后都是应用了默认绑定规则。
第三这种:间接引用
function showName() { console.log(this.name) } var name = 'kkxiao'; var obj = { name: "hello", say: showName } var obj1 = { name: "world" } obj.say(); // hello 隐式绑定规则 (obj1.say = obj.say)(); // kkxiao
针对于 obj.say
你们应该都很清楚这是应用了隐式绑定规则,可是(obj1.say = obj.say)()
这种方式调用为何会是输出全局做用域下的变量呢?
你们仔细想想,obj1.say = obj.say
它们都引用了谁? 其实,它们都是引用了全局做用欲下的 showName
方法。但致使输出这一结果的或者说让人产生疑惑的地方在于 obj1.say
,觉得采用了隐式绑定规则,其实否则,咱们稍微留下神就会发现,它实际上是经过 showName()
独立函数调用的。既然是独立函数调用那就是采用了默认绑定规则,普通模式下 this
指向 window
, 严格模式下 this
绑定为 undefined
。
刚开始是否是以为很疑惑? 与咱们分析的过程相比,其实结果自己并不那么重要了,重要的是咱们经过这些例子来搞懂了 this
在隐士绑定的规则。
再次回到咱们讨论的话题,既然隐式绑定容易形成 this
丢失,那该如何作能固定住咱们指望的 this
呢?下面咱们接着介绍显示绑定。
咱们再将隐式绑定的时候提到过,那就是经过call
、apply
、bind
。这三种均可以显示的改变上下文对象,可是 call
和 apply
的区别就在于参数上,而 bind
会返回绑定函数的的拷贝函数,同时支持柯里化。
还有一些细节咱们稍后会讲到,咱们先来看下显示绑定:
var obj = { name: 'kkxiao' } function showName() { console.log(this.name) } showName.call(obj); // kkxiao
咱们先来看一下什么是硬绑定,其实再讲隐式绑定的时候咱们提到过:
function showName() { console.log(this.name) } function toSay(fn) { fn.call(obj); // <-- 调用位置 } var obj = { name: "kkxiao", say: showName } var name = "hello~~" toSay(obj.say); // kkxiao // 或者 setTimeout(showName.bind(obj), 100); // kkxiao
你们注意到 toSay
方法里面显示的使用 call
来改变上下午对象,这样的话即使是独立函数调用也不受影响,由于上下文对象已经改变。其次 bind
跟它思路相似,都是能够手动强制更改上下文对象,只不过调用方式会有些不一样。此外,bind
的功能不限于更改上下文对象,它还能够用做函数柯里化。
须要注意一点,当使用显示绑定(call、apply)的时候若是不关心当前的上下文对象,当传入 null
或
undefined
,这时候 this
会被绑定到 window
(非严格模式下):
function foo() { console.log(this.a); } var a = 123; foo.call(null); // 123
就像这样,一旦传入 null
或 undefined
的时候须要主要是否会形成负面做用,须要谨慎。
此外须要说一下,即使强制更改上下文对象,可是有些状况 this
丢失的问题依然存在:
var obj = { name: "kkxiao" } var name = "hello" function showName() { return function() { console.log(this.name) } } var say = showName.call(obj); var say1 = showName.bind(obj)(); say(); // hello say1(); // hello
小伙伴们可能会有疑惑,这里好像是应用了闭包,可是为何却应用了默认绑定规则呢? 咱们来分析一下,若是调用 showName.call
或者 showName.bind
产生了一下闭包,那么即使是独立函数调用,也不会影响到闭包,由于 say
和 say1
若是是闭包引用,那么它关联的是覆盖整个 showName
内部整个做用域 this
天然是咱们强制更改后的对象 obj
,最后会如愿输出 kkxiao
。
事实并不是咱们想的那样,结果输出的是全局变量 hello
,说明 say
和 say1
引用的不是指向 showName
内部做用域的闭包。仔细想一下,这个问题和咱们讨论隐式绑定间接引用的例子很接近,当时咱们讨论最后确认缘由是间接引用的函数的调用方式为独立函数调用。咱们回头看一下这个例子,showName
返回的是一个 function
而后赋值给 say
变量,最后调用 say
方法不就是间接引用的例子是一个问题么;因此,抛除其它因素,单看这个例子它确实是采用了隐式绑定规则。
话又说回来,这个showName
若是建立了闭包环境,那结果就又不同了。
咱们回顾一下前面咱们讨论闭包的时候,产生闭包须要具有两前提条件:一是调用了想要建立内部做用域的包装函数;二是包装函数的返回值必须至少包括一个对内部做用域的引用。咱们再来分析一下上述的showName
方法,能够发现其实咱们少了一个很关键的因素 —— 返回值必须至少包括一个对内部做用域的引用。
咱们先来打印一下当前上下文对象都是什么:
var obj = { name: "kkxiao" } var name = "hello" function showName() { console.log(this); // {name: "kkxiao"} return function() { console.log(this.name) // this 指向 window } } var say = showName.call(obj); var say1 = showName.bind(obj)(); say(); // hello say1(); // hello
能够看到返回的函数外层做用域绑定的 this
是 {name: "kkxiao"}
,这符合预期(使用显示绑定更改上下文对象)。但如何产生闭包呢? 咱们只须要一个外层做用域的一个引用:
function showName() { console.log(this); // {name: "kkxiao"} var that = this; // 引用自身便可 return function() { console.log(that.name) // this 指向 window } }
就像这样,返回的函数中有外层做用域的一个引用,这样就会建立一个指向 showName
内部做用域的一个闭包并把它赋值给 say
和 say1
并利用利用了词法做用域的查找规则成功访问到 showName
的内部做用域。
前面介绍了三种 this
的绑定规则,最后一种即是 new
绑定。具体来说当使用相似 new myFunction()
的时候会发生什么,咱们能够参见 new
运算符,它默认执行以下操做:
**{}**
)myFunction.prototype
)this
会被绑定到该新对象上myFunction
未返回其它对象,最后的 new
操做会返回这个新建立的对象如:
function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } var kk = new Persion('kkxiao', 25, '男'); kk.name; // kkxiao kk.age; // 25 kk.sex; // 男
这也是最多见的或者说构建 "类" 对象的操做,这里的 this
绑定便称为 new
绑定。
说完了四种 this
的绑定规则,咱们在来讲说它们之间优先级。平常开发中,可能这些不起眼的操做时常会出如今你的代码中,同一种代码中可能应用了好几种规则,可是它们的优先级是须要咱们格外注意的。
由于默认绑定(window 或 undefined)的优先级毫无疑问是最低的,剩下三种的优先级咱们逐步查看。这里的例子是咱们这次学习的书中的提到的例子。
隐式绑定和显示绑定:
function foo() { console.log(this.a); } var obj1 = { a: 123, foo } var obj2 = { a: 456, foo } obj1.foo(); // 123 obj2.foo(); // 456 obj1.foo.call(obj2); // 456 obj2.foo.call(obj1); // 123
这说明隐式绑定和显示绑定同时存在的话,显示绑定的优先级更高。
隐式绑定和 new
绑定:
function foo(id) { this.id = id; } var obj1 = { foo } var obj2 = {} obj1.foo(1); console.log(obj1.id); // 1 obj1.foo.call(obj2, 2); console.log(obj2.id); // 2 var bar = new obj1.foo(3); console.log(obj1.id); // 1 console.log(bar.id); // 3
这个 demo 说明了在隐身规则和 new
绑定规则存在的状况之下,new
绑定规则的优先级更高。可是咱们也一样看到了,显示绑定和 new
绑定它俩之间的优先级谁会更高呢?
由于 call
和 apply
不能使用 new
运算符,可是 bind
方法可使用,而且 new
运算符和 bind
一块儿使用的时候 this
会忽略传入的上下午对象,而是和当前调用的 new
运算符的对象之上:
function foo(id) { this.id = id; } var obj = {}; var bar = foo.bind(obj); bar(123); console.log(obj.id); // 123 var baz = new bar(456); console.log(obj.id); // 123 console.log(baz.id); // 456
咱们看到在当使用 new
运算符调用经过 bind
返回的绑定函数的时候,它并无将 this
绑定到咱们提供的 obj
对象之上,而是将 this
绑定到了一个新对象之上。
接下来咱们来看一下MDN上面的
上面的 bind
polyfill实现 :
if (!Function.prototype.bind) (function(){ var slice = Array.prototype.slice; Function.prototype.bind = function() { var thatFunc = this, thatArg = arguments[0]; var args = slice.call(arguments, 1); if (typeof thatFunc !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - ' + 'what is trying to be bound is not callable'); } return function(){ var funcArgs = args.concat(slice.call(arguments)) return thatFunc.apply(thatArg, funcArgs); }; }; })();
可是这段 polyfill
没法使用 new
运算符,由于不管如何 this
都会强制绑定到传入的对象上(null
和 undefined
)会应用默认绑定规则。现在咱们使用的 bind
是支持 new
操做符的,下面咱们稍微改造一下:
if (!Function.prototype.bind) (function(){ var slice = Array.prototype.slice; Function.prototype.bind = function() { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - ' + 'what is trying to be bound is not callable'); } var thatFunc = this, thatArg = arguments[0], args = slice.call(arguments, 1), F = function () {}, fBind = function () { var funcArgs = args.concat(slice.call(arguments)) return thatFunc.apply( (this instanceof F ? this : thatArg), funcArgs) }; F.prototype = this.prototype; fBind.prototype = new F(); // fBind.prototype = Object.create(this.prototype) return fBind; }; })();
this
的优先级的问题:
new绑定的优先级最高,经过new绑定建立的对象的过程上文已经提到。所以,经过new绑定的对象的this
指向很容易区分。
其次即是显示绑定,涉及到的方式以 call
、apply
、bind
为主,其中 bind
又能够称做为硬绑定。经过显示绑定的对象能够更改上下午对象。
再后就是隐式绑定,隐式绑定是关于 this
指向中最让人产生疑惑的一种,因为 this
在函数调用时的位置不定,因此此时的上下午对象也会不确认。不过,就其 this
指向来说,咱们已经分析了大部分的状况 。所以,只要确认了 this
调用时候的上下午对象就能确认出此时的 this
指向。
这也是四种规则中最基础的一种,它的优先级最低。须要注意的一点是,在严格模式下,默认绑定规则中的 this
会被绑定到 undefined
,不然会绑定到全局对象(window)上。
this
指向关于箭头函数,以前咱们已经介绍了一部分。这里咱们再补充几点与 this
指向相关的内容:
function Fn() { setTimeout(() => { console.log(this.a) }, 0) } Fn.call({a: '测试箭头函数'}) // 测试箭头函数
function Fn() { return () => { console.log(this.a) } } var obj1 = {a: 'obj1.a'} var obj2 = {a: 'obj2.a'} var fn = Fn.call(obj1); fn.call(obj2); // obj1.a function Fn() { setTimeout(() => { console.log(this.a); setTimeout((() => { console.log(this.a); }).bind({a: '强制更换绑定'}), 0) }, 0) } Fn.call({a: '首次绑定'}); // 首次绑定 // 首次绑定
箭头函数没有自已的 this
、arguments
、super
或者使用 new.target
,而且不能看成构造函数进行调用。所以,它更适用于匿名的场景。
咱们经过实例讲解了 this
指向的问题,若是想要真正的掌握它还须要在平时写代码的时候仔细品味。不过,理解它的前提条件不会改变: this
是在函数调用时发生的绑定,它的指向取决于函数在哪里被调用(确认被调用位置的上下午对象)。只要明确这一点,this
指向问题就能清晰的辨析。