学习ES6 入门教程的总结学习:
1. 查看 Node.js 默认没有打开的 ES6 实验性语法
// Linux & Mac
$ node --v8-options | grep harmony
// Windows
$ node --v8-options | findstr harmony
2.Babel转码器 将 ES6 代码转为 ES5 代码,就能在不支持的 JavaScript 环境执行了;
Babel 的配置文件是.babelrc
,存放在项目的根目录下
Babel 提供命令行工具@babel/cli
,用于命令行转码。
$ npm install --save-dev @babel/core
{
"presets": [
"@babel/env",
"@babel/preset-react"
],
"plugins": []
}
$ npm install --save-dev @babel/cli
3. let
命令所在的代码块内有效;
不存在变量提升:所声明的变量一定要在声明后使用,否则报错;
暂时性死区:在代码块内,使用let
命令声明变量之前,该变量都是不可用的;
let
不允许在相同作用域内,重复声明同一个变量,不能在函数内部重新声明参数;
ES5 只有全局作用域和函数作用域,没有块级作用域,let
为 JavaScript 新增了块级作用域;
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
4.const
声明一个只读的常量。一旦声明,常量的值就不能改变。其他用法和let一样;
const PI = 3.1415;
PI // 3.1415
PI = 3; // TypeError: Assignment to constant variable.
5.顶层对象,同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
关键字,但是有局限性。
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。 - 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。 - Node 里面,顶层对象是
global
,但其他环境都不支持。
6. ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
var a = 1;
let b = 1;
console.log(window.a,window.b)//1 undefined
// 数组的结构赋值
let [c, d, f] = [1,2,3];
console.log(c,d,f)//1 2 3
let [g, ...h] = [1,2,3,4];
console.log(g,h)//1 [2, 3, 4]
let [i, j, ...k] = ["a"];
console.log(i,j,k)//a undefined []
let [foo] = [];
console.log(foo)//undefined
// 如果等于右边等于undefined设置的默认值优先
let [foo1 = true] = [];
console.log(foo1)//true
let [foo2 = 1] = [null]
console.log(foo2)//null
let [foo3 = 1] = [undefined]
console.log(foo3)//1
//部分解构不完全解构
let [x,y] = [1,2,3];
console.log(x,y)//1 2
let [l, [m], n] = [1, [2, 3], 4];
console.log(l,m,n)//1 2 4
// 报错
let [a1] = 1;
let [a2] = false;
let [a3] = NaN;
let [a4] = undefined;
let [a5] = null;
let [a6] = {}
console.log(a1,a2,a3,a4,a5,a6)//Uncaught TypeError: ... is not iterable
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
解构赋值允许指定默认值:ES6 内部使用严格相等运算符(===
),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined
,默认值才会生效,如果一个数组成员是null
,默认值就不会生效,因为null
不严格等于undefined
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined x用y做默认值时,y还没有声明
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
数值和布尔值的包装对象都有toString属性,因此变量s都能取到值
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
函数的参数也可以使用解构赋值。
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
undefined就会触发函数参数的默认值。
7. 变量的解构赋值用途很多。
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
上面代码交换变量x
和y
的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
(3)函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
(4)提取 JSON 数据
解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
上面代码可以快速提取 JSON 数据的值。
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';
这样的语句。
(6)遍历 Map 结构
任何部署了 Iterator 接口的对象,都可以用for...of
循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
(7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
8.字符的 Unicode 表示法
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
'中' === '\u4e2d' // true
字符串for...of
循环遍历
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "𠮷"
可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
模板字符串(template string)是增强版的字符串,用反引号(`)标识。模板字符串中嵌入变量,需要将变量名写在${}
之中:
能运算,调用函数,嵌套
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
let func = (name) => `Hello ${name}!`;
func('Jack') // "Hello Jack!"
alert`hello`
// 等同于
alert(['hello'])
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。函数tag依次会接收到多个参数。
第一个参数:['Hello ', ' world ', '']
第二个参数: 15
第三个参数:50
function tag(s, v1, v2) {
console.log(s[0]);
console.log(s[1]);
console.log(s[2]);
console.log(v1);
console.log(v2);
return "OK";
}
tag`Hello ${ a + b } world ${ a * b}`;
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"
字符串新增方法:
- String.fromCodePoint() 兼容>码点
- String.raw() 转义
- 实例方法:codePointAt() 返回字符的码点
- 实例方法:normalize() Unicode 正规化
- 实例方法:includes(), startsWith(), endsWith() 是否找到参数字符串 返回值true
- 实例方法:repeat() 返回一个新字符串,表示将原字符串重复
n
次 - 实例方法:padStart(),padEnd() 字符串补全长度
- 实例方法:trimStart(),trimEnd() 消除字符串空格
- 实例方法:matchAll() 返回一个正则表达式在当前字符串的所有匹配
- 实例方法:replaceAll() 替换所有匹配
- 实例方法:at() 接受一个整数作为参数,返回参数指定位置的字符,支持负索引
String.raw`Hi\\n`
// 返回 "Hi\\\\n"
String.raw`Hi\\n` === "Hi\\\\n" // true
String.raw = function (strings, ...values) {
let output = '';
let index;
for (index = 0; index < values.length; index++) {
output += strings.raw[index] + values[index];
}
output += strings.raw[index]
return output;
}
// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
使用第二个参数n时(第二个参数可有可无),endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
'aabbcc'.replace('b', '_')
// 'aa_bcc'
'aabbcc'.replace(/b/g, '_')
// 'aa__cc'
'aabbcc'.replaceAll('b', '_')
// 'aa__cc'
/ $& 表示匹配的字符串,即`b`本身
// 所以返回结果与原字符串一致
'abbc'.replaceAll('b', '$&')
// 'abbc'
// $` 表示匹配结果之前的字符串
// 对于第一个`b`,$` 指代`a`
// 对于第二个`b`,$` 指代`ab`
'abbc'.replaceAll('b', '$`')
// 'aaabc'
// $' 表示匹配结果之后的字符串
// 对于第一个`b`,$' 指代`bc`
// 对于第二个`b`,$' 指代`c`
'abbc'.replaceAll('b', `$'`)
// 'abccc'
// $1 表示正则表达式的第一个组匹配,指代`ab`
// $2 表示正则表达式的第二个组匹配,指代`bc`
'abbc'.replaceAll(/(ab)(bc)/g, '$2$1')
// 'bcab'
// $$ 指代 $
'abc'.replaceAll('b', '$$')
// 'a$c'
const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"
8. 正则
'2015-01-02'.replace(re, (
matched, // 整个匹配结果 2015-01-02
capture1, // 第一个组匹配 2015
capture2, // 第二个组匹配 01
capture3, // 第三个组匹配 02
position, // 匹配开始的位置 0
S, // 原字符串 2015-01-02
groups // 具名组构成的一个对象 {year, month, day}
) => {
let {day, month, year} = groups;
return `${day}/${month}/${year}`;
});
const string = 'test1test2test3';
const regex = /t(e)(st(\d?))/g;
for (const match of string.matchAll(regex)) {
console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
遍历器转为数组是非常简单的,使用...运算符和Array.from()方法就可以了。
// 转为数组的方法一
[...string.matchAll(regex)]
// 转为数组的方法二
Array.from(string.matchAll(regex))
9.数值
二进制和八进制数值的新的写法:分别用前缀0b
(或0B
)和0o
(或0O
)表示
0b111110111 === 503 // true
0o767 === 503 // true
将0b和0o前缀的字符串数值转为十进制,要使用Number方法。
Number('0b111') // 7
Number('0o10') // 8
数值分隔符 _ 有几个使用注意点:
- 不能放在数值的最前面(leading)或最后面(trailing)。
- 不能两个或两个以上的分隔符连在一起。
- 小数点的前后不能有分隔符。
- 科学计数法里面,表示指数的
e
或E
前后不能有分隔符。 - Number()parseInt()parseFloat() 不支持分隔符
let num = 12_345;
num // 12345
num.toString() // 12345
Number('123_456') // NaN
parseInt('123_456') // 123
如果参数类型不是数值,Number.isFinite
一律返回false
。
如果参数类型不是NaN
,Number.isNaN
一律返回false
ES6 将全局方法parseInt()
和parseFloat()
,移植到Number
对象上面,取整取字符串
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
Number.isInteger()
用来判断一个数值是否为整数。
Number.EPSILON
可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)
),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。
Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
这两个常量,用来表示这个范围的上下限。
Math 静态数学方法 Math 对象的扩展 :(共17个)
1.Math.trunc
方法用于去除一个数的小数部分,返回整数部分。
对于非数值,Math.trunc
内部使用Number
方法将其先转为数值。
对于空值和无法截取整数的值,返回NaN
。
Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};
2.Math.sign
方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
- 参数为正数,返回
+1
; - 参数为负数,返回
-1
; - 参数为 0,返回
0
; - 参数为-0,返回
-0
; - 其他值,返回
NaN
Math.sign = Math.sign || function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
3.Math.cbrt()
方法用于计算一个数的立方根
Math.cbrt = Math.cbrt || function(x) {
var y = Math.pow(Math.abs(x), 1/3);
return x < 0 ? -y : y;
};
4.Math.clz32()
方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。
5.Math.imul
方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
6.Math.fround
方法返回一个数的32位单精度浮点数形式。(2的24次方)
Math.fround = Math.fround || function (x) {
return new Float32Array([x])[0];
};
7.Math.hypot
方法返回所有参数的平方和的平方根
8. Math.expm1(x)
返回 ex - 1,即Math.exp(x) - 1
。
Math.expm1 = Math.expm1 || function(x) {
return Math.exp(x) - 1;
};
9.Math.log1p(x)
方法返回1 + x
的自然对数,即Math.log(1 + x)
。如果x
小于-1,返回NaN
。
Math.log1p = Math.log1p || function(x) {
return Math.log(1 + x);
};
10.Math.log10(x)
返回以 10 为底的x
的对数。如果x
小于 0,则返回 NaN。
Math.log10 = Math.log10 || function(x) {
return Math.log(x) / Math.LN10;
};
11.Math.log2(x)
返回以 2 为底的x
的对数。如果x
小于 0,则返回 NaN。
Math.log2 = Math.log2 || function(x) {
return Math.log(x) / Math.LN2;
};
12.双曲线(6个)
Math.sinh(x)
返回x
的双曲正弦(hyperbolic sine)Math.cosh(x)
返回x
的双曲余弦(hyperbolic cosine)Math.tanh(x)
返回x
的双曲正切(hyperbolic tangent)Math.asinh(x)
返回x
的反双曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回x
的反双曲余弦(inverse hyperbolic cosine)Math.atanh(x)
返回x
的反双曲正切(inverse hyperbolic tangent)