近些日子在git上经常看到以es6编写的js代码。
以前有些了解,但没有深入学习。
现在用的人多了,也就找了些时间来学习了下。
应用:
在webpack 中会使用es6。
当然肯定需要模块来翻译成es5的代码:babel
es6文档看了下,我觉得对于类模块这块用处比较大。
在现实中使用也简单方便些。
js 语言传统是通过构造函数,定义并生成新对象。
在es6中引用了class 类。作为对象的模板。通过class关键字,定义类。
新的class写法只是让对象原型的写法更新清晰、更像面向对象编程的语法而以。
和后端写法类似了。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }constructor j 是构造函数,this 表示实例对象。tostring 是类的方法。
方法的定义 实现是扩展了Point.prototype
实例对象:
var a=new Point(参数)
Point .name //Point
Class 表达式:
const MyClass = class Me { getClassName() { return Me.name; } };以上创建了一个类,类名是MyClass而不是Me. me只能在类的内部代码可用。
let inst = new MyClass();
inst.getClassName() // Me
当然me是可以省略的
const MyClass = class { /* ... */ };
采用class 表达式,可以写出立即执行的class
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}("张三");
person.sayName(); // "张三"
class 不存在变量提升,
class 继承
class之间可以通过extends关键字实现继承。
class ColorPoint extends Point {}
上面定义了一个colorpoint 类,继承了point类的所有属性和方法。
但是由于没有部署任何任何,所以这两个类完全一样。等于复制了一个point。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
上面代码定义了新的构造函数,和tostring方法。
方法内的super指向父类的方法。
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
class的静态方法
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: undefined is not a function
staitc可以创建静态方法或属性。
模块的定义和引用。
js一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件。
在es6之前社区制定了一些模块加载方案,最主要的有commonjs 和amd 两种。
前者用于服务器,后者用于浏览器
es6实现了模块功能,完全可以取代commonjs amd规范。
es6模块的设计思想,是尽量的静态化。使得编译时就能确定模块的依赖关系。
以及输入和输出的变量。
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
上面代码的实质是整体加载fs模块,生成一个对象。然后从这个对象上获取三个方法。这种加载为“运行时加载”
因为只有运行时才能得到这个对象,导致完全没有办法 在编译时做“静态优化”
es6模块不是对象,而是通过export命令显式指定输出的代码,输出时采用静态命令的形式。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面是加载fs模块的三个方法,其他方法不加载。
称为“编译加载”,即es6在编译时就完成模块加载。
当然这也导致了没法引用es模块本身,因为它不是对象。
export 命令
模块功能主要由export 和Import 。
export 命令用于规定模块的对外接口。
Import 命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。
如果你希望外部能够读取模块内部的某个变量,就必须使用export 输出该变量。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
对外输出三个变量。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
也可以这样写。
使用大括号指定要输出的一组变量。
export function multiply (x, y) {
return x * y;
};
还可以输出函数或类。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
export输出的变量就是本来的名字,但是 可以使用as 重合名。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
export语句输出的值是动态绑定,绑定其所在的模块。
上在代码输出变量foo 什为bar ,500ms后变成baz
import 命令
js文件可通过import命令加载这个模块。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
import 命令接受一个对象,里面指定要多其他模块导入的变量名。
大括号里的变量名,必须与被导入模块对外接口名称相同。
import { lastName as surname } from './profile';
as 可将输入的的名称,从命名。
import 命令具有提升效果,会提升到整个模块的头部,首先执行。
foo();
import { foo } from 'my_module';
上面代码不会报错,因为Import 的执行早于foo的调用。
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
模块中可以先输入加载模块,再输出此模块
export 输出和import输入可以结合一起。
import 'lodash'
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用* 指定一个对象,所有输出值都加载在这个对象上面。
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
输出三个方法。
// main.js
import { area, circumference } from './circle';
console.log("圆面积:" + area(4));
console.log("圆周长:" + circumference(14));
上面写法是指定要加载的方法,整体加载如下:
import * as circle from './circle';
console.log("圆面积:" + circle.area(4));
console.log("圆周长:" + circle.circumference(14));
export default 命令
import命令使用时,用户需要知道所需加载的变量名,否则无法加载。
但为了方便,可以用default 命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
默认输出一个函数
// import-default.js
import customName from './export-default';
customName(); // 'foo'
加载时import 可以为默认函数指定任意的名称。
需要注意的是,这时Import命令后面,不使用大括号 。
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
default 命令用在非匿名函数前,也是可以的。
// 输出
export default function crc32() {
// ...
}
// 输入
import crc32 from 'crc32';
// 输出
export function crc32() {
// ...
};
// 输入
import {crc32} from 'crc32';
// modules.js
function add(x, y) {
return x * y;
};
export {add as default};
// 等同于
// export default add;
// app.js
import { default as xxx } from 'modules';
// 等同于
// import xxx from 'modules';
本质上export default 就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
因为export default 命令其实中是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
import customName, { otherMethod } from './export-default';
可以在一条import语句中,同时输入默认方法和其他变量。
模块的继承
模块间可以继承
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
以上继承了circle模块。
export * 表示输出circle 模块的所有属性和方法,当然它会忽略default。
export { area as circleArea } from 'circle';
可以将circle 改名后再输出 。
import * as math from "circleplus";
import exp from "circleplus";
console.log(exp(math.e));
加载模块再定义新引入对象,再获取模块的默认方法定义成exp
import * as math from "circleplus";
import exp from "circleplus";
console.log(exp(math.e));
import * as math from "circleplus";
import exp from "circleplus";
console.log(exp(math.e));
es6模块加载的实质
es6模块加载的机制,与commonjs模块完全不同。
commonjs模块输出的是一个值的拷贝,而es模块输出的是值的引用。
拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
lib加载以后,它的内部变化就不影响输出的counter了。这是因为mod.counter是一个原始类型的值,会被缓存。
es6模块的运行机制,与commonjs不一样。
它遇到模块加载命令Import时,不会执行模块,而是只生成一个动态的只读引用。
等到真的需要用到时,再到模块里去取值。
es6输出有点像地址引用,原始值变了,输入的值也会变。
因此es6模块是动态引用,模块里的变量绑定其白发在的模块。
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
由于es6输入的模块变量,只是一个“符号连接”,所以这个变量是只读的。对它进行重新赋值会报错。