要弄懂原型链,首先应先明白prototype原型对象、__proto__、对象三者之间的关系。
引入构造函数的相关定义:
构造函数是一种比较特殊的函数,用于批量实例化对象。通俗一点说,构造函数是用于生成对象的模板。
由于工厂模式在实例化对象时会存在同一功能代码在内存中开辟不同内存空间从而造成内存空间浪费的问题,更多的人选择使用构造函数来实例化对象,es6中引进的class关键字正是基于构造函数的思想
构造函数的本质上是将对象中一些公共的方法和属性 抽取出来,然后将这个函数封装成可复用的构造函数.
构造函数的特点(与工厂函数相比较):
a. 函数名首字母大写;
b. 函数体内没有关键字new,在实例化一个对象时会使用关键字new;
c. 构造函数体内的this指代当前实例化对象;
d. 函数体内没有关键字return。
function Fn() { this.n = 20 } Fn.prototype.say= function() { console.log(this.n) } var deb = new Fn()
构造函数的new关键字在实例化对象时会做以下四件事:
a.现在内存中开辟一块内存空间(new obj);
b.让构造函数体内的this指向这个对象 (因而,this指代当前对象);
c.将构造函数体内的属性和方法赋值给这个这个对象;
d.返回这个对象 (因而,构造函数体内没有return关键字)
为了区别对待,以下将构造函数称为父类,将实例化对象称为子类。
一:原型
1.定义
每一个函数身上都有一个prototype(原型),由于这个prototype的值是一个对象类型,因而又叫做原型对象,ptototype属性是函数独有的属性!
2.原型的作用
原型对象的作用通常用来共享父类的方法,由于方法是函数,本质上也是一个对象,而对象的内存地址保存在堆空间里,如同工厂模式一般,多个内存空间存放相同的代码,会造成大量空间被占用,因而将公有的方法添加在父类的prototype上,也就是写入了同一对象上,调用的时候也就避免了多个空间存放同一代码的错误示范,而由于父类的属性的值是基本类型,所占内存较少,所以我们往往将其添加到父类函数体的内部。
function Fn() { this.n = 20 } Fn.prototype.sing = function() { console.log(this.n) } var deb = new Fn() var deb2 = new Fn() console.log(deb.sing === deb2.sing) //true
二:__proto__
与函数的prototype属性不同,每一个obj的对象都有一个__ptoto__属性,这个__proto__属性隐式的指向了这个子类的的构造函数的prototype属性!但是这个__proto__属性是不可见的,这也就是为什么将其称作是隐式的,不过好在浏览器的支持下,它被定义为__proto__
如果看到这里,希望你再好好思考一下 ”万物皆对象“这句话的含义
从父类的角度看,子类的_proto_属性隐式指向了它的父类的prototype对象;从子类的角度看,子类自身的__proto__属性指向了它的父类的prototype属性
假如我们对比父类的prototype与子类的__proto__属性:
console.log(Fn.prototype === deb.__proto__)//true
理解了这一点,就可以明白为什么构造函数添加在其原型对象上的方法,实例化对象可以共享使用了:因为构造函数的prototype属性指向的 与 实例化对象的__proto__属性指向的 是同一个对象,也就是父类的prototype对象!
这样,就具备了深入理解原型链的条件。