ES5继承

130 阅读4分钟

自己苦继承久已,今日再学再总结,不当之处请不吝赐教,我及时纠正,免得误人误己。

归纳下实现方式,总的来说就是利用原型链+构造函数来实现。

  1. 单纯依赖原型链继承,无法给父类传参,引用类型属性会共享,需要改善;
  2. 由于1的问题,引入了构造函数,子类里面call父类,借用父类的属性和方法(对象冒充)。它也有问题,就是没有用到原型链,就没办法复用后续新增的属性和方法;
  3. 汲取二者只精华,孕育出组合式继承,子类借用父类的构造函数,子类的原型指向父类的实例。这种方法比较好,但是会两次调用父类构造函数,call一次,new一次,如果是个臃肿的父类,会额外占用一些内存;
  4. 为了改善二次调用,设计出寄生组合式继承,就是将new的那次调用优化掉,利用一个新构造函数F,克隆一份原型链,但是构造函数很干净,什么也没挂,不过依旧需要new,只是构造函数由Parent变成了F。它拥有组合式继承的优点,也优化了组合式继承的不足,只是实现起来是最复杂的。
function Person(age) {
  this.name = ['A', 'B', 'C'];
  this.age = age;
}

Person.prototype.run = function () {
  return this.name + ' - ' + this.age;
};

new Person(18).run(); // "A,B,C - 18"



/* 1 原型链继承 ------------------------------------------------------------ */
function Child() {}

// 子类实例的原型等于父类的实例
/* new Child().__proto__ = */ Child.prototype = new Person();

var child = new Child(18);
var child1 = new Child(20);

console.log(child.run(), child1.run()); // A,B,C - undefined A,B,C - undefined
console.log(child.age, child1.age); // undefined undefined
console.log(child.name, child1.name); // ["A", "B", "C"] ["A", "B", "C"]
child.name.push('D');
console.log(child.name, child1.name); // ["A", "B", "C", "D"] ["A", "B", "C", "D"]
/**
 * ! 问题
 * ! 1. 不能传参,age未接收到
 * ! 2. 一个实例改变引用属性,影响其他实例
 */



/* 2 借用构造函数继承 ------------------------------------------------------- */
// 对象冒充,借用构造函数,可以传参
function Child(age) {
  // 对象冒充,给超类型传参
  Person.call(this, age);
}

var child = new Child(18);
var child1 = new Child(20);

console.log(child.age, child1.age); // 18 20
console.log(child.name, child1.name); // ["A", "B", "C"] ["A", "B", "C"]
child.name.push('D');
console.log(child.name, child1.name); // ["A", "B", "C", "D"] ["A", "B", "C"]
console.log(child.run(), child1.run()); // Uncaught TypeError: *.run is not a function
/**
 * * 解决了什么问题
 * * 1. 给超类传参
 * * 2. 引用属性共享
 * ! 引入了什么问题
 * ! 1. 没有原型,无法进行复用
 */



/* 3 组合继承 -------------------------------------------------------------- */
// 原型链继承+构造函数继承
function Child(age) {
  //对象冒充
  Person.call(this, age);
}

// 原型链继承
Child.prototype = new Person();
var child = new Child(18);
var child1 = new Child(20);

console.log(child.age, child1.age); // 18 20
console.log(child.name, child1.name); // ["A", "B", "C"] ["A", "B", "C"]
child.name.push('D');
console.log(child.name, child1.name); // ["A", "B", "C", "D"] ["A", "B", "C"]
console.log(child.run(), child1.run()); // "A,B,C,D - 18" "A,B,C - 20"
/**
 * * 解决了什么问题
 * * 1. 不存在引入属性共享的问题
 * * 2. 能给超类传参
 * * 3. 拥有原型链,可以复用原型链属性和方法
 * ! 引入了什么问题
 * ! 1. 有两次调用的情况,对于Person的调用,子类call一次,原型链继承new一次
 */
 
 
 
/* 4 原型式继承 ------------------------------------------------------------ */
// object.create()
function createObj(obj) {
  function F() {} // 创建一个构造函数
  F.prototype = obj; // 把实例对象赋值给构造函数的原型
  return new F(); // 返回实例化对象
}

// 基础对象
// var child = {
//   name: ['A', 'B', 'C'],
//   age: 18,
// };

var child = new Person(18); // Person {name:['A','B','C'], age:18}
var child1 = createObj(child); // F {}
var child2 = createObj(child);
var child3 = createObj(child);

console.log(child.name, child1.name); // ["A", "B", "C"] ["A", "B", "C"]
console.log(child.age, child1.age); // 18 18
child.name.push('D');
child1.age = 20;
child2.name = ['E'];
child3.addr = 'China';
console.log(child.name, child1.name, child2.name); // ["A", "B", "C", "D"] ["A", "B", "C", "D"] ["E"]
console.log(child.age, child1.age, child2.age); // 18 20 18
console.log(child.addr, child1.addr, child2.addr);

/**
 * * 优点
 * * 1. 类似于复制一个对象,用函数来包装
 * ! 不足
 * ! 所有实例都会继承原型上的属性
 * ! 无法实现复用,为新实例添加属性,其他实例不能使用
 */
 
 
 
/* 5 寄生式继承 ------------------------------------------------------------ */
// 基础对象
var child = {
  name: ['A', 'B', 'C'],
  age: 18,
};

function createObj(obj) {
  function F() {} // 创建一个构造函数
  F.prototype = obj; // 把实例对象赋值给构造函数的原型
  return new F(); // 返回实例化对象
}

// 在原型继承基础上,用函数包裹住创建新实例的方法,为新实例添加属性和方法,并返回新实例
function createChild(obj) {
  var newobj = createObj(obj); // 创建对象 或者用 var newob = Object.create(ob)
  newobj.sayName = function () {
    // 增强对象
    console.log(this.name);
  };
  return newobj; // 指定对象
}

createChild(child);
// ! 主要用来与寄生组合式继承做对比


/* 6 寄生组合式继承---------------------------------------------------------- */
function Child(age) {
  Person.call(this, age);
}

// ! ---------------------------------------------- ! //
function createObj(obj) {
  function F() {}
  F.prototype = obj; // Parent.prototype
  return new F();
}

function prototype(Child, Parent) {
  var prototype = createObj(Parent.prototype);
  prototype.constructor = Child;
  Child.prototype = prototype;
}

prototype(Child, Person);
// ! ---------------------------------------------- ! //

Child.prototype = Object.create(Person.prototype); // 核心代码
Child.prototype.constructor = Child; // 核心代码

// ! 替换为上面俩函数调用 ! //
// Child.prototype = new Person();

var child = new Child(18);
var child1 = new Child(20);

console.log(child.age, child1.age); // 18 20
console.log(child.name, child1.name); // ["A", "B", "C"] ["A", "B", "C"]
child.name.push('D');
console.log(child.name, child1.name); // ["A", "B", "C", "D"] ["A", "B", "C"]
console.log(child.run(), child1.run()); // "A,B,C,D - 18" "A,B,C - 20"
/**
 * * 解决了什么问题
 * * 1. 不存在引入属性共享的问题
 * * 2. 能给超类传参
 * * 3. 拥有原型链,可以复用原型链属性和方法
 * * 4. 不存在两次调用超类型的情况
 * ! 引入了什么问题
 * ! 1. 复杂性
 */

寄生组合式继承.png


AD:给自己打个广告,我们浩鲸持续招新哦,考虑换工作的coder可以联系我试试哦,说不定咱就成同事了呢~