JavaScript的类以及类的继承都经理了什么

使用类可以大大简化我们的代码,本篇将会说一说从ES 6之前的继承到ES 6规定的类

ES 6之前

1. 类的定义

我们可以使用三种方法来创建一个数组或对象

let arr1 = [1,2,3];
let arr2 = new Array(1,2,3);
let arr3 = Array(1,2,3);

let obj1 = { color: 'red' };
let obj2 = new Object({ color: 'red' });
let obj3 = Object({ color: 'yellow' });

但是我们最常用也是被推荐的方法都是字面量创建,这是因为如果使用new方法,会有重载的概念:意思就是说,根据传入的参数不同,会有不同的结果,举个栗子,了解一下

let arr = new(3);	// Array(3) {length: 3}

我们想要创建一个[3]结果他给我们创建了以长度为3的空数组,显然这就有问题,这里先不说这个,前面说的所有都是为了引出所谓的安全类

// 创建一个构造函数(也就是一个类)
function People(name,age) {
    this.name = name;
    this.age = age;
};
// 分别new和直接调用People创建一个实例
let p1 = new People('zs',20);
let p2 = People('ls',30);
console.log(p1,p2);		People{}  undefined;

当我们直接调用People创建实例时,它会将People当做普通函数,而普通函数中的this是指向他的直接调用者,在这里也就是window了,所以我们直接调用People创建时,不仅创建不成功,而且还会造成全局污染,如果全局也有一个name变量,那么它的值就会被改变,所以我们说这个类不安全,那么如何创建一个安全类

// 创建一个安全类
function People(name,age) {
    if(this instanceof People) {
        this.name = name;
        this.age = age;
    } else {
        return new People(name,age);
    }
}

这样我们自己创建的类也就可以像Array Object那样通过People直接调用了,他们的实现原理是一致的

2. 类的继承(重头戏)

1. 构造函数式继承

// 创建一个父类
function Animal(legs) {
 	this.legs = legs;
};
// 在父类原型上添加一个getLeg方法
Animal.prototype.getLeg = function() { console.log(this.legs); }; 
// 创建一个子类,通过构造函数继承父类
function Dog(legs, eyes) {
    Animal.call(this,legs);	// 将Animal这个构造函数绑定在Dog中实现继承
    this.eyes = eyes;
};
// 创建一个子类实例
let animal = new Animal(8);
animal.getLeg();			// 8
let dog = new Dog(4,2);
cosonle.log(dog.legs);		// 4
dog.getLeg();				// dog.getLeg() is not a function;

可以看出,构造函数式继承,只能继承父类的自身属性(也就是卸载大括号里的),但是不能继承父类原型上的属性

2. 类式继承(原型式继承)

如果我们加上Dog.prototype = Animal.prototype是不是就可以继承父类的原型方法了?

function Animal(legs) {this.legs = legs};
Animal.prototype.getLeg = function() {
     return `动物有${this.legs}条腿`;
    };
function Dog(legs) {
    Animal.call(this,legs);
};
Dog.prototype = Animal.prototype;
let dog = new Dog(4);
console.log(dog.getLeg());  // 动物有四条腿;

// 但是当我们有其他需求时(比如Dog的getLeg 要返回"狗有4条腿")
// 此时修改Dog原型方法
Dog.prototype.getLeg = function() {
        return `狗有${this.legs}条腿`;
    };
let animal = new Animal(4);
console.log(animal.getLeg());   //狗有4条腿
console.log(dog.getLeg());      //狗有4条腿

我们只希望改变Dog原型的方法,结果Animal的原型的方法也被改变了,这不合理,这是应为引用类型传递的是地址,这个可以看看JS数据类型的事,可以通过新建解决

Dog.prototype = Object.create(Animal.prototype);
// 或者
Dog.prototype = new Animal();

问题(还挺多):

  1. 子类原型对象上出现多余的父类的自身属性,而且错误(值是undefined)

  2. 多执行了一次父类构造函数

  3. 子类的constructor属性获取错误,指向错误

  4. 无法复用 构造函数中存储属性(自身属性)问题

3. 组合式继承

结合前两中继承方式,就可以实现一个比较完整的继承,但是必须解决子类constructor指向的问题

function People (name,age) {
    this.name = name;
    this.age  =  age;
};
People.prototype.getName = function() {
    console.log(`我的名字是${this.name}`);
};
function Kid(name,age,sex) {
    People.call(this,name,age);		// 使用构造函数的方法继承父类自身属性
    this.sex = sex;
};
Kid.prototype = Object.create(People.prototype);	// 原型继承继承父类原型属性
Kid.prototype.constructor = Kid();					// 改变constructor指向

let kid = new Kid('小白',10,'男');
kid.getName();

问题 :多执行了一次父类构造函数;无法复用 构造函数中存储属性(自身属性)问题

4. 寄生式继承

寄生式:在一个继承方法中,创建一个寄生类,等于父类的原型,在实例寄生类赋值给子类原型

// 创建一个寄生类
function inherit(child, parent) {
    //1 定义寄生类
    function F() {
        // 4. 修改constructor指向
        this.constructor = child;
    };
    // 2. 让寄生类的原型等于父类的原型
    F.prototype = parent.prototype;
    // 3. 将寄生类 的实例赋值给子类原型
    child.prototype = new F();
    return child;
};
function People (name,age) {
    this.name = name;
    this.age  =  age;
};
People.prototype.getName = function() {
    console.log(`我的名字是${this.name}`);
};
function Kid(name,age,sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
};
inherit(Kid,People);	// 寄生式继承

let kid = new Kid('小白',10,'男');

kid.getName();			// 我的名字是小白

问题: 无法复用 构造函数中存储属性(自身属性)问题

5. 寄生组合式继承(寄生+构造函数)

这是ES 6之前的最高配置,只需要在子类对象中添加构造函数式继承语句People(父类).call(this,参数1,参数2)就OK了,基本没问题

ES 6

1. 类的定义

// 创建类
class Book {
    // 构造函数
    constructor (title, price) {
        // 实例数据(每个实例都会有的特有数据)
        this.title = title;
        this.price = price;
    }
    // 原型方法(每个实例都可以使用的公用方法)
    getTitle() {
        console.log(this.title);
    }
    getPrice() {
        console.log(this.price);
    }
    // 原型属性(添加给自身的属性,自能自己访问)
    set num(val) {
        this._num = val;
    }
    get num() {
        return this._num;
    }
    //  静态数据(只能通过Book.xx访问)
    static get write() {
        return 'zzq';
    }
    static getWrite() {
        return this.write;
    }
}
// 外部添加静态数据
Book.msg = 'hello';
Book.getMsg = function() {
    return this.msg;
}
// 实例化
let b1 = new Book('javascript', 59);
let b2 = new Book('面试题', 60);
console.log(b1, b2);	// Book {title: "javascript", price: 59}
console.log(b1._num);		// undefined;
console.log(Book.write);	//zzq

类的继承

ES 6继承非常容易,而且还可以重写 构造函数,不过既然是继承,那就要给constructor添加super(),它的参数就是父类传入的参数

// JsBook 继承于 Book
class JsBook extends Book{
    // 可以重写构造函数
    constructor(title, price, score) {
        // 继承父类的部分
        super(title, price);
        // 自己拥有 的部分
        this.score =  score;
    }
}
let jb = new JsBook('js', 60, 100);

到这里,JS类的发展史就说完了,内容挺多,要完全理解还是需要点时间滴,感谢观看,记得点赞~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值