es6_es6 阮一峰 - CSDN
es6 订阅
ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015)。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言 [1]  。另外,一些情况下ES6也泛指ES2015及之后的新增特性,虽然之后的版本应当称为ES7、ES8等。 展开全文
ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015)。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言 [1]  。另外,一些情况下ES6也泛指ES2015及之后的新增特性,虽然之后的版本应当称为ES7、ES8等。
信息
简    称
ES6
类    型
前端语言
通过日期
2015年6月
性    质
JavaScript语言的标准
正式名字
ECMAScript 2015(ES2015)
外文名
ECMAScript 6
ECMAScript 6发展历史
2000年,ECMAScript 4.0开始酝酿。这个版本最后没有通过,但是它的大部分内容被ECMAScript6继承了。因此,ECMAScript6制定的起点其实是2000年。2007年10月,ECMAScript 4.0草案发布,本来预计2008年8月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以Yahoo、Microsoft、Google为首的大公司,反对JavaScript的大幅升级,主张小幅改动;以JavaScript创造者Brendan Eich为首的Mozilla公司,则坚持当前的草案。2008年7月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA开会决定,中止ECMAScript 4.0的开发,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1就改名为ECMAScript 5。2009年12月,ECMAScript 5.0正式发布。Harmony项目则一分为二,一些较为可行的设想定名为 JavaScript.next继续开发,后来演变成ECMAScript 6;一些不是很成熟的设想,则被视为JavaScript.next.next,在更远的将来再考虑推出。2011年,ECMAScript 5.1发布后开始6.0版的制定。2013年3月,ECMAScript 6草案冻结,不再添加新功能。新的功能设想将被放到ECMAScript 7。2013年12月,ECMAScript 6草案发布。然后是12个月的讨论期,听取各方反馈。由于这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。标准委员会最终决定,标准在每年的6月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的6月份,草案就自然变成了新一年的版本。2015年6月,ECMAScript 6(ES6)正式通过,成为国际标准,正式名称是“ECMAScript 2015”(简称ES2015)。2016年6月,小幅修订的“ECMAScript 2016”(简称ES2016或ES7)标准发布,相当于ES6.1版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准 [1]  。
收起全文
精华内容
参与话题
  • 你必须知道的ES6语法

    2020-03-02 10:26:29
    初步掌握 ES6 中常用和核心的语法,并能运用到实际开发中。 通过每节一个语法的方式,详细解析ES6中重要语法的使用和应用场景,如解构赋值,箭头函数等热门语法,课程内容短小,侧重实战和学员动手能力,通过本...
  • ES6简介 1.什么是ES6? ES的全称是ECMAScript,它是由ECMA国际标准化组织制定的一项脚本语言的标准化规范。 年份 版本 2015年6月 ES2015 2016年6月 ES2016 2017年6月 ES2017 2018年6月 ES2018 … ...

    ES6简介

    1.什么是ES6?

    • ES的全称是ECMAScript,它是由ECMA国际标准化组织制定的一项脚本语言的标准化规范。
    年份 版本
    2015年6月 ES2015
    2016年6月 ES2016
    2017年6月 ES2017
    2018年6月 ES2018
    • ES6实际上是一个泛指,泛指ES2015及后续的版本。(每年的6月更新一次,区别以年份命名,如:ES2015)。

    2.为什么要使用ES6?

    • 因为JavaScript本身存在的缺陷,如:

      (1)变量提升特性增加了程序运行时的不可预测性。

      (2)语法过于松散,实现相同的功能,不同的人可能会写出不同的代码。

    ES6的新增语法

    1.let

    • let是ES6中新增的用于声明变量的关键字。

    (1)let声明的变量只在所处的块级有效。

    if(true){
      let a = 10;
    }
    console.log(a);		//报错,a is not defined
    

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

    • let可以防止循环变量变成全局变量。
    for(var i = 0; i<2; i++){
      
    }
    console.log(i);		//输出结果为:2
    
    for(let i = 0; i<2; i++){		//虽然这个i没有写在大括号中,但是它却和这个for循环绑定在一起,块级作用域外是访问不到这个变量的
      
    }
    console.log(i);		//输出结果为:报错,i is not defined
    
    

    (2)不存在变量提升。

    console.log(a);		//报错,a is not defined
    let a = 20;
    

    (3)暂时性死区(在块级作用域内使用let声明的变量会被整体绑定在这个块级作用域内,不受外部代码影响)。

    //例子1:
    var num = 10;
    if(true){
      console.log(num);		
      let num =20;				//因为在块级作用域内使用了let关键字声明变量,所以这个变量和整个块级作用域进行了绑定,所以执行console.log(num)时,会报num没有定义的错误
    }
    //例子2:
    var tmp = 123;
    if(true){
      tmp = 'abc';		//因为该块级作用域使用了let tmp;所以tmp和该块级作用域整体进行了绑定,没先声明就赋值就会出现错误(因为没有变量提升的功能)。如果只有tmp = 'abc';那么就会隐式声明,该tmp变量会成为全局变量。如果只有var tmo =123;和tmp = 'abc';那么就会修改tmp的值。
      let tmp;
    }
    //拓展:
    function test(){
      console.log(a);		//会报错,因为a = 3存在隐式声明,而隐式声明没有变量提升的功能,所以会报错
      
      a = 3;
      
    }
    
    
    
    
    

    (4)不可重复声明。

    • 简单面试题:
    var arr = [];
    for(var i = 0; i < 2; i++){
      arr[i] = function(){
        console.log(i);
      }
    }
    arr[0]();		//输出结果为:2
    arr[1]();		//输出结果为:2
    

    在这里插入图片描述

    • 解题关键:变量i是全局的,函数执行时输出的都是全局作用域下的值。
    var arr = [];
    for(let i = 0; i < 2; i++){
      arr[i] = function(){
        console.log(i);
      }
    }
    arr[0]();		//输出结果为:0
    arr[1]();		//输出结果为:1
    

    在这里插入图片描述

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

    • 注意:

    let num = 10;
    if (true) {
      console.log(num);
    }		//如果在大括号内没有用let声明变量,但大括号外面用let声明了该变量,那么大括号是可以使用大括号外面声明的该变量的(跟var一样)
    
    

    2.const

    • 作用:声明常量,常量就是值(内存地址)不能变化的量。

    (1)具有块级作用域。

    if(true){
      const a = 10;
    }
    console.log(a);		//报错,a is not defined
    

    (2)声明常量时必须赋值(否则就会报错)。

    const PI;		//报错,Missing initializeri in const declaration
    

    (3)常量赋值后,值不能修改。

    const PI = 3.14;
    PI = 100;		//报错,Assignment to constant variable
    
    const ary = [100,200];
    arr[0] = 'a';
    arr[1] = 'b';
    console.log(arr);		//输出结果为:['a','b']
    arr = ['a','b'];		//报错,Assignment to constant variable
    

    (4)不可重复声明。

    3.let、const、var的区别

    • 使用var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
    • 使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
    • 使用const声明的是常量,在后面出现的代码中不能再修改该常量的值。
    var let Const
    函数级作用域 块级作用域 块级作用域
    变量提升 不存在变量提升 不存在变量提升
    值可更改 值可更改 值不可更改
    • 如果存储的值不需要变化,推荐使用const。比如:函数的定义、对象、数学公式中的一些恒定不变的值。

    4.解构赋值

    • ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
    • 解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数),那么解构赋值拷贝的是这个值的引用(即地址),而不是这个值的副本。
    (1)数组解构
    • 数组解构允许按照一一对应的关系从数组中提取值,然后将值赋值给变量。
    let [a,b,c] = [1,2,3];
    console.log(a);		//输出结果为:a
    console.log(b);		//输出结果为:b
    console.log(c);		//输出结果为:c
    
    • 如果解构不成功,变量的值为undefined。
    let [a] = [];				//a的值为undefined
    let [a,b] = [1];		//a的值为1,b的值为undefined
    
    (2)对象解构
    • 对象解构允许使用变量的名字匹配对象的属性,匹配成功则将对象属性的值赋值给变量。
    let person = {name:'andy', age:22};
    let {name,age} = person;
    console.log(name);		//输出结果为:'andy'
    console.log(age);			//输出结果为:22
    
    let person = {name:'andy', age:22};
    let { name:myName, age:myAge} = person;		//myName和myAge属于别名
    console.log(myName);		//输出结果为:'andy'
    console.log(myAge);			//输出结果为:22
    

    5.箭头函数

    • 箭头函数是ES6新增的定义函数的方式。
    () => {}
    const fn = () => {}		//通常将箭头函数赋值个一个变量,变量名字就是箭头函数的名字
    
    • 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号。
    //有return返回值的情况:
    //传统的函数:
    function sum(num1,num2){
      reutrn num1 + num2;
    }
    //箭头函数:
    const sum = (num1,num2) => num1 + num2;
    
    //没有return返回值的情况:
    //传统的函数:
    function fn(){
      console.log('Hello!');
    }
    //箭头函数:
    const fn = () => console.log('Hello!');
    
    • 如果形参只有一个,则可以省略小括号。
    //传统的函数:
    function fn(v){
      return v;
    }
    //箭头函数:
    const fn = v => v;
    
    • 箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this。
    const obj = { name: 'andy'};
    function fn(){
      console.log(this);		//输出结果为:{ name: 'andy'}
      return () => {
        console.log(this);	//输出结果为:{ name: 'andy'}
      }
    }
    const resFn = fn.call(obj);
    resFn();		//尽管调用的是window下的resFn函数,但因为它是箭头函数,它没有自己的this关键字,如果使用this,则指向箭头函数定义位置的this。由于箭头函数定义位置的this已经被修改为obj,所以此时箭头函数的this就指向obj对象。
    
    
    
    • 箭头函数面试题:
    var obj = {
      age: 22,
      say: () => {
        alert(this.age);	//该this定义在obj对象内,但因为obj对象不能产生作用域,所以箭头函数中的this指向的是window,但由于window下面没有定义age变量,所以输出结果为undefined。	
      }
    }
    obj.say();
    
    

    6.剩余参数

    • 剩余参数允许将一个不定数量的参数表示为一个数组。
    function sum(first,...args){
      console.log(first);	//10
      console.log(args);	//[20,30]
    }
    sum(10,20,30);
    //注意:箭头函数使用不了argument
    
    • 剩余参数和解构配合使用。
    let students = ['andy','Mary','lebron'];
    let [s1,...s2] = students;
    console.log(s1);		//输出结果为:'andy'
    console.log(s2);		//输出结果为:['Mary','lebron']
    

    ES6的内置对象扩展

    1.Array的扩展方法

    (1)扩展运算符(展开语法)
    • 扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。(扩展运算符后面可以跟表达式)

    注意:用扩展运算符对数组或者对象进行拷贝时,只能扩展和深拷贝第一层的值,对于第二层极其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或者数组仍然引用的是同一个地址,其中一方改变,另一方也跟着改变。

    let ary = [1,2,3];
    ...ary;		//1,2,3
    console.log(...ary);	//1 2 3
    console.log(1,2,3);		//1 2 3
    
    let n = {a:2,b:4};
    let m = {...n};
    m		//{a:2,b:4}
    //深拷贝
    n.a = 6;
    console.log(n);		//{a:6,b:4}
    console.log(m);		//{a:2,b:4}
    
    • 扩展运算符可以应用于合并数组。(注意:如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉)
    //方法一:
    let ary1 = [1,2,3];
    let ary2 = [4,5,6];
    let ary3 = [...ary1, ...ary2];	//[1,2,3,4,5,6]
    //方法二:
    ary1.push(...ary2);		//[1,2,3,4,5,6]
    
    //用户自定义的属性放在扩展运算符后面
    const a = {
      x: 5,
      y: 6,
      n: 7,
      m: 8
    }
    let ary = {..a,x:1,y:2};
    console.log(ary);		//输出结果为:{x:1,y:2,n:7,m:8}
    
    • 扩展运算符可以将类数组(伪数组)或可遍历对象转换为真正的数组。
    let oDivs = document.getElementsByTagName('div');
    oDivs = [...oDivs];
    
    (2)构造函数方法:Array.from()
    • 将类数组(伪数组)或可遍历对象转换为真正的数组。
    let arrayLike = {
      '0': 'a',
      '1': 'b',
      '2': 'c',
      'length': 3
    };
    let arr = Array.from(arrayLike);		//['a','b','c']
    
    • 该方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
    let arrayLike = {
      '0': 1,
      '2': 2,
      'length': 2
    };
    let newAry = Array.from(arrayLike,item => item * 2);	//[2,4]
    
    (3)实例方法:find()
    • 用于找出第一个符合条件的数组成员。如果找到就返回该元素值;如果没有找到就返回undefined。
    let ary = [{
      id: 1,
      name: 'andy'
    },{
      id: 2,
      name: 'Mary'
    }];
    let target = ary.find(item => item.id == 2);	//item.id==2就是循环体,就是查找条件,return和大括号省略了
    console.log(target);		//输出结果为:{id: 2, name: 'Mary'}
    
    
    (4)实例方法:findIndex()
    • 用于找出第一个符合条件的数组成员的位置,如果没有找到就返回-1。
    let ary = [1,5,10,15];
    let index = ary.findIndex(value => value > 9);
    console.log(index);		//输出结果为:2
    
    (5)实例方法:includes()
    • 表示某个数组是否包含给定的值,返回布尔值。
    let a = [1,2,3].includes(2);		
    console.log(a);		//true
    let b = [1,2,3].includes(4);
    console.log(b);		//false
    

    2.String的扩展方法

    (1)模版字符串
    • 模版字符串是ES6新增的创建字符串的方式,使用反引号定义。
    let name = `andy`;
    
    • 模版字符串中可以解析变量。
    let name = 'andy';
    let sayHello = `hello,my name is ${name}`;	
    console.log(sayHello);		//输出结果为:hello,my name is andy
    
    • 模版字符串中可以换行。
    let result = {
      name: 'andy',
      age: 22
    };
    let html = `
    		<div>
    				<span>${result.name}</span>
    				<span>${result.age}</span>
    		</div>
    `;
    console.log(html);		//输出结果也是换行的
    
    • 在模版字符串中可以调用函数。
    const fn = () => {
      return '我是andy!'
    }
    let html = `你好,${fn()}`;
    console.log(html);		//输出结果为:你好,我是andy!
    
    (2)实例方法:startsWith()和endsWith()
    • startsWith():表示参数字符串是否在原字符串的头部,返回布尔值。
    • endWith():表示参数字符串是否在原字符串的尾部,返回布尔值。
    let str = 'Hello world!';
    let a = str.startsWith('Hello');
    console.log(a);		//输出结果为:true
    let b = str.endsWith('!');
    console.log(b);		//输出结果为:true
    
    (3)实例方法:repeat()
    • repeat()方法表示将字符串重复n次,返回一个新字符串。
    let a = 'x'.repeat(4);
    console.log(a);		//输出结果为:'xxxx'
    let b = 'hello'.repeat(2);
    console.log(b);		//输出结果为:'hellohello'
    

    3.Set数据结构

    • ES6提供了新的数据结构Set。它类似于数组,也是用来存储数据的,但它的成员的值都是唯一的,没有重复的值。
    • Set本身是一个构造函数,实例化该构造函数就是生成Set数据结构。
    const s = new Set();
    
    • Set函数可以接受一个数组作为参数,用来初始化。
    const set = new Set([1,2,3,4,5]);
    
    • Set数据结构有size属性,可以返回Set数据结构中存储了多少数据。
    const s = new Set([1,2,3]);
    console.log(s.size);		//输出结果为:3
    
    • 通过Set数据结构,可以实现数组去重。
    const a = ['a','a','b','b'];
    const s = new Set(a);
    var ary = [...s];
    console.log(ary);		//输出结果为:['a','b']
    
    • 实例方法:

      (1)add(value):添加某个值,返回Set结构本身。

      (2)delete(value):删除某个值,返回一个布尔值表示删除是否成功。

      (3)has(value):返回一个布尔值,表示该值是否为Set的成员。

      (4)clear():清除所有成员,没有返回值。

      const s = new Set();
      s.add(1).add(2).add(3);
      console.log(s);		//输出结果为:3
      const a = s.delete(3);
      console.log(a);		//输出结果为:true
      cosnt b = s.has(2);
      console.log(b);		//输出结果为:true
      s.clear();
      console.log(s.size);	//输出结果为:0
      
    • 遍历:

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

      const s = new Set(['a','b','c']);
      s.forEach(value => console.log(value));		//依次输出a、b和c
      

    注意:forEach()方法无法在所有元素都传递给调用的函数之前终止遍历,所以没有返回值,不能使用return。


    微信公众号也会定期更新,觉得文章写得还可以的,可以加个关注!点个赞!谢谢!

    在这里插入图片描述

    展开全文
  • javascript学习笔记(ES6最常用语法)

    千次阅读 2018-01-21 23:59:30
    ES6最常用语法

    ES6常用特性

    let,const

    ES5只有全局作用域和函数作用域,没有块级作用域,let则为JavaScript新增了块级作用域,用它所声明的变量,只在let命令所在的代码块内有效。

    {
        {
            let a = 1;
            let b = 1;
            console.log(a);// 1
            console.log(b);// 1
        }
        console.log(a);// a is not defined
        console.log(b);// 1
        for (let i = 0; i < 10; i++) {
            //...
        }
        console.log(i); // i is not defined
        for (var j = 0; j < 10; j++) {
            //...
        }
        console.log(j);//10
    }

    const也用来声明常量, 一旦声明,常量的值就不能改变。

    const PI = 3.1415926
    PI = 3.14;// Assignment to constant variable

    默认参数

    ES6之前处理默认参数,基本使用以下方法

    function toDecimal(number, precision) {
        var precision = precision||2;
        var temp = 1;
        for (let i = 0; i < precision; i++) {
            temp = temp * 10;
        }
        return Math.round(number * temp)/temp;
    }

    ES6则可以采用如下写法

    function toDecimal(number, precision = 2) {
        var temp = 1;
        for (let i = 0; i < precision; i++) {
            temp = temp * 10;
        }
        return Math.round(number * temp)/temp;
    }

    模板对象

    ES6之前拼接字符串对象,一般使用如下方式

    var firstName = 'Fei';
    var lastName = 'Zhang';
    console.log('your name is ' + firstName + ' ' + lastName + '.');

    ES6中可以使用${}的方式来做

    var firstName = 'Fei';
    var lastName = 'Zhang';
    console.log('your name is ${firstName} %{lastName}.');

    多行字符串

    ES6之前字符串换行,一般使用\n

    var strTemp = 'abcdefg\n'+
        'hijklmn\n';
    console.log(strTemp);

    ES6只需要反引号即可

    var strTemp = ·abcdefg
        hijklmn·;
    console.log(strTemp);

    箭头函数=>

    =>可以是function写法简洁清晰很多

    function(i){ return i + 1; } // ES5
    (i) => i + 1; // ES6
    
    function(x, y) {
        x++;
        y--;
        return x + y;
    }
    (x, y) => {x++; y--; return x+y}
    

    当使用匿名函数时,不需要额外定义变量指向this

    // ES5
    var obj = this;
    foo(function(){
        obj.foo1();
    });
    // ES6
    foo(()=>{this.foo1();});

    解构(Destructuring)

    ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值。

    let cat = 'cat'
    let dog = 'dot'
    let zoo = {cat: cat, dog: dog} // es5
    let zoo1 = {cat, dog} // es6
    // 也可以反过来写
    let {cat1, dog1} = zoo;

    rest参数(…变量名)

    rest参数搭配的变量是一个数组,可以使用数组的一切操作

    function sum(...values){
        let temp = 0;
        for (var value of values){
            temp = temp+ value;
        }
        return temp;
    }
    展开全文
  • ES6笔记上(深入浅出ES6—阮一峰)

    万次阅读 多人点赞 2018-06-10 12:03:54
    在线转换 Babel 提供一个REPL在线编译器,可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。

    在线转换

    Babel 提供一个REPL在线编译器,可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。


    let和const命令

    let和var的区别

    var a = [];
    for (var i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 10

    上面代码中,变量i是var声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的function在运行时,会通过闭包读到这同一个变量i,导致最后输出的是最后一轮的i的值,也就是10。

    而如果使用let,声明的变量仅在块级作用域内有效,最后输出的是6。

    var a = [];
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
    

    上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

    另外,for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

    for (let i = 0; i < 3; i++) {
      let i = 'abc';
      console.log(i);
    }
    // abc
    // abc
    // abc

    上面代码输出了3次abc,这表明函数内部的变量i和外部的变量i是分离的。就是说i每循环一次 ,每次产生新的作用域 (块级作用域) 也就是把当前的j值保存下来

    不存在变量提升

    var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

    为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

    
    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;

    暂时性死区

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

    var tmp = 123;

    if (true) {
    tmp = ‘abc’; // ReferenceError
    let tmp;
    }
    上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

    ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

    总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

    ES6 规定暂时性死区和let、const语句不出现变量提升

    不允许重复声明

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

    // 报错
    function () {
      let a = 10;
      var a = 1;
    }
    
    // 报错
    function () {
      let a = 10;
      let a = 1;
    }
    

    因此,不能在函数内部重新声明参数。

    function func(arg) {
      let arg; // 报错
    }
    
    function func(arg) {
      {
        let arg; // 不报错
      }
    }

    块级作用域

    ES5 只有全局作用域和函数作用域,没有块级作用域

    第一种场景,内层变量可能会覆盖外层变量。

    var tmp = new Date();
    
    function f() {
      console.log(tmp);
      if (false) {
        var tmp = 'hello world';
      }
    }
    
    f(); // undefined

    上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

    第二种场景,用来计数的循环变量泄露为全局变量。

    var s = 'hello';
    
    for (var i = 0; i < s.length; i++) {
      console.log(s[i]);
    }
    
    console.log(i); // 5

    上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。


    ES6 的块级作用域

    • let实际上为 JavaScript 新增了块级作用域。
    function f1() {
      let n = 5;
      if (true) {
        let n = 10;
      }
      console.log(n); // 5
    }

    上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。

    ES6 允许块级作用域的任意嵌套。

    {{{{{let insane = 'Hello World'}}}}};

    上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。

    {{{{
      {let insane = 'Hello World'}
      console.log(insane); // 报错
    }}}};

    内层作用域可以定义外层作用域的同名变量。

    {{{{
      let insane = 'Hello World';
      {let insane = 'Hello World'}
    }}}};

    块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

    // IIFE 写法
    (function () {
      var tmp = ...;
      ...
    }());
    
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }

    块级作用域与函数声明

    
     function f() { console.log('I am outside!'); }
    
    (function () {
      if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());

    上面代码在 ES5 中运行,会得到“I am inside!”,ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。

    另外,还有一个需要注意的地方,考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

    
    
    // 函数声明语句
    {
      let a = 'secret';
      function f() {
        return a;
      }
    }
    
    // 函数表达式
    {
      let a = 'secret';
      let f = function () {
        return a;
      };
    }

    do 表达式

    本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。

    {
      let t = f();
      t = t * t + 1;
    }

    上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。

    do表达式,可以返回值。

    let x = do {
      let t = f();
      t * t + 1;
    };

    上面代码中,变量x会得到整个块级作用域的返回值。


    const 命令

    基本用法

    const声明一个只读的常量。一旦声明,常量的值就不能改变。

    const PI = 3.1415;
    PI // 3.1415
    
    PI = 3;
    // TypeError: Assignment to constant variable.

    上面代码表明改变常量的值会报错。

    const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    const foo;
    // SyntaxError: Missing initializer in const declaration

    上面代码表示,对于const来说,只声明不赋值,就会报错。

    const的作用域与let命令相同:只在声明所在的块级作用域内有效。

    if (true) {
      const MAX = 5;
    }
    MAX // Uncaught ReferenceError: MAX is not defined

    const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

    
    if (true) {
      console.log(MAX); // ReferenceError
      const MAX = 5;
    }

    上面代码在常量MAX声明之前就调用,结果报错。

    const声明的常量,也与let一样不可重复声明。

    var message = "Hello!";
    let age = 25;
    
    // 以下两行都会报错
    const message = "Goodbye!";
    const age = 30;

    常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

    const foo = {};
    // 为 foo 添加一个属性,可以成功
    foo.prop = 123;
    foo.prop // 123
    
    // 将 foo 指向另一个对象,就会报错
    foo = {}; // TypeError: "foo" is read-only

    下面是另一个例子。

    const a = [];
    a.push('Hello'); // 可执行
    a.length = 0;    // 可执行
    a = ['Dave'];    // 报错

    上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

    如果真的想将对象冻结,应该使用Object.freeze方法。

    const foo = Object.freeze({});
    // 常规模式时,下面一行不起作用;
    // 严格模式时,该行会报错
    foo.prop = 123;

    上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

    除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

    var constantize = (obj) => {
      Object.freeze(obj);
      Object.keys(obj).forEach( (key, i) => {
        if ( typeof obj[key] === 'object' ) {
          constantize( obj[key] );
        }
      });
    };

    ES6 声明变量的六种方法

    ES5 只有两种声明变量的方法:var命令和function命令。ES6除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有6种声明变量的方法。


    let arr = [1, 2, 3,4];
    let {0 : first, 1:second, [arr.length - 1] : last} = arr;
    console.log(first); // 1
    console.log(second); //
    console.log(last); // 3

    上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”,参见《对象的扩展》一章。


    字符串的解构赋值

    字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"

    类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

    let {length : len} = 'hello';
    len // 5

    变量的解构赋值

    用途

    (1)交换变量的值

    let x = 1;
    let y = 2;
    [x,y] = [y,x];
    console.log(y);  //x = 2,y = 1;

    这样的写法不仅简洁,而且易读,语义非常清晰。

    (2)从函数返回多个值
    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

    // 返回一个数组
      function example() {
      return [1, 2, 3];
      }
      let [a, b, c] = example();
      console.table([a,b,c]);

    结果:
    这里写图片描述

    (3)函数参数的定义
    解构赋值可以方便地将一组参数与变量名对应起来。

    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3]);
    
    // 参数是一组无次序的值
    function f({x, y, z}) { ... }
    f({z: 3, y: 2, x: 1});

    (4)提取JSON数据

    解构赋值对提取JSON对象中的数据,尤其有用。

    let jsonData = {
      id: 42,
      status: "OK",
      data: [867, 5309]
    };
    
    let { id, status, data: number } = jsonData;
    
    console.log(id, status, number);
    // 42, "OK", [867, 5309]

    上面代码可以快速提取 JSON 数据的值。

    (5)函数参数的默认值

    jQuery.ajax = function (url, {
      async = true,
      beforeSend = function () {},
      cache = true,
      complete = function () {},
      crossDomain = false,
      global = true,
      // ... more config
    }) {
      // ... do stuff
    };

    指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。

    (6)遍历Map结构
    任何部署了Iterator接口的对象,都可以用for…of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。

    var map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    
    for (let [key, value] of map) {
      console.log(key + " is " + value);
    }
    // first is hello
    // second is world

    如果只想获取键名,或者只想获取键值,可以写成下面这样。

    // 获取键名
    for (let [key] of map) {
      // ...
    }
    
    // 获取键值
    for (let [,value] of map) {
      // ...
    }

    这里说明下:Set 本身是一个构造函数,用来生成 Set 数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。

    例如:

    var s = new Set();
    [2,3,5,4,5,2,2].map(x => s.add(x))
    for (i of s) {console.log(i)}
    // 2 3 5 4

    注:add()方法,向Set 数据结构数据结构添加元素。

    (7)输入模块的指定方法
    加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

    const { SourceMapConsumer, SourceNode } = require("source-map");

    字符串的扩展

    codePointAt()

    codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。

    function is32Bit(c) {
      return c.codePointAt(0) > 0xFFFF;
    }
    
    is32Bit("") // true
    is32Bit("a") // false

    String.fromCodePoint()

    ES5提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符(Unicode编号大于0xFFFF)。

    String.fromCharCode(0x20BB7)
    // "ஷ"

    ES6提供了String.fromCodePoint方法,可以识别0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。

    String.fromCodePoint(0x20BB7)
    // ""
    String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
    // true

    注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。


    字符串的遍历器接口

    用for…of循环遍历

    代码如下:

     for(let codePointAt of 'hicai'){
         console.log(codePointAt);
      }  
    

    结果:
    这里写图片描述

    除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点

    var text = String.fromCodePoint(0x20BB7);
    
    for (let i = 0; i < text.length; i++) {
      console.log(text[i]);
    }
    // " "
    // " "
    
    for (let i of text) {
      console.log(i);
    }
    // ""

    上面代码中,字符串text只有一个字符,但是for循环会认为它包含两个字符(都不可打印),而
    for…of循环会正确识别出这一个字符。


    at()

    ES5对字符串提供charAt方法,但不能识别码点大于0xFFFF的字符。
    ES6的at方法,可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。

    'abc'.at(0) // "a"
    ''.at(0) // ""

    normalize()

    比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。

    这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别。

    '\u01D1'==='\u004F\u030C' //false
    '\u01D1'.length // 1
    '\u004F\u030C'.length // 2

    ES6提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为Unicode正规化。

    '\u01D1'.normalize() === '\u004F\u030C'.normalize()
    // true

    includes(), startsWith(), endsWith()

    ES5中的indexOf方法,用来检索字符串中指定字符串出现的位置 而ES6又提供了三种新方法。

    includes():返回布尔值,表示是否找到了参数字符串。
    startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
    endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。

    var s = 'Hello world!';
    s.startsWith('Hello') // true
    s.endsWith('!') // true
    s.includes('o') // true
    

    这三个方法都支持第二个参数,表示开始搜索的位置。

    var s = 'Hello world!';
    s.startsWith('world', 6) // true
    s.endsWith('Hello', 5) // true
    s.includes('Hello', 0) // true
    

    注意:endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。


    repeat()

    repeat方法返回一个新字符串,表示将原字符串重复n次。

    'x'.repeat(3) // "xxx"
    'hello'.repeat(2) // "hellohello"
    'na'.repeat(0) // ""

    参数如果是小数,会被取整。

    'na'.repeat(2.9) // "nana"

    如果repeat的参数是负数或者Infinity,会报错。

    'na'.repeat(Infinity)
    // RangeError
    'na'.repeat(-1)
    // RangeError
    

    但是,如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于-0,repeat视同为0。

    'na'.repeat(-0.9) // " "

    参数NaN等同于0。

    'na'.repeat(NaN) // ""
    

    如果repeat的参数是字符串,则会先转换成数字。

    'na'.repeat('na') // ""
    'na'.repeat('3') // "nanana"
    

    padStart(),padEnd()

    字符串补全长度功能,padStart()用于头部补全,padEnd()用于尾部补全。

    //padStart()
    'x'.padStart(5, 'ab') // 'ababx'
    'x'.padStart(4, 'ab') // 'abax'
    //padEnd()
    'x'.padEnd(5, 'ab') // 'xabab'
    'x'.padEnd(4, 'ab') // 'xaba'

    参数:第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。

    如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。

    'xxx'.padStart(2, 'ab') // 'xxx'
    'xxx'.padEnd(2, 'ab') // 'xxx'

    如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。

    'abc'.padStart(10, '0123456789')
    // '0123456abc'
    

    如果省略第二个参数,默认使用空格补全长度。

    'x'.padStart(4) // '   x'
    'x'.padEnd(4) // 'x   '

    padStart的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。

    '1'.padStart(10, '0') // "0000000001"
    '12'.padStart(10, '0') // "0000000012"
    '123456'.padStart(10, '0') // "0000123456"

    另一个用途是提示字符串格式。

    '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
    '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

    数值的扩展

    Number.isFinite(), Number.isNaN()

    Number.isFinite()用来检查一个数值是否为有限的(finite)。

    Number.isFinite(15); // true
    Number.isFinite(0.8); // true
    Number.isFinite(NaN); // false
    Number.isFinite(Infinity); // false
    Number.isFinite(-Infinity); // false
    Number.isFinite('foo'); // false
    Number.isFinite('15'); // false
    Number.isFinite(true); // false

    Number.isNaN()用来检查一个值是否为NaN。

    Number.isNaN(NaN) // true
    Number.isNaN(15) // false
    Number.isNaN('15') // false
    Number.isNaN(true) // false
    Number.isNaN(9/NaN) // true
    Number.isNaN('true'/0) // true
    Number.isNaN('true'/'true') // true

    注意:它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。

    例如:

    isFinite(25) // true
    isFinite("25") // true
    Number.isFinite(25) // true
    Number.isFinite("25") // false
    
    isNaN(NaN) // true
    isNaN("NaN") // true
    Number.isNaN(NaN) // true
    Number.isNaN("NaN") // false
    Number.isNaN(1) // false

    Number.parseInt(), Number.parseFloat()

    ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。

    
    // ES5的写法
    parseInt('12.34') // 12
    parseFloat('123.45#') // 123.45
    
    // ES6的写法
    Number.parseInt('12.34') // 12
    Number.parseFloat('123.45#') // 123.45
    

    这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

    Number.parseInt === parseInt // true
    Number.parseFloat === parseFloat // true
    

    Number.isInteger()

    Number.isInteger()用来判断一个值是否为整数。

    Number.isInteger(25) // true
    Number.isInteger(25.0) // true
    Number.isInteger(25.1) // false
    Number.isInteger("15") // false
    Number.isInteger(true) // false
    

    Number.EPSILON

    ES6在Number对象上面,新增一个极小的常量Number.EPSILON。

    Number.EPSILON
    // 2.220446049250313e-16
    Number.EPSILON.toFixed(20)
    // '0.00000000000000022204'

    安全整数和Number.isSafeInteger()

    JavaScript能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

    Math.pow(2, 53) // 9007199254740992
    
    9007199254740992  // 9007199254740992
    9007199254740993  // 9007199254740992
    
    Math.pow(2, 53) === Math.pow(2, 53) + 1
    // true

    上面代码中,超出2的53次方之后,一个数就不精确了。

    ES6引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

    Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
    // true
    Number.MAX_SAFE_INTEGER === 9007199254740991
    // true
    
    Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
    // true
    Number.MIN_SAFE_INTEGER === -9007199254740991
    // true

    Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

    Number.isSafeInteger('a') // false
    Number.isSafeInteger(null) // false
    Number.isSafeInteger(NaN) // false
    Number.isSafeInteger(Infinity) // false
    Number.isSafeInteger(-Infinity) // false
    
    Number.isSafeInteger(3) // true
    Number.isSafeInteger(1.2) // false
    Number.isSafeInteger(9007199254740990) // true
    Number.isSafeInteger(9007199254740992) // false
    
    Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
    Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
    Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
    Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

    Math对象的扩展

    Math.trunc()

    Math.trunc方法用于去除一个数的小数部分,返回整数部分。

    Math.trunc(4.1) // 4
    Math.trunc(4.9) // 4
    Math.trunc(-4.1) // -4
    Math.trunc(-4.9) // -4
    Math.trunc(-0.1234) // -0

    对于非数值,Math.trunc内部使用Number方法将其先转为数值。

    Math.trunc('123.456')
    // 123

    对于空值和无法截取整数的值,返回NaN。

    Math.trunc(NaN);      // NaN
    Math.trunc('foo');    // NaN
    Math.trunc();         // NaN

    Math.sign()

    Math.sign方法用来判断一个数到底是正数、负数、还是零。

    它会返回五种值。
    参数为正数,返回+1;
    参数为负数,返回-1;
    参数为0,返回0;
    参数为-0,返回-0;
    其他值,返回NaN。

    Math.sign(-5) // -1
    Math.sign(5) // +1
    Math.sign(0) // +0
    Math.sign(-0) // -0
    Math.sign(NaN) // NaN
    Math.sign('foo'); // NaN
    Math.sign(); // NaN

    Math.cbrt()

    Math.cbrt方法用于计算一个数的立方根。( 3的立方)

    Math.cbrt(-1) // -1
    Math.cbrt(0)  // 0
    Math.cbrt(1)  // 1
    Math.cbrt(2)  // 1.2599210498948734
    

    对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。

    Math.cbrt('8') // 2 
    Math.cbrt('hello') // NaN

    Math.clz32()

    Math.clz32方法返回一个数的32位无符号整数形式有多少个前导0。

    Math.clz32(0) // 32
    Math.clz32(1) // 31
    Math.clz32(1000) // 22
    Math.clz32(0b01000000000000000000000000000000) // 1
    Math.clz32(0b00100000000000000000000000000000) // 2

    上面代码中,0的二进制形式全为0,所以有32个前导0;1的二进制形式是0b1,只占1位,所以32位之中有31个前导0;1000的二进制形式是0b1111101000,一共有10位,所以32位之中有22个前导0。

    Math.imul()

    Math.imul方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。

    Math.imul(2, 4)   // 8
    Math.imul(-1, 8)  // -8
    Math.imul(-2, -2) // 4

    Math.fround()

    Math.fround方法返回一个数的单精度浮点数形式。

    Math.fround(0)     // 0
    Math.fround(1)     // 1
    Math.fround(1.337) // 1.3370000123977661
    Math.fround(1.5)   // 1.5
    Math.fround(NaN)   // NaN
    

    Math.hypot()

    Math.hypot方法返回所有参数的平方和的平方根。

    Math.hypot(3, 4);        // 5
    Math.hypot(3, 4, 5);     // 7.0710678118654755
    Math.hypot();            // 0
    Math.hypot(NaN);         // NaN
    Math.hypot(3, 4, 'foo'); // NaN
    Math.hypot(3, 4, '5');   // 7.0710678118654755
    Math.hypot(-3);          // 3

    上面代码中,3的平方加上4的平方,等于5的平方。

    如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回NaN。


    对数方法

    (1) Math.expm1()

    Math.expm1(x)返回ex - 1,即Math.exp(x) - 1。

    Math.expm1(-1) // -0.6321205588285577
    Math.expm1(0)  // 0
    Math.expm1(1)  // 1.718281828459045
    

    (2)Math.log1p()
    Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。

    Math.log1p(1)  // 0.6931471805599453
    Math.log1p(0)  // 0
    Math.log1p(-1) // -Infinity
    Math.log1p(-2) // NaN

    (3)Math.log10()
    Math.log10(x)返回以10为底的x的对数。如果x小于0,则返回NaN。

    Math.log10(2)      // 0.3010299956639812
    Math.log10(1)      // 0
    Math.log10(0)      // -Infinity
    Math.log10(-2)     // NaN
    Math.log10(100000) // 5

    ( 4 ) Math.log2()
    Math.log2(x)返回以2为底的x的对数。如果x小于0,则返回NaN。

    Math.log2(3)       // 1.584962500721156
    Math.log2(2)       // 1
    Math.log2(1)       // 0
    Math.log2(0)       // -Infinity
    Math.log2(-2)      // NaN
    Math.log2(1024)    // 10
    Math.log2(1 << 29) // 29

    三角函数方法

    ES6新增了6个三角函数方法。
    Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
    Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
    Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
    Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
    Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
    Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)

    Math.signbit()

    Math.sign()用来判断一个值的正负,但是如果参数是-0,它会返回-0。

    Math.signbit(2) //false
    Math.signbit(-2) //true
    Math.signbit(0) //false
    Math.signbit(-0) //true

    该方法的算法如下。

    如果参数是NaN,返回false
    如果参数是-0,返回true
    如果参数是负值,返回true
    其他情况返回false

    指数运算符

    ES2016 新增了一个指数运算符()**。

    2 ** 2 // 4
    2 ** 3 // 8

    指数运算符可以与等号结合,形成一个新的赋值运算符(=)**。

     let b = 4;
     b **= 3;  // 等同于 b = b * b * b;
     console.log(b)  //64
    

    数组的扩展

    Array.from()

    Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
    下面是一个类似数组的对象,Array.from将它转为真正的数组。

     let arrayLike = {
        '0':'a',
        '1':'b',
        '2':'c',
        length:3
      };
      //ES5写法
      var arr1 = [].slice.call(arrayLike);
      //ES6写法
      let arr2 = Array.from(arrayLike);
      console.log(arr1);
      console.log(arr2); 
    

    控制台结果:
    这里写图片描述

    实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

    举个例子:

    <p>1</p>
    <p>2</p>
    <p>3</p>
    <p>4</p>
    <p>5</p>
    <script>
    let ps = document.querySelectorAll('p');
      Array.from(ps).forEach(function(item){
        console.log(item)
      })
    </script>

    控制台结果:
    这里写图片描述

    
    // arguments对象
    function foo() {
      var args = Array.from(arguments);
      // ...
    }
    

    只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。

    Array.from('hello')
    // ['h', 'e', 'l', 'l', 'o']
    
    let namesSet = new Set(['a', 'b'])
    Array.from(namesSet) // ['a', 'b']

    如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。

    let arr = Array.from([1,2,3])
    console.log(arr) //[1, 2, 3]
    

    扩展运算符(…)也可以将某些数据结构转为数组。

    // arguments对象
    function foo() {
      var args = [...arguments];
    }
    
    // NodeList对象
    [...document.querySelectorAll('div')]

    Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

    Array.from(arrayLike, x => x * x);
    // 等同于
    Array.from(arrayLike).map(x => x * x);
    Array.from([1, 2, 3], (x) => x * x)
    // [1, 4, 9]

    利用Array.from获取一组DOM节点的文本内容
    举个例子:

    <span>苹果</span>
    <span>香蕉</span>
    <span></span>
    <span>西瓜</span>
    <span>桃子</span>
    <script>
        var spans = document.querySelectorAll('span');
        //ES5写法
        var names1 = Array.prototype.map.call(spans,function(i){
            return i.textContent;
        })
        console.log(names1);
    
       //ES6写法
       let spans2 = document.querySelectorAll('span');
       let names2 = Array.from(spans2,i => i.textContent);
       console.log(names2);
    </script>

    控制台结果都相同:
    这里写图片描述

    Array.from可以将数组中布尔值为false的成员转为0。
    例如:

    Array.from([1, , 2, , 3], (n) => n || 0)
    // [1, 0, 2, 0, 3]

    经过测试:NaN、undefined转化也为0

    let arr = Array.from([1,NaN, 2,undefined, 3], (n) => n || 0)
    console.log(arr);

    Array.from返回各种数据的类型
    举个例子:

    function typesOf () {
      return Array.from(arguments, value => typeof value)
    }
    typesOf(null, [], NaN)
    // ['object', 'object', 'number']
    

    Array.of()

    Array.of方法用于将一组值,转换为数组。

    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
    

    Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于2个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。

    Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。

    Array.of() // []
    Array.of(undefined) // [undefined]
    Array.of(1) // [1]
    Array.of(1, 2) // [1, 2]
    

    Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。

    数组实例的copyWithin()

    数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组

    Array.prototype.copyWithin(target, start = 0, end = this.length)

    三个参数:
    target(必需):从该位置开始替换数据。
    start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
    end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。*

    举个例子:

    let arr = Array.of(1,2,3,4,5);
    console.log(arr.copyWithin(0,3)) //[4, 5, 3, 4, 5]

    上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。

    注意:三个参数都应该是数值,如果不是,会自动转为数值。

    例如刚才的例子,输出结果相同:

    console.log(arr.copyWithin(0,'3')) //[4, 5, 3, 4, 5]

    数组实例的find()和findIndex()

    find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。

    [1, 4, -5, 10].find((n) => n < 0)
    // -5
    

    find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
    切记:是返回第一个符合条件的值,就不会找下一个了。例如:

      var arr = [1, 5, 10, 15].find(function(value, index, arr) {
      return value > 9;
    
    }) 
     console.log(arr)  //10

    findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

    切记:是返回第一个符合条件值的索引值

    [1, 5, 10, 15].findIndex(function(value, index, arr) {
      return value > 9;
    }) // 2
    

    另外,这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。

    [NaN].indexOf(NaN)
    // -1
    [NaN].findIndex(y => Object.is(NaN, y))
    // 0
    

    注意:Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与===的行为基本一致。

    例如:

    var a = 3; 
    var b = "3"; 
    
    a==b;    // true
    a===b;   // false,因为*a*,*b*的类型不一样 
    Object.is( a, b );  //false,因为*a*,*b*的类型不一样 

    数组实例的fill()

    fill方法使用给定值,填充一个数组。

      let arr = new Array('a','b','c');
      console.log(arr.fill(7)) //[7, 7, 7];
    
      //fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
      let arr2 = new Array(3).fill(6);
      console.log(arr2)// [6, 6, 6]
    

    fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
    例如:fill方法从1号位开始,向原数组填充7,到2号位之前结束。

    ['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']
    

    entries(),keys()和values()用于遍历数组

    用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历。

    keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

    //entries()是对键值对的遍历
     let arr = new Array('A','B','C');
       for(let [index,elem] of arr.entries()){
          console.log(index,elem);
       }

    这里写图片描述

    //keys()是对键名的遍历
    for (let index of ['a', 'b'].keys()) {
      console.log(index);
    }
    // 0
    // 1
    //values()是对键值的遍历
    let arr = [['姓名','阿蔡'],['年龄','23']];
       for(var elem of arr.values()){  
             console.log(elem); 
      }

    控制台结果:
    这里写图片描述


    includes()

    Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。

    [1, 2, 3].includes(2);     // true
    [1, 2, 3].includes(4);     // false
    [1, 2, NaN].includes(NaN); // true

    函数的扩展

    基本用法

    ,可以把参数默认值写在参数定义后面
    例如:

    //ES5写法
    function log(x, y) {
     if (typeof y === 'undefined') {
      y = 'World';
      }
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello World
    
    //ES6写法
    function log(x,y='World'){
      console.log(x, y);
    }
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello
    

    可以看到,ES6 的写法比 ES5 简洁许多。

    注意:参数变量是默认声明的,所以不能用let或const再次声明。

    function foo(x = 5) {
      let x = 1; // error
      const x = 2; // error
    }
    

    参数默认值的位置

    *注意:如果非尾部的参数设置默认值,实际上这个参数是没法省略的。是非位数!!如果省略就报错*
    举个例子:

        function foo(x,y=1){
            console.log(x,y);
        }
        foo(1,); //[1,undefined]
    
        //如果是非尾部参数设置默认值
        function foo(x=1,y){
            console.log(x,y);
        }
       foo(,2) //报错

    再举个例子:

    function f(x, y = 5, z) {
      return [x, y, z];
    }
    
    f() // [undefined, 5, undefined]
    f(1) // [1, 5, undefined]
    f(1, ,2) // 报错
    f(1, undefined, 2) // [1, 5, 2]
    

    总结:
    有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined。

    function foo(x = 5, y = 6) {
      console.log(x, y);
    }
    
    foo(undefined, null)
    // 5 null

    如果传入undefined,将触发该参数等于默认值,null则没有这个效果。


    函数的 length 属性

    指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

    (function (a) {}).length // 1
    (function (a = 5) {}).length // 0
    (function (a, b, c = 5) {}).length // 2

    length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入length属性。

    (function(...args) {}).length // 0

    如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

    (function (a = 0, b, c) {}).length // 0
    (function (a, b = 1, c) {}).length // 1

    作用域

    函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。

      var x = 8;
       function f(x,y = x){
         console.log(y)
       } 
    
       f();//undefined
       f(2); //2
       f(1,3) //3

    参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。

    再看下面的例子。

    
    let x = 1;
    
    function f(y = x) {
      let x = 2;
      console.log(y);
    }
    
    f() // 1

    上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。

    如果此时,全局变量x不存在,就会报错。

    function f(y = x) {
      let x = 2;
      console.log(y);
    }
    
    f() // ReferenceError: x is not defined
    

    这样写,也会报错。

    var x = 1;
    
    function foo(x = x) {
      // ...
    }
    
    foo() // ReferenceError: x is not defined
    

    参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“。

    再举个例子:

    var x = 1;
    function foo(x, y = function() { x = 2; }) {
      var x = 3;
      y();
      console.log(x);
    }
    
    foo() // 3
    x // 1

    上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。


    应用

    利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

    function throwIfMissing() {
      throw new Error('Missing parameter');
    }
    
    function foo(mustBeProvided = throwIfMissing()) {
      return mustBeProvided;
    }
    
    foo()
    // Error: Missing parameter

    上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。


    rest参数

    ES6 引入 rest 参数(形式为“…变量名”),用于获取函数的多余参数。
    例如:

    function add(...values) {
      let sum = 0;
    
      for (var val of values) {
        sum += val;
      }
    
      return sum;
    }
    
    add(2, 5, 3) // 10

    注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

    // 报错
    function f(a, ...b, c) {
      // ...
    }

    扩展运算符

    扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

    console.log(...[1, 2, 3])
    // 1 2 3
    
    console.log(1, ...[2, 3, 4], 5)
    // 1 2 3 4 5
    

    扩展运算符可以用于函数的调用

    function push(array, ...items) {
      array.push(...items);
    }
      function add(x,y){
        return x + y;
      }
      var numbers = [3,7];
      console.log(add(...numbers)); //10

    扩展运算符还可以与正常函数参数结合使用

      function f(a,b,c,d,e){
        console.log(a,b,c,d,e)
      }
      var number = [3,5];
      f(1,...number,...[7,9])  //1,3,5,7,9

    替代数组的apply方法

    由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

    // ES5的写法
    function f(x, y, z) {
      // ...
    }
    var args = [0, 1, 2];
    f.apply(null, args);
    
    // ES6的写法
    function f(x, y, z) {
      // ...
    }
    var args = [0, 1, 2];
    f(...args);
    

    通过push函数,将一个数组添加到另一个数组的尾部

    //ES5的写法
    var arr1 = [0,1,2];
    var arr2 = [3,4,5];
    Array.prototype.push.apply(arr1,arr2);
    
    //ES6的写法
    var arr1 = [0,1,2];
    var arr2 = [3,4,5];
    arr1.push(...arr2)

    扩展运算符的应用

    1.合并数组

    // ES5
    [1, 2].concat(more)
    // ES6
    [1, 2, ...more]

    再举个例子:

    
       var arr1 = ['a','b'];
       var arr2 = ['c'];
       var arr3 = ['b'];
    
       //ES5写法
       var array = arr1.concat(arr2,arr3);
       console.log(array);    
    
      //ES6写法
      var array2 = [...arr1,...arr2,...arr3];
       console.log(array2);
       //["a", "b", "c", "b"]

    注意:将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    2.与解构赋值结合
    扩展运算符可以与解构赋值结合起来,用于生成数组。

    const [first, ...rest] = [1, 2, 3, 4, 5];
    first // 1
    rest  // [2, 3, 4, 5]
    
    const [first, ...rest] = [];
    first // undefined
    rest  // []:
    
    const [first, ...rest] = ["foo"];
    first  // "foo"
    rest   // []

    如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    const [...butLast, last] = [1, 2, 3, 4, 5];
    // 报错
    
    const [first, ...middle, last] = [1, 2, 3, 4, 5];
    // 报错
    

    3.函数的返回值
    JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

    var dateFields = readDateFields(database);
    var d = new Date(...dateFields);

    上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。

    4.字符串
    扩展运算符还可以将字符串转为真正的数组。

    [...'hello']
    // [ "h", "e", "l", "l", "o" ]

    正确返回字符串长度的函数,识别32位的Unicode字符。

    function length(str) {
      return [...str].length;
    }
    
    length('x\uD83D\uDE80y') // 3

    5.实现了Iterator接口的对象
    任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。

    var nodeList = document.querySelectorAll('div');
    var array = [...nodeList];
    

    6.Map和Set结构,Generator函数
    扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。

    let map = new Map([ //Map是键值对结构
      [1,'one'],
      [2,'two'],
      [3,'three']
    ])
    let arr = [...map.keys()];
    console.log(arr); // [1, 2, 3]

    注意:Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。


    name 属性

    函数的name属性,返回该函数的函数名。

    function foo() {}
    foo.name // "foo"

    ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

    var f = function () {};
    
    // ES5
    f.name // ""
    
    // ES6
    f.name // "f"

    如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。

    const bar = function baz() {};
    
    // ES5
    bar.name // "baz"
    
    // ES6
    bar.name // "baz"

    Function构造函数返回的函数实例,name属性的值为anonymous。

    (new Function).name // "anonymous"
    

    箭头函数

    (箭头函数导致this总是指向函数定义生效时所在的对象)

    基本用法

    ES6允许使用“箭头”(=>)定义函数。

    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}
    

    如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

    var sum = (num1, num2) => { return num1 + num2; }

    由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

    var getTempItem = id => ({ id: id, name: "Temp" });

    箭头函数可以与变量解构结合使用。

    const full = ({first,last}) => first + ' ' + last;
    
    //等同于
    function full(person){
       return person.first + ' ' + person.last;
    }
    

    箭头函数的一个用处是简化回调函数。

    //ES5写法
    [1,2,3].map(function(x)){
        return x * x;
    }
    
    //ES6写法
    [1,2,3].map(x => x * x);
    

    rest参数与箭头函数结合

    const numbers = (...nums) => nums;
    numbers(1,2,3,4) //[1,2,3,4]
    
    const headAndTail = (head, ...tail) => [head, tail];
    headAndTail(1, 2, 3, 4, 5)
    // [1,[2,3,4,5]]
    

    使用注意点

    箭头函数有几个使用注意点。

    (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

    (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

    (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

    (4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

    箭头函数this指向问题
    箭头函数里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

    this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。


    对象的扩展

    属性的整洁表达法

    ES6允许直接写入变量和函数,作为对象的属性和方法。这样书写更简洁。

      var foo = 'bar';
      var obj = {foo};
      obj //{foo: "bar"}

    ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值

    //函数参数名直接作为对象属性值
      function f(x,y){
        return {obj1: x , obj2: y};
      } 
      f(1,2) //{obj1: 1, obj2: 2}
    
    

    除了属性简写,方法也可以简写

      //ES5写法 
       var o = {
          method: function(){
             return 'hello';
          }
       }
    
       //ES6写法
       var o2 = {
         method:() => 'hello'
       }

    再举个例子:

      var birth = '1995/1/5'
      var Person = {
          name:'阿蔡',
          birth,
          hello(){
             return 'hello'
          }
    
      } 
     console.log(Person);

    控制台结果:
    这里写图片描述

    还可以,用于函数的返回值

    function getPoint() {
      var x = 1;
      var y = 10;
      return {x, y};
    }
    
    getPoint() // {x:1, y:10}

    属性名表达式

    JavaScript语言定义对象的属性,有两种方法。

    // 方法一:直接用标识符作为属性名
    obj.foo = true;

    // 方法二:表达式作为属性名,这时要将表达式放在方括号之内
    obj[‘a’ + ‘bc’] = 123;

    ES5和ES6字面量定义对象写法:

      // ES5
      var obj = {
           foo: true,
           abc: 123
      }
       console.log(obj);  //{foo: true, abc: 123}
    
      //ES6
      let propKey = 'foo';
      let obj2 = {
          [propKey]: true,
          ['a' + 'bc']: 123 
      }; 
      console.log(obj2);  //{foo: true, abc: 123}
    
    

    再举个例子:

    var lastWord = 'last word';
    
    var a = {
      'first word': 'hello',
      [lastWord]: 'world'
    };
    
    a['first word'] // "hello"
    a[lastWord] // "world"
    a['last word'] // "world"
    

    表达式还可以用于定义方法名

       let obj = {
          ['h' + 'ello'](){
             return 'hello'
          }
       }
       console.log(obj.hello())    //hello
       //或者可以这么写  obj['h' + 'ello']()
    

    注意,属性名表达式与简洁表示法,不能同时使用,会报错。

    正确写法:

        let name = '姓名:';
        let obj = {[name]:'阿蔡'} //{姓名:: "阿蔡"} 
    

    方法的 name 属性

    1、函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

    举个例子:name属性返回函数名

      const person = {
          sayName(){
            return 'hello';
          }
       }
       console.log(person.sayName.name); //sayName
    

    2、name属性读取取值函数(getter)和存值函数(setter)上的方法

    const obj = {
      get foo() {},
      set foo(x) {}
    };
    
    obj.foo.name
    // TypeError: Cannot read property 'name' of undefined
    
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
    
    descriptor.get.name // "get foo"
    descriptor.set.name // "set foo"
    

    3、bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous。

    (new Function()).name // "anonymous"
    
    var doSomething = function() {
      // ...
    };
    doSomething.bind().name // "bound doSomething"
    

    4.如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。

    const key1 = Symbol('description');  //新的原始数据类型Symbol,表示独一无二的值
    const key2 = Symbol();
    let obj = {
      [key1]() {},
      [key2]() {},
    };
    obj[key1].name // "[description]"
    obj[key2].name // ""

    Object.is()

    在ES5中,比较两个值是否相等,只有两个运算符:相等运算符(==)和严格等于运算符(===)。

    但是缺点:
    (==)会自动转换数据类型。
    (===)NaN不等于自身,+0等于-0。

    在ES6中,Object.is()可以比较两个值是否相等,与严格相等运算符行为基本一致,不同处在于:NaN等于自身,+0不等于-0

    //ES6写法
     Object.is('foo','foo') //true
     Object.is({},{})  //false
     Object.is('+0','-0') //false
     Object.is(NaN,NaN) //true
    
    //ES5写法
    +0 === -0 //true
    NaN === NaN //false
    

    Object.assign()

    基本用法
    Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

    var targeObject.assign方法的第一个参数是目标对象,后面的参数都是源对象t = { a: 1 };
    
    var source1 = { b: 2 };
    var source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}

    Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。

    1.注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

    var target = { a: 1, b: 1 };
    
    var source1 = { b: 2, c: 2 };
    var source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}

    再举个例子:

    var target = { a: { b: 'c', d: 'e' } }
    var source = { a: { b: 'hello' } }
    Object.assign(target, source)
    // { a: { b: 'hello' } }

    2.对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

    3.如果该参数不是对象,则会先转成对象,然后返回。

    typeof Object.assign(2) // "object"

    4.undefined和null无法转成对象,所以如果它们作为参数,就会报错。

    Object.assign(undefined) // 报错
    Object.assign(null) // 报错

    5.非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。

    let obj = {a: 1};
    Object.assign(obj, undefined) === obj // true
    Object.assign(obj, null) === obj // true

    6.其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

    var v1 = 'abc';
    var v2 = true;
    var v3 = 10;
    
    var obj = Object.assign({}, v1, v2, v3);
    console.log(obj); // { "0": "a", "1": "b", "2": "c" }

    v1、v2、v3分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。

    7.Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)

    Object.assign({b: 'c'},
      Object.defineProperty({}, 'invisible', {
        enumerable: false,
        value: 'hello'
      })
    )
    // { b: 'c' }

    8.属性名为Symbol值的属性,也会被Object.assign拷贝。

    Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
    // { a: 'b', Symbol(c): 'd' }

    注意:Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

    9.Object.assign可以用来处理数组,但是会把数组视为对象。

    Object.assign([1,2,3],[4,5]) //[4,5,3]
    

    上面代码,Object.assign把数组视为属性名为0、1、2的对象,因此源数组的0号属性4覆盖了目标数组的0号属性1。

    常见用途

    (1)为对象添加属性
    通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

    class Point {
      constructor(x, y) {
        Object.assign(this, {x, y});
      }
    }
    

    (2)为对象添加方法

    Object.assign(SomeClass.prototype, {
      someMethod(arg1, arg2) {
        ···
      },
      anotherMethod() {
        ···
      }
    });

    (3)克隆对象

    function clone(origin) {
      return Object.assign({}, origin);
    }

    上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。

    如果想要保持继承链,可以采用下面的代码。

    function clone(origin) {
      let originProto = Object.getPrototypeOf(origin);
      return Object.assign(Object.create(originProto), origin);
    }
    

    (4)合并多个对象

    将多个对象合并到某个对象。

    const merge =
      (target, ...sources) => Object.assign(target, ...sources);

    如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。

    const merge =
      (...sources) => Object.assign({}, ...sources);
    

    (5)为属性指定默认值

    const DEFAULTS = {
      logLevel: 0,
      outputFormat: 'html'
    };
    
    function processContent(options) {
      options = Object.assign({}, DEFAULTS, options);
      console.log(options);
      // ...
    }

    上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。


    属性可枚举性

    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

    let obj = {foo:123};
    Object.getOwnPropertyDescripto(obj,'foo')
    //  {
    //    value: 123,
    //    writable: true,
    //    enumerable: true,  //描述对象enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
    //    configurable: true
    //  }
    

    ES5有三个操作会忽略enumerable为false的属性。
    for…in循环:只遍历对象自身的和继承的可枚举的属性
    Object.keys():返回对象自身的所有可枚举的属性的键名
    JSON.stringify():只串行化对象自身的可枚举的属性

    ES6新增了一个操作Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

    toString和length属性的enumerable都是false,因此for…in不会遍历到这两个继承自原型的属性。

    Object.getOwnPropertyDescriptor(Object.prototype,'toString').enumerable //false
    Object.getOwnPropertyDescriptor([],'length').enumerable //false
    

    另外,ES6规定,所有Class的原型的方法都是不可枚举的。

    Object.getOwnPropertyDescriptor(class {foo(){}}.prototype, 'foo').enumerable // false
    

    属性的遍历

    ES6一共有5种方法可以遍历对象的属性。

    var obj = {a:'1',[Symbol('b')]:2,d:4};
        //添加新的属性
        Object.defineProperty(obj,'c',{
            value:3,
            enumerable:false
        });
    
        console.log(obj); //{a: "1", d: 4, c: 3, Symbol(b): 2}
    
         //属性遍历
    
         //1.for...in循环,遍历对象自身和继承可枚举性
        for(var i in obj){
            console.log(i) //a , b
        }
    
         //2.Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
        console.log(Object.keys(obj)); //["a", "d"]
    
        //3.Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
        console.log(Object.getOwnPropertyNames(obj)) //["a", "d", "c"]
    
    
        //4.Object.getOwnPropertySymbols(obj)返回一个数组,包含对象自身的所有Symbols属性
       console.log(Object.getOwnPropertySymbols(obj))   //[Symbol(b)]
    
       //5返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。
       console.log(Reflect.ownKeys(obj)) //["a", "d", "c", Symbol(b)]

    以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

    首先遍历所有属性名为数值的属性,按照数字排序。
    其次遍历所有属性名为字符串的属性,按照生成时间排序。
    最后遍历所有属性名为Symbol值的属性,按照生成时间排序。


    proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()

    proto属性

    proto属性(前后各两个下划线),用来读取或设置当前对象的prototype(原型)对象。目前,所有浏览器(包括 IE11)都部署了这个属性。

    //es6的写法
    var obj = {
      method:function(){...}
    };
    obj.__proto__ = someOtherObj;
    
    //es5的写法
    var obj = Object.create(someOtherObj);
    obj.method = function() { ... };
    

    注意:该属性没有写入 ES6 的正文,本质上是一个内部属性,而不是一个正式的对外的 API。无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。


    Object.setPrototypeOf()

    Object.setPrototypeOf方法的作用与_proto_相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

    格式:

    Object.setPrototypeOf(object, prototype)

    用法:

    var o = Object.setPrototypeOf({}, null)

    举个例子:

    let proto = {};
    let obj = {x:10};
    Object.setPrototypeOf(obj,proto); //proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
    
    proto.y = 20;
    proto.z = 40;
    obj.x //10
    obj.y //20
    obj.z //40
    

    如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。

    Object.setPrototypeOf(1, {}) === 1 // true
    Object.setPrototypeOf('foo', {}) === 'foo' // true
    Object.setPrototypeOf(true, {}) === true // true
    

    由于undefined和null无法转为对象,所以如果第一个参数是undefined或null,就会报错。


    Object.getPrototypeOf()

    该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

    Object.getPrototypeOf(obj);

    举个例子

     function foo() {
      // ...
    }
    
    var rec = new foo();
    
    console.log(Object.getPrototypeOf(rec) === foo.prototype);// true
    
    console.log(Object.setPrototypeOf(rec, Object.prototype)); //__proto__ :Object
    console.log(Object.getPrototypeOf(rec) === foo.prototype);// false

    如果参数不是对象,会被自动转为对象

    Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
    Object.getPrototypeOf('foo') //String {length: 0, [[PrimitiveValue]]: ""}
    Object.getPrototypeOf(true) // Boolean {[[PrimitiveValue]]: false}
    

    如果参数是undefined或null,则无法转化为对象。直接报错。

    Object.getPrototypeOf(undefined)
    Object.getPrototypeOf(null)
    // TypeError: Cannot convert undefined or null to object
    

    Object.keys()

    ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

    var obj = {foo:'bar',baz:42};
    Object.keys(obj) //['foo','baz'];
    

    ES7 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for…of循环使用。

    let {keys,values,entries} = Object;
    let obj = {a:1,b:2,c:3};
    console.log(keys(obj))  //["a", "b", "c"]
    
    for(let key of keys(obj)){
      console.log(key); //a,b,c
    }
    
    for(let value of values(obj)){
       console.log(value) // 1,2,3
    }
    
    for(let [key,value] of entries(obj)){
       console.log([key,value]);  // ['a', 1], ['b', 2], ['c', 3]
    }
    

    Object.values()

    Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

    var obj = {foo:'bar',baz:42};
    Object.values(obj) // ["bar", 42]
    

    属性名为数值的属性,是按照数值大小,从小到大遍历的。因此返回的顺序是b、c、a。

    var obj = { 100: 'a', 2: 'b', 7: 'c' };
    Object.values(obj) //["b", "c", "a"]
    

    Object.values只返回对象自身的可遍历属性。

    var obj = Object.create({}, {p:{value:1}});
    Object.values(obj) //[]
    

    上面代码中,用Object.create方法的第二参数添加对象属性,默认情况下不可遍历,因为p**属性描述对象enumerable默认是false**,Object.values不会返回这个属性。只要把enumerable改成true,Object.values就会返回属性p的值

    var obj = Object.create({},{p:{
        value:1,
        enumerable:true
      }
    });
    Object.values(obj) // [42]
    

    Object.values会过滤属性名为 Symbol 值的属性。

    Object.values({ [Symbol()]: 123, foo: 'abc' });
    // ['abc']

    如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

    Object.values('foo')
    // ['f', 'o', 'o']
    

    如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

    Object.values(42) // []
    Object.values(true) // []
    

    Object.entries

    Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

    var obj = {foo:'bar',baz:'42'};
    Object.entries(obj) //[foo:'bar',baz:'42']
    

    除了返回值不一样,该方法的行为与Object.values基本一致。
    如果原对象的属性名是一个Symbol值,该属性会被忽略。

    var obj = {[Symbol()]:123,foo:'abc'};
    Object.entries(obj) // ['foo','abc']
    

    对象的扩展运算符

    (1)解构赋值
    对象的解构赋值用于从一个对象取值,相当于将所有可遍历的,但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

      let {x,y,...z} = {x:1,y:2,a:3,b:4};
      x //1
      y //2
      z //{a:3,b:4}
    

    解构赋值要求等会右边是一个对象,所以如果等会右边是undefined或null,就会报错。因为它们无法转化为对象。

    let {x,y,...z} = null;
    let {x,y,...z} = undefined
    //运行时发生错误
    

    解构赋值必须是最后一个参数,否则会报错

    let {...x,y,z} = obj //句法错误
    let {x,...y,...z} = obj //句法错误
    

    只要解构赋值不是最后一个参数,就会报错。

    注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

    let obj = {a:{b:1}};
    let {...x} = obj;
    obj.a.b = 2;
    x.a.b //2
    

    上面代码中,x是结构赋值所在的对象,拷贝了对象的a属性。a属性引用了一个对象,修改了这个对象的值,会影响到解构赋值对它的引用。

    另外,解构赋值不会拷贝继承自原型对象的属性

    let o1 = {a:1};
    let o2 = {b:2};
    o2.__proto__ = o1;
    let o3 = {...o2};
    o3 //{b:2}
    

    上面代码中,对象o3是o2的拷贝,但是只复制了o2自身的属性,没有复制它的原型对象o1的属性。

    解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。

    function foo({a,b}){
     //...
    }
    function wrapFoo({x,y,...restConfig}){
       // 使用x和y参数进行操作
      // 其余参数传给原始函数
      return foo(restConfig);
    }
    

    原始函数foo接受a和b作为参数,函数wrapFoo在foo的基础上进行了扩展,能够接受多余的参数,并且保留原始函数的行为。

    (2)扩展运算符
    扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

    let z = {a:1,b:2};
    let n = {...z};
    n // {a:1, b:2}
    

    这等同于使用Object.assign方法。

    let aClone = {...a};
    //等同于
    let aClone = Object.assign({},a)
    

    扩展运算符可以用于合并两个对象

    let ab = { ...a, ...b };
    // 等同于
    let ab = Object.assign({}, a, b);
    

    扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。

    // 并不会抛出错误,因为x属性只是被定义,但没执行
    let aWithXGetter = {
      ...a,
      get x() {
        throws new Error('not thrown yet');
      }
    };
    
    // 会抛出错误,因为x属性被执行了
    let runtimeError = {
      ...a,
      ...{
        get x() {
          throws new Error('thrown now');
        }
      }
    };
    

    如果扩展运算符的参数是null或undefined,这两个值会被忽略,不会报错。

    let obj = {...null,...undefined}; //不报错
    

    Object.getOwnPropertyDescriptors()

    ES5有一个Object.getOwnPropertyDescriptor方法,返回某个对象属性的描述对象(descriptor)。

    var obj = {p:'a'};
    Object.getOwnPropertyDescriptors(obj,'p') //切记描述对象要加上''
    //{value: "a", writable: true, enumerable: true, configurable: true}
    

    ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。

    const obj = {
        foo:123,
        get bar() { return 'abc'}
    }
    Object.getOwnPropertyDescriptors(obj)
    // { foo:
    //    { value: 123,
    //      writable: true,
    //      enumerable: true,
    //      configurable: true },
    //   bar:
    //    { get: [Function: bar],
    //      set: undefined,
    //      enumerable: true,
    //      configurable: true } }
    

    Object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。


    Null 传导运算符

    const firstName = message?.body?.user?.firstName || 'default';

    上面代码有三个?.运算符,只要其中一个返回null或undefined,就不再往下运算,而是返回undefined。

    “Null 传导运算符”有四种用法。
    obj?.prop // 读取对象属性
    obj?.[expr] // 同上
    func?.(…args) // 函数或对象方法的调用
    new C?.(…args) // 构造函数的调用

    传导运算符之所以写成obj?.prop,而不是obj?prop,是为了方便编译器能够区分三元运算符?:(比如obj?prop:123)。

    例子。

    // 如果 anull 或 undefined, 返回 undefined
    // 否则返回 a.b.c().d
    a?.b.c().d
    
    // 如果 a 是 null 或 undefined,下面的语句不产生任何效果
    // 否则执行 a.b = 42
    a?.b = 42
    
    // 如果 a 是 null 或 undefined,下面的语句不产生任何效果
    delete a?.b
    展开全文
  • ES6

    千次阅读 2018-09-20 14:33:24
    ES6 ES6 一、 变量赋值与解构 二、- 字符串拓展 三、- 正则拓展 四、- 数值的拓展 五、- 函数的扩展 五、- 数组的扩展 六、- 对象的扩展 七、- Symbol类型:表示独一无二的值 八、 Set 和 Map ...

    ES6

    一、 变量赋值与解构

    • 默认值赋值注意点:
      • 只有当一个数组成员严格等于undefined,默认值才会生效。
          let [x = 1] = [undefined];
          x // 1
          
          let [x = 1] = [null];
          x // null
      
      • 默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
          let [x = 1, y = x] = [];     // x=1; y=1
          let [x = 1, y = x] = [2];    // x=2; y=2
          let [x = 1, y = x] = [1, 2]; // x=1; y=2
          let [x = y, y = 1] = [];     // ReferenceError: y is not defined
      

    • 变量的解构注意点:

      • 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
          let arr = [1, 2, 3];
          let {0 : first, [arr.length - 1] : last} = arr;
          first // 1
          last // 3
      
      • 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
          let { prop: x } = undefined; // TypeError
          let { prop: y } = null; // TypeError
      
      • 为了区分模式,还是表达式 --> 只要有可能导致解构的歧义,就不得使用圆括号。
          //变量申明
          let [(a)] = [1];
          let {x: (c)} = {};
          
          // 函数参数
          function f([(z)]) { return z; }
          function f([z,(x)]) { return x; }
          
          //赋值语句的模式部分
          ({ p: a }) = { p: 42 };
          ([a]) = [5];
          
          //可以使用(赋值语句的非模式部分)
          [(b)] = [3]; // 正确
          ({ p: (d) } = {}); // 正确
          [(parseInt.prop)] = [3]; // 正确
      
       > 与`for...of`配合 获取键名和键值就非常方便。
      

    二、- 字符串拓展

    1. 详见阮一峰老师网站-String



    三、- 正则拓展

    1. 详见阮一峰老师网站-RegExp
      • u修饰符,含义为“Unicode 模式”

      • y 修饰符“粘连”(sticky)修饰符:确保匹配必须从剩余的第一个位置开始

      • ‘后行断言’

        • 先行断言:x只有在y前面才匹配,必须写成/x(?=y)/
          比如:只匹配百分号之前的数字,要写成/\d+(?=%)/

        • 先行否定断言:x只有不在y前面才匹配,必须写成/x(?!y)/

        • 后行断言:x只有在y后面才匹配,必须写成/(?<=y)x/
          如:只匹配美元符号之后的数字,要写成/(?<=\$)\d+/

        • 后行否定断言:x只有不在y后面才匹配,必须写成/(?<!y)x/

        后行断言与正常情况顺序是相反的,为“先右后左”的执行顺序

      • 具名组匹配:允许为每一个组匹配指定一个名字;
        引用某个“具名组匹配” \k<组名>或者\1
        具名组匹配在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。

            const REG = /^(?<word>[a-z]+)!\k<word>!\1$/;
            REG.test('abc!abc!abc') // true
            REG.test('abc!abc!ab') // false
        



    四、- 数值的拓展

    1. 二进制和八进制表示法(前缀0b | 0o)
    2. Number.isFinite(), Number.isNaN()
    3. Number.parseInt(), Number.parseFloat()
    4. Number.isInteger()
    5. Number.EPSILON(最小精度) 2 ** -52
    6. 安全整数和 Number.isSafeInteger(): 判断是否在安全值范围
    7. Math 对象的扩展
    8. 指数运算符**



    五、- 函数的扩展

    1. 函数参数的默认值

    2. rest 参数 …props

    3. 严格模式

    4. name 属性

    5. 箭头函数 =>this引用的外层函数
      注意点:

      • (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
      • (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
      • (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
      • (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
    6. 双冒号运算符

       foo::bar;
       // 等同于
       bar.bind(foo);
       
       var method = obj::obj.foo;
      // 等同于
      var method = ::obj.foo;
      
    7. 尾调用:(指某个函数的最后一步是调用另一个函数)优化

      • 只保留内层调用

      注意点:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

    8. 尾递归:尾调用自身

      //计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
       function factorial(n) {
          if (n === 1) return 1;
          return n * factorial(n - 1);
      }
      
      factorial(5) // 120
      
      //尾递归优化,只保留一个调用记录,复杂度 O(1) 。
      function factorial(n, total = 1) {
          if (n === 1) return total;
          return factorial(n - 1, n * total);
      }
      
      factorial(5) // 120
      

      tips:把所有用到的内部变量改写成函数的参数



    六、- 数组的扩展

    1. 扩展运算符(...[])的应用

      • 转成参数序列

      • 复制数组

        const a1 = [1, 2];
        const a2 = a1.concat(); //es5
        
        const a3 = [...a1];     //es6
        const [...a4] = a1;
        
      • 合并数组 [...arr1, ...arr2, ...arr3] (浅拷贝)

      • 解构赋值 const [first, ...rest] = [1, 2, 3, 4, 5]

      • 正确返回字符长度 [...str].length

        'x\uD83D\uDE80y'.length // 4
        [...'x\uD83D\uDE80y'].length // 3
        
      • 实现 Iterator 接口的对象.
        任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。
        对于那些没有部署 Iterator接口的类似数组的对象,使用Array.from(arrayLike),实际上任何有length属性的对象,都可以通过Array.from方法转为数组。

    2. Array.from():

      • 用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象(包括 ES6 新增的数据结构 Set 和 Map)。
      • Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
         Array.from(arrayLike, x => x * x);
         // 等同于
         Array.from(arrayLike).map(x => x * x);
      
         Array.from([1, 2, 3], (x) => x * x)
         // [1, 4, 9]
      
    3. Array.of():

    - 用于将一组值,转换为数组。
    - 为了弥补构造函数`Array()`的不足
    
        ```
            //参数个数不一样行为不同
            Array() // []
            Array(3) // [, , ,]
            Array(3, 11, 8) // [3, 11, 8]
        ```
        `Array.of`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
    
    1. 数组实例的 copyWithin()

      • 将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。。
      • Array.prototype.copyWithin(target, start = 0, end = this.length)
    2. 数组实例的 fill()

      • 可用于填充数组,数组中已有的元素,会被全部抹去。

      • fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

        ['a', 'b', 'c', 'd'].fill(7, 1, 3)
        // ['a', 7, 7, 'd']
        
        //> 注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
        let arr = new Array(3).fill([]);
        arr[0].push(5);
        // arr = [[5], [5], [5]]
        
    3. 数组实例的 entries()keys()values()

      • for...of循环进行遍历,keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

        for (let index of ['a', 'b'].keys()) {
            console.log(index);
        }
        // 0
        // 1
        
        for (let elem of ['a', 'b'].values()) {
          console.log(elem);
        }
        // 'a'
        // 'b'
        
        for (let [index, elem] of ['a', 'b'].entries()) {
          console.log(index, elem);
        }1
        // 0 "a"
        // 1 "b"
        
    4. 数组实例的 includes()

      • 表示某个数组是否包含给定的值(第二个参数表示搜索的起始位置),与字符串的includes方法类似
         [1, 2, 3].includes(3, 3)   // false
         [1, 2, 3].includes(3, -1)  // true
         [1, 2, [3]].includes([3])  // false
         [NaN].includes(NaN)        // true
      
    5. 数组实例的 flat()flatMap() —Chrome无此方法(2018/8/21)

      • flat()用于将嵌套的数组“拉平”,变成一维的数组。参数:想要拉平的层数,默认为1。

        [1, 2, [3, [4, 5]]].flat()
        // [1, 2, 3, [4, 5]]
        
        //如果原数组有空位,flat()方法会跳过空位。
        [1, 2, , 4, 5].flat()
        // [1, 2, 4, 5]
        
        [1, [2, [3]]].flat(Infinity) //Infinity:不管有多少层嵌套,都要转成一维数组
        // [1, 2, 3]
        
      • flatMap() 方法对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。传入函数(类似map),只能展开一层

    6. 数组的空位的处理

      • ES5:

        • forEach(), filter(), reduce(), every() 和some()都会跳过空位。
        • map()会跳过空位,但会保留这个值
        • join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
      • ES6:

        • 明确将空位转为undefined

          [...['a',,'b']]
          // [ "a", undefined, "b" ]
          



    七、- 对象的扩展

    1. 简写{foo} === {foo: foo}

    2. Object.is():

          +0 === -0 //true
          NaN === NaN // false
          
          Object.is(+0, -0) // false
          Object.is(NaN, NaN) // true
      
    3. 对象详情



    八、Symbol类型:表示独一无二的值

    1. Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
    2. Symbol 值不能与其他类型的值进行运算,会报错。
    3. Symbol 值可以显式转为字符串|布尔值但不能转为数值。
    4. Symbol 作为属性名,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。



    九、 Set 和 Map 数据结构

    1. Set: 它类似于数组,但是成员的值都是唯一的,没有重复的值。
       const set = new Set([1, 2, 3, 4, 4, 4, 4]);
       [...set]
       // [1, 2, 3, 4]
       
       // 去除数组的重复成员
       [...new Set(array)]
       
       //在 Set 内部,两个NaN是相等。
       //另外,两个对象总是不相等的。
    
    - add(value):添加某个值,返回 Set 结构本身。
    - delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    - has(value):返回一个布尔值,表示该值是否为Set的成员。
    - clear():清除所有成员,没有返回值。
    
    1. WeakSet :与 Set 类似,但 WeakSet 的成员只能是对象,而不能是其他类型的值。
      没有size属性,没有办法遍历它的成员。

    2. Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

          const map = new Map();
          const k1 = ['a'];
          const k2 = ['a'];
          
          map
          .set(k1, 111)
          .set(k2, 222);
          
          map.get(k1) // 111
          map.get(k2) // 222
      
    3. WeakMap :与Map结构类似,也是用于生成键值对的集合。

      • 区别:
        • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。

        • WeakMap的键名所指向的对象,不计入垃圾回收机制。

          const wm = new WeakMap();
          
          const element = document.getElementById('example');
          
          wm.set(element, 'some information');
          wm.get(element) // "some information"
          /*
          先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,
          并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element
          的引用就是弱引用,不会被计入垃圾回收机制即DOM节点对象的引用计数是1,而不是2。
          可以有效防止内存泄漏。
          (WeakMap 的另一个用处是部署私有属性。如果删除实例,它们也就随之消失)
          */
          



    十、 Proxy

    1. 含义:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。(代理器)

    2. new Proxy(target, handler)

    3. Proxy支持的拦截操作

      1. get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
      2. set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
      3. has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
      4. deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
      5. ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
      6. getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
      7. defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
      8. preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
      9. getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
      10. isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
      11. setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
      12. apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
      13. construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)



    十一、 Reflect

    1. 从Reflect对象上可以拿到语言内部的方法。

    2. 13个静态方法

      • Reflect.apply(target, thisArg, args)

      • Reflect.construct(target, args)

      • Reflect.get(target, name, receiver)

        const myObject = {
          foo: 1,
          bar: 2,
          get baz() {
            return this.foo + this.bar;
          },
        }
        console.log(Reflect.get(myObject, 'foo')) // 1
        console.log(Reflect.get(myObject, 'bar')) // 2
        console.log(Reflect.get(myObject, 'baz')) // 3
        
      • Reflect.set(target, name, value, receiver)

        myObject.foo // 1
        
        Reflect.set(myObject, 'foo', 2);
        myObject.foo // 2
        
      • Reflect.defineProperty(target, name, desc)

        // 新写法
        Reflect.defineProperty(MyDate, 'now', {
          value: new Date()
        })
        
        console.log(MyDate.now)
        
      • Reflect.deleteProperty(target, name)

      • Reflect.has(target, name)

      • Reflect.ownKeys(target)

      • Reflect.isExtensible(target)

      • Reflect.preventExtensions(target)

      • Reflect.getOwnPropertyDescriptor(target, name)

        const myObject = {};
        Reflect.defineProperty(myObject, 'hidden', {
          value: true,
          writable:true,
        });
        
        const theDescriptor = Reflect.getOwnPropertyDescriptor(myObject,'hidden');
        console.log(theDescriptor)
        //{value: true, writable: true, enumerable: false, configurable: false}
        myObject.hidden = 1
        console.log(myObject) //{hidden: 1}
        
      • Reflect.getPrototypeOf(target)

      • Reflect.setPrototypeOf(target, prototype)

    3. 实例:使用 Proxy 实现观察者模式

      const queuedObservers = new Set();
      
      const observe = fn => queuedObservers.add(fn);
      const observable = obj => new Proxy(obj, {set});
      
      function set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver);
        queuedObservers.forEach(observer => observer());
        return result;
      }
      
      const person = observable({
        name: '张三',
      });
      function print() {
        console.log(`${person.name}`)
      }
      
      observe(print);
      person.name = '李四';  //李四
      person.name = '33';     //33
      
      

      上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,拦截赋值操作。拦截函数set之中,会自动执行所有观察者—这跟vue很相似



    十二、 Promise 对象

    1. 含义:Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

    2. Promise.prototype.then()
      then方法返回的是一个新的Promise实例。因此可以采用链式写法

    3. Promise.prototype.catch()
      .then(null, rejection)的别名,一般建议总是使用catch方法。catch方法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。

    如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码

    1. Promise.prototype.finally()
      不管 Promise 对象最后状态如何,都会执行的操作。

      promise
      .finally(() => {
        // 语句
      });
      
      // 等同于
      promise
      .then(
        result => {
          // ...
          return result;
        },
        error => {
          //...
          throw error;
        }
      )
      
    2. Promise.all()
      const p = Promise.all([p1, p2, p3]);

      注意:

      • Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
      • 只有所有的状态都变成fulfilled,返回值组成一个数组,传递给回调函数,
        或者有一个变为rejected,被reject的实例的返回值才会继续传递给回调函数。
    3. Promise.race()
      const p = Promise.race([p1, p2, p3]);
      只要有一个状态改变就。。。

      //当接口五秒内未返回就报超时错误
      const p = Promise.race([
        fetch('/resource-that-may-take-a-while'),
        new Promise(function (resolve, reject) {
          setTimeout(() => reject(new Error('request timeout')), 5000)
        })
      ]);
      
      p
      .then(console.log)
      .catch(console.error);
      
    4. Promise.resolve()

      Promise.resolve('foo')
      // 等价于(可以将现有对象转成Promise对象)
      new Promise(resolve => resolve('foo'))
      
      • 参数是个Promise 实例:不做修改直接返回这个实例
      • 参数是个具有then方法的对象:先转Promise然后立即执行then方法
      • 参数不是具有then方法的对象,或根本就不是对象:返回状态为resolved的Promise对象
        不带有任何参数:直接返回一个resolved状态的 Promise 对象。
    5. Promise.reject()
      Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

    6. Promise.try()
      不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它时。

      解决方案1 :

      const f = () => console.log('now');
      (async () => f())()
      console.log('next')
      // now
      // next
      

      解决方案2 :

      const f = () => console.log('now');
      (
        () => new Promise(
          resolve => resolve(f())
        )
      )();
      console.log('next');
      // now
      // next
      


    十三、Iterator 和 for…of 循环

    ①. Iterator

    1. 凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
      原生具备Iterator接口的数据结构 (可以用for…of遍历它们):

      • Array
      • Map
      • Set
      • String
      • TypedArray
      • 函数的 arguments 对象
      • NodeList 对象

    2. 默认调用Symbol.iterator方法的情况

      • 对数组和 Set 结构进行解构赋值时
      • 使用扩展运算符(…)
      • yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
      const generator = function* () {
        yield* [1,2];
      };
      
      var iterator = generator();
      
      iterator.next() // { value: 1, done: false }
      iterator.next() // { value: 2, done: false }
      iterator.next() // { value: undefined, done: true }
      
      • 数组的遍历会调用遍历器接口
        • for…of
        • Array.from()
        • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
        • Promise.all()
        • Promise.race()
    3. 字符串是一个类似数组的对象,也原生具有 Iterator 接口。

    4. 遍历器对象的 return()

      可以触发return的情况

      // 情况一:break
      for (let line of readLinesSync(fileName)) {
        console.log(line);
        break;
      }
      
      // 情况二:Error
      for (let line of readLinesSync(fileName)) {
        console.log(line);
        throw new Error();
      }
      
      • 情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;
      • 情况二会在执行return方法关闭文件之后,再抛出错误。

    #### ②. for...of 循环 1. `for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。 2. `for...in`循环读取键名,`for...of`循环读取键值。 `for...of`循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。 ``` let arr = [3, 5, 7]; arr.foo = 'hello';
    for (let i in arr) {
      console.log(i); // "0", "1", "2", "foo"
    }
    
    for (let i of arr) {
      console.log(i); //  "3", "5", "7"
    }
    ```
    

    Set和Map

    1. Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组([k,v])

    其他

    1. 对于没有Iterator接口的类数组对象:使用Array.from方法将其转为数组。



    十四、Generator 函数的语法

    1. Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
       const myIterable = {}
       myIterable[Symbol.iterator] = function* () {
         yield 1
         yield 2
         yield 3
       };
       
       console.log([...myIterable])  // [1, 2, 3]
    

    2. 注意: - `yield`表达式只能用在 Generator 函数里面,用在其他地方都会报错。 - `yield`表达式如果用在另一个表达式之中,必须放在圆括号里面。 - `yield`表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

    1. next 方法的参数

      • next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
        也就是说:可以在 Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
        • 注意: 由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的

    2. Generator.prototype.throw():

      • 只要 Generator 函数内部部署了try…catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。
      • 一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。

    3. Generator.prototype.return():

      • 可以返回给定的值,并且终结遍历 Generator 函数。

    4. yield* 表达式:为了说明yield表达式后面跟的是一个遍历器对象时加*

    5. Generator 函数的this:

      • Generator 函数总是返回遍历器对象而不是this
      function* gen() {
        this.a = 1;
        yield this.b = 2;
        yield this.c = 3;
      }
      
      function F() {
        return gen.call(gen.prototype);//<--
      }
      
      var f = new F();
      
      f.next();  // Object {value: 2, done: false}
      f.next();  // Object {value: 3, done: false}
      f.next();  // Object {value: undefined, done: true}
      
      f.a // 1
      f.b // 2
      f.c // 3
      

    6. Generator

      • 与状态机:完美管理状态
      • 协程:既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。
      • 传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。
      • 与普通线程差异:
        • 同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。
        • 普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。



    十五、Generator 函数的异步应用

    1. 传统方法

      • 回调函数 ->(callback hell)强耦合

      • 事件监听

      • 发布/订阅

      • Promise对象 ->代码冗余,原来的任务被Promise包装了一下,一堆then,原来的语义变得很不清楚。

    2. Thunk 函数:是“传名调用”的一种实现策略,用来替换某个表达式。
      /JavaScript 语言是传值调用,以下是个自执行器(thunkify、co模块、)

      //实现自执行
      

    function thunkify(fn) {
    return function() {
    var args = new Array(arguments.length);
    var ctx = this;

        for (var i = 0; i < args.length; ++i) {
          args[i] = arguments[i];
        }
    
        return function (done) {
          var called;
          args.push(function () {
            if (called) return;
            called = true;
            done.apply(null, arguments);
          });
    
          try {
            fn.apply(ctx, args);
          } catch (err) {
            done(err);
          }
        }
      }
    }   
       
     const ft = (n,cb) =>cb(n)
     const CB = thunkify(ft)  
        
        //实现自执行
        function run(fn) {
          var gen = fn()
        
          function next(err, data) {
            var result = gen.next(data)
            console.log(result)
            if (result.done) return;
            result.value(next)
          }
        
          next()
        }
        
    
        const g = function* (){
          var f1 = yield CB('aaaaaa')
          var f2 = yield CB('bbbbbb')
          // ...
          var fn = yield CB('nnnnnn')
        }
        
        run(g)
    ```
    <br/>
    
    1. CO模块

      1. 原理:Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。以下两种方法可以做到这一点:
        • 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
        • Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。

      2. 基于 Promise 对象的自动执行
      function run(gen){
        var g = gen();
      
        function next(data){
          var result = g.next(data)
          console.log(result)
          if (result.done) return result.value
          result.value.then(function(data){
            next(data);
          });
        }
      
        next();
      }
      
      //需要返回promise 然后链式自执行
      const CO = (data) => new Promise((resolve)=> resolve(data))
      const g = function* (){
        var f1 = yield CO('aaaaaa')
        var f2 = yield CO('bbbbbb')
        // ...
        var fn = yield CO('nnnnnn')
      }
      
      run(g)
      
      1. 处理并发的异步操作
      • co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。
      1. 实例:处理 Stream


    十六、async 函数

    1. Generator 函数的语法糖

      • 改进
      • (1)async函数自带执行器。
      • (2)更广的适用性
        co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
      • (3)返回值是 Promise
        Generator 函数的返回值是 Iterator 对象

    2. 注意点

      1. async函数内部return语句返回的值,会成为then方法回调函数的参数。
      2. async函数内部抛出错误,会导致返回的Promise对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
      3. 只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。 => 想继续执行 可以吧代码放在try...catch中。
      4. await命令只能用在async函数之中,如果用在普通函数,就会报错。
      • forEach/Map中使用async也会出问题 :
      // scope B
      array.forEach(async function(item) { // scope A
       await wait(1000);
      });
      /*
      `Scope B` 部分的代码并不会等待 `Scope A` 中的 `async/await` 执行完后继续执行后面的代码,相反,他会立刻执行后面的代码
      */
      
      
      • 正确的写法是采用for循环、

    3. 并发执行

      // 写法一
      let [foo, bar] = await Promise.all([getFoo(), getBar()]);
      
      // 写法二
      let fooPromise = getFoo();
      let barPromise = getBar();
      let foo = await fooPromise;
      let bar = await barPromise;
      
    4. for await...of:遍历异步的 Iterator 接口。

      for await (const chunk of readStream) {
          console.log('>>> '+chunk);
      }
      
    5. 异步 Generator 函数

      • 在头部加async 标志,此时即可返回一个Promise



    十七、Class 的基本语法

    1. constructor 方法
      • 通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
      • constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

    2. class Foo {}不存在声明提升

    3. 私有方法和私有属性
      • 函数名加下划线(仅为命名方法,外部仍可调用)
      • 将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
      class Widget {
        foo (baz) {
          bar.call(this, baz);
        }
        // ...
      }
      
      function bar(baz) {
        return this.snaf = baz;
      }
      
      • 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
      const bar = Symbol('bar')
      const snaf = Symbol('snaf')
      
      export default class myClass{
        foo(baz) {
          this[bar](baz);
        }
      
        // 私有方法
        [bar](baz) {
          return this[snaf] = baz;
        }
        // ...
      };
      

    4. this 的指向 - 当类的方法中有this使用且方法被提取出来单独使用时this会指向方法运行环境导致意料之外的错误 1. 可以在构造函数中绑定`this.printName = this.printName.bind(this)` 2. 箭头函数: ``` constructor() { this.printName = (name = 'there') => { this.print(`Hello ${name}`) } }
    ```
    3. 使用Proxy,获取方法的时候,自动绑定this
    ```
    function selfish (target) {
      const cache = new WeakMap();
      const handler = {
        get (target, key) {
          const value = Reflect.get(target, key);
          if (typeof value !== 'function') {
            return value;
          }
          if (!cache.has(value)) {
            cache.set(value, value.bind(target));
          }
          return cache.get(value);
        }
      };
      const proxy = new Proxy(target, handler);
      return proxy;
    }
    
    const logger = selfish(new Logger());
    ```
    <br/>
    - Class 的取值函数(getter)和存值函数(setter)
    ```
    class MyClass {
      constructor() {
        // ...
      }
      get prop() {
        return 'getter';
      }
      set prop(value) {
        console.log('setter: '+value);
      }
    }
    
    let inst = new MyClass();
    
    inst.prop = 123;
    // setter: 123
    
    inst.prop
    // 'getter'
    
    ```
    
    </br>
    - Class 的静态方法
        在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用
    



    十八、Class 的继承

    1. 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。 =>constructor不写默认 有super(...args)

    2. Object.getPrototypeOf:从子类上获取父类

      //可以使用这个方法判断,一个类是否继承了另一个类。
      Object.getPrototypeOf(ColorPoint) === Point
      // true
      
      
    3. super:

      • super在B的构造函数中,指向A.prototype,所以super可以拿到父类原型对象上的实例方法
      • 如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
    4. 原生构造函数的继承

      • Boolean()
        Number()
        String()
        Array()
        Date()
        Function()
        RegExp()
        Error()
        Object()

      原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。
      extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数!

    5. Mixin 模式的实现(多个对象合成一个新的对象)

      function mix(...mixins) {
        class Mix {}
      
        for (let mixin of mixins) {
          copyProperties(Mix.prototype, mixin); // 拷贝实例属性
          copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷贝原型属性
        }
      
        return Mix;
      }
      
      function copyProperties(target, source) {
        for (let key of Reflect.ownKeys(source)) {
          if ( key !== "constructor"
            && key !== "prototype"
            && key !== "name"
          ) {
            let desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
          }
        }
      }
      



    十九、修饰器(提案)

    1. 修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升
    2. 详见>>ES6-修饰器



    二十、Module 的语法

    1. 注意点
      • import在静态解析阶段执行,所以它是一个模块之中最早执行的。
      • import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
      // 报错
      import { 'f' + 'oo' } from 'my_module';
      
      // 报错
      let module = 'my_module';
      import { foo } from module;
      
      // 报错
      if (x === 1) {
        import { foo } from 'module1';
      } else {
        import { foo } from 'module2';
      }
      //在静态分析阶段,这些语法都是没法得到值的。
      
    2. export default命令其实只是输出一个叫做default的变量
    3. export 与 import 的复合写法
      //foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口
      export { foo, bar } from 'my_module'
      //>可以用来合并一些模块
      
      
    4. 提案:import()函数 => 用来动态加载模块 返回Promise



    二十、Module 的加载实现

    1. 浏览器加载

      • 传统方法
      • defer :“渲染完再执行” , 多个defer脚本,会按照它们在页面出现的顺序加载。
      • async :“下载完就执行” , 多个async脚本,不能保证加载顺序。
      • 加载 ES6 模块
      • <script type="module" src="./...js"></script>:此时行为与defer相同

    2. ES6 模块与 CommonJS 模块的差异

      • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
      • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    3. Node 加载

      • Node 有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的

      • Node 要求 ES6 模块采用.mjs后缀文件名。(新版)
      $ node --experimental-modules my-app.mjs
      
      • 内部变量差异
      • ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块。
      • ES6不存在这些顶层变量:
        1. arguments
        2. require
        3. module
        4. exports
        5. __filename
        6. __dirname

      • ES6 模块加载 CommonJS 模块
        • Node 的import命令加载 CommonJS 模块,Node 会自动将module.exports属性,当作模块的默认输出,即等同于{ default: module.exports }
        • import命令加载 CommonJS 模块时,不允许采用解构的写法,改为整体输入

      • CommonJS 模块加载 ES6 模块
      • 不能使用require命令,而要使用import()函数。ES6 模块的所有输出接口,会成为输入对象的属性。

    4. 循环加载

      • CommonJS 模块加载原理
      • require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
      • CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
      • 由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。

      • ES6 模块的循环加载
      • 与CommonJS不同:变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
      • a 引用 b,b中引用a,执行到b中对a引用时会认为a已经存在,继续往下执行
    展开全文
  • es6

    2019-12-31 16:08:03
    JavaScript ES6 规范 ES6 简介 ECMAScript 6 简称 ES6,是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 ...
  • es6文档笔记(全)

    千次阅读 2019-04-18 13:37:11
    ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。 for循环的计数器,就很合适使用let命令。 另外,for循环还有一个特别之处,就是设置循环变量的那部分是一...
  • JS ES6

    千次阅读 2019-06-11 20:20:34
    1. Let和Const let和const定义的变量都是块级作用域,var定义的变量是函数级作用域 { var a = 10 } console.log(a) // 10 { let a = 10 } console.log(a) // ReferenceError: a is not defined ...
  • ES6】for of用法

    万次阅读 多人点赞 2019-03-12 17:14:40
    前言:for of是ES6新增的循环方法。前面已经说到了 【JavaScript】for、forEach 、for in、each循环详解。那for of又是怎么使用的? 一、使用例子 使用例子(一) var arr = ['nick','freddy','mike','james']; ...
  • ES6-字符串扩展-padStart(),padEnd()

    千次阅读 2019-01-11 16:14:35
    ES6 引入了字符串补全长度的功能,如果某个字符串不够指定长度,会在头部活尾部补全。 padStart() 用于头部补全; padEnd() 用于尾部补全。   'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // '...
  • ES6 深入理解 ${ } 模版

    千次阅读 2020-03-01 15:45:23
    本文将带你深入理解ES6中 ${ } 模版 后续的文章都会与前端有关,欢迎各位同路途的人一起交流学习,3月份又是努力的开头,加油! 推荐阅读:来自 菜鸟 的 前端实习面经 大厂 春招实习生 ES 6专栏 -> 传送门 如果...
  • 针对ES6各主流浏览器支持情况

    万次阅读 2019-07-11 10:59:57
    Browser Version Date Chrome 58 Apr 2017 Firefox 54 Jun 2017 Edge 14 Aug 2016 Safari 10 Sep 2016 Opera 55 Aug 2017
  • IDEA支持es6设置

    万次阅读 2018-04-17 14:17:05
    file&gt;settings&gt;Lauguages &amp; Frameworks&gt;javascript:将javascript languages version 修改为 ECMAScript 6。
  • 1.Babeljs,在线转换地址 2.es6console, 在线转换及运行
  • 本套课程讲解了运行环境已支持的 ES6 特性,而忽略掉未支持的特性,随着 Node.js 、babel 和浏览器等 ES6 运行环境的逐步支持,本套视频课程的内容也会补充。这样授课的好处是可以让学员直接在开发中实际的应用 ES6 ...
  • es6转es5的在线工具

    万次阅读 2018-12-14 21:51:16
    有时自己写的带有es6语法的js不能执行,但是偷懒找个在线工具 https://babeljs.io/en/repl.html 有可能需要翻墙才能访问
  • ES6在线编译ES5 工具

    千次阅读 2018-08-08 09:18:42
    ES6在线编译ES5 工具地址:在线转换地址 
  • ES6代码转为ES5代码的在线转换工具

    万次阅读 2017-05-08 20:52:45
    1.Babel,在线转换地址 2.Traceur,Google公司出品,在线转换地址
  • es6删除数组某一项

    千次阅读 2018-08-17 15:41:27
    先找到数组是否存在,再splice list.splice(list.findIndex(v =&gt; v.id === id), 1)
  • 在eclipse中配置js语法检查支持es6语法。 找到项目下的文件.jshintrc,编辑esnext为true。
1 2 3 4 5 ... 20
收藏数 158,662
精华内容 63,464
关键字:

es6