prototype和__proto__的来历和使用

prototype和__proto__是什么以及两者的区别

前言

相信很多初学JavaScript的同学和我一样,可能听过prototype和__proto__两个属性,但是在实际的项目开发中很少或者几乎没有用到。那么这两个属性究竟是什么?学习这两个属性又有什么用处呢?

prototype的来历

要说明prototype属性,就需要从JavaScript的设计思想说起了。JavaScript的作者Brendan Eich在设计JavaScript语言的时候就曾经考虑过是否需要引入“继承”机制,因为JavaScript中里面都是对象,就需要将这些对象联系起来,可是引入类(class)就会让JavaScript变成一种正式的面向对象编程语言,从而增加初学者的入门难度。

所以作者就参考了C++和Java生成实例的new命令。
其中,C++:

ClassName *object = new ClassName(param);

Java:

Foo foo = new Foo();

在JavaScript中也引入了new命令,用于从原型对象中生成实例。但是,JS中没有类的概念,那用什么方式来表示原型呢?
同样,也是参考C++和Java,二者在生成实例的时候都调用了“类”中的构造函数,而Eich在设计的时候就做了简化,直接使用构造函数作为原型对象。
比如,我们现在有一个名为Cat的构造函数:

function Cat(name){
    this.name = name
    this.species = '猫科'
}

对这个构造函数使用new,就会生成一个猫对象的实例。

var Tom = new Cat('Tom')
console.log(Tom.name) // Tom

注意构造函数Cat中的this关键字,代表了新创建的实例对象。
在这里插入图片描述

var Tom = new Cat('Tom')

这句话,相当于

var Tom = {
    
    }
Tom.name = 'Tom'
Tom.species = '猫科'
Tom.__proto__.constructor = Cat
Tom.__proto__.__proto__ = Object.prototype

new运算符的缺点

用构造函数生成实例对象的缺点,就是无法共享属性和方法。比如:

扫描二维码关注公众号,回复: 13563548 查看本文章
var Tom = new Cat('Tom')
var Jack = new Cat('Jack')
Jack.species = '鼠种'
console.log(Tom.species) // 猫科

即使改变了其中一个实例对象的属性,另一个对象不会受影响。每一个实例对象,都有属于自己的对象和方法的副本,并且相互独立。这无法做到数据共享,同事也是极大的资源浪费。

prototype属性的引入

考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。

这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
在这里插入图片描述
在这里插入图片描述
可以看到在实例对象Tom和Jack未定义species属性时,就会通过__proto__属性去寻找父级(原型链上的共享属性prototype)中的species属性。即实例对象的__proto__指向的就是父级的prototype属性。
在这里插入图片描述
但是一旦实例对象定义了species属性,就不会也没有必要去引用原型链上的species属性了。
但是一旦删除实例对象的species属性,就会重新去原型链上寻找该属性。
在这里插入图片描述

总结

1.prototype属性的出现是为了解决用构造函数实例化对象而导致的无法共享属性和方法的问题。

2.实例对象的__proto__属性指向的就是其构造函数的prototype属性。

3.当实例对象找不到属性时,就会通过__proto__属性寻找其构造函数的prototype中的同名属性;如果还是找不到,就会继续通过构造函数的__proto__属性寻找其构造函数的构造函数中的prototype中的同名属性,直到顶层的对象。如果顶层对象也找不到对应的属性,则返回undefined

猜你喜欢

转载自blog.csdn.net/qq_39055970/article/details/115415297
今日推荐