资讯专栏INFORMATION COLUMN

ES6 中Class创建对象与继承实现

zhou_you / 737人阅读

摘要:使用类创建实例对象也是直接对类使用命令,跟中构造函数的用法一致。中没有构造函数,作为构造函数的语法糖,同时有属性和属性,因此同时存在两条继承链。子类的属性,表示构造函数的继承,总是指向父类。

1 Class in ES6

ES6提出了类(Class)的概念,让对象的原型的写法更像面向对象语言写法。 ES6中通过class定义对象,默认具有constructor方法和自定义方法,但是包含在class中的方法不可枚举。

        class Point{
            constructor(){
            this.x=x;
            this.y=y;
            }
        toString(){
            return this.x+this.y;
            }
        }

注意:constructor方法对应ES5的构造函数;创建类的方法不需要使用function关键字,方法之间不需要逗号分隔。

ES5中定义对象时组合使用构造函数模式和原型模式,构造函数模式用于定义实例属性,原型模式用于定义共享方法和共享属性,节省内存,而且支持向构造函数传递参数。

        function Point (x,y){
            this.x=x;
        this.y=y;
        }
        Point.prototype.toString= function(){
            return this.x+this.y;
        }
        
2 ES5和ES6创建实例和对象对比

1)ES5中的Person构造函数和Person原型对象以及实例Person1、Person2的关系:构造函数的prototype属性以及实例的__proto__属性指向原型对象,原型对象的constructor属性指向构造函数。

构造函数的原型(prototype)属性在ES6中依旧存在,这一点和ES5一样:类的方法都定义在类的原型(prototype)上,在类的实例上面调用方法,其实就是调用原型上的方法。

    //实例的constructor方法就是类的原型(prototype)的constructor方法
    class B{
      constructor(){}
    }
    let b = new B();
    console.log(b.constructor === B.prototype.constructor);//true

2)ES6的类可以看做是构造函数的另一种写法(和ES5中构造函数等同的并不是constructor方法):类是function类型,且类本身指向类的prototype对象的constructor属性,这与ES5一样。

        class Point{
          // ...
        }
        console.log(typeof Point) // "function"
        console.log(Point === Point.prototype.constructor) // true

3)使用类创建实例对象也是直接对类使用new命令,跟ES5中构造函数的用法一致。

4)类内部定义的方法都是不可枚举的,这和ES5不同。

5)constructor方法
一个类必须有constructor方法,如果没有就默认添加constructor(){}。虽然类是函数,但是和ES5不同,不通过new而直接调用类会导致类型错误,也就是说这个函数仅当在和new一起使用时才有意义。
6)类的实例
与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

            class Point {
              constructor(x, y) {
                this.x = x;
                this.y = y;
              }
              toString() {
                return "(" + this.x + ", " + this.y + ")";
              }
            }
            var point = new Point(2, 3);
            point.toString() // (2, 3)
            point.hasOwnProperty("x") // true
            point.hasOwnProperty("y") // true
            point.hasOwnProperty("toString") // false
            point.__proto__.hasOwnProperty("toString") // true

以上代码中,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。这些都与ES5的行为保持一致。

与ES5一样,类的所有实例共享一个原型对象。

        var p1 = new Point(2,3);
        var p2 = new Point(3,2);
        p1.__proto__ === p2.__proto__
        //true

可以通过实例的__proto__属性为Class原型添加方法,增加后所有的原型都具有这个方法,这和ES5一样。

        var p1 = new Point(2,3);
        var p2 = new Point(3,2);
        p1.__proto__.printName = function () { return "Oops" };
        p1.printName() // "Oops"
        p2.printName() // "Oops"

7)Class不存在变量提升(hoist),这一点与ES5完全不同。

        new Foo(); // ReferenceError
        class Foo {}

上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把类的声明提升到代码头部。这种规定的原因与继承有关,必须保证子类在父类之后定义。

8)严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。

3 ES6 Class的继承

Class通过extends关键字实现继承,这和ES5通过修改原型链实现继承不同(巧合的是,SASS也通过@extend实现样式继承):

        class ColorPoint extends Point{}

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
      }
      toString() {
        return this.color + " " + super.toString(); // super代表父类原型,调用父类的toString()
      }
    }

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
super这个关键字,既可以当作函数使用,也可以当作对象使用。第一种情况,super作为函数调用时,代表父类的构造函数,只能用在子类的构造函数中。ES6 要求,子类的构造函数必须执行一次super函数。第二种情况,super作为对象时,指代父类的原型对象。

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。(ES5通过原型模式的继承:创建子类,然后将父类的实例赋值给子类原型,也就是重写子类原型,代之以一个新类型的实例。)ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

如果子类没有定义constructor方法,这个方法会被默认添加。在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

        class Point {
          constructor(x, y) {
            this.x = x;
            this.y = y;
          }
        }
        
        class ColorPoint extends Point {
          constructor(x, y, color) {
            this.color = color; // ReferenceError
            super(x, y);
            this.color = color; // 正确
          }
        }

和ES5一样,通过子类创建的实例是父类以及子类的实例:

        let cp = new ColorPoint(25, 8, "green");
        
        cp instanceof ColorPoint // true
        cp instanceof Point // true
4 补充 1)类的prototype属性和__proto__属性(这段还没看明白,太绕了。)

ES5中每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。ES6中没有构造函数,Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
这样的结果是因为,类的继承是按照下面的模式实现的。

        class A {
        }
        class B {
        }
        // B的实例继承A的实例
        Object.setPrototypeOf(B.prototype, A.prototype);
        const b = new B();
        // B的实例继承A的静态属性
        Object.setPrototypeOf(B, A);
        const b = new B();

这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。

2)Object.getPrototypeOf

Object.getPrototypeOf方法可以用来从子类上获取父类。

        Object.getPrototypeOf(ColorPoint) === Point
        // true

因此,可以使用这个方法判断,一个类是否继承了另一个类。

3)实例的__proto__属性

子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类实例的原型的原型,是父类实例的原型。

            
            var p1 = new Point(2, 3);
            var p2 = new ColorPoint(2, 3, "red");
            p2.__proto__ === p1.__proto__ // false
            p2.__proto__.__proto__ === p1.__proto__ // true

总的来说,ES6是对ES5的形式上的改变,真实内容依旧不变,本质上依旧是通过原型链实现继承。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81821.html

相关文章

  • ES6 Class创建对象继承实现

    摘要:使用类创建实例对象也是直接对类使用命令,跟中构造函数的用法一致。中没有构造函数,作为构造函数的语法糖,同时有属性和属性,因此同时存在两条继承链。子类的属性,表示构造函数的继承,总是指向父类。 1 Class in ES6 ES6提出了类(Class)的概念,让对象的原型的写法更像面向对象语言写法。 ES6中通过class定义对象,默认具有constructor方法和自定义方法,但是包含...

    wind5o 评论0 收藏0
  • JavaScript基础: 类继承

    摘要:类的方法相当于之前我们定义在构造函数的原型上。的构造函数中调用其目的就是调用父类的构造函数。是先创建子类的实例,然后在子类实例的基础上创建父类的属性。 前言   首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。    许久已经没有写东西了,因为杂七杂八的原因最近一直没有抽出时间来把写作坚持下来,感觉和跑步一...

    liuchengxu 评论0 收藏0
  • ES6】更易于继承的类语法

    摘要:的类使用熟悉的关键字指定类继承的函数,并且可以通过方法访问父类的构造函数。例如继承一个的类继承了,术语上称为基类,为派生类。例如注意到上例中,不仅是派生类的实例,也是派生类的实例,内建对象继承的实用之处是改变返回对象的类型。 和其它面向对象编程语言一样,ES6 正式定义了 class 类以及 extend 继承语法糖,并且支持静态、派生、抽象、迭代、单例等,而且根据 ES6 的新特性衍...

    Lionad-Morotar 评论0 收藏0
  • 如何继承Date对象?由一道题彻底弄懂JS继承

    摘要:前言见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正。倘若用的是中文搜索。所以最终的实例对象仍然能进行正常的原型链回溯,回溯到原本的所有原型方法这样通过一个巧妙的欺骗技巧,就实现了完美的继承。 前言 见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正。 20180201更新: 修改用词描述,如组合寄生式改成寄生组合式,修改多处笔误(感谢@Yao Ding的...

    sunnyxd 评论0 收藏0
  • JavaScript继承理解:ES5继承方式+ES6Class继承对比

    摘要:寄生组合式继承的继承方式有多种主要有原型链继承借用构造函数组合式继承寄生式继承和寄生组合式继承。中利用定义类,实现类的继承子类里调用父类构造函数实现实例属性和方法的继承子类原型继承父类原型,实现原型对象上方法的继承。 JavaScript中实现继承   在JavaScript中实现继承主要实现以下两方面的属性和方法的继承,这两方面相互互补,既有共享的属性和方法,又有特有的属性和方法。 ...

    liaoyg8023 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<