本系列属于阮一峰老师所著的学习笔记
概念
// 生成实例对象的传统方法是通过构造函数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') // truepoint.hasOwnProperty('toString') // falsepoint.__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() // MeMe.name // ReferenceError: Me is not defined// 内部未使用的话可以省略Meconst MyClass = class { /* ... */ }// 利用Class表达式,可以写出立即执行的Classlet person = new class{ constructor(name){ this.name = name } sayName(){ console.log(this.name) }}('Angus')person.sayName() // 'Angus'
不存在变量提升
// 与ES5完全不同的是,类不存在变量提升new Foo() // ReferenceErrorclass 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 } = loggerprintName() // TypeError: Cannot read property 'prin' of undefined// 如果单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而报错// 解决办法一:在构造方法中绑定thisclass 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: 123inst.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,返回当前Classclass 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会报错