this的多重身影:JavaScript中不同情境下的行为剖析

226 阅读5分钟

前言

当谈论 JavaScript 中的 this 关键字时,你可能会发现这个概念相当令人困惑。在不同的上下文中,this 的含义会发生变化,这使得它成为 JavaScript 开发人员经常会遇到的难题之一。了解 this 在不同情境下的行为是编写高效、可维护代码的关键所在。本文将深入探讨 this 在 JavaScript 中的多重身份,从全局范围到函数调用,再到对象方法和构造函数,我们将一一解析 this 在各种情境下的行为,帮助大家更好地理解和应用这个关键概念。

正文

  • 为什么要有this

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

  • 我们来看下面这段代码
let obj = {
    myname:'涛哥',
    age:18,

    bar: function(){ 
        console.log(myname);
    }
 }
 obj.bar()

image.png 在执行这段代码时我们会发现并没有打印出 涛哥 而是报错了,这是为什么呢? 这时我们在console.log(myname);改为console.log(this。myname);我们通过this来访问myname,终于也成功打印出来了

  • this的妙用:可以显著提升代码质量,减少上下文参数的传递
function identify(){
    return this.name.toUpperCase()
}

function speak(){
    var greeting = 'Hello i am' + identify.call(this)
    console.log(greeting);
}

var me = {
    name:'xiaoxiao',
}

speak.call(me)

在这段代码中我们在 speak() 函数中,我们创建了一个变量 greeting,其值为 'Hello i am'identify.call(this) 的结果的拼接。identify.call(this) 的调用意味着我们将 identify() 函数中的 this 绑定到了 speak() 函数中的 this。这种方式称为显式绑定,通过 call() 方法我们可以手动指定函数内 this 的指向。

  1. 然后,我们在 me 对象上调用了 speak() 函数,这样就将 speak() 函数中的 this 绑定到了 me 对象上。因此,identify.call(this) 中的 this 将指向 me 对象,于是 identify() 函数返回的是 me 对象的 name 属性的大写形式。
  2. 最后,console.log(greeting) 将会打印出 'Hello i am XIAOXIAO',其中 'XIAOXIAO'me 对象的 name 属性的大写形式。

我们可以在 speak() 函数内部控制 identify() 函数中 this 的指向。这种灵活性使得我们能够在不同的上下文中重复利用函数,并且可以动态地改变函数内部 this 的指向。

this的绑定规则

- [ ] 1.默认绑定: 当一个函数独立调用,不带任何修饰符的时候函数在哪个词法作用域下生效,函数中的this就指向哪里 --(只要是默认绑定,this一定指向window)*

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

这段代码打印出来了window:image.png

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

而这段代码打印出了obj里的对象: image.png

  • 当函数的引用有下文对象时(当函数被某个对象所拥有时)函数的this指向引它的对象
const obj = {
  name: 'John',
  greet: function() {
    console.log('Hello, ' + this.name);
  }
};

obj.greet(); // Output: Hello, John

在这个例子中,greet 函数作为 obj 对象的方法被调用。在 greet 函数内部,this 关键字指向调用它的对象,也就是 obj 对象,因此 this.name 将输出 John

  • 隐式丢失:当一个函数被多个对象链式调用时,函数的this指向就近的那个对象
const obj1 = {
  name: 'John',
  greet: function() {
    console.log('Hello, ' + this.name);
  }
};

const obj2 = {
  name: 'Alice'
};
obj1.greet.call(obj2); // Output: Hello, Alice

在这个例子中,greet 函数本来是作为 obj1 对象的方法,但是通过 call 方法将 greet 函数从 obj1 对象中提取出来,并在 obj2 对象上调用。在这种情况下,this 关键字在 greet 函数内部指向了 obj2 对象,而不是 obj1 对象,因此输出的结果是 Hello, Alice,而不是 Hello, John

这里发生了隐式丢失,因为原本 greet 函数的期望是在 obj1 上下文中被调用,但是在调用时上下文变成了 obj2。这种情况下,需要特别注意函数被调用的上下文,以避免隐式丢失问题。

显示绑定:call apply bind

  • callcall 方法调用一个函数,其第一个参数是要设置为函数执行上下文的对象。之后的参数是传递给函数的参数列表
function greet(name) {
    console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = {
    name: 'Alice'
};

greet.call(person, 'Bob');

这会打印出:Hello, Bob! My name is Alice.。在这个例子中,call 方法设置了 greet 函数的执行上下文为 person 对象,并传递了一个参数 'Bob'greet 函数。

applyapply 方法和 call 方法类似,不同之处在于它的第二个参数是一个数组,数组中的元素会被作为参数传递给函数。

function greet(name, age) {
    console.log(`Hello, ${name}! I am ${age} years old. My name is ${this.name}.`);
}

const person = {
    name: 'Alice'
};

greet.apply(person, ['Bob', 30]);

这会打印出:Hello, Bob! I am 30 years old. My name is Alice.apply 方法设置了 greet 函数的执行上下文为 person 对象,并传递了两个参数 'Bob'30greet 函数。

bindbind 方法创建一个新的函数,并将指定的对象绑定为该函数的执行上下文。与 callapply 不同,bind 不会立即执行函数,而是返回一个新的函数,你可以在之后调用该函数。

function greet(name) {
    console.log(`Hello, ${name}! My name is ${this.name}.`);
}

const person = {
    name: 'Alice'
};

const greetPerson = greet.bind(person);
greetPerson('Bob');

这会打印出:Hello, Bob! My name is Alice.bind 方法创建了一个新的函数 greetPerson,并将 person 对象绑定为该函数的执行上下文。之后调用 greetPerson 函数时,person 对象会成为 greet 函数的 this 值。

总的来说,这三个方法都允许你在调用函数时显式设置函数内部的 this 值,并且可以传递参数给函数。callapply 在设置函数上下文和传递参数方面非常相似,而 bind 则创建了一个新的函数,并将指定的对象绑定为该函数的执行上下文。

  • new 绑定: this指向创建的实例对象
function Person(name, age) {
    this.name = name;
    this.age = age;
}

const person1 = new Person('Alice', 30);
console.log(person1.name); // 输出:Alice
console.log(person1.age);  // 输出:30

new 绑定是一种创建新对象的方式,它会将构造函数的 this 绑定到新创建的实例对象上。当你使用 new 关键字来调用一个函数时,JavaScript 会执行以下步骤:

  1. 创建一个空的普通 JavaScript 对象(即空对象 {})。
  2. 将这个空对象的原型链接到构造函数的原型对象上。
  3. 执行构造函数,并将这个空对象绑定到构造函数内部的 this 上。
  4. 如果构造函数没有显式返回一个对象,则返回这个新创建的对象。

总结

总的来说,this 的值在 JavaScript 中是动态的,并且取决于函数的调用方式。理解 this 的行为对于正确地使用 JavaScript 函数和面向对象编程非常重要。