前言
Javascript
的this
用法在许多面试题上有它的身影,对于刚入门的小白来说,实在是摸不着头脑,甚至对于比较熟悉JS
的程序员也偶尔出现一些小差错。
this
我们是又爱又恨,在某些方面,它给予了我们很大程度的方便,比如使用new
,构造函数调用,新对象绑定到函数调用的this
;使用"胖箭头"将函数内部的this
绑定到函数的外部;使用对象定义或使用函数作为属性值,调用该函数时,可将内部的this
指向其本身。然而,如果不稍加注意的话,可能会得到我们意向不到的结果,至于是怎样的结果,后面将会举例到。
本章将比较全面解析this
,如果有写的不对的地方,不吝赐教。
错误的看法
指向函数本身
可能职场面试上,有人见到过这样一道类似的面试题:
如果this
指向函数本身的话,foo.count
按理应该输出5
,然而结果并不是我们想象的那样。函数运行时,内部的this
指向了全局window
,在count
为5
我们可以看出。
它的作用域
可以明确的一点是,this
在任何情况下不指向函数的作用域。作用域和对象类似,但是无法通过js
代码访问,它存在于引擎内部。
上图中,bar
函数试图通过作用域来访问foo
函数的变量a
,其结果肯定是失败的。
this是什么
排除一些误解后,this
到底是怎样的机制。
this
是运行时绑定的,并不是在编写绑定,它的上下文取决于函数调用时的各种条件。this
的绑定与函数的声明位置没有任何关系,只取决于函数的调用方式。
当函数被调用时,会创建一个活动记录(执行上下文),里面包括调用栈、调用方法、参数等信息。this就是记录的其中一个属性。
而this
如何绑定,我们可以从调用位置
来判断。
this的看法
调用位置
调用栈(执行某函数需要调用的所有函数),而调用位置为当前正在执行的函数的前一个调用中。
我们需要分析出真正的调用位置,因为它决定this
绑定。
而调用位置如何绑定this
,我们需要根据绑定规则。
绑定规则
默认绑定
最常见的默认绑定,如独立函数的调用。
调用函数时,如果没有任何修饰的函数引用进行调用,才使用默认绑定。
需要注意的是,在严格模式下,全局对象无法使用默认绑定,所以this
会绑定到undefined
。
隐式绑定
调用位置如果存在上下文对象,则this
将隐式地绑定到该对象。
另外,可能有些同学会在口头上说:函数foo
属于obj
对象。这种说法其实不太正确。
无论是直接在 obj
中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj
对象。
如果存在多层的调用关系,则只有最后一层会影响调用位置。
隐式丢失
下面几个例子,可能也在职场面试题上经常出现。
虽然foo1
是obj.foo
的引用,但实际上foo1
引用的是foo
本身,并且foo1
是不带任何修饰的函数调用,所以应用了默认绑定。
这道跟上例一样。
显式绑定
我们知道,如果单独调用某个函数不带任何修饰引用,会被应用默认绑定。如果我们想把函数的this
绑定到某对象,且不使用隐私绑定。我们可以使用call(..)
和apply(..)
、bind(..)
来显示绑定。
call
与apply
无多大差别,后者第二个参数接收的是数组,而前者第二个参数开始接收的是非对象类型。
显示绑定视乎也不能解决我们前面的隐私丢失的问题,我们可以将显式绑定换个写法,来硬绑定。
硬绑定
我们多创建了一个函数,来解决丢失的问题。由于这种写法经常用到,所以在 ES5
中提供了内置的方法 Function.prototype. bind
bind(..)
会返回一个硬编码的新函数,它会把参数设置为 this
的上下文并调用原始函数。
new 绑定
在传统面向语言中,构造函数是类中的特殊方法,当使用new
初始化类时会调用类中的构造函数。形式如:
myclass = new MyCalss(...)
而Javascript
的new
的机制与面向类语言完全不同。
首先,在Javascript
中,构造函数只是一些使用 new
操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,
它们甚至都不能说是一种特殊的函数类型,它们只是被 new
操作符调用的普通函数而已。
举例来说,思考一下 Number(..) 作为构造函数时的行为,ES5.1 中这样描述它:
Number 构造函数当 Number 在 new 表达式中被调用时,它是一个构造函数:它会初始化新创建的对象。
所以,包括内置对象函数(比如 Number(..) )在内的所有函数都可以用 new
来调用,这种函数调用被称为构造函数调用。这里有一个重要但是非常细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”
使用 new
调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1.创建(或者说构造)一个全新的对象。
2.这个新对象会被执行 [[ 原型 ]] 连接。
3.这个新对象会绑定到函数调用的 this 。
4.如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
我们现在关心的是第 1 步、第 3 步、第 4 步,所以暂时跳过第 2 步
使用 new
来调用 foo(..)
时,我们会构造一个新对象并把它绑定到 foo(..)
调用中的 this
上。 new
是最后一种可以影响函数调用时 this
绑定行为的方法,我们称之为 new 绑定。
到这里几种绑定方式也讲完了,但是还没完。当同时存在几种绑定方式时,会优先采用哪种绑定方式?
优先级
隐式绑定 和 显示绑定
默认绑定显然是最低的,这里就不举例了。我们来看看隐式绑定和显示绑定。 显然显示绑定比隐式绑定优先级高。
隐式绑定 和 new 绑定
显然new 绑定比隐式绑定优先级高。
显示绑定 和 new 绑定
显然new 绑定比显示绑定优先级高。
判断 this
现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:
函数有
new
,则绑定新的对象;函数有
call
、apply
或者bind
,则绑定指定对象;函数有上下文,则绑定该上下文对象;
如果都不是,则调用默认绑定。
凡事都有例外,在绑定的过程中,同样如此。
绑定例外
被忽视的this
使用call
、apply
时,不绑定对象,直接传入null
时常见到。比如使用apply
展开一个数组。
但是如果函数内部存在this
时,会出现一种情况,例:
我们会发现,我们无意创建了一个全局的name
变量,而this
为何绑定到全局,我们前面有说过,不了解的同学可往上翻看。
为了阻止这种情况的发送,我们可以使用Object.create(null)
来创建一个空对象,它比{}
更空,不会创建Object.prototype
间接引用
箭头函数
使用箭头函数绑定规则并不适用,箭头函数内部this
绑定的是外部。所以当使用call
、apply
等都无效。
到这,文章就结束啦,希望同学们能Get到新的知识点。
亲,分享不易额,喜欢的话一定别忘了点💖!!!
只关注不点💖的都是耍流氓,只收藏也不点💖的也一样是耍流氓。