this、class、new
this
是什么
在 JavaScript 中,this
是一个关键字,它在函数被调用的时候设定,指向函数运行时的上下文(context)。this
的值并不取决于函数本身是如何定义的,而是取决于函数是如何被调用的。
绑定方式
默认绑定:在非严格模式下,当一个函数独立调用(即它不是作为对象的属性或方法调用),
this
通常会绑定到全局对象(在浏览器中是window
对象,在 Node.js 中是global
对象)。在严格模式下,this
会绑定到undefined
。隐式绑定:当一个函数作为对象的方法调用时,
this
会绑定到那个对象,就近原则。显式绑定:我们可以使用
call
、apply
或bind
方法显式地设置this
的值。call
和apply
会立即调用函数,并允许你传入一个对象作为this
的值,而bind
会创建一个新的函数,当这个新函数被调用时,this
会被绑定到你传入的对象。硬绑定:硬绑定是显式绑定的一种,使用
.bind()
方法可以创建一个新的函数,这个函数的this
被永久绑定到bind
的第一个参数。new 绑定:当一个函数或类被
new
关键字作为构造函数调用时,JavaScript 会创建一个新的空对象,然后this
会被绑定到这个新对象,也就是实例对象。箭头函数:箭头函数不会创建自己的
this
上下文,而是会捕获其所在(即定义的位置)上下文的this
值。这意味着箭头函数内的this
值与它被定义时的外围上下文中的this
值相同。事件处理器:事件处理函数内部的
this
总是指向被绑定DOM
元素,这种行为使得你可以在事件处理器中方便地访问和操作触发事件的元素。需要注意的是,如果你使用箭头函数作为事件处理器,那么this
将不会指向触发事件的元素,而是指向箭头函数在定义时的上下文。
这些规则的优先级从高到低依次是:new
绑定 > 显式绑定/硬绑定 > 隐式绑定 > 默认绑定。也就是说,如果一个函数同时满足多个规则,那么优先级高的规则将决定 this
的值。
理解 this
的工作原理对于编写和理解 JavaScript 代码非常重要,因为 this
在函数调用、对象方法、构造函数、事件处理器等多个场景中都有广泛的应用。
使用场景
this
在 JavaScript 中有很多使用场景,下面是一些典型的例子:
对象方法中使用
this
在对象的方法中,
this
通常指向调用该方法的对象。javascriptlet obj = { name: "Alice", greet: function () { console.log("Hello, " + this.name); }, }; obj.greet(); // 输出:'Hello, Alice'
在这个例子中,
greet
方法内的this
指向了obj
对象。构造函数中使用
this
在使用
new
关键字调用的函数(即构造函数)中,this
指向新创建的对象。javascriptfunction Person(name) { this.name = name; } let alice = new Person("Alice"); console.log(alice.name); // 输出:'Alice'
在这个例子中,
this
在Person
函数内部指向新创建的对象。显式绑定
this
我们可以使用
call
、apply
或bind
方法显式地设置this
的值。javascriptfunction greet() { console.log("Hello, " + this.name); } let obj = { name: "Alice" }; greet.call(obj); // 输出:'Hello, Alice'
在这个例子中,我们使用
call
方法将this
绑定到obj
对象,然后调用greet
函数。箭头函数中的
this
箭头函数不会创建自己的
this
上下文,而是继承外层代码块的this
。javascriptlet obj = { name: "Alice", greet: function () { setTimeout(() => { console.log("Hello, " + this.name); }, 1000); }, }; obj.greet(); // 一秒后输出:'Hello, Alice'
在这个例子中,箭头函数内的
this
是从外层的greet
方法继承的,所以它指向obj
对象。DOM 事件处理器中的
this
在 DOM 事件处理器中,
this
通常指向触发事件的元素。javascriptlet button = document.querySelector("button"); button.addEventListener("click", function () { console.log(this); // 输出:点击的按钮元素 });
在这个例子中,
this
在事件处理器函数中指向触发点击事件的按钮元素。Function 与箭头函数
var a = 2;
var obj = {
a: 1,
test: () => {
console.log(this.a);
},
do: function () {
console.log(this.a);
},
testThis: function () {
console.log("testThis", this); // 最近引用作用域 -> obj
const test1 = () => {
console.log("test1", this); // 上层作用域 -> obj
const test2 = () => {
console.log("test2", this); // 上层作用域 -> 上层作用域 -> obj
const test3 = function () {
console.log("test3", this); // 最近引用作用域 -> function -> window
const test4 = () => {
console.log("test4", this); // 上层作用域 -> window
};
test4();
};
test3();
};
test2();
};
test1();
},
insideObj: {
a: 3,
test: function () {
console.log(this.a); // 3
},
},
};
const test = () => {
console.log(this.a);
};
test(); // 2
test.call(obj); // 2
test.apply(obj); // 2
test.bind(obj)(); // 2
obj.test(); // 2
obj.do(); // 1
以上就是 this
在 JavaScript 中的一些典型使用场景。理解这些场景可以帮助你更好地理解和使用 this
关键字。
class
是什么
在 JavaScript 中,class
是 ES6(ECMAScript 2015)中引入的一个关键字,用于定义类。类是一种特殊的函数,它可以包含构造函数、方法、属性等。
类主要用于创建对象(实例)。它是对象创建的蓝图,定义了一个对象的属性和方法。
这是一个类的基本定义:
class MyClass {
constructor() {
// 初始化实例属性
}
myMethod() {
// 定义一个方法
}
}
你可以使用new
关键字来创建一个类的实例:
let instance = new MyClass();
constructor
方法是类的特殊方法,它在创建新实例时被自动调用。
类也支持继承,你可以通过extends
关键字来创建一个类的子类:
class MySubClass extends MyClass {
// ...
}
在子类中,你可以使用super
关键字来调用父类的方法。
执行过程
JavaScript 中的class
的执行过程可以分为以下几个步骤:
- 声明和定义:首先,我们需要声明并定义一个类。这包括定义类的构造函数、方法和属性。这些方法和属性将被存储在类的原型对象中。
class MyClass {
constructor() {
// 初始化实例属性
}
myMethod() {
// 定义一个方法
}
}
- 实例化:当我们使用
new
关键字创建一个类的实例时,JavaScript 会创建一个新的对象,并将类的原型对象设置为这个新对象的原型。然后,JavaScript 会执行类的构造函数,初始化新对象的属性。
let instance = new MyClass();
- 方法调用:当我们调用实例的方法时,JavaScript 会首先在实例自身的属性中查找这个方法。如果没有找到,它会在实例的原型(也就是类的原型对象)中查找。如果在原型中找到了这个方法,JavaScript 会执行它。
instance.myMethod();
- 继承:如果我们定义了一个继承自其他类的类,那么在实例化这个子类时,JavaScript 会首先创建一个新的对象,并将子类的原型对象设置为这个新对象的原型。然后,JavaScript 会执行父类的构造函数,初始化新对象的属性。最后,JavaScript 会执行子类的构造函数,进一步初始化新对象的属性。
class MySubClass extends MyClass {
constructor() {
super(); // 调用父类的构造函数
// 进一步初始化新对象的属性
}
}
- 子类方法调用:当我们调用子类实例的方法时,JavaScript 的查找顺序是:首先在实例自身的属性中查找,然后在子类的原型中查找,最后在父类的原型中查找。如果在这个过程中找到了这个方法,JavaScript 会执行它。
以上就是 JavaScript 中class
的基本执行过程。
模拟实现
在 ES6 之前,JavaScript 并没有类(class)这个概念,但我们可以通过函数和原型(prototype)来模拟实现类的功能。以下是一个例子:
// 定义构造函数
function MyClass() {
// 初始化实例属性
this.myProperty = "some value";
}
// 在原型上定义方法
MyClass.prototype.myMethod = function () {
// 定义一个方法
console.log("This is my method");
};
// 实例化
var instance = new MyClass();
instance.myMethod(); // 输出: 'This is my method'
// 模拟实现继承
function MySubClass() {
MyClass.call(this); // 调用父类构造函数
this.mySubProperty = "some other value";
}
// 设置原型为父类的实例,实现继承
MySubClass.prototype = Object.create(MyClass.prototype);
MySubClass.prototype.constructor = MySubClass;
// 在子类原型上添加方法
MySubClass.prototype.mySubMethod = function () {
console.log("This is my sub method");
};
// 实例化子类
var subInstance = new MySubClass();
subInstance.myMethod(); // 输出: 'This is my method'
subInstance.mySubMethod(); // 输出: 'This is my sub method'
在上面的代码中,我们使用了函数和原型来模拟实现了类、实例化、方法定义和继承等功能。这就是在 ES6 引入class
关键字之前,JavaScript 是如何模拟实现类的功能的。
使用场景
JavaScript 的class
主要用于创建对象模型,它的使用场景非常广泛。以下是一些常见的使用场景:
- 封装:我们可以使用
class
来封装相关的数据和操作。例如,我们可以定义一个Person
类来封装人的属性和方法:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
let john = new Person("John", 30);
john.greet(); // 输出: 'Hello, my name is John'
- 继承:我们可以使用
class
来实现继承,以复用和扩展现有的代码。例如,我们可以定义一个Student
类继承自Person
类,并添加新的方法:
class Student extends Person {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
study() {
console.log(`${this.name} is studying`);
}
}
let alice = new Student("Alice", 20, "A");
alice.greet(); // 输出: 'Hello, my name is Alice'
alice.study(); // 输出: 'Alice is studying'
- 多态:我们可以使用
class
来实现多态,即同一个方法在不同的类中有不同的实现。例如,我们可以定义一个Animal
类和两个继承自它的Dog
和Cat
类,它们都有一个makeSound
方法,但实现不同:
class Animal {
makeSound() {
throw new Error("This method must be overridden in a subclass");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof!");
}
}
class Cat extends Animal {
makeSound() {
console.log("Meow!");
}
}
let dog = new Dog();
dog.makeSound(); // 输出: 'Woof!'
let cat = new Cat();
cat.makeSound(); // 输出: 'Meow!'
- 模块化和代码复用:我们可以将类定义在一个模块中,然后在其他模块中导入和使用它,从而实现代码的模块化和复用。例如,我们可以在一个模块中定义一个
Vector
类,然后在其他模块中导入它:
// 在Vector.js模块中
export class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(vector) {
return new Vector(this.x + vector.x, this.y + vector.y);
}
}
// 在其他模块中
import { Vector } from "./Vector.js";
let v1 = new Vector(1, 2);
let v2 = new Vector(2, 3);
let v3 = v1.add(v2);
以上就是class
在 JavaScript 中的一些常见使用场景。
new
是什么
在 JavaScript 中,new
关键字用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。使用 new
关键字创建对象的过程主要有以下几个步骤:
创建一个新对象:JavaScript 首先会创建一个新的空对象。
设置原型:新创建的对象的
__proto__
属性会被设置为构造函数的prototype
对象。这样新对象就可以访问构造函数原型中定义的方法和属性。绑定
this
:在构造函数内部,this
关键字被设置为新创建的对象。如果构造函数中的代码引用了this
,那么这个this
就指向新创建的对象。执行构造函数中的代码:构造函数内部的代码(对
this
的属性和方法的引用)被执行,通常这些代码会初始化新对象的状态。返回新对象:如果构造函数没有显式返回一个对象,则会自动返回新创建的对象。如果构造函数返回了一个对象,那么这个对象会作为
new
表达式的结果返回;如果构造函数返回了一个非对象类型的值,那么这个返回值会被忽略,还是会返回新创建的对象。
以下是一个简单的例子:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function () {
console.log("Hello, my name is " + this.name);
};
var john = new Person("John", 30);
john.sayHello(); // 输出 "Hello, my name is John"
在这个例子中,new Person('John', 30)
创建了一个新的 Person
对象,并将 name
和 age
属性初始化为 'John' 和 30。新对象的 __proto__
属性被设置为 Person.prototype
,所以它可以访问 sayHello
方法。
执行过程
在 JavaScript 中,new
操作符的执行过程可以分为以下四个步骤:
创建一个新对象:首先,
new
操作符会创建一个空的 JavaScript 对象。这个新对象是一个实例,它的类型是定义的特定构造函数。设置原型:新创建的对象的
__proto__
属性会被指向构造函数的prototype
属性,从而继承构造函数原型上的属性和方法。这是 JavaScript 的原型继承机制。执行构造函数:然后,
new
操作符会调用构造函数,执行其中的代码。注意,构造函数中的this
会被指向新创建的对象,因此构造函数中的属性和方法都会被添加到新对象上。返回新对象:如果构造函数没有手动返回一个对象,那么
new
操作符会自动返回新创建的对象。如果构造函数返回的是一个非对象类型,那么这个返回值会被忽略,仍然返回新创建的对象。但是,如果构造函数返回的是一个新对象,那么new
操作符会返回这个新对象,而不是之前创建的对象。
以下是一个简单的例子来说明new
操作符的执行过程:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function () {
console.log("Hello, my name is " + this.name);
};
var john = new Person("John", 30);
john.sayHello(); // 输出:Hello, my name is John
在这个例子中,new Person('John', 30)
的执行过程如下:
- 创建一个新对象
{}
。 - 设置新对象的
__proto__
属性指向Person.prototype
,从而继承Person.prototype
上的方法。 - 执行
Person
构造函数,其中的this
被指向新对象,因此name
和age
属性被添加到新对象上。 new
操作符返回新创建的对象。
模拟实现
在 JavaScript 中,我们可以模拟实现 new
操作符的功能。以下是一个简单的实现:
function myNew(Constructor, ...args) {
// 创建一个新的空对象
var obj = {};
// 设置新对象的 __proto__ 属性指向构造函数的 prototype 对象
obj.__proto__ = Constructor.prototype;
// 执行构造函数,并将 this 指向新创建的对象
var result = Constructor.apply(obj, args);
// 如果构造函数执行的结果是一个对象,就返回这个对象;否则,返回新创建的对象
return typeof result === "object" ? result : obj;
}
这个 myNew
函数首先创建一个新的空对象,然后设置这个新对象的 __proto__
属性指向构造函数的 prototype
对象,这样就可以让新对象继承构造函数的 prototype
对象上的属性和方法。接着,执行构造函数,并将 this
指向新创建的对象。最后,如果构造函数执行的结果是一个对象,就返回这个对象;否则,返回新创建的对象。
下面是一个简单的例子来说明 myNew
函数的使用方法:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function () {
console.log(
"Hello, my name is " + this.name + ", I am " + this.age + " years old."
);
};
var john = myNew(Person, "John", 20);
john.sayHello(); // 输出:Hello, my name is John, I am 20 years old.
在这个例子中,myNew(Person, 'John', 20)
创建了一个新的 Person
对象,并返回这个对象。这个新的对象可以访问 Person.prototype
对象上的 sayHello
方法。
使用场景
new
关键字在 JavaScript 中主要用于创建对象的实例,这在以下几种场景中特别有用:
- 创建自定义对象类型:当你需要创建具有特定属性和方法的多个对象时,可以创建一个构造函数,并使用
new
关键字来创建新的对象实例。
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var myCar = new Car("Toyota", "Corolla", 2005);
console.log(myCar.make); // 输出 "Toyota"
- 使用内置对象类型:JavaScript 内置了一些对象类型,如
Date
、Array
、Set
、Map
等,你可以使用new
关键字来创建这些对象的实例。
// 创建一个新的日期对象
var now = new Date();
console.log(now); // 输出当前日期和时间
// 创建一个新的数组对象
var arr = new Array(1, 2, 3, 4, 5);
console.log(arr); // 输出 [1, 2, 3, 4, 5]
- 创建继承其他对象的对象:你可以使用
new
关键字和Object.create
方法来创建一个新对象,该对象的原型被设置为另一个对象。
var person = {
name: "Alice",
sayHello: function () {
console.log("Hello, my name is " + this.name);
},
};
var employee = Object.create(person);
employee.name = "Bob";
employee.sayHello(); // 输出 "Hello, my name is Bob"
在这个例子中,employee
对象是通过Object.create(person)
创建的,所以employee
的原型就是person
,并且employee
继承了person
的sayHello
方法。