简介
Javascript语言的传统方法是通过构造函数定义并生成新对象。
function Point (x, y) {
this.x = x
this.y = y
}
Point.prototype.toString = function () {
return '(' + this.x + ',' + this.y +')'
}
let p = new Point(1, 2)
上面这种写法与传统的面向对象语言(c++和java)的写法差异很大,ES6提供了更接近传统语言的写法,引入了Class(类)这个概念作为对象的模版。通过class关键字可以定义类。
基本上ES6中的class可以看作ES5构造函数的语法糖,它的绝大部分功能ES5原型都可以实现,新的class写法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。上面的代码可以用ES6改写成这样:
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
toString () {
return '(' + this.x + ',' + this.y +')'
}
}
从上面代码可以看出,ES5构造函数Point对应ES6的Point类的构造方法。
class类型的方法不需要用逗号分隔。
ES6的类完全可以看作构造函数的另一种写法。
class Point {
//
}
typeof Point // 'function'
Point === Point.protoytpe.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候也是直接对类使用new命令,跟构造函数的用法完全一致。
class Bar {
doStuff () {
console.log('stuff')
}
}
let b = new Bar()
b.doStuff() // stuff
构造函数的prototype属性在ES6的类上继续存在。事实上类的所有方法都定义在类的prototype属性上。
class Point {
constructor () {
// ...
}
toString () {
// ...
}
toValue () {
// ...
}
}
// 等同于
Point.prototype = {
constructor () {},
toString () {},
toValue () {}
}
在类的实例上调用方法,其实就是调用原型上的方法。由于类的方法(除了constructor以外)都定义在prototype对象上,所以类的新方法可以添加在prototype。
另外,类的内部定义的所有方法都是不可枚举的。
class Point {
constructor () {
// ...
}
toString () {
// ...
}
}
Object.keys(Point.prototype) // []
Object.getOwnPropertyNames(Point.prototype) // ['constructor', 'toString']
let Point = function (x, y) {
// ...
}
Point.prototype.toString = function () {
// ...
}
Object.keys(Point.prototype) // ['toString']
Object.getOwnPropertyNames(Point.prototype) // ['constructor', 'toString']
上面代码中,toString方法是Point类内部定义的方法,它是不可枚举的。这一点与ES5的行为不一致。
类的属性名可以是表达式,也可以是变量。
let methodNames = 'getArea'
class Square {
[methodNames] () {
// ...
}
}
constructor方法
constructor方法是类的默认方法,通过new命令生成对象实例时自动调用该方法。一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加。
class Point {}
// 等同于
class Point {
constructor () {}
}
constructor方法默认返回实例对象(即this),不过完全可以指定返回另外一个对象。
class Foo {
constructor () {
return Object.creat(null)
}
}
new Foo() instance Foo // false
上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。类必须使用new来调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
类的实例对象
与ES5一样,实例的属性除非显示定义在其本身(即this对象)上,否则都是定义在原型(即class)上。
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
toString () {
return '(' + this.x + ',' + this.y +')'
}
}
let point = new Point(2, 3)
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
上面代码中,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty返回true,而toString是原型的属性,所以返回false。这些都与ES5行为保持一致。
与ES5一样,类的所有实例共享一个原型对象。
let p1 = new Point(2, 3)
let p2 = new Point(1, 1)
p1.__proto === p2.__proto__
// true
p1.__proto__.printName = function () {return 'oops'}
let p3 = new Point(4, 2)
p3.printName() // oops
上面代码显示p1、p2、p3都是公用一个类,所以在p1上添加了一个方法在p3上也可以调用。
Class表达式
与函数一样,Class也可以使用表达式的形式定义。
const MyClass = class Me {
getClassName () {
return Me.name
}
}
这个类的名字是myClass而不是Me,Me只在Class内部代码可用,指代当前类。
let inst = new MyClass()
inst.getClassName() // Me
Me.name // 报错
上面代码表示,Me只在Class内部有定义。如果Class内部没有用到也可以省略Me。
不存在变量提升
Class类跟普通函数不同之处还在于,前者没有变量提升,后者有。
总结:构造函数和类的异同
相同点:ES5中是在构造函数的原型上定义方法,这样子实例就会继承构造函数的所有方法,ES6是在类的原型上定义方法,实例也会继承类的所有方法,类可以看成是构造函数的语法糖。
不同点:写法不同,构造函数的方法需要写在prototype上,类省略了prototype,但是定义的方法还是在prototype上。
属性枚举:构造函数定义的方法是可枚举的,类定义的方法都是不可枚举的。
调用:构造函数可以不用new调用,但是类必须要用new调用,否则报错。
ES6的类本质上是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,也可以理解为实例的原型对象
网友评论