咳咳,各位观众老爷,晚上好!我是你们的老朋友,代码界的段子手。今天咱们聊聊 JavaScript 里让人又爱又恨的 this
关键字。这玩意儿要是没搞明白,写出来的代码就跟薛定谔的猫似的,运行结果全靠猜,刺激!
咱们今天就来扒一扒 this
的底裤,让它在咱们面前一丝不挂,彻底臣服!
一、this
的绑定规则:一场权力游戏
this
就像个墙头草,它指向谁,取决于它被调用的方式。记住这句话,非常重要!
JavaScript 中 this
的绑定规则主要有以下几种:
-
默认绑定(Default Binding):
- 规则: 在非严格模式下,如果
this
没有被其他规则绑定,它会默认绑定到全局对象。在浏览器中,全局对象通常是window
。在 Node.js 中,全局对象是global
。在严格模式下 ("use strict";
),this
会绑定到undefined
。 - 应用场景: 这种情况通常发生在函数独立调用时。
- 示例:
function foo() { console.log(this); // 在浏览器中输出 Window 对象 } foo(); function bar() { "use strict"; console.log(this); // 输出 undefined } bar();
- 规则: 在非严格模式下,如果
-
隐式绑定(Implicit Binding):
- 规则: 当函数作为对象的方法被调用时,
this
会绑定到该对象。 - 应用场景: 这是最常见的
this
绑定方式,尤其是在面向对象编程中。 - 示例:
const obj = { name: "张三", sayHello: function() { console.log("Hello, my name is " + this.name); } }; obj.sayHello(); // 输出 "Hello, my name is 张三"
注意: 如果方法链式调用,
this
始终指向调用该方法的对象。const obj1 = { name: "李四", method: function() { console.log("obj1的this:" + this.name); return this; } }; const obj2 = { name: "王五" }; obj2.method = obj1.method; obj1.method().method(); //李四 李四 this指向obj1 obj2.method().method(); //王五 王五 this指向obj2
- 规则: 当函数作为对象的方法被调用时,
-
显式绑定(Explicit Binding):
call()
,apply()
,bind()
- 规则: 通过
call()
,apply()
,bind()
方法,你可以明确地指定函数执行时this
的值。 - 应用场景: 当你需要控制
this
的指向,或者需要借用其他对象的方法时,可以使用显式绑定。 - 示例: 稍后详细讲解。
- 规则: 通过
-
new
绑定(new
Binding):- 规则: 当使用
new
关键字调用函数(构造函数)时,会发生以下事情:- 创建一个新的空对象。
- 将这个新对象的
__proto__
属性指向构造函数的prototype
属性。 - 将
this
绑定到这个新对象。 - 执行构造函数中的代码。
- 如果构造函数没有显式返回一个对象,则返回这个新对象。如果构造函数显式返回一个对象,则返回该对象。
- 应用场景: 用于创建对象实例。
- 示例:
function Person(name, age) { this.name = name; this.age = age; this.sayHello = function() { console.log("Hello, my name is " + this.name + ", I am " + this.age + " years old."); }; } const person1 = new Person("赵六", 30); person1.sayHello(); // 输出 "Hello, my name is 赵六, I am 30 years old." const person2 = new Person("钱七", 25); person2.sayHello(); // 输出 "Hello, my name is 钱七, I am 25 years old." // 构造函数显式返回对象的情况 function Animal(name) { this.name = name; return { // 显式返回一个对象 nickname: "小" + name, sayName: function() { console.log("我的昵称是:" + this.nickname); } }; } const animal1 = new Animal("老虎"); animal1.sayName(); // 输出 "我的昵称是:小老虎" console.log(animal1.name); // undefined 因为Animal构造函数最终返回的是另一个对象,没有name属性
- 规则: 当使用
优先级:
这些绑定规则有优先级,当多个规则同时适用时,优先级高的规则会覆盖优先级低的规则。优先级从高到低依次为:
new
绑定- 显式绑定 (
call()
,apply()
,bind()
) - 隐式绑定
- 默认绑定
二、call()
, apply()
, bind()
:显式绑定的三剑客
这三个方法都是 Function.prototype
上的方法,也就是说,所有的函数都可以调用它们。它们的作用都是改变函数执行时的 this
指向。
方法 | 作用 | 参数 | 返回值 | 应用场景 |
---|---|---|---|---|
call() |
立即调用函数,并将 this 绑定到指定的对象。 |
第一个参数是要绑定的对象,后面的参数是函数的参数,依次传入。 | 函数的返回值。 | 借用方法: 当你需要使用一个对象的方法,但该对象本身没有这个方法时,可以使用 call() 来借用其他对象的方法。 继承: 在构造函数中,可以使用 call() 来调用父类的构造函数,实现继承。 * 改变 this 指向: 当你需要手动控制 this 的指向时,可以使用 call() 。 |
apply() |
立即调用函数,并将 this 绑定到指定的对象。 |
第一个参数是要绑定的对象,第二个参数是一个数组,数组中的元素是函数的参数。 | 函数的返回值。 | 借用方法: 与 call() 类似,可以借用其他对象的方法。 求数组最大值/最小值: 可以使用 apply() 将 Math.max() 或 Math.min() 的 this 绑定到 null ,并将数组作为参数传入,快速求出数组的最大值或最小值。 * 参数不确定时: 当函数的参数个数不确定时,可以使用 apply() 将参数放入数组中传入。 |
bind() |
创建一个新的函数,该函数与原函数具有相同的函数体和作用域,但是 this 被永久绑定到指定的对象。 注意:bind() 不会立即执行函数,而是返回一个新的函数。 |
第一个参数是要绑定的对象,后面的参数是函数的参数,依次传入。 | 返回一个新的函数,该函数与原函数具有相同的函数体和作用域,但是 this 被永久绑定到指定的对象。 |
事件处理函数: 当你需要将一个函数作为事件处理函数,并且希望在事件处理函数中访问特定的对象时,可以使用 bind() 将 this 绑定到该对象。 柯里化: 可以使用 bind() 来实现柯里化,即固定函数的部分参数,生成一个新的函数。 * 避免 this 丢失: 当你将一个对象的方法传递给其他函数时,可能会导致 this 丢失,可以使用 bind() 将 this 绑定到该对象,避免 this 丢失。 |
2.1 call()
的用法
call()
方法接受一个 thisArg
参数,以及可选的参数列表。thisArg
指定了函数执行时 this
的值。如果 thisArg
是 null
或 undefined
,则 this
会绑定到全局对象(非严格模式)或 undefined
(严格模式)。
function sayHello(greeting) {
console.log(greeting + ", my name is " + this.name);
}
const person = {
name: "张三"
};
sayHello.call(person, "你好"); // 输出 "你好, my name is 张三"
sayHello.call(null, "Hello"); //非严格模式下 输出 "Hello, my name is undefined" (浏览器环境)
sayHello.call(undefined, "Hi"); //非严格模式下 输出 "Hi, my name is undefined" (浏览器环境)
应用场景:
-
借用方法:
const obj1 = { name: "李四", age: 20 }; const obj2 = { name: "王五" }; function showInfo() { console.log("Name: " + this.name + ", Age: " + this.age); } // obj2 没有 age 属性,但我们可以借用 obj1 的属性 obj2.age = 25; // 先给obj2加一个age属性 showInfo.call(obj2); // 输出 "Name: 王五, Age: 25"
-
继承:
function Animal(name) { this.name = name; } Animal.prototype.sayName = function() { console.log("I am an animal, my name is " + this.name); }; function Dog(name, breed) { // 调用 Animal 构造函数,将 this 绑定到 Dog 实例 Animal.call(this, name); this.breed = breed; } // 继承 Animal 的原型 Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; // 修正 constructor 指向 Dog.prototype.bark = function() { console.log("Woof!"); }; const dog = new Dog("旺财", "中华田园犬"); dog.sayName(); // 输出 "I am an animal, my name is 旺财" dog.bark(); // 输出 "Woof!"
2.2 apply()
的用法
apply()
方法与 call()
类似,也接受一个 thisArg
参数,但它只接受一个参数,即一个包含函数参数的数组(或者类数组对象)。
function sayHello(greeting, punctuation) {
console.log(greeting + ", my name is " + this.name + punctuation);
}
const person = {
name: "赵六"
};
sayHello.apply(person, ["Hello", "!"]); // 输出 "Hello, my name is 赵六!"
应用场景:
-
求数组最大值/最小值:
const numbers = [5, 2, 8, 1, 9, 4]; const max = Math.max.apply(null, numbers); // 将 Math.max 的 this 绑定到 null,并将 numbers 作为参数传入 const min = Math.min.apply(null, numbers); console.log("Max:", max); // 输出 "Max: 9" console.log("Min:", min); // 输出 "Min: 1"
-
参数不确定时:
function sum() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; } console.log("Sum:", total); } const nums = [1, 2, 3, 4, 5]; sum.apply(null, nums); // 输出 "Sum: 15"
2.3 bind()
的用法
bind()
方法创建一个新的函数,当这个新的函数被调用时,它的 this
值会被设置成 bind()
传入的第一个参数。bind()
还可以接受额外的参数,这些参数会在调用新函数时作为参数传递给原函数。注意:bind()
不会立即执行函数,而是返回一个新的函数。
function sayHello(greeting) {
console.log(greeting + ", my name is " + this.name);
}
const person = {
name: "钱七"
};
const boundSayHello = sayHello.bind(person); // 创建一个新的函数,this 绑定到 person
boundSayHello("你好"); // 输出 "你好, my name is 钱七"
const boundSayHello2 = sayHello.bind(person, "Hi"); //预先绑定greeting参数
boundSayHello2(); //输出 "Hi, my name is 钱七"
应用场景:
-
事件处理函数:
<!DOCTYPE html> <html> <head> <title>Bind Example</title> </head> <body> <button id="myButton">Click Me</button> <script> const obj = { name: "周八", handleClick: function() { console.log("Button clicked by " + this.name); } }; const button = document.getElementById("myButton"); button.addEventListener("click", obj.handleClick.bind(obj)); // 将 this 绑定到 obj </script> </body> </html>
在这个例子中,
handleClick
方法需要访问obj
的name
属性。如果不使用bind()
,this
在事件处理函数中会指向button
元素,而不是obj
。 -
柯里化:
function add(x, y) { return x + y; } const addFive = add.bind(null, 5); // 创建一个新的函数,x 固定为 5 console.log(addFive(3)); // 输出 8 console.log(addFive(10)); // 输出 15
-
避免
this
丢失:const obj = { name: "吴九", greet: function() { console.log("Hello, my name is " + this.name); }, delayedGreet: function() { setTimeout(this.greet.bind(this), 1000); // 将 this 绑定到 obj } }; obj.delayedGreet(); // 1秒后输出 "Hello, my name is 吴九"
如果不使用
bind()
,setTimeout
中的this
会指向全局对象,导致this.name
为undefined
。
三、总结
this
的绑定规则是 JavaScript 中一个重要的概念,理解 this
的指向对于编写高质量的 JavaScript 代码至关重要。call()
, apply()
, bind()
是显式绑定 this
的三种方法,它们各有特点,适用于不同的场景。
希望今天的讲解能够帮助大家彻底理解 this
关键字,告别薛定谔的代码!如果觉得有用,记得点个赞,分享给你的小伙伴们!下次再见!