本系列属于阮一峰老师所著的ECMAScript 6 入门学习笔记javascript
// 生成实例对象的传统方法是经过构造函数 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](){ // ... } }
类和模块的内部,默认就是严格模式。java
考虑到将来全部的代码,其实都是运行在模块之中,因此ES6实际把整个语言升级到严格模式。es6
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属性改写原型必须至关谨慎,由于这会改变类的原始定义,影响到全部实例,不推荐使用。
// 与函数同样,类也可使用表达式的形式定义 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,则默认指向类的实例。可是一旦单独使用该方法,极可能报错 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}`) } } }
// 本质上,ES6的类只是ES5构造函数的一层包装,因此函数的许多特性都被class继承了,包括name属性 class Point {} // name属性老是返回紧跟在class关键字后面的类名 Point.name // 'Point'
// 与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对象上的
// 若是在方法以前加上星号(*),就表示该方法是一个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
// 至关于实例的原型,全部在类中定义的方法,都被会实例继承。若是在一个方法前加上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命令做用于的那个构造函数。若是构造函数不是经过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会报错