ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JavaScript 的原型与继承——对象之间是怎么扯上关系的

JavaScript 的原型与继承——对象之间是怎么扯上关系的 引言如果说作用域和闭包是 JS 的空间问题那原型和继承就是 JS 的血缘问题。JS 没有传统面向对象语言里的类继承体系它用的是另一套逻辑原型链。这套逻辑让很多人初学时一头雾水但一旦想通你会发现它其实比类继承更灵活、更简洁。这篇文章不带代码把原型和继承讲清楚。一、从对象说起JS 里几乎所有东西都是对象。对象就是一堆键值对的集合你可以往里加属性、加方法。但问题来了如果你创建了一百个对象它们都需要一个相同的方法难道要重复写一百遍吗显然不合理。解决方案是让这些对象共享同一个方法。怎么共享这就引出了原型。二、原型是什么每个对象在创建时都会和一个幕后对象关联起来。这个幕后对象就是它的原型。当你访问一个对象的某个属性时如果它自己身上没有系统就会去它的原型上找。原型上还没有就继续去原型的原型上找一层层往上直到找到或者到达尽头。这条由原型串起来的链条就叫原型链。可以把原型想象成家谱。你想找一个亲戚先看自己家没有就问父母父母没有就问祖父母一代代往上追溯。原型链就是这个追溯过程。三、原型链的终点原型链不会无限延伸它有终点。最顶层的那个原型对象它的原型是空表示到此为止。这个顶层原型身上定义了所有对象都能用的通用方法比如判断属性是否存在、转成字符串等等。你平时能直接调用这些方法就是因为它们在原型链的顶端等着你。理解这一点很重要你写的对象虽然简单但它背后连着一条完整的链继承了一大堆通用能力。四、构造函数与原型的关系在 JS 里创建一个对象常见的方式是用构造函数。构造函数本质上就是一个普通函数只是约定用大写字母开头并且配合一个特殊关键字来调用。每个构造函数身上都有一个属性指向一个对象这个对象就是用它创建出来的实例的原型。反过来每个实例身上也有一个隐藏属性指向它的原型。于是形成这样一种关系构造函数造出实例实例通过隐藏链接找到原型原型又关联回构造函数。三者环环相扣。当你给构造函数的原型添加方法时所有由它创建的实例都能立刻用上这个方法。这就是共享的实现方式。五、继承是怎么实现的传统语言里继承靠类。JS 早期没有类继承靠原型链。思路是让子类型的原型指向父类型的一个实例。这样子类型的实例在查找属性时找不到就去子类型原型找再找不到就顺着链条去父类型实例找进而找到父类型的原型。一层层串下去就实现了继承的效果。子类型可以使用父类型的所有方法还可以添加自己特有的方法甚至可以覆盖父类型的方法。这种方式非常灵活但也容易写错。链条搭得不对就会出现方法找不到、或者意外覆盖的问题。六、class 语法糖后来JS 引入了 class 关键字。看起来和传统语言的类很像写起来也更清晰。但要注意class 只是语法糖底层仍然是原型链。它没有引入新的继承机制只是把原来繁琐的原型操作包装了一层更好看的皮。理解这一点非常关键。如果你只学 class 语法遇到原型相关的问题就会懵如果你懂了原型链再看 class就会发现它每一步在做什么都一目了然。七、原型带来的好处与陷阱好处很明显方法共享节省内存动态扩展随时可以给原型加方法所有实例立即生效继承灵活可以构建复杂的对象体系。陷阱也不少。原型上的属性是共享的如果某个实例修改了原型上的引用类型属性其他实例也会受影响这是经典误区。另外原型链过长会影响查找性能。还有判断属性到底是自己的还是继承来的需要用专门的方法不能想当然。八、总结原型和继承是 JS 对象体系的骨架。对象通过原型共享能力通过原型链实现继承class 只是这层机制的外衣。理解它你才能真正看懂 JS 的对象行为也才能在用框架、读源码时不被各种为什么这个方法能用的问题卡住。这是 JS 进阶的另一块基石。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进