地址:web前端面试题库
原型和原型链是JavaScript中一个重要且常常被误解的概念。它们在理解对象、继承和属性查找时扮演着关键的角色。
在JavaScript中,几乎所有的对象都是通过构造函数(constructor)创建的。每个构造函数都有一个特殊的属性称为原型(prototype),它是一个对象,包含了构造函数的属性和方法。当我们创建一个对象时,它会继承构造函数的原型上的属性和方法。
让我们通过一个示例来理解构造函数和原型之间的关系:
function Person(name, age) {
this.name = name;
this.age = age;
}
// 创建一个 Person 对象
const person1 = new Person('Alice', 25);
// 创建另一个 Person 对象
const person2 = new Person('Bob', 30);
console.log(person1.name); // 输出 'Alice'
console.log(person2.age); // 输出 30
在上面的示例中,我们定义了一个 Person
构造函数,它有两个属性 name
和 age
。然后,我们使用 new
关键字创建了两个 Person
对象 person1
和 person2
。这两个对象继承了 Person
构造函数的属性。
每个JavaScript对象都有一个指向其原型的隐藏属性 [[Prototype]]
(通常通过 __proto__
访问)。这个原型对象是一个普通对象,它包含了对象的方法和属性。
让我们看一下 person1
对象的原型:
console.log(person1.__proto__); // 输出 Person {}
person1
的原型是一个空的对象,它实际上就是 Person
构造函数的原型。这意味着 person1
可以访问 Person
构造函数原型上的属性和方法。它们之间的关系可以用下图表示:
需要注意:constructor 是原型的一个属性。
当我们访问一个对象的属性时,JavaScript 引擎会首先查找对象本身是否包含这个属性。如果对象没有这个属性,它会继续查找对象的原型,以及原型链上的其他原型,直到找到属性或查找到达原型链的末端。
让我们来看一个属性查找的例子:
console.log(person1.name); // 输出 'Alice'
console.log(person1.toString()); // 输出 '[object Object]'
在上面的示例中,当我们访问 person1
的 name
属性时,它首先查找 person1
对象本身,找到了属性 name
并输出 'Alice'。当我们调用 toString()
方法时,它查找 person1
对象本身没有这个方法,然后继续查找 Person
构造函数的原型,最终找到了 Object
构造函数的原型上的 toString()
方法。
原型链是由一系列对象链接在一起的链条,用于实现属性和方法的继承。每个对象都有一个原型,这个原型又有自己的原型,以此类推,形成了原型链。原型链的终点是一个对象,它的原型为 null
。
让我们通过一个例子来构建原型链:
function Animal(name) {
this.name = name;
}
// Animal 的原型
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Dog(name, breed) {
// 调用 Animal 构造函数
Animal.call(this, name);
this.breed = breed;
}
// 建立原型链,使 Dog 继承 Animal
Dog.prototype = Object.create(Animal.prototype);
// Dog 的原型
Dog.prototype.bark = function() {
console.log(`${this.name} is barking.`);
};
const myDog = new Dog('Buddy', 'Golden Retriever');
在上面的示例中,我们定义了一个 Animal
构造函数和一个 Dog
构造函数。然后,我们通过 Object.create()
来建立原型链,使 Dog
继承了 Animal
。最后,我们创建了一个 myDog
对象。
现在,myDog
对象继承了 Dog
构造函数和 Animal
构造函数的属性和方法。这意味着它可以访问 Dog
构造函数原型上的方法 bark
,以及 Animal
构造函数原型上的方法 eat
。
myDog.bark(); // 输出 'Buddy is barking.'
myDog.eat(); // 输出 'Buddy is eating.'
在上面的示例中,myDog
对象成功地继承了原型链上的属性和方法。
你还可以在继承的基础上修改原型链上的方法,以满足子类型的需求。
// 修改 Dog 原型上的方法
Dog.prototype.bark = function() {
console.log(`${this.name} is barking loudly.`);
};
myDog.bark(); // 输出 'Buddy is barking loudly.'
在上面的示例中,我们修改了 Dog
构造函数原型上的 bark
方法,使其输出不同的消息。
原型链的终点是一个对象,它的原型为 null
。这个对象是所有对象原型链的顶层,没有任何属性或方法。
console.log(Object.getPrototypeOf(Object.prototype)); // 输出 null
在上面的示例中,我们使用 Object.getPrototypeOf()
方法来获取 Object.prototype
的原型,发现它的原型是 null
。
在JavaScript中,每个对象都继承自 Object
构造函数的原型。这意味着所有对象都具有一些通用的属性和方法,例如 toString()
、valueOf()
等。
Object.prototype
所有对象的原型链的顶端都是 Object.prototype
,它包含了一些通用的方法,例如 toString()
和 valueOf()
。
const obj = {};
console.log(obj.toString()); // 输出 '[object Object]'
在上面的示例中,我们使用 obj
对象调用了 toString()
方法,这个方法实际上是从 Object.prototype
继承而来的。
Array.prototype
数组对象继承自 Array.prototype
,这个原型包含了许多用于操作数组的方法,如 push()
、pop()
、forEach()
等。
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.length); // 输出 5
numbers.push(6);
console.log(numbers); // 输出 [1, 2, 3, 4, 5, 6]
在上面的示例中,我们使用了 Array.prototype
上的方法来操作数组 numbers
。
Function.prototype
所有函数对象继承自 Function.prototype
,这个原型包含了一些用于函数的方法,如 call()
和 apply()
。
function greet(name) {
console.log(`Hello, ${name}!`);
}
greet.call(null, 'Alice'); // 输出 'Hello, Alice!'
在上面的示例中,我们使用了 Function.prototype
上的 call()
方法来调用函数 greet
。
原型和原型链在JavaScript中的实际应用非常广泛,它们是面向对象编程的基础,并且可以用于实现继承和共享方法。以下是一些实际应用的示例:
通过构造函数和原型链,你可以实现对象之间的继承关系,创建子类型并继承父类型的属性和方法。
function Shape() {
this.color = 'red';
}
Shape.prototype.getArea = function() {
return 0;
};
function Circle(radius) {
this.radius = radius;
}
// Circle 继承 Shape
Circle.prototype = Object.create(Shape.prototype);
// 添加 Circle 自己的方法
Circle.prototype.getArea = function() {
return Math.PI * Math.pow(this.radius, 2);
};
const circle = new Circle(5);
console.log(circle.color); // 输出 'red',继承自 Shape
console.log(circle.getArea()); // 输出 78.53981633974483,来自 Circle
在上面的示例中,我们创建了一个 Shape
构造函数,它有一个 color
属性和一个 getArea()
方法。然后,我们创建了一个 Circle
构造函数,通过原型链实现了对 Shape
的继承,并添加了自己的 getArea()
方法。
通过将方法添加到原型上,可以实现多个对象之间共享方法,节省内存并提高性能。
function Car(make, model) {
this.make = make;
this.model = model;
}
// 将方法添加到 Car 构造函数的原型上
Car.prototype.start = function() {
console.log(`Starting ${this.make} ${this.model}`);
};
const car1 = new Car('Toyota', 'Camry');
const car2 = new Car('Honda', 'Accord');
car1.start(); // 输出 'Starting Toyota Camry'
car2.start(); // 输出 'Starting Honda Accord'
在上面的示例中,我们将 start()
方法添加到 Car
构造函数的原型上,这意味着所有的 Car
对象都可以共享这个方法。
对象字面量是一种方便快捷的方式来创建对象,但它们实际上是通过原型链继承自 Object
构造函数的。
const person = {
name: 'John',
age: 30,
};
console.log(person.toString()); // 输出 '[object Object]'
在上面的示例中,person
对象是通过对象字面量创建的,它实际上继承了 Object.prototype
上的方法,包括 toString()
。
原型和原型链是JavaScript中非常重要的概念,它们用于实现对象的继承和方法的共享。了解原型和原型链的工作原理对于理解JavaScript中的对象和继承非常关键。
总结一下:
__proto__
或 Object.getPrototypeOf()
来访问。Object.prototype
,它包含了一些通用的方法。地址:web前端面试题库