Javascript的原型链与继承


Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。这部分知识也是JavaScript里的核心重点之一,同时也是一个难点。本文先给出了ES5最经典的寄生组合式继承图的详细描述,然后给出了其实现代码,可以配合该图来加深理解。

1. ES5最经典的寄生组合式继承图

js原型链

  • 注意:如果A.prototype没有constructor,只要B.prototype的原型对象是A.prototype,则b instance of A就为true

2. ES5和ES6的继承

//ES5继承
function Super(name){
    this.name=name
    this.colors=[1,2,3]
}
Super.prototype.sayName=function(){
    alert(this.name)
}
function Sub(name,age){
    Super.call(this,name)//显式传入this作为显示调用者调用父类构造函数
    this.age=age
}
Sub.prototype=Object.create(Super.prototype,{
    constructor:{
        value:Sub
    }
})//避免调用父类的构造函数
Sub.prototype.sayAge=function(){
    alert(this.age)
}

//ES6继承
class Super{
    constructor(name){
        this.name=name
    }
    sayName(){
        alert(this.name)
    }
    static haha(){
        alert(this.hahatext)//Super函数对象的静态属性
    }
}
Super.hahatext="dingxu1"
class Sub extends Super{
    constructor(name,age){
        super(name)//先实例化父构造函数,在更改this的指向,super()之后才有了this!
        this.age=age
    }
    sayAge(){
        alert(this.age)
    }
}
Sub.hahatext="dingxu2"
Super.haha()//dingxu1
Sub.haha()//dingxu2
/***********************************************/
Sub.__proto__ === Super // true 作为一个对象,子类(Sub)的原型(__proto__属性)是父类(Super)
Sub.prototype.__proto__ === B.prototype // true 作为一个构造函数,子类(Sub)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。

  • ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。
  • ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。下面是一个继承Array的例子。
class MyArray extends Array{
    constructor(...args){
        super(...args)
    }
}
var arr=new MyArray()
arr[0]=12
arr.length
arr.length=0
arr[0]

extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。

class VersionedArray extends Array {
  constructor() {
    super();
    this.history = [[]];
  }
  commit() {
    this.history.push(this.slice());
  }
  revert() {
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = new VersionedArray();

x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]

x.push(3);
x // [1, 2, 3]
x.history // [[], [1, 2]]

x.revert();
x // [1, 2]

注意,继承Object的子类,有一个行为差异。

class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}
var o = new NewObj({attr: true});
o.attr === true  // false

上面代码中,NewObj继承了Object,但是无法通过super方法向父类Object传参。这是因为 ES6 改变了Object构造函数的行为,一旦发现Object方法不是通过new Object()这种形式调用,ES6 规定Object构造函数会忽略参数。

posted @ 2018-09-13 19:52  David丁  阅读(143)  评论(0编辑  收藏  举报