ES6之什么是class?

传统的 javascript 中只有对象,没有类的概念。它是基于原型的面向对象语言。 原型对象特点就是将自身的属性共享给新对象。这样的写法相对于其它传统面向 对象语言来讲,很有一种独树一帜的感脚!非常容易让人困惑!

首先大家要明白,为什么要出现 class ?

首先回顾我们的 JavaScript 编程思想的发展史。 从 JS 诞生之时,刚开始做的就是面向过程的编程,把一个问题给解释清楚了, 几行 js 就可以搞定。随着 js 的发展以及浏览器对于 js 执行速度越来越高。我们 对于 js 实现的功能越来越多,伴随代码量也会越来越多,我们仍然使用面向过 程式的编程方案,就会有问题。 我们制作打怪兽游戏:

攻击 function attack()

逃跑 function escape()

加血 function resume()

会关注这三个方法的具体实现,并且反复调用这些方法完成游戏。这是面向过程 的思路。 当我们增加打怪兽的使用者时,单纯这几个方法不足以完成多人游戏。 我们需要更高级的思想面向对象,尽管 javascript 不具备面向对象的特征(继承, 封装,多态)。但是我们可以采用类似的这种思想的方式去改写这样的需求。

function BraveMan() {
}
BraveMan.prototype.attack = function () {}
Braveman.prototype.escape = function () {}
BraveMan.prototype.resume = function() {}

基于我们原来过程的基础之上,我们封装这样的构造函数,用于产生可以多次执 行这样过程的对象。这样的话我们的打怪兽,不单纯如何打怪兽(面向过程), 而是变成了 谁能打怪兽(类似面向对象的思想),这里的勇者就是我们想要的 对象,可以多次实例化。这也是这种思想给我们带来的好处,模块化,可扩展等 好处。

在我们的日常 codeing 中,很多大的项目当中都需要使用这种类似面向对象的思 想去进行编程,在 Javascript 中不存在面向对象,我们采用的类似面向对象的过 程叫,基于原型编程,下面是工作中的存在的代码(音乐播放器中的两个模块):

// ControlIndex 模块
function ControlIndex(len) {
    // this 指向 Control 对象
    // Control 上挂载两个属性一个为 index 初始值为 0
    // len 为当前数据长度
    this.index = 0;
    this.len = len;
}

// 将方法挂在对象得原型上
ControlIndex.prototype = {
    prev: function () {
        return this.getIndex(-1);
    },
    next: function () {
        return this.getIndex(1);
    },
    getIndex: function (val) {
        var index = this.index;
        var len = this.len;
        var curIndex = (index + val + len) % len;
        this.index = curIndex;
        return curIndex;
    }
}

// AudioManager 模块
function AudioManager(){
    // 创建一个音频 audio 对象
    this.audio = new Audio();
    // 默认音乐状态暂停
    this.status = 'pause';
}
AudioManager.prototype = {
    play:function(){
        this.audio.play();
        this.status = 'play';
    },
    pause:function(){
        this.audio.pause();
        this.status = 'pause';
    },
    setAudioSource:function(src){
        this.audio.src = src;
        // 重新加载音频元素
        this.audio.load();
    }
}

在这里使用的基于原型编程的一个例子,将一个项目中的不同模块分解,每个模 块使用这种方式进行编程。复用性更好,同时分工明确,不单单是 A 方法做完 B 方法做,而是统一的交给管理对象去执行这些方法。

对于 Javascript 的函数来说,我们的函数是由很多不完善的地方,首先我们通过 function 声明的函数,可以声明,普通方法,构造函数,单纯是这两个函数我们 是没有犯法区分的,在之前我们默认采用大头峰式写法,表明构造函数,但是必 须每个遵守才可以,而且容易出问题,当把构造函数当成普通函数来执行,会产 生全局变量,还可能会报错。

基于上面的种种现象: 我们需要类似的面向对象的思想进行编程。 我们的原始的 function 声明的构造函数,约束性,安全性不够,不足以支撑这 种思想。

所以在新的语法规范当中 ECMAScript6 中引入了 class,基于原有 function 的方式 的语法糖,让我们使用起来,更方便,更安全,目的性更强。

而在 ES6 中引入了 Class(类)这个概念,通过 class 关键字可以定义类。该关键 字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语言。ES6 的写 法就会是这个样子:

class Person{//定义了一个名字为 Person 的类
    constructor(name,age){//constructor 是一个构造方法,用来接收参数
        this.name = name;//this 代表的是实例对象
        this.age=age;
    }
    say(){//这是一个类的方法,注意千万不要加上 function
        return "我的名字叫" + this.name+"今年"+this.age+"岁了";
    }
}
var obj=new Person("yinchengnuo",18);
console.log(obj.say());//我的名字叫 yinchengnuo今年 18 岁了

但是要注意的是:

1.在类中声明方法的时候,千万不要给该方法加上 function 关键字

2.方法之间不要用逗号分隔,否则会报错

通过以下代码可以看出类实质上就是一个函数。类自身指向的就是构造函数。所 以可以认为 ES6 中的类其实就是构造函数的另外一种写法!

console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true

以下代码说明构造函数的 prototype 属性,在 ES6 的类中依然存在着。

console.log(Person.prototype);//输出的结果是一个对象

实际上类的所有方法都定义在类的 prototype 属性上。 一起来证明一下:

Person.prototype.say=function(){//定义与类中相同名字的方法。成功实现了覆盖!
 return "我是来证明的,你叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("yinchengnuo",23);
console.log(obj.say());//我是来证明的,你叫 yinchengnuo今年 23 岁了

当然也可以通过 prototype 属性对类添加方法。如下:

Person.prototype.laodeng=function(){
    return "我是通过 prototype 新增加的方法,名字叫 yyy";
}
var obj=new Person("ycn",18);
console.log(obj.laodeng());//我是通过 prototype 新增加的方法,名字叫yyy

还可以通过 Object.assign 方法来为对象动态增加方法:

Object.assign(Person.prototype,{
    getName:function(){
        return this.name;
    },
    getAge:function(){
        return this.age;
    }
})
var obj=new Person("ycn",1);
console.log(obj.getName());//ycn
console.log(obj.getAge());//1

constructor 方法是类的构造函数的默认方法,通过 new 命令生成对象实例时, 自动调用该方法。

class Box{
    constructor(){
        console.log("啦啦啦,今天天气好晴朗");//当实例化对象时该行代码会执行。
    }
}
var obj=new Box();

constructor 方法如果没有显式定义,会隐式生成一个 constructor 方法。所以即 使你没有添加构造函数,构造函数也是存在的。constructor 方法默认返回实例对 象 this,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对 象不是该类的实例。

class Desk{
    constructor(){
        this.xixi="666";
    }
}
class Box{
    constructor(){
        return new Desk();// 这里没有用 this,直接返回一个全新的对象
    }
}
var obj=new Box();
console.log(obj.xixi);//666

constructor 中定义的属性可以称为实例属性(即定义在 this 对象上),constructor 外声明的属性都是定义在原型上的,可以称为原型属性(即定义在 class 上)。 hasOwnProperty()函数用于判断属性是否是实例属性。其结果是一个布尔值, true 说明是实例属性,false 说明不是实例属性。in 操作符会在通过对象能够访 问给定属性时返回 true,无论该属性存在于实例中还是原型中。

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box=new Box(12,88);
console.log(box.hasOwnProperty("num1"));//true
console.log(box.hasOwnProperty("num2"));//true
console.log(box.hasOwnProperty("sum"));//false
console.log("num1" in box);//true
console.log("num2" in box);//true
console.log("sum" in box);//true
console.log("say" in box);//false

类的所有实例共享一个原型对象,它们的原型都是 Person.prototype,所以 proto 属性是相等的。

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
//box1 与 box2 都是 Box 的实例。它们的__proto__都指向 Box 的 prototype
var box1=new Box(12,88);
var box2=new Box(40,60);
console.log(box1.__proto__===box2.__proto__);//true

由此,也可以通过 proto 来为类增加方法。使用实例的 proto 属性改写原型,会 改变 Class 的原始定义,影响到所有实例,所以不推荐使用!

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box1=new Box(12,88);
var box2=new Box(40,60);
box1.__proto__.sub=function(){
    return this.num2-this.num1;
}
console.log(box1.sub());//76
console.log(box2.sub());//20

class 不存在变量提升,所以需要先定义再使用。因为 ES6 不会把类的声明提升 到代码头部,但是 ES5 就不一样,ES5 存在变量提升,可以先使用,然后再定义。

//ES5 可以先使用再定义,存在变量提升
new A();
function A(){
}
//ES6 不能先使用再定义,不存在变量提升 会报错
new B();//B is not defined
class B{
}

这是我们对 ES6 中 class(类)的概念的了解,既然提出了类,这个类又是怎么实现的呢?

在这首先要了解一下类的继承:有三种属性,公有属性,私有属性, 静态属性(Es7)/静 态类(Es6)

继承公有属性:

function Parent(){
    this.name = 'parent';
}
new Parent();//this 指向当前实例
Parent() //this 指向 window
function Child(){
    this.age = 9;
    Parent.call(this);//相当于 this.name = 'parent' //继承父类属性
}

继承父类属性:

function Parent(){
    this.name = 'parent';
}
Parent.prototype.eat = function(){
    console.log('eat')
}
function Child(){
    this.age = 9;
    Parent.call(this);//相当于 this.name = 'parent' //继承父类属性
}
Child.prototype.smoking = function(){
    console.log('smoking')
}

Child.prototype = Parent.prototype;//这个不叫继承
//因为这样如果改变 Child.prototype 加属性,Parent.prototype 的实例也会有这个属性,,
此时这两者属于兄弟关系
Child.prototype._proto_ = Parent.prototype // 方法一
//object.create
Child.prototype = object.create(Parent.prototype); // 常用,方法二    
function create(parentPrototype,props){
    function Fn(){}
    Fn.prototype = parentPrototype;
    let fn = new Fn();
    for(let key in props){
        Object.defineProperty(fn,key,{
            ...props[key],
            enumerable:true
        });
    }
    return fn();
}
Child.prototype = create(Parent.prototype,{
    constructor: {
        value:Child
    }
})

继承公有属性和私有属性:

之前的继承都是原型链的继承,圣杯模式 在 Class 中 的继承,有什么不一样了呢, 在 ES5 中真正的继承应该是什么样呢:

function Animal (weight, name) {
    this.name = name
    this.age = 0
    this.weight = 10
}
Animal.prototype.eat = function () {
    console.log('animal eat')
}
Animal.prototype.drink = function () {
    console.log('a d')
}
function Person(name, weight) {
    Animal.call(this, weight, name)
}
Person.prototype = Object.create(Animal.prototype)
Person.prototype.eat = function () {
    console.log('Person eat')
}
 var p = new Person('dxm', 70) 

这里有两点,要注意的地方,首先是

1 父类构造函数 为什么要 call

2 原型需要重写。 在 class 中继承就变的简单了许多,同比上面的例子通过 class 来实现

class Animal {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log('a e')
    }
    drink() {
        console.log('a d')
    }
}
class Person extends Animal {
    constructor(name) {
        super(name)
    }
    eat() {
        console.log('p e')
    }
}

在这里实现之后,多了两个大家不认识的次, extends super extends 后面跟的就是我们要继承的内容

super 有些注意点 子类也叫派生类,必须在 constructor 中调用 super 函数,要不无法使用 this, 准确的说子 类是没有 this 的 就算是继承之后什么也不写,默认的也会填上去

class Person extends Animal {
    constructor(...arg) {
        super(...arg)    
    }
}

在调用 super 之前不可以使用 this 回报错 super 可以作为对象使用,指向的是 父类的原型 super 调用父类方法时,会绑定子类的 this。

类只能 new:

class Parent{
    //私有属性
    constructor(){
        this.name = 'parent',
        this.age = '40'
    }
    //公有属性,原型上的方法
    eat(){
        console.log('eat')
    }
    //静态方法/属性 es6/es7
    //属于类上的方法 Child.a()
    static b(){
        return 2
    }
}
new Parent();
class Child extends Parent{ //继承父亲的私有和公有
    //私有属性
    constructor(){
        super() // 相当于 Parent.call(this)
        this.name = 'child'
    }
    //公有属性,原型上的方法
    smoking(){
        console.log('smoking')
    }
    //静态方法/属性 es6/es7
    //属于类上的方法 Child.a()
    static a(){
        return 1
    }
}
let child = new Child();
console.log(child.name,child.age,child.eat(),child.smoking,Child.b())
//类可以继承公有,私有和静态
//父类的构造函数中返回类一个引用类型,会把这个引用类型作为子类的 this

我们首先写一个创建类的函数:

//检测实例是不是 new 出来的
function _classCallCheck(instance,constructor){
    if(!(instance instanceof constructor)){
        throw new Error('Class constructor Child cannot be invoked without new')
    }
}

//constructor 构造函数
//prprotoPropertys 构造函数原型
//staticPropertys 静态方法的描述
function definePropertys(target,arr){
    for(let i=0;i<arr.length;i++){
        Object.defineProperty(target,arr[i].key,{
            ...arr[i],
            configurable : true,
            enumerable : true,
            writable:true
        })
    }
}
function _createClass(constructor,protoPropertys,staticPropertys){
    if(protoPropertys.length > 0){
        definePropertys(constructor.prototype,protoPropertys)
    }
    if(staticPropertys.length > 0){
        definePropertys(constructor,staticPropertys)
    }
}
let Parent = function(){
    //写逻辑
    function P(){
        _classCallCheck(this,P)
        this.name = 'parent';
        //return {}
    }
    _createClass(P,//属性描述器
        [
            {
                key: 'eat',
                value: function () {
                    console.log('吃')
                }
            }
        ],
        [
            {
                key:'b',
                value:function () {
                    return 2;
                }
            }
        ]
    )
    return P;
}()
let p = new Parent();
console.log(p.eat())

这样我们的类就创建完成了

猜你喜欢

转载自blog.csdn.net/weixin_40851188/article/details/90711216
今日推荐