一文带你搞懂JavaScript中的原型和原型链
作者:LBruse
原型和原型链
JavaScript
和Java
这种面向对象的语言不太一样,JavaScript
是基于原型继承
的语言。虽然在ES6
及之后,class
、extend
语法也渐渐取代了之前修改prototype
实现继承的方式,但本质上还是通过修改prototype
来实现继承的。本文则是重点对prototype
相关知识点做拆解和梳理
通过class声明并实例化对象
在java
中声明并实例化对象是这样的
package geek.springboot.application.entity; public class WechatUser { private String name; private String openId; private String avatar; public WechatUser(String name, String openId, String avatar) { this.name = name; this.openId = openId; this.avatar = avatar; } // 打印输出当前微信用户信息 public void print() { System.out.println("name: " + this.name + " openId: " + this.openId + " avatar: " + this.avatar); } // java程序启动入口 public static void main(String[] args) { WechatUser user = new WechatUser("Bruse", "opwrogfajadfoa113", "avatar-1.png"); user.print(); } }
JavaScript
实例化对象是这样的
class WechatUser { constructor(name, openId, avatar) { this.name = name this.openId = openId this.avatar = avatar } print(){ console.log(`name: ${this.name} openId:${this.openId} avatar: ${this.avatar}`) } } const user = new WechatUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg') user.print()
输出name: Bruse openId:sfoqioiooa1 avatar: avatar-1.jpg
从语法上看两者差别并不大,class
定义对象模板,定义了对象该有的属性和方法,然后通过new
关键字将对象进行实例化
extend继承
通过extend
便可让不同的class
实现继承关系,达到代码复用的效果
class MiniProgramUser extends WechatUser { constructor(name, openId, avatar, appId) { // 调用父类的构造函数 super(name, openId, avatar); this.appId = appId } // 重写父类方法 print() { console.log(`name: ${this.name} openId:${this.openId} avatar: ${this.avatar} appId: ${this.appId}`) } }
输出name: Bruse openId:sfoqioiooa1 avatar: avatar-1.jpg appId: appId13322
原型
以上示例演示了如何用class
进行声明和实例化对象,但其实class
只不过是所谓的语法糖
,本质上JavaScript
并不会像Java
那样基于类实现面向对象。
class WechatUser{}
实际上也还是个函数,class
只是个语法糖
,它等同于
function WechatUser() {}
隐式原型和显式原型
隐式原型
const user = new WechatUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg') console.log('user.__proto__ ', user.__proto__)
以上边的代码为例,其实在创建出来的user
对象中,有一个__protocol__
属性,这个即每个实例都有的隐式原型
,打印输出如下
显式原型
console.log('WechatUser.prototype ',WechatUser.prototype)
输出WechatUser
的prototype
属性,prototype
即原型
的意思,每个class(function)都有显式原型
,结果如下
隐式原型和显式原型的关系
可以看到无论是user.__proto__
还是WechatUser.prototype
,都有print
方法,constructor
都是WechatUser
,那么是否也就意味着user.__proto__[实例的隐式原型]
===WechatUser.prototype[class的显式原型]
?
console.log('equals ', user.__proto__ === WechatUser.prototype)
输出为equals true
,证明user.__proto__
的确等于WechatUser.prototype
,引用地址是同一个。
这里的关系可以用下图表示
- 每个class都有显式原型prototype
- 每个实例都有隐式原型__proto__
- 实例的__proto__指向其所对应的class的prototype
基于原型的属性/方法查找
基于上边的内容,其实可以总结出:获取实例属性或执行方法时,会先在实例自身进行寻找有没有相关的属性或方法,有的话就获取或调用,没有的话,会顺着实例的__proto__往上找到实例对应的class的prototype,并对prototype进行变量查找或方法调用。这也就是所谓的基于原型的继承方式
原型链
搞明白了基于原型是怎么回事后,那接下来就是多个原型之间的关联形成原型链
const miniUser = new MiniProgramUser('Bruse', 'sfoqioiooa1', 'avatar-1.jpg', "appId13322")
这里声明一个miniUser
,它是基于MiniProgramUser
实例化的,所以miniUser.__proto__
相等于MiniProgramUser.prototype
。
但其实MiniProgramUser.prototype
也有一个__proto__
属性,输出如下
miniUser
的隐式原型等于MiniProgramUser
的显式原型,可MiniProgramUser
的显式原型的隐式原型(是有点绕)
又等于谁呢?
因为定义MiniProgramUser
这个class
的时候,使用了extend
关键词,表示其继承于WechatUser
,而且WechatUser
也有自己的prototype
,输出如下
那么尝试将MiniProgramUser.prototype.__proto__
与WechatUser.prototype
比较,结果如下
console.log('equals', MiniProgramUser.prototype.__proto__ === WechatUser.prototype)
输出equals true
,证明MiniProgramUser
的显式原型
的隐式原型
等于WechatUser
的显示原型,隐约间形成了一条原型链
原型链的尽头
那么在这里其实也可以做一个举一反三,既然每个class
的prototype
都会有一个__proto__
,既然WechatUser
这个class
并没有在代码中显式指定继承于哪个class
,那么WechatUser.prototype.__proto__
应该就等同于Object.prototype
,输出验证如下
console.log(WechatUser.prototype.__proto__ === Object.prototype)
结果为true
这里也有一个知识点,因为Object
在JavaScript
所有对象中是最顶级的存在了,所以虽然Object
的prototype
也有__proto__
,但它实际上不指向任何对象,仅为null
console.log('Object.prototype.__proto__ ', Object.prototype.__proto__)
输出 Object.prototype.__proto__ null
原型链总结
这里可以用一张图清楚表示形成的原型链是怎么样的
typeof vs instanceof
typeof
typeof
是用来判断当前变量是何种类型?基本类型?引用类型?也就是说它能
- 识别所有值类型
- 识别函数
- 判断是否引用类型,但只能判断出为
object
,没法再细分
判断值类型
const name = 'Bruse' typeof name // 输出'string' const sym = Symbol('sym') typeof sym // 输出'symbol' const done = false typeof done // 输出'boolean'
识别函数
typeof console.log // 'function' function print () { console.log(1+1) } typeof print // 'function'
引用类型则非常笼统地识别为object
typeof null // 'object' typeof [1,2,3] // 'object' typeof {name: 'Bruse', age: 16} // 'object'
instanceof
instanceof也是用作类型判断的,只不过比typeof
更精准了,它可以判断出当前变量是否该class
构建出来的。
[] instanceof Array // true [] instanceof Object // true {} instanceof Object // true miniUser instanceof MiniProgramUser // true miniUser instanceof WechatUser // true miniUser instanceof Object // true
结合出上边原型链
的知识,其实可以搞清楚instanceof
的原理,其实就是根据instanceof
左边变量miniUser
的原型链一层层往上找,判断prototype
或__proto__
是否等于instanceof
右边的class
WechatUser
的prototype
到此这篇关于一文带你搞懂JavaScript中的原型和原型链的文章就介绍到这了,更多相关JavaScript原型和原型链内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!