本文主要讲解原型Prototype与原型链Prototype Chain,通过一个案例了解相关知识。
1.对原型、原型链的理解
(1)原型(Prototype)
定义:原型是JavaScript中对象的一个内置属性([[Prototype]]
,通常通过__proto__
访问,但建议使用Object.getPrototypeOf()
),每个构造函数都有一个prototype
属性,它包含了所有通过该函数创建的实例共享的属性和方法。
在下面例子中,Person.prototype
是Person
函数的原型对象,它定义了sayHello
方法。所有通过new Person()
创建的实例都会继承这个方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
};
let person1 = new Person('Alice', 30);
let person2 = new Person('Bob', 25);
person1.sayHello(); // 输出: Hello, my name is Alice
person2.sayHello(); // 输出: Hello, my name is Bob
console.log(Object.getPrototypeOf(person1)); // 获取对象的原型
(2)原型链(Prototype Chain)
定义:原型链是通过对象之间的原型关系实现继承的一种机制,它定义了对象之间的继承关系。它是由多个原型对象通过__proto__
属性(在ES6中推荐使用Object.getPrototypeOf()
方法)连接而成的一种链式结构。当一个对象需要访问某个属性或方法时,如果该对象本身不包含该属性或方法,JavaScript会沿着原型链向上查找,直到找到该属性或方法为止。
原型链的顶端是Object.prototype
,这是所有JavaScript对象最终都会继承的原型对象。
继续上面的例子,当调用person1.sayHello()
时,JavaScript会首先查找person1
对象上是否有sayHello
方法。如果没有找到,它会查找person1
的原型(即Person.prototype
)上是否有该方法。因为Person.prototype
上定义了sayHello
方法,所以该方法会被调用。
综上所述:在JavaScript中,原型和原型链是实现基于原型的继承的重要基础。通过原型和原型链,我们可以实现对象之间的属性和方法的共享和继承。
2.原型修改、重写
(1)原型修改
修改原型意味着向现有的原型对象添加新的属性或方法,或者修改现有属性或方法的值,这会影响到所有通过该构造函数创建的实例。
// 接着上面的例子
// 修改原型
Person.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
// 修改后的原型会影响之前的实例
person1.greet(); // 输出: Hi, I am Alice
// 创建一个新实例也会继承修改后的原型
let person3 = new Person('哈哈');
person3.greet(); // 输出: Hi, I am 哈哈
(2)原型重写
重写原型意味着你完全替换一个对象的原型对象,而不是仅仅修改它。这可以通过将新的对象直接赋值给构造函数的prototype
属性来实现。
注意:重写原型时要特别小心,因为这可能会破坏现有的实例。
// 继续上面例子
// 原型重写
Person.prototype = {
greet: function() {
console.log('I am ' + this.name + ',' + this.age + 'years old.');
},
// 添加constructor属性指向构造函数,以保持一致性
constructor: Person
};
// 注意:person1 仍然引用旧的原型对象,因为它是在原型重写之前创建的
person1.greet(); // Hi, I am Alice
// 但新创建的实例会继承新的原型
let person4 = new Person('Bob',18);
person4.greet(); // 输出: I am Bob,18years old.
3.原型链指向
(1)对象的__proto__
属性
每个对象都有一个__proto__
属性,它指向该对象的原型对象,这是实现原型链的基础。
(2)原型对象的constructor
属性
原型对象通常会有一个constructor
属性,它指向创建该原型对象的构造函数,这有助于在原型链中追踪对象的创建来源。
(3)原型链的终点
原型链最终会指向Object.prototype
,这是JavaScript中所有对象的最终原型。Object.prototype
本身没有原型,即Object.prototype.__proto__
为null
标志着原型链的结束。
(4)属性/方法的查找
当尝试访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript会沿着原型链向上查找,直到找到该属性、方法或到达原型链的末端(即Object.prototype
)。如果在Object.prototype
中也没有找到,则返回undefined
(对于属性)或抛出错误(对于方法)。
// 原型链指向
console.log(person4.__proto__);
console.log(person4.__proto__.__proto__);
console.log(person4.__proto__.__proto__.__proto__);// null
console.log(person4.constructor);
console.log(Object.getPrototypeOf(person4.__proto__.__proto__));// null
4.原型链的终点
上面提到过,原型链终点是Object.prototype.__proto__,当Object.prototype.__proto__
为null
标志着原型链的结束。
console.log(Object.getPrototypeOf(person4.__proto__.__proto__));// null
5.判断属性是否属于原型链上的属性
使用hasOwnProperty()方法判断是否属于原型链的属性。
// 判断属性是否属于原型链的属性
console.log(person4.hasOwnProperty('name'));// true
console.log(person4.hasOwnProperty('age'));// true
console.log(person4.hasOwnProperty('greet'));// false
console.log(person4.hasOwnProperty('constructor'));// false
console.log(person4.__proto__.hasOwnProperty('greet'));// true
console.log(person4.__proto__.__proto__.hasOwnProperty('constructor'));// true
总结:
原型是包含共享属性和方法的对象,是对象之间共享功能的桥梁。
原型链是对象之间通过原型连接起来的链式结构,它决定了对象如何查找属性和方法。
原型和原型链在JavaScript中是实现继承、方法共享和扩展对象功能的关键机制。它们的应用贯穿了JavaScript面向对象编程的各个方面。