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 Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
同样有原型对象表
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
因为实例b和B.prototype指向同一个地方也就是B的原型,所以上面也就不难理解了。
由于类的方法都定义在
prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign
方法可以很方便地一次向类添加多个方法。
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
上面的意思是这样子的
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
方法就是可枚举的。