1、let
+const
let
和const
引入了块级 作用域的绑定构造,与传统的var
声明方式有很大不同。这些新的声明方式帮助开发者更清晰地控制变量的作用域和使用限制。var
存在变量提升和全局作用域,可以先用(值为undefined
)再声明,且可以多次声明。let
和const
必须先声明才能使用,否则会报错。暂时性死区
let
是变量,可以多次赋值,但是同一作用域不能多次声明。const
是常量,对于基本类型的数据不能够多次赋值,也不能在同一作用域下多次声明。
2、箭头函数
- 箭头函数是一种简写语法,与普通函数不同,箭头函数与其所在代码的词法作用域共享
this
,即箭头函数内部的this
指向定义它的外层 作用域,而非调用它的对象。此外,如果箭头函数位于另一个函数内部,他会共享父函数的arguments
变量。
3、模板字符串
-
模板字符串提供了一种用于构建字符串的简洁语法(语法糖)。使用反引号(`)包裹字符串,并通过 ${} 插入变量或表达式来实现插值。
javascriptconst name = "Alice"; const greeting = `Hello, ${name}!`; console.log(greeting); // 输出 "Hello, Alice!"
-
此外,模板字符串可以通过添加标签函数(tagged templates)来定制字符串的处理方式。这种标签函数可以防止注入攻击或将字符串内容构造成更高级的数据结构。标签函数在模板字符串前面使用。
4、模块化 Modules
- 使用
export
导出模块,使用import
导入模块。 - JavaScript 的模块系统通过标准化模块的加载和管理,提高了代码的可维护性和可重用性。
- 关于
import
和export
的用法,请参照:JavaScript 模块化
5、类 class
- ES6 中的类是对基于原型 的面向对象模式的语法糖。通过提供一种方便的声明式语法形式,使得类模式更易于使用,并促进了代码的互操作性。类支持基于原型的继承、super 调用、实例方法和静态方法以及构造函数。
6、Promise
Promise
是 JavaScript 中用于处理异步编程的一种机制。它提供了一种更加简洁、清晰的方式来处理异步操作,避免了回调函数(callback hell)的问题。Promise
代表一个 可能尚未完成的操作的结果,并允许我们在未来某个时间点处理操作的成功或失败。- 信息量很大,想了解请看另一篇介绍:JavaScript中的异步和Promise
7、Map
+ Set
+ WeakMap
+ WeakSet
(新数据结构)
Map
- 通过
new Map()
来使用 - 是一个键值对集合,键和值可以是任意类型,包括对象
- 常用方法:
set(key,value)
、get(key)
、has(key)
、delet(key)
、clear()
- 通过
Set
- 通过
new Set()
来使用 - 是一个值得集合,可以存储任意类型,值唯一
- 常用方法:
add(value)
、has(value)
、delete(value)
、clear()
- 通过
WeakMap
WeakMap
是一种特殊的Map
,它的键必须是对象,且这些键是"弱引用"的,这意味着当一个键对象不再被其他地方引用时,该键值对会被垃圾回收。- 不支持遍历,普通
Map
支持
WeakSet
同理(值为对象,弱引用,不支持遍历)Map
和对象的区别:object
的键为字符串或Symbol
,Map
中可以是任意类型;object
的大小需要手动遍历计算,而Map
可以使用size
属性得到Map
的遍历遵循插入顺序object
有原型,所以映射中有缺省的键
8、迭代器和for...of
- 迭代器是一个对象,定义了一个
next()
方法,用于返回序列中的下一个值。每次调用next()
,它都会返回一个对象,包含value
和done
两个属性。 for...of
用于遍历可迭代对象的循环,包括数组、字符串、Map、Set,以及实现了[Symbol.iterator]
的对象。它与for...in
的区别是for...of
迭代的是值,而for...in
迭代的是对象的键。for...in
会遍历原型属性。
9、增强对象字面量
javascript
var obj = {
// 设置原型
__proto__: theProtoObj,
// 计算属性名
['__proto__']: somethingElse,
// handler:handler的简写
handler,
// 方法简写,省略了function关键字
toString() {
// 调用Super
return "d " + super.toString();
},
//动态计算属性名
[ "prop_" + (() => 42)() ]: 42
};
//相当于:
{
__proto__: theProtoObj,
__proto__: somethingElse, // 并不会影响原型,因为是计算属性,和原型同名,但是没有原型的功能
handler: handler, // 简写形式
toString: function() { // 方法定义
return "d " + super.toString();
},
prop_42: 42 // 动态属性名
}
10、解构赋值
- 解构赋值是一种通过模式匹配来绑定变量的语法,支持对数组和对象进行解构。它允许我们从数组或对象中提取值,并将它们赋给变量,语法简洁且可读性高。解构赋值具有容错性(fail-soft),即当值不存在时不会报错,而是赋值为 undefined,类似于标准的对象查找。
11、默认参数、剩余参数、拓展运算符
- 默认参数:函数未传入参数时,使用默认参数。
- 剩余参数:剩余参数使用
...
语法,将函数的剩余参数收集到一个数组中。它替代了arguments
对象,更直接地满足了需要将多个参数绑定到数组的需求。 - 拓展运算符:扩展运算符也使用
...
语法,可以将数组中的元素展开为单独的参数传递给函数。这种方法比apply
更直观,并且支持任何可迭代对象。
12、Proxy
Proxy
是 JavaScript 中的一个强大特性,允许你通过拦截(interception)和修改对象的基本操作来创建一个对象的"代理"。这个对象被称为"代理对象",你可以定义一组"捕获器"(handlers),以自定义对象的行为。Proxy
使得你能够控制对对象的读取、写入、调用等行为。Vue3
的响应式原理就基于此。
13、Symbol
- 新的原始数据类型,用于创建唯一的标识符,广泛用于对象的属性键,以避免命名冲突和提供某种程度的访问控制。Symbol 的主要特点是唯一性,这使得它非常适合用作对象的私有属性或状态标识符。
14、内建对象的子类化 Array
、Date
、Element
15、Math + Number + String + Object APIs
javascript
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].findIndex(x => x == 2) // 1
["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) })
16、二进制和八进制字面量
17、Reflect
Reflect
API 是 JavaScript 中提供的一组方法,允许对对象进行"反射"操作,即能够动态地操作对象的属性、方法以及其内部行为。它暴露了对象的元操作(meta-operations),并允许我们直接与对象的内部结构进行交互。Reflect
API 的核心特性是它提供了与 Proxy API 中的陷阱(traps)对应的方法,使得我们能够在没有创建 Proxy 的情况下,直接对对象执行类似的元操作。
18、尾调用(Tail Call)
- 尾调用(Tail Call)是指在函数的最后一步调用另一个函数,并且该函数的返回值直接作为当前函数的返回值。尾调用优化(Tail Call Optimization,TCO)是一种性能优化技术,它能够避免递归调用导致的栈溢出,从而使递归算法在处理大量数据时更加安全高效。
- 在具有尾调用优化的语言中,当一个函数调用自己作为最后一步时,不会在调用栈中添加新的堆栈帧,而是复用当前的堆栈帧。这使得递归的深度不再影响内存的使用,避免了栈溢出问题。