likes
comments
collection
share

探索 JavaScript “this”指向的内在逻辑

作者站长头像
站长
· 阅读数 24

前言

本文讲解一下thisthis是一个至关重要且常常令人感到困惑的概念

在不同的情境下指向不同的对象,决定着函数内部对属性和方法的访问方式

理解“this”的指向机制,对于编写清晰、正确且可维护的 JavaScript 代码来说,具有不可忽视的重要性

  • this存在的意义

为了让对象中的函数有能力访问对象自己的属性

this可以显著的提升代码质量,减少上下文参数的传递

  • this的绑定规则
  1. 默认绑定:当一个函数直接独立调用,不带任何修饰符时,默认绑定。
    • 函数在哪个词法作用域中生效,函数中的this就指向哪里。(只要是默认绑定this一定指向全局对象window。)
  2. 隐式绑定:当函数的引用有上下文对象时(当函数被某个对象所拥有时),隐式绑定。
    • 函数中的this指向的是引用它的对象。
  3. 显示绑定:通过bind、apply、call,将函数的this指向指定的对象。call直接接受参数,apply接受参数需要用数组装起来。

接下来我们通过代码讲解一下这些绑定规则

默认绑定

我们来思考一下代码

let obj = {
    myName: 'c',
    age: 18,
    bar: function () { 
        console.log(myName);
    }
}

obj.bar();

这段代码的执行结果为:myName is not defined

探索 JavaScript “this”指向的内在逻辑

为什么是这个结果呢?明明在obj里面确实 存在一个变量myName,却访问不到

在 bar 函数内部,我们并没有使用 this.myName 来访问这个属性,而是直接使用了 myName

JavaScript 在查找变量时的搜索顺序是:

  1. 当前函数作用域
  2. 外部函数作用域(如果有的话)
  3. 全局作用域

在这个例子中,myName 并没有在 bar 函数的作用域内定义,所以 JavaScript 会继续向上查找,最终在全局作用域中查找 myName。但是在全局作用域中也没有定义 myName,所以会报 ReferenceError: myName is not defined

接下来我们继续分析代码

function foo() {
    console.log(this);
}
// this 代指某一块作用域
foo();

这段代码在浏览器执行的结果为

探索 JavaScript “this”指向的内在逻辑

当在浏览器中运行这段代码时,this 关键字的指向通常会指向全局对象,也就是 window 对象

这便是第一种绑定规则:默认绑定

当一个函数直接独立调用,不带任何修饰符时,默认绑定。

函数在哪个词法作用域中生效,函数中的this就指向哪里。(只要是默认绑定this一定指向全局对象window。)

那么这段代码在浏览器执行结果将是什么呢?


function foo() {
    console.log(this);
}

function bar() {
    foo();
}

function baz() {
    bar();
}
baz();

结果为:

探索 JavaScript “this”指向的内在逻辑

就是因为这段代码就是一个默认绑定,函数在哪个词法作用域中生效,函数中的this就指向哪里。(只要是默认绑定this一定指向全局对象window。)

这里我们说一个注意点:在全局,通过var声明的变量相当于window.xxx,在函数中,通过var声明的变量相当于函数的词法作用域。

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

探索 JavaScript “this”指向的内在逻辑

可以看到这段代码的执行结果,正是证明了这个结论

如果在node环境执行这段代码为undefined,这是由于node环境下没有Windows

接下来我们分析下一段代码:

function foo2() {
    console.log(this.a);
}
var obj = {
    a: 1,
    foo: function () {
        foo2()
    }
}
var a = 2
obj.foo();

这段代码执行结果为

探索 JavaScript “this”指向的内在逻辑

可见这里依旧是 默认绑定,因为执行this的函数依旧是一个独立调用

对比一下代码

function foo2() {
    console.log(this.a);
}
var obj = {
    a: 1,
    foo: function () {
        console.log(this.a);
    }
}
var a = 2
obj.foo();

这段代码执行结果为

探索 JavaScript “this”指向的内在逻辑

对比可知,一个是独立调用,一个是非独立调用

这里就讲到了隐式绑定

隐式绑定

代码中没有独立调用foo

探索 JavaScript “this”指向的内在逻辑

这里只是给foo赋值,并不是调用foo

继续分析代码

var a = 2
var obj = {
    a: 1,
    foo: foo
}

var obj2 = {
    a: 3,
    obj: obj
}

function foo() {
    console.log(this.a);
}

obj2.obj.foo()

这段代码是输出什么结果呢?

答案是

探索 JavaScript “this”指向的内在逻辑

这里都是按照隐式绑定的规则,但是这里的规则是就近原则,离谁更近就this指向的是谁

这叫隐式丢失

当一个函数被赋值被多个对象链式调用时,函数的this指向就近的那个对象

显示绑定

  • 显示绑定

以这段代码为例

const obj = {
  a: 1,
};

function foo() {
  console.log(this.a);
}

foo();

如果我就是要让其打印结果为1,怎么办?

  • 使用call
const obj = {
  a: 1,
};

function foo() {
  console.log(this.a);
}

foo.call(obj);

call 方法可以显式地指定函数内部的 this 指向。在这里,我们传递 obj 作为第一个参数,这样 foo 函数内部的 this 就会指向 obj 对象。

在执行 foo.call(obj) 时,输出结果会是 1,因为 this.a 会被解析为 obj.a,也就是 1

call可以传递参数

var obj = {
    a: 1
}
function foo(x, y) {
    console.log(this.a, x + y);
}

foo.call(obj,12);
  • apply

apply也可以传递参数,call和apply的区别就在于apply是用数组装参数

const obj = {
  a: 1,
};

function foo() {
  console.log(this.a);
}

foo.apply(obj ,[1, 2])
  • bind

bind的使用穿参数又有一点不一样

var obj = {
    a: 1
}
function foo(x, y) {
    console.log(this.a, x + y);
}

var bar = foo.bind(obj, 1, 2)
bar()

bind 方法用于创建一个新的函数实例,并将其 this 值绑定到 objbind 还允许我们传递函数的参数,这里我们传递了 1 和 2

它穿参数有以下三种方式

  • 第一种
var bar = foo.bind(obj, 1, 2)
bar()
  • 第二种
var bar = foo.bind(obj)
bar(1, 2)
  • 第三种
var bar = foo.bind(obj, 1)
bar(2)

如果出现参数过多就近原则

总结

本文深入讲解了this关键字,通过代码的方式深入讲解了它的指向问题以及指向规则

相信看到这里的你一定会有所收获的!!!

转载自:https://juejin.cn/post/7386875278423752739
评论
请登录