图解的方式带你掌握JS原型和原型链
什么是原型
在JavaScript中,每个对象都有一个原型(prototype),它是一个对象或null。原型是通过构造函数创建的对象的一个属性,它允许对象继承属性和方法。在JavaScript官方中是这样解释原型的:
在JavaScript中,原型是一个内部链接到另一个对象的引用。当试图访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript引擎会查找对象的原型链,逐级向上查找,直到找到相应的属性或方法,或者到达原型链的末端(即原型为
null)。
大家看到这里是不是脑瓜子有点嗡嗡的,没关系接下来我将用图解的方式带大家彻底理解JavaScript原型和原型链。
思考题
题一: 原型与原型链的基本应用
Person.prototype.lastName = '张'
Person.prototype.say = function () {
console.log('hello world')
}
function Person() {
this.name = '三'
}
let p1 = new Person() // p1隐式具有 lastName say
console.log(p1)
console.log(p1.lastName)
p1.say()
代码输出结果:

大家对输出的结果会不会感到疑惑?请看如下图解:

在这段代码中,首先创建了一个空对象obj。
接下来,定义了一个构造函数Person(),其中通过this.name = '三'将一个属性name赋值为字符串'三'。同时,在Person.prototype上定义了一个属性lastName,它的值为字符串'张',以及一个方法say(),它打印出字符串'hello world'。
然后,通过使用new关键字和构造函数Person实例化了一个新的对象p1。由于使用了构造函数,p1会自动具有构造函数Person的属性name,并且它的原型链上也包含了Person.prototype的属性lastName和方法say。
最后,通过console.log()打印出了对象p1,访问了p1的属性lastName和调用了p1的方法say()。结果分别是:
Person { name: '三' }
张
hello world
这表明p1继承了Person.prototype上的属性和方法,并且可以直接访问和使用它们。
在该例子中我们可以得出结论:
对象的隐式原型 === 创建它的构造函数的显式原型
js引擎查找属性时,会先查找对象显示具有的属性。找不到,再找对象的隐式原型(
__proto__)
题二: 工厂化函数对象
Car.prototype.name = 'su7'
Car.prototype.lang = 5000
Car.prototype.height = 1400
function Car(color, onwer) {
this.color = color
this.onwer = onwer
}
let car = new Car('black', 'me')
console.log(car)
console.log(car.name)
这段代码的功能是定义了一个构造函数Car()和一些其原型上的属性,然后使用new关键字和构造函数实例化一个名为car的新对象,并在控制台中打印出这个对象和它的name属性。
在构造函数Car()之外,通过Car.prototype给所有Car类型对象添加了三个属性,分别是name、lang、height,这些属性是所有Car对象公用的。在函数内部,使用this关键字给每个创建的汽车对象设置了颜色和车主属性。
然后,通过new关键字和构造函数Car实例化了一个新的对象car,传入了颜色值black和车主值me。由于car是通过Car构造函数创建的,因此它继承了Car.prototype上定义的所有属性和方法,包括name、lang和height。在这里,car对象的name属性被设置为'su7',因为name是在Car.prototype上定义的,而不是在构造函数内部定义的。
最后,通过console.log()打印出了car对象以及它的name属性,输出结果为:
Car {color: "black", onwer: "me"}
"su7"
可以看到,car对象的name属性为"su7",这是因为Car.prototype.name被设置为"su7"。
题三: 原型的修改问题
原型是函数上的一个属性,它定义了构造函数的对象的公共祖先。
构造函数new出来的对象会隐式继承到构造函数原型上的属性。
- 实例对象可以修改显式继承到的属性,但是无法修改隐式继承到的属性(原型上的)。
- 实例对象无法给原型新增属性
- 实例对象无法删除原型上的属性
Car.prototype.product = 'xiaomi'
function Car() {
this.name = 'su7'
}
let car = new Car()
car.name = '保时捷'
console.log(car)
// 为car 对象 添加了一个属性 ,隐式的product仍然存在
car.product = '宝马'
car.nickName = '小红'
console.log(car)
console.log(car.product)
console.log(car.__proto__.product)
运行结果:

详解如下:
初始化原型链属性
Car.prototype.product = 'xiaomi';
- 这行代码给
Car构造函数的原型对象(Car.prototype)添加了一个属性product,值为'xiaomi'。这意味着所有通过Car构造函数创建的实例将共享这个product属性,除非它们自己也定义了同名属性。
定义构造函数
function Car() {
this.name = 'su7';
}
Car是一个构造函数,用于创建新的对象实例。在这个构造函数内部,通过this.name = 'su7';为每个新创建的实例设置了一个name属性,初始值为'su7'。
创建对象实例
let car = new Car();
- 使用
new操作符调用Car构造函数,创建了一个新的Car实例,并将其引用存储在变量car中。此时,car拥有自己的属性name: 'su7',并且通过原型链继承了product: 'xiaomi'。
修改对象实例的自有属性
car.name = '保时捷';
- 这行代码修改了
car实例的name属性,将其值从'su7'改为'保时捷'。
输出对象信息
console.log(car);
- 此时打印
car,会看到它现在的状态:name属性为'保时捷',并且通过原型链可以访问到product: 'xiaomi'。
覆盖原型链属性 & 添加新属性
car.product = '宝马';
car.nickName = '小红';
car.product = '宝马';这行代码在car实例上创建了一个新的自有属性product,值为'宝马',这实际上隐藏了原型链上的同名属性。car.nickName = '小红';添加了一个新的自有属性nickName,值为'小红'。
再次输出对象信息及属性值
console.log(car);
console.log(car.product);
- 这两次打印显示了
car当前的状态:name为'保时捷',自有属性product为'宝马'(覆盖了原型链上的值),以及新增的nickName: '小红'。
访问原型链上的属性
console.log(car.__proto__.product);
- 这行代码直接通过
__proto__访问了car对象的原型,并打印出原型上的product属性,其值为'xiaomi'。尽管car实例上有一个同名的自有属性,但这条语句明确指定了要访问原型链上的属性,因此不受实例属性的影响。
题四: 原型链的综合应用
GrandFather.prototype.say = function () {
console.log('haha');
}
function GrandFather() {
this.age = 60
this.like = 'drink'
}
Father.prototype = new GrandFather()
function Father() {
this.age = 40
this.fortune = {
card: 'visa'
}
}
Son.prototype = new Father() // {age: 40, fortune: {xxx}}
function Son() {
this.age = 18
}
let son = new Son()
console.log(son.age); // 18
console.log(son.fortune);
console.log(son.like);
console.log(son.say());
上述代码展示了原型链的综合应用,初看代码大家是不是感到很迷惑,这么多重原型的继承关系怎么看得过来。😭请看以下图解你就会对原型链和原型的继承问题豁然开朗。
图解

代码详解
原型方法定义
GrandFather.prototype.say = function () {
console.log('haha');
}
- 为
GrandFather构造函数的原型对象添加了一个方法say,所有通过GrandFather构造函数创建的实例及其子类实例都可以访问到这个方法。
构造函数定义
function GrandFather() {
this.age = 60;
this.like = 'drink';
}
GrandFather构造函数定义了两个属性:age和like。
继承关系建立
Father.prototype = new GrandFather();
- 通过让
Father的原型对象指向一个GrandFather的新实例,实现了从GrandFather到Father的继承。这样,所有Father的实例都将继承GrandFather的属性和方法。
function Father() {
this.age = 40;
this.fortune = {
card: 'visa'
};
}
Father构造函数定义了两个属性:覆盖了从GrandFather继承来的age属性,以及新增了一个fortune属性。
Son.prototype = new Father();
- 同理,
Son的原型对象被设置为一个Father的新实例,实现了从Father到Son的继承。
function Son() {
this.age = 18;
}
Son构造函数定义了一个属性age,覆盖了从父类继承来的同名属性。
实例化与属性访问
let son = new Son();
- 创建了一个
Son的实例son。
console.log(son.age); // 18
- 打印出
son的age属性,值为18,因为实例属性优先于原型链上的属性。
console.log(son.fortune);
- 打印出
son的fortune属性,说明它继承自Father。
console.log(son.like);
- 打印出
son的like属性,值为'drink',这是从GrandFather继承过来的。
console.log(son.say());
- 调用
son的say方法,输出'haha',表明方法也能通过原型链被正确继承。
总结
这段代码通过原型链展示了多级继承的实现方式,从GrandFather到Father再到Son,每个构造函数定义或覆盖了属性,同时通过原型链传递了方法。最终的Son实例能够访问到所有层级上的属性和方法,体现了JavaScript中基于原型的继承机制。
运行结果

总结
1.原型:函数都有prototype属性,称之为原型,也称为原型对象
原型可以放一些属性和方法,共享给实例对象使用
原型可以做继承
2.原型链:对象都有 proto 属性,这个属性指向它的原型对象,原型对象也是对象,也有 proto 属性,指向原型对象的原型对象,这样一层一层形成的链式结构称为原型链,最顶层找不到则返回 null
关于原型与原型链的分析,主要可以用以下两张图来体现:


本篇文章就到此为止啦,希望通过这篇文章能对你了解JavaScript中原型和原型链有所帮助,本人水平有限难免会有纰漏,欢迎大家指正。如觉得这篇文章对你有帮助的话,欢迎点赞收藏加关注,感谢支持🌹🌹。

转载自:https://juejin.cn/post/7369470562144288778