【前端基础】继承

232 阅读4分钟

什么是继承呢?

打个比方:A 有很多的属性和方法,B 也有很多的属性和方法,B 想用 A 的方法,但是 A 不能用 B 的方法,这种时候他们就形成了继承的关系。

所以继承就是:父类有若干的属性和方法,子类有若干的属性和方法,让子类有父类的属性方法但是他们相互之间不产生影响。

继承有很多种实现方式:

构造函数实现继承

function fun () {
    this.name = 'fun';
}
fun.prototype.log = function () {
    console.log(1);
}

function Obj () {
    fun.call(this);
    this.type = 'child';
}
var test = new Obj;
console.log(test.log); // undefined
console.log(test.name); // fun
console.log(test.type); // child

通过构造函数实现的继承的本质是:

使用 call 改变了 this 的指向,让父类的里面的 this 指向了子类的上下文,这样父类里面通过 this 设置的属性和方法会被写入到子类里面。

其缺点从代码里也能看出来:通过原型设置的属性和方法不会被写入到子类上,即只能继承父类构造函数上的属性和方法,不能继承父类原型上的属性和方法。

构造函数继承还有一个优势就是,可以在子类构造函数中向父类构造函数传递参数:

function fun(name) {
    this.name = name || 'echo';
}
function SetName() {
    fun.call(this, 'jsoo');
    this.type = '111';
}
let test = new SetName();
console.log(test.name); // jsoo

原型链实现继承

function Fun() {
    this.name = 'fun';
    this.arr = [1,3,5];
}
Fun.prototype.log = function () {
    console.log(this.name);
}

function Obj(type) {
    this.type = type;
}
Obj.prototype = new Fun();

// 注意!!!如果使用下面这样的字面量的方法添加新方法,会导致上一行方法无效
//	Obj.prototype = {
//		log: function () {
//        	console.log(this.arr); // !!会报错 error
//    	}
//	};

let obj1 = new Obj('obj1');
let obj2 = new Obj('obj2');

obj1.name = 'is obj1';
obj2.arr.push(7);

console.log(obj1.log); // 'is obj1'
console.log(obj2.arr); // [1,3,5,7]

原型链实现继承的原理是:

利用原型链向上查找的机制实现继承,给 obj.prototype 赋值为父类的一个实例,当把 obj 作为构造函数在它的实例obj1 上查找属性时查找顺序依次是 obj1 本身 -> obj.prototype(fun实例)-> fun.prototype 这样既能继承父类构造函数上的属性方法。也能继承父类原型上的属性方法。

而原型链继承的缺点之一也正是构造函数继承的优点,就是原型链继承在创建子类型的实例时,不能向父类型的构造函数中传递参数。

缺点之二是:因为 obj1._proto_ === obj2._proto_ 所以当改变父类构造函数上的属性时 obj1 和 obj2 会相互影响,例子中当改变 obj1.arr 时 obj2.arr 也跟着变了就是这个原因。

而 obj1.name 变了 obj2.name 没变是因为当设置值时会优先在 obj1 自身上查找,没有发现 name 属性会在 obj1 自身上设置 name 值,这个时候根本没有影响到 _proto_ 上的 name。obj1 和 obj2 上的值不管是自身构造函数上的还是父类构造函数的都应该独立维护相互影响是我们不希望看到的。

组合继承(构造 + 原型链)

function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }

function obj () {
  fun.call(this)
  this.type = 'obj'
}
obj.prototype = new fun()

var O1 = new obj()
O1.arr.push('123')
console.log(O1.arr) // [1, 2, 3, "123"]
delete O1.arr
console.log(O1.arr) // [1, 2, 3]

原理:原理:通过fun.call(this)改变上下文this指向,父类构造函数上的属性和方法设置到了子类上,相互独立避免影响;通过 obj.prototype = new fun() 实现了继承父类原型上的属性和方法。

缺点:这种方法实现继承,父类构造函数会被执行两次分别在 fun.call(this) 和 obj.prototype = new fun(),而且父类构造函数上的属性在子类自身和子类的原型上都存在,这导致执行了 delete O1.arr 只是删除了 O1 自身上的arr 属性,O1 原型上依然存在,根据原型链向上查找机制 O1.arr 依然可以访问到。

那么如何来优化呢?

function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }

function obj() {
  fun.call(this)
  this.type = 'obj'
}
obj.prototype = fun.prototype  // 把实例改成了引用解决了上诉问题

var O1 = new fun()
var O2 = new obj()

O1 instanceof obj  // true
O2 instanceof obj  // true
(new fun()).__proto__.constructor  // 父类函数
(new obj()).__proto__.constructor  // 父类函数

原理:这个原理就不讲了,上面看明白了这个道理是一样的。 缺点:因为 obj.prototype = fun.prototype,导致父类和子类的实例无法做出区分。

寄生式继承

class 继承(ES6 的方法)