概述
JS原型链
别名:隐式原型链
作用
根据一定路径查找属性或方法,类似传统面向对象的"继承"(原型链机制也可以说是弥补了一些传统面向对象编程语言中的继承的不足)。
对象间共享属性或方法。
举例
我们定义一个构造函数Fn,使用此构造函数创建一个对象fn1,接着使用创建的对象fn1去调用toString方法并打印,我们再使用对象fn1去调用test方法并打印,结果是第一个调用toString方法不会报错方法没有找到,而第二个调用test方法会报错。
代码:
<script>
function Fun(){} //构造函数
var fun1 = new Fun(); //创建对象
console.log(fun1.toString()); //[object Object]
fun1.test() //报错fun1.test is not a function
</script>
提出问题:两个方法我们对象中都没有定义,但为什么会出现一个调用成功,一个却调用失败呢?
说明:调用成功是因为当我们对象去调用toString方法时,先在自身查找是否存在此方法,如果不存在,则会沿着某路径查找下去直到尽头,此路径就是原型链,当找到原型链的尽头时,我们会找到toString方法。所以执行成功。而test方法直到找到尽头也没有找到,所以就会报错。
当然,我们还可以创建fn2,fn3....这样的对象,都可以调用到toString()。
至于原型链的路径是怎么样的,toString方法是在哪声明的,原型链尽头又是哪,这些问题下面会介绍到。
显示原型和隐式原型
核心概念
1.基本的,每一个构造函数都会存在一个属性:prototype,prototype属性默认指向空Object对象,此空对象称为原型对象。我们可以在此对象中设置属性或方法,那么由构造函数创建出的对象都能够访问到此原型对象的属性或方法(类似继承,空对象就像是父类)。
prototype属性就是我们所说的显示原型。原型中有一个属性constructor,它指向函数对象。
//我们可以打印一下函数的prototype属性
function Fun(){}
console.log(Fun.prototype);
打印结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/724e006a278aeb82a97231e23b198eb1.png)
基本的,每一个实例对象都会有一个属性:__proto__,__proto__默认指向构造函数的prototypy属性值。__proto__属性就是我们所说的隐式原型。
![](https://i-blog.csdnimg.cn/blog_migrate/06ee8b66ebd6892cdf8e034ba3acd80d.png)
3.特殊的,Object对象中的__proto__指向Object的原型对象,此原型对象中已经存在了如toString()等方法。值得注意的是,Object的原型对象中的__proto__不再有指向,值为null,既为尽头。
4.原型链路径:当我们查找某属性,自身没有时,会沿着__proto__查找,直到尽头,因此原型链也称为隐式原型链。
prototype和__proto__的生命起点:prototype由函数的创建而产生,__proto__由实例对象的创建而产生。
**直接看核心概念会很懵,接下来对着核心概念看如下的内存解析图会清楚很多。
内存说明
内存解析图一
根据以上核心概念,理解下方的内存中指向。
记住:构造函数中有prototype,对象中有___proto__
![](https://i-blog.csdnimg.cn/blog_migrate/2a5776d193a2c33b57a0849557357d88.png)
解读:首先创建了一个Fn函数,语句function Fn(){},可以看作var Fn = new Function();
所以首先在右边的堆空间中开辟了一块空间,也就是图中的A块,返回一个引用地址值0x123返回给栈空间的Fn,根据核心概念,函数中会有prototype属性存在,而prototype会指向一个
Object空对象,接下来我们有Fn函数创建了一个fn对象,语句var f1 = new Fn();所以会在内存中开辟一块空间,也就是图中的B块,根据核心概念实例对象中会有__proto__属性,指向构造函数的prototype,最终也会指向Object空对象块。
此图仅说明了函数和实例对象的内存指向,并未指向尽头。
内存解析图二
![](https://i-blog.csdnimg.cn/blog_migrate/57043472b4c6f287005320092ba07137.jpeg)
解读:首先创建了一个构造函数Fn,在其中添加了一个方法test1,并且由Fn创建了一个对象fn,这和上面的内存解析相同,但是有不同的是,在其中有语句:Fn.prototype.test2=function(){log...},此语句是在Fn的原型对象中添加了test2方法,由图就可以看出来。值得注意的是,可以看到Object函数,此函数是一开始就存在了,引用地址是0x456,Object函数对象中存在显示原型prototype,但我们知道,一般的函数的prototype属性指向的是Object的一个空对象,但是,Object函数的prototype是一个特殊,他指向的是Object的一个实例对象,此实例对象中存在了许多内置方法,此实例对象中的__proto__值为null,既为原型链尽头。
关于原型链的属性问题
在上面的演示和说明中,都是以方法来说明,那么关于原型链操作属性时是什么情况呢?
其实是有些区别的。
原型链操作属性情况
读取对象的属性时,自身没有时依然会自动到原型链中查找。
设置对象的属性时,当自身没有此属性时,就会为对象直接添加此属性。
方法一般定义的原型中,属性一般通过构造函数定义在对象本身。
总结(原型链查找过程说明)
原型链过程:使用一个构造函数创建了一个对象,使用此对象调用某属性(方法),先会在自身中查找是否定义了此属性(方法),如果找到则返回,如果没有找到,则会通过隐式原型__proto__地址值找到构造函数的prototype属性值,然后就会找到指向的原型对象(原始为空的object对象),查找此原型对象是否存在此属性(方法),如果存在则引用,如不存在,在继续通过原型对象的__proto__找到Object的prototype属性,prototype会找Object的原型对象,在其中查找,有则引用,没有则返undefind,因为当Object原型对象中也没有时,就会接着找__proto__,但是我们知道Object原型对象中的__proto__值为null,所以就可以说达到了尽头。