一文带你搞懂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原型和原型链内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
