likes
comments
collection
share

图解的方式带你掌握JS原型和原型链

作者站长头像
站长
· 阅读数 24

什么是原型

在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()

代码输出结果:

图解的方式带你掌握JS原型和原型链

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

图解的方式带你掌握JS原型和原型链

在这段代码中,首先创建了一个空对象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类型对象添加了三个属性,分别是namelangheight,这些属性是所有Car对象公用的。在函数内部,使用this关键字给每个创建的汽车对象设置了颜色和车主属性。

然后,通过new关键字和构造函数Car实例化了一个新的对象car,传入了颜色值black和车主值me。由于car是通过Car构造函数创建的,因此它继承了Car.prototype上定义的所有属性和方法,包括namelangheight。在这里,car对象的name属性被设置为'su7',因为name是在Car.prototype上定义的,而不是在构造函数内部定义的。

最后,通过console.log()打印出了car对象以及它的name属性,输出结果为:

Car {color: "black", onwer: "me"}
"su7"

可以看到,car对象的name属性为"su7",这是因为Car.prototype.name被设置为"su7"

题三: 原型的修改问题

原型是函数上的一个属性,它定义了构造函数的对象的公共祖先。

构造函数new出来的对象会隐式继承到构造函数原型上的属性。

  1. 实例对象可以修改显式继承到的属性,但是无法修改隐式继承到的属性(原型上的)。
  2. 实例对象无法给原型新增属性
  3. 实例对象无法删除原型上的属性
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)

运行结果:

图解的方式带你掌握JS原型和原型链

详解如下:

初始化原型链属性

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()); 

上述代码展示了原型链的综合应用,初看代码大家是不是感到很迷惑,这么多重原型的继承关系怎么看得过来。😭请看以下图解你就会对原型链和原型的继承问题豁然开朗。

图解

图解的方式带你掌握JS原型和原型链

代码详解

原型方法定义


GrandFather.prototype.say = function () {
   console.log('haha');
}
  • GrandFather构造函数的原型对象添加了一个方法say,所有通过GrandFather构造函数创建的实例及其子类实例都可以访问到这个方法。

构造函数定义


function GrandFather() {
    this.age = 60;
    this.like = 'drink';
}
  • GrandFather构造函数定义了两个属性:agelike

继承关系建立

Father.prototype = new GrandFather();
  • 通过让Father的原型对象指向一个GrandFather的新实例,实现了从GrandFatherFather的继承。这样,所有Father的实例都将继承GrandFather的属性和方法。
function Father() {
    this.age = 40;
    this.fortune = {
        card: 'visa'
    };
}
  • Father构造函数定义了两个属性:覆盖了从GrandFather继承来的age属性,以及新增了一个fortune属性。

Son.prototype = new Father();
  • 同理,Son的原型对象被设置为一个Father的新实例,实现了从FatherSon的继承。
function Son() {
    this.age = 18;
}
  • Son构造函数定义了一个属性age,覆盖了从父类继承来的同名属性。

实例化与属性访问

let son = new Son();
  • 创建了一个Son的实例son
console.log(son.age); // 18
  • 打印出sonage属性,值为18,因为实例属性优先于原型链上的属性。
console.log(son.fortune);
  • 打印出sonfortune属性,说明它继承自Father
console.log(son.like);
  • 打印出sonlike属性,值为'drink',这是从GrandFather继承过来的。
console.log(son.say());
  • 调用sonsay方法,输出'haha',表明方法也能通过原型链被正确继承。

总结

这段代码通过原型链展示了多级继承的实现方式,从GrandFatherFather再到Son,每个构造函数定义或覆盖了属性,同时通过原型链传递了方法。最终的Son实例能够访问到所有层级上的属性和方法,体现了JavaScript中基于原型的继承机制。

运行结果

图解的方式带你掌握JS原型和原型链

总结

1.原型:函数都有prototype属性,称之为原型,也称为原型对象

原型可以放一些属性和方法,共享给实例对象使用

原型可以做继承

2.原型链:对象都有 proto 属性,这个属性指向它的原型对象,原型对象也是对象,也有 proto 属性,指向原型对象的原型对象,这样一层一层形成的链式结构称为原型链,最顶层找不到则返回 null

关于原型与原型链的分析,主要可以用以下两张图来体现:

图解的方式带你掌握JS原型和原型链

图解的方式带你掌握JS原型和原型链

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

图解的方式带你掌握JS原型和原型链

转载自:https://juejin.cn/post/7369470562144288778
评论
请登录