proto、prototype和继承在JavaScript中的实际工作原理
你是否曾想过为什么JavaScript中的所有东西都表现得像对象?或者继承在幕后是如何实际工作的?__proto__和prototype之间有什么区别?
字符串方法之谜
让我们从一个简单的例子开始:
|
|
声明此变量后,我们可以使用字符串方法:
|
|
这看起来很正常,但等等——有些不同寻常的事情正在发生。我们在字符串原始类型上使用点符号,但原始类型通常不是对象。
答案在于理解JavaScript如何处理原始类型和原型。
对象的内部工作原理
当你在JavaScript中创建这样的对象时:
|
|
JavaScript在幕后做了一些有趣的事情。它会自动向你的对象添加一个名为__proto__的隐藏属性。此属性指向Object.prototype,即内置Object类的原型。
Object.prototype也有一个__proto__吗?是的,但它的值为null。这是因为Object.prototype位于原型链的顶部,不继承任何其他东西。
让我们看一个更复杂的例子:
|
|
我们可以从info3访问fName1吗?
|
|
是的,我们可以!
理解原型链
当你尝试访问对象上的属性时,JavaScript遵循特定的查找过程:
- 首先,它在对象本身(基础对象)中搜索属性
- 如果在那里找不到,它会在对象的__proto__中查找
- 如果仍然找不到,它会继续向上链,检查每个__proto__,直到找到属性或到达null
在我们的info3.fName1示例中:
- JavaScript首先在info3中查找——没有找到fName1
- 然后检查info3.proto,它指向info2——也没有找到fName1
- 接下来检查info2.proto,它指向info1——在这里找到了fName1!
这称为原型链,这就是JavaScript中继承的工作方式。
为什么JavaScript中的所有东西都是对象
现在让我们解决我们开始时的谜团:原始类型如何使用对象方法?
在JavaScript中,几乎所有东西都表现得像对象,即使原始类型(如字符串、数字和布尔值)技术上不是对象。这是通过称为自动装箱或包装对象的过程实现的。
当你在字符串上尝试使用方法时:
|
|
JavaScript在幕后执行以下操作:
- 它临时将原始值包装在包装对象中:new String(“Boltu”)
- 此临时对象的__proto__自动指向String.prototype
- 在String.prototype中找到方法并执行
- 操作完成后,包装对象被丢弃
- yourName恢复为简单的原始值
这就是为什么你可以在原始类型上使用方法,即使它们不是对象。
__proto__与prototype的区别
什么是prototype?
当你在JavaScript中创建函数或类时,语言会自动创建一个名为prototype的蓝图对象。
例如:
|
|
当JavaScript看到此函数时,它在内部执行:
|
|
Person函数现在有一个名为prototype的隐藏属性,它是一个包含constructor属性的对象。
你可以向此原型对象添加方法:
|
|
什么是__proto__?
__proto__是存在于每个对象实例(数组、函数、对象——所有东西)上的属性。它是一个内部引用或指针,指示对象继承自哪个原型。
默认情况下,当你创建对象时,其__proto__指向Object.prototype。
它们如何协同工作
当你使用new关键字时:
|
|
JavaScript在内部执行以下步骤:
- 创建一个新的空对象:p1 = {}
- 设置对象的__proto__:p1.proto = Person.prototype
- 使用新对象调用构造函数:Person.call(p1, “Shejan”)
- 返回对象:return p1
现在当你尝试访问方法时:
|
|
JavaScript首先在p1中查找sayHi。当找不到时,它检查p1.proto,它指向Person.prototype,方法在那里定义。
关系可以表示为:
|
|
总结:
- prototype是函数/类的属性,用作实例的蓝图
- __proto__是对象实例的属性,指向它们继承的原型
原型如何与函数一起工作
让我们看一个完整的函数示例:
|
|
这里的关键好处是内存效率:introduce方法仅存在于Person.prototype中一次,但所有实例都可以通过原型链访问它。
原型如何与类一起工作
ES6引入了类语法,它看起来不同,但在幕后以相同的方式工作:
|
|
类本质上是JavaScript基于原型的继承的语法糖。在内部:
- 类仍然是构造函数
- 在类中定义的方法被添加到ClassName.prototype
- 使用new创建的实例将其__proto__设置为类的prototype
这意味着我们学到的关于函数原型的所有内容也适用于类。
结论
理解原型和原型链对于掌握JavaScript至关重要。这些概念构成了JavaScript实现继承和面向对象编程的基础。
关键要点
让我们回顾一下我们学到的内容:
- 每个对象都有__proto__:此属性指向对象继承的原型,启用原型链查找机制。
- 函数和类有prototype:此属性用作使用new关键字创建的实例的蓝图。
- 原型链启用继承:当JavaScript在对象上找不到属性时,它会向上遍历原型链,直到找到属性或到达null。
- 原始类型使用包装对象:即使原始类型不是对象,JavaScript也会临时将它们包装在对象中以提供对方法的访问。
- 类是语法糖:现代类语法更清晰,但在幕后仍然使用原型。
JavaScript起初可能看起来古怪,但一旦你理解了它的幕后工作原理,你就会欣赏其优雅灵活的设计。
祝你编码愉快!