背景
js 是一个面向对象的动态脚本语言,而设计者当初10日创造javaScript,参考了当时面向对象语言程序设计,又考虑到是为Web做脚本动态化服务的,因此需要尽可能轻量,就舍弃了面向对象语言中的 Class 概念,但是核心设计理念,比如封装 继承 多态等需要保留,而针对继承,就设计了js原型链来实现js 继承。
设计原理
抽象出某些类共有的属性 行为,封装为一个类(父类),而当某个类(子类)需要实现这些属性,行为,就可以直接继承这个类(父类),这就是继承最大的功能.
那么原型链如何实现的上述功能?
首先js万物皆为对象,包括Function也是一个对象,所以要实现原型继承,还得从对象下手 !
那么设计者就在每个对象里都加入了一个属性 从最顶端的Object 到你实例化的一个var a = [] 数组对象都有这个属性 _ proto _.
有什么用呢?
这个_proto_属性存储着一个引用,指向它的构造函数的原型对象 (prtotype)。
原型对象当然也有一个 _ proto _,引用着它的原型对象,就这样层层向上直到一个对象的原型对象为 null
。根据定义,null
没有原型,并作为原型链的结束.
也就是我们常见的
let a = [];
a._proto_ = Array.prototype // 所以a可以调用有数组方法
看不懂没关系,我们慢慢来解析上边这句话\
1.一个新名词原型对象
首先原型对象顾名思义就是一个对象,所以我们可以对这个对象的属性进行 修改 增加等操作
// 覆写之前的Array.find() 函数
Array.prototype.find = function(){
}
// 添加属性
Array.prototype.myAttr = 'test'
那么问题来了,这个原型对象在什么时候创建的?又在何处引用呢?
js 里除了每个对象有_proto_属性外,每个函数在创建时都附带创建一个原型对象(保存在堆中),而对此原型对象的引用就保存在函数的 prototype 属性中(别忘了函数也是对象)
2. 构造函数
js函数都可以作为构造函数,因此这里的构造函数,也可以理解为new了一个对象的函数
function myFun(num){
const a = 0;
const b = {};
this.c = num;
}
let myObj = new myFun(2);
myObj._proto_ = myFun.prototype
3.为什么要指向构造函数的原型对象呢
因为原型对象中,存储的就是可以复用与继承的属性与方法 !
这是不是类似于我们抽象出来的超类或者父类?\
所以通过_proto_能访问到其构造函数的原型对象,也就能获取到可以继承的属性与方法,比如 数组的length属性的get,数组的find() every() 方法等
let arr = [1,2,3,4,6];
console.log(arr.length);
当你访问arr.length,发现并没有这个length 属性,就通过_proto_找到arr的原型对象,也就是Array.prototype,再找一找length,找到了! 如果依旧没找到,继续通过_proto_找到原型对象... 大部分最终都会到Object.prototype._ proto _ = null ,也就结束了.
补充
-
每个对象都有 __ proto __ 属性,但只有函数对象才有 prototype 属性
-
constructor属性,只有 prototype对象 才有这个属性,这个属性很简单,就是从prototype对象指向构造函数,而构造函数的peototype属性是从构造函数指向prototype对象,相对应.而其它对象的constructor属性(比如你声明的arr 也有constructor属性)都是通过__proto__对象从prototype对象继承而来。
-
函数在创建时,JS会为该函数创建一个对应的prototype对象,而这个prototype对象的constructor属性又指向该函数,即Foo.prototype.constructor === Foo