前端--关于继承那点事

128 阅读4分钟

 关于继承那点事

    实现方式1: 基于原型链继承

function Parent1() {    
     this.name = 'parent1';
}
function Child1() {    
     this.type = 'child1';
}
Child1.prototype = new Parent1();
console.log(new Child1())

对于该种方式的实现来说, 父类的方法和实例都可以访问到

但是会有一个问题: 原型的属性共享的问题

 实现方式2:  借助于构造函数继承

function Parent2() {
     this.name = 'parent2'
}
Parent2.prototype.getName = function() {    
    return this.name;
}
function Child2() {   
    Parent2.call(this)
    this.type = 'child1';
}

let child2 = new Child2();
console.log(child2)
console.log(child2.getName()) // child2.getName is not a function

那么我们对于借助于构造函数的方式,父类的引用属性不会被共享

但是还是有缺点:只能继承父类的实例属性和方法,不能继承原型属性和方法

实现方式3: 会综合1,2方式  组合继承

function Parent3() {
    this.name = 'parent3';
    this.play = [1,2,3]
}
Parent3.prototype.getName = function() {
    return this.name;
}
function Child3() {
    // 第二次调用 parent3
    Parent3.call(this)
    this.type = 'child3';
}
// 第一次调用
Parent3Child3.prototype = new Parent3();
Child3.prototype.constructor = Child3;
var s3 = new Child3()
var s4 = new Child3()
s3.play.push(4);
console.log(s3.play, s4.play) // (4) [1, 2, 3, 4] (3) [1, 2, 3]

组合继承可以借助构造函数和原型去实现继承,不过还存在一个问题在上述代码中也提到了就是会调用两次 parent3,那么多构造一次就会多进行一次性能开销,这是我们不愿意看到的,那么还有没解决的方案的呢,后续揭晓。

上面都是基于构造函数去实现继承, 如果是针对于js 的普通对象呢,怎么实现继承呢?有没有思路呢?

接下来我们来探索一下

实现的方式4:  原型式继承

这里我们就会提到 es5 里面的 Object.create 方法:

                   这个方法会接受两个参数:  一是用作新对象原型的对象

                    二是为新对象定义额外属性的对象(可选参数)

let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
 };

let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push('jiuwo');

let person5 = Object.create(parent4);
person5.friends.push("lucy");

console.log(person4.name);  // tom
console.log(person4.name === person4.getName()); // true
console.log(person5.name);   // parent4
console.log(person4.friends);  // ['p1', 'p2', 'p3', 'jiuwo', 'lucy']
console.log(person5.friends);  // ['p1', 'p2', 'p3', 'jiuwo', 'lucy']

第一个结果 “tom”,比较容易理解, person4 继承了 person4的name 属性,但是在这个基础又进行了自定义

第二个是继承而来的 getName 方法检查自己的name 是否和属性里面的值一样 , true

第三个结果"parent4"也比较容易理解, person5继承了parent4的name 属性, 没有进行覆盖, 因此输出父对象的属性

最后的输出结果是一致的, 其实就是 Object.create() 方法是可以为一些对象实现浅拷贝

但是这种继承方式的缺点也很明显, 多个实例的引用类型属性指向相同的内存,存在篡改的可能

实现的方式5:  寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝, 然后利用这个浅拷贝的能力再进行增强,添加一些方法, 这样的继承方式叫做寄生式继承

 let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
       return this.name;
    }
 };

  function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
      return this.friends;
    };
    return clone;
  }

  let person5 = clone(parent5);
  console.log(person5.getName());  // parent5 
  console.log(person5.getFriends());  // ['p1', 'p2', 'p3']

虽然优缺点和原型继承一样, 但是对于普通对象的继承方式而言, 寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法

实现的方式6:  寄生组合继承

function Parent() {    
    this.name = "parent";    
    this.play = ['1', '2', '3']
}
Parent.prototype.getName = function() {    
    return this.name;
}
function Child() {    
    Parent.call(this);    
    this.type = 'child';
}
function clone(child, parent) {
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}
clone(Child, Parent);
Child.prototype.getFriends = function() {
    return this.type;
}
let parent6 = new Child();
console.log(parent6) // name: "parent" play: (3) ['1', '2', '3'] type: "child"
console.log(parent6.getName()) // parent
console.log(parent6.getFriends()); // child

通过这段代码可以看出来 这种寄生组合式继承的方式, 基本上可以解决 前几种继承方式的缺点

较好的实现了继承, 同时减少了构造次数, 减少了性能的开销。

当然 es6 提供了 继承的关键字 extends 我们可以来看一下 它的底层实现的逻辑

es6 的 extends 关键字实现逻辑

先看一下 extends 如何实现实现继承

// es6  --- class  extends  superclass Parent {
    constructor(name) { 
       this.name = name;
    }    
   // 原型方法 
   // Parent.prototype.getName = function() {}
    getName = function() {
        console.log('parent:', this.name)
    }}
class Child extends Parent {
    constructor(name, age) { 
       super(name)
        // 子类的 构造函数中 则需要 在使用 this 之前 首先调用 super();
        this.age = age;
    }}
const a = new Child('123', 15)
a.getName(); 

其实我们可以通过babel这个编译工具看看 具体做了什么事情

function _possibleConstructorReturn (self, call) { 
		// ...
		return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 
}

function _inherits (subClass, superClass) { 
    // 这里可以看到
	subClass.prototype = Object.create(superClass && superClass.prototype, { 
		constructor: { 
			value: subClass, 
			enumerable: false, 
			writable: true, 
			configurable: true 
		} 
	}); 
	if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}



var Parent = function Parent () {
	// 验证是否是 Parent 构造出来的 this
	_classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
	_inherits(Child, _Parent);
	function Child () {
		_classCallCheck(this, Child);
		return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
	return Child;
}(Parent));

然后 我们是不是 可以尝试类似的思路去做一些尝试呢?

//  extends 
class Super {};
Super.prototype.getName = function() {
    console.log('123:');
}
function Sub() {};
// constructor: {
//     value: Sub,
//     enumerable: false,
//     writeable: true,
//     configurable: true
// }
Sub.prototype = Object.create(Super.prototype, {
    constructor: {
        value: Sub,
        enumerable: false,
        configurable: true,
        writeable: true,
    }})
let s = new Sub()s.getName()

总结: