Ref: Class 的基本语法

Ref: Class 的基本继承

许多面向对象的语言都有修饰器(Decorator)函数,用来修改类的行为。目前,有一个提案将这项功能,引入了 ECMAScript。

Ref: JavaScript 中的 this 用法以及 call(apply) 的理解

Ref: JavaScript Object-Oriented Programming Tutorial - OOP with E6【简单介绍引入了类后,带来的简单写法】

基础概念

Class关键字

(1) 传统写法

  1. function Point(x, y) {
  2. this.x = x;
  3. this.y = y;
  4. }
  5.  
  6. Point.prototype.toString = function () {
  7. return '(' + this.x + ', ' + this.y + ')';
  8. };
  9.  
  10. var p = new Point(1, 2);

(2) ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

类的数据类型就是函数,类本身就指向构造函数。

  1. class Point {
  2. constructor(x, y) {
  3. this.x = x;
  4. this.y = y;
  5. }
  6.  
  7. toString() {
  8. return '(' + this.x + ', ' + this.y + ')';
  9. }
  10. }

类的方法

  1. class Point {
  2. constructor() {  // 都定义在类的prototype属性上面
  3. // ...
  4. }
  5.  
  6. toString() {    // 都定义在类的prototype属性上面
  7. // ...
  8. }
  9.  
  10. toValue() {    // 都定义在类的prototype属性上面
  11. // ...
  12. }
  13. }

类的所有方法都定义在类的prototype属性上面。也就是等同于:

  1. Point.prototype = {
  2. constructor() {},
  3. toString() {},
  4. toValue() {},
  5. };

也就是等价:

在类的实例上面调用方法,其实就是调用原型上的方法。

  1. class B {}
  2. let b = new B();
  3.  
  4. b.constructor === B.prototype.constructor // true ----> 其实就是调用原型上的方法。

扩展技巧:

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

  1. class Point {
  2. constructor(){
  3. // ...
  4. }
  5. }
  6.  
  7. Object.assign(Point.prototype, {
  8. toString(){},
  9. toValue(){}
  10. });

内部属性方法不可枚举:

  1. 可枚举:
    var Point = function (x, y) {
  2. // ...
  3. };
  4.  
  5. Point.prototype.toString = function() {
  6. // ...
  7. };
  8.  
  9. Object.keys(Point.prototype)
  10. // ["toString"]
  11. Object.getOwnPropertyNames(Point.prototype)
  12. // ["constructor","toString"]
  13.  
  14. ---------------------------------------------------------------------
    不可枚举:
  15. class Point {
  16. constructor(x, y) {
  17. // ...
  18. }
  19.  
  20. toString() {
  21. // ...
  22. }
  23. }
  24.  
  25. Object.keys(Point.prototype)
  26. // []
  27. Object.getOwnPropertyNames(Point.prototype)
  28. // ["constructor","toString"]

严格模式:

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。

考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。

Goto: [JS] Why "strict mode" here

类的实例对象

定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor方法。Constructor返回this,也就是指向自己。

  1. class Point {
  2. // ...
  3. }
  4.  
  5. // 报错
  6. var point = Point(2, 3);
  7.  
  8. // 正确
  9. var point = new Point(2, 3);  // 只能new,这样才更为接近“类”

对象方法调动的实际上都是prototype上的类型。

  1. //定义类
  2. class Point {
  3.  
  4. constructor(x, y) {
  5. this.x = x;    
  6. this.y = y;
  7. }
  8.  
  9. toString() {          // 要注意:其实是定义在原型prototype上
  10. return '(' + this.x + ', ' + this.y + ')';
  11. }
  12.  
  13. }
  14. ----------------------------------------------------
  15. var point = new Point(2, 3);
  16.  
  17. point.toString() // (2, 3)
  18.  
  19. point.hasOwnProperty('x') // true,因为上面给 this.x赋值
  20. point.hasOwnProperty('y') // true,因为上面给 this.y赋值
  21. point.hasOwnProperty('toString') // false
  22. point.__proto__.hasOwnProperty('toString') // true

Class 表达式

使用表达式的形式定义。

  1. // 类的名字是MyClass
  1. const MyClass = class Me {    // Me只在class内部代码中可用;如果类的内部没用到的话,可以省略Me。
  2. getClassName() {
  3. return Me.name;   // Me指代当前类
  4. }
  5. };

这么下,可用写成一个立即执行的类(函数):

  1. let person = new class {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5.  
  6. sayName() {
  7. console.log(this.name);
  8. }
  9. }('张三');
  10.  
  11. person.sayName(); // "张三"

不存在变量提升

如果class被提升到代码头部,而let命令是不提升的,将导致Bar继承Foo的时候,Foo还没有定义。

  1. {
  2. let Foo = class {};
  3. class Bar extends Foo {  // <---- 如果class提升,也就是提升到let Foo之前,返回会引来麻烦
  4. }
  5. }

私有方法和私有属性

方法一:

私有方法是常见需求,但 ES6 不提供。【自己通过命名约定】

方法二:

将私有方法移出模块,因为模块内部的所有方法都是对外可见的。

  1. class Widget {
  2. foo (baz) {          // foo是公有方法
  3. bar.call(this, baz);    // 这使得bar实际上成为了当前模块的私有方法
  4. }
  5.  
  6. // ...
  7. }
  8.  
  9. -----------------------------------------------------
  10.  
  11. function bar(baz) {
  12. return this.snaf = baz;
  13. }

方法三:

将私有方法的名字命名为一个Symbol值。

  1. const bar = Symbol('bar');
  2. const snaf = Symbol('snaf');
  3.  
  4. export default class myClass{
  5.  
  6. // 公有方法
  7. foo(baz) {
  8. this[bar](baz);
  9. }
  10.  
  11. // 私有方法
  12. [bar](baz) {
  13. return this[snaf] = baz;
  14. }
  15.  
  16. // ...
  17. };

私有属性当前只是提案 - 暂略

this 的指向

这里主要是讲如何绑定this的问题,让this的指向比较可控。

(1) 这里找不到print,因为运行时this变为了全局环境。

  1. class Logger {
  2. printName(name = 'there') {
  3. this.print(`Hello ${name}`);  // 这里的this,默认指向Logger类的实例
  4. }
  5.  
  6. print(text) {
  7. console.log(text);
  8. }
  9. }
  10. -----------------------------------------------------------------------
  11.  
  12. const logger = new Logger();
  13. const { printName } = logger;
  14. printName(); // 将这个方法提取出来单独使用了
    // TypeError: Cannot read property 'print' of undefined

(2) 改进:在构造方法中绑定this,这样就不会找不到print方法了。

(3) 使用箭头函数,使this只跟定义时的位置有关。

(4) 使用Proxy,获取方法的时候,自动绑定this。【暂时不懂】

name 属性

ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

  1. class Point {}
  2. Point.name // "Point"

取值函数(getter)和存值函数(setter)

  1. class MyClass {
  2. constructor() {
  3. // ...
  4. }
    -----------------------------------------------
  5. get prop() {
  6. return 'getter';
  7. }
  8. set prop(value) {  // value是属性的value,通过__.prop = 123 等号的方式传过来的
  9. console.log('setter: '+value);
  10. }
  11. }
    -----------------------------------------------
  12.  
  13. let inst = new MyClass();
  14.  
  15. inst.prop = 123;
  16. // setter: 123
  17.  
  18. inst.prop
  19. // 'getter'

Generator 方法

参见:Generator 函数的语法

静态方法

(1) 加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用。而不是生成实例后调用。

  1. class Foo {
  2. static classMethod() {
  3. return 'hello';
  4. }
  5. }
  6.  
  7. Foo.classMethod() // 'hello'
  8.  
  9. var foo = new Foo();
  10. foo.classMethod()      // 生成实例后调用是不行的!
  11. // TypeError: foo.classMethod is not a function

(2) 如果静态方法包含this关键字,这个this指的是类,而不是实例。

  1. class Foo {
  2. static bar () {
  3. this.baz();
  4. }
  5. static baz () {
  6. console.log('hello');
  7. }
  8. baz () {    // 静态方法可以与非静态方法重名
  9. console.log('world');
  10. }
  11. }
  12.  
  13. Foo.bar() // hello

(3) 父类的静态方法,可以被子类继承

  1. class Foo {
  2. static classMethod() {
  3. return 'hello';
  4. }
  5. }
  6.  
  7. class Bar extends Foo {
  8. }
  9.  
  10. Bar.classMethod() // 'hello'

(4) 静态方法也是可以从super对象上调用的。

  1. class Foo {
  2. static classMethod() {
  3. return 'hello';
  4. }
  5. }
  6.  
  7. class Bar extends Foo {
  8. static classMethod() {
  9. return super.classMethod() + ', too';
  10. }
  11. }
  12.  
  13. Bar.classMethod() // "hello, too"

Class 的静态属性和实例属性

(1)类的实例属性

  1. class MyClass {
  2. myProp = 42;  // 使用的是等号
  3.  
  4. constructor() {
  5. console.log(this.myProp); //
  6. }
  7. }

以前,我们定义实例属性,只能写在类的constructor方法里面。

  1. class ReactCounter extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. count: 0
  6. };
  7. }
  8. }
  9.  
  10. ----------------------------------------------------------------
  11. 新的写法,可以不在constructor方法里面定义
  12.  
  13. class ReactCounter extends React.Component {
  14. state = {
  15. count: 0
  16. };
  17. }

(2)类的静态属性

ES6 明确规定,Class 内部:只有静态方法,没有静态属性。

那就暂时定义在外面:

  1. class Foo {
  2. }
  3.  
  4. Foo.prop = 1;  // 定义在外面,为Foo类定义了一个静态属性prop
  5. Foo.prop // 1
  6.  
  7. --------------------------------------------------------
    // 新写法
  1. class Foo {
  2. static prop = 1;
  3. }

new.target 属性

ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。

这个属性可以用来确定构造函数是怎么调用的。

应用1. 构造函数只能通过new命令调用

  1. function Person(name) {
  2. if (new.target !== undefined) {
  3. this.name = name;
  4. } else {
  5. throw new Error('必须使用 new 命令生成实例');
  6. }
  7. }
  8.  
  9. // 另一种写法
  10. function Person(name) {
  11. if (new.target === Person) {
  12. this.name = name;
  13. } else {
  14. throw new Error('必须使用 new 命令生成实例');
  15. }
  16. }
  17. --------------------------------------------------------------
  18. var person = new Person('张三'); // 正确
    var notAPerson = Person.call(person, '张三'); // 报错

应用2. 不能独立使用、必须继承后才能使用的类

  1. class Shape {
  2. constructor() {
  3. if (new.target === Shape) {
  4. throw new Error('本类不能实例化');
  5. }
  6. }
  7. }
  8.  
  9. class Rectangle extends Shape {
  10. constructor(length, width) {
  11. super();
  12. // ...
  13. }
  14. }
  15.  
  16. var x = new Shape(); // 报错
  17. var y = new Rectangle(3, 4); // 正确

注意,在函数外部,使用new.target会报错。

[JS] ECMAScript 6 - Class : compare with c#的更多相关文章

  1. [JS] ECMAScript 6 - Variable : compare with c#

    前言 范围包括:ECMAScript 新功能以及对象. 当前的主要目的就是,JS的学习 --> ECMAScript 6 入门 let 命令 js 因为let, i的范围限制在了循环中. var ...

  2. [JS] ECMAScript 6 - Inheritance : compare with c#

    这一章,估计是js最操蛋的一部分内容. 现代方法: 简介 Object.getPrototypeOf() super 关键字 类的 prototype 属性和__proto__属性 原生构造函数的继承 ...

  3. [JS] ECMAScript 6 - Prototype : compare with c#

    开胃菜 prototype 对象 JavaScript 语言的继承则是通过“原型对象”(prototype). function Cat(name, color) { // <----构造函数 ...

  4. [JS] ECMAScript 6 - Async : compare with c#

    一段引言: Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大. 它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对 ...

  5. [JS] ECMAScript 6 - Array : compare with c#

    扩展运算符(spread) 先复习下 rest 参数. (1) argument模式,但不够好. // https://blog.csdn.net/weixin_39723544/article/de ...

  6. [JS] ECMAScript 6 - Object : compare with c#

    Ref: 对象的扩展 Outline: 属性的简洁表示法 属性名表达式 方法的 name 属性 Object.is() Object.assign() 属性的可枚举性和遍历 Object.getOwn ...

  7. [JS] ECMAScript 6 - String, Number, Function : compare with c#

    字符串的扩展 正则的扩展 数值的扩展 函数的扩展 字符串的扩展 js 字符的 Unicode 表示法 codePointAt() String.fromCodePoint() 字符串的遍历器接口 at ...

  8. [JS] ECMAScript 6 - Set & Map : compare with c#

    Ref: Set 和 Map 数据结构 Day 0 - 1所学

  9. [Node.js] ECMAScript 6中的生成器及koa小析

    原文地址:http://www.moye.me/2014/11/10/ecmascript-6-generator/ 引子 老听人说 koa大法好,这两天我也赶了把时髦:用 n 安上了node 0.1 ...

随机推荐

  1. thinkphp5 学习笔记

    一.开发规范: 二.API: 1.数据输出:新版的控制器输出采用 Response 类统一处理,而不是直接在控制器中进行输出,通过设置 default_return_type 就可以自动进行数据转换处 ...

  2. Javascript 函数声明先提升还是变量先提升

    大家都知道js 分为词法阶段 和执行阶段 也知道它是因为var变量和函数声明会提升 但是你知道他们两个谁先提升的吗 测试一下 function test(){ alert(4); } var test ...

  3. Ftrace使用指南及跟踪系统调用

    http://vonnyfly.github.io/2013/06/24/ftraceshi-yong-zhi-nan/

  4. iOS 转换异步block为同步方式运行

    使用dispatch_semaphore_t 实现 dispatch_semaphore_t sema = dispatch_semaphore_create(0); //创建信号量 __block ...

  5. SpringMVC类型转换、数据绑定详解

    public String method(Integer num, Date birth) { ... } Http请求传递的数据都是字符串String类型的,上面这个方法在Controller中定义 ...

  6. Spark2.3(三十六):根据appName验证某个app是否在运行

    具体脚本 #/bin/sh #LANG=zh_CN.utf8 #export LANG export SPARK_KAFKA_VERSION=0.10 export LANG=zh_CN.UTF- # ...

  7. Android Gesture Detector

    Android Touch Screen 与传统Click Touch Screen不同,会有一些手势(Gesture),例如Fling,Scroll等等.这些Gesture会使用户体验大大提升.An ...

  8. ubuntu 安装JDK1.6(jdk-6u45-linux-x64.bin)

    ubuntu 安装JDK1.6 首先在官网下载JKD1.6 linux的版本:http://www.oracle.com/technetwork/java/javasebusiness/downloa ...

  9. IOPS计算

    Device Type IOPS 7,200 rpm SATA drives HDD ~75-100 IOPS[2] 10,000 rpm SATA drives HDD ~125-150 IOPS[ ...

  10. MySQL replicate-ignore-db详解

    1:官方的解释是:在主从同步的环境中,replicate-ignore-db用来设置不需要同步的库.解释的太简单了,但是里面还有很多坑呢. 生产库上不建议设置过滤规则.如果非要设置,那就用Replic ...