前言
学习JS一周了, 发现原型链是个常问的问题,今天想总结下,谈谈自己的理解,主要是方便自己日后复习。小菜鸡第一次写文章,不太懂排版,内容也可能理解得不太深刻。
原型
函数也是对象,可以有属性和方法。浏览器为我们定义的每一个函数都创建了一个prototype属性,该属性保存着原型对象的引用,而且原型对象会自动获得一个constructor的属性,指回那个函数。所以在我们自定义构造函数时,会在堆内存中生成一个原型,构造函数的prototype指向该原型。当我们每次调用构造函数生成新实例时,实例也会自动生成一个__proto__属性,指向构造函数的原型。其实实例与构造函数或者说类之间没有直接联系,但实例与构造函数的原型之间有直接联系,如图。
function Person(){ this.name="盖伦"; } let person1 = new Person();
这里顺便说下创建对象的过程:
1 new 之后,在堆内存中创建一个新对象
2 新对象的__proto__属性指向构造函数的原型
3 构造函数中this指向新对象
4 执行构造函数体内函数
5 给变量返回这个新对象 (这里传的时引用,JS中引用数据类型存储的都是引用,图中__proto__,prototype,constructor都是引用)
原型的作用
创建一个类的多个实例可以发现,这些实例指向同一个原型,那么实例就可以共享原型中的属性和方法。不用分别为每个实例分别再去添加属性和方法,最直观的好处就是节省了内存。
function Hero(name){ this.name=name; } Hero.prototype.sayName=function(){ console.log("i am "+this.name) } let hero1 = new Hero("盖伦"), hero2 = new Hero("剑圣"); hero1.sayName(); hero2.sayName(); console.log(hero1.sayName===hero2.sayName);
// 可以看到两个实例的sayName方法是同一个引用
需要注意的是通过对象访问属性和方法时,会先在对象本身中寻找,若没有再去他的原型中寻找,直到找到,或者都没有返回undefined。
原型链
终于到原型链了!原型链是JS主要继承方式。我们知道实例的__proto__指向构造函数的原型,原型也是对象,如果这个对象作为另一个类或者说构造函数的实例,那么原型的__proto__也会指向那个构造函数的原型。现在它此时既是一个原型也是另一个构造函数的实例,就会产生一条由实例与原型连接而成的原型链。
function Hero(){ this.power=100; } Hero.prototype.sayPower=function(){ console.log("继承了力量点数"+this.power); } function Gailun(){ this.name="盖伦"; } Gailun.prototype=new Hero(); //子类的原型成为父类的实例 let gailun = new Gailun(); console.log(gailun.name); gailun.sayPower();
完成了子类Gailun对父类Hero的继承,产生了一条原型链,要注意的是子类的原型是没有constructor的,因为 Gailun.prototype=new Hero()中原型成为一个父类的新实例,实例本来就没有constructor属性的,并且将新实例的引用返回给子类的prototype, 相当于重写了子类的原型。换句话说,在这步发生之前,子类构造函数是有一个默认原型的,但重写后,没有指针再指向原来的原型了,由于JS的垃圾回收机制,那片内存会被回收。当然如果在子类原型成为父类的新实例之前,先创建一个子类的实例,那么这个实例的__proto__会指向默认的子类原型,默认子类原型中的constructor会指向子类。如图,很明显xiaogailun是不能继承父类的,因为没有一条原型链支持它指向父类的原型。图中子类看似有两个原型都叫Gailun Prototype,其实只有一个就是子类的prototype属性保存的那个引用,名字只是为了好区别,对于构造函数来说,它只认prototype保存的那个引用,按照引用来找到原型。
function Hero(){ this.power=100; } Hero.prototype.sayPower=function(){ console.log("继承了力量点数"+this.power); } function Gailun(){ this.name="盖伦"; } let xiaogailun=new Gailun(); //先创建一个子类的实例 Gailun.prototype=new Hero(); //子类的原型成为父类的实例 let gailun = new Gailun(); console.log(gailun.name); gailun.sayPower();
最后,一般境况下,构造函数的原型都是object类的原型。所以很多对象可以继承很多object原型中的方法,如hasOwnProperty(),isProperty()... 。
原型链的缺点
当父类原型中存贮引用数据类型比如数组时,继承了它的属性的实例在操作数组时,比如添加,那么所有实例的属性都会变化,因为属性实际上存贮的是数组的引用。感觉类似浅拷贝。
结语
第一次写,感觉思路还不是很清晰,也不美观,不能完全把自己所想的表达出来。以后要多看看大佬们如何规划的。