原型与继承
这次的blog主要是笔记,代码主要来自js高程,对一些概念作了自己的总结和思考(加粗部分)
创建对象
发展
1.起初,通过new Object()
,后来则使用对象字面量
来创建对象,这种方式在创建重复对象时会导致创建对象代码的冗余。
2.后来产生工厂模式
,主要是 将创建对象的代码封装在普通函数内,这解决了代码冗余问题,但创建出来的对象难以区分类型。
3.又产生了构造函数模式
,即 用构造函数创建特定类型的对象,
这种方式创建出的对象可以区分类别,但是每创建一个对象,会产生新的作用域链,而且所有的属性和方法都将重新建立一遍,没有重用本该共享的属性和方法,以后可能导致共享数据不一致和性能问题。
4.原型模式
又产生了,单纯的原型模式主要是 在构造函数的原型对象属性(prototype)中设置实例应要继承且会被共享的属性方法。
缺点:
- 单纯使用原型模式无法通过构造函数初始化属性;
- 过度共享问题:原型模式带来属性方法的共享,但又导致本不该共享的数据也被共享,对这些数据的更改,会牵一发而动全身地影响到其他实例。
5.后来一个更完善的模式蹦出来了–构造函数与原型模式的结合
,它主要是:构造函数模式用于定义实例属性(在构造函数定义不应共享的属性),原型模式用于定义方法和共享的属性。
优点:这种模式不但重用了方法,节省了内存,还很好的处理了实例中的非共享属性。
6.上面的结合模式的代码中:定义构造函数的代码与设置原型的是分开的,于是又产生动态原型模式
,它 将设置原型的代码放到构造函数内部,通过判断原型是否已经被设置再决定是否执行设置原型。
注意: 使用该模式最好不要重写原型,如果重写原型,初次调用构造函数new出来的对象不是以重写的原型对象为原型的,仍是默认原型(Object对象),之后new的对象才以重写的原型对象为原型。
也就是说:在真正执行构造函数的代码之前,new的实例已经创建并关联着原型。
7.此外:还有寄生构造函数模式
和稳妥构造函数模式
,详情看js高程,个人觉得很少用到,就不说了。
其中的稳妥构造函数觉得是 利用了闭包,不直接在返回的对象上定义变量,若对象不提供方法的话,外界基本无法访问,这样可以实现属性的私有化。
一些概念
原型对象:
- 为实例提供可共享的属性和方法,是实例的原型对象;
- 构造函数在创建实例时,会将实例与原型对象关联起来;
- 构造函数中的prototype属性可访问,创建出来的实例亦可以通过__proto__访问。
默认原型对象: 在定义构造函数时,原型对象默认是一个含有不可枚举的constructor属性的Object对象,而constructor指向构造函数。
重写原型对象: 以特定对象重写原型对象时,会把默认的原型对象覆盖,如果不在这个特定对象中添加constructor属性并指向构造函数的话,当想要访问constructor时,会沿着原型链向上找,结果不是原型对应的构造函数。
构造函数、原型对象和实例的关系: 构造函数以自身为模板,创建一个实例,同时关联起实例与原型对象。
原型链: 以某个类型的实例为原型创建的实例存在一个指向原型的指针__proto__,而原型对象亦存在指向上一层原型的指针,层层连接,构成原型链
继承
原型链实现继承
将SubType构造函数的prototype重写为SuperType类型的实例,从而实现SubType继承SuperType。
缺点:
- 因为是直接将SuperType的实例替换为SubType的prototype,所以prototype对象里的属性会被所有子实例共享;
- 如果单纯使用原型链实现继承,要想对父类(即原型对象)的属性赋值,这会导致所有实例都受影响;
借用构造函数
在子类型构造函数内部调用父类型的构造函数,call或apply,使得子类实例以自身实例属性的方式拥有父类的属性和方法。
优点:可以向父类构造函数传递参数,以初始化。
缺点:由于只是简单当做普通函数那样call或apply父构造函数,一方面无法复用父类里的方法,另一方面子类无法获取父类原型中定义的属性和方法
组合继承
上述两种方式的结合:
- 子类型构造函数内部调用父类型构造函数,用于实现不共享实例属性的继承;
- 将子类构造函数的prototype重写为父类实例,实现要共享的原型属性和方法的继承。123456789101112131415161718function SuperType(name){this.name = name;this.colors = ["red", "blue", "green"];}SuperType.prototype.sayName = function(){alert(this.name);};function SubType(name, age){//继承属性SuperType.call(this, name);this.age = age;}//继承方法SubType.prototype = new SuperType();SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function(){alert(this.age);};
优点:解决了方法无法复用和原型属性过度共享的问题
缺点:要调用两次父类的构造函数,一次是重写子类构造函数的prototype属性时,一次是在子类构造函数中定义非共享的实例属性时,这屏蔽了子类原型中的属性;这时,子类原型中被屏蔽的属性有点多余
。
寄生组合式继承(组合继承优化版)
关键在于优化 重写子类构造函数的prototype 这一步,利用父类的prototype对象,通过原生式继承的方式生成一个不含任何实例属性的对象,再用该对象重写子类构造函数的prototype,这样便达到少调用一次父类构造函数并剔除了子类prototype的被屏蔽的多余属性。
原型式继承
以特定实例为原型对象生成一个子实例
ES5规范提供了Object.create(obj,descriptors)
来实现这种继承,其中obj是即将作为原型的对象,descriptors是子实例新增实例属性的属性描述符对象。
特点:这种继承方式不用创建构造函数和定义新的类型,适合小范围的继承,它仍存在原型的属性过度共享的问题
此外,还有寄生式继承,个人感觉也很少用到,就不说了