ES6 & ESNext 规范及编译工具简介
1、历史简介
ECMAScript:一种标准(ECMA-262)
JavaScript:一种语言
ES5:2011发布,第五个版本
ES6:2015发布的,第六个版本,大型变动 break change
ESNext:符合现在主流ES的预案,有几个阶段(大于1的就是属于ESNext)
阶段:
Stage 0:开工
Stage 1:收录意见
Stage 2:草案
3:候选者(基本要准备发布)
4:定案
2、变量定义新形式(let、const)
Q: var 定义变量有什么问题?
- 变量提升
- 无法形成词法作用域
- 可以随意篡改变量值,重复声明
2.1 let
let 关键字用于声明一个块级作用域
的局部变量,可以将 let 声明的变量重新赋值。let 声明的变量只在代码块内部
有效。
if (true) {
let i = 1;
console.log(i); // 输出 1
}
console.log(i); // 报错,i 未定义
console.log(a); // undefind(变量提升)
var a=2;
console.log(b); //报错,暂时性死区,let在下面定义了
let b=2
死区
function bar(x=y,y=2){
return [x,y]
}
bar()
//报错,y死区(运行时报错)
function bar(x=2,y=x){
return [x,y]
}
bar()
//正常执行
不允许重复声明
var a=2;
let a=1;
//报错
2.2 const
const 关键字用于声明一个块级作用域
的只读常量
,一旦 const 声明了某个变量,就不能使用赋值语句改变它的值。常量必须在声明时进行初始化。
const PI = 3.1415926535;
PI = 3; // 报错,无法修改常量
const obj={}//具体引用的地址不能变
obj.a=1//不报错
obj=1//报错
ES5里没有块级作用域
//ES6写法
if(true){
function fn(){
}
}
//ES5写法
if(true){
var fn = function(){
}
}
2.3 实际开发中的使用
- 示例一:循环中使用 let 声明变量避免问题
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2, 3, 4
}, 1000);
}
使用 let 声明的变量 i 有块级作用域,在每一次循环中都会重新定义并赋值,避免了使用 var 声明变量可能导致的变量共享问题。
- 示例二:使用 const 声明常量
const PI = 3.1415926;
const URL = "https://www.example.com";
使用 const 声明常量可以防止被修改,保证代码的可靠性和稳定性。
- 示例三:使用 const 声明对象属性避免误修改
const user = {
name: "张三",
age: 18,
gender: "男"
};
user.name = "李四";
console.log(user); // { name: "李四", age: 18, gender: "男" }
Object.freeze(user);
user.age = 20;
console.log(user); // { name: "李四", age: 18, gender: "男" }
使用 const 声明对象属性可以避免误修改,同时使用 Object.freeze() 方法可以将对象冻结,防止意外修改对象属性。
总的来说,使用 let 或者 const 声明变量可以解决一些以往使用 var
变量定义存在的问题,例如变量作用域混乱,变量共享、变量易被修改等情况。使用 let 和 const
可以使代码更加健壮、可维护,提升开发效率和代码质量。
3、解构语法
定义:将数组或对象中的元素提取出来并赋值给变量
3.1 数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
在这个例子中,JavaScript 引擎背后发生的事情如下:
const tempArray = [1, 2, 3];
const a = tempArray[0];
const b = tempArray[1];
const c = tempArray[2];
console.log(a, b, c); // 1 2 3
let [a, ...rest] = [1, 2, 3];
console.log(a, rest); // 1 [2,3]
[x,y,,z]=[1,2,3,4]
console.log(x,y,z); // 1,2,4
3.2 对象解构
const {firstname: first, lastname: last, ...rest} = { firstname: 'John', lastname: 'Doe' };
console.log(first, last); // John Doe
let obj={
p:['Hello',
{y:'World'}
]
}
let {
p:[x,{y}]
}=obj
//x,y Hello,World
3.3 嵌套解构
const [a, [b, [c]]] = [1, [2, [3]]];
console.log(a, b, c); // 1 2 3
3.4 默认值
const [a = 1, b = 2] = [];
console.log(a, b); // 1 2
let obj={a:1,b:2,c:3}
const {a=2}=obj
//a=obj.a?obj.a:2,obj的a存在,取obj.a,不存在,取默认值2
const {a:d=2}=obj
//另外取名字 d=obj.a?obj.a:2
4、模板字符串
const name = "Tom";
const age = 20;
const str = `My name is ${name}, I'm ${age} years old.
I'm from China.`;
console.log(str);
5、 箭头函数
箭头函数与普通函数有哪些不同?
- this 指向,箭头函数不能定义构造器
- 不能 new
- 内部无 arguments 对象
- this 绑定方法失效,比如:call apply bind
// 传统函数定义
function multiply(x, y) {
return x * y;
}
// 箭头函数
const multiply = (x, y) => x * y;
function foo(){
setTimeout(() => {
console.log(`num`,this.num);//没有自己的this,这里的this是foo的this
}, 1000);
}
var num =123
foo()//输出 num 123
//改变函数指向
foo.call({
num:456
})
foo()//输出 num 456
//看一个例子
function Timer(){
this.s1=0;
this.s2=0;
//箭头函数
setInterval(()=>this.s1++,1000);//外层函数的this,指向Timer,改变了Timer实例的s1
//普通函数
setInterval(function(){
this.s2++;//function的this,没有改变Timer实例的s2
},1000);
}
var timer =new Timer()
setTimeout(() => {
console.log('s1:',timer.s1)
}, 3100);
setTimeout(() => {
console.log('s2:',timer.s2)
}, 3100);
//输出:
//s1:3
//s2:0
6、ES6新增的数组方法
ES6新增了一些数组方法,具体可以看 ES6 入门教程
Js的全部数组方法,可以看 数组方法总结+详解
如:
- Array.flat()
Array.flat()
方法创建一个新数组,其中所有子数组元素递归地连接到指定的深度。(打平数组深度)
const arr = [1, [2, [3, [4]]]];
console.log(arr.flat(1)); // [1, 2, [3, [4]]]
console.log(arr.flat(2)); // [1, 2, 3, [4]]
console.log(arr.flat(3)); // [1, 2, 3, 4]
console.log(arr.flat(4)); // [1, 2, 3, 4]
- Array.includes()
Array.includes()
方法判断一个数组是否包含某个指定的元素,根据情况,如果包含则返回true
,否则返回false
。
const arr = [1, 2, 3, 4, 5];
console.log(arr.includes(3)); // true
console.log(arr.includes(6)); // false
Array.from()
Array.of()
Array.find()
Array.findIndex()
Array.includes()
Array.flat()
Array.flatMap()
Array.every()
Array.some()
Array.reduce()
Array.reduceRight()
Array.sort()
Array.reverse()
Array.fill()
Array.slice()
Array.splice()
Array.copyWithin()
Array.forEach()
Array.map()
Array.filter()
7、Set和Map
Set 和 Map 主要的应用场景在于 数据重组
和 数据储存
。
Set 是一种叫做 集合
的数据结构,Map 是一种叫做 字典
的数据结构。
7.1 Set
- ES6 新增的一种新的数据结构,类似于
数组
,成员唯一(内部元素没有重复的值)。且使用键对数据排序即顺序存储。 - Set 本身是一种构造函数,用来生成 Set 数据结构。
- Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用
var s1=new Set();
var s2=new Set([1,2,3]);
// 重复元素在Set中会自动过滤(即重复元素不会被保留)
var s=new Set([1,2,3,3]);
s.add(4); // set{1,2,3,4}
s.add(3); //set{1,2,3,4}
s.size(); //4
s.has(3); //true
weakSet 和Set一样的用法,不过里面的值只能是Symbol或对象,垃圾回收不会考虑weakSet 和weakMap里的值,内部变量没有可达性
7.2 Map
Map是一组键值对
的结构,用于解决以往不能用对象做为键的问题,具有极快的查找速度。(注:函数、对象、基本类型都可以作为键或值。)
const m=new map(['Kris',21],['Bob',19],['Lily',25],['Jack',27]);
m.get('Kris'); // 21
m.get('Lily'); // 25
var mm=new Map( ); //初始化一个空的 map
mm.set('Pluto',23); //添加新的key-value 值
mm.has('Pluto'); //true 是否存在key ‘Pluto’
mm.get('Pluto'); //23
mm.delete('Pluto'); //删除key ‘Pluto ’
一个key只能对应一个value,所以多次对一个key放入value,后面的值会把前面的值冲掉
const m=new Map([['Lily',100],['Bob',97]]);
m.get('Bob'); //97
m.set('Bob',88); //对key放入新的value
m.get('Bob'); //88
8、面向对象编程——class 语法
JavaScript 的类最终也是一种函数
,JavaScript 的 class 语法只是语法糖
,使用 class 关键字创建的类会被编译成一个函数,因此其底层原理与函数有一些相似之处。
JavaScript原型继承实现继承的有哪些方式(详情可以看这篇博客)
- 原型链继承
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合继承
Class 语法继承,最接近我们自己实现的那种继承方式?
- 寄生组合继承
8.1 类、构造函数
ES6里的类
class Point{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return '('+this.x+','+this.y+')'
}
}
new Point()
等价于ES5
var Point = /*#__PURE__*/ (function () {
function Point(x, y) {
this.x = x;
this.y = y;
}
var _proto=Point.prototype;
_proto.toString=function toString(){
return '('+this.x+','+this.y+')'
}
return Point;
})();
new Point();
8.2 继承
在 JavaScript 中,继承是通过类的 prototype 属性实现的。在定义类时,可以使用 extends 关键字来继承其他的类:
class Student extends Person {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
}
8.3 静态方法和属性
类中的静态方法和属性可以使用 static 关键字来定义,它们不是类实例的属性,而是类本身的属性和方法。
class Person {
static species = "human";
static saySpecies() {
console.log(`We are ${this.species}.`);
}
}
8.4 getter 和 setter
在类中定义 getter 和 setter 方法可以让我们封装实例的内部数据属性,使得这些属性的读写行为更加的安全和合理。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
get name() {
return this.name.toUpperCase();
}
set age(age) {
if (age > 0 && age < 120) {
this.age = age;
} else {
console.log("Invalid age value.");
}
}
get age() {
return this.age;
}
}
在类的实现中,getter 和 setter 其实是被定义在构造函数的 prototype 属性上,从而可以被该类的所有实例对象访问。
9、生成器 generator
生成器为了解决什么问题?
解决了函数的不可中断性
生成器是可中断函数,yield 关键字暂时中断函数执行,在合适的实际从中断位置继续执行。
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
const sequence = generateSequence(); // 获取生成器实例
console.log(sequence.next().value); // 输出 1
console.log(sequence.next().value); // 输出 2
console.log(sequence.next().value); // 输出 3
10、异步处理——callback、Promise、async & await
这个部分可以看博客:3.1、前端异步编程(超详细手写实现Promise;实现all、race、allSettled、any;async/await的使用)