ES6 新特性知识点总结

Posted YuLong~W

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES6 新特性知识点总结相关的知识,希望对你有一定的参考价值。

ES6

ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。


ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。

每一次标准的诞生都意味着语言的完善,功能的加强。javascript语言本身也有一些令人不满意的地方。

  • 变量提升特性增加了程序运行时的不可预测性
  • 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码

let及const

1、let: ES6中新增的用于声明变量的关键字。let声明的变量只在所处于的块级有效

是ES6新增的声明格式,用于补全ES5标准中var声明变量的不足:在JavaScript中用 ‘var’ 来声明变量会出现变量提升的情况,即通过"var"声明的变量系统都会把声明隐式的升至顶部,这样的特性往往会让刚接触JavaScript及习惯其他语言的开发人员不适应,导致程序出现问题。

注意: 使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。

  • ES5中定义作用域有两种:全局作用域和函数作用域

  • ES6中新增了块级作用域,用'{ }'表示。块级作用域用于声明作用域之外无法访问的变量,主要有两种:

    • 函数内部块级作用域:

      function test() {
        let a = 20
      }
      test()
      console.log(a) // a is not defined
      
    • 在字符{ }之间的区域:

      {
          let a = 10
      }
      console.log(a) // a is not defined
      

例题:

 var arr = [];
 for (var i = 0; i < 2; i++) {
     arr[i] = function () {
         console.log(i);   //当i的值为2时,不满足条件,才会跳出循环,输出2
     }
 }
 arr[0](); // 2
 arr[1](); // 2


关键点在于: 变量i是全局的,函数执行时输出的都是全局作用域下的i值


 let arr = [];
 for (let i = 0; i < 2; i++) {
     arr[i] = function () {
         console.log(i); 
     }
 }  //循环结束后产生了两个块级作用域,且产生的两个i处于不同的块级作用域中,互不影响
 arr[0](); //0
 arr[1](); //1


关键点在于: 每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值


2、const: 使用const声明的是常量,常量的值不能通过重新赋值来改变,并且不能重新声明,所以每次通过const来声明的常量必须进行初始化

  • 具有块级作用域

     if (true) { 
         const a = 10;
     }
    console.log(a) // a is not defined
    
  • 声明常量时必须赋值

    const PI; // Missing initializer in const declaration
    
  • 常量赋值后,值不能修改

    const PI = 3.14;
    PI = 100; // Assignment to constant variable. 
    
     const arry = [100, 200];
     arry[0] = 'a'; arry[1] = 'b';
     console.log(arry); // ['a', 'b']; 
     arry = ['a', 'b']; // Assignment to constant variable.
    
  • 如果声明的是对象,可以修改对象的属性值,但不允许修改已经声明的对象

    const obj = {
        name: '张三',
        age: 20
    }
    //属性值是可以直接修改的
    obj.name = '李四'
    console.log(obj)
    
    //对象直接修改会报错
    obj = {} // Assignment to constant variable.
    
  • 如果让对象属性不能修改,可以借助 Object.freeze函数冻结对象

    const obj = {
        name: '张三',
        age: 20
    }
    Object.freeze(obj); //冻结对象
    obj.name = "李四"; //修改属性
    console.log(obj); //结果仍然是{name:'张三',age:20}
    
  • 通过Object.freeze冻结对象需要注意的是:不能冻结多层对象

    const obj = {
        name: '张三',
        age: 20,
        family: {
            father: {
                name: '张安',
                age: 48
            }
        }
    }
    Object.freeze(obj); //冻结对象
    obj.family.father.age = 50; //family对象里的属性不能被冻结
    console.log(obj); //对象age的值被改变
    
  • 解决多层冻结问题可以通过封装一个deepFreeze函数来实现:

    const obj = {
        name: '张三',
        age: 20,
        family: {
            father: {
                name: '张安',
                age: 48
            }
        }
    }
    function deepFreeze(obj){
        Object.freeze(obj);
        for(let key in obj){
            if(obj.hasOwnProperty(key) && typeof obj[key] === 'object'){
                deepFreeze(obj[key])
            }
        }
    }
    
    deepFreeze(obj); //冻结对象
    obj.family.father.age = 50; //修改对象属性的值
    console.log(obj); //对象age的值未被改变
    

3、临时死区:

let和const都是块级标识符,所以let和const都是在当前代码块内有效,常量不存在变量提升的情况。

但是通过let和const声明的常量,会放在 临时死区(temporal dead zone),通过下面代码可以看出:

{
   console.log(typeof a);
   let a = 10; 
   // 报错:ReferenceError: Cannot access 'a' before initialization
}

即使通过安全的typeof操作符也会报错,原因是JavaScript引擎在扫描代码变量时,要么会把变量提升至顶部(遇到 var 声明),要么会把变量放在临时死区(遇到 letconst 声明)。

因此,这里通过let声明的’a’变量会被放在临时死区,所以在声明之前打印就会报错

可复习变量提升相关知识:JavaScript 作用域(链)、预解析、闭包函数

4、循环中let和const的使用:

(1)在ES5标准中,for循环中的循环变量都是通过var来声明的,由于var没有独立的作用域,导致在循环中创建函数时会出现结果和思路不一致的情况,如下:

let funArr = []; //该数组中存放的是函数
for(var i=0;i<5;i++) {
    funArr.push(function(){
          console.log(i)
    })
}
funArr.forEach(item=> {
    item()
})
//分行打印了5 5 5 5 5

循环结果不是预想的0,1,2,3,4,而是5个5,这是因为var声明在循环中作用域共用,并且会把i保存在全局作用域中。


(2)要解决循环中保存函数的问题,可以利用 闭包 创建独立的作用域,代码如下:

let funArr = [];
for(var i=0;i<5;i++) {
    (
        function(i) {
            funArr.push(function(){
                console.log(i)
            })
        }
    )(i) 
}
funArr.forEach(item=> {
    item()
})
//分行打印 0 1 2 3 4

这样通过自执行函数就可以解决循环中创建函数的问题。


(3)利用ES6中 let 和 const 提供的 块级作用域 可以让代码更简洁:

let funArr = [];
for(let i=0;i<5;i++) {
    funArr.push(function(){
        console.log(i)
    })
}
funArr.forEach(item=> {
    item()
})
//分行打印 0 1 2 3 4

(4)在 for-in或for-of循环 中使用const时,方法与let一致:

let obj = {
    name: '张三',
    age: 20
}
for(const i in obj){
   console.log(i)  //输出name、age
}

let arr = ['张三','李四','王五']
for(const value of arr){
    console.log(value) //输出张三、李四、王五
}

5、let、const、var 的区别:

  1. 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
  2. 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
  3. 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值

解构赋值

ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。

1、数组的解构:

(1) 赋值多个变量

在ES5标准中赋值多个变量采用的方法是:

var a = 10;
var b = 20;
var c = 30;

ES6提供了更简洁的解构赋值来实现上述变量的定义:

 let [a,b,c] = [10,20,30];

等号右边的值会按照顺序依次赋值给左边的变量。

(2)非一一对应关系的赋值

 let [a,b] = [10,20,30]
 console.log(a,b); //输出10,20

let [a,b,c] = [10,20]
console.log(a); //10
console.log(b); //20
console.log(c); //undefined

(3)也可以通过'...'把特定的元素放在变量里

let [a,...arr] = [10,20,30]
console.log(a); //10
console.log(arr); //20,30

(4)可以通过解构赋值来互换变量

let a = 10;
let b = 20;
[a,b] = [b,a];
console.log(a,b)

2、对象的解构:

(1)对象解构的写法与数组解构类似

let obj = {
    name: '张三',
    age: 20,
    height: '178com'
}

let { name,age,height } = obj; //变量的名称必须和对象的属性名相同
console.log(name,age,height);

(2)也可以解构多层对象

let person = {
    name: '张三',
    age: 20,
    family: {
        father: '张武',
        mother: '李燕'
    }
}
let { name,age,family: {father,mother }} = person
console.log(name,father)

(3)在解构对象时也可以自定义变量名称:

let obj = {
    name: '张三',
    age: 20
}
let { name:myname,age:myage } = obj;
console.log(myname,myage);

3、解构的默认值和参数的解构:

(1)不管是数组的解构赋值,还是对象的解构赋值都可以添加默认参数。如下:

let obj = {
    name: '李四',
    age: 20
}

let { name,age,height="178com" } = obj;
console.log(height); //178com

(2)在函数参数中使用解构,参数解构也可以给默认参数

function fun({name,age,height="178com"} = {}){
     console.log(name,age); //张三,20
}
let obj = {
   name: '张三',
   age: 20
}
fun(obj)

模板字符串

ES5标准中一般输出模板是通过字符串拼接的方式进行的。

在ES6中可以通过模板字符串简化字符串的拼接,模板字符串通过反引号来表示'``',如果要嵌入变量通过'${ 变量名 }'来实现:

let arr = [
    {
        name: '张三',
        age: 20
    },
    {
        name: '李四',
        age: 23
    },
    {
        name: '王五',
        age: 25
    }
]
let str = "";
for(let i=0;i<arr.length;i++){
    str += `姓名是:${ arr[i].name },年龄是:${ arr[i].age }`;
}
console.log(str)
//姓名是:张三,年龄是:20姓名是:李四,年龄是:23姓名是:王五,年龄是:25

Symbol类型

ES5中提供了 6种数据类型 分别是:undefined、null、boolean、string、number、object

ES6中新增了一种数据类型Symbol表示唯一的值,每个创建的Symbol都是唯一的,这样在实际运用中可以创建一些唯一的属性及定义私有变量。例如:

let s1=Symbol; //直接创建
let s2=Symbol('s2'); //传入字符串创建
let s3=Symbol('s2');

console.log(s1); //[Function: Symbol]
console.log(s2); //Symbol(s2)
console.log(s2===s3) //false

1、目前前端项目都会采用模块化构建,为了防止对象属性名被覆盖,可以通过symbol来定义属性名。例如:

//a.js
const NAME = Symbol('name')
let obj = {
            [NAME]:'张三',
            age: 20
}
module.export = obj;

//b.js
import obj from './a.js'
const NAME = Symbol('name');
obj[NAME] = '李四';
console.log(obj); //{age:20,Symbol():'张三',Symbol():'李四'}

2、利用Symbol作为属性名,属性名不会被 Object.keys()Object.getOwnPropertyNames()for…in循环返回。例如:

let obj={
    [Symbol('name')]:'张三',
    age:40,
    height:'178cm'
}

for(let key in obj){  //for...in循环不能返回Symbol属性
    console.log(key);  //age  40
    console.log(obj[key]);  //height   178cm
}

let keys=Object.keys(obj); //提取obj对象所有可枚举属性名
console.log(keys); //[ 'age', 'height' ]

let name=Object.getOwnPropertyNames(obj);//获取obj对象所有属性名
console.log(name); //[ 'age', 'height' ]

3、可以在类里利用Symbol来 定义私有属性及方法例如:

let People=(
    function(){
        let name=Symbol('name');
        class p{
            constructor(yourname){
                this[name]=yourname;
            }
            sayName(){
                console.log(`姓名:${this[name]}`)
            }
        }
        return p
    }
)();


//对上述代码的拆分:
//原理是:闭包+匿名函数 
// function fun(){
//     let name=Symbol('name');
//         class p{
//             constructor(yourname){
//                 this[name]=yourname;
//             }
//             sayName(){
//                 console.log(`姓名:${this[name]}`)
//             }
//         }
//         return p
// }
// let People=fun();

let p1=new People('张三'); //调用构造方法
console.log(p1[Symbol('name')]); //undefined
p1.sayName();

Set和Map数据结构

1、Set

Set类似于数组,但是它里面每一项的值是唯一的,没有重复的值,set是一个构造函数,用来生成set的数据结构

let s = new Set();
let arr = [2, 3, 5, 4, 5, 2, 2];
arr .forEach(item => arr.add(item)); //向set添加重复的值
for (let i of s) {
  console.log(i);
}
// 2 3 5 4 结果set不会添加重复的值

4种操作方法:

  • add(value):添加某个值,返回 Set 结构本身
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功
  • has(value):返回一个布尔值,表示该值是否为 Set 的成员
  • clear():清除所有成员,没有返回值

4种遍历方法:可用于遍历成员

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

注意: 由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

实现并集(Union)、交集(Intersect)、差集(Difference):

let a = new Set([1, 2, 3]);
let b = new Set([以上是关于ES6 新特性知识点总结的主要内容,如果未能解决你的问题,请参考以下文章

总结常见的ES6新语法特性

总结常见的ES6新语法特性。

ES6新特性总结-julia

ES6中的一些新特性

es6/es7/es8常用新特性总结(实用)文末有彩蛋

Atitit js es5 es6新特性 attilax总结