函数数据类型:普通函数、类(内置类/自定义类)、箭头函数 (都是Function的实例)
对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象(函数也是一个对象,也像普通对象一样,有自己的键值对)、类的prototype也是对象 (都是Object的实例)
-
- 每一个函数(除ES6的箭头函数外)都有一个内置的属性prototype(原型属性),属性值是一个对象,在对象中会存储当前类的公共属性和方法
-
- 在prototype的堆内存中,如果是浏览器为其默认开辟的堆内存,会存在一个内置的属性:constructor构造函数,属性值就是当前类本身
-
- 每一个对象都有一个内置属性:就是__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();
由于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属性