深入JavaSript中的继承

66 阅读3分钟

继承

原型链继承

function Person() {
  this.name = 'zs',
    this.friends = []
}

Person.prototype.eating = function () {
  console.log(this.name + 'eating');
}

function Student() {
  this.id = 111
}

Student.prototype = new Person()
// 等于
// var p = new Person()
// Student.prototype = p

Student.prototype.studying = function () {
  console.log((this.name + 'studying'));
}

var s1 = new Student()
var s2 = new Student()
// 会找到原型上的friends添加
s1.friends.push = 'ls'
// 不会找到原型上面添加,自己添加
s1.age = 18
s2.name = 'ww'
console.log(s1);
console.log(s2);
console.log(s1.eating());
console.log(s2.eating());
console.log(s1.friends);
console.log(s2.friends);

原型链继承弊端:

  1. 打印对象,继承的属性看不到
  2. 可能会修改源数据的值
  3. 传入参数不好处理

借用构造函数继承

使用构造函数继承,需要使用 call() 函数

function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.eating = function () {
  console.log(this.name + 'eating');
}

function Student(name, age, friends) {
  // 借用Person构造函数中的赋值代码this.name=name等 为自己赋值
  Person.call(this, name, age, friends)
  this.id = 111
}

Student.prototype = new Person()
// 等于
// var p = new Person() 在这个步骤p对象(也就是s1的原型对象)会多出一些属性 name=undefined ...
// Student.prototype = p

var s1 = new Student('zs', 18, 'ls')
var s2 = new Student('ww', 20, 'll')

console.log(s1);
console.log(s2);

优点:

  • 解决了子类构造函数向父类构造函数中传递参数
  • 可以实现多继承( call 或者 apply 多个父类)

缺点:

  • 方法都在构造函数中定义,无法复用,Person被调用了两次
  • 不能继承原型上的属性和方法,只能继承父类的实例属性和方法,s1的原型对象会多出一些属性,但是这些属性没有必要存在

寄生式继承

var personObj = {
  running: function () {
    console.log('running');
  }
}

function createStudent(name) {
  var stu = Object.create(personObj)
  stu.name = name
  stuObj.studying = function () {
    console.log('studying');
  }
  return stu
}

var stuObj = createStudent('zs')
var stuObj1 = createStudent('ls')
var stuObj2 = createStudent('ww')

原型式继承

我们想要实现的是构造函数的继承,但是我们先看下面的实现对象继承的例子,了解原型式继承的一些原理

// 把info的原型对象绑定到obj上
var obj = {
  name: 'zs',
  age: 18
}

// 方法1
function createObject1(o) {
  var newObj = {}
  Object.setPrototypeOf(newObj, o)
  return newObj
}

// 方法2
function createObject2(o) {
  function Fn() {
    Fn.prototype = o
    var newObj = new Fn()
    // 相当于 newObj.__proto__ = Fn.prototype = o
    return newObj
  }
}

// var info = createObject1(obj)

// 方法3 目前ECMA给我们提供了Object.create()方法,实现的功能与上面的一样
var info = Object.create(obj)

console.log(info);
console.log(info.__proto__);

寄生组合式继承

完整的继承方案

function createObject(o) {
  function Fn() { }
  Fn.prototype = o
  return new Fn()
}

function inheritPrototype(SubType, SuperType) {
  // Object.create会返回一个对象,这个对象指向Person.prototype,相当于上面的createObject函数 所以也可以使用createObject函数来替代create
  // SubType.prototype = Object.create(SuperType.prototype)
  SubType.prototype = createObject(SuperType.prototype)
  Object.defineProperty(SubType.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    writable: true,
    value: Student
  })
}

function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.eating = function () {
  console.log(this.name + '-eating');
}

function Student(name, age, friends, id, score) {
  // 借用Person构造函数中的赋值代码this.name=name等 为自己赋值
  Person.call(this, name, age, friends)
  this.id = id
  this.score = score
}

inheritPrototype(Student, Person)

Student.prototype.studying = function () {
  console.log('studying');
}

var s1 = new Student('zs', 18, 'ls', 111, 100)
var s2 = new Student('ww', 20, 'll', 222, 90)

console.log(s1);
console.log(s2);
s1.eating()
s1.studying()
console.log(s1.constructor.name === s1.__proto__.constructor.name);

原型对象继承

如果直接将父构造函数的原型赋值给子构造函数原型,子构造原型发生改变会直接改变父构造函数原型,不建议使用

son.prototype = father.prototype
var son = new son('小明', 20)
son.show1()
son.show2()

建议使用下面的写法

son.prototype = new father()
son.prototype.constructor = son
console.log(son.prototype);
var son = new son('小明', 20)
son.show1()
son.show2()

注意:第一条语句只是给 son.prototype 重新赋值,但是缺失的原型的 constructor 构造方法,所以需要第二条语句 son.prototype.constructor = son,如下打印的原型(画方括的地方没有 constructor)在 new 一个新的实例的时候,会把这个实例的对象打印出来

类继承

也可以继承静态方法

类继承的时候,子类必须先调用super初始化父类实例

class Father {
  // 创建构造函数 相当于 C++ 的构造函数 Student()
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  // 创建成员方法 
  sayHello(msg) {
    console.log(this.name + msg);
  }
  handler() {
    console.log('1');
    console.log('2');
    console.log('3');
  }
}

class Son extends Father {
  constructor(name, age, gender) {
    super(name, age)
    this.gender = gender
  }
  handler() {
    // 调用父类的方法
    super.handler()
    console.log('4');
    console.log('5');
    console.log('6');
  }
}
var s1 = new Son('张三', 20, '男')
s1.sayHello('你好')
console.log('性别: ' + s1.gender);
s1.handler()