js的原型及原型链

190 阅读3分钟

函数数据类型:普通函数、类(内置类/自定义类)、箭头函数 (都是Function的实例)

对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象(函数也是一个对象,也像普通对象一样,有自己的键值对)、类的prototype也是对象 (都是Object的实例)

    1. 每一个函数(除ES6的箭头函数外)都有一个内置的属性prototype(原型属性),属性值是一个对象,在对象中会存储当前类的公共属性和方法
    1. 在prototype的堆内存中,如果是浏览器为其默认开辟的堆内存,会存在一个内置的属性:constructor构造函数,属性值就是当前类本身
    1. 每一个对象都有一个内置属性:就是__proto__,属性值是当前实例所对应类的prototype
  • 原型链查找机制:调用当前实例对象的某个属性(成员访问),先看自己私有属性中是否存在,存在调用的就是自己私有的;不存在,则默认按照__proto__找所属类prototype上的公有属性和方法;如果还没有,再基于prototype上的__proto__继续向上级查找,直到找到Object.prototype为止

让我们用一个例子来理解:

例1:
/*
 * EC(G)
 *  变量提升:Fn------函数堆的16进制地址(AAAFFF000)
 *  代码执行
 */
function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
let f1 = new Fn(); 

函数堆不只有里面内容的代码字符串,还有一些属性方法(例如length、name,最主要的是有prototype和__proto__)

由于prototype也是一个对象,所以Fn:prototype也有一个自己的堆,里面有constructor这个内置属性,和__proto__原型链属性,

Fn是个函数,所以Fn的__proto__原型链指的是Function.prototype,

而因为Fn这个类不知道指向谁,所以Fn.prototype.__proto__指向的是Object这个内置类,Object内置类作为类也属于函数,所以也有自己的函数堆,里面存放着Object的共有属性及方法(例如assign()、create())

当然,Object内置类也有自己的prototype和__proto__,Object的__proto__原型链指的也是Function.prototype,Object的prototype又是个对象,所以又有个堆,里面依然存放着constructor、__proto__和一些方法(例如valueOf()、toString())

这时是不是蒙了,咋还没完了呢......,

别着急,快到头了,哈哈

Object.prototype也是一个普通对象,所以他也是Object的一个实例,按理论来讲它的__proto__指向的Object.prototype,也就是指向自己了,这样没有意义,所以Object(对象的基类)的prototype上的__proto__是null

别忘了,函数Fn执行时,也会自动创一个对象,并且函数没有返回值时,默认返回的就是该对象,所以该对象也有一个堆,它的里面属性及方法就是this.、this.y、this.getX,

由于它是Fn函数这个类的实例,那么它的__proto__自然就是指向Fn.prototype这个堆了

如果我们在创建个let f2 = new Fn,那与f1的是一样的,只是又多创建了个对象

上图栈内存+堆内存看着是不是有点晕,那我们把堆内存摘出来,看的就明朗许多了

那函数类new Fn与new Fn()有什么区别吗?

其实没什么大差别,这与普通函数还不太一样

普通函数
    Fn() 函数执行
    Fn  函数本身
类函数
都是把Fn执行了,创造了实例(区别就是运算符优先级)
let f1 = new Fn(); //有参数new   19
let f2 = new Fn; //无参数new     18
还有就是()可以传参,不加()就不可以了

还有一点,如果对象上的prototype被重定向(重新赋值)后,原来的prototype堆内存会被释放掉,之前原型上的内容会丢失,包含constructor...,

原型重定向后,为了保证结构的完整性,我们一般要手动设置constructor属性