在 JavaScript 里,每个对象都有一个“隐藏的小伙伴”,这个“小伙伴”就是原型。可以把原型想象成一个模板或者一个仓库,对象能从它这个“小伙伴”那里借用一些属性和方法。当你访问一个对象的某个属性或方法时,JavaScript 会先在这个对象本身找,如果没找到,就会去它的原型里找。
// 定义一个构造函数
function Person(name) {
this.name = name;
}
// 给 Person 的原型添加一个方法
Person.prototype.sayHello = function() {
console.log('你好,我是'+ this.name);
};
// 创建一个 Person 对象
let person1 = new Person('张三');
// 调用对象本身没有,但原型上有的方法
person1.sayHello();
在上面的代码中,Person
是一个构造函数,Person.prototype
就是通过 Person
构造函数创建出来的对象的原型。我们给 Person.prototype
添加了一个 sayHello
方法,那么通过 new Person()
创建的对象(如 person1
)就可以调用这个方法,就好像对象从原型这个“仓库”里借到了 sayHello
方法。
原型链就像是一个长长的“接力棒链条”。每个对象都有自己的原型,而这个原型本身也是一个对象,它也有自己的原型,以此类推,一直到最顶层的原型(Object.prototype
),这就形成了一个链条。当你访问一个对象的属性或方法时,JavaScript 会沿着这个链条一级一级地往上找,直到找到或者到链条的顶端(Object.prototype
)还没找到就返回 undefined
。
// 定义一个 Animal 构造函数
function Animal() {
this.type = '动物';
}
// 给 Animal 的原型添加一个方法
Animal.prototype.getInfo = function() {
console.log('这是一只'+ this.type);
};
// 定义一个 Dog 构造函数,继承自 Animal
function Dog(name) {
this.name = name;
}
// 设置 Dog 的原型为 Animal 的一个实例
Dog.prototype = new Animal();
// 给 Dog 的原型添加一个方法
Dog.prototype.bark = function() {
console.log(this.name + '汪汪叫');
};
// 创建一个 Dog 对象
let dog1 = new Dog('旺财');
// 调用 Dog 本身没有,但原型链上有的方法
dog1.getInfo();
dog1.bark();
在这个例子中,dog1
是 Dog
的实例,Dog.prototype
是 Animal
的实例,Animal.prototype
又有自己的原型,最终会指向 Object.prototype
。当我们调用 dog1.getInfo()
时,dog1
本身没有 getInfo
方法,JavaScript 就会去 Dog.prototype
(也就是 Animal
的实例)里找,找到了就执行;当调用 dog1.bark()
时,直接在 Dog.prototype
里找到了这个方法。这就是原型链在起作用,它让对象可以在不同层级的原型中查找属性和方法。
在 JavaScript 里,继承就是一个对象能够使用另一个对象的属性和方法。原型链就像是一条“传承线”,让对象之间可以传承特性。就好比儿子可以继承爸爸的一些本事和财产。
// 定义一个动物构造函数
function Animal() {
this.eat = function() {
console.log('动物会吃东西');
};
}
// 定义一个狗构造函数
function Dog() {
this.bark = function() {
console.log('狗会汪汪叫');
};
}
// 让 Dog 继承 Animal 的特性
Dog.prototype = new Animal();
// 创建一个狗的实例
let myDog = new Dog();
// 调用继承来的方法
myDog.eat();
// 调用自己的方法
myDog.bark();
在这个例子中,Dog
通过原型链继承了 Animal
的 eat
方法。当我们创建 myDog
这个狗的实例后,它既可以调用自己的 bark
方法,也能调用从 Animal
那里继承来的 eat
方法。
代码复用就是避免重复写相同的代码。原型链可以让多个对象共享同一个原型上的属性和方法,就好像大家都去一个公共仓库里拿东西用,而不用每个人都自己准备一份。
// 定义一个形状构造函数
function Shape() {
this.draw = function() {
console.log('绘制形状');
};
}
// 定义圆形构造函数
function Circle() {}
// 让圆形继承形状的特性
Circle.prototype = new Shape();
// 定义方形构造函数
function Square() {}
// 让方形继承形状的特性
Square.prototype = new Shape();
// 创建圆形和方形的实例
let circle = new Circle();
let square = new Square();
// 圆形和方形都可以使用 draw 方法
circle.draw();
square.draw();
这里 Circle
和 Square
都继承了 Shape
原型上的 draw
方法,不用在 Circle
和 Square
里分别写 draw
方法的代码,实现了代码复用。
原型链可以给对象提供默认的属性和方法。当对象本身没有这些属性和方法时,就会去原型链上找默认的。这就像你去超市买东西,如果自己没带购物袋,超市会给你提供一个默认的购物袋。
// 定义一个汽车构造函数
function Car() {}
// 给汽车的原型设置默认属性
Car.prototype.color = '白色';
Car.prototype.wheels = 4;
// 创建一辆汽车实例
let myCar = new Car();
// 访问默认属性
console.log(myCar.color);
console.log(myCar.wheels);
myCar
这个实例本身没有定义 color
和 wheels
属性,但通过原型链可以使用 Car
原型上设置的默认值。
原型链具有动态特性,你可以在程序运行的时候修改原型上的属性和方法,这样所有继承自这个原型的对象都会受到影响。这就好比你修改了公共仓库里的东西,所有从这个仓库拿东西的人都会拿到修改后的东西。
// 定义一个人构造函数
function Person(name) {
this.name = name;
}
// 给人的原型添加一个方法
Person.prototype.sayHello = function() {
console.log('你好,我是'+ this.name);
};
// 创建一个人的实例
let person = new Person('张三');
// 调用 sayHello 方法
person.sayHello();
// 在运行时修改原型上的方法
Person.prototype.sayHello = function() {
console.log('哈喽呀,我是'+ this.name);
};
// 再次调用 sayHello 方法,行为已改变
person.sayHello();
我们先让 person
调用 sayHello
方法,然后在运行时修改了 Person
原型上的 sayHello
方法,再次调用时,person
执行的就是修改后的方法,体现了原型链动态修改对象行为的作用。