关于 js 的原型链

面试时,经常有这个问题:

如何理解原型链

原型与原型链

先看这样一段代码:

let obj = {}
obj.__proto__.haha = 'gogo'
console.log(obj.haha) // "gogo"

运行一下上面的代码,输出结果为 gogo

有几个疑问:

  • obj 哪来的 __proto__属性?
  • 为什么添加到 __proto__上的属性,可以直接通过 obj 拿到?
第一个问题

js 中每个对象都有一个“原型”
原型一般可以通过 __proto__访问到

let obj = {}
console.log(obj.__proto__)

原型,也是一个对象
就像每个“人”都有一个“爸爸”,但“爸爸”也是一个“人”
“爸爸”除了是某个人的爸爸外,与其他人并没有本质的区别
爸爸,也是普通人
类似的,“原型”是一个普通的对象
爸爸也有他的爸爸,原型也有它的原型

第二个问题

不同的是,人不可以随便拿爸爸的东西,而对象可以随便拿原型里的东西(很多人并不懂这个,我说的是前半句)

比如,当你向一个对象,索要一个属性时
如果这个对象没有你要的属性,它就会让它的原型(爸爸)给你
如果它爸也没有,那它爸就会找它爸的爸:

let obj = {
  __proto__: {
    __proto__: {
      haha: 'gogo'
    }
  }
}
console.log(obj.haha) // "gogo"

如果这样写:

let 爷爷 = {
  haha: 'gogo'
}
let 爸爸 = {
  __proto__: 爷爷
}
let obj = {
  __proto__: 爸爸
}
console.log(obj.haha) // "gogo"

obj -> 爸爸 -> 爷爷像不像一条链子呢?
这就是原型链

以下内容,需要先了解 js 中的类,本文不详细介绍

prototype

有这样一句话:

类是对象的模板

你与我,都是人,“人”是类,是模板
你与我,都属于“人”类,有很多共性:

  • 有一张嘴
  • 有两条腿
  • 会吃饭
  • 会睡觉

这些共性是人类共有的

也有差异,比如我的名字叫 X,你的名字叫 Y
这些不同点,是对象“私有”的

看一段 js 创建对象的代码(注意注释部分):

function Person(name) {
  this.name = name
}
Person.prototype.吃饭 = function() {
  console.log('吃吃吃')
}
Person.prototype.睡觉 = function() {
  console.log('睡睡睡')
}

let= new Person('我').吃饭() // "吃吃吃"
console.log(Person.prototype ==.__proto__) // true
// 可以看出,在实例化的过程中,类的 prototype 成为对象的原型  

let= new Person('你').睡觉() // "睡睡睡"
console.log(.__proto__ ==.__proto__) // true
// 同一类的多个实例(对象),共享一个原型?.__proto__.吃饭 = function() {
  console.log('再吃一点')
}
console.log(.吃饭 ==.吃饭) // true.吃饭() // "再吃一点"
// 没错,同一类的多个实例,共享一个原型

类比于人类社会,就是:
你的兄弟姐妹和你“共享”一个爸爸
当你的爸爸烫了个头时,你弟弟的爸爸也烫头。这个过程中,不是两个爸爸同时烫头,而是本来就一个爸爸

重要结论:

  • 实例化的过程中(也就是“当 new 一个对象的时候”),类的 prototype 成为对象的原型
  • 同一个类的多个实例(也就是“对象”),共享一个原型

学以致用

原型是 js 底层的东西
不懂原型,几乎不影响工作

类似“原型有什么用”的问题,就像“砖块(或水泥)对盖楼有什么用”
其实在写代码的过程中,几乎不会用到原型的知识
但是如果遇到了问题、出现了 bug、性能优化时
底层的知识是肯定有大用途的

JavaScript 中的原型链是一个非常重要的概念,理解它可以让你更好地掌握 JavaScript 的工作原理对象之间的关系。接下来我将为你详细解释什么是原型链以及它是如何工作的。 ### 什么是原型链? 每个 JavaScript 对象都有一个内部属性称为 `[[Prototype]]`(也叫做“__proto__”,注意这不是标准属性),这个属性指向另一个对象——即它的“原型”。而该原型对象本身也有自己的原型...以此类推形成了一条链条结构,这条链条就是所谓的**原型链**。当访问对象的某个属性时,如果找不到的话就会沿着原型链向上查找,直到找到对应的属性或到达最顶端的对象 `null`(因为 `Object.prototype.__proto__ === null`)。因此可以说,所有函数实例都共享同一个构造函数所关联的原型对象。 #### 构造函数与 prototypeJavaScript 中,当你定义一个新的函数时,默认会给它添加一个名为 `prototype` 的公共属性,并且这个属性会自动成为一个新对象(我们称作原型对象)。如果你使用这个函数去创建实例化对象 (`new`) ,那么每一个生成的新对象都会有一个隐含指针指向原始函数的 `prototype` 属性: ```javascript function Person(name) { this.name = name; } // 这里给Person设置了prototype属性 Person.prototype.sayHello = function() { console.log(`Hi, I'm ${this.name}`); }; let p1 = new Person('Alice'); console.log(p1 instanceof Person); // true p1.sayHello(); // Hi, I'm Alice ``` 在这个例子中,`p1` 实际上是通过其内部链接到 `Person.prototype` 来获取方法 `sayHello()` 的引用。这也说明为什么我们可以直接修改原型上的成员而不必担心会影响已经存在的对象;同样地,只要更改了某一处原型内的内容,所有的子节点都能立即看到更新后的结果。 ### 原型链的实际应用 利用好原型机制能够带来很多好处,比如减少内存占用(只需要存储一次共有功能)、方便拓展已有库等。此外还有一些常见的模式如寄生组合式继承、工厂模式等等都可以借助原型完成高效的代码复用。然而值得注意的是滥用这种特性也可能导致性能问题甚至难以调试的问题发生,所以在日常编码实践中需要谨慎对待。 --- ### 示例代码展示原型链的关系: 假设我们现在有两个自定义类型 A B,其中 B 继承自 A: ```javascript function Animal(type){ this.type = type; } Animal.prototype.eat = function(){ return `${this.type} is eating.`; }; function Dog(name){ Animal.apply(this, arguments); this.name = name; } Dog.prototype = Object.create(Animal.prototype); // 创建新的原型对象 Dog.prototype.constructor = Dog; // 恢复 constructor 引用 Dog.prototype.bark = function(){ // 添加特有行为 return `${this.name}: Woof!`; }; const myDog = new Dog("Max"); console.log(myDog.eat()); // 输出 "is eating." console.log(myDog.bark()); // 输出 "Max: Woof!" ``` 这里可以看到我们不仅可以在 `myDog` 上调用来自 `Dog.prototype` 的 `.bark()` 方法,也可以顺利调用了来源于更高级别原型的方法 `.eat()` 。这就是典型的原型链工作机制! --- 为了帮助你进一步理解探索关于 JavaScript 原型链的知识,请考虑以下几个相关话题进行学习:
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值