美文网首页
开发过程中遇到的一些奇妙的事情(es6):

开发过程中遇到的一些奇妙的事情(es6):

作者: HW_____T | 来源:发表于2017-07-30 12:46 被阅读0次

    开发过程中遇到的一些奇妙的事情:(基础知识,碰到了忘了又百度了又怕忘了就记下来了)
    注:此篇文章是我参考阮一峰老师的[ECMAScript 6 入门]文章,自己记录的笔记,详细的内容请移步阮一峰老师的文章。
    注:es6语法目前主流浏览器已经兼容了,无需babel转换成低版本的es5,需要注意,而且转换成es5之后,输出结果可能会不同

    1. 配置babel-cli。
    • 安装全局babel-clinpm install -g babel-cli
      备注:node.js 记得先装。
    • 进入项目工作区,npm init
    • 安装项目依赖,npm install --save-dev babel-cli
    • 在工作区建立配置文件.babelrc,
      内容为:
    {
        "presets": [],
        "plugins": []
    }
    
    • 安装转码规则,npm install --save-dev babel-preset-latest
    • 更新.babelrc文件
    {
      "presets": ["latest"],
      "plugins": []
    }
    
    • 常用指令
      转码结果输出到标准输出
      babel example.js
      转码结果写入一个文件
      --out-file 或 -o 参数指定输出文件
      babel example.js --out-file compiled.js 或者
      babel example.js -o compiled.js
      整个目录转码
      --out-dir 或 -d 参数指定输出目录
      babel src --out-dir lib或者
      babel src -d lib
    2. let 与 const。
    • 相对于varlet声明的变量只在命令所在的代码块内有效。
    • var命令有变量提升现象而let没有.
      使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。
    // 不报错
    var x = x;
    
    // 报错
    let x = x;
    // ReferenceError: x is not defined
    
    • let不允许在作用域内声明同个变量
    //报错
    var a=10;
    let a=2;
    
    • 块级作用域
      let提供了块级作用域,var为js提供了函数作用域和全局作用域。
    function f1() {
      let n = 5;
      if (true) {
        let n = 10;
      }
      console.log(n); // 5
    }
    
    • do表达式
      块级作用域是一个语句,将多个操作放在一起,没有返回值,使用do表达式使块级作用域有返回值
    let a =do{
      let b =f();
    b=b+1;
    }
    
    3. Number扩展
    • Number.isFinite(),Number.isNaN();
      Number.isFinite()用来检查一个数值是否为有限的。
      Number.isNaN()用来检查一个值是否为NaN。
      它们与传统的全局方法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.isInteger()
      Number.isInteger()用来判断一个值是否为整数。需要注意的是,在 JavaScript 内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值
    • Number.parseInt(), Number.parseFloat()
      ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
    4. 函数扩展
    • 函数可以设定默认值
    function log(x, y = 'World') {
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello
    
    • 与解构赋值默认值结合使用
      参数默认值可以与解构赋值的默认值,结合起来使用。
    // 写法一
    function m1({x = 0, y = 0} = {}) {
      return [x, y];
    }
    
    // 写法二
    function m2({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
    

    上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。

    // 函数没有参数的情况
    m1() // [0, 0]
    m2() // [0, 0]
    
    // x和y都有值的情况
    m1({x: 3, y: 8}) // [3, 8]
    m2({x: 3, y: 8}) // [3, 8]
    
    // x有值,y无值的情况
    m1({x: 3}) // [3, 0]
    m2({x: 3}) // [3, undefined]
    
    // x和y都无值的情况
    m1({}) // [0, 0];
    m2({}) // [undefined, undefined]
    
    m1({z: 3}) // [0, 0]
    m2({z: 3}) // [undefined, undefined]
    
    • 作用域
      一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
    var x = 1;
    
    function f(x, y = x) {
      console.log(y);
    }
    
    f(2) // 2
    

    上面代码中,参数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。

    function foo(x,y=function(){x=2}){
        console.log(x)
        y();
        console.log(x);
    }
    foo();  // undefiend 2
    
    var x=100;
    function foo(x,y=function(){x=2}){
           var x=10;
        console.log(x);
        y();
        console.log(x);
    }
    foo();  // 10 2 
    console.log(x)//100
    //注:这里是转换成es5,如果是es6,就打印的10,10。
    

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

    如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

    var x=100;
    function foo(x,y=function(){x=2}){
            x=10
        console.log(x)
        y();
        console.log(x);
    }
    foo();  // 10 2
    console.log(x);//100
    //注:这里是转换成es5,如果是es6,就打印的10,2。
    
    

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

    如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

    • name 属性
      函数的name属性,返回该函数的函数名。
    function foo() {}
    foo.name // "foo"
    
    • 箭头函数
      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;
    }
    

    使用注意点

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

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

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

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

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

    上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    var id = 21;
    
    foo.call({ id: 42 });
    // id: 42
    

    上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。

    注:如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向都是window。这是因为JS的定时器方法是定义在window下的。

    箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。

    function Timer() {
      this.s1 = 0;
      this.s2 = 0;
      // 箭头函数
      setInterval(() => this.s1++, 1000);
      // 普通函数
      setInterval(function () {
        this.s2++;
      }, 1000);
    }
    
    var timer = new Timer();
    
    setTimeout(() => console.log('s1: ', timer.s1), 3100);
    setTimeout(() => console.log('s2: ', timer.s2), 3100);
    // s1: 3
    // s2: 0
    

    上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。

    • 尾调用
      什么是尾调用?
      尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
    function f(x){
      return g(x);
    }
    

    上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。

    以下三种情况,都不属于尾调用。

    // 情况一
    function f(x){
      let y = g(x);
      return y;
    }
    
    // 情况二
    function f(x){
      return g(x) + 1;
    }
    
    // 情况三
    function f(x){
      g(x);
    }
    

    尾调用不一定出现在函数尾部,只要是最后一步操作即可。

    function f(x) {
      if (x > 0) {
        return m(x)
      }
      return n(x);
    }
    

    上面代码中,函数m和n都属于尾调用,因为它们都是函数f的最后一步操作。

    • 尾调用优化
      尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
    function f() {
      let m = 1;
      let n = 2;
      return g(m + n);
    }
    f();
    
    // 等同于
    function f() {
      return g(3);
    }
    f();
    
    // 等同于
    g(3);
    

    上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

    这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

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

    function addOne(a){
      var one = 1;
      function inner(b){
        return b + one;
      }
      return inner(a);
    }
    

    上面的函数不会进行尾调用优化,因为内层函数inner用到了外层函数addOne的内部变量one。

    5. 对象扩展
    • ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
    var foo = 'bar';var baz = {foo};
    baz // {foo: "bar"
    }// 等同于
    var baz = {foo: foo};
    

    上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。下面是另一个例子。

    function f(x, y) { return {x, y};}
    // 等同于
    function f(x, y) { return {x: x, y: y};}
    f(1, 2)
    //{x:1,y:2}
    
    • 属性名表达式

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

    // 方法一
    obj.foo = true;
    
    // 方法二
    obj['a' + 'bc'] = 123;
    

    上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。

    • Object.is
      它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
    Object.is('foo', 'foo')
    // true
    Object.is({}, {})
    // false
    
    • Object.assign
      它用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
    var target = { a: 1 };
    var source1 = { b: 2 };
    var source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    

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

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

    • 对象属性的枚举。

    相关文章

      网友评论

          本文标题:开发过程中遇到的一些奇妙的事情(es6):

          本文链接:https://www.haomeiwen.com/subject/xqaclxtx.html