es6学习笔记

《ECMAScript6 入门》阮一峰 读书笔记

  • let和const
    • let
      1. 声明的变量仅在块级作用域内有效,var声明的变量在全局范围内有效
      2. let不存在变量提升,所以变量一定要先声明后使用,换言之,var是有变量提升的(也就是可以先使用后声明,声明在编译的时候会被提升到最开头)
      3. 暂时性死区,也就是使用let命令声明变量之前,该变量都是不可用的,也就是第二条。暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
      4. 不允许重复声明,也就是在同一个作用域内不能重复声明同一个变量。
  • 块级作用域
    1. 不合理场景一
var tmp = new Date();
function f() {
  console.log(tmp);
  if (false) {
      var tmp = "hello world";
  }
}
f(); // undefined,因为变量提升内层的tmp变量覆盖了外层的tmp变量????
🚩有疑惑,并不理解。。。
2. 不合理场景二,变量泄漏为全局变量(比如for循环里面的计数变量,如果用var来声明,其实在全局也是可以访问到的)
3. 块级作用域与函数声明
    - es5规定函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域中声明,比如在if语句里面就不能声明一个变量,但实际上,浏览器是支持的
    - es6明确允许在块级作用域之中声明函数。比较特别的是,块级作用域之中,函数声明语句的行为类似于`let`,在`块级作用域之外是不可以引用的`
    
    ```
    eg:
    function f(){console.log("I am outside");}
    (function (){
        if(false){
          function f(){console.log("I am inside");}
        }
        f()
    }())
    *===================================================*
    es5中实际上是如此运行的:
    function f(){console.log("I am outside");}
    (function (){
        function f(){console.log("I am inside");}//变量提升
        if(false){}
        f()
    }())
    *===================================================*
    es6中实际运行代码如下:
    function f(){console.log("I am outside");}
    (function(){
    f()//在块级内声明的那个inside的f函数对作用域之外没有影响,所以相当于不存在,
    调用的是外面的函数,因为作用域查找都是向外查找哒!
    }())
    ```
    
    - ⚠️为了兼容的问题,es6允许浏览器可以不遵守上面的规定,也就是可以有自己的行为方式,具体表现为(也就是实际上定义函数的时候遵守的规则,只针对支持es6的浏览器有效):
        - 允许在块级作用域内声明函数
        - 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
        - 同时,函数声明还会提升到所在的块级作用域的头部
     - 在块级作用域内声明函数的时候,块级作用域一定要加大括号,比如在if里面声明一个函数,if后面一定要有大括号
  • const声明一个只读的常量,声明之后,值不能改变。所以,声明的时候就要赋值,其他的和let一样。有一个注意的点是,比如const了一个数组,这个数组本身是不能给赋值的,但是这个数组是可以push一个变量的!

  • 顶层对象的属性

    • 全局变量在es5中是顶层对象的属性,es6规定,var和function命令声明的全局变量依然是顶层对象的属性,但是let、const、class命令声明的全局变量不属于顶层对象的属性。
  • 顶层对象

    混乱的顶层对象

    • 浏览器里面,顶层对象是window,但Node和Web Worker没有window
    • 浏览器和Web Worker里面,self指向顶层对象,但是Node 没有self
    • Node里面,顶层对象是global,但其他环境都不支持

    为了在不同的环境中都能去到顶层对象,一般使用this变量,但是有局限性
    - 全局环境中,this会返回顶层对象,但是,Node模块和ES模块中,this返回的是当前模块
    - 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象,但是,严格模式下,this会返回undefined
    - 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象,但是,如果浏览器用了CSP(Content Security Policy,内容安全政策),那么evalnew Function这些方法都可能无法使用。
    综上所述,两种解决办法: 方法一: (typeof window !== 'undefined' ? window: (typeof process === 'object' && typeof require === 'function' && typeof global === 'object')? global : this ) 方法二: var getGlobal = function(){ if(typeof self !== 'undefined'){return self;} if(typeof window !== 'undefined'){return window;} if(typeof global !== 'undefined'){return global;} throw new Error('unalble to locate global object'); }

  • 变量的解构赋值

es6允许按照一定模式,从数组和对象中提取值,对变量进行复赋值,成为解构赋值。

  • 数组的解构赋值
    • “模式匹配”,也就是只要等号两边的模式相同,左边的变量就会被赋予对应的值。
    let [foo, , baz] = [1,2,[d]];//foo就是1,baz就是[d]这个数组
    let [head, ...tail] = [1,2,3,4];//head就是1,tail就是[2,3,4]
    let [x,y,...z] = ['a'];//x就是“   a”,y是undefined(解构不成功的话,变量的值就等于undefined),z是[],为啥z是[](????为啥😭),因为"..."是将右边剩余的值用数组的形式赋给左边的值,所以z就得到了一个空的数组
    let [a,[b],d] = [1,[2,3],4];//a是1,b是2(完全对应过去,如果b不加外面的中括号,那b就是[2,3]),d是4
    
    • 等号的右边如果不是可遍历的结构,会报错的
    • 默认值,允许指定默认值,数组成员严格等于undefined才会使默认值生效
    var [foo = true] = [];//foo是true
    
  • 对象的解构赋值
    • 变量必须与对象的属性同名,才能取到正确的值,如果没有对应的同名属性,取不到值的话,值为undefined
    var {bar, foo} = {foo:'aaa',bar:'bbb'}//foo的值是'aaa',bar的值是'bbb'
    //注意真正被赋值的是谁
    var {foo:baz} = {foo:"aaa",bar:"bbb"}//baz的值是"aaa"而不是foo的值是“aaa”,foo是个“模式”
    
    谁后面有“:”,谁就是模式,并不是变量
    • 这种写法,变量的声明和赋值是一体的,像下面,如果用let或const是不行的,但是用var是可以的。
    let foo;
    let {foo} = {foo:1}//这样会报错滴,因为foo在上面已经声明过了,所以不能再次声明
    //如果非常想用let并且分开的话,可以用下面的方式
    let foo;
    ({foo} = {foo:1})//这样不会报错,外面必须用()包起来,因为解析器会将起首的大括号理解成一个代码块而不是赋值语句。
    
    • 默认值,默认值生效的条件是,对象的属性值严格等于undefined,同样,如果解构失败(也就是对应右边没有相同变量名的属性),那么变量的值等于undefined
    • 实际应用场景:可以很方便的把一些对象的属性值赋值给其他的变量,不用一个一个的写
    var { log,sin,cos } = Math
    
  • 字符串的解构赋值
    var [a,b,c] = 'hello';
    //a==>h,b==>e,c==>l
    var {length:len} = 'hello';
    //len的值是5,有点懵❓❓❓(类似数组的对象都有一个length属性,所以就5了❓❓❓)
    
  • 数值和布尔值的解构赋值

    解构赋值时,如果等号右边是数值或布尔值,则会先转为对象,(undefined和null无法转为对象,所以对他们解构赋值会出错)

  • 函数形式参数的解构赋值
  • 变量的解构赋值的用途
    1. 交换变量的值
    [x,y] = [y,x]//数组的解构是严格按照顺序的
    
    1. 从函数返回多个值:函数一次只能返回一个值,如果要返回多个值,只能将她们放在数组或对象里返回,有了解构赋值,可以很方便的取出这些值
    function example(){
      return [1,2,3]
    }
    var [a,b,c] = example()//这样就赋值了,多方便😄
    
    1. 函数参数的定义
    function f([x,y,z]){....}
    f([1,2,3])
    
    1. 提取JSON数据
    var jsonData = {
     id:42,
     status:"ok",
     data:[22,3]
    }
    let { id, status, data:number } = jsonData;
    
    1. 遍历Map结构
    2. 输入模块的指定方法

  • 字符串的扩展(这节好深奥,先放下😳
    • 字符的Unicode表示法,用\uxxxx形式来表示一个字符,其中xxxx表示字符的码点
  • 正则的扩展
  • 数值的扩展

  • 数组的拓展

    • Array.from(),用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象。
    let arrayLike = {
      '0':'a',
      '1':'b',
      '2':'c',
      length:3
    };//类似数组的对象
    let arr = Array.from(arrayLike);//['a','b','c']
    
    • 实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。(所谓类似数组的对象本质特征只有一点,即必须有length属性,任何有length属性的对象,都可以通过Array.from方法转为数组)
    • Array.of(),用于将一组值转换为数组,没有参数的时候返回一个空数组
    Array.of(1,2,4)//【1,2,4】
    
    • copyWithin(),在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,也就是会修改当前数组。
    Array.prototype.copyWith(target,start,end)//接受三个参数,其中target是必需的,target代表从该位置开始替换数据,start表示从该位置开始读取数据,默认为0,负数则表示倒数,end表示到该位置前停止读取数据,默认等于数组长度,负数则表示倒数
    [1,2,3,4].copyWithin(0,3)//[4,4,4,4],从0开始用3位置上(也就是4这个数字到末尾的0来替换0
    
    • 数组实例的find()和findIndex()

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

    [1,2,-5].find(n => n < 0)//返回第一个小于0的成员
    

    findIndex方法返回第一个符合条件的数组成员的位置,如果都不符合条件,则返回-1,回调函数参数和find方法一致。

    find和findIndex方法都可以接受除回调函数以外的第二个参数,用来绑定回调函数的this对象。
    
    • 数组实例的fill()

    fill方法使用给定值,填充一个数组。如果数组中已有元素,则会全部被抹去。参数和copyWithin函数的参数是一致的。

    ['a','b','c'].fill(7)//返回一个[7,7,7]
    
    • 数组实例的entries(),keys()和values()----遍历数组的函数
    ------对键名的遍历
    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);
     }//结果如下
     //0 "a"
     //1 "b"
    
    • 数组实例的includes()

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

    [1,2,3].includes(2)//包含2,所以返回true
    
    Map结构的has方法,是用来查找键名的
    Set结构的has方法,是用来查找值的
    
    • 数组的空位

    数组的空位是指,数组的某一个位置没有任何值,es6明确的将空位转为undefined。以上数组的扩展方法都会把空位考虑进去,而不是忽略。

  • 函数的扩展

    • 函数参数的默认值
    function log(x, y = 'world'){
    ...}
    
    • 与解构赋值默认值结合使用(没看懂😳
    • 参数默认值的位置

    非尾部的参数如果设置了默认值,那么这个参数是不能够省略的!

    • 函数的length属性

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

    (function (a,b=2){}).length
    //1
    
    • 作用域---大爷

      1. 如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。
      情况一:
      var x = 1;
      function f(x, y = x){
      略
      }
      //比如这里,y的默认值就是一个变量x,那么x的值要优先取传进来的x的值,如果没有,
      才是全局变量中的x。
      
      情况二:
      let x = 1;
      function f(y=x){
        let x = 2;
        console.log(y);
      }
      f()//输出的是1,因为给y赋值的时候,x尚未在函数内部生成,故取全局变量中的x给y赋值,
      如果此时全局变量中的x不存在,就会报错了。
      
      情况三:
      let foo = "outer";
      function bar(func = x=> foo){
         let foo = "inner";
         console.log(func());
      }
      bar()
      //当函数的参数是一个函数的时候,该函数的作用域是其声明时所在的作用域。
      ⚠️⚠️bar方法的参数func是一个匿名函数,具体的意义是传进去x,返回值为foo,
      也就是func()的值,匿名函数声明的时候(也就是给func赋值的时候),bar函数的作
      用域还没有形成,所以匿名函数里面的foo指向外层作用域来的foo,输出的是outer
      ⚠️⚠️
      

      2.rest参数

      rest参数形式为"...变量名",用于获取函数的多余参数,这样就不需要使用arguments对象了,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。👉需要注意的是rest参数之后不能再有其他参数,也就是这个参数只能放在最后面。

      function add(...values){
       let sum = 0;
       for(var val of values){
          sum+=val;
       }
       return sum;
      }
      add(2,4,5)//神似python里的参数🙄
      

    3.扩展运算符

    扩展运算符(spread)是三个点...,rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。注意,扩展运算符针对的是数组数组数组❗️也就是把一个数组相当于给拆开了。

    console.log(...[1,2,3])
    //1 2 3
    
    function push(array, ...items){
      array.push(...items);
    }
    function add(x, y){
       return x + y;
     }
     var numbers = [4,3];
     add(...numbers)//7
    
    • 替代数组的apply方法(⚠️总结一下apply方法,因为得知道apply究竟是干嘛才知道怎么个替代法🚩10.26)

    • 扩展运算符的应用:

      • 合并数组
      var arr1 = ['a','b'];
      var arr2 = ['c','d'];
      var arr = [...arr1,...arr2]
      
      • 与解构赋值结合
      这里和前面的解构赋值很容易混啊🚩
      const [first,...rest]=[1,2,3,4];
      first===》1
      rest====》2,3,4
      //有个易错点,和前面很像,就是i 这个...变量只能放在最后⚠️
      
      • 函数的返回值,返回多个值的时候
      • 字符串,可以将字符串转换为真正的数组
      [...'hello']
      //['h','e','l','l','o']
      
      • 实现了Iterator接口的对象,任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
      let arrayLike = {
       '0':'a',
       '1':'b',
       '2':'c',
       length:3
      };
      let arr = [...arrayLike]//TypeError
      //arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错,
      这时,可以改为使用Array.from方法将arrayLike转为真正的数组。
      
      • Map结构和Set结构,Generator函数

      扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符。

    4.严格模式use strict

    es6规定,只要函数的形参使用了默认值、解构赋值或者扩展运算符,那么函数内部就不能显示的设定为严格模式。

    5.name属性,函数的name属性,返回该函数的函数名

    6.箭头函数 =>

    • 如果箭头函数不需要参数或者需要多个参数,就使用一个圆括号代表参数部分。
    • 如果箭头函数的代码块部分多于一条语句,就要使用大括号将他们括起来,并且使用return语句返回
    • 如果要返回一个对象,外面要用小括号包起来,因为大括号被解释为代码块,所以必须在对象外面加上括号
    • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,在箭头函数中,this是固定的。因为箭头函数没有自己的this,他们的this其实都是外层对象(也就是函数)的this。所以bind(),call(),apply()这些方法无法改变this的指向。
    • 不可以当做构造函数,也就是不能用new操作符
    • 不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以用rest参数代替
    • 不可以使用yield命令,因此箭头函数不能用做generator函数

    7.绑定this::es7的一个题案,如果用的话需要babel转码器

    双冒号左边是一个对象,右边是一个函数,该运算符会自动将左边的对象,作为上下文环境(即this对象)绑定到右边的函数上面,如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

    8.尾调用优化😳看不懂🚩10.27

    尾调用是函数式编程的一个重要概念,指的是某个函数的最后一步是调用另一个函数

  • 对象的扩展

    • 属性的简洁表示法
    情况一:
    var foo = 'a';
    var baz = {foo};
    //以往写对象的时候要属性名、属性值成对出现,es6允许在对象中,只写属性名,
    不写属性值,这时,属性值等于属性名所代表的变量。
    情况二:
    var o = {
      method(){
          return "hello";//这不就是之前写react的用法嘛😮
      }
    };
    等同于:
    var o = {
      method:function(){
          return "hello";
      }
    };
    综合的例子:
    var birth = '2000/01/01';
    var Person = {
    name: '张三',
    //等同于birth: birth
    birth,//react返回对象的时候就是这样写的!!!😌
    // 等同于hello: function ()...
    hello() { console.log('我的名字是', this.name); }
    };
    
    • 属性名表达式🚩
    • Object.js()

    比较两个值是否相等,只有两个运算符:相等运算符==和严格相等运算符===,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。Object.is用来比较两个值是否严格相等,弥补了===的缺点。

    • Object.assign()

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

    Object.assign(target,source1,source2...)
    //第一个参数是目标对象,也就是最终的,后面都是源对象。如果只有一个参数那就直接返回
    //⚠️⚠️⚠️⚠️如果目标对象与源对象有同名属性,或者多个源对象有同名属性,则后面的属性会
    覆盖前面的属性
    ⚠️undefined和null不能放在目标对象的位置(放,报错,不放也就是放在别的位置会被跳过)
    ⚠️字符串可以作为源对象,复制到目标对象里会是数组的形式,布尔值和数值都不能作为源对象,
    虽然不报错,但是会跳过
    
    ⚠️注意点
    1.Object.assign是浅拷贝,如果源对象某个属性的值是对象,如果源对象某个属性的值是对象,
    那么目标对象拷贝得到的是这个对象的引用。
    2.Object.assign也可以处理数组,但是也是将数组视为对象。
    
    - Object.assign常见用途
      - 为对象添加属性
      - 为对象添加方法,目标对象就是xxx.prototype,然后源对象就是用大括号包裹的函数
      - 克隆对象
      - 合并多个对象
      - 为属性指定默认值(可以将属性的默认值为一个对象,用户输入的为另一个对象,两个对象作为源对象复制给目标对象,因为用户输入的放在后面所以会覆盖掉默认值对象的值,so~)
    
    • 属性的可枚举性

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

    let obj = {foo:123};
    Object.getOwnPropertyDescriptor(obj,'foo')
    //{
    //    value:123,
    //    writable:true,
    //    enumerable:true,是否可以枚举
    //    configurable:true
    //}
    
    • 属性的遍历
      1. for...in,遍历对象自身的和继承的可枚举属性(不包含Symbol属性)
      2. Object.keys(obj),返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
      3. Object.getOwnPropertyNames(obj),返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
      4. Object.getOwnPropertySymbols(obj),返回一个数组,包含对象自身的所有Symbol属性
      5. Reflect.ownKeys(obj),返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可以枚举。

推荐阅读更多精彩内容

  • 强大的for-of循环 ES6不会破坏你已经写好的JS代码。目前看来,成千上万的Web网站依赖for-in循环,其...
    Awe阅读 6,776评论 2 7
  • ECMAScript 6.0( 以下简称ES6) 是JavaScript语言的下一代标准。 ECMAScript和...
    EarthChen阅读 43评论 0 0
  • 第一章 块级作用域绑定 let 和 const 都是不存在提升,声明的都是块级标识符都禁止重声明 每个const声...
    NowhereToRun阅读 879评论 0 2
  • 一. 函数参数的默认值 ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。 Tips:参数变量是默认声...
    markpapa阅读 20评论 0 0
  • 终于您今晚现身啦~ 很久没现身啦。 让我激动的不自觉的微笑,从心底里开心起来。从店里骑摩拜到宿舍,吹着凉风,这感...
    淡墨o阅读 30评论 0 0