ES6

ES6 语法

Posted by violetks on December 19, 2019

关于 ES6 在《ECMAScript6 入门》中已经有很详细的教程了,这里主要再举些例子

ES6,全称ECMAScript 6.0,是 JavaScript 的第六个版本标准

一、var、let 和 const 的区别

1、ES6letconst声明变量和常量,ES5var声明。

2、letconst作用域只局限于当前代码块,是块级作用域;var的作用域是方法作用域,整个方法内的定义变量后的代码都可以使用。

3、使用letconst声明变量作用域不会被提升,在变量未声明前直接使用会报错;var具有变量提升,在变量未赋值时,变量 undefined。

{
  var str;
  console.log(str);  // undefined
  str = "张三";      // var 这样是可行的
}

4、在相同作用域下letconst不能重复声明同一个变量,var可以重复声明同一个变量。

5、const定义的对象,属性可以改变

因为对象是引用类型的,const仅保证指针不发生改变,修改对象的属性不会改变对象的指针,所以是被允许的。

6、for循环体现let的父子作用域

例子:点击按钮

<body>

<button>按钮1</button>

<button>按钮2</button>

<button>按钮3</button>

<script>
    var btns = document.querySelectorAll("button");
    for (var i=0;i<btns.length;i++) {
        btns[i].onclick = function () {
            alert("点击了第"+ i +"个按钮");
        }
    }
    //上面这样点击每个按钮都是 i=3
    //改为闭包的方式可以解决
    var btns = document.querySelectorAll("button");
    for (var i=0;i<btns.length;i++) {
        (function (i) {
            btns[i].onclick = function () {
                alert("点击了第"+ i +"个按钮");
            }
        })(i);
    }
    //ES6 用 let,使代码更简洁
    let btns = document.querySelectorAll("button");
    for (let i=0;i<btns.length;i++) {
        btns[i].onclick = function () {
            alert("点击了第"+ i +"个按钮");
        }
    }
</script>

</body>

二、箭头函数

1.基本格式:(参数)=>{ }

// 使用箭头函数对数组排序
'use strict'
var arr = [10, 20, 1, 2];
arr.sort((x, y) => {
    return x-y;
});
console.log(arr); // [1, 2, 10, 20]

2.简写:
(1)有且仅有一个参数,()可以不写
(2)如果有且仅有一个语句并且是return,{}可以不写

3.作用:修复 this 指向

let json={
  a: 12,
  fn: function (){
    alert(this.a);
  }
}

json.fn();   //12
let json={
  a: 12,
  fn: function (){
    alert(this.a);
  }
}

let x = new Date();
x.fn = json.fn;

x.fn();   //undefined

用箭头函数改写:

let json={
  a: 12,
  fn: ()=>{
    alert(this.a);    //this 指向当前环境,此处是 window
  }
}

json.fn();   //结果是 undefined,因为 window 对象中没有 a

使用构造函数:

class Json{
  constructor(){
    this.a = 12;
    this.fn = ()=>{
      alert(this.a);
    }
  }
}

let json = new Json();
json.fn();   //12,因为当前环境是 Json 对象,this 指向 Json 对象

let x = new Date();
x.fn = json.fn;
x.fn();     //12

三、函数的参数扩展(…)

(1)参数展开

function show(a, b, ...c){
  console.log(a,b,c);      //a=1,b=3,c=[4,2,6,8]
}

show(1,3,4,2,6,8);
let arr = [1,5,8];
function show(a,b,c){
  alert(a+b+c);
}

show(...arr);   // 14

(2)数组展开 合并数组

let arr1 = [1,5,8];
let arr2 = [4,5,6];
let arr = [...arr1, ...arr2];
alert(arr);   // [1,5,8,4,5,6]

(3)JSON展开

let json = {a: 1, b: 5, c: 8};
let json2 = {
  ...json,
  d: 7
};

四、解构赋值

  • 数组的解构赋值
[a, b] = [10, 20];  // a=10,b=20
[a, b, ...rest] = [1, 2, 3, 4, 5];  // a=1,b=2,rest=[3,4,5]

// 嵌套数组解构
[a, [[b], c], d] = [1, [[2], 3], 4];

// 不需要匹配的位置可以置空
[,,c] = [1, 2, 3];  // c=3

1、默认值

# 为了防止从数组中取出一个值为 undefined 的对象可以在表达式左边的数组中为任意对象预设默认值
[a=5, b=7] = [1]; // a=1,b=7

2、交换变量

# 在一个解构表达式中可以交换两个变量的值
[a, b] = [b, a];

3、解析一个从函数返回的数组

# 可以使用解构在一行内完成解析
function f() {
  return [1, 2];
}
var a, b;
[a, b] = f(); // a=1,b=2
  • 对象的解构赋值
// 变量必须与属性同名
let { a, b } = { a: 1, c: 2 };  // a=1,b=undefined

1、无声明赋值

# 如果在解构之前就已经定义了对象
let obj;
{obj}={obj:'James'};  //报错
# 大括号{}位于行首JS 引擎就会认为 { obj } 是一个代码块所以等号就出问题了
# 解决方式是在包裹一层括号()
let obj;
({obj}={obj:'James'});
# 括号的出现让整个解构赋值的结构被看做一个代码块而内部的 { obj } 模式则可以正常匹配到

2、对任意深度的嵌套对象进行解构

let obj = { arr: [1, {a: 2}] };
let { arr: [x, {y}] } = obj;

3、自定义属性名称

var { name, id: ID } = { name: 'jack', id: 1 };  // ID=1

4、for of迭代和解构

var people = [{
    name: 'Mike Smith',
    family: {
      mother: 'Jane Smith',
      father: 'Harry Smith',
    },
    age: 35
  },
  {
    name: 'Tom Jones',
    family: {
      mother: 'Norah Jones',
      father: 'Richard Jones',
    },
    age: 25
  }];

for (var {name: n, family: {father: f}} of people) {
  console.log('Name: ' + n + ', Father: ' + f);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

5、解构对象时会查找原型链(如果属性不在对象自身,将从原型链中查找)

// 声明对象 和 自身 self 属性
var obj = {self: '123'};
// 在原型链中定义一个属性 prot
obj.__proto__.prot = '456';

const {self, prot} = obj;
// self "123"
// prot "456"(访问到了原型链)
  • 字符串解构
const [a,b,c,d,e] = 'hello';

五、Object.assign 的理解

1、作用:Object.assign可以实现对象的合并。
2、语法:Object.assign(target, ...sources)
3、解析:
(1)Object.assign会将源对象source里面的可枚举属性复制到目标对象target,如果和target有同名属性值,后面的属性值会覆盖前面的属性值。
(2)后面的source会覆盖前面的source的同名属性。
(3)如果源对象source的属性值为基本类型,通过Object.assign得到的新对象为深拷贝;如果源对象source的属性值为引用类型如对象,那对于这个对象而言是浅拷贝。
即:第一级属性深拷贝,从第二级属性开始就是浅拷贝。

let obj = { name: "Tom", age: 11, job: ["web"] };
let copyObj = Object.assign({}, obj);
// 情况一:改变 copyObj 的整个 job 数组,obj 的 job 不变
copyObj.job = ["IT"];
console.log(obj);     // { name: "Tom", age: 11, job: ["web"] }
console.log(copyObj); // { name: "Tom", age: 11, job: ["IT"] }
// 情况二:通过数组下标改变 copyObj 中 job 数组中的某个值,obj 的 job 跟着改变
copyObj.job[0] = "IT";
console.log(obj);     // { name: "Tom", age: 11, job: ["IT"] }
console.log(copyObj); // { name: "Tom", age: 11, job: ["IT"] }

六、原生对象扩展

1.Array 扩展:map、reduce、filter、forEach

  • map 映射:一一对应
let arr = [68,53,12,98,65];

let arr2 = arr.map(function (item){
  if(item>=60){
    return '及格';
  }else{
    return '不及格';
  }
});

console.log(arr);
console.log(arr2);

使用箭头函数:

let arr = [68,53,12,98,65];
let arr2 = arr.map(item=>item>=60?'及格':'不及格');

map创建的对象常用方法:setgetdeletehasclearkeysvaluesentries

  • reduce n=>1
let arr = [68,53,12,98,65];
let result = arr.reduce(function (tmp, item, index){
  //alert(index+':'+tmp+','+item);
  return tmp+item;   //总和
});

alert(result);
// 求平均值
let arr = [68,53,12,98,65];
let result = arr.reduce(function (tmp, item, index){
  if(index==arr.length-1){
    return (tmp+item)/arr.length;   //平均值
  }else{
    return tmp+item;   //总和
  }
});

alert(result);
  • filter 过滤
// 获取一个数组中的偶数
let arr = [68,53,12,98,65,83,16];
let arr2 = arr.filter(item=>item%2==0);
  • forEach 遍历
let arr = [68,53,12,98,65,83,16];
arr.forEach((item,index)=>{
  //alert('第'+index+'个:'+item);
  //使用模板字符串
  alert(`第${index}个:${item}`);
});

2.模板字符串

3.JSON

七、异步操作——Ajax,Promise

ajax('http://aaa.com/a/1.txt',function(){});

异步操作:同时进行多个操作,用户体验好,代码混乱
同步操作:一次只能进行一个操作,用户体验差,代码清晰
当许多功能需要连续调用,Ajax 代码就会一层一层嵌套,产生回调地狱😱
回调地狱:函数作为参数层层嵌套
回调函数:一个函数作为参数需要依赖另一个函数执行调用
例子:

ajax('url1',function (data1){
  ajax('url2',function (data2){
    ajax('url3',function (data3){
      //数据处理
    },function(){alert("error3!");})
  },function(){alert("error2!");})
},function(){alert("error1!");})

使用 ES6 提供的 Promise 对象解决异步编程

例子:获取文件1.txt中的一个数组,运行环境类似这样localhost/demo/1.html

// 创建一个 Promise 对象
let p = new Promise(function (resolve,reject){
  $.ajax({
    url:'data/1.txt',
    dataType:'json',
    success(arr){
      resolve(arr);
    },
    error(res){
      reject(res);
    }
  })
});
// then 方法指定 resolve 状态和 reject 状态的回调函数
// p.then(function(){},function(){});
p.then(function(arr){
  console.log(arr+'success');
},function(res){
  console.log(res+'error');
});

JQuery 中的$.ajax会返回一个结果,也是一个 Promise

// 验证 $.ajax 的结果
let res = $.ajax({
  url:'data/1.txt',
  dataType:'json'
});

console.log(res);

因此可以调用 then 方法:

$.ajax({
  url:'data/1.txt',
  dataType:'json'
}).then(arr=>{
  alert(arr);
},res=>{
  alert('失败');
});

使用Promise.all()方法同时处理多个请求

Promise.all([
  $.ajax({url:'data/1.txt',dataType:'json'}),
  $.ajax({url:'data/2.txt',dataType:'json'}),
  $.ajax({url:'data/3.txt',dataType:'json'}),
]).then(arr=>{
  console.log(arr);   //arr 是一个数组
  let [data1,data2,data3] = arr;   //解构赋值,也可直接在参数里解构
  console.log(data2);
},res=>{
  alert('失败');
});

注意:使用Promise.all()需要几个请求全部成功才行
Promise.race()只要有一个实例状态率先改变状态,Promise 实例就跟着改变

八、async/await 函数

  1. generator/yield 已经被 async/await 取代。
  2. async放在函数前面,表示异步执行,该函数的执行不阻塞后面代码的执行。
  3. await表示等待 await 后面的函数执行完毕,并且有了返回结果之后,才能继续执行下面的代码。
async function show(){
  let a=12;
  let b=5;

  try {
    let data = await $.ajax({url:'data/1.txt',dataType:'json'});
    alert(a+b+data[0]);
  } catch(e) {
    alert("读取失败");
  }
}

show();

九、面向对象、模块化

1.类的构造函数

class Person{
  constructor(name,age){
    this.name = name;
    this.age = age;
  }

  showName(){
    alert(this.name);
  }

  showAge(){
    alert(this.age);
  }
}

let p = new Person('xl',18);
p.showName();
p.showAge();

2.类的继承

class Worker extends Person{
  constructor(name,age,job){
    super(name,age);
    this.job = job;
  }

  showJob(){
    alert(this.job);
  }
}

let p = new Person('xl',18,'Java');
p.showName();
p.showAge();
p.showJob();

3.模块化

(1)导入
(2)导出
(3)webpack 编译:因为浏览器不支持 ES6 模块

//modle.js - 定义模块
export let a = 12;
//index.js - 使用模块
import * as modle from './modle';

alert(modle.a);
//index.html
<script src="index.js"></script>
//webpack.config.js - 编译配置
const pathlib = require('path');

module.exports={
  mode:"production",
  entry:"./js/index.js",
  output:{
    path: pathlib.resolve(__dirname,'build'),
    filename:'bundle.js'
  }
}

使用 cmd 命令行窗口运行命令webpack,就会在目录下生成 build 文件夹,里面有文件bundle.js
然后 index.html 中引入bundle.js
(4)默认成员

export default 9;
import modle from './modle';

console.log(modle);     // 9

(5)几种引入的写法

import * as mod from 'xxx';    //引入所有成员
import mod from 'xxx';         //引入 default 成员
import {a,b as c} from 'xxx';
import 'xxx';
let p = import("./mod");       //异步引入

学习视频:智能社-ES6 精讲