引言:
JavaScript 中的原型对象(prototype)是一个重要而强大的概念,它对于理解对象的继承和原型链非常关键。以下是与 JavaScript 原型对象相关的一些常用知识点:
-
构造函数和原型对象:
- 构造函数是用于创建对象的函数。每个构造函数都有一个关联的原型对象。
- 使用构造函数创建的对象会继承构造函数的原型对象上的属性和方法。
-
原型链:
- 对象之间的继承关系通过原型链建立。每个对象都有一个原型,而原型又可以有自己的原型,形成链式结构。
- 当你访问一个对象的属性或方法时,JavaScript 引擎首先在该对象上查找,然后沿着原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶端(
Object.prototype
)。
-
prototype 属性:
- 每个函数都有一个
prototype
属性,它是一个指向函数原型对象的引用。 - 通过修改构造函数的
prototype
属性,你可以向构造函数的所有实例添加共享的属性和方法。
- 每个函数都有一个
-
__proto__
属性:__proto__
是每个对象都有的属性,它指向该对象的原型。- 尽管
__proto__
在现代 JavaScript 中被认为是过时的,但它仍然可以用于访问和修改对象的原型。
-
Object.create() 方法:
Object.create(proto)
方法创建一个新对象,该对象的原型指向proto
。这是一种显式设置对象原型的方式。
-
hasOwnProperty 方法:
hasOwnProperty
是对象的原型链上的方法,用于检查对象自身是否包含指定的属性(而不是从原型链继承的属性)。
-
继承和原型对象:
- 通过原型对象,JavaScript 实现了基于原型的继承。子对象通过原型链继承父对象的属性和方法。
-
Object.prototype
:Object.prototype
是所有对象原型链的顶端。所有对象最终都继承自Object.prototype
,这包括基本数据类型的包装对象。
详细讲解:
01.构造函数和原型对象
构造函数:
构造函数是用于创建对象的函数。让我们以一个简单的例子开始,创建一个 Person
构造函数:
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 使用构造函数创建对象
const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);
console.log(person1); // 输出 { name: 'Alice', age: 25 }
console.log(person2); // 输出 { name: 'Bob', age: 30 }
在这个例子中,Person
是一个构造函数,它接受两个参数 name
和 age
,并将它们分别赋值给新创建的对象的属性。
原型对象:
每个函数都有一个 prototype
属性,它是一个对象。通过这个属性,我们可以将共享的属性和方法添加到由该构造函数创建的所有对象上。让我们为 Person
构造函数添加一个共享的方法:
// 在构造函数的原型上添加方法
Person.prototype.sayHello = function() {
console.log("Hello, my name is " + this.name + " and I'm " + this.age + " years old.");
};
// 使用构造函数创建对象
const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);
// 调用共享的方法
person1.sayHello(); // 输出 "Hello, my name is Alice and I'm 25 years old."
person2.sayHello(); // 输出 "Hello, my name is Bob and I'm 30 years old."
在这个例子中,我们在 Person
构造函数的原型上添加了一个 sayHello
方法,这样所有由 Person
构造函数创建的对象都可以共享这个方法。
prototype
属性的作用是允许我们在构造函数的原型对象上添加方法和属性,使得这些方法和属性可以被构造函数创建的所有对象所共享。
02.原型链
原型链的概念:
原型链是由对象的原型组成的链状结构,每个对象都有一个原型,而原型又可以有自己的原型,依此类推,形成一条链。这条链的最顶端是 Object.prototype
。
原型链的查找过程:
当你访问一个对象的属性或方法时,JavaScript 引擎会首先在该对象上查找,然后沿着原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶端。
让我们通过一个例子来理解原型链:
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 在构造函数的原型上添加方法
Person.prototype.sayHello = function() {
console.log("Hello, my name is " + this.name + " and I'm " + this.age + " years old.");
};
// 创建对象
const person1 = new Person("Alice", 25);
// person1 对象的原型是 Person.prototype
// Person.prototype 的原型是 Object.prototype,形成了原型链
// 调用方法,JavaScript 引擎首先在 person1 上查找,然后沿着原型链向上查找
person1.sayHello(); // 输出 "Hello, my name is Alice and I'm 25 years old."
在这个例子中,person1
对象通过 new Person
创建,它的原型是 Person.prototype
。当调用 sayHello
方法时,JavaScript 引擎首先在 person1
对象上查找,找到了就执行,如果没有找到,就沿着原型链向上查找,直到找到为止。
原型链的继承:
原型链实现了对象之间的继承关系。子对象通过原型链继承父对象的属性和方法。
// 创建新的构造函数
function Student(name, age, grade) {
// 调用父构造函数,实现属性的继承
Person.call(this, name, age);
this.grade = grade;
}
// 使用 Object.create 建立原型链关系,实现方法的继承
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
// 在 Student.prototype 上添加新方法
Student.prototype.showGrade = function() {
console.log("I am in grade " + this.grade);
};
// 创建 Student 对象
const student1 = new Student("Bob", 15, 9);
// 继承了 Person 的属性和方法
student1.sayHello(); // 输出 "Hello, my name is Bob and I'm 15 years old."
// 新方法来自 Student.prototype
student1.showGrade(); // 输出 "I am in grade 9"
在这个例子中,我们创建了一个 Student
构造函数,通过 Object.create
建立了与 Person.prototype
的原型链关系。这样,Student
对象就继承了 Person
的属性和方法。
03.__proto__
属性
__proto__
属性是每个对象都具有的一个属性,它指向该对象的原型。
原型链的核心概念之一就是通过 __proto__
属性实现对象之间的原型关系。尽管在现代 JavaScript 中,更推荐使用 Object.getPrototypeOf()
来访问对象的原型,但了解 __proto__
的作用仍然有助于理解原型链。
让我们通过一个例子来理解 __proto__
属性:
// 构造函数
function Bird(name) {
this.name = name;
}
// 在构造函数的原型上添加方法
Bird.prototype.fly = function() {
console.log(this.name + " is flying!");
};
// 创建对象
const bird = new Bird("Sparrow");
// 使用 __proto__ 访问原型上的方法
bird.__proto__.fly(); // 输出 "Sparrow is flying!"
// 请注意,这是为了演示目的,实际上应该使用 Object.getPrototypeOf()
在这个例子中,我们创建了一个 Bird
构造函数,并在其原型上添加了一个 fly
方法。通过 bird.__proto__.fly()
,我们可以直接访问原型上的方法。
__proto__
的作用:
-
访问原型链: 通过
__proto__
属性,你可以直接访问对象的原型,查看其原型链上的属性和方法。 -
实现继承关系: 在对象创建过程中,
__proto__
被用于建立对象与构造函数的原型之间的连接,从而实现继承。
需要注意的是,在现代 JavaScript 中,推荐使用 Object.getPrototypeOf()
:
Object.getPrototypeOf(bird).fly(); // 输出 "Sparrow is flying!"
这是因为 __proto__
不是标准的 JavaScript 特性,而是由浏览器实现的。使用 Object.getPrototypeOf()
更具可移植性和规范性。
04.Object.create() 方法
Object.create()
是一个用于创建新对象的方法,它的原理是以指定的对象为原型创建一个新的对象。这个方法在实现原型链继承时非常有用。让我们通过一个例子来理解:
// 定义一个对象作为原型
const animal = {
makeSound: function() {
console.log("Some generic sound");
}
};
// 使用 Object.create() 创建新对象,其原型为 animal
const cat = Object.create(animal);
// cat 继承了 animal 的方法
cat.makeSound(); // 输出 "Some generic sound"
注意事项:
1.原型对象可以为空对象: 你可以将 Object.create(null)
作为原型对象,这样新创建的对象将没有任何继承的属性和方法,即原型链为空。
const emptyObj = Object.create(null);
console.log(emptyObj.toString); // 输出 undefined,因为没有继承 Object.prototype 的方法
2.使用第二个参数添加属性: 你还可以通过传递第二个参数,以及类似于 Object.defineProperty
的方式,添加新对象的属性。
const dog = Object.create(animal, {
breed: {
value: "Labrador",
writable: true,
enumerable: true
}
});
console.log(dog.breed); // 输出 "Labrador"
05.hasOwnProperty 方法
hasOwnProperty
是对象的原型链上的方法之一,用于检查对象自身是否包含指定的属性(而不是从原型链继承的属性)。这在遍历对象属性时很有用。让我们通过一个例子来理解:
// 构造函数
function Car(make, model) {
this.make = make;
this.model = model;
}
// 创建 Car 对象
const myCar = new Car("Toyota", "Camry");
// 添加属性到原型上
Car.prototype.year = 2022;
// 使用 hasOwnProperty 检查属性是否存在
console.log(myCar.hasOwnProperty("make")); // 输出 true,因为 make 是 myCar 自身的属性
console.log(myCar.hasOwnProperty("year")); // 输出 false,因为 year 是从原型继承的属性
在这个例子中,myCar
对象是通过 Car
构造函数创建的,它有自己的属性 make
和 model
,还从 Car.prototype
继承了 year
属性。使用 hasOwnProperty
方法,我们可以判断属性是对象自身的属性还是继承自原型链的属性。
注意事项:
1.hasOwnProperty
不检查原型链上的属性: hasOwnProperty
只检查对象自身的属性,不会查找原型链。如果你想检查对象的原型链上是否有某个属性,需要使用其他方法,比如 in
运算符。
console.log("year" in myCar); // 输出 true,因为 year 是从原型链上继承的属性
2.使用 Object.prototype.hasOwnProperty.call(obj, prop)
: 有时候,如果对象的原型上有一个名为 hasOwnProperty
的属性,直接调用 obj.hasOwnProperty(prop)
可能会导致错误。为了避免这种情况,可以使用 Object.prototype.hasOwnProperty.call(obj, prop)
。
const obj = Object.create({ hasOwnProperty: null });
// 下面的调用会抛出错误
// console.log(obj.hasOwnProperty("x"));
// 使用 Object.prototype.hasOwnProperty.call() 来调用
console.log(Object.prototype.hasOwnProperty.call(obj, "x"));