ES6 Class

分享

简介

ES6class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

简单的对比

// ES5
function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype.toString = function () {
    return '(' + this.x + ', ' + this.y + ')';
}

var p = new Point(1, 2);

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

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

let p = new Point(1, 2)

上述的代码分别定义了在 ES5ES6 中类声明的方式,可以看出有几点区别:

  • ES5function 声明类,ES6 使用 class 声明。
  • ES5 中函数中的内容移到了 ES6/constructor 中。
  • ES5 中在 prototype 上配置原型链上的方法,在 ES6 中直接定义。

由上可以看出,ES6 中的 class 一定程度上可以认为是 ES5 类写法的语法糖。

接着思考下 ES5 中类是如何实例化的:

  1. 直接调用类的函数,然后把 this 对象给返回出来。
  2. prototype 上的内容定义在实例的原型链上。

首先我们来看第一点,在 ES5 的类编写过程中,总会有这么一句

Point.prototype.constructor === Point // true

这说明类 prototype 上的 constructor 属性是自己。
那现在在来看实例化过程中第一步,可以认为调用类下 prototype 上的 constructor 方法。

那么是否可以这么认为:一个类的实例化过程其实和类本身没多大关系了,仅仅和 prototype 有关。

接着在来看 ES6 中的构造方式,我们定义了一个名字和一系列的方法,这其实就是在定义一个类的 prototype

但有一点需要尤其注意:

  • ES5 中在 prototype 上定义的方法是可枚举的,ES6 中定义的所有方法都是不可枚举的。

类的属性名,可以用 [] 表示:

let methodName = 'getArea';

class Square {
    constructor(length) {
        // ...
    }

    [methodName]() {
        // ...
    }
}

constructor 方法

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

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

constructor 方法默认返回实例对象(this),当然也可以指定对象返回,但是这就会照成实例对象并不是类的实例。

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

new Foo() instanceof Foo
// false

Class 表达式

Class 表达式的效果与函数一致

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

let inst = new MyClass();
inst.getClassName();                // Me
Me.name;                            // ReferenceError: Me is not defined

如果一个类被定义成表达式的形式,那么跟在 class 关键字后的类名在类外部不可见,仅仅只能在类内部使用,如果内部不使用的话,可以去掉。

采用 Class 表达式,可以写出立即执行的 Class
let person = new class {
    constructor(name) {
        this.name = name
    }
    
    sayName() {
        console.log(this.name)
    }
}('张三')

person.sayName();               // "张三"

但感觉这很没必要,这还不如直接写一个对象。

不存在变量提升

类在 ES6 中的定义方式与 let 变量一致,不存在变量提升。

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

this 指向

ES6 实例对象下的方法表现与 ES5 一致,在方法中使用 this 对象需要小心,this 代指拥有这个方法的对象。

class Logger {
    printName(name = 'there') {
        this.print(`Hello ${name}`);
    }
    
    print(text) {
        console.log(text);
    }
}

const logger = new Logger();
const { printName } = logger;
printName();
// TypeError: Cannot read property 'print' of undefined

上述写法会报错,原因在于声明的 printName 变量的拥有者是 window 。这与函数下的 this 对象的绑定一致,并不存在说 ES6 下有特殊情况,解决方式和函数确定 this 指向方式一致。

  1. 使用 bind
  2. 使用箭头函数。

不过使用这两种方法,都需要在 this 对象上绑定同名的原型链上的方法:

class Logger {
    constructor() {
        this.printName = this.printName.bind(this);
    }

    // ...
}

class Logger {
    constructor() {
        this.printName = (name = 'there') => {
            this.print(`Hello ${name}`);
        }
    }

    // ...
}

Class get/set

ES5 一样,在 的内部可以使用 getset 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {
    constructor() {
        // ...
    }
    get prop() {
        return 'getter';
    }
    set prop(value) {
        console.log('setter: ' + value);
    }
}

let inst = new MyClass();

inst.prop = 123;                    // setter: 123

inst.prop;                          // 'getter'

这其实也是在语法糖内的简单实现,ES5 中通过 defineProperty 定义 prototype 可以实现一样的效果。

Class 的静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例实现。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例实现,而是直接通过类来调用,这就称为 静态方法
class Foo {
    static classMethod() {
        return 'hello';
    }
}

Foo.classMethod();                  // 'hello'

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

当然如果在静态方法包含 this 关键字,这个 this 指的是类,而不是实例,这其实也是遵循函数执行时的 this 指向问题,总是执行调用这个函数的对象,而类仅仅是一个特殊的对象。

父类的静态方法,可以被子类继承。
class Foo {
    static classMethod() {
        return 'hello';
    }
}

class Bar extends Foo {
}

Bar.classMethod();                  // 'hello'

但目前 ES6 规定,类下仅仅只能有静态方法,不能有静态属性。

阅读更多

JavaScript 对象 & 原型

前言 这次的 why what or how 主题:JavaScript 对象 & 原型。 此类文章在百度上一搜一大把,其实不用再写了,但是本着把这个问题解释的清清楚楚明明白白,还是开始写了。 原因如下: 1. 面试宝典类文章,弄张图片一糊弄,让人觉得自己理解了。 2. 解释类文章,告诉你一堆语法,对语法一顿解释,告诉你就是这样的,还是没说清楚。 3. 很少有文章单独解释这个点!但这个点是基础!真的很重要! 所以本篇文章想说一说对象 & 原型,但为了确保能顺利理解,请先看完 JS 变量存储?栈 & 堆?NONONO!,因为该篇文章从变量存储的角度来解释 JavaScript 对象 & 原型,请确保看完,在看这篇文章。 什么是对象? 既然要说清楚,

By Breeze

浏览器下的 Event Loop

前言 javascript 是以单线程的形式运行在宿主环境下,javascript 采用了回调的形式来解决异步任务。 为什么是单线程? javascript 的最开始的出现是为了给 web 页面增添一些动态的效果,那么就避免不了获取页面上的元素信息,如果 javascript 是以多线程的形式运行在浏览器内,如果两个线程内的 javascript 同时去获取/修改,某个页面上的元素,那么浏览器该让哪个 javascript 线程拥有获取/修改该元素的权限呢?由于元素的信息会经常性的发生变化,那么又改如何去同步各个线程内所保存的元素信息呢? 所以综合以上问题, javascript 是单线程的原因就显示意见了,单线程在执行时,对于元素信息的引用在同一时间仅可能只有一个,那么以上所有的问题都不存在了。 什么是异步任务? 任何代码在执行时,都会碰到一些需要经过大量时间运算或是等待的代码,在浏览器的环境下,常见的就是 http 任务,比如:资源的加载(图片的 onload 事件),ajax 的请求(XMLHttpRequest 的 onLoad 事件)还有页面元素的点击事件以及

By Breeze

node 下的 Event Loop

前言 通过 浏览器下的 Event Loop ,可以得知 javascript 是一门事件驱动的语言, javascript 主线程通过不断的调用事件队列中的事件来完成异步任务。那么 Node 下是否也是如此? Node 下的事件队列 在 Node 官网有这样一篇文章:The Node.js Event Loop, Timers, and process.nextTick() 。 该文章主要讲述了 Node 中是如何处理以及实现 Node 下的 Event Loop。 主要包含以下内容: 事件队列 不同于浏览器,Node 下的有 6 个事件处理阶段,其执行顺序和队列名称如下: ┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │

By Breeze

什么是 HTML 5?

前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how 每篇文章试图解释清楚一个问题。 这次的 why what or how 主题:现在几乎所有人都知道了 HTML5 ,那么 H5 到底相比于 HTML4 有什么区别呢? 升级版?标准版! HTML5 做为 HTML 标准的第 5 版,于 2014 年发布,相信关注过 HTML5 的发展史的朋友都知道该版本是 WHATWG 和 W3C 握手言和后诞生的,是 W3C 组织与浏览器厂商相互妥协的结果,其中的绝大部分规范都由 WHATWG 组织所制定,之后由 W3C 采纳并入标准中。

By Breeze