继承类型
类式继承
function Parent(){
...
}
function Child(){
...
}
Child.prototype = new Child();
Child.prototype.childFunc(){
...
}
类式继承是直接修改子类的原型对象指向父对象的实例。 它有三个缺点:
- 如果父类上的属性有引用属性的话,子类共用这个属性时,子类的某个实例就有可能改变全局的继承实例
- 子类无法传参到父类中去初始化实例父类,因为
Child.prototype在构造函数外面 - 继承单一。
构造函数继承
function Parent(Pparam){
...
}
Parent.prototype.parentFunc(){
...
}
function Child(Cparam){
Parent.call(this,Cparam)
...
}
new Child(123).parentFunc() //error
它有一个缺点:
- 由于只是将父类函数的构造方法里的
this指针改变,所有没有涉及父类的原型prototype,所有原型上的方法无法使用。
组合继承
就是将上面的类式继承和构造函数继承组合
function Parent(Pparam){
...
}
Parent.prototype.parentFunc(){
...
}
function Child(Cparam){
Parent.call(this,Cparam)
...
}
Child.prototype = new Parent();
这个继承方式的缺陷是: 在子类构造函数执行了一边父类的构造函数,而在子类的原型上又实例了一次父类构造函数,这两次的执行不是最优解决方案。
原型式继承
function createChild(obj){
function F(){};
F.prototype = obj;
return new F();
}
function Parent(){
...
}
let child = createChild(new Parent())
child.func()=function(){
...
}
这个继承方式其实是在类式继承上的一次封装,类式继承存在的问题在它上面任然存在,但是它作为抛砖引玉的功能,所有我们继续下面的继承模式
寄生继承
寄生继承其实就是对原型继承的再一次封装,但是它可以在寄生函数里面做一些子类的定义,寄生的概念就是来源于寄生在函数内部实现功能。
function createChild(obj){
function F(){};
F.prototype = obj;
return new F();
}
function createChildAdd(obj){
let o = createChild(obj);
o.func = function(){
...
}
return o
}
<!--使用-->
function Parent(){
...
}
let child = createChildAdd(new Parent())
寄生组合式继承
终极继承方式:将寄生继承和组合继承组合使用
function createChild(obj){
function F(){};
F.prototype = obj;
return new F();
}
//寄生继承
function extendClass(child,parent){
let p = createChild(parent.prototype);
p.constructor = child;
child.prototype = p;
}
<!--使用-->
function Parent(Pparam){
...
}
Parent.prototype.parentFunc(){
...
}
function Child(Cparam){
//构造函数继承,继承父类的实例上的属性和方法
Parent.call(this,Cparam)
...
}
//寄生继承,继承父类的原型上的方法
extendClass(Child,Parent);
let child = new Child(12);
child.parentFunc();
使用寄生继承去创建父类原型上的实例挂载到子类上,同时改变子类的构造函数指向子类,这样实例执行instanceof判断才能返回子类,让后用构造函数继承去继承父类构造函数上的方法和属性,
这中既有原型继承,构造函数继承,组合继承,寄生继承的方式就是寄生组合式继承。
深入继承内容
多继承
javascript中只要一条原型链,无法继承多个原型链,所有多继承的实现方式就考虑对象的合并,在对象合并这里又会涉及到对象的浅复制和深复制。
浅复制
function shallowClone(source) {
let target = {};
for(leti in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
所谓的浅复制就是,只能对javascript中的基本类型进行深拷贝,对引用类型,只是复制的引用类型的指针。
在ES6种的Object.assign()就是浅拷贝
深拷贝
function isObject(x) {
return (Object.prototype.toString.call(x) === '[object Object]'|| Object.prototype.toString.call(x) === '[object Array]');
}
function clone(source) {
let target = {};
for(let i in source) {
if (source.hasOwnProperty(i)) {
if (isObject(source[i])) {
target[i] = clone(source[i]); // 判断仍是对象,就进行递归
} else {
target[i] = source[i];
}
}
}
return target;
}
对数组和对象再次递归进行一次深拷贝,对于函数,直接共用就可以。
对于这块的优化,我们可以思考对递归的优化,因为睡着对象深度的加深,这种浅拷贝的实现还是有点影响性能的。
使用JSON.parse(JSON.stringify(oldObj))可以实现深拷贝,但是由于JSON对函数的限制,所有无法深拷贝函数。
尾调用优化
由于递归的使用,会使调用栈不断增加,可以使用函数的尾调用,来实现执行栈只有一次插入。
<!--计算阶乘-->
//递归使用
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
//尾递归
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。
多态
多态就是同一个方法多种参数调用的方式,在javascript中函数里面的arguments属性能够完成这样的需求
function Compute(){
function add1(num){
return 10 + num;
}
function add2(num1,num2){
return num1 + num2;
}
function add3(num1,num2,num3){
return (num1+num2)^num3;
}
this.add = function(){
let len = arguments.length
switch(){
case 1:
return add1(...arguments);
case 2:
return add2(...arguments);
case 3:
return add3(...arguments);
}
}
}