珠峰培训

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

作者:

2017-12-05 12:04:13

670

珠峰ES6免费视频地址:http://html5train.com/kecheng/detail_1416807?f=org_coursecenter

使用Babel编译ES6

一、下载安装Babel
环境:需要电脑上安装node(node中一般都会自带npm包管理器)

npm install babel-cli -g 把模块安装在全局环境下(在任何的项目中,都可以使用命令来编译我们的代码了)
npm uninstall babel-cli -g 把全局下安装的babel模块卸载掉

Alt text

观看安装目录发现一些细节需要了解的知识点:
1、我们后期之所以可以使用babel的命令,是因为安装在全局环境下之后,会生成一些 xxx.cmd 的文件,而这里的xxx就是可以在DOC窗口中执行的命令
babel.cmd 以后可以使用babel命令了
babel-node.cmd

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

Alt text

二、配置.babelrc文件,安装一些语言解析包
1、我们需要把.babelrc文件配置在当前项目的根目录下(这个文件没有文件名,后缀名是babelrc)
a:在电脑上不能直接创建没有文件名的文件,我们需要使用WS中的 new -> file 来创建,或者使用命令创建
b:babelrc这个后缀名在某些ws中是不识别的,它其实是一个json文件,我们需要在ws中配置一下(让他隶属于json文件)

Alt text

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

{
"presets": [], //=>存放的是,我们编译代码时候需要依赖的语言解析包
"plugins": [] //=>存放的是,我们编译代码时候需要依赖的插件信息
}

3、安装依赖的语言解析包
在当前项目的根目录下安装(不是安装在全局),需要特殊注意的是:要在当前项目根目录中打开DOC命令才可以
npm install babel-preset-latest 安装最新已经发布的语言标准解析模块
npm install babel-preset-stage-2 安装当前还没有发布但是已经进入草案的语言解析模块(如果你的代码中用到了发布非标准的语法,我们需要安装他)

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

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

{
"presets": [
"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 监听文件中代码的改变,当代码改变后,会自动进行编译

结束监听的程序:ctrl+c 按两遍

Alt text

ES6中的let和const

let基础语法

let 变量名 = 变量值

使用let创建变量和使用var创建变量的区别

1、let不存在变量提升机制

console.log(str);//=>undefined
console.log(fn);//=>FN本身
console.log(avg);//=>undefined
console.log(sum);//=>Uncaught ReferenceError: sum is not defined
console.log(num);//=>Uncaught ReferenceError: num is not defined
var str = '珠峰培训';
let num = 12;
function fn() {}
var avg = function () {};
let sum = function () {};
//=>ES6中只提供了创建变量的新语法标准(let),创建函数还是沿用ES5中的function(还会存在变量提升),如果想让函数也不存在变量提升,都使用函数表达式赋值的方式操作:let fn=function(){}
//=>创建变量
let xxx=xxx;
//=>创建函数
let xxx=function(){}
//=>自执行函数
;(function(){
})();
//=>好处:此时代码中就不要在考虑变量提升了,只要这样处理,没有所谓的变量提升

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

var num2 = 12;
var num2 = 13;
console.log(num2);//=>13
let str = '珠峰';
let str = '培训';
console.log(str);//=>Uncaught SyntaxError: Identifier 'str' has already been declared 当前报错,上面代码也不会执行(在JS代码执行之前就已经知道有重复声明的了,也就是浏览器依然存在类似于变量提升的机制:在JS代码之前先把所有LET声明的变量过一遍,发现有重复的直接报错)
let num = 12;
num = 13;
console.log(num);//=>13 LET不允许重复被声明,但是允许重新赋值
var att=200;
let att=100;//=>Uncaught SyntaxError: Identifier 'att' has already been declared 不管你之前使用什么方式在当前作用域中声明的变量,再使用let声明的时候都会报错
let num = 12,
fn = function () {
let num = 13;
};
console.log(num);//=>12 当前作用域下别重复声明即可(不同作用域中的变量是自己私有的,名字重复没有关系)
let att = 13,
sum = function () {
att = 14;
};
sum();
console.log(att);//=>let也存在私有变量和作用域链的概念,和ES5中基本上差不多 =>14

3、关于暂时性死区:使用typeof检测一个未被声明过的变量
ES5中返回的结果是undefined但是不报错
ES6中直接报错

"use strict";
console.log(typeof num);//=>undefined 当前变量不存在,但是使用typeof检测的时候,不会提示错误,而是返回undefined
console.log(typeof num);//=>Uncaught ReferenceError: num is not defined ES6中检测一个没有被声明过的变量直接报错,不像之前ES5中的值是UNDEFINED一样了
let num;
let num;
console.log(typeof num);//=>undefined 只声明没有定义(赋值),默认值是UNDEFINED

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

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

[ES6]
除了有ES5中的两个作用域,ES6中新增加块级作用域(我们可以把块级作用域理解为之前学习的私有作用域:存在私有变量和作用域链的一些机制) ES6语法中把大部分用大括号包起来都称之为块级作用域

let num = 12,
str = '';
let fn = function (str) {
str = 'HELLO';
//console.log(arguments[0]);//=>"HELLO" 当前JS并没有开启严格模式,所以形参变量和ARG存在映射机制(但是我们以后尽量不要这样处理:因为把ES6编译为ES5之后,会默认的开启严格模式,映射机制会中断,此处的值依然是'珠峰',这样导致我们的ES6结果和ES5结果不一致)
// console.log(num);//=>Uncaught ReferenceError: num is not defined
let num = 13;
console.log(num, str);//=>13 "HELLO"
};
fn('珠峰');
console.log(num, str);//=>12 ''

大部分我们遇到的大括号操作都是块级作用域

/*
if (10 >= 10) {
let total = 100;
}
console.log(total);//=>Uncaught ReferenceError: total is not defined 判断体也是一个块级私有作用域,在这个作用域中声明的变量是私有变量,在块级作用域之外是无法使用的
*/
/*
for (let i = 0; i < 5; i++) {
console.log(i);
}
console.log(i);//=>Uncaught ReferenceError: i is not defined 循环体也是一个块级作用域(每一次循环都会形成一个新的块级作用域:当前案例形成五个块级作用域,每一个块级作用域中都有一个私有变量i,分别存储的是0~4)
*/
/*{
let i=12;
}
console.log(i);//=>Uncaught ReferenceError: i is not defined 这是一个ES6中标准的块级作用域*/
/*
let i=10;
{
let i=20;
{
{
console.log(i);//=>Uncaught ReferenceError: i is not defined 虽然ES6没有变量提升,但是每一次进入当前作用域都会把LET定义的变量找一遍(不提升但是找了,找到了说明当前作用域中是有这个变量的,提前用都会报错)
}
let i=30;
}
}
*/
/*
try{
let i=100;
}catch (e){
let k=200;
}
console.log(k);//=>Uncaught ReferenceError: k is not defined
console.log(i);//=>Uncaught ReferenceError: i is not defined TRY CATCH中的任何大括号都是块级作用域
*/
/*
switch (10){
case 10:
let i=20;
break;
}
console.log(i);//=>Uncaught ReferenceError: i is not defined
*/
/*
let obj={name:'珠峰'};
for (let key in obj) {
}
console.log(key);//=>Uncaught ReferenceError: key is not defined
*/

块级作用域的增加有什么用?

let tempList = document.getElementsByName('TEMP');
// for (var i = 0; i < tempList.length; i++) {
// tempList[i].onclick = function () {
// console.log(i);//=>5 怎么点击都是5 异步操作以及作用域链的查找,找到的都是全局下最后一次循环的结束值
// }
// }
//=>自定义属性解决
// for (var i = 0; i < tempList.length; i++) {
// tempList[i].index = i;
// tempList[i].onclick = function () {
// console.log(this.index);
// }
// }
//=>闭包解决
// for (var i = 0; i < tempList.length; i++) {
// ~function (i) {
// tempList[i].onclick = function () {
// console.log(i);
// }
// }(i);
// }
//=>使用ES6的块级作用域
for (let i = 0; i < tempList.length; i++) {
tempList[i].onclick = function () {
console.log(i);
}
}

const的基础语法

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

变量:值是可以修改的
常量:值不能被修改

let num = 12;
num = 13;
console.log(num);//=>13
const str = '珠峰';
str = '培训';//=>而且使用BABEL如果遇到了CONST设置的常量在进行修改,就无法进行编译了
console.log(str);//=>Uncaught TypeError: Assignment to constant variable.

JS中创建变量方式汇总

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

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

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

class Parent{
constructor(){
//=>this.xxx=xxx
}
//=>Parent.prototype
aa(){}
//=>Parent own property
static bb(){}
}

ES6中的解构赋值

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

数组的解构赋值

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

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

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

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
let [,E]=[12, 23, 34];
console.log(E);//=>23

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

let [,,,A]=[12, 23, 34];
console.log(A);//=>undefined
let [,,,B = 0]=[12, 23, 34];
console.log(B);//=>0

在解构赋值中,支持...xxx的拓展运算符

let [A,...B]=[12, 23, 34, 45, 56];
console.log(A, B);//=>12 [23,34...]
let [...C]=[12, 23, 34, 45, 56];
console.log(C);//=>[12,23...] 数组克隆
let [D,...E,F]=[12, 23, 34, 45, 56];
console.log(D, E, F);//=>Uncaught SyntaxError: Rest element must be last element 拓展运算符只能出现在解构赋值中的结构末尾的位置
let [G,,,...H]=[12, 23, 34, 45, 56];
console.log(G, H);//=>12 [45,56]

对象的解构赋值

let {name, age}={name: '珠峰培训', age: 9};
console.log(name, age);//=>'珠峰培训' 9
let {A, B}={name: '珠峰培训', age: 9};
console.log(A, B);//=>在对象的解构赋值中需要注意的是:赋值的变量需要和对象中的属性名吻合,否则无法获取对应的属性值 undefined*2
let {C = 0}={name: '珠峰培训', age: 9};
console.log(C);//=>0 可以给当前的变量设置默认值
let {name}={name: '珠峰培训', age: 9};
console.log(name);//=>'珠峰培训' 和数组的解构赋值一样,我们可以把后面不需要获取的结构省略掉
let {,age}={name: '珠峰培训', age: 9};//=>Uncaught SyntaxError: Unexpected token , 和数组的解构赋值不一样的地方在于,对象前面不允许出现空来占位(因为对象获取需要通过具体的属性名获取,写成空的话,浏览器不知道怎么识别)
let {age}={name:'xxx',age:'xxx'};//=>但是我们可以把逗号去掉,这样就是只获取其中一个
let {name, ...arg}={name: '珠峰培训', age: 9, teacher: '周啸天'};
console.log(name, arg);//=>'珠峰培训' {age:9...} 支持拓展运算符的
//=>把对象进行浅克隆(只把第一级克隆了)
let obj = {name: 'xxx', age: 10, score: [100, 90, 80]};
let {...arg}=obj;
console.log(arg, obj);
console.log(arg === obj);//=>false
console.log(arg.score === obj.score);//=>true
let {name:A, age:B}={name: '珠峰培训', age: 9};
console.log(A, B);//=>'珠峰培训' 9 在对象的结构赋值中,我们可以把对象的属性名起一个小名(A和B相当于小名或者叫做别名)
let data = [
{
"name": "张三",
"age": 25,
"score": {
"english": [100, 90, 95],
"math": [100, 100, 100],
"chinese": [98, 99, 100]
}
},
{
"name": "李四",
"age": 24,
"score": {
"english": [8, 9, 3],
"math": [149, 150, 148],
"chinese": [138, 140, 145]
}
}
];
let [{score:{english:[A], math:[,B], chinese:[,,C]}}]=data;
console.log(A, B, C);//=>100 100 100

解构赋值的作用

快速交换两个变量的值

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

接收函数返回的多个值

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

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

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 函数中的ABC是私有的变量
//=>快速处理options参数的初始化操作
let animate = function ({curEle = null, target = {}, duration = 1000, callBack = null}={}) {
console.log(curEle, target, duration, callBack);//=>body {opacity: 1} 1000 null
};
animate({
curEle: document.body,
target: {opacity: 1}
});

在ES6中支持给函数设置默认值

let fn = function (x) {
console.log(x);//=>undefined
x = x || 0;
console.log(x);//=>0
};
fn();
let fn2 = function (x = 0) {
console.log(x);//=>0
};
fn2();

ES6中的箭头函数

箭头函数的基础语法

let fn = function (x, y) {
return x + y;
};
console.log(fn(10, 20));//=>30
//=>改写成箭头函数
let fn = (x, y)=> x + y;
let fn = function (n = 0) {
let x = 10,
y = 20;
return x + y + n;
};
//=>改写成箭头函数
let arrowFn = (n = 0)=> {
let x = 10,
y = 20;
return x + y + n;
};

箭头函数中不支持arguments

//=>传统函数支持ARGUMENTS
// let fn = function () {
// let arg = Array.prototype.slice.call(arguments);
// return eval(arg.join('+'));
// };
let fn = (...arg)=> {
//console.log(arguments);//=>Uncaught ReferenceError: arguments is not defined
//=>不支持ARGUMENTS没事,我们使用ES6中的剩余运算符...来获取传递的进来的所有参数值(优势:使用剩余运算符接收到的结果本身就是一个数组,不需要再转换了)
//console.log(arg instanceof Array);//=>true
return eval(arg.join('+'));
};
//=>也可以把FN简写成以下方式
//let fn = (...arg)=> eval(arg.join('+'));
console.log(fn(10, 20, 30, 40));

箭头函数中的this问题

复习普通函数中this指向的问题

let obj = {
name: 'obj',
fn(){
//=>这样处理和下面SUM的处理是一样的
console.log(this);
},
sum: function () {
}
};
obj.fn();//=>this:obj 普通函数执行THIS的指向:看函数执行前面是否有点,有点,点前面是谁THIS就是谁,没有点THIS指向WINDOW或者UNDEFINED(严格模式下)
document.body.onclick = obj.fn;//=>this:body
setTimeout(obj.fn, 1000);//=>this:window
obj.fn.call(12);//=>this:12

箭头函数中没有自己的THIS指向,用到的THIS都是所在宿主环境(它的上级作用域)中的THIS

let obj = {
name: 'obj',
fn: ()=> {
console.log(this);
//=>不管我们怎么去操作,最后THIS都指向WINDOW:箭头函数中没有自己的THIS指向,用到的THIS都是所在宿主环境(它的上级作用域)中的THIS
}
};
obj.fn();
document.body.onclick = obj.fn;
setTimeout(obj.fn, 1000);
obj.fn.call(12);

以后实战项目中,不是要把所有的函数都改为箭头函数,根据自身需要来修改即可(例如:我们需要让函数中的this是宿主环境中的this,我们才来使用箭头函数;或者不涉及this问题,我们想让代码写起来简单一些也可以使用箭头函数;)

let obj = {
name: 'obj',
fn(){
//=>this:obj
// setTimeout(function () {
// //=>this:window
// }, 1000);
// setTimeout(function () {
// //=>this:obj
// }.bind(this), 1000);
// var _this = this;
// setTimeout(function () {
// //=>_this:obj
// }, 1000);
setTimeout(()=> {
//=>this:obj
}, 1000);
}
};
obj.fn();

箭头函数的一点扩充

宿主环境:不是执行的环境而是定义的环境

let fn = ()=> {
console.log(this);
};
let obj = {
name: 'obj',
sum: function () {
//=>this:obj
fn();//=>this:window
//宿主环境:不是执行的环境而是定义的环境,FN虽然是在这执行的,但是它是在WINDOW下定义的,所以它的宿主环境还是WINDOW
}
};
obj.sum();

层级嵌套的箭头函数

// let fn = function (i) {
// return function (n) {
// return n + (++i);
// }
// };
let fn = (i)=> (n)=> n + (++i);

ES6中的类和继承

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

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('王雪超', '80');
Person('王雪超', '80');

ES6中创建类

//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~`);
}
//=>把PERSON当做普通对象设置属性和方法,只需要在设置的方法前面加STATIC即可
static 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创建的类,天生自带NEW.TARGET的验证,不允许把创建的类当做普通函数执行

ES6中的继承

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、让子类实例的原型链上能够找到PERSON父类的原型(这样子类的实例就可以调用父类原型上的方法了)
//-------------
//=>我们可以不写CONSTRUCTOR,浏览器默认会创建它,而且默认就把父类私有的属性继承过来了(而且把传给子类的参数值也传递给父类了)
// constructor(...arg) {
// //=>ARG:传递给子类的参数(数组) [剩余运算符]
// super(...arg);//=>[展开运算符] 把ARG中每一项值展开,分别传递给父类方法 SUPPER(10,20,30)
// }
//------------
//=>很多时候我们不仅要继承父类私有的,还需要给子类增加一些而外私有的,此时就必须写CONSTRUCTOR,但是一定要在CONSTRUCTOR中的第一行写上SUPPER,否则会报错
// constructor(...arg) {
// super(...arg);
//
// let [,,z]=arg;
// this.z = z;
// }
constructor(x, y, z) {
super();//<=>Person.prototype.constructor.call(this)
this.z = z;
}
fn() {
}
}
let c = new Child(10, 20, 30);

JS视频观看地址:http://www.html5train.com/kecheng/detail_1029018