JavaScript 中的 this 及 this 指向的改变方法

在 JavaScript 的世界里,this是一个既强大又容易让人困惑的概念。它的指向在不同的函数调用场景下会动态变化,而call()、apply()和bind()这三个方法则为我们提供了精确控制this指向的能力。本文将从基础概念出发,结合具体案例,带大家深入理解这些核心知识点。

一、JavaScript 中 this 的本质

1. 作为普通函数调用(独立调用)​

当函数作为独立函数调用时(即没有任何对象前缀),this指向全局对象(浏览器环境为window,Node 环境为global)。在严格模式下("use strict"),this会被绑定为undefined。

function sayHello() {
  console.log(this); // 普通调用时指向window(非严格模式)
}
sayHello(); // window

2. 作为对象方法调用​

当函数作为对象的方法被调用时(即通过对象。方法 () 的形式),this指向该对象本身:

const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, ${this.name}`); // 指向person对象
  }
};
person.greet(); // Hello, Alice

3. 作为构造函数调用​

使用new关键字调用函数时,会创建一个新对象,此时this指向这个新创建的实例对象:

function Person(name) {
  this.name = name; // 指向新创建的实例
}
const alice = new Person('Alice');
console.log(alice.name); // Alice

4. 箭头函数中的 this​

箭头函数不具备自身的this,它的this继承自外层作用域(词法作用域),且无法通过call()、apply()、bind()改变:

⑴.箭头函数的 this 继承外层作用域

箭头函数的 this 值在函数 定义时 就确定,继承自外层(词法作用域)的 this,而非运行时的调用上下文。

测试代码 1:全局作用域

// 全局作用域(非严格模式)
const globalArrow = () => {
  console.log(this); // 输出:window(浏览器环境)或 global(Node.js)
};

globalArrow();

// 严格模式下
(function() {
  "use strict";
  const strictArrow = () => {
    console.log(this); // 输出:undefined(因为外层严格模式的 this 是 undefined)
  };
  strictArrow();
})();

测试代码 2:对象方法中的 this

const obj = {
  name: '普通函数',
  regularMethod: function() {
    console.log(this.name); // 输出:"普通函数"(this 指向 obj)
  },
  arrowMethod: () => {
    console.log(this.name); // 输出:undefined(或全局对象的 name,如果存在)
  }
};

obj.regularMethod();  // 普通函数:this 指向 obj
obj.arrowMethod();    // 箭头函数:this 继承自外层(全局或严格模式下的 undefined)

⑵.无法通过 bind()call()apply() 改变 this

箭头函数的 this固定 的,无法通过 bindcallapply 改变。

测试代码 3:尝试绑定 this

const arrowFunc = () => {
  console.log(this); // 始终继承定义时的 this
};

const obj = { name: '尝试绑定' };

// 尝试绑定 this 到 obj
const boundFunc = arrowFunc.bind(obj);
boundFunc(); // 输出:全局对象(或 undefined,取决于定义时的外层 this)

// 使用 call/apply 也无效
arrowFunc.call(obj); // 输出:全局对象

⑶.构造函数中的箭头函数

箭头函数 不能作为构造函数 使用,因为没有自己的 this,且没有 prototype

测试代码 4:构造函数错误

// 错误示例:尝试用箭头函数作为构造函数
const Person = (name) => {
  this.name = name; // 报错!箭头函数没有 this
};

new Person('Alice'); // 抛出错误:this is undefined

⑷.嵌套函数中的 this 传递

箭头函数的 this 继承自 外层最近的非箭头函数this

测试代码 5:嵌套函数中的 this

function outer() {
  this.message = '外层函数';
  const innerRegular = function() {
    console.log(this.message); // 输出:undefined(因为 innerRegular 的 this 默认指向全局或严格模式下的 undefined)
  };
  
  const innerArrow = () => {
    console.log(this.message); // 输出:"外层函数"(继承 outer 的 this)
  };
  
  innerRegular();   // 普通函数:this 未绑定
  innerArrow();     // 箭头函数:this 继承自 outer
}

outer();

二、改变 this 指向的三种方法​

1. call () 方法 - 显式指定 this 并立即执行​

call() 方法是 JavaScript 中函数对象的一个内置方法,它允许你调用一个函数,并指定这个函数内部 this 的值,同时还可以传递参数给这个函数。

语法

function.call(thisArg, arg1, arg2, ...)
函数名.call(此值指向对象, 参数1, 参数2, …)
  • 函数名:调用 call 方法的函数。
  • 此值指向对象:调用函数时,函数内部 this 关键字所指向的值。若该参数为 null 或者 undefined,那么 this 会指向全局对象(在浏览器环境里是 window 对象)。
  • 参数 1, 参数 2, …:属于可选参数,是传递给函数的参数列表。

案例

案例一:改变 this 的指向

const person = {
    name: 'Alice',
    greet: function() {
        console.log(`Hello, my name is ${this.name}.`);
    }
};

const anotherPerson = {
    name: 'Bob'
};

// 使用 call 方法改变 greet 函数的 this 指向
person.greet.call(anotherPerson);

在这个例子中,person 对象有一个 greet 方法,该方法使用 this.name 来输出信息。通过 call 方法,我们将 greet 方法内部的 this 指向了 anotherPerson 对象,所以输出的是 Bob 的名字。

案例二:传递参数

function add(a, b) {
    return a + b;
}

// 使用 call 方法调用 add 函数并传递参数
const result = add.call(null, 3, 5);
console.log(result);

在这个例子中,add 函数是一个简单的加法函数。由于 add 函数不依赖于 this,所以第一个参数可以传入 null。然后我们通过 call 方法传递了两个参数 3 和 5,最终得到结果 8

案例三:类数组对象使用数组方法

const arrayLike = {
    0: 'apple',
    1: 'banana',
    2: 'cherry',
    length: 3
};

// 使用 call 方法让类数组对象使用数组的 forEach 方法
Array.prototype.forEach.call(arrayLike, function(item) {
    console.log(item);
});

在这个例子中,arrayLike 是一个类数组对象,它没有数组的方法。通过 call 方法,我们让 arrayLike 对象使用了数组的 forEach 方法,从而可以遍历它的元素。

2.apply () 方法

apply() 方法也是 JavaScript 中函数对象的一个内置方法,与 call() 方法类似,都可以用来调用函数并指定函数内部 this 的值,不同之处在于 apply() 方法接收参数的方式是一个数组或类数组对象。

语法

function.apply(thisArg, [argsArray])

参数解释

  • function:要调用 apply 方法的函数。
  • thisArg:在调用函数时,this 关键字会指向这个值。如果该参数为 null 或 undefined,在非严格模式下,this 会指向全局对象(在浏览器环境中是 window 对象);在严格模式下,this 的值为 null 或 undefined
  • argsArray:一个包含要传递给函数的参数的数组或类数组对象。如果不需要传递参数,可以传入一个空数组 []

案例

案例一:改变 this 的指向

const person = {
    name: 'Alice',
    greet: function() {
        console.log(`Hello, my name is ${this.name}.`);
    }
};

const anotherPerson = {
    name: 'Bob'
};

// 使用 apply 方法改变 greet 函数的 this 指向
person.greet.apply(anotherPerson);

在这个例子中,person 对象有一个 greet 方法,通过 apply 方法将 greet 方法内部的 this 指向了 anotherPerson 对象,所以输出的是 Bob 的名字。

案例二:传递参数

function add(a, b) {
    return a + b;
}

// 使用 apply 方法调用 add 函数并传递参数
const result = add.apply(null, [3, 5]);
console.log(result); 

这里 add 函数是一个简单的加法函数,因为 add 函数不依赖于 this,所以第一个参数传入 null。通过 apply 方法传递了一个包含 3 和 5 的数组作为参数,最终得到结果 8

案例三:求数组中的最大值

const numbers = [5, 10, 3, 8, 15];

// 使用 Math.max.apply 求数组中的最大值
const max = Math.max.apply(null, numbers);
console.log(max); 

Math.max() 函数用于求多个数中的最大值,但它接收的是多个独立的参数,而不是一个数组。通过 apply 方法,将 numbers 数组作为参数传递给 Math.max() 函数,就可以求出数组中的最大值 15

3. bind () 方法

bind()方法会创建一个新函数,当这个新函数被调用时,this会被绑定为bind()方法的第一个参数。

语法

function.bind(thisArg, arg1, arg2, ...)
函数名.bind(此值指向对象, 参数1, 参数2, …)

参数解释

  • 函数名:调用 bind 方法的原函数。
  • 此值指向对象:在调用新创建的函数时,函数内部 this 关键字会指向该值。若传入 null 或 undefined,在非严格模式下,this 指向全局对象(浏览器环境中是 window 对象);严格模式下,this 就是 null 或 undefined
  • 参数 1, 参数 2, …:可选参数,是在调用新创建的函数时预先传入的参数。

案例

案例一:改变 this 的指向

const person = {
    name: 'Alice',
    greet: function() {
        console.log(`Hello, my name is ${this.name}.`);
    }
};

const anotherPerson = {
    name: 'Bob'
};

// 使用 bind 方法创建一个新函数,改变 greet 函数的 this 指向
const newGreet = person.greet.bind(anotherPerson);
newGreet();

在这个例子里,person 对象有 greet 方法,借助 bind 方法创建了一个新函数 newGreet,并把 greet 方法内部的 this 指向了 anotherPerson 对象,调用 newGreet 时输出的就是 Bob 的名字。

案例二:预先传入参数

function multiply(a, b) {
    return a * b;
}

// 使用 bind 方法创建一个新函数,预先传入第一个参数
const double = multiply.bind(null, 2);
console.log(double(5)); 

这里的 multiply 函数用于计算两个数的乘积。通过 bind 方法创建了新函数 double,预先传入参数 2 作为第一个参数。调用 double(5) 时,相当于调用 multiply(2, 5),最终结果是 10

案例三:在事件处理程序中使用

const button = document.createElement('button');
button.textContent = 'Click me';

const obj = {
    message: 'Button clicked!',
    handleClick: function() {
        console.log(this.message);
    }
};

// 使用 bind 方法确保 handleClick 中的 this 指向 obj
button.addEventListener('click', obj.handleClick.bind(obj));
document.body.appendChild(button);

在这个例子中,创建了一个按钮元素,obj 对象有 handleClick 方法。通过 bind 方法将 handleClick 方法的 this 指向 obj,这样在点击按钮触发事件时,就能正确输出 Button clicked!

4.bind() 与 call()apply() 的区别

  • call() 和 apply() 是直接调用函数,同时可以指定 this 值和参数。
  • bind() 并不直接调用函数,而是创建一个新的函数,这个新函数在被调用时,会使用指定的 this 值和预先传入的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值