探讨一下js原型和原型链

80 阅读3分钟

一、原型(Prototype)

在JavaScript中,每个函数都有一个prototype属性(除了nullundefined,它们不是函数),这个属性指向一个对象,称为原型对象。原型对象的作用是为通过该函数创建的实例对象提供共享的属性和方法。

1. 构造函数与原型对象

当使用构造函数创建对象时,新创建的对象会自动获得一个内部属性[[Prototype]](在现代JavaScript中可以通过__proto__访问,但__proto__不建议在生产环境中使用,应优先使用Object.getPrototypeOf()Object.setPrototypeOf()方法),这个内部属性指向构造函数的原型对象。

function Person(name) { 
    this.name = name; 
} 
// Person函数有一个prototype属性,指向原型对象 
Person.prototype.sayHello = function () { 
    console.log(`Hello, I'm ${this.name}`); 
}; 
let person1 = new Person('Alice'); // person1的__proto__指向Person函数的原型对象 
console.log(person1.__proto__ === Person.prototype); // true 
person1.sayHello(); // Hello, I'm Alice 

在上述例子中,Person是一个构造函数,它的prototype属性所指向的原型对象上定义了sayHello方法。当使用new Person('Alice')创建person1实例时,person1[[Prototype]](即__proto__)就指向了Person.prototype。所以person1可以访问到Person.prototype上的sayHello方法。

2. 原型对象的属性共享

原型对象的重要特性是它的属性和方法会被所有通过该构造函数创建的实例对象共享。这意味着,当修改原型对象上的属性或方法时,所有相关实例对象都会受到影响。

function Animal(species) { 
this.species = species; 
} 
Animal.prototype.eat = function () {
console.log(`${this.species} is eating.`); 
}; 
let dog = new Animal('Dog'); 
let cat = new Animal('Cat'); // 修改原型对象上的方法 
Animal.prototype.eat = function () { 
console.log(`${this.species} is enjoying food.`); 
}; 
dog.eat(); // Dog is enjoying food. 
cat.eat(); // Cat is enjoying food. 

这里,通过修改Animal.prototype上的eat方法,dogcat实例对象的eat行为都发生了改变,体现了属性共享的特性。

二、原型链(Prototype Chain)

原型链是JavaScript实现继承和属性查找的重要机制。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(Object.prototype,其[[Prototype]]null)。

1. 原型链的构建

每个对象都有[[Prototype]]属性,它指向其原型对象。而原型对象本身也是一个对象,也有自己的[[Prototype]]属性,这样就形成了一条链式结构。

function Vehicle() { 
this.wheels = 4; 
} 
Vehicle.prototype.move = function () { 
console.log('Moving forward.'); 
}; 
function Car(model) { 
this.model = model; 
Vehicle.call(this); 
} 
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car; 
let myCar = new Car('Toyota'); // 原型链:myCar -> Car.prototype -> Vehicle.prototype -> Object.prototype -> null 
console.log(myCar.wheels); // 4,从Vehicle构造函数继承 
myCar.move(); // Moving forward.,从Vehicle.prototype继承 

在这个例子中,myCarCar的实例,Car.prototype通过Object.create(Vehicle.prototype)创建,使得Car.prototype[[Prototype]]指向Vehicle.prototype,而Vehicle.prototype[[Prototype]]指向Object.prototypeObject.prototype[[Prototype]]null,从而构建了原型链。

2. 属性查找过程

当访问myCar的属性或方法时,JavaScript首先在myCar自身查找,如果没有找到,则沿着原型链到Car.prototype查找,接着到Vehicle.prototype查找,最后到Object.prototype查找。如果直到Object.prototype都没有找到,返回undefined

function Rectangle(width, height) {
this.width = width; 
this.height = height; 
} 
Rectangle.prototype.getArea = function () { 
return this.width * this.height; 
}; 
let rect = new Rectangle(5, 3); // 查找getArea方法 // 1. 首先在rect自身查找,没有找到 // 2. 到Rectangle.prototype查找,找到并执行 
console.log(rect.getArea()); // 15 

三、总结

原型和原型链是JavaScript面向对象编程的核心概念。原型通过共享属性和方法,减少了内存消耗,提高了代码的复用性。原型链则实现了对象的继承机制,使得对象能够访问其原型对象以及原型对象的原型对象上的属性和方法。深入理解原型和原型链对于编写高效、可维护的JavaScript代码以及理解JavaScript的运行机制至关重要。同时,在使用过程中要注意避免原型污染等问题,确保代码的健壮性。