OpenAI见了也皱眉?JS的this关键字,十分钟带你跨过大山!

前言

OpenAI见了也皱眉!JavaScript中this关键字的指向问题。

今天,我们来学习JavaScript中的另外一座"大山"----this关键字!!

正文

thisjs最复杂的机制,一般被定义在作用域当中。然而搞清楚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。

  1. 首先,我们来分析一下这段代码,我们在全局中定义了b=2,在foo()函数体中定义b=1接着又定义了一个bar()函数和一个baz()函数
  2. baz()函数中输出this.b,在bar()函数中调用baz(),在foo()函数中调用bar(),再在全局中调用foo()
  3. 那么为什么这样还是会输出2呢?
  4. 我们拿到this,我们要记住一句话,函数没有词法作用域!,所以在baz()this会找这个函数在哪调用!
  5. 于是来到bar()同理,函数没有词法作用域,于是又来到foo(),foo()也是函数吧?
  6. 于是最后this又指向了全局
  7. 所以最后结果输出的还是全局中的b,也就是b=2

隐式绑定

当函数被一个对象所拥有,再调用时,此时this会指向该对象!

具体来说,隐式绑定发生在以下情况:

  1. 当函数作为对象的方法被调用时,this会隐式绑定到该对象上。(我们以学习这个为主)
  2. 当函数作为回调函数被调用时,this会隐式绑定到全局对象上(非严格模式下)或undefined(严格模式下)。
  3. 当函数作为构造函数被调用时,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和两个对象objobj2

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参数nullundefined,那么 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,...)
  • objstr 函数运行时的 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

我们今天的学习就到这里啦!

如果你发现文章的某个地方有小错误或者有自己的想法和建议都欢迎大家再评论区留言哦~

点个赞鼓励支持一下吧🌹🌹🌹

相关推荐
再吃一根胡萝卜5 小时前
08 · 前端:Vue3 + SSE 流式与执行链路可视化
面试
再吃一根胡萝卜5 小时前
03 · 后端:FastAPI 与分层架构
面试
mldong7 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60618 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角9 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!9 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
Interview Aid1129 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
mayaairi11 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js