目录
[2、ES6 的class语法](#2、ES6 的class语法)
[4、类的constructor() 方法](#4、类的constructor() 方法)
[5、类的实例 new](#5、类的实例 new)
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
1、传统ES5写法
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。
javascript
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
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
2、ES6 的class语法
javascript
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
-----------------------------------------------
// ES6的类完全可以看作构造函数的另外一种写法
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
// 上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
// 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
上面代码定义了一个"类",可以看到里面有一个constructor()
方法,这就是构造方法,而this
关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数Point
是一致的。
Point
类除了构造方法,还定义了一个toString()
方法。注意,定义
toString()
方法的时候,前面不需要加上function
这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用new
命令,跟构造函数的用法完全一致。
javascript
class Bar {
doStuff() {
console.log('stuff');
}
}
const b = new Bar();
b.doStuff() // "stuff"
构造函数的prototype
属性,在 ES6 的"类"上面继续存在。
事实上,类的所有方法都定义在类的prototype
属性上面。
javascript
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
------------------------------
class B {}
const b = new B();
b.constructor === B.prototype.constructor // true 等同
// b是B类的实例,它的constructor()方法就是B类原型的constructor()方法
上面代码中,constructor()
、toString()
、toValue()
这三个方法,其实都是定义在Point.prototype
上面。
因此,在类的实例上面调用方法,其实就是调用原型上的方法。
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign()
方法可以很方便地一次向类添加多个方法。
javascript
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
3、ES5与ES6行为对比
javascript
1、prototype对象的constructor属性,直接指向"类"的本身,这与 ES5 的行为是一致的。
var Point = function (x, y) {
// ...
};
class Point {
constructor(){
// ...
}
}
Point.prototype.constructor === Point // true
------------------------------------------------------------------------------
2、类的内部所有定义的方法,都是不可枚举的(non-enumerable)
// toString()方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
Object.keys(Point.prototype)
// [] 不可枚举
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
// 上面代码采用 ES5 的写法,toString()方法就是可枚举的。
var Point = function (x, y) {
// ...
};
Point.prototype.toString = function () {
// ...
};
Object.keys(Point.prototype)
// ["toString"] 可枚举
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
4、类的constructor() 方法
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。
一个类必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加。
javascript
class Point {
}
// 等同于
class Point {
constructor() {}
}
5、类的实例 new
生成类的实例的写法,与 ES5 完全一样,也是使用new
命令。前面说过,如果忘记加上new
,像函数那样调用Class()
,将会报错。
javascript
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
// 实例报错
var point = Point(2, 3);
// 实例正确
var point = new Point(2, 3);
----------------------------------------------------------------------
// 类的属性和方法,除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
// x和y都是实例对象point自身的属性(因为定义在this对象上),所以hasOwnProperty()方法返回true,而toString()是原型对象的属性(因为定义在Point类上),所以hasOwnProperty()方法返回false。这些都与 ES5 的行为保持一致
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
------------------------------------------------------------------------
// 类的所有实例共享一个原型对象,它们的原型都是Point.prototype,所以__proto__属性是相等的
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__ //true 相等
// 此时可以通过实例__proto__属性为"类"添加方法,并共享方法(一般不推荐此种方法,会改变原始定义类方法,污染全局!!!)
p1.__proto__.printName = function () { return 'Oops' };
p1.printName() // "Oops"
p2.printName() // "Oops" p2共享p1方法
6、类的对象属性(新写法)
ES6为类规定新的写法,定义在类的最顶层;
改变实例属性定义在constructor()方法里面的this上的写法。
javascript
// 原来的写法
class IncreasingCounter {
constructor() {
this._count = 0;
}
increment() {
this._count++;
}
}
// 新写法
// 实例属性_count与increment()方法,处于同一个层级(不需要在实例属性前面加上this)
// 新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面)
class IncreasingCounter {
_count = 0;
increment() {
this._count++;
}
}
7、类的取值函数(getter)和存值函数(setter)
与 ES5 一样,在"类"的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
javascript
class MyClass {
name = '张三'
get prop() {
console.log('getter: '+this.name);
return this.name;
}
set prop(value) {
this.name = value
console.log('setter: '+this.name);
}
}
let inst = new MyClass();
inst.prop = '刘五';
// setter: '刘五'
inst.prop
// getter:'张三'
// 存值函数和取值函数是设置在属性的 Descriptor 对象上的
var descriptor = Object.getOwnPropertyDescriptor(
MyClass.prototype, "prop"
);
"get" in descriptor // true
"set" in descriptor // true
8、Class类的表达式
与函数一样,类也可以使用表达式的形式定义。
javascript
const MyClass1 = class isMe {
name = 'sun'
getClassName() {
console.log(this)
console.log(isMe)
console.log(isMe.name)
return isMe.name;
}
};
// 类的名字是isMe,但是isMe只在 Class 的内部可用,指代当前类。
// 在 Class 外部,这个类只能用MyClass引用
let inst = new MyClass1();
inst.getClassName() // isMe
isMe.name // ReferenceError: isMe is not defined
上面代码表示,isMe
只在 Class 内部有定义。
如果类的内部没用到的话,可以省略isMe
,也就是可以写成下面的形式。
javascript
// 简写表达式,类的名字在内部没有使用到
const MyClass1 = class { /* ... */ };
-------------------------------------------------
// 或采用 Class 表达式,可以写出立即执行的 Class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三" person是一个立即执行的类的实例
9、Class的static静态方法与静态属性
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个类的方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"
javascript
class Foo {
//静态属性(Class 本身的属性,即Foo.prop,而不是定义在实例对象(this)上的属性)
static prop = 123;
//静态方法(Class 本身的方法)
static classMethod() {
console.log(this)
console.log(Foo.prop); // 123
this.baz(); // 等同于类静态方法调用Foo.baz()
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.classMethod() // 'hello' 直接使用类来调用
// 如果静态方法包含this关键字,这个this指的是类,而不是实例
// 静态方法classMethod 调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz()。另外,从这个例子还可以看出,静态方法可以与非静态方法重名
------------------------------------------------------------------
class Foo2 {
classMethod() {
return 'hello';
}
}
Foo2.classMethod()
// TypeError: Foo2.classMethod is not a function
// 只有静态方法才能直接使用类来调用,不然就报错
------------------------------------------------------------------
// 实例化类
let isfoo = new Foo();
isfoo.classMethod();
// TypeError: isfoo.classMethod is not a function
//(静态方法不能继承到实例对象中,无法调用)
子类能继承父类的静态方法
javascript
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello' 子类Bar直接类调用(已经继承了Foo父类的静态方法classMethod)