ES5与ES6

254 阅读12分钟

严格模式

在ES5中我们可以使用严格模式字符串:"use strict"。 在严格模式下会对我们一些非常规的操作进行限制,这样做的好处就是代码的执行效率更高。

为整个脚本开启严格模式

我们一般会在所有的语句之前写入字符串:"use strict"。这表示为整个脚本开启严格模式。

    "use strict";
    var str = '此时已经开启了严格模式!';

注意:不推荐为整个脚本都开启严格模式,因为我们在开发项目的时候经常会引入一些外部的库或者框架,别人的代码可不一定都是采用严格模式的。所以我们将来可能会这些代码进行合并,就会出现一些未知的错误。

在函数内开启严格模式

如果你只是想对某一个函数作用域单独进行严格模式的话,就将严格模式的字符串写入该函数作用域的顶部。

    function fn(){
        "use strict";
         var str = '严格模式只会在该函数中起作用';
     }

推荐:这是我推荐的做法,这样严格模式只会在局部作用域内工作而不会影响到其他的代码。

创建的严格模式的处理

 num = 10;// 报错,变量必须使用var命令定义
    function fn(num1,num2,num1){// 语法错误:形参不能重名
        "use strict"
        return num1+num2+num1;
    }
    // 普通模式
    (function(a){
        arguments[0] = 100;
        console.log(a);// 100
    })(1);
    // 严格模式
    (function(a){
        "use strict";
        arguments[0] = 100;
        console.log(a);// 1
    })(1);
    // 不允许使用部分保留字
    var private = 10;// 语法错误
    var interface = 10;// 语法错误
    var implements = 10;// 语法错误
    ...

具体详细内容请参考:MDN strict

let/const

在ES6以前我们在JS中定义变量都是用过var关键字来实现的。ES6中新增了let命令,它的用法类似于var,但是更加严谨。

块作用域(明确的局部作用域)

在ES6之前JS中是不存在块级作用域的,在之前JS只有两个作用域: 全局作用域 函数作用域 但是这样的机制会带来很多的问题,例如下面的代码:。

 	var num1 = 10;// 全局变量
    {
        var num2 = 20;// 全局变量
        let num3 = 30;// 局部变量
        num3;// 30
    }
    num2;// 20
    num3;// error:num3 is not defined

上面的代码中一对花括号表示一个块级作用域,在花括号中使用var定义的变量依然还是全局的,也就是说这一对花括号丝毫没有起作用,这种情况我们把它叫做:变量泄露。而使用let定义的变量就是局部的,在外部是无法访问的。

	// for循环中的变量就是变量泄露的典型
    for(var i=0;i<5;i++){ ... }
    i;// 5
    // 使用let关键字可以有效的规避此问题
    for(let i=0;i<5;i++){ ... }
    i;// error:i is not defined

没有变量提升

使用let不会对变量有本来提升的操作。所以变量必须是先定义后使用,否则报错。

	num1;// undefined
    num2;// error
    var num1 = 10;
    let num2 = 20;

暂时性死区(作用域绑定)

只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

 	var num1 = 10;
    var num2 = 20;
    {
        console.log(num1);// 10
        console.log(num2);// error
        let num2 = 30;
    }

这种去除变量提升和引入暂时性死区的做法对于开发者是非常棒的,我们程序员并不总是非常规范的使用变量,所以我们的代码在运行时极其容易出现未知的bug。

不允许重复声明

let不允许在同一个作用域内重复声明变量。

	// 允许
    let num1 = 10;
    function fn(){ let num1 = 20; }

    // 报错
    function fn(){
        // 同一个作用域内不允许重复声明
        var num = 5;
        let num = 12;
    }

    // 允许
    function fn(num){
        {
            let num = 10;
        }
    }

const声明常量

常量:其值始终不会发生改变的量,也不允许改变。 const命令声明一个常量。对于const命令我们需要了解:

  • 其块级作用域的概念与let相同
  • 常量也不会提升
  • 一样有暂时性死区
  • 常量在声明时就必须赋值,不能先声明再赋值。
	const num = 10;
    num = 20;// TypeError: Assignment to constant variable
    const num;// SyntaxError: Missing initializer in const declaration

const定义复合类型数据

对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。

	const obj = {
        name:'tom',
        age:20
    };
    obj.name = 'jack';// 成功

    obj = {// 失败,
        name:'chris',
        age:20
    }

在上面的代码中我们修改了obj.name是成功的,因为修改对象的属性值并不会直接修改对象在内存中的地址,而第二次为obj对象赋值的时候其实就是把一个新的对象赋值变量obj,这样的操作就是在修改obj中储存的地址值。

全局对象的属性

在ES6以前所有的全局变量其实都是绑定在全局对象window身上的,也就是说它们都是全局对象的属性。这其实是一种非常大的语言设计失误,然而当基调被定下之后就无法再修改了。 在ES6中的let和const命令声明的变量/常量都不再是绑定在window身上的属性了。

	var num = 10;
    window.num === num;// true
    let num2 = 20;
    window.num2 === num2;// false

变量的解构

ES6中关于变量的解构我们需要了解两种,它们分别是:

  • 数组解构
  • 对象解构

数组解构

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

	// 以前的做法
    let num1 = 10;
    let num2 = 20;
    let num3 = 30;
    // es6
    let [num1,num2,nu3] = [10,20,30];

上面的代码会按照变量和值在数组对应的位置进行赋值操作。 其实这种操作属于是“模式匹配”,只要等号两边的模式相同,就可以进行对应的赋值操作。

 	let [num1,[num2,num3]] = [10,[20,30]];
    num1;// 10
    num2;// 20
    num3;// 30

如果解构失败那么变量的值就会是undefined。这倒是符合JS语言的套路,变量只要没有值就是undefined。

	let [num1,num2] = [10];
    num1;// 10
    num2;// undefined

我们可以为数组解构指定默认值。

	let [num1,num2 = '20'] = [10];
    num1;// 10 
    num2;// undefined

我们需要注意的是,因为数组解构使用的严格相等,所以等号右半部分的值必须是undefined才会使用默认值。

对象解构

对象解构月数组解构最大的不同点就在于数组必须是按照顺序解构,而对象则不需要,对象的解构只要变量名和对象的属性名相同即可。

	let obj = {
        status:200,
        msg:'连接成功',
        ok:true
    };
    // 固定语法 {变量名}
    let {status} = obj;
    status;// 200

如果需要从对象中取出多个值,只要在变量名之间添加逗号即可。

	let {status,msg} = obj;
    status;// 200
    msg;// '连接成功'

嵌套对象解构。

	let person = {
        name:'tom',
        age:20,
        height:'180cm',
        hobby:['basketball','football','baseball']
    };

    let {name,hobby} = person;
    name;// 'tom'
    hobby;// ['basketball','football','baseball']

    // 当hobby作为匹配模式时
    let {name,hobby:[h1,h2,h3]} = person;
    name;// 'tom'
    h1;// basketball
    h2;// football
    h3;// baseball

注意:如果需要为已经声明过的变量进行解构赋值必须在语法上非常小心。

	 let num;
    { num } = { num : 10 };// 语法错误

上面的代码是不符合JS语言的语法规范的,因为JS会把"{num}"当成一个单独的代码块去执行,所以我们需要将这一整句话包含在一起执行才能能够正常的解构赋值。

	let num;
    ( { num } = { num : 10 } );// 将解构的代码放入小括号内,作为一个整体执行

字符串扩展

ES6中对字符串进行了很多的增强,其中对于我们平时学习和开发影响最大的就是:模板字符串。 在ES6之前我们写一个需要换行的字符串。

	let str = 'good morning!'+'<br>'+
    'what\'s your name?'+'<br>'+
    'i am jim green,and what is your name?'+'<br>'+
    'my name is hanmeimei!';

上面的代码因为JS语言不允许字符串换行的写法,所以我们只能使用拼接字符串的方式。 而在ES6中我们可以使用模板字符串的方式,而模板字符串会使得我们之前的复杂的写法变得非常简单。具体语法:将之前包含字符串的引号替换成" ` ",也就是数字1左边和tab按键上面的按键。

	let str = `good morning!<br>
    what\‘s your name?<br>
    i am jim green,and what is your name?<br>
    my name is hanmeimei!`;

在ES6之前我们需要在字符串写入变量或者一个表达式一般都是进行字符串的拼接操作。 这在ES6之后我们可以使用模板字符串来实现。

	// 字符串中写入变量
    let stuName = 'tom';
    console.log(`hello:${stuName}`);// 'hello:tom'

    // 字符串中写入表达式
    let num1 = 10;
    let num2 = 20;
    console.log(`${num1}+${num2}=${num1+num2}`);
	// '10+20=30'

上面的代码其实对我们编写代码逻辑并没有很明显的提升,但是对于代码的语法编写却是方便了很多。最后再为大家介绍一个字符串的**repeat()**方法

	'hello'.repeat(3);// 'hellohellohello'
    '哈哈'.repeat(2.9);// '哈哈' 自动取整
    '哈哈'.repeat(-3);// 报错

函数扩展

在ES6中关于函数的扩展我们可以聊很多,但是其中最重要的三部分如下:

  • 参数的默认值
  • rest参数(参数的扩展运算符)
  • 箭头函数

参数的默认值

在ES6中我们可以为函数的参数设置默认值。

	 // 在ES6之前我们如果希望函数的参数拥有默认值可以采用如下写法
    function fn(num1,num2){
        num1 = num1 || 10;
        num2 = num2 || 20;
        return num1+num2;
    }
    fn();// 30

    function fn( num1=10,num2=20 ){
        return num1+num2;
    }
    fn();// 30

rest参数(参数的扩展运算符)

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。

	function fn( ...vals ){
        console.log(vals);// [10, 20, 30]
        console.log(typeof vals);// object
        console.log(Array.isArray(vals));// true
    }
    fn(10,20,30);

从上面的代码中我们可以看出实际上rest参数是一个数组,该数组接收了所有的实参。 当然我们并不是需要所有的参数都是放进rest参数中的。

	 // 求和函数,前两个参数为必填,从第三个参数开始为选填
    // getSum(num1,num2[,num3...])
    function getSum( num1,num2,...nums ){
        let sum = num1+num2;
        for( let i in nums ){
            sum += nums[i]
        }
        return sum;
    }	

注意:rest参数之后不能有其他的参数,也就是说rest参数实际上只能出现在参数列表的最后一个位置。

	function fn(...vals,num1){ // 语法错误

    }

箭头函数

在ES6中函数最最大的改动与增强当属箭头函数了。 语法:使用"=>"来代替之前的function来定义函数。

	var f = v => v;
    // 等同于
    var f = function (v) {
      return v;
    };

    var f = () => 5;
    // 等同于
    var f = function () { return 5 };

    var sum = (num1, num2) => num1 + num2;
    // 等同于
    var sum = function(num1, num2) {
      return num1 + num2;
    };

在上面的代码中我们可以看出如果箭头函数不需要参数或者需要多个参数的时候我们需要一个圆括号包裹起来。 从语法上来看箭头函数比较简单,但是我们需要注意以下几点:

  • 函数内部的this指向了定义时的对象,而不是调用时的对象。
  • 不可以作为构造函数,也就是说不可以使用new命令。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点关于this关键字的用法最为重要。 箭头函数中的this是固定的,而且是指向了箭头函数定义时的作用域上下文。

	btn.onclick = ()=>{
            console.log(this);// window
        };
        btn.onclick = function(){
            console.log(this);// <inpur type="button" value="按钮" id="btn">
        };

set集合

在ES6中提供了一个新的数据集合:set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

	let arr = [1,2,3,4,1,2,3,4];
    let s1 = new Set();
    // 将数组中的值添加到set集合中
    arr.forEach(val=>{
        s1.add(val);
    });
    // 遍历set集合中的数据
    for(let i of s1){
        console.log(i);
   }

Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

	// 将数组放入set
    let arr = [1,2,3,4,5];
    let s1 = new Set(arr);

    // 将类数组对象放入set
    let lis = document.getElementsByTagName('li');
    let s2 = new Set(lis);

因为set集合所具有的元素唯一的特点我们利用set集合实现数组去重的需求。

	let arr1 = ['hello','js','hello',10,20,10];
    let arr2 = [];
    let s1 = new Set();
    arr1.forEach(val=>{
        s1.add(val);
    });
   for(let i of s1){
        arr2.push(i);
   }
   console.log(arr2);// ["hello", "js", 10, 20]

Set集合的属性和方法

  • size:返回Set实例的成员总数,类似于数组的length属性。
  • add():添加某个值,返回 Set 结构本身。
  • delete():删除某个值,返回一个布尔值,表示删除是否成功。
  • has():返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值(谨慎使用)。
 let arr = [1,2,3,4,5];
    let s1 = new Set(arr);

    s1.size;// 5

    s1.add('hello').add('world');
    s1;//  {1, 2, 3, 4, 5, 'hello', 'world'}

    s1.delete('hello');// true
    s1.delete('js');// false
    s1;//  {1, 2, 3, 4, 5, 'world'}

    s1.has('hello');// false
    s1.has('world')// true

    s1.clear();
    s1;// Set(0) {}

Set集合的遍历

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

keys方法、values方法、entries方法返回的都是遍历器对象。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

	let set = new Set(['red', 'green', 'blue']);

    for (let item of set.keys()) {
      console.log(item);
    }
    // red
    // green
    // blue

    for (let item of set.values()) {
      console.log(item);
    }
    // red
    // green
    // blue

    for (let item of set.entries()) {
      console.log(item);
    }
    // ["red", "red"]
    // ["green", "green"]
    // ["blue", "blue"]

map集合

在ES6中为我们提供了一个新的数据解构:map。

	let arr = ['basketball','swiming','computer','TV'];
    let map = new Map();

    map.set(arr=>'hobby');

class类

在ES6中新增了一个构造函数的语法糖:class,在以前我们只能模拟类的定义。

	 function Student(){
        // 学生的构造函数,模拟学生类
    }
    new Student();// 创建一个学生对象

ES6为我们提供了相似于JAVA中类的写法。

// 定义学生类
    class Student{
        // 学生类的构造函数
        constructor(){
        }
    }