js 面向对象 ES5 AND ES6

1. ES5实现

父类:

// 职员类
function Employees(id,name,salary) {  // 属性
    this.id = id;
    this.name = name;
    this.salary = salary;
}
Employees.prototype.work = function (dish) { // 方法
    return dish
};

子类继承父类:

function Waiter(id, name, salary) {
    Employees.call(this, id, name, salary) //  使用call, 继承父类
}
Waiter.prototype = Object.create(Employees.prototype);// 继承父类原型
Waiter.prototype.constructor= Waiter; // 设置constructor指向自己
Waiter.prototype.work = function (arg) { // 重写原型上的方法
    if (arg instanceof Array){
        return arg
    } else { //上菜行为
        console.log('not a array')
    }
};

在ES5继承的实现非常有趣的,由于没有传统面向对象类的概念,Javascript利用原型链的特性来实现继承,这其中有很多的属性指向和需要注意的地方。

2. ES6实现

父类:

// 职员类
class Employees {
    constructor(id, name, salary){ // 属性
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
    work(dish){  // 方法
        return dish
    }
}

金沙官网线上,子类继承:

// Waiter
class Waiter extends Employees { // 使用关键字extends,继承父类
    constructor(...arg){ // 继承父类
        super(...arg)
    }
    work(arg){  // 重写父类中work方法
        if (arg instanceof Array){ //数组的话,记录点菜
            return arg
        } else { //上菜行为
            console.log('上菜')
        }
    }
}

 

原型链的特点和实现已经在之前的一篇整理说过了,就是通过将子类构造函数的原型作为父类构造函数的实例,这样就连通了子类-子类原型-父类,原型链的特点就是逐层查找,从子类开始一直往上直到所有对象的原型Object.prototype,找到属性方法之后就会停止查找,所以下层的属性方法会覆盖上层。

一个基本的基于原型链的继承过程大概是这样的:

//先来个父类,带些属性
function Super(){
    this.flag = true;
}
//为了提高复用性,方法绑定在父类原型属性上
Super.prototype.getFlag = function(){
    return this.flag;
}
//来个子类
function Sub(){
    this.subFlag = false;
}
//实现继承
Sub.prototype = new Super;
//给子类添加子类特有的方法,注意顺序要在继承之后
Sub.prototype.getSubFlag = function(){
    return this.subFlag;
}
//构造实例
var es5 = new Sub;

原型链实现的继承主要有几个问题:
1、本来我们为了构造函数属性的封装私有性,方法的复用性,提倡将属性声明在构造函数内,而将方法绑定在原型对象上,但是现在子类的原型是父类的一个实例,自然父类的属性就变成子类原型的属性了;
这就会带来一个问题,我们知道构造函数的原型属性在所有构造的实例中是共享的,所以原型中属性的改变会反应到所有的实例上,这就违背了我们想要属性私有化的初衷;
2、创建子类的实例时,不能向父类的构造函数传递参数

function Super(){
    this.flag = true;
}
function Sub(){
   this.subFlag = false;
}
Sub.prototype = new Super;
var obj = new Sub();
obj.flag = flase;  //修改之后,由于是原型上的属性,之后创建的所有实例都会受到影响
var obj_2 = new Sub();
console.log(obj.flag)  //false;

为了解决以上两个问题,有一个叫借用构造函数的方法
只需要在子类构造函数内部使用apply或者call来调用父类的函数即可在实现属性继承的同时,又能传递参数,又能让实例不互相影响

function Super(){
    this.flag = true;
}
function Sub(){
    Super.call(this)  //如果父类可以需要接收参数,这里也可以直接传递
}
var obj = new Sub();
obj.flag = flase;
var obj_2 = new Sub();
console.log(obj.flag)  //依然是true,不会相互影响

结合借用构造函数和原型链的方法,可以实现比较完美的继承方法,可以称为组合继承:

function Super(){
    this.flag = true;
}
Super.prototype.getFlag = function(){
    return this.flag;     //继承方法
}
function Sub(){
    this.subFlag = flase
    Super.call(this)    //继承属性
}
Sub.prototype = new Super;
var obj = new Sub();
Super.prototype.getSubFlag = function(){
    return this.flag;
}

这里还有个小问题,Sub.prototype = new Super; 会导致Sub.prototype的constructor指向Super;
然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,所以应该添加一句纠正:
Sub.prototype.constructor = Sub;

看完ES5的实现,再来看看ES6的继承实现方法,其内部其实也是ES5组合继承的方式,通过call借用构造函数,在A类构造函数中调用相关属性,再用原型链的连接实现方法的继承

class B extends A {
  constructor() {
    return A.call(this);  //继承属性
  }
}
A.prototype = new B;  //继承方法

ES6封装了class,extends关键字来实现继承,内部的实现原理其实依然是基于上面所讲的原型链,不过进过一层封装后,Javascript的继承得以更加简洁优雅地实现

本文由金沙官网线上发布于Web前端,转载请注明出处:js 面向对象 ES5 AND ES6

您可能还会对下面的文章感兴趣: