[JS] ECMAScript 6 - Class : compare with c#

Ref: Class 的基本语法

Ref: Class 的基本继承

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

 

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

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

 

 

基础概念

Class关键字

(1) 传统写法

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);

 

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

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

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

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

 

类的方法

class Point {
  constructor() {  // 都定义在类的prototype属性上面
    // ...
  }

  toString() {    // 都定义在类的prototype属性上面
    // ...
  }

  toValue() {    // 都定义在类的prototype属性上面
    // ...
  }
}

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

Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

  

也就是等价:

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

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

b.constructor === B.prototype.constructor // true ----> 其实就是调用原型上的方法。

  

扩展技巧:

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

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

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

  

内部属性方法不可枚举:

可枚举:
var
Point = function (x, y) { // ... }; Point.prototype.toString = function() { // ... }; Object.keys(Point.prototype) // ["toString"]
Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] ---------------------------------------------------------------------
不可枚举: class Point { constructor(x, y) { // ... } toString() { // ... } } Object.keys(Point.prototype) // [] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]

  

严格模式:

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

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

Goto: [JS] Why "strict mode" here

 

 

类的实例对象

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

class Point {
  // ...
}

// 报错
var point = Point(2, 3);

// 正确
var point = new Point(2, 3);  // 只能new,这样才更为接近“类”

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

//定义类
class Point {

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

  toString() {          // 要注意:其实是定义在原型prototype上
    return '(' + this.x + ', ' + this.y + ')';
  }

}
----------------------------------------------------
var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true,因为上面给 this.x赋值
point.hasOwnProperty('y') // true,因为上面给 this.y赋值
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

 

 

Class 表达式

使用表达式的形式定义。

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

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

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('张三');

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

 

 

不存在变量提升

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

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

 

  

私有方法和私有属性

方法一:

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

 

方法二:

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

class Widget {
  foo (baz) {          // foo是公有方法
    bar.call(this, baz);    // 这使得bar实际上成为了当前模块的私有方法
  }

  // ...
}

-----------------------------------------------------
function bar(baz) { return this.snaf = baz; }

 

方法三:

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

const bar  = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

  // ...
};

 

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

 

 

this 的指向

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

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

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

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

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

 

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

 

 

 

name 属性

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

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

 

 

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

class MyClass {
constructor() {
// ... }
----------------------------------------------- get prop() {
return 'getter'; }
set prop(value) {  // value是属性的value,通过__.prop = 123 等号的方式传过来的 console.log(
'setter: '+value); } }
----------------------------------------------- let inst
= new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'

 

 

Generator 方法

参见:Generator 函数的语法

 

 

静态方法

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

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

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()      // 生成实例后调用是不行的!
// TypeError: foo.classMethod is not a function

 

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

class Foo {
  static bar () {
    this.baz();
  }
static baz () { console.log(
'hello'); }
baz () {    // 静态方法可以与非静态方法重名 console.log(
'world'); } } Foo.bar() // hello

 

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

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

class Bar extends Foo {
}

Bar.classMethod() // 'hello'

 

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

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

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

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

 

 

Class 的静态属性和实例属性

(1)类的实例属性

class MyClass {
  myProp = 42;  // 使用的是等号

  constructor() {
    console.log(this.myProp); // 42
  }
}

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

class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}

----------------------------------------------------------------
新的写法,可以不在constructor方法里面定义

class ReactCounter extends React.Component { state
= { count: 0 }; }

 

(2)类的静态属性 

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

那就暂时定义在外面:

class Foo {
} Foo.prop = 1;  // 定义在外面,为Foo类定义了一个静态属性prop Foo.prop // 1

--------------------------------------------------------
// 新写法
class Foo {
  static prop = 1;
}

 

 

new.target 属性 

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

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

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

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

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}
--------------------------------------------------------------
var person     = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错

 

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

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本类不能实例化');
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确

 

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

 

posted @ 2018-04-14 21:10  郝壹贰叁  阅读(224)  评论(0编辑  收藏  举报