一起来了解JS中的this

引言

各位掘友大家好,今天我想要和大家一起来学习一下JS中的 "this" 关键字。"this" 是 JavaScript 中一个非常重要的概念,但也容易让初学者陷入一些常见的误解和陷阱中。比如忘记绑定 "this" 、在对象方法中丢失等等。

而在其他编程语言中,比如 Java 或 C++,"this" 关键字通常用于表示当前对象的引用,而在 JavaScript 中,"this" 的值取决于函数被调用的方式。这意味着在 JavaScript 中,同一个函数在不同的上下文中可能会有不同的 "this" 值,而在其他语言中 "this" 通常是固定的,指向当前对象。

此外,在 JavaScript 中,"this" 在箭头函数中的行为与传统函数不同,箭头函数没有自己的 "this" 绑定,它会继承外层函数的 "this" 值。而在其他语言中,箭头函数类似的语法可能不存在,或者具有不同的行为。

因此,要想在 JavaScript 中正确地理解和使用 "this",需要对其绑定规则有深入的理解,而不是简单地将其视为其他语言中的 "this" 关键字的直接对应。

为什么需要 "this" ?

为了理解 "this" 的作用,让我们考虑一个简单的场景:在一个对象中定义了一系列方法,这些方法需要访问对象自身的属性。如果没有 "this",我们可能需要将对象作为参数传递给这些方法,然后在方法内部使用对象参数来访问属性。这样做会使代码变得冗长且不直观。

js 复制代码
// 没有使用 "this" 的情况下访问对象属性 
const person = { 
    name: 'John', 
    greet: function() { 
        console.log('Hello, ' + person.name); 
        } 
    }; 
    person.greet(); // 输出:Hello, John

而有了 "this",我们就可以更自然地访问对象自身的属性,而无需显式传递对象:

js 复制代码
// 使用 "this" 访问对象属性
const person = {
  name: 'John',
  greet: function() {
    console.log('Hello, ' + this.name);
  }
};
person.greet(); // 输出:Hello, John

总而言之,this 让对象中的函数有能力访问对象自己的属性。this 可以显著提升代码质量,减少上下文参数的传递。

"this" 的绑定规则

在 JavaScript 中,"this" 的值取决于函数被调用的方式,有着不同的绑定规则:

1. 默认绑定

默认绑定 是当函数被独立调用时应用的规则。当函数没有显式绑定到任何对象时,它的 this 会被绑定到全局对象。或者说,当一个函数独立调用,不带任何修饰符时,默认绑定规则会把this绑定到全局对象。

函数在哪个词法作用域中生效,函数的this就指向哪里。这种行为在浏览器环境下通常是 window 对象,在 Node.js 环境下是 global 对象。

js 复制代码
function foo() {
    console.log(this === window);
}
foo(); // true

在例子中,当我们调用 foo() 函数时,没有明确指定执行上下文,因此默认绑定规则生效,将 this 绑定到全局对象 window(在浏览器环境下)

2. 隐式绑定

隐式绑定 是当函数作为对象的方法被调用时应用的规则。在这种情况下,函数的 this 将自动绑定到调用它的对象上。或者说,当函数有上下文对象时,即函数被某个对象所拥有时,this 将隐式地绑定到该对象。函数的this指向引用它的对象。

js 复制代码
const obj = {
    name: 'Alice',
    greet() {
        console.log(`Hello, ${this.name}!`);
    }
};
obj.greet(); // Hello, Alice!

在这个例子中,this 指向了对象 obj。当我们调用 obj.greet() 时,greet 函数作为 obj 的方法被调用,因此隐式绑定规则生效,将 this 隐式地绑定到 obj。这使得在 greet 函数内部可以访问 obj 对象的属性。

3. 显式绑定

显式绑定 是通过调用函数的 callapplybind 方法来手动指定函数的执行上下文。这种方式可以覆盖默认绑定和隐式绑定,确保函数的 this 是我们明确指定的对象。

通过显式绑定,我们可以更精确地控制函数的执行上下文,从而避免意外的行为和错误。

  • call 方法

call 方法允许你调用一个函数,并且可以指定函数体内 this 对象的值。例如:

js 复制代码
function greet() {
    console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Alice' };

// 使用 call 方法将 greet 函数的 this 绑定到 person 对象上
greet.call(person); // 输出:Hello, Alice!

这个例子中,我们调用了 greet 函数,并且通过 call 方法将 this 绑定到了 person 对象上,从而让函数可以访问 person 对象的属性。

  • apply 方法

apply 方法与 call 方法类似,唯一的区别是 apply 方法接收一个参数数组而不是一系列参数。例如:

js 复制代码
function greet(message) {
    console.log(`${message}, ${this.name}!`);
}

const person = { name: 'Bob' };

// 使用 apply 方法将 greet 函数的 this 绑定到 person 对象上,并传递一个参数数组
greet.apply(person, ['Greetings']); // 输出:Greetings, Bob!

在这个例子中,我们调用了 greet 函数,并且通过 apply 方法将 this 绑定到了 person 对象上,并传递了一个参数数组 ['Greetings']greet 函数。

  • bind 方法

bind 方法创建一个新函数,其中 this 的值被永久地绑定到指定的对象。

js 复制代码
function greet() {
    console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Charlie' };

// 使用 bind 方法创建一个新函数,其中 this 绑定到 person 对象上
const greetPerson = greet.bind(person);

// 调用新函数
greetPerson(); // 输出:Hello, Charlie!

在这个例子中,我们使用 bind 方法创建了一个新函数 greetPerson,其中 this 被永久地绑定到了 person 对象上。这意味着,无论在何处调用 greetPerson,其 this 都将指向 person 对象。

这些方法都用于显式绑定函数的 this,但它们之间存在一些细微的区别。bind 方法会创建一个新函数,而不会立即调用它;callapply 则会立即调用函数,并允许传递参数列表或参数数组。

以上就是三种显示绑定方法的示例。它们都允许你明确指定函数体内 this 的值,并在需要时将其绑定到指定的对象上。

4. 隐式丢失

隐式丢失 指的是当函数被多个对象链式调用时,函数的 this 指向就近的那个对象。这种情况下,函数的 this 可能不是我们期望的对象。

为了避免隐式丢失,我们可以使用 callapplybind 方法显式地绑定函数的 this。这样可以确保函数的执行上下文是我们所期望的对象。

js 复制代码
const person = {
    name: 'Bob',
    greet() {
        console.log(`Hello, ${this.name}!`);
    }
};
const anotherPerson = {
    name: 'Charlie'
};
person.greet.call(anotherPerson); // Hello, Charlie!

在这个例子中,我们定义了一个 person 对象和另一个没有 greet 方法的 anotherPerson 对象。然后,我们通过 call 方法将 greet 函数从 person 对象上解绑,并显式绑定到 anotherPerson 对象上。尽管 greet 函数原本属于 person 对象,但由于我们使用了显式绑定,this 指向了最近的对象 anotherPerson。这个过程展示了隐式丢失的现象。

5. new 绑定

当函数作为构造函数使用时,this 指向新创建的实例对象。这种方式称为 new 绑定。

在使用构造函数创建对象时,this 可以让我们在每个实例中访问和操作特定于该实例的属性和方法,这对于创建可重复使用的代码模块非常有用。

js 复制代码
function Person(name) {
    this.name = name;
}
const john = new Person('John');
console.log(john.name); // John

在构造函数中,this 指向新创建的实例对象。

理解这些绑定规则有助于我们编写更具可维护性和可扩展性的 JavaScript 代码,并避免常见的错误和陷阱。对于初学者来说,熟悉这些规则可能需要一些时间和练习,但随着经验的增长,它们将成为编写高效 JavaScript 代码的重要工具。

箭头函数中的 "this"

箭头函数与传统函数不同,箭头函数没有this这个机制,它没有自己的 this 绑定,而是继承父作用域的 this 值。写在箭头函数中this也是它外层非箭头函数的this。

js 复制代码
function outer() {
   return () => {
       console.log(this === window);
   };
}
const inner = outer();
inner(); // true

在箭头函数内部,this 将指向外层函数 outerthis,即全局对象 window

var 关键字的作用域

这里我想补充一个小的知识点:

在全局,通过var声明的变量相当于是往window 上添加了一个属性,而通过let声明的变量相当于是往全局对象上添加了一个属性。 这句话的意思是:

当你在全局作用域中使用 var 声明变量时,这个变量会被添加为全局对象的属性。在浏览器环境中,全局对象通常是 window 对象。因此,通过 var 声明的全局变量实际上是在 window 对象上创建了一个属性

js 复制代码
var a = 10;
console.log(window.a); // 10

let b = 20;
console.log(window.b); // undefined

通过了解 this 的绑定规则以及如何正确使用箭头函数和变量声明关键字,我们可以编写更加优雅和易于理解的 JavaScript 代码。

this 陷阱

在严格模式下,JavaScript 的行为与非严格模式下有所不同,特别是在默认绑定方面。在严格模式下调用函数时,全局对象不会被绑定到this,而是会被绑定到undefined。这可能导致意外的行为,特别是在访问全局变量时。让我们详细解释一下这个陷阱:

在非严格模式下,如果一个函数在全局作用域中被调用而没有显式绑定到其他对象,this会默认绑定到全局对象,如浏览器环境 下的window对象。

js 复制代码
function foo() {
  console.log(this === window);
}

foo(); // true

但是,在严格模式下,这种行为会有所不同:

js 复制代码
'use strict';

function bar() {
  console.log(this === window);
}

bar(); // false

在严格模式下调用函数bar()时,this不再指向全局对象window,而是被绑定到undefined。这种行为的变化可能会导致意外的结果,特别是当函数在严格模式和非严格模式之间切换时。

一个典型的陷阱是当我们在严格模式下访问全局变量时可能会导致错误:

js 复制代码
'use strict';

let globalVar = 'Hello';
function logGlobalVar() {
  console.log(this.globalVar); // TypeError: Cannot read property 'globalVar' of undefined
}
logGlobalVar();

在这个例子中,logGlobalVar()函数尝试访问全局变量globalVar,但由于在严格模式下调用,this被绑定到undefined,因此会导致TypeError错误。

为了避免这种陷阱,当在严格模式下编写代码时,要格外小心不要依赖于默认绑定到全局对象的行为,并确保正确处理this的值。

闭包中的 "this"

当在闭包中使用 this 时,可能会导致一些意外的结果,这是因为闭包中的函数并不会绑定新的 this,而是共享外部函数的 this

在 JavaScript 中,闭包是指内部函数可以访问其外部函数作用域中的变量。当内部函数引用外部函数的变量时,即使外部函数已经执行完毕,这些变量仍然可以被内部函数访问,因为内部函数形成了闭包。

然而,在闭包中使用 this 时,this 的值取决于函数的调用方式,而不是定义它的位置。这意味着闭包中的函数并不会创建新的 this,而是共享其外部函数的 this。这可能会导致一些意外的结果,特别是当我们期望 this 指向闭包函数自身所在的对象时。

下面我们举个例子,一起来看看怎么回事:

js 复制代码
const obj = {
    myName: "Alice",
    sayName: function() {
        setTimeout(function() {
            console.log(this.myName); // 在闭包中使用 this
        }, 1000);
    }
};

obj.sayName(); // 输出:undefined

首先补充一个知识点,就是 setTimeout 函数中的 this 指向全局,浏览器中也就是window

在这个例子中,obj.sayName() 方法内部的 setTimeout 函数形成了一个闭包。在闭包中,我们尝试使用 this.myName 来访问 obj 对象的 myName 属性。

然而,由于 setTimeout 的回调函数是在全局作用域中调用的,而不是 obj 对象上调用的,因此 this 的值不再是 obj 对象,而是全局对象(在浏览器中是 window)。因此,this.myName 的结果是 undefined

为了解决这个问题,可以使用箭头函数来代替普通函数,因为箭头函数继承了父作用域的 this 值,而不是创建新的 this。修改示例代码如下:

js 复制代码
const obj = {
    myName: "Alice",
    sayName: function() {
        setTimeout(() => {
            console.log(this.myName); // 在箭头函数中使用 this
        }, 1000);
    }
};

obj.sayName(); // 输出:Alice

在这个修改后的代码中,箭头函数继承了 sayName 方法中的 this 值,因此在闭包中可以正确地访问 obj 对象的 myName 属性,输出结果为 "Alice"

总结

以上便是对 this 在 JS 中的重要知识点的分享,希望能够对你有所帮助和理解,未来我还会继续学习 this 关键字并和大家分享,我们拭目以待。

觉得有用话,可以点赞收藏哟~

相关推荐
风清扬_jd4 分钟前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java20 分钟前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
It'sMyGo29 分钟前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式
懒羊羊大王呀30 分钟前
CSS——属性值计算
前端·css
李是啥也不会1 小时前
数组的概念
javascript
无咎.lsy1 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec1 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec1 小时前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆2 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
JUNAI_Strive_ving2 小时前
番茄小说逆向爬取
javascript·python