推荐阮一峰大佬的ES 入门教程,中文文档没有比他更详细的了
像是解構賦值、箭頭函數、class 語法等等都是在 ES6 版本當中規範。因為 ES6 在 2015 年發布所以又叫做 ES2015,目前看起來似乎是用 ES + 年份居多,如 ES2016/ES2017。
let/const
为什么要引入 let/const
var 的缺陷-变量提升,变量提升带来的问题
- 变量容易在不被察觉的情况下呗覆盖掉
- 本应销毁的变量没有被销毁
JavaScript 是如何支持块级作用域的
let/const 的典型使用场景
如何使用 ES5 模拟 const(面试题)
todo。。。
如何使用 ES5 模拟块级作用域(面试题)
箭头函数(arrows)
箭头函数是使用=>
语法的函数简写。与一般函数不同的是
- 函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象。
- this 对象的指向是可变的,但是在箭头函数中,它是固定的。
- 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用rest
参数代替。 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
var f = (v) => v;
// 等同于
var f = function (v) {
return v;
};
function foo() {
setTimeout(() => {
console.log("id:", this.id);
}, 100);
}
var id = 21;
// 箭头函数导致this总是指向函数定义生效时所在的对象({id: 42}),所以打印出来的是42
foo.call({ id: 42 });
// id: 42
// 对象不构成单独的作用域,使得this指向全局对象
globalThis.s = 21;
const obj = {
s: 42,
m: () => console.log(this.s),
};
obj.m(); // 21
更多详细内容参考ES 入门教程-箭头函数
类(Class)
// ES5
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);
// ES6
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return "(" + this.x + ", " + this.y + ")";
}
}
更多详细内容参考ES 入门教程-Class
对象的扩展(enhanced object literals)
对象的属性的简洁表示法
let test1 = "a";
let test2 = "b";
// bad 👴
let obj = { test1: test1, test2: test2 };
// better 👶
let obj = { test1, test2 };
const foo = "bar";
const method = function () {
return "Hello!";
};
const filed = "name";
const baz = {
foo,
method,
[filed]: "小王",
};
// 等同于
const baz = {
foo: foo,
method: function () {
return "Hello!";
},
name: "小王",
};
更多详细内容参考ES 入门教程-对象扩展
模板字符串
如果你厌倦了使用 + 将多个变量连接成一个字符串,那么这个简化技巧将让你不再头痛。
// 字符串中嵌入变量
// bad 👴
const welcome = "Hi " + test1 + " " + test2 + ".";
// better 👶
const welcome = `Hi ${test1} ${test2}`;
// 跨行字符串
// bad 👴
const data =
"abc abc abc abc abc abc\n\t" + "test test,test test test test\n\t";
// better 👶
const data = `abc abc abc abc abc abc
test test,test test test test`;
更多详细内容参考ES 入门教程-字符串模板
数组解构+扩展运算符
var [a] = [];
a === undefined; // true
var [a = 1] = [];
a === 1; // true
场景: 交换数组元素的位置
// bad 👴
const swapWay = (arr, i, j) => {
const newArr = [...arr];
let temp = newArr[i];
newArr[i] = list[j];
newArr[j] = temp;
return newArr;
};
ES6 开始,从数组中的不同位置交换值变得容易多了
// better 👶
const swapWay = (arr, i, j) => {
const newArr = [...arr];
const [newArr[j],newArr[i]] = [newArr[i],newArr[j]];
return newArr;
};
更多详细内容参考ES 入门教程-数组的扩展运算符
函数默认参数+剩余参数+扩展运算符
//如果没有传递y 或者y===undefined ,则y=12
function f(x, y = 12) {
return x + y;
}
f(3) == 15;
function f(x, ...y) {
// y 是一个数组
return x * y.length;
}
f(3, "hello", true) == 6;
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1, 2, 3]) == 6;
// bad 👴
function add(test1, test2) {
if (test1 === undefined) test1 = 1;
if (test2 === undefined) test2 = 2;
return test1 + test2;
}
// better 👶
add = (test1 = 1, test2 = 2) => test1 + test2;
add(); //output: 3
更多详细内容参考ES 入门教程-函数默认参数
块级作用域变量
随着 ES6 中引入 let/const
关键字,JS 才具有函数作用域和全局作用域,现在 JS 也可以有块级作用域了。
function f() {
{
let x;
{
// 正常,因为在一个新的块级作用域中
const x = "sneaky";
// const 定义的是常量无法被修改,因此会报错
x = "foo";
}
// 在块级作用域中已声明x,因此会报错
let x = "inner";
}
}
更多详细内容参考ES 入门教程-unicode
遍历/迭代器+for..of(iterators + for..of)
一个数据结构只要部署了 Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用 for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的 Symbol.iterator
方法。
for ... of
是for ... in
和forEach()
的替代方法,它循环访问可迭代的数据结构,如数组,映射,集合和字符串。
JavaScript 原有的 for...in
循环,只能获得对象的键名,不能直接获取键值。ES6 提供 for...of
循环,允许遍历获得键值。
var arr = ["a", "b", "c", "d"];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
const str = "helloworld";
for (let a of str) {
console.log(a); // h e l l o w o r l d
}
更多详细内容参考ES 入门教程-iterators
生成器(generators)
Generators 使用function *
和yield
简化了迭代器的创建。 声明为function *
的函数一个遍历器对象,也就是说,Generator 函数是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
生成器是迭代器的子类型,因此具有next
和throw
方法。
yield
表达式是暂停执行的标记,而next
方法可以恢复执行
注意:ES7 出现后,推荐使用await
。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
下面是一个利用 Generator 函数和for...of
循环,实现斐波那契数列的例子。
var fibonacci = {
[Symbol.iterator]: function* () {
let [prev, curr] = [0, 1];
for (;;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
},
};
for (var n of fibonacci) {
//
if (n > 1000) break;
console.log(n);
}
从上面代码可见,使用for...of
语句时不需要使用next
方法。
利用for...of
循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有迭代器接口,无法使用for...of
循环,通过 Generator 函数为它加上这个接口,就可以用了。
生成器(Generator) 实质上继承了迭代器(Iterator)
interface Generator extends Iterator {
next(value?: any): IteratorResult;
throw(exception: any);
}
更多详细内容参考ES 入门教程-iterators
Unicode
ES6 增强了 Unicode 的功能,包括
- 支持字符的 Unicode 表示法
举例来说,“中”的 Unicode 码点是 U+4e2d
,你可以直接在字符串里面输入这个汉字,也可以输入它的转义形式\u4e2d
,两者是等价的。
"中" === "\u4e2d"; // true
- 使用
/u
匹配码点的正则表达式
// new RegExp behaviour, opt-in ‘u’
"𠮷".match(/./u)[0].length == 2;
- 获取 32 位的 UTF-16 字符的码点-
codePointAt
"𠮷".codePointAt(0) == 0x20bb7;
let s = "𠮷a";
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61
更多详细内容参考ES 入门教程-unicode
模块化(modules)
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
使用 export default
或 export
进行导出
// math.js
export const pi = 3.141593;
export default function sum(x, y) {
return x + y;
}
使用 import
进行导入
// app.js
import sum, { pi } from "./math";
alert("2π = " + sum(pi, pi));
更多详细内容参考ES 入门教程-module
模块加载器规则(module loaders)
模块加载器支持:
- 异步加载
- 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
- 模块之中,顶层的
this
关键字返回 undefined,而不是指向window
。也就是说,在模块顶层使用this
关键字,是无意义的
//index.js
const x = 1;
console.log(x === window.x); //false
console.log(this === undefined); // true
利用顶层的 this 等于 undefined 这个语法点,可以侦测当前代码是否在 ES6 模块之中。
const isNotModuleScript = this !== undefined;
更多详细内容参考ES 入门教程-module-loader
import
and export
Map + Set + Weakmap + Weakset
ES6 提供了新的数据结构 Set
。它类似于数组,但是成员的值都是唯一的,没有重复的值。
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
ES6 提供了 Map
数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map
结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map
比 Object 更合适。
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
WeakMap
结构与 Map
结构类似,也是用于生成键值对的集合。
WeakMap
与 Map
的区别有两点。
- WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。
- WeakMap 的键名所指向的对象,不计入垃圾回收机制。
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined;
WeakSet
结构与 Set
类似,也是不重复的值的集合。但是,它与 Set
有两个区别。
- WeakSet 的成员只能是对象,而不能是其他类型的值。
- WeakSet 中的对象都是弱引用
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
更多详细内容参考ES 入门教程-Set 和 Map
代理(proxies)
Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改。 可以用于操作拦截,日志记录/分析等。
// 代理一个普通对象
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
},
};
var p = new Proxy(target, handler);
// true
p.world === "Hello, world!";
下面是 Proxy
所有可以代理的"元操作"
var handler =
{
get:...,
set:...,
has:...,
deleteProperty:...,
apply:...,
construct:...,
getOwnPropertyDescriptor:...,
defineProperty:...,
getPrototypeOf:...,
setPrototypeOf:...,
enumerate:...,
ownKeys:...,
preventExtensions:...,
isExtensible:...
}
// 代理一个函数对象
var target = function () {
return "I am the target";
};
var handler = {
apply: function (receiver, ...args) {
return "I am the proxy";
},
};
var p = new Proxy(target, handler);
//true
p() === "I am the proxy";
更多详细内容参考ES 入门教程-proxy
symbols
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值
Symbol
值通过 Symbol 函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol
类型。凡是属性名属于 Symbol
类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
var MyClass = (function () {
//
var key = Symbol("key");
function MyClass(privateData) {
this[key] = privateData;
}
MyClass.prototype = {
doStuff: function () {
this[key];
},
};
return MyClass;
})();
var c = new MyClass("hello");
// true
console.log(c["key"] === undefined);
创建 Symbol
的时候,可以添加一个描述。
const sym = Symbol("foo");
上面代码中,sym
的描述就是字符串 foo
。
Symbol
作为属性名,遍历对象的时候,该属性不会出现在 for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol
属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol
值。
const obj = {};
let a = Symbol("a");
let b = Symbol("b");
obj[a] = "Hello";
obj[b] = "World";
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols;
// [Symbol(a), Symbol(b)]
更多详细内容参考ES 入门教程-symbol
期约(promises)
Promise
是一个用于异步编程的库,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。 许多现有的 JavaScript 库已经使用了 Promise
。
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
});
}
var p = timeout(1000)
.then(() => {
return timeout(2000);
})
.then(() => {
throw new Error("hmm");
})
.catch((err) => {
return Promise.all([timeout(100), timeout(200)]);
});
更多详细内容参考ES 入门教程-promise
math + number + string + array + object APIs
添加了许多类型的扩展方法,包括:Math
,Array
,String
,Object
Number.EPSILON;
Number.isInteger(Infinity); // false
Number.isNaN("NaN"); // false
Math.acosh(3); // 1.762747174039086
Math.hypot(3, 4); // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2); // 2
"abcde".includes("cd"); // true
"abc".repeat(3); // "abcabcabc"
Array.from(document.querySelectorAll("*")); // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[(0, 0, 0)].fill(7, 1) // [0,7,7]
[(1, 2, 3)].find((x) => x == 3) // 3
[(1, 2, 3)].findIndex((x) => x == 2) // 1
[(1, 2, 3, 4, 5)].copyWithin(3, 0) // [1, 2, 3, 1, 2]
[("a", "b", "c")].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
[("a", "b", "c")].keys() // iterator 0, 1, 2
[("a", "b", "c")].values(); // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0, 0) });
更多详细内容参考 ES 入门教程:
二进制和八进制(binary and octal literals)
两种新的数字表示形式。
- 二进制: 0b 开头
- 八进制: 0o 开头
0b111110111 === 503; // true
0o767 === 503; // true
reflect api
reflect API 公开对象上的运行时级别的元操作。
最重要的目的是配合 Proxy
使用,执行原生行为
让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
// 老写法
"assign" in Object; // true
// 新写法
Reflect.has(Object, "assign"); // true
更多详细内容参考ES 入门教程-reflect
尾调用(tail calls)
- 尾调用:某个函数的最后一步是返回并调用另一个函数
- 尾递归:函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
- 尾调用优化
注意,目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。这里就不深入研究了 😁
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
// 大多数浏览器中都会出现 堆栈溢出 的错误,
// 但是在 ES6的Safari中是安全的
factorial(100000);
更多详细内容参考ES 入门教程-尾调用
最后
文章浅陋,欢迎各位看官评论区留下的你的见解!
觉得有收获的同学欢迎点赞,关注一波!