ES6之class--01类的由来和使用

由于class这块老是卡壳,所以整理两篇博文出来,本文主要参考《ES6入门》和《JS高程》,《JS权威指南》

1)类的由来

首先明确一下构造函数的概念,《JS高程》中定义

构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的var person = new Object()。这行代码创建了Object引用类型的一个新实例,然后把该实例保存在了变量person中,使用的构造函数是Object。

在js中,我们一般通过构造函数(Point)来生成实例对象(p),如下

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

 上面写法和java,c++差异很大,ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

上面的代码用 ES6 的class改写,就是下面这样。

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

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

 

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法(constructor构造方法)。

关于函数和方法的区别,《JS权威指南》中这样定义

  函数(function): 函数是带有名称(named)和参数的JavaScript代码段,可以一次定义多次调用。

  方法(method): 当将函数和对象合写在一起时,函数就变成了 "方法"(method)。即一个方法就是保存在一个对象的属性里的js函数。eg  o.m = f,给对象定义了方法m()

Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

ES6 的类,完全可以看作构造函数的另一种写法。

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。 要理解Point === Point.prototype.constructor // true这行代码,我们先回顾一下这篇博文《面向对象(创建对象)--原型模式03(上)》,里面讲的

代码:

function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas" 
var person2 = new Person();
person2.sayName(); //"Nicholas" 
alert(person1.sayName == person2.sayName); //true 

对应的原型对象表

ES6之class--01类的由来和使用

所以对于刚才的es6代码

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

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

同样有原型对象表                                                                                                                                                                               ES6之class--01类的由来和使用            

 

 2)类的使用

使用的时候,也是直接对类使用new命令。

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"

 构造函数的prototype属性,在 ES6 的“类”上面继续存在。即就相当于类上面有prototype。类的所有方法都定义在类的prototype属性上面。

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

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

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

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

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

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

 ES6之class--01类的由来和使用

因为实例b和B.prototype指向同一个地方也就是B的原型,所以上面也就不难理解了。

 由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。

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

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

 上面的意思是这样子的

ES6之class--01类的由来和使用

prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的。

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

 另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

 上面代码中,toString方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。

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

Point.prototype.toString = function() {
  // ...
};

Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

上面代码采用 ES5 的写法,toString方法就是可枚举的。