Class的基本语法
本系列属于阮一峰老师所著的ECMAScript 6 入门学习笔记
概念
// 生成实例对象的传统方法是通过构造函数
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)
// 为了让写法更接近面向对象语言,引入Class(类)这个概念
class Point{
constructor(x,y){
this.x = x
this.y = y
}
toString(){
return '(' + this.x + ',' + this.y + ')'
}
}
// 由于类的方法都定义在prototype对象上,所以类的新方法可以添加在prototype对象上面
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"]
// 这一点与ES5不同,ES5中对象内部定义的方法是可枚举的
var Point = function(x,y){
// ...
}
Point.prototype.toString = function(){
// ...
}
Object.keys(Point.prototype) // ["toString"]
Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
// 类的属性名,可以采用表达式
let methodName = 'getArea'
class Square {
constructor(length){
// ...
}
[methodName](){
// ...
}
}
严格模式
类和模块的内部,默认就是严格模式。
考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际把整个语言升级到严格模式。
constructor方法
constructor方法是类的默认方法,通过new命名生成对象实例时,自动调用该方法。
// constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象
class Foo{
constructor(){
return Object.create(null)
}
}
new Foo() instanceof Foo // false
// 类必须用new调用,否则会报错。这是和普通构造函数的一个主要区别
类的实例对象
// 与ES5一样,实例属性除非显式定义在其本省(即定义在this对象上),否则都是定义在原型上
class Point{
constructor(x,y){
this.x = x
this.y = y
}
toString(){
return '(' + this.x + ',' + this.y + ')'
}
}
var point = new Point(2,3)
point.toString() // (2,3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
// 与ES5一样,共享一个原型对象
var p1 = new Point(1,2)
var p2 = new Point(2,3)
p1.__proto__ === p2.__proto__ // true
// 这也意味着,可以通过实例的__proto__属性为类添加方法
p1.__proto__.printName = function () { return 'Oops' }
p1.printName() // 'Oops'
p2.printName() // 'Oops'
var p3 = new Point(4,2)
p3.printName() // 'Oops'
// 使用实例的__proto属性改写原型必须相当谨慎,因为这会改变类的原始定义,影响到所有实例,不推荐使用。
Class表达式
// 与函数一样,类也可以使用表达式的形式定义
const MyClass = class Me {
getClassName(){
return Me.name
}
}
// 需要注意的是,这个类的名字是MyClass而不是Me,Me只在Class的内部代码可用,指代当前类
let inst = new MyClass()
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
// 内部未使用的话可以省略Me
const MyClass = class { /* ... */ }
// 利用Class表达式,可以写出立即执行的Class
let person = new class{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}('Angus')
person.sayName() // 'Angus'
不存在变量提升
// 与ES5完全不同的是,类不存在变量提升
new Foo() // ReferenceError
class Foo {}
// 该规定与类的继承有关,必须保证子类在父类之后定义
私有方法
// 私有方法是常见需求,但是ES6不提供,只能通过变通方法模拟实现
// 利用命名区别私有方法(加_),但是不保险,类的外部依旧可以调用这个方法
class Widget{
// 公有方法
foo(baz){
this._bar(baz)
}
// 私有方法
_bar(baz){
return this.snaf = baz
}
}
// 将私有方法移出模块,因为模块内部的所有方法都是对外可见的
class Widget{
foo(baz){
bar.call(this,baz)
}
}
function bar(baz){
return this.snaf = baz
}
// 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值,使第三方无法获取
const bar = Symbol('bar')
const snaf = Symbol('snaf')
export default class myClass{
// 公有方法
foo(baz){
this[bar](baz)
}
// 私有方法
[baz](baz){
return this[snaf] = baz
}
}
私有属性
与私有方法一样,ES6不支持私有属性。目前有一个提案,为class加私有属性,方法是在属性名之前,使用#表示。
class Point{
#x
constructor(x=0){
#x = +x // 写成this.#x亦可
}
get x() { return #x }
set x(value) { #x = +value }
}
this的指向
// 类的方法内部如果有this,则默认指向类的实例。但是一旦单独使用该方法,很可能报错
class Logger {
printName(name = 'there'){
this.print(`Hello ${name}`)
}
print(text){
console.log(text)
}
}
const logger = new Logger()
const { printName } = logger
printName() // TypeError: Cannot read property 'prin' of undefined
// 如果单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而报错
// 解决办法一:在构造方法中绑定this
class Logger {
constructor(){
this.printName = this.printName.bind(this)
}
}
// 解决办法二:使用箭头函数
class Logger{
constructor(){
this.printName = (name = 'there') => {
this.print(`Hello ${name}`)
}
}
}
name属性
// 本质上,ES6的类只是ES5构造函数的一层包装,所以函数的许多特性都被class继承了,包括name属性
class Point {}
// name属性总是返回紧跟在class关键字后面的类名
Point.name // 'Point'
Class的取值函数(getter)和存值函数(setter)
// 与ES5一样,在类的内部可以使用get和set关键字,对某属性设置存值函数和取值函数,拦截该属性的存取行为
class MyClass{
constructor(){
// ...
}
get prop(){
return 'getter'
}
set prop(value){
console.log('setter:' + value)
}
}
let inst = new MyClass()
inst.prop = 123 // setter: 123
inst.prop // 'getter'
// 存值函数和取值函数是设置在属性的Descriptor对象上的
Class的Generator方法
// 如果在方法之前加上星号(*),就表示该方法是一个Generator函数
class Foo{
constructor(...args){
this.args = args
}
*[Symbol.iterator](){
for (let arg of this.args) {
yield arg
}
}
}
// Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器
for (let x of new Foo('Hello','world')) {
console.log(x)
}
// Hello
// world
Class的静态方法
// 相当于实例的原型,所有在类中定义的方法,都被会实例继承。如果在一个方法前加上static关键字,就表示该方法不会被继承,而是直接通过类来调用,称为“静态方法”
class Foo {
static classMethod() {
return 'Hello'
}
}
Foo.classMethod() // 'Hello'
var foo = new Foo()
foo.classMethod() // TypeError: foo.classMethod is not a function
// 如果静态方法中包含this关键字,这个this指的是类,而不是实例
class Foo {
static bar(){
this.baz()
}
static baz(){
console.log('hello')
}
baz(){
console.log('world')
}
}
// this指的是Foo类,而不是Foo实例,等同于调用Foo.baz,另外静态方法可以与非静态方法重名
Foo.bar() // 'hello'
// 父类的静态方法,可以被子类继承
class Foo {
static classMethod(){
return 'hello'
}
}
class Bar extends Foo {}
Bar.classMehod() // 'hello'
// 静态方法也可以从super对象上调用
class Foo {
static classMethod(){
return 'hello'
}
}
class Bar extends Foo {
static classMethod(){
return super.classMethod() + ',too'
}
}
Bar.classMethod() // 'hello,too'
new.target属性
// 该属性一般用在构造函数中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的
function Person(name){
if(new.target !== undefined){
this.name = name
}else{
throw new Error('必须使用new命令生成实例')
}
}
// 另外一种写法
function person(name){
if(new.target === Person){
this.name = name
}else{
throw new Error('必须使用new命令生成实例')
}
}
var person = new Person('Angus') // 正确
var notAPerson = Person.call(person,'Angus') // 报错
// Class内部调用new.target,返回当前Class
class Rectangle{
constructor(length,width){
console.log(new.target === Rectangle)
this.length = length
this.width = width
}
}
var obj = new Rectangle(3,4) // true
// 子类继承父类时,new.target会返回子类
class Square extends Rectangle {
constructor(length){
super(length,length)
}
}
var obj = new Square(3) // false
// 利用这个特点可以写出不能独立使用,必须继承后使用的类
class Shape{
constructor(){
if(new.target === Shape){
throw new Error('本类不能被实例化')
}
}
}
class Rectangle extends Shape {
constructor(length,width){
super()
// ...
}
}
var x = new Shape() // 报错
var y = new Rectangle(3,4) // 正确
// 注意,在函数外部,使用new.target会报错

浙公网安备 33010602011771号