ECMAScript6(17):Class类

var p = new Point(1,4);

console.log(p+“”); //(1,4)

console.log(typeof Point); //“function”

console.log(Point.prototype.constructor === Point); //true

console.log(Point.prototype.constructor === p.constructor); //true

Point.show(); //“Static function!”

相当于传统写法:

function Point(x, y){

this.x = x;

this.y = y;

}

Point.prototype.toString = function(){

return (${this.x},${this.y});

}

Point.show = function(){

console.log(“Static function!”);

}

var p = new Point(1,4);

console.log(p+“”); //(1,4)

这里不难看出,class 的类名就是 ES5 中的构造函数名,静态方法就定义在其上,而类的本质依然是个函数。而 class 中除了 constructor 是定义的构造函数以外,其他的方法都定义在类的 prototype 上,这都和 ES5 是一致的,这就意味着,ES5 中原有的那些方法都可以用, 包括但不限于:

  • Object.keys(), Object.assign() 等等

  • 而且 class 也同样支持表达式做属性名,比如 Symbol

  • ES5 函数具有的属性/方法:length、name、apply、call、bind、arguments 等等

但有些细节还是有区别的,比如:

class Point{

constructor(x, y){ //定义构造函数

this.x = x; //定义属性x

this.y = y; //定义属性y

} //这里没有逗号

toString(){ //定义动态方法,不需要 function 关键字

return (${this.x},${this.y});

}

getX(){

return this.x;

}

getY(){

return this.y;

}

}

var p = new Point(1,4);

var keys = Object.keys(Point.prototype);

var ownKeys = Object.getOwnPropertyNames(Point.prototype);

console.log(keys); //[]

console.log(ownKeys); //[“constructor”, “toString”, “getX”, “getY”]

console.log(p.hasOwnProperty(“toString”)); //false

console.log(p.proto.hasOwnProperty(“toString”)); //true

//ES5

function Point(x, y){

this.x = x;

this.y = y;

}

Point.prototype = {

toString(){

return (${this.x},${this.y});

},

getX(){

return this.x;

},

getY(){

return this.y;

}

}

var p = new Point(1,4);

var keys = Object.keys(Point.prototype);

var ownKeys = Object.getOwnPropertyNames(Point.prototype);

console.log(keys); //[“toString”, “getX”, “getY”]

console.log(ownKeys); //[“toString”, “getX”, “getY”]

console.log(p.hasOwnProperty(“toString”)); //false

console.log(p.proto.hasOwnProperty(“toString”)); //true

这个例子说明,class 中定义的动态方法是不可枚举的,并且 constructor 也是其自有方法中的一个。

使用 class 注意一下几点:

  • class 中默认是严格模式,即使不写"use strict。关于严格模式可以看:Javascript基础(2) - 严格模式特点

  • 同名 class 不可重复声明

  • class 相当于 object 而不是 map,不具有 map 属性,也不具有默认的 Iterator。

  • constructor 方法在 class 中是必须的,如果没有认为指定,系统会默认生成一个空的 constructor

  • 调用 class 定义的类必须有 new 关键字,像普通函数那样调用会报错。ES5 不限制这一点。

TypeError: Class constructor Point cannot be invoked without ‘new’

  • constructor 方法默认返回值为 this,可以认为修改返回其他的值,但这会导致一系列奇怪的问题:

class Point{

constructor(x,y){

return [x, y];

}

}

new Point() instanceof Point; //false

  • class 声明类不存在变量提升

new Point(); //ReferenceError: Point is not defined

class Point{}

class 表达式

这个和面向对象不一样了,js 中函数可以有函数声明形式和函数表达式2种方式定义,那么 class 一样有第二种2种定义方式:class 表达式

var className1 = class innerName{

//…

};

let className2 = class innerName{

//…

};

const className3 = class innerName{

//…

};

class 表达式由很多特性和 ES5 一样:

  • 和函数表达式类似,这里的innerName可以省略,而且innerName只有类内部可见,实际的类名是赋值号前面的 className。

  • 这样定义的类的作用域,由其所在位置和声明关键字(var, let, const)决定

  • const申明的类是个常量,不能修改。

  • 其变量声明存在提升,但初始化不提升

  • class 表达式也不能和 class 申明重名

ES5 中有立即执行函数,类似的,这里也有立即执行类:

var p = new class {

constructor(x, y){

this.x = x;

this.y = y;

}

toString(){

return (${this.x},${this.y});

}

}(1,5); //立即生成一个对象

console.log(p+“”); //(1,5)

getter, setter 和 Generator 方法

getter 和 setter 使用方式和 ES5 一样, 这里不多说了,举个例子一看就懂:

class Person{

constructor(name, age, tel){

this.name = name;

this.age = age;

this.tel = tel;

this._self = {};

}

get id(){

return this._self.id;

}

set id(str){

if(this._self.id){

throw new TypeError(“Id is read-only”);

} else {

this._self.id = str;

}

}

}

var p = new Person(“Bob”, 18, “13211223344”);

console.log(p.id); //undefined

p.id = ‘30010219900101009X’;

console.log(p.id); //‘30010219900101009X’

var descriptor = Object.getOwnPropertyDescriptor(Person.prototype, ‘id’);

console.log(‘set’ in descriptor); //true

console.log(‘get’ in descriptor); //true

p.id = ‘110’; //TypeError: Id is read-only

Generator 用法也和 ES6 Generator 部分一样:

class Person{

constructor(name, age, tel){

this.name = name;

this.age = age;

this.tel = tel;

this._self = {};

}

*Symbol.iterator{

var keys = Object.keys(this);

keys = keys.filter(function(item){

if(/^_/.test(item)) return false;

else return true;

});

for(let item of keys){

yield this[item];

}

}

get id(){

return this._self.id;

}

set id(str){

if(this._self.id){

throw new TypeError(“Id is read-only”);

} else {

this._self.id = str;

}

}

}

var p = new Person(“Bob”, 18, “13211223344”);

p.id = ‘30010219900101009X’;

for(let info of p){

console.log(info); //依次输出: “Bob”, 18, “13211223344”

}

class 的继承

这里我们只重点讲继承,关于多态没有新的修改,和 ES5 中一样,在函数内判断参数即可。关于多态可以阅读Javascript对象(1) - 对象、类与原型链中关于多态重构的部分。

此外,class 继承属于 ES5 中多种继承方式的共享原型,关于共享原型也在上面这篇文章中讲解过。

class 实现继承可以简单的通过 extends 关键字实现, 而使用 super 关键字调用父类方法:

//定义 ‘有色点’’ 继承自 ‘点’

class ColorPoint extends Point{ //这里延用了上面定义的 Point 类

constructor(x, y, color){

super(x, y); //利用 super 函数调用父类的构造函数

this.color = color;

}

toString(){

return ${super.toString()},${this.color}; //利用 super 调用父类的动态方法

}

}

var cp = new ColorPoint(1, 5, ‘#ff0000’);

console.log(cp+“”); //(1,5),#ff0000

ColorPoint.show(); //“Static function!” 静态方法同样被继承了

cp instanceof ColorPoint; //true

cp instanceof Point; //true

使用 extends 继承的时候需要注意一下几点:

  • super 不能单独使用,不能访问父类属性,只能方法父类方法和构造函数(super本身)

  • 子类没有自己的 this,需要借助 super 调用父类构造函数后加工得到从父类得到的 this,子类构造函数必须调用 super 函数。这一点和 ES5 完全不同。

  • 子类如果没有手动定义构造函数,会自动生成一个构造函数,如下:

constructor(…args){

super(…args);

}

  • 子类中使用 this 关键字之前,必须先调用 super 构造函数

  • 由于继承属于共享原型的方式,所以不要在实例对象上修改原型(Object.setPrototypeOf, obj.__proto__等)

  • super 也可以用在普通是对象字面量中:

var obj = {

toString(){

return MyObj ${super.toString()};

}

}

console.log(obj+“”); //MyObj [object Object]

prototype__proto__

在 class 的继承中

  • 子类的 __proto__ 指向其父类

  • 子类 prototype 的 __proto__ 指向其父类的 prototype

class Point{

constructor(x, y){

this.x = x;

this.y = y;

}

}

class ColorPoint extends Point{

constructor(x, y, color){

super(x, y);

this.color = color;

}

}

ColorPoint.proto === Point; //true

ColorPoint.prototype.proto === Point.prototype; //true

其等价的 ES5 是这样的:

function Point(){

this.x = x;

this.y = y;

}

function ColorPoint(){

this.x = x;

this.y = y;

this.color = color;

}

Object.setPrototypeOf(ColorPoint.prototype, Point.prototype); //继承动态方法属性

Object.setPrototypeOf(ColorPoint, Point); //继承静态方法属性

ColorPoint.proto === Point; //true

ColorPoint.prototype.proto === Point.prototype; //true

这里我们应该理解一下3种继承的 prototype 和 __proto__

  1. 没有继承

class A{}

A.proto === Function.prototype; //true

A.prototype.proto === Object.prototype; //true

  1. 继承自 Object

class A extends Object{}

A.proto === Object; //true

A.prototype.proto === Object.prototype; //true

  1. 继承自 null

class A extends null{}

A.proto === Function.prototype; //true

A.prototype.proto === undefined; //true

判断类的继承关系:

class A{}

class B extends A{}

Object.getPrototypeOf(B) === A; //true

子类的实例的 __proto____proto__ 指向其父类实例的 __proto__

class A{}

class B extends A{}

var a = new A();

var b = new B();

B.proto.proto === A.proto; //true

因此,可以通过修改子类实例的 __proto__.__proto__ 改变父类实例的行为。建议:

  • 总是用 class 取代需要 prototype 的操作。因为 class 的写法更简洁,更易于理解。

  • 使用 extends 实现继承,因为这样更简单,不会有破坏 instanceof 运算的危险。

此外存取器和 Generator 函数都可以很理想的被继承:

class Person{

constructor(name, age, tel){

this.name = name;

this.age = age;

this.tel = tel;

this._self = {};

}

*Symbol.iterator{

var keys = Object.keys(this);

keys = keys.filter(function(item){

if(/^_/.test(item)) return false;

else return true;

});

for(let item of keys){

yield this[item];

}

}

get id(){

return this._self.id;

}

set id(str){

if(this._self.id){

throw new TypeError(“Id is read-only”);

} else {

this._self.id = str;

}

}

}

class Coder extends Person{

constructor(name, age, tel, lang){

super(name, age, tel);

this.lang = lang;

}

}

var c = new Coder(“Bob”, 18, “13211223344”, “javascript”);

c.id = ‘30010219900101009X’;

for(let info of c){

console.log(info); //依次输出: “Bob”, 18, “13211223344”, “javascript”

}

console.log(c.id); //‘30010219900101009X’

c.id = “110”; //TypeError: Id is read-only

多继承

多继承指的是一个新的类继承自已有的多个类,JavaScript 没有提供多继承的方式,所以我们使用 Mixin 模式手动实现:

function mix(…mixins){

class Mix{}

for(let mixin of mixins){

copyProperties(Mix, mixin); //继承静态方法属性

copyProperties(Mix.prototype, mixin.prototype); //继承动态方法属性

}

return Mix;

function copyProperties(target, source){

for(let key of Reflect.ownKeys(source)){

if(key !== ‘constructor’ && key !== “prototype” && key !== “name”){

if(Object(source[key]) === source[key]){

target[key] = {};

copyProperties(target[key], source[key]); //递归实现深拷贝

} else {

let desc = Object.getOwnPropertyDescriptor(source, key);

Object.defineProperty(target, key, desc);

}

}

}

}

}

//使用方法:

class MultiClass extends mix(superClass1, superClass2, //){

//…

}

由于 mixin 模式使用了拷贝构造,构造出的子类的父类是 mix 函数返回的 class, 因此 prototype 和 __proto__ 与任一 superClass 都没有直接的联系,instanceof 判断其属于 mix 函数返回类的实例,同样和任一 superClass 都没有关系。可以这么说:我们为了实现功能破坏了理论应该具有的原型链。

原生构造函数继承

在 ES5 中,原生构造函数是不能继承的,包括: Boolean(), Number(), Date(), String(), Object(), Error(), Function(), RegExp()等,比如我们这样实现:

function SubArray(){}

Object.setPrototypeOf(SubArray.prototype, Array.prototype); //继承动态方法

Object.setPrototypeOf(SubArray, Array); //继承静态方法

var arr = new SubArray();

arr.push(5);

arr[1] = 10;

console.log(arr.length); //1 应该是2

arr.length = 0;

console.log(arr); //[0:5,1:10] 应该为空

很明显这已经不是那个我们熟悉的数组了!我们可以用 class 试试:

class SubArray extends Array{}

var arr = new SubArray();

arr.push(5);

arr[1] = 10;

console.log(arr.length); //2

arr.length = 0;

console.log(arr); //[]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值