ES6-Review

JS语法规范ECMAScript(ES6)基础知识及核心原理

ES6 发展史

这里推荐阅读阮一峰ECMAScript 6 入门

使用babel对ES6进行编译

1、 下载安装Babel
环境:需要在电脑上安装node(node自带npm包管理器)

1
2
3
npm install babel-cli -g 把模块安装在全局环境下(在任何的项目中,都可以使用命令来变异我们的代码)

npm uninstall babel-cli -g 把全局下安装的babel-cli 卸载掉

安装日志

1
2
3
4
5
6
7
8
9
$ npm install babel-cli -g
C:\Users\xxx\AppData\Roaming\npm\babel-doctor -> C:\Users\yuboyang\AppData\Roaming\npm\node_modules\babel-cli\bin\babel-doctor.js
C:\Users\xxx\AppData\Roaming\npm\babel -> C:\Users\xxx\AppData\Roaming\npm\node_modules\babel-cli\bin\babel.js
C:\Users\xxx\AppData\Roaming\npm\babel-external-helpers -> C:\Users\yuboyang\AppData\Roaming\npm\node_modules\babel-cli\bin\babel-external-helpers.js
C:\Users\xxx\AppData\Roaming\npm\babel-node -> C:\Users\yuboyang\AppData\Roaming\npm\node_modules\babel-cli\bin\babel-node.js
C:\Users\xxx\AppData\Roaming\npm
`-- babel-cli@6.26.0

......

C:\Users\xxx\AppData\Roaming\npm 这个路径很重要

1、在该路径下可以看到,安装babel后声称的xxx.cmd文件(可以在doc窗口执行命令)
babel.cmd以后可以使用babel命令
babel-node.cmd

2、执行babel命令后我们可以完成一些变异或者其他任务,主要原因是执行babel命令后,会自动加在一些处理任务的文件

二、配置.babelrc文件,安装一些语言解析包

1、我们需要把.babelrc文件配置在当前项目的根目录下(这个文件没有文件名,后缀名就是.babelrc)

a:在电脑上不能直接创建没有文件名的文件,我们需要使用WS中的new->file创建,或者使用命令创建dir>.babelrc

b:babelrc这个后缀名在某些ws中是不识别的,它其实是一个json文件,我们需要在ws中配置一下(让它隶属于json文件)

2、在文件中编写一些内容

1
2
3
4
{
"persets": [], //=> 存放的是,我们变异代码的时候需要以来的语言解析包
"plugins": [] //=> 存放的是,我们变异代码时候需要以来的插件信息
}

3、安装以来的语言解析包

在当前项目的根目录下安装(不是安装在全局),需要特别注意的是:需要在当前项目根目录中打开doc命令才可以

npm install babel-preset-latest 安装最新已经发布的语言标准解析模块
npm install babel-preset-stage-2 安装当前还没有发布但是已经进入草案的语言解析模块(如果你的代码中用到了发布非标准的语法,我们需要安装它)

安装成功后,会在当前目录下存在一个node_modules文件夹,在这个文件夹中有我们安装的模块

4、完成最后.babelrc文件的配置

1
2
3
4
5
6
7
{
"persets": [
"latest",
"stage-2"
],
"plugins": []
}

三、使用命令变异js代码

基本上所有支持命令操作的模块都有一个命令
babel --help / babel -h 查看帮助

babel --version / babel -V 查看版本号

babel --out-file / babel -o 把某一个js文件中的es6代码进行编译

babel --out-dir / babel -d 把某一个文件夹中所有的js文件中的es6代码进行编译

babel --watch / babel -w 监听文件中代码的改变,当代码改变后,会自动进行编译

1
2
3
4
5
6
7
babel ES6/1.js -o ES5/1.js //=> 当个文件编译后保存

babel ES6 -d ES5 //=> 将ES6下全部js文件编译后保存到ES5下

babel ES6 -d ES5 -w //=> 监听改变自动编译

//=> 结束监听 ctrl+c 两次即可

ES6中的let和const

let 基础语法

let 变量名 = 变量值

1、 let声明变量不存在变量提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
console.log(str); //=> undefined
console.log(fn); //=> fn本身
console.log(avg); //=> undefined
console.log(sum); //=> sum is not defined
console.log(num); //=> num in not defined

var str = 'aa';
let num = 12;

function fn(){}

var avg = function(){};
let sum = function(){};
//=> ES6当中只提供了创建变量的信誉发标准,创建函数还是沿用ES5中的function(还会存在变量提升),如果想让函数也不存在变量提升,偶使用函数表达式赋值的方式:let fn = fuction(){}

//=> 创建变量
let xxx = xxx;

//=> 创建函数
let xxx = function(){}

//=> 自执行函数
;(function(){

})();

2、使用let定义的变量不允许在同一个作用域中重复生命

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var num = 12;
var num = 13;
console.log(num); //=> 13

let str = 'aa';
let str = 'bb';
console.log(str); //=> Identifier 'str' has already been declared 当前报错,上面代码也不会执行(在JS代码执行之前,就已经知道有重复声明的了,也就是浏览器依然存在类似于变量提升的机制:在JS代码执行之前先把所有let声明的变量过一遍,发现有重复的直接报错)

let num = 12,
fn = function(){
let num = 12;
};
console.log(num); //=> 12 当前作用域下别重复声明即可(不同作用域中的变量是自己私有的,名字重复没关系)

var att = 200;
let att = 100; //=> Identifier 'att' has already been declared
//=> 不管之前使用什么方式在当前作用域中声明的变量,再使用let声明的时候都会报错

3、暂时性死区:使用typeof检测一个未被声明过的变量

1
2
3
4
5
"use strict";
// console.log(typeof num); //=> undefined 当前变量不存在,但是使用typeof检测的时候,不会提示错误,而是返回undefined

// console.log(typeof num); //=> Uncaught ReferenceError: num is not defined ES6中检测一个没有被生命过的变量直接报错,不像之前ES5中的值是undefined一样了
// let num;

4、ES6语法创建的变量(let)存在会级作用域,ES5语法创建变量(var/function)没有会级作用域

[ES5]
window全局作用域
函数执行形成的私有作用域

[ES6]
除了ES5中的两个作用域,ES6中新增加块级作用域(我们可以把块级作用域理解为私有作用域:存在私有变量和作用域链的一些机制)

1
2
3
4
5
6
7
8
9
10
11
let num = 12,
str = '';
let fn = function(str){
str = 'hello';
console.log(arguments[0]); //=> hello 当前JS并没有开启严格模式,所以形参变量和arguments存在映射机制(但我们应尽量不要这样处理,因为把ES6编译成ES5之后,会默认的开启严格模式,映射机制会终端,此处的值依然是‘word’)
console.log(num); //=> Uncaught ReferenceError: num is not defined
let num = 13;
console.log(num, str); //=> 13 hello
};
fn('word');
console.log(num, str); //=> 12 ''
1
2
3
4
5
"use strict"
if(10 >= 10){
var total = 100;
}
console.log(total); //=> Uncaught ReferenceError: total is not defined 判断体也是一个块级作用域,在这个作用域中声明的变量时私有变量,再块级作用域之外时无法使用的
1
2
3
4
for(let i = 0; i < 5; i += 1){
console.log(i);
}
console.log(i); //=> Uncaught ReferenceError: i is not defined 循环体也是一个块级作用域(每一次循环都会形成一个新的块级作用域,当前例子形成五个块级作用域,每一个块级作用域中都有一个私有变量i,分别存储的时0-4)
1
2
3
4
5
6
7
8
9
10
let i = 10;
{
let i = 20;
{
{
console.log(i); //=> Uncaught ReferenceError: i is not defined 虽然ES6没有变量提升,但是每一次进入当前作用域都会把let定义的变量找一边(不提升但是找了,找到了说明当前作用域中是有这个变量的,提前用都会报错)
}
let i = 30;
}
}
1
2
3
4
5
6
7
8
try{
let i = 100;
}catch(e){
let k = 200;
}
console.log(i); //=> Uncaught ReferenceError: i is not defined
console.log(k); //=> Uncaught ReferenceError: k is not defined
//=> try catch 中的任何大括号都是块级作用域
1
2
3
4
5
6
switch(10){
case 10:
let i = 10;
break;
}
console.log(i); //=> Uncaught ReferenceError: i is not defined
1
2
3
4
5
let obj = {name: 'a'};
for(let key in obj){

}
console.log(key); //=> Uncaught ReferenceError: k is not defined

增加这些块级作用域的作用

1
2
3
4
5
6
let list = document.getElementsByClassName('item');
for(let i = 0; i < list.length; i += 1){
list[i].onclick = function(){
console.log(i);
}
}

const的基础语法
const的细节知识点和let一样,和let的主要区别在于:let是创建变量,const是创建常量

1
2
3
4
5
6
7
let num = 12;
num = 13;
console.log(num); //=> 13

const str = 'hello';
str = 'world';
console.log(str); //=> Uncaught TypeError:Assignment to constant variable 而且使用babel 遇到了const设置的常量再进行修改就无法进行编译了

JS中创建变量方式汇总

var: ES5中创建变量
function: ES5中创建函数
ES5中创建变量或函数存在:变量提升、重复声明等特征,但是没有块级作用域

let: ES6中创建变量
const: ES6中创建常量
ES6中创建的变量或者常量都不可以变量提升,也不可以重复生命,而且还存在块级作用域

class: ES6中创建类的方式
import: ES6中模块导入的方式

1
2
3
4
5
6
7
8
9
10
11
class Parent {
constructor(){
//=> this.xxx = xxx
}

//=> Parent.prototype
aa(){}

//=> Parent own property
static bb(){}
}

ES6的解构赋值

按照原有的解构,把原有值中的某一部分内容快速获取到(快速复制给一个变量)

数组的解构赋值

解构赋值本身是ES6的语法规范,使用什么关键字来申明这些变量是无所谓的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let ary = [12,23,34];
//=> 传统的取值赋值操作
//=>let a = ary[0];
//=>let b = ary[1];
//=>let c = ary[2];
//=>console.log(a,b,c);

let [a,b,c] = [12,23,34];
console.log(a,b,c); //=> 12 23 34

~function(){
[d,e,f] = [12,23,34];
}()
console.log(d.e.f); //=> 12 23 34

[g,h,i] = [12,23,34]; //=> 12 23 34
//=> 此处相当于给window增加的全局属性
//=> 但是这个操作在JS严格模式下是不允许的,因为严格模式下不允许出现非使用var/let等声明的变量

多位数组的解构赋值,可以让我们快速获取到需要的结果

1
2
3
4
5
6
7
//=> [12,[23,34],[45,56,[67,78]]] 把多维数组中的34 56 78获取到,并分别复制给A B C

let [,[,A],[,B,[,C]]] = [12,[23,34],[45,56,[67,78]]];
console.log(A,B,C); //=> 34 56 78

let [D] = [12,23,34];
console.log(D); //=> 12 如果指向获取数组中前面的某一项,后面的解构不需要补全

在解构赋值中,可以给某一项设置默认值

1
2
3
4
5
let [,,,A] = [12,23,34];
console.log(A); //=> undefined

let [,,,B = 0] = [12,23,34];
console.log(B); //=> 0
1
2
3
4
5
6
7
8
let [A,...B] = [12,23,34,45];
console.log(A,B); //=> 12 [23,34,45]

let [...C] = [12,23,34];
console.log(C); //=> [12,23,34] 数组克隆

let [D,...E,F] = [12,23,34,45,56,67];
console.log(D,E,F);//=> Uncaught SyntaxError: Rest element must be last element 拓展运算符只能出现在解构赋值的末尾

对象的解构赋值

1
2
3
4
5
6
let {name, age} = {name: 'a', age: 12};
console.log(name, age); //=> a 12

let {A,B} = {name: 'a', age: 12}
console.log(A,B); //=> undefined undefined
//=> 在对象的解构赋值中需要注意的是:赋值的变量需要和对象中的属性名吻合,否则无法获取对应的属性值
1
2
let {C=0} = {name: 'a', age: 12};
console.log(C); //=> 0 和数组的结构赋值一样,我们可以把后面不需要赋值的结果省略掉,而且也可以给当前的变量设置默认值
1
2
3
let {name} = {name: 'a', age: 12};
console.log(name); //=> a
//=> let {,age} = {name: 'a', age: 12}; //=> Uncaught SyntaxError: Unexpected token,和数组的解构赋值不一样的地方在于,对象前面不允许出现空来占位(因为对象获取需要通过具体的属性名,写成空的话,浏览器不知道怎么识别)
1
2
let {name, ...arg} = {name: 'a', age: 12, sex: 0};
console.log(name, arg); //=> a {age: 12, sex: 0} 支持拓展运算符
1
2
3
4
5
//=> 把对象进行浅克隆(只把第一级克隆了)
let obj = {name: 'a', age: 12, sex: 0,score: [1, 2, 3]};
let {...arg} = obj;
console.log(arg === obj); //=> false
console.log(arg.score === obj.score); //=> true
1
2
let {name:A, age:B} = {name: 'a', age: 12}
console.log(A, B); //=> a 12 在对象的解构赋值中,我们可以把对象的属性名起一个别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let data = [
{
"name": "张三",
"age": 20,
"score": {
"English": [100, 95, 90],
"math": [100, 100, 80],
"Chinese": [98, 99, 100]
}
},
{
"name": "李四",
"age": 18,
"score": {
"English": [10, 5, 9],
"math": [80, 50, 50],
"Chinese": [68, 49, 80]
}
},
{
"name": "王五",
"age": 23,
"score": {
"English": [77, 45, 67],
"math": [67, 89, 90],
"Chinese": [43, 65, 98]
}
}
];

let [{score:{English:[A],math:[,B],Chinese:[,,C]}}] = data;
console.log(A,B,C);

解构赋值在项目中的应用

可以快速接收到传递的多个值(我传递的是一个对象或数组)

1
2
3
4
5
//=> 快速交换两个变量的值
let a = 12;
let b = 13;
[a, b] = [b, a];
console.log(a, b); //=> 13 12
1
2
3
4
5
6
7
8
let fn = function(){
let a = 12,
b = 13,
c = 14;
return [a, b, c];
};
let [a, b, c] = fn();
console.log(a, b, c); //=> 12 13 14
1
2
3
4
5
let fn = function([a, b, c, d = 0]){
console.log(a, b, c, d); //=> 12 23 34 0
};
fn([12, 23, 34]);
console.log(a); //=> Uncaught ReferenceError: a is not defined 函数中的a b c 是私有变量
1
2
3
4
5
6
7
8
9
10
11
//=> 设置参数默认值
let fn1 = function({x = null, y = {}, z = 1000, callback = null}){

};
fn1({x: 10, y: {a:1}});

//=> 不传参数,让其在空对象中结构赋值,避免报错
let fn2 = function({x = null, y = {}, z = 1000, callback = null} = {}){

};
fn2();
1
2
3
4
5
6
let obj = new Map();
obj.set('x', 10);
obj.set('y', 20);
for(let [key, value] in obj){
console.log(key, value);
}

ES6中的箭头函数

箭头函数基础语法

1
2
3
4
let fn = function(x, y){
return x + y;
}
console.log(fn(10, 20)) //=> 30
1
2
//=> 将上面函数改写成箭头函数
let fn = (x, y) => x + y;
1
2
3
4
5
6
let fn = function(n){
let x = 10,
y = 20;
return x + y;
};
fn();
1
2
3
4
5
let fn = n => {
let x = 10,
y = 20;
return x + y;
}

箭头函数中不支持arguments

1
2
3
4
5
let fn = function(){
let arg = Array.prototype.slice.call(arguments);
return eval(arg.join('+'));
}
fn(10, 20, 30, 40); //=> 100
1
2
3
4
5
6
7
let fn = ()=> {
console.log(arguments); //=> Uncaught ReferenceError: arguments is not defined
//=> 不支持arguments,可以使用ES6中的生于运算符`...`来获取传递进来的所有参数值(相对于arguments的优势:使用剩余运算符接收到的结果本身就是一个数组,不需要再转换了)
//console.log(arg instanceof Array); //=> true
return eval(arg.join('+'))
};
fn(10, 20, 30, 40);
1
2
let fn = (...arg) => eval(arg.join('+'));
console.log(fn(10, 20, 30, 40));

箭头函数中的this问题

普通函数中的this指向问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//=> 普通函数执行this的指向:看函数执行前面是否有点,点前面是谁this就是谁,没有点this指向window或者undefined(严格模式下)
let obj = {
name: 'obj',
fn(){
//=> 这样处理和下面sum的处理是一样的
console.log(this);
},
sum: function(){

}
}
obj.fn(); //=> this => obj
document.body.onclick = obj.fn; //=> this => body
setTimeout(obj.fn, 1000); //=> this => window
obj.fn.call(12); //=> this => 12

箭头函数中的this

1
2
3
4
5
6
7
let obj = {
name: 'obj',
fn: () => {
console.log(this);
//=> 不管怎么操作this都指向window,箭头函数中没有this指向,用到的this都是所在宿主环境(它的上级作用域)中的this
}
}

项目中并不是把所有的函数都改成箭头函数,根据自身需要来修改即可

箭头函数的一点扩充

1
2
3
4
5
6
7
8
9
10
let fn = ()=>{
console.log(this);
}
let obj = {
name: 'obj',
sum: function(){
fn(); //=> this -> window 宿主环境:不是执行的环境而是定义的环境,fn虽然是在这执行,但是他是在window下定义的所以他的宿主环境韩式window
}
};
obj.sum();

对于层级桥操的箭头函数

1
2
3
4
5
6
7
8
let fn = function(i){
return function(n){
return n + (++i);
}
}

//=> 改成箭头函数
let fn1 = (i) => (n) => n + (++i);

ES6中创建类的基础语法

ES5中创建类和实例,以及如何禁止用户把类当作普通函数执行: new.target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(name, age) {
// console.log(new.target); //=> ES6新增加的语法,如果通过NEW执行,返回的结果是当前创建的实例,如果是当作普通函数执行,返回的是undefined
if(typeof new.target === 'undefined'){
throw new SyntaxError(`当前Person不能作为一个普通函数执行,请使用new Person 来执行~~`);
}

//=> new 执行的时候,this是当前类的实例,this.xxx = xxx是给当前实例增加的私有属性
this.name = name;
this.age = age;
}
//=> 原型上存放的是公有的属性和方法:给创建的实力使用
Person.prototype = {
constructor: Person,
say: function(){
console.log(`my name is ${this.name}, i am ${this.age} years old~`);
}
}
//=> 把person当作一个普通对象,给对象设置的私有属性
Person.study = function(){
console.log('good good study, day day up~');
};
var p1 = new Person('小二', '22');

Person();

ES6中创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// console.log(Person); //=> Uncaught ReferenceError Person is not defined 不存在变量提升 
class Person {
constructor(name = '张三', age = '9'){
//=> 给实例设置的私有属性
this.name = name;
this.age = age;
}
//=> 直接再大括号中编写的方法都直接设置在类的原型上,ES6默认把constructor的问题解决了,此时原型上的constructor指向就是Person
say(){
console.log(`my name is ${this.name}, i am ${this.age} years old~`);
}

study(){
console.log('good good study, day day up~');
}
}
let p1 = new Person();
// Person(); //=> Uncaught TypeError: Class constructor Person cannot be invoked without 'new' ES6中使用class创建的类,不允许把创建的类当作普通函数执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person {
constructor(...arg){
let [x=0,y=0] = arg;
this.x = x;
this.y = y;
}

sum(){
return this.x + this.y;
}
}

class Child extends Person {
//=> 创建Child类,并且让Child类继承了Person类:
1、把Person的私有属性继承过来设置给;额子类的私有属性
2、让字类实例的__proto__上能够找到Person父类的原型(这样子类的实例就可以调用父类的方法)

// constructor(){} //=> Uncaught REeferencerror: Must call super constructor in dervied class before accessing 'this' or returning from derived constructor

//=> 我们不写constructor,浏览器默认会创建它,而且默认就会把父类的属性继承过来(把传给子类的参数值也传给父类了)

//constructor(...arg){ //=> arg:传递给子类的参数(数组)[剩余运算符]
//super(...arg); //=> [展开运算符] 把arg中每一项值展开,分别传递给父类方法 //super(10, 20, 30)
//}
//---------------
//=> 很多时候我们不仅要继承父类私有的,还需要给子类增加一些额外似有的,此时就必须写constructor,但是一定要在constructor中的第一行写上super,否则会报错

constructor(...arg){
super(...arg);
let [,,z] = arg;
this.z = z;
}

fn(){}
}

let c = new Child(10, 20, 30);