ES6小记(6)
ES6小计(6)
17. Class
17.1 Class基本语法
JavaScript中其实是没有类这个概念的,有的只是对象的引用。
function Point(x, y) {
this.x = x
this.y = y
}
Point.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ')'
}
上面这种写法不方便,且封装不完全。故ES6引入了Class概念做为对象模板,新的这个写法仅仅是让对象原型的写法更加清晰而已。
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return '(' + this.x + ', ' + this.y + ')'
}
}
可以看出ES6中类的写法中,有一个constructor
方法,称为构造函数方法,类中this
代表实例对象,另类内部函数类似于对象,但是属性之间不要加,
,否则会报错。自定义属性需要放在constructor
构造函数中。
typeof Point // function
Point === Point.prototype.constructor
类的数据类型就是一个函数,类本身指向构造函数。类的所有方法(包括构造方法)均定义在类的prototype属性上。实例方法的调用,就是调用原型上的方法。类内部定义的所有方法都是不可枚举的,这点与ES5中prototype上的方法不同,但很有意义。
constructor方法是类的默认方法,在实例化类new
时执行,若没有显示定义constructor方法,系统将会自动为类添加一个空的constructor方法。
类的实例化同ES5的构造函数一致,使用new
关键字,若没有使用new
实例化,将会报错。类的所有实例共享一个原型对象,同ES5的继承一致。
类的name
属性总是返回class
关键字后面的类名。class Point {}; Point.name // Point
Class定义不存在变量提升,与let
一致,必须先定义,再使用,当使用未定义的Class时,会报错。
类和模块的内部默认为严格模式,所以不需要使用use strict
指定运行模式。
17.2 Class的继承
Class之间可以通过extends
关键字实现继承。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y)
this.color = color
}
toString() {
return this.color + ' ' + super.toString()
}
}
let cp = new ColorPoint(25, 7, 'green')
cp instanceof ColorPoint // true
cp instanceof Point // true
Class内部的super
指代父类的实例。子类必须在constructor
方法中调用super
方法,否则在新建实例的时候会报错。这是因为子类没有自己的this
对象,而是继承父类的this对象,然后对其加工。如不调用super
方法,则得不到this
对象,自然也就无法返回this
实例。故必须在使用super
后再使用this
。
类的prototype
属性和__proto__
属性:大多数浏览器的ES5实现中,每一个对象均有__proto__
属性,指向对应构造函数的prototype
属性。而类同时有prototype
属性和__proto__
属性,因此存在两条继承链。
- 子类的
__proto__
属性表示构造函数的继承,总指向父类(实例继承实例); 子类
prototype
属性的__proto__
属性表示方法的继承,总指向父类的prototype
属性(原型继承原型)。class A {}
class B extends A {}
B.proto === A // true
B.prototype.proto === A.prototype // true
B.Proto.proto === A.proto // true
Object.getPrototypeOf()Object.getPrototypeOf()
可以用于从子类上获取父类,可以用此方法判断一个类是否继承了另一个类。
Object.getPrototypeOf(ColorPoint) === Point // true
17.3 原生构造函数的继承
ECMAScript的原生构造函数:
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- Regexp()
- Error()
- Object()
ES6之前,由于子类无法获取原生构造函数的内部属性,故无法对原生构造函数进行继承。而通过extends
进行继承,是通过先建立父类实例对象的this
,再对this
对象进行修饰,故extends
的继承可以继承原生构造函数,也就意味着可以在原生构造函数的基础上进行新的类定义。
class MyArray extends Array {
constructor(...args) {
super(...args)
}
}
17.4 Class的getter和setter
与ES5一致,在Class内部可以使用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
17.5 Class的Generator方法
同对象的Generator函数一致,只需要在属性函数名前加上*
即可。
17.6 Class的静态方法
在类中定义的方法均会被实例继承,但若在方法前加上static
关键字,则表示该方法不会被实例继承。而只能通过类取调用,称之为“静态方法”。
class Foo {
static classMethod() {
return 'abc'
}
}
Foo.classMethod() // abc
var foo = new Foo()
foo.classMethod() // classMethod is not a function
class Bar extends Foo {}
Bar.clasMethod() // abc
静态方法可以被子类继承及调用。
17.7 Class的静态属性
静态属性是指在Class本身的属性,即Class.propName,而不是在实例对象this上的属性。ES6规定类内只能定义属性,不能定义方法,故想给Class定义属性,需要在外部定义,且Class实例的属性也只能在constructor方法内定义。
class Foo {}
Foo.prop = 1
Foo.prop // 1
17.8 new.target属性
new.target
是构造函数的内部属性,只能在构造函数内使用。new.target
返回通过new
命令所作用的构造函数,若构造函数不是通过new
调用,则返回undefined
。此属性可以用于指定类实例的创建方式。
function Person(name) {
if (new.target !== undefined) { // 或 new.target === Person
this.name = name
} else {
throw new Error('请使用new进行实例化')
}
}
注意子类继承父类时,new.target会返回子类。
18. Module
模块加载规范:
CommonJS: 模块就是对象,通过对象的加载而引用,运行时加载,同步执行。Node中,主要就是使用CommonJS进行模块加载。
// file
exports.sum = function() {}
// file2
module.exports = {sum: 123}
// main
let {sum} = require('file')
AMD: 异步的加载模块,先定义所有依赖,然后在加载完成后的回调函数中执行。
define(['clock'],function(clock){
clock.start()
})
CMD: 依赖就近,用的时候再require。
define(function(require, exports, module) {
var clock = require('clock')
clock.start()
})
ES6模块加载: ES6模块不是对象,而是通过export
命令显示指定输出的代码,编译时加载。
import {stat, exists, readFile} from 'fs'
// 从fs模块加载3个方法,其它不加载
另ES6的模块自动采用严格模式。
18.1 export命令
export
用于规定模块对外接口。
// file.js
export var firstName = 'Michael'
export var lastName = 'Jackson'
// file2.js
var firstName = 'Michael'
var lastName = 'Jackson'
export {firstName, lastName}
export
命令可以出现在模块的任意位置,只要处于模块的顶层即可,当处于块级作用域时,会报错。
18.2 import命令
import
用于加载文件模块。
import {firstName as fName, lastName as lName} from 'file2.js'
import
的变量名可以用as
进行重命名。注意import
命令具有变量提升效果,会优先定义。建议不去掉大括号,当多变量引用时,方便阅读。
18.3 模块的整体加载
当需要加载整个模块时,可用*
和as
指定一个对象用于承接export的所有属性和方法。
import * as total from 'file.js'
total // {firstName, lastName}
18.4 module命令
module
命令可以取代import
做整体模块输入。
module total from 'file.js'
total // {firstName, lastName}
18.5 export default命令
export default
用于指定模块的默认输出,当文件需要引用一个模块的默认输出时,直接import
到自定义变量即可。显然export default
一个模块只能有一个。
// file.js
export default {
a: 1,
b: function() {}
}
// main.js
import file from 'file.js'
file // {a: 1, b: function() {}}
18.6 ES6模块加载实质
ES6模块输出的是对象的引用,而CommonJS输出的是值得拷贝。
// file.js
var counter = 3
function incCounter() {
counter++
}
module.exports = { counter, incCounter }
// main.js
var counter = require('file.js').counter
var incCounter = require('file.js').incCounter
console.log(counter) // 3
incCounter()
console.log(counter) // 3
// file2.js
export let counter = 3
export function incCounter() {
counter++
}
// main2.js
import {counter, incCounter} from 'file2.js'
console.log(counter) // 3
incCounter()
console.log(counter) // 4
THE END!