ES6必知必会 (六)—— Class

Class

1.在之前的JS面向对象编程中,如果定义一个构造函数,一般来说是这样:

function Person( name , age ) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
}

这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,对于一些新学习JS的程序员来说不太容易理解,ES6 引入了 Class(类)这个概念,提供了更接近传统语言的写法,上述代码用 ES6 实现就是:

class Person {
  constructor( name , age ) {
    this.name = name;
    this.age = age;
  }

  say() {
    return return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
  }
}

上述代码中的constructor方法,这就是构造方法,而this关键字则代表实例对象

2.constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,会被默认添加一个空的constructor方法;

class Person {
}

// 等同于
class Person {
  constructor() {}
}

3.constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象,但实际开发中不建议这么做;

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo  //false

此时constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例,因此会返回 false;

4.使用new运算符用于生成类的实例对象,如果忘记加上new,像函数那样调用Class,将会报错;

class Person {
  // ...
}

// 报错
var person = Person();

// 正确
var person = new Person();

5.实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上),并且,类的所有实例共享一个原型对象

class Person {

  //自身属性
  constructor( name , age ) {
    this.name = name;
    this.age = age;
  }
  
  //原型对象的属性
  say() {
    return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
  }
}

var person = new Person( 'Jack' , 23);
person.hasOwnProperty('name')                   // true
person.hasOwnProperty('age')                    // true
person.hasOwnProperty('say')                    // false
person.__proto__.hasOwnProperty('say')          // true

上述代码中,name 和 age 实例对象person自身的属性(因为定义在this变量上) ,所以hasOwnProperty方法返回true,而say是原型对象的属性(因为定义在Person类上),所以hasOwnProperty方法返回false;

6.与函数一样,可以使用表达式的形式定义一个类,即

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
}

值得注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,用来指代当前类;如果类的内部没用到的话,可以省略Me,写成下面的形式:

const MyClass = class {
  getClassName() {
    return Me.name;
  }
}

7.采用 Class 表达式,可以写出立即执行的 Class。

let Person = new class {
  constructor(name , age ) {
    this.name = name;
    this.age = age;
  }

  say() {
    return 'My name is ' + this.name + ', I am ' + this.age + ' years old';
  }
}('Jack' , 23 );

Person.say();   // 'My name is Jack, I am 23 years old'

8.ES6中类不存在变量提升,不会把类的声明提升到代码头部,即如果类使用在前,定义在后,这样会报错;

new Person();   //Uncaught ReferenceError : Person is not defined
class Person{}

9.跟ES5一样,ES6的类定义中,也不支持私有属性和私有方法,只能通过变通的方法模拟实现(命名上加下划线等);

10.可以在一个类的方法前,加上static关键字,声明其为‘静态方法’,这样就表示该方法不会被实例继承,而是直接通过类来调用;

class Foo {
  static classMethod() {
    return 'Hello World';
  }
}

Foo.classMethod()   // 'Hello World'

var foo = new Foo();
foo.classMethod()   // TypeError: foo.classMethod is not a function

11.父类的静态方法,可以被子类继承,也可以从super对象上调用。

class Parent {
  static classMethod() {
    return 'Hello World';
  }
}

//子类继承
class Child extends Parent {
}

Child.classMethod() // 'Hello World'

//super对象上调用
class Child extends Parent {
  static classMethod() {
    return super.classMethod();
  }
}

Child.classMethod()  // 'Hello World'

12.静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性,并且目前只能有以下方式定义:

class Foo {
}

Foo.prop = 1;
Foo.prop // 1

13.Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多;

class Parent {
  //
}

//子类继承
class Child extends Parent {
}

上面代码定义了一个Child类,该类通过extends关键字,继承了Parent类的所有属性和方法;

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

class Parent {
  constructor(name , age ) {
    this.name = name;
    this.age = age;
  }
}

//子类继承
class Child extends Parent {
    constructor( name , age , hobby ) {
        super( name , age );  // 调用父类的constructor( name , age )
        this.hobby = hobby;
    }
    
    say(){
        return 'My name is ' + this.name + ', I am ' + this.age + ' years old , I like ' + this.hobby;
    }
}

15.super关键字,既可以当作函数使用,也可以当作对象使用:

  • 作为函数调用时,代表父类的构造函数,并且只能用在子类的构造函数之中,用在其他地方就会报错;
    <pre>
    class A {}

    class B extends A {
    m() {
    super(); // 报错
    }
    }
    </pre>

  • 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
    <pre>
    class A {
    p() {
    return 2;
    }
    }

    class B extends A {
    constructor() {
    super();
    console.log(super.p()); // 2
    }
    }

    let b = new B();
    </pre>

需要注意的是,super指向的是父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m                 // undefined

16.ES6 规定,通过super调用父类的方法时,super会绑定子类的 this ;

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

let b = new B();
b.m() // 2

上面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()会绑定子类B的this,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)。

17.由于绑定子类的this,因此如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性;

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
  }
  
  print(){
     console.log(super.x);  // undefined
     console.log(this.x);   // 3
  }
}

let b = new B();
b.print();

上面代码中,super.x 赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,而A的原型上没有x属性,所以返回undefined。

18.如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。

class Parent {
  static myMethod(msg) {
    console.log('static');
  }

  myMethod(msg) {
    console.log('instance');
  }
}

class Child extends Parent {
  static myMethod() {
    super.myMethod();
  }

  myMethod(msg) {
    super.myMethod();
  }
}

Child.myMethod(); // static 

var child = new Child();
child.myMethod(); // instance 

上述代码表明super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。

19.Class 作为构造函数的语法糖,同时有prototype属性和 _proto_属性,因此同时存在两条继承链

  • 子类的_proto_属性,表示构造函数的继承,总是指向父类。
  • 子类prototype属性的_proto_属性,表示方法的继承,总是指向父类的prototype属性

<pre>
class A {
}

class B extends A {
}

B.\__proto__ === A // true
B.prototype.\__proto__ === A.prototype // true

</pre>

20.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]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • class的基本用法 概述 JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子: ...
    呼呼哥阅读 4,067评论 3 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 拓展阅读: 知乎周源:问答社区创业是怎样体验? http://www.cyzone.cn/a/20150417/2...
    叫我老王就好阅读 514评论 0 2