本文大部分参考自https://www.cnblogs.com/chengzp/p/prototype.html
原型链
简单理解就是原型组成的链,实例的__proto__是它的原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,直到最后的null,这就是原型链,当向上找找到Object的原型(null)的时候,这条原型链就算到头了。
实例声明的过程:生成构造函数,这个构造函数会自带一个propertype的属性指向原型对象,这个原型对象自带一个constructor属性指向构造函数本生,然后利用构造函数new产生一个实例,实例自带一个__proto__属性指向原型对象。
只有函数有prototype、对象实例有__proto__属性,所以原型对象也具有__proto__属性,会指向对应的原型对象。
另:instanceof原理
instanceof用于判断实例的__proto__和生成该实例构造函数的prototype是不是引用同一个地址。
举几个例子:
这里定义了一个构造函数,然后通过构造函数定义了一个p实例。
参考上图和文字的理解,实例的__proto__和构造函数的prototype它们的引用是相同的。
第一个console中p实例的__proto__和它的构造函数Person的prototype是相同的
第二个console中Person的__proto__,对应的就是Person的构造函数的prototype也就是Function的prototype
第三个console中Person的prototype对应的简单对象即Object对象,而这个对象的构造函数对应的是Object,所以将Person.prototype看作对象a,那么对象a的__proto__也就是对象a的构造函数的prototype,即Object.prototype
第四个console中Object的prototypeObject
console.log(Object.__proto__ === Function.prototype)//true 由于object是构造函数,道理同上
图片取自https://segmentfault.com/q/1010000013876889/a-1020000013879132中的一个回答
看完后茅塞顿开感觉清晰了很多
继承
可以看到Sub中并没有声明toString方法,但是仍然可以运行,因为,在sub实例中找不到方法,会在原型链中找,而Sub的原型对象是object对象,而toString又是object原型中的方法,所以可以找到。(类似于作用域链一样)
原型链继承
关键:子类型的原型为夫类型的一个实例对象
Sub.prototype = new Super();
代码和内存图
图解释:首先声明了一个Super方法,于是有了Super函数对象和Super原型对象,然后声明sup实例,实例中的proto会指向它的构造函数Super的原型对象;同理,然后声明了一个Sub方法,于是有了Sub函数对象和Sub原型对象,然后声明sub实例,实例中的proto会指向它的构造函数Sub的原型对象。
现在想让sub实例能访问到super中的name和age属性。思考:直接通过sub.age会出现undefined,因为sub对象本生没有该属性,而且搜索原型链也找不到这个属性(也就是在Sub的prototype中找不到,在prototype的proto也找不到),那么既然是继承,sub想访问super中所有属性,通过访问顺序(找sub,然后找Sub.prototype),所以可以将Sub.prototype指向super的实例,这样sub中虽然找不到,但在prototype中可以访问到,达到了继承的效果。
上面的思考为看了原型链后的想法。以下为具体的实现形式
原型式继承:原理,让子类构造函数的prototype指向父类的prototype,即共享同一个原型对象咯
缺点:子类只能继承父类原型对象上的成员,子类成员都共享父类的原型对象成员,导致修改一个子类,其他子类也会改变
注,这里的修改子类,是指修改了子类共享了那个原型对象
例:
原型链继承:原理,让子类构造函数的prototype指向父类的实例
解决了原型式继承中的无法继承父类实例中的成员的问题
同上,子类成员的修改会互相影响,同样的,由于Father对象对于Son来说是它的原型对象,所以除了基础类型无法修改外,对其他类型的修改都会影响到其他子类成员
注:切记,对原型对象的成员无法修改值,除了引用类型的成员
借用构造函数继承
原理:使用call和apply借用其他构造函数的成员
解决了子类成员相互影响的问题,但子类成员无法访问父类的原型对象。
在s = new Son()构造函数时,构造函数内部this的值指向s,所以Father.call(this)相当于Father.call(s),由于call方法可以改变函数的作用域,也就相当于s.Father(),于是当s调用Father时,会在堆中生成新的对象,并赋值,所以子类都是相互独立的,不会互相影响。
缺点:假如父类构造函数中有方法,那么内存消耗会很大,因为每个实例都会拷贝相同的方法;无法访问父类的原型对象。
组合继承
原理:借用构造函数+原型链继承
解决了借用构造函数继承时无法访问父类原型对象的问题;
缺陷:s1对原型对象的修改会影响到s1的访问,因为按照原型式继承,他们是共享Father对象的原型成员。(尽管子类的其他成员都是相互独立的)
还有个问题,就是这种继承会调用两次父类构造函数,一次是,
二次是,如下图:
寄生组合式
原理:借用构造函数+原型链继承,并解决组合模式中多次调用父类构造函数的问题。
这个方法只是进行了一个简单的赋值操作,生成一个父类原型,并将该原型的constructor指向son,最后将该原型传给son的prototype
继承大概就这些吧,等学到后面再回来补充。