ES6原生Class知识介绍

前言

js的原生Class直到ES6才实现,而Class已经在其他语言中实现了几十年,现在我们研究一下原生Class。

本文大量参考阮一峰老师的ES6手册。

浏览器支持情况

IE方面,由于国内需要支持到IE8,所以支持Class就别想了。
移动方面,包括微信浏览器、QQ浏览器、其他主流浏览器,都支持Class,所以移动端可以放心用。

ES5之前定义构造函数的常见方法

// 先定义一个函数,强行叫它构造函数,大写的P也不是必须的,只是约定俗成
function Point(x, y) {
  this.x = x; // 构造函数的属性都定义在函数内部
  this.y = y; // this指向实例对象
}

// 构造函数的方法都定义在构造函数的原型上
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

// new 一个对象,就OK了
var p = new Point(1, 2);

ES6定义类的常见方法

由于要兼容过去的构造函数写法,所以ES6的类其实就是语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的class改写,就是:

//定义类
class Point {
  constructor(x, y) { // 定义构造方法
    this.x = x; // this指向实例对象
    this.y = y;
  }

  toString() { // 定义一个方法,注意这里没有function关键字
    return '(' + this.x + ', ' + this.y + ')'; // this指向实例对象
  }
}

class就是function的另一种写法,本质还是function

class Point {
}

typeof Point // "function" 类的数据类型就是函数
Point === Point.prototype.constructor // true 类本身就指向构造函数

类里面能定义什么,不能定义什么?

由于class只是构造函数的语法糖,所以class内部能定义什么有很多限制,截止2018年上半年,规定如下:

能定义:

  1. 构造方法
  2. 若干个实例方法
  3. 静态方法(就是类本身的方法)

有限定条件的定义:

实例属性:不能在构造方法外部定义实例属性,只能在构造方法里定义实例属性。

绝对不能定义:

不能定义静态属性(就是类本身的属性),只能在class外部定义

所以,实例属性必须在constructor(){}里面;静态属性必须在class的外面。

这些名词的具体解释见下文。

如何使用类

必须用new,不用new会报错。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return this.x + this.y;
  }
}

var a = new Point(10, 20);
console.log(a.toString()); // 30

class也有prototype

class的所有方法(比如上面的toString()方法),都定义在prototype上面,这跟构造函数其实是一致的,只不过构造函数是显式的写出来,class是隐式定义。

在类的实例上面调用方法,其实还是调用原型上的方法。跟构造函数情况一样。

class B {}
let b = new B();

b.constructor === B.prototype.constructor // true

给类增加新动态方法

Object.assign方法可以很方便地一次向类添加多个动态方法。

由于类的动态方法都定义在prototype对象上面,所以类的新动态方法添加在prototype对象上面就可以了。

class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  // 这两个就是给Point新增的动态方法
  toString(){
    // ...
  }, // 别忘了这里的逗号
  toValue(){
    // ...
  }
});

类里的方法名可以采用表达式

class里的方法名可以使用计算值,这是构造函数做不到的。

let a = 'toString';

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  [a]() { // 把表达式用[]包起来就可以了
    return this.x + this.y;
  }
}

var b = new Point(10, 20);
console.log(b.toString()); // 30

注意严格模式

class内部是默认使用严格模式的,所以,最好你就从今天开始养成js代码全局都使用严格模式的习惯,只有好处没有坏处。

class默认自带constructor方法

constructor方法是类的默认方法,它的作用是:通过new命令生成对象实例时,自动调用该方法。通常类的动态属性都在constructor方法中初始化。

如果没有显式定义constructor方法,则class会默认创建一个空的constructor方法。

class的表达式写法

跟函数一样,class也可以用表达式写法,下面栗子中,当前作用域下,这个类叫PointClass,但是类的内部,这个类叫Point

表达式写法中,Point可以省略,跟函数一样。只要类的内部不需要用到Point,就可以省略。

const PointClass = class Point {
  constructor(x, y) {
    Point.x = x;
    Point.y = y;
  }

  toString() {
    return Point.x + Point.y;
  }
}

var a = new PointClass(10, 20);
console.log(a.toString()); // 30

类的声明,不存在变量提升

我们知道function是有变量提升特性的:

a();

function a() {
  console.log('b'); // 会被执行
}

而class是没有变量提升特性的。记住这一点。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

私有方法、私有属性

ES6依然没有实现其他语言早已实现的私有方法和私有属性。只能变通实现:

有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。具体需要先了解Symbol,本文略过。

this的指向问题

class也有name属性

name属性是class默认自带属性,它就是返回class的名字,就是跟在class后面的那个类名。

class的取值函数(get)和存值函数(set)

在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

为什么会有这种函数呢?看个栗子。我设一个班作为一个类,这个class有一个人数属性,下面例子中,我new的时候设定初始人数是60人。然后,某时间,班里转入新生30人,我想知道现在多少人?然后过了一阵,又转入20人,我想知道现在多少人,怎么写代码?

class MyClassroom {
  constructor(number) {
    this.number = number;
  }
  get newnumber() {
    return this.number;
  }
  set newnumber(value) {
    this.number += value;
  }
}

var classroom = new MyClassroom(60);
classroom.newnumber = 30;
console.log(classroom.newnumber); // 90
classroom.newnumber = 20;
console.log(classroom.newnumber); // 110

说白了,get和set关键字,一个负责取值,一个负责存值,如果不这样做,我还能怎么写呢?

class MyClassroom {
  constructor(number) {
    this.number = number;
  }
  getnewnumber() {
    return this.number;
  }
  setnewnumber(value) {
    this.number += value;
  }
}

var classroom = new MyClassroom(60);
classroom.setnewnumber(30);
console.log(classroom.getnewnumber()); // 90
classroom.setnewnumber(20);
console.log(classroom.getnewnumber()); // 110

这样我不用get和set关键字,而是定义了2个方法,也达到了同样的目的。

所以get和set关键字的优势是什么?

  1. 简练。如果不用get、set关键字,那么就需要写2个方法名,这样显然不够简练。原本早期阶段编程语言种确实没有get、set概念,那时候程序员们的确经常用getnewnumber和setnewnumber这种方法来定义方法,后来语言的维护者看到既然大家都喜欢这么写,就干脆提出了get和set两个关键字,这样就更精炼了。
  2. 直观。 classroom.newnumber = 30是一种更直观的赋值写法,比classroom.setnewnumber(30)这种传参的写法要直观的多。
  3. 处理流程分离。get要做的事情跟set要做的事情完全不用。
  4. 最重要的一点:安全。虽然ES6还没有实现私有属性和方法,但是其他语言早就实现了,既然私有,意味着不能直接写入和读取,那么怎么办呢?就用get和set作为对外窗口,来统一接收数据和发出数据。当某个属性只允许写,那就只设set没有get,只允许读,就只设get没有set。
  5. 操作拦截,其实也是为了安全。如果让你随意修改属性,比如班级人数,没有set的话,你修改number属性就直接修改了人数,怎么可以让你这么轻松的修改呢?因为你如果修改成一个很扯淡的人数呢?或者你不小心把布尔值当数值传进去又该咋办?现在,有set函数,于是所有的类型判断、范围判断、有效性判断,都可以加到里面,全OK的情况下,最终才允许你修改成功。

class的Generator方法

这个Generator词,跟ES6引入的Generator函数的Generator是一个意思。

如果class的某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。class结合Generator比较复杂,外部需要for (... of ...) {}来处理实例,如果真的需要的时候,可以再去查专门的资料,这里不多说。

class内部无法定义自身静态属性,只能在外部定义

需要注意,class内部的this关键字,指向的是实例,并不是类本身。

在class的内部定义静态属性是非法的:

class MyClass {
  a: 1 // Uncaught SyntaxError: Unexpected token :
}

所以,定义类自身的静态属性, 只能是在外部定义:

class MyClass {
}
MyClass.a = 1;
console.log(MyClass.a); // 1

class可以定义实例属性但必须定义在构造方法里

目前ES6的规定就是如此,因为class毕竟只是语法糖。

栗子从略,可参看本文任意构造方法。

class可以定义类自身方法(即静态方法)

类自身的方法,叫做静态方法,做法是在class的一个方法前,加上static关键字。静态方法不会被实例继承,而是直接通过类来调用。

一定要区分class的方法和实例的方法。

class MyClassroom {
  constructor(number) {
    this.number = 60;
  }
  static get1() {
    return this.number; 静态方法包含的this关键字,这个this指的是类,而不是实例
  }
   static get2() {
    return 80;
  }
}

console.log(MyClassroom.get1()); // undefined
MyClassroom.number = 60;
console.log(MyClassroom.get1()); // 60
console.log(MyClassroom.get2()); // 80

var classroom = new MyClassroom();
console.log(classroom.get1()); // undefined

静态方法可以与非静态方法重名。

虽然实例无法继承静态方法,但是类的子类可以继承静态方法:

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello' 父类Foo有一个静态方法,子类Bar可以调用这个方法。

静态方法也是可以从super对象上调用。具体参考下文关于继承的知识。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}

Bar.classMethod() // "hello, too"

new.target用于实现只允许继承,不允许调用的class

new.target必须写在构造方法里面,它指向类本身。具体指向哪个类,有下面说道:

  1. 类没有被继承的话,new.target就指向类自身。
  2. 类被继承的话,new.target指向子类。

new.target的用途主要是确定new的对象到底是以哪个类为范本。实践中,基类是不允许被实例化的,靠这个new.target就可以实现基类禁止被实例化。

function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

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

推荐阅读更多精彩内容

  • class的基本用法 概述 JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子: ...
    呼呼哥阅读 4,068评论 3 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,771评论 1 10
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,629评论 0 11
  • 文:曹阳 大学军训的我和健身后的我,被爱奇艺的《大学生来了》0625期用作话题对比图,大学是座整容院,相信你也一样...
    男流阅读 1,427评论 39 26