js 继承小结

410 阅读3分钟

js的是利用原型链实现面向对象的类行为,和oo语言中的类有这巨大的不同。

下面讲述几种继承的方式以及一些扩展。

我对js的原型继承的一些理解:

  • js中只有函数存在prototype这个属性,代表原型,object中只有__proto__属性
  • js中函数也是object的一种,所以函数同时存在prototype和__proto__属性,但是这是两个完全不同的属性
  • js中所有的object都是继承于底层的Object.prototype
  • js中所有函数的构造函数是Function
  • Function.prototype是函数(这就是解决原型链循环的关键),正常函数的prototype属性都是一个object
  • js中实例的__proto__总是指向它构造函数的prototype(这个也是原型链的重点)
// 父类
function Animal() {
  this.name = 'animal';
  this.type = 'animal';
}
Animal.prototype.say = function () {
  console.log(this.name);
}

原型链继承, 通过子类的prototype是父类的一个实例,从而实现一条原型链,如下:(Sub代表子类,sub代表子类的一个实例,Super代表父类,super代表父类的实例)

sub.__proto__ = Sub.prototype = super, super.__proto__ = Super.prototype

缺点:父类的数据对象处于子类的prototype中,prototype是所有子类实例所共有的,所以对于引用类型的数据存在一改全改的问题

function Dog() {
  this.name = 'dog'
}
Dog.prototype = new Animal();

var dog = new Dog();
dog.say();

dog的详细结构

为了解决上面的原型链缺点,利用构造函数的优点(组合继承)

function Cat(data) {
  Animal.call(this, data); // 将父类的数据类型在子类复制一份
  this.name = 'cat';
}
Cat.prototype = new Animal();

var cat = new Cat(); // 构造函数再次存储了一份父类的数据类型
cat.say();

cat详细结构,可以发现有个缺点就是父类的数据类型被存储了2遍

ES5中有个api:

Object.create(object1, object2): 返回一个object,object的prototype = object1,object2代表prototype中新增或者覆盖的属性
eg:
var cat = Object.create(new Animal(), {
    name: {
        value: 'cat'
    }
})

上述cat详细结构

参考红宝书(js高级程序设计):这种方式称为原型式继承

function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

结合原型式继承和构造函数,可以有如下的继承方式

function Bird(data) {
  Animal.call(this, data);
  this.name = 'bird';
}
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;

var bird = new Bird();
bird.say();

bird的详细结构:解决了原型链继承和组合继承的问题

构造函数有个特点,就是return的是一个对象的时候new的时候生成的就是这个对象,否则将按照正常的原型链生成对象。eg:

function Test() {
    var arr = [1, 2, 3];
    this.arr = arr;
    return arr;
}
Test.prototype.say = function() {
    console.log(this.arr);
}
var test = new Test();
test.say // undefined

这就是传说中的寄生继承,寄生继承返回的对象跟原型链基本保持一致,唯一的问题就是丢失了子类的构造函数吧,以及第一层prototype

function Horse() {
  var o = Object.create(new Animal());
  o.name = 'horse';
  return o;
}
var horse = new Horse();
horse.say();

horse详细结构:

扩展题目: 如何继承数组?

// 实现数组继承
var push = [].push;
function myArray() {
  var array = Array.apply(this, arguments);
  array.__proto__ = myArray.prototype;
  return array;
}
myArray.prototype = Object.create(Array.prototype)
myArray.prototype.constructor = myArray;
myArray.prototype.push = function(value) {
  console.log('push:', value);
  push.call(this, value);
}
var list = new myArray(1, 2, 3);
list.push(4);

因为数组的独特性所以利用了寄生继承以及object的__proto__属性,以及原型式继承