ES6

ES6的目标是使JavaScript语言可以适应更复杂的应用,实现代码库之间的共享,并迭代维护新版本,成为企业级开发语言。

ECMAScript和JavaScript语言,两者之间存在的关系。
简单来说,ECMAScript是JavaScript语言的国际标准,JavaScript是实现ECMAScript标准的脚本语言。

ES6历史发展

2011年,ECMA国际标准化组织在发布ECMAScript 5.1版本之后,就开始着手制定第6版规范。

存在的问题:这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。很明显,在一个版本中不可能包含所有的要引入的新功能,这些功能要被发布在不同的版本中。

解决方法:标准的制定者决定在每年的6月份发布一次新标准,并且使用年份作为版本号。

ECMAScript各版本的发布时间

let和const关键字

let关键字

let是ES6中新增的用于声明变量的关键字。在ES6之前,我们使用var关键字来声明变量。与var关键字相比,let关键字有一些新的特性。

let关键字的新的特性

  • let关键字声明的变量只在所处的块级作用域有效。

  • let关键字声明的变量不存在变量提升

  • let关键字声明的变量具有暂时性死区特性。

let关键字声明的变量只在所处的块级作用域有效

第1个好处:防止代码块内层变量覆盖外层变量

<script>
if (true) {
let a = 10;
console.log(a); // 输出结果:10
}
console.log(a); // 报错,a未定义
</script>

第2个好处:防止循环变量变成全局变量

<script>
for (let i = 0; i < 2; i++) { }
console.log(i); // 报错,i未定义
</script>

let关键字声明的变量不存在变量提升

在使用var关键字声明变量时,变量可以先使用再声明,这种现象就是变量提升。但一般的编程习惯,变量应该在声明之后才可以使用。

在ES6中,变量的使用规范更加严格,使用let关键字声明的变量只能在声明之后才可以使用,否则就会报错。

不存在变量提升的代码演示

<script>
console.log(a); // 报错,a未初始化
let a = 10;
</script>

let关键字声明的变量具有暂时性死区

使用let关键字声明的变量具有暂时性死区特性。这是因为let关键字声明的变量不存在变量提升,其作用域都是块级作用域,在块级作用域内使用let关键字声明的变量会被整体绑定在这个块级作用域中形成封闭区域,不再受外部代码影响。

所以在块级作用域内,使用let关键字声明变量之前,该变量都是不可用的。这在语法上,称为**“暂时性死区”**。

暂时性死区的代码演示

<script>
var num = 10;
if (true) {
console.log(num); // 报错,无法在初始化之前访问num
let num = 20;
}
</script>

const关键字

const是ES6中新增的用于声明常量的关键字。所谓常量就是值(内存地址)不能变化的量。

const关键字声明的常量的特点:

  • const关键字声明的常量具有块级作用域

  • const关键字声明常量时必须赋值

  • const关键字声明常量并赋值后常量的值不能修改

const关键字声明的常量具有块级作用域

演示const关键字声明的常量块级作用域效果

<script>
if (true) {
const a = 10;
console.log(a); // 输出结果:10
}
console.log(a); // 报错,a未定义
</script>

演示const关键字声明常量时不赋值的情况

<script>
const PI; // 报错,常量PI未赋值
</script>

const关键字声明常量并赋值后常量的值不能修改

对于基本数据类型(如数值、字符串),一旦赋值,值就不可修改

<script>
const PI = 3.14;
PI = 100; // 报错,无法对常量赋值
</script>

对于复杂数据类型(如数组、对象),虽然不能重新赋值,但是可以更改内部的值。

<script>
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // 可以更改数组内部的值,结果为['a', 'b']
ary = ['a', 'b’]; // 报错,无法对常量赋值
</script>

let、const、var关键字之间的区别

变量的作用域范围不同

使用var关键字声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。使用let关键字声明的变量和使用const关键字声明的常量都具有块级作用域。

变量提升

使用var关键字声明的变量存在变量提升,可以先使用再声明。使用let关键字声明的变量和const关键字声明的常量不存在变量提升功能,只能先声明再使用。

值是否可以更改

使用var关键字和let关键字声明的变量,可以更改变量的值,但使用const关键字声明的常量,不能更改常量值对应的内存地址。

在编写程序的过程中,如果要存储的数据不需要更改,建议使用const关键字,如函数的定义、 π值或数学公式中一些恒定不变的值。
由于使用const声明的常量,其值不能更改,且JavaScript解析引擎不需要实时监控值的变化,所以使用const关键字要比let关键字效率更高。

解构赋值

数组的解构赋值

解构表示对数据结构进行分解,赋值指变量的赋值。

在ES6中,允许按照一一对应的方式,从数组或对象中提取值,然后将提取出来的值赋给变量。

解构赋值的优点是它可以让编写的代码简洁易读,语义更加清晰,并且可以方便地从数组或对象中提取值。

数组的解构赋值,就是将数组中的值提取出来,然后赋值给另外的变量。

变量的数量和数组中值的数量相一致的情况

<script>
let [a, b, c] = [1, 2, 3];
console.log(a); // 输出结果:1
console.log(b); // 输出结果:2
console.log(c); // 输出结果:3
</script>

变量的数量和数组中值的数量不一致的情况

<script>
let [a, b, c, d] = [1, 2, 3];
console.log(a); // 输出结果:1
console.log(b); // 输出结果:2
console.log(c); // 输出结果:3
console.log(d); // 输出结果:undefined
</script>

对象的解构赋值

对象解构允许使用变量的名字匹配对象中属性,如果匹配成功就将对象中属性的值赋值给变量。

对象解构的示例代码

<script>
let person = { name: 'zhangsan', age: 20 };
let { name, age } = person; // 解构赋值
console.log(name); // 输出结果:zhangsan
console.log(age); // 输出结果:20
</script>

使用另外一种形式去实现对象的解构赋值,该形式支持变量的名字和对象中属性的名字不一样的情况,等号左边的大括号代表对象解构。

对象解构的另一种形式

<script>
let person = { name: 'zhangsan', age: 20, sex: '男' };
let { name: myName } = person;
console.log(myName); // zhangsan
</script>

箭头函数

箭头函数是ES6中新增的函数,它用于简化函数的定义语法,有自己的特殊语法,接收一定数量的参数,并在其封闭的作用域的上下文(即定义它们的函数或其他代码)中操作.

箭头函数的语法

箭头函数基本语法

() => { }

箭头函数以小括号开头,在小括号中可以放置参数。小括号的后面要跟着箭头(=>),箭头后面要写一个大括号来表示函数体,这是箭头函数的固定语法

箭头函数调用:因为箭头函数没有名字,我们通常的做法是把箭头函数赋值给一个变量,变量名就是函数名,然后通过变量名去调用函数即可。

定义没有参数的箭头函数

<script>
const fn = () => {
console.log(123); // 输出结果:123
};
fn(); // 函数调用
</script>

箭头函数的特点

特点1: 省略大括号

在箭头函数中,如果函数体中只有一句代码,且代码的执行结果就是函数的返回值,此时可以省略函数体大括号。

计算两个数值相加的结果

<script>
  const sum = (num1, num2) => num1 + num2;
// 等价于
  // const sum = (num1, num2) => {
  //   return num1 + num2;
  // };
  const result = sum(10, 20); // 使用result接收sum()函数执行的结果
  console.log(result); // 在控制台输出result值,结果为30
</script>

特点2: 省略参数外部的小括号

在箭头函数中,如果参数只有一个,可以省略参数外部的小括号。

只有一个参数的函数定义方式

<script>
// 传统的函数定义方式
// function fn(v) {
// return v;
// }
// ES6中函数定义方式
const fn = v => v;
</script>

定义带有一个参数的函数,并在浏览器的弹出框中弹出参数值。

示例代码

<script>
const fn = v => {
alert(v);
};
fn(20);
</script>

箭头函数中的this关键字

ES6前,JavaScript的this关键字指向的对象是在运行时基于函数的执行环境绑定的,在全局函数中,this指向的是window;当函数被作为某个对象的方法调用时,this就指向那个对象。

ES6中,箭头函数不绑定this关键字,它没有自己的this关键字,如果在箭头函数中使用this关键字,那么this关键字指向的是箭头函数定义位置的上下文this。也就是说,箭头函数被定义在哪,箭头函数中的this就指向谁。

箭头函数解决了this执行环境所造成的一些问题,如解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout()和setInterval()中使用this所造成的问题。

演示ES6中this的指向,示例代码

<script>
  const obj = { name: 'zhangsan' };
  function fn() {
    console.log(this);     // 输出结果:{name: "zhangsan"}
    return () => {
      console.log(this);   // 输出结果:{name: "zhangsan"}
    };
  }
// call()方法可以改变函数内部的this指向,将函数fn()内部的this指向obj对象
  const resFn = fn.call(obj);
  resFn();
</script>

剩余参数

剩余参数的语法

剩余参数是指在函数中,当函数实参个数大于形参个数时,剩余的实参可以存放到一个数组中。

剩余参数语法允许将一个不确定数量的参数表示为数组。这种不确定参数的定义方式对于声明一个具有未知参数个数的函数非常方便。

剩余参数代码演示

<script>
function sum(first, …args) {
console.log(first); // 输出结果:10
console.log(args); // 输出结果:[20, 30]
}
sum(10, 20, 30);
</script>

剩余参数是程序员自定义的一个普通标识符,接收剩余参数的变量是一个数组(Array的实例),能够直接使用所有的数组方法,如 sort()、map()、forEach()或pop()方法等。

使用剩余参数计算多个数值的求和结果

<script>
const sum = (…args) => {
let total = 0;
args.forEach((item) => {
total += item;
});
// 等价于:args.forEach(item => total += item);
return total;
};
console.log(sum(10, 20)); // 输出结果:30
console.log(sum(10, 20, 30)); // 输出结果:60
</script>

剩余参数和解构赋值配合使用

演示剩余参数和解构的使用方法

<script>
// 以数组的解构赋值为例
let students = ['王五', '张三', '李四'];
let [s1, …s2] = students;
console.log(s1); // 输出结果:王五
console.log(s2); // 输出结果:["张三", "李四"]
</script>

扩展运算符

扩展运算符的语法

扩展运算符和剩余参数的作用是相反的,扩展运算符可以将数组或对象转换为用逗号分隔的参数序列。扩展运算符也用3个点…表示。

扩展运算符在数组中的使用演示

<script>
let ary = [1, 2, 3];
// …ary相当于1, 2, 3
console.log(…ary); // 输出结果:1 2 3
// 等价于
console.log(1, 2, 3); // 输出结果:1 2 3
</script>

小提示:使用扩展运算符将ary数组拆分成以逗号分隔的参数序列后,又将参数序列放在了console.log()方法中,此时参数序列中的逗号会被当成console.log()方法的参数分隔符,所以输出结果中没有逗号。

扩展运算符的应用

利用扩展运算符合并数组

数组合并的第1种方法

<script>
let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
// …ary1 // 表示将ary1数组拆分成1, 2, 3
// …ary2 // 表示将ary2数组拆分成4, 5, 6
let ary3 = […ary1, …ary2];
console.log(ary3); // 输出结果:(6) [1, 2, 3, 4, 5, 6]
</script>

数组合并的第2种方法

<script>
let ary1 = [1, 2, 3];
let ary2 = [4, 5, 6];
ary1.push(…ary2);
console.log(ary1); // 输出结果:(6) [1, 2, 3, 4, 5, 6]
</script>

利用扩展运算符将伪数组转换为真正的数组

伪数组:可以应用数组的length属性但是无法直接调用数组方法,它也可以像数组一样进行遍历。典型的伪数组包括函数中的argumentsdocument.getElementsByTagName()返回的元素集合,以及document.childNodes等。

将伪数组转换为真正的数组

<body>
  <div>1</div><div>2</div><div>3</div>
<div>4</div><div>5</div><div>6</div>
  <script>
    var oDivs = document.getElementsByTagName('div');
    console.log(oDivs); // 输出结果:HTMLCollection(6) [div, div, div, div, div, div]
    var ary = […oDivs];
    ary.push('a'); // 在数组中追加a
    console.log(ary); // 输出结果:(7) [div, div, div, div, div, div, "a"]
  </script>
</body>

模板字符串

模板字符串的语法

模板字符串是ES6新增的创建字符串的方式,它使用反引号进行定义。

定义模板字符串

<script>
let name = 这是一个模板字符串;
console.log(name); // 输出结果:这是一个模板字符串
</script>

模板字符串的应用

模板字符串可以解析变量

演示模板字符串如何解析变量

<script>
let name = 张三;
let sayHello = Hello, 我的名字叫$&#123;name&#125;;
console.log(sayHello); // 输出结果:Hello, 我的名字叫张三
</script>

在模板字符串中可以换行

演示模板字符串中如何换行

<script>
  let result = { name: 'zhangsan',age: 20,sex: '男' };
  let html = &lt;div&gt;   &lt;span&gt;$&#123;result.name&#125;&lt;/span&gt;   &lt;span&gt;$&#123;result.age&#125;&lt;/span&gt;   &lt;span&gt;$&#123;result.sex&#125;&lt;/span&gt; &lt;/div&gt;;
  console.log(html);
</script>

在模板字符串中可以调用函数

演示模板字符串中如何调用函数

<script>
const fn = () => {
return '我是fn函数';
};
let html = 我是模板字符串 $&#123;fn()&#125;;
console.log(html); // 输出结果:我是模板字符串 我是fn函数
</script>

ES6的内置对象扩展

数组的扩展方法

ES6为Array(数组)、String(字符串)等内置对象提供了许多扩展方法,从而帮助开发人员提高开发效率。

通过扩展方法可以实现很多方便的功能,如将伪数组转换为真正的数组、在数组中查找出符合条件的数组成员等。

Array.from()方法

Array构造函数提供了一个from()方法,它可以接收一个伪数组作为参数,返回值为转换后的结果,这个结果是一个真正的数组。

示例代码

<script>
  var arrayLike = {
'0': '张三','1': '李四','2': '王五',length: 3
  };
  var ary = Array.from(arrayLike);
  console.log(ary); // 输出结果:(3) ["张三", "李四", "王五"]
</script>

在Array构造函数中,from()方法还可以接收两个参数,这与数组中的map()方法类似,它用于处理数组中的每个元素并将处理后的结果放入返回的数组中。

示例代码

<script>
  var arrayLike = {
    '0': 1,'1': 2,'2': 3,length: 3
  };
  var ary = Array.from(arrayLike, (item) => {
    return item * 2;
  });
  // 等价于:var ary = Array.from(arrayLike, item => item * 2)
  console.log(ary); // 输出结果:[2, 4, 6]
</script>

数组实例的find()方法

在数组实例中,ES6提供了一个find()方法,它用于在数组中查找出第一个符合条件的数组成员。

演示查找出item.id值为2的对象

<script>
  var ary = [{
    id: 1, name: '张三'
  }, {
    id: 2,name: '李四'
  }];
  let target = ary.find((item, index) => item.id == 2);
  console.log(target); // 输出结果:{id: 2, name: "李四"}
</script>

数组实例的findIndex()方法

数组实例提供了一个findIndex()方法,用于在数组中查找出第一个符合条件的数组成员的索引,如果没有找到则返回-1。

演示如何查找出数组中大于9的元素的位置

<script>
  let ary = [1, 5, 10, 15];
  let index = ary.findIndex((value, index) => {
    return value > 9;
  });
  // 等价于:let index = ary.findIndex((value, index) => value > 9);
console.log(index);  // 输出结果:2
</script>

数组实例的includes()方法

ES6中为数组实例提供了includes()方法来检查某个数组是否包含给定的值,返回一个布尔值,true表示包含给定的值,false表示不包含给定的值。

演示includes()方法的使用

<script>
let ary = ['a', 'b', 'c'];
let result = ary.includes('a');
console.log(result); // 输出结果:true
result = ary.includes('e')
console.log(result); // 输出结果:false
</script>

字符串的扩展方法

前面我们学习了数组的includes()方法,它用于在数组中查找出第一个符合条件的数组成员的位置。

ES6也提供了字符串的includes()方法用来确定一个字符串是否包含在另一个字符串中。

除此之外,ES6还提供了startsWith()方法和endsWith()方法同样可以用于字符串的查找。

字符串实例的startsWith()方法和endsWith()方法

startsWith()方法表示参数字符串是否在原字符串的头部,用于判断字符串是否以某字符串开头;

endsWith()方法表示参数字符串是否在原字符串的尾部,用于判断字符串是否以某字符串结尾。

上述两个方法如果满足条件则返回true,反之返回false。

演示startsWith()方法和endsWith()方法的使用

<script>
let str = 'Hello ECMAScript 2015';
let r1 = str.startsWith('Hello');
console.log(r1); // 输出结果:ture
let r2 = str.endsWith('2016');
console.log(r2); // 输出结果:false
</script>

字符串实例的repeat()方法

repeat()方法表示将原字符串重复n次,它返回一个新字符串,并接收一个数值作为参数,表示将字符串重复多少次。

演示repeat()方法的使用

<script>
console.log('y' . repeat(5)); // 输出结果:yyyyy
console.log('hello' . repeat(2)); // 输出结果:hellohello
</script>

Set数据结构

Set数据结构基本使用

ES6提供了新的数据结构Set

Set类似于数组,但是成员的值都是唯一的,没有重复的值。

Set实例的方法分为两大类,即操作方法(用于操作数据)和遍历方法(用于遍历成员)。

Set数据结构常用于电商网站的搜索功能中,用户搜索完成后,网站要记录用户搜索的关键字,方便用户下次直接单击搜索历史关键字来完成搜索。

创建Set数据结构

Set本身是一个构造函数,创建此构造函数的实例对象就是创建Set数据结构。

创建Set数据结构示例代码

<script>
const s1 = new Set(); // 使用new关键字创建Set构造函数的实例
console.log(s1.size); // 输出结果:0
</script>

初始化Set数据结构

在创建Set数据结构时,也可以传递一个数组作为参数,用来初始化。

初始化Set数据结构示例代码

<script>
const s2 = new Set(['a', 'b']);
console.log(s2.size); // 输出结果:2
</script>

利用Set数据结构给数组去重

在初始化Set构造函数时,可以传递一个数组作为参数,如果数组中有重复的值,那么Set数据结构会把重复的值过滤掉。

数组去重示例代码

<script>
const s3 = new Set(["a", "a", "b", "b"]);
console.log(s3.size); // 输出结果:2
const ary = […s3];
console.log(ary); // 输出结果:(2) ["a", "b"]
</script>

Set实例的操作方法

Set实例的add()方法

Set实例提供的add()方法用于向Set数据结构中添加某个值,它接收一个参数代表要添加的值,返回Set结构本身。

演示add()方法的使用

<script>
const s4 = new Set();
s4.add('a').add('b');
console.log(s4.size); // 输出结果:2
</script>

Set实例的delete()方法

Set实例提供的delete()方法用于删除Set数据结构中的某个值,它接收一个参数代表要删除的值,返回一个布尔值,如果结果为true则表示删除成功,为false则表示删除失败。

演示delete()方法的使用

<script>
const s4 = new Set();
s4.add('a').add('b');
const r1 = s4.delete('a');
console.log(s4.size); // 输出结果:1
console.log(r1); // 输出结果:true
</script>

Set实例的has()方法

Set实例提供has()方法,该方法接收一个参数并判断该参数是否为Set数据结构中的成员,返回一个布尔值,如果结果为true则表示包含该成员,为false则表示不包含该成员。

演示has()方法的使用

<script>
const s4 = new Set();
s4.add('a').add('b');
const r1 = s4.delete('a');
console.log(s4.size); // 输出结果:1
console.log(r1); // 输出结果:true
const r2 = s4.has('a');
console.log(r2); // 输出结果:false
</script>

Set实例的clear()方法

Set实例提供的clear()方法用于清除Set数据结构中的所有成员,该方法没有返回值。

演示clear()方法的使用

<script>
const s4 = new Set();
s4.add('a').add('b');
s4.clear();
console.log(s4.size); // 输出结果:0
</script>

Set实例的遍历方法

Set数据结构的实例与数组一样,也拥有一个forEach()方法,用于遍历Set数据结构中的成员,该方法可以对每个成员执行某种操作,没有返回值。

演示forEach()方法的使用

<script>
const s5 = new Set(['a', 'b', 'c']);
s5.forEach(value => {
console.log(value); // 依次输出a、b、c
});
</script>

初识Symbol

Symbol的基本使用

Symbol是ES6中引入的原始数据类型,它的功能类似于一种标识唯一性的ID值,表示独一无二。

Symbol通过Symbol()函数创建。Symbol()函数可以接收一个字符串作为参数,为新创建的Symbol实例提供描述信息,该描述信息主要是在控制台中显示或转为字符串时使用,以便于区分。Symbol不是对象,所以Symbol()函数不能使用new命令。

演示Symbol的基本使用

<script>
let s1 = Symbol('a');
let s2 = Symbol('b');
console.log(s1); // 输出结果:Symbol(a)
console.log(s2); // 输出结果:Symbol(b)
</script>

每个Symbol实例都是唯一的,即使具有相同参数的两个Symbol()函数进行比较时,函数的返回结果都会是false。

示例代码

<script>
let s1 = Symbol('a');
let s2 = Symbol('a');
console.log(s1); // 输出结果:Symbol(a)
console.log(s2); // 输出结果:Symbol(a)
console.log(s1 === s2); // 输出结果:false
</script>

使用Symbol作为对象属性名

因为每一个Symbol的值都是不相等的,所以Symbol作为对象的属性名,可以保证属性不重名。这适用于对象由多个模块组成的情况,可以防止某一个键被意外改写或覆盖。

示例代码-第1种写法

<script>
  let MY_NAME = Symbol();
  // 第1种写法
  let a = {};
  a[MY_NAME] = 'Hello!';
  console.log(a);  // 输出结果:{ Symbol(): "Hello!" }
</script>

示例代码-第2种写法

<script>
  let MY_NAME = Symbol();
  // 第2种写法
  let a = {
    [MY_NAME]: 'Hello!',
  };
  console.log(a);  // 输出结果:{ Symbol(): "Hello!" }
</script>

示例代码-第3种写法

<script>
  let MY_NAME = Symbol();
  // 第3种写法
  let a = {};
  Object.defineProperty(a, MY_NAME, { value: 'Hello!' });
  console.log(a);  // 输出结果:{ Symbol(): "Hello!" }
</script>