-
首先,原型的出现是为了解决什么问题?为什么会需要原型?
我们知道使用自定义构造函数的方式去创建一个带有属性和方法的对象,通过new的方式就可以快速的实例化出对象。
用自定义构造函数Person()创建对象代码示例:
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
console.log('hello constructor')
}
}
// 第一次 new 时, Person 函数要执行一遍
// 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi
var o1 = new Person()
// 第二次 new 时, Person 函数要执行一遍
// 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi
var o2 = new Person()
输出如图:
- 我们可以看到俩次new的是不同的对象,虽然两个对象里面的属性和方法相同,但还是占用了两个存储空间,存储结构如下图所示:
- 以sayHi方法为例,如果要验证两个实例化对象中的sayHi方法的存储空间是否相等
console.log(o1.sayHi === o2.sayHi)
的到的结果是false
- 就可以发现这种处理方式的缺点: 一摸一样的函数出现了两次,占用了两个空间地址
- 这样就会占用很多内存空间,为解决这个问题,原型出现了。
2 什么是prototype?
每一个函数天生就自带一个叫做prototype的成员,是一个对象空间。
prototype(原型)属性:这个属性是一个指针,指向一个对象,它的用途是包含特定类型的所有实例共享的属性和方法,即这个原型对象是用来给实例共享属性和方法的。
每个实例内部都有一个指向原型对象的指针。
即使用这个原型对象来共享实例的属性和方法的模式就叫原型模式。
注意:在函数的prototype里面存储的内容,不是给函数的使用的,是给函数的每一个实例化对象使用的。
3 什么是__proto__?
每一个对象都天生自带一个成员,叫做
__proto__
,是一个对象空间这个
__proto__
对象空间是给每一个对象使用的实例化对象的
__proto__
和所属的构造函数的prototype
是一个对象空间,这是能够调用公共属性和方法的重要一个点。代码如下:function Person() {} var p1 = new Person() console.log(p1.__proto__ === Person.prototype) // true
注:当我们写构造函数时,属性直接写在构造函数体内,方法写在原型上
function Person() {} Person.prototype.sayHi = function () { console.log('这是一个原型空间中的方法函数') }
4 什么是原型链?
我们让
第一个构造函数的原型对象=等于第二个构造函数的实例 =》结果是第一个构造函数的原型对象将包含一个指向第二个原型对象的实例
再让
第三个原型对象=第一个构造函数的实例
此时得出的结果就是第三个原型对象也将包含指向第一个原型对象的指针。以此类推,构成了原型的链条。
5 原型链的访问原则
我们现在已经知道的原型链的衔接,就可以来看一下原型链的访问原则。
访问原则:
- 实例化对象访问某个成员时,先在自己的成员上面寻找,
- 自己没有就会去自己的__proto__寻找
- 自己的__proto__也没有,就会往上逐层查找,即
__proto__
里面没有就再去__proto__
里面找一直找到
Object.prototype
里面都没有,那么就会返回undefiend或报错
原型对象指向示意图:
更多关于prototype和__proto__的知识,可到https://zhuanlan.zhihu.com/p/92894937