前言
OpenAI见了也皱眉!JavaScript中this关键字的指向问题。
今天,我们来学习JavaScript中的另外一座"大山"----this关键字!!
正文
this
是js
最复杂的机制,一般被定义在作用域当中。然而搞清楚this关键字的指向是我们必须要学习内容。
今天我们的学习就从一个小案例开始!
js
console.log(this)
在全局作用域下,我们直接输出this
会出现什么情况呢?
js
输出:{}
我们会发现它指向的是一个空对象!那么this
关键字到底如何指向呢?
不过,我在学习之前,先得搞懂两个东西
在JavaScript中,函数的作用域是它能够访问的变量和其他资源的集合。这个作用域决定了函数内部声明的变量和函数的可见性。当你在函数内部声明一个变量时,这个变量只在该函数的作用域内可见。
词法作用域(或词法环境)是另外一回事。在JavaScript中,每个函数都有一个词法环境,这是函数在创建时获得的环境。这个环境包括了该函数能够访问的所有变量和其他资源。当你在函数内部声明一个变量时,这个变量就被添加到该函数的词法环境中。
this
关键字的作用
在JavaScript中,this
是一个关键字,它会引用当前执行上下文中的对象。this
的值取决于函数是如何被调用的,它可以是全局对象(在浏览器中通常是window
)、某个特定的对象或者undefined
。
this
的出现是为了简化代码!
我们要学习this
关键字是如何指向的,我们就得搞明白,this
关键字的绑定规则!
默认绑定
首先,我们来思考一句话是否正确:this
关键字一定会指向某个作用域!这句话是对的吗??
这句话其实不能说错,但不全对!
this
关键字在默认绑定下一定会指向某个作用域!这样说才是对的。
接下来,我们就来学习默认绑定!!
首先,我们来看案例:
js
var a = 1
function foo(){
console.log(this.a)
}
foo()
这一段代码,我们拿到浏览器中运行!这是因为浏览器中会有一个全局对象window
,这样更加方便我们理解this
的指向原理!
我们看看输出:
我们可以看到,在上述案例当中,我们的this
关键字会默认指向全局的对象!
那么,在默认绑定下,到底是什么执行原理呢?在默认绑定下:函数在哪个词法作用域里生效,this就指向哪里。
函数是是没有词法作用域的,所以在我们的上述案例中,因为函数foo
本身没有词法作用域,所以我们的this
关键字就默认指向了全局
到这里,其实还是有点懵的。
那我们再来看看别的案例:
js
function foo(){
var a =2
this.bar()
bar()//这样会输出undefined
}
function bar(){
console.log(this.a)//this无法代表foo,不能用this来引用一个词法作用域内部的东西
}
foo()
我们来看看输出结果:
我们会发现这样会输出两个undefined
,这是为什么呢?我们已经说过,在默认绑定下,this
关键字会自动地指向全局,而全局当中,是访问不到foo
中的a
,也就是在全局中,找不到a
所以我们的的this.a
会输出undefined
我们要知道this
写在哪就谁的,但是不代表this
就是指向你的,也就是说this
写在函数体内,但是不代表this
就是指向函数体的!
我们再来看看这个案例:
js
//词法环境不是函数自带,不要和调用栈里面的弄混了,词法环境也就是词法作用域
//函数只有作用域
var b =2
function foo(){
// [[scope]]是以整个属性为一个key值,没有权限访问
var b = 1
function bar(){
baz()
}
function baz()
{
console.log(this.b)
}
bar()//函数没有词法作用域,所以bar()会一直往外面去找
}
foo()//在浏览器中调用
我们先来看看输出结果:
在这里,我们就会输出2。
- 首先,我们来分析一下这段代码,我们在全局中定义了
b=2
,在foo()
函数体中定义b=1
接着又定义了一个bar()
函数和一个baz()
函数 - 在
baz()
函数中输出this.b
,在bar()
函数中调用baz()
,在foo()
函数中调用bar()
,再在全局中调用foo()
- 那么为什么这样还是会输出
2
呢? - 我们拿到
this
,我们要记住一句话,函数没有词法作用域!,所以在baz()
中this
会找这个函数在哪调用! - 于是来到
bar()
同理,函数没有词法作用域,于是又来到foo()
,foo()
也是函数吧? - 于是最后
this
又指向了全局 - 所以最后结果输出的还是全局中的
b
,也就是b=2
隐式绑定
当函数被一个对象所拥有,再调用时,此时this会指向该对象!
具体来说,隐式绑定发生在以下情况:
- 当函数作为对象的方法被调用时,this会隐式绑定到该对象上。(我们以学习这个为主)
- 当函数作为回调函数被调用时,this会隐式绑定到全局对象上(非严格模式下)或undefined(严格模式下)。
- 当函数作为构造函数被调用时,this会隐式绑定到一个新对象上。
我们直接来看案例:
js
function foo()
{
console.log(this.a)
}
var obj = {
a:2,
foo:foo//没有调用,这里是引用
}
//这样写不走默认规则 ,这样是
obj.foo()
js
输出:2
在我们这次的案例当中,this
居然指定了到了对象当中的a
,这是为什么呢?
当我们遇见在一个对象中,引用 一个函数的时候!注意注意!!!是引用不是调用 !此时this
的绑定规则要发生变化了。
此时就不再是默认绑定了,此时行使的是隐式绑定 ,当我们用对象调用引用函数的key
时,此时函数中的this
会指向引用这个函数的对象
我们还可以再看一个案例:
js
function foo()
{
console.log(this.a)
}
var obj = {
a:2,
foo:foo()//这里是调用
}
obj.foo
js
输出:undefined
为什么这个案例里面输出的是undefined
因为,在这个对象中,对函数不再是引用 ,而是调用!所以这个时候就不满足隐式绑定的调用了!!
所以这里还是默认绑定
隐式丢失
当函数多个对象链式调用时,this指向引用该函数的对象
js
function foo()
{
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
var obj2 = {
a:3,
obj:obj
}
obj2.obj.foo()//这里是输出2
js
输出:2
我们来分析这一段代码,我们定义了一个函数foo
和两个对象obj
和obj2
在obj2
引用obj
,在对象obj
引用了函数foo
隐式绑定是为什么?函数的this
会指向引用这个函数的对象!!所以此时函数中的this
还是指向obj
对象!
自然,结果输出的就是2
显式绑定
call,apply,bind(考题)调用!
其中,call就是借助隐式绑定的原理!
接下来,我们就一一介绍:
call方法
JavaScript中的call()
方法是一种可以在任意对象上调用一个函数,并指定这个函数的this
值的方法。它的第一个参数是this
的值,后续的参数将传递给调用的函数。
call就是强行把this掰弯,把this指向哪里去,显式绑定
call也是个函数
我们来上案例:
js
function foo(n,m)
{
console.log(this.a,n,m)
}
var obj = {
a:2
}
foo()
// call就是强行把this掰弯,把this指向哪里去,显式绑定
// call也是个函数
foo.call(obj,100,99)//括号运行的call
我们再来看看输出结果!
js
输出:
undefined undefined undefined
2 100 99
由这个案例,我们可以很显著的看出call
方法的与直接调用的区别,他能够直接把this
掰弯,让它指向我们想让它取的地方。
apply方法
apply()
是 JavaScript 中的一个内置方法,它允许你在特定的对象上调用一个函数,同时将一个数组(或类数组对象)解析为参数。
语法
js
func.apply(this参数, [数组])
其中:
this参数
是func
函数运行时的this
值。如果this参数
是null
或undefined
,那么this
会被全局对象替代。如果this参数
不是对象,那么会被转化成对象。[数组]
是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func
函数。
我们来看案例:
js
function foo(n,m)
{
console.log(this.a,n,m)
}
var obj = {
a:2
}
foo()
//foo是由apply调用,接收参数时要用数组
foo.apply(obj,[100,200])
js
输出:
undefined undefined undefined
2 100 200
从我们上述案例可以看出来,apply
方法也可以强行把this
指向我们目标的对象,在apply
方法中有两个参数,一个是this
要指向的目标,第二个是数组是要传给调用apply
函数的参数!
bind方法
bind()
是JavaScript中的一个内置方法,它创建一个新的函数,该函数在调用时将设置为在特定的this
值下执行,以及(可选的)预先提供的参数作为新函数的参数。
语法:
js
str.bind(obj,参数1,参数2,...)
obj
是str
函数运行时的this
值。在bind()
创建的新函数中,obj
就是this
的值。当新函数被调用时,obj
会被传递给str
函数作为其this
值。参数1, 参数2, ...
是要传递给str
函数的参数。这些参数在创建新函数时被固定下来,不会改变。当新函数被调用时,它自动将参数传递给str
函数。
因为bind
方法会返回一个新函数,所以我们要用一个变量来接收这个函数。
看案例
js
function foo(n,m)
{
console.log(this.a,n,m)
}
var obj = {
a:2
}
foo()
var bar = foo.bind(obj,100,200)//结果会返回一个函数
bar()
js
输出:
undefined undefined undefined
2 100 200
我们也可以看出来,相比较一般的函数调用,bind
方法,也是通过设置参数来设置我们函数this
的指向,同时会返回一个新的函数!
其中,第一个参数是我们设置的this
的指向目标。
后面接着的参数是给调用bind
的函数设置的实参。
当然bind
函数的参数有多种方式,我们再来一个案例看看
输出
function foo(n,m)
{
console.log(this.a,n,m)
}
var obj = {
a:2
}
foo()
var bar = foo.bind(obj,100)//结果会返回一个函数题
bar(200)
js
输出:
undefined undefined undefined
2 100 200
我们可以直接说,这个参数传递原理是这样的:就近原则,先从bind找,没有了再找bar
new 绑定
new绑定一般是构造函数中使用,也就是说,this
指向的就是构造函数的实例化对象!
我们直接上案例:
js
var a = 1
function Obj(a){
this.a =a
console.log(this.a)
}
var obj = new Obj(10)
obj
js
输出:10
在上述案例当中:我们的this
指向的就是构造函数的实例化对象!!!
我们再介绍一个特殊的:
箭头函数(ES6新增加的函数书写方式)
箭头函数没有this
这个概念,写在箭头函数中的this也是它外层函数的this
我们直接来看案例,对它进行学习吧!
js
var Foo = ()=>{
}
console.log(new Foo())//这里会报错,箭头函数不承认this,new使用的是构造函数,构造函数一定有this,箭头函数不能被当作this
kotlin
输出:
TypeError: Foo is not a constructor
这里会直接TypeError,也就是因为箭头函数不承认this
所以,箭头函数也无法作为构造函数!
再看这个案例:
js
var a = 5
var foo = ()=>{
console.log(this.a)
}
foo()
输出结果为:
也就是说,箭头函数不承认this
的存在,所以箭头函数体内的this
会变为这个函数的外层的函数的this
总结
kotlin
函数天生就有作用域,且作用域是一个可见的属性[[scope]]
但是函数没有词法作用域
默认绑定就是直接拿着函数名调用
this
this不能引用一个词法作用域中的内容
this的绑定规则
1. 默认绑定----函数在哪个词法作用域里生效,this就指向哪里
(this一定会指向某个作用域在默认绑定下才是对的)
2. 隐式绑定----当函数被一个对象所拥有,再调用时,此时this会指向该对象!
3. 隐式丢失----当函数多个对象链式调用时,this指向引用该函数的对象
4. 显式绑定----call,apply,bind(考题)call就是借助隐式绑定的原理
5. new 绑定
箭头函数(ES6新增加的函数书写方式)
箭头函数没有this这个概念,写在箭头函数中的this也是它外层函数的thi
我们今天的学习就到这里啦!
如果你发现文章的某个地方有小错误或者有自己的想法和建议都欢迎大家再评论区留言哦~
点个赞鼓励支持一下吧🌹🌹🌹