转行学前端的第 38 天 : 了解 ECMAScript function 箭头函数

2,480 阅读9分钟

我是小又又,住在武汉,做了两年新媒体,准备用 6 个月时间转行前端。

今日学习目标

昨天基于搜索来基础学习 Function 数据结构。今天主要是基于搜索来学习 function 箭头函数 ,又是适合学习的一天,加油,小又又!!!!


基本说明

ES6 允许使用箭头=>)定义函数。箭头函数表达式语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数


语法

基础语法

(param1, param2, …, paramN) => { statements }
// 等同于
// function(param1, param2, …, paramN){ return {statements}; }

(param1, param2, …, paramN) => expression
// 等同于
// function(param1, param2, …, paramN){ return expression; }

// Parentheses are optional when there's only one parameter name:
// 当只有一个参数时,圆括号是可选的
(singleParam) => { statements }
singleParam => { statements }

// 等同于
// function(singleParam){ return expression; }

// The parameter list for a function with no parameters should be written with a pair of parentheses.
// 没有参数的函数应该写成一对圆括号。
() => { statements }

高阶语法

// Parenthesize the body of function to return an object literal expression:
// 加括号的函数体返回对象字面表达式:
params => ({foo: bar})

// Rest parameters and default parameters are supported
// 支持剩余参数和默认参数
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => {
statements }

// Destructuring within the parameter list is also supported
// 同样支持参数列表解构
const f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6
```

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

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

这一段出现了好些新名词,剩余参数和默认参数,解构,感觉有些看不太懂~~~~


箭头函数变量作用域

箭头函数内定义的变量及其作用域

常规内部变量

    // 常规写法
    const greeting_1 = () => {let now = new Date(); return ("Good" + ((now.getHours() > 17) ? " evening." : " day."));}
    console.log(greeting_1());          //"Good evening."
    console.log(now);    // ReferenceError: now is not defined 标准的let作用域

形参

参数括号内定义的变量是局部变量(默认参数)

    const greeting_2 = (now=new Date()) => "Good" + (now.getHours() > 17 ? " evening." : " day.");
    console.log(greeting_2());          //"Good evening."
    console.log(now);    // ReferenceError: now is not defined

函数体内定义变量

函数体内{}不使用var定义的变量是全局变量

    const greeting_3 = () => {now = new Date(); return ("Good" + ((now.getHours() > 17) ? " evening." : " day."));}
    console.log(greeting_3());           //"Good evening."
    console.log(now);     // Wed Apr 24 2019 23:02:07 GMT+0800 (中国标准时间)


函数体内{}const定义的变量是局部变量

    const greeting_4 = () => {const now = new Date(); return ("Good" + ((now.getHours() > 17) ? " evening." : " day."));}
    console.log(greeting_4()); //"Good evening."
    console.log(now);    // ReferenceError: now is not defined


主要解决问题

视觉优化

无参数箭头函数在视觉上容易分析

    setTimeout( () => {
      console.log('I happen sooner');
      setTimeout( () => {
        // deeper code
        console.log('I happen later');
      }, 1);
    }, 1);

更短的函数

    const elements = [
      'Hydrogen',
      'Helium',
      'Lithium',
      'Beryllium'
    ];

    console.log(elements.map(function(element) { 
      return element.length; 
    })); // [8, 6, 7, 9]

    // 上面的普通函数可以改写成如下的箭头函数
    console.log(elements.map((element) => {
      return element.length;
    })); // [8, 6, 7, 9]

    // 当箭头函数只有一个参数时,可以省略参数的圆括号
    console.log(elements.map(element => {
    return element.length;
    })); // [8, 6, 7, 9]

    // 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
    console.log(elements.map(element => element.length)); // [8, 6, 7, 9]

    // 在这个例子中,因为我们只需要 `length` 属性,所以可以使用参数解构
    // 需要注意的是字符串 `"length"` 是我们想要获得的属性的名称,而 `lengthFooBArX` 则只是个变量名,
    // 可以替换成任意合法的变量名
    console.log(elements.map(({ "length": lengthFooBArX }) => lengthFooBArX)); // [8, 6, 7, 9]

不绑定this

箭头函数出现之前,每个新定义的函数都有它自己的 this值(在构造函数的情况下是一个新对象,在严格模式的函数调用中为 undefined,如果该函数被作为对象方法调用则为基础对象等)。This被证明是令人厌烦的面向对象风格的编程。

    function Person() {
      // Person() 构造函数定义 `this`作为它自己的实例.
      this.age = 0;

      setInterval(function growUp() {
        // 在非严格模式, growUp()函数定义 `this`作为全局对象, 
        // 与在 Person()构造函数中定义的 `this`并不相同.
        this.age++;
      }, 1000);
    }

    const p = new Person();
    console.log(p);// Person {age: 0}

ECMAScript 3/5中,通过将this值分配给封闭变量,可以解决this问题。

    function Person() {
      const that = this;
      that.age = 0;

      setInterval(function growUp() {
        //  回调引用的是`that`变量, 其值是预期的对象. 
        that.age++;
      }, 1000);
    }

或者,可以创建绑定函数,以便将预先分配的this值传递到绑定的目标函数(上述示例中的growUp()函数)。

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:

    function Person(){
      this.age = 0;

      setInterval(() => {
        this.age++; // |this| 正确地指向 p 实例
      }, 1000);
    }

    const p = new Person();

不绑定arguments

箭头函数不绑定Arguments 对象。因此,在本示例中,arguments只是引用了封闭作用域内的arguments

    const arguments = [1, 2, 3];
    const arr = () => arguments[0];

    console.log(arr()); // 1

    function foo(n) {
      const f = () => arguments[0] + n; // 隐式绑定 foo 函数的 arguments 对象. arguments[0] 是 n
      return f();
    }

    console.log(foo(1)); // 2

在大多数情况下,使用剩余参数是相较使用arguments对象的更好选择。

    function foo(arg) {
      const f = (...args) => args[0];
      return f(arg);
    }
    console.log(foo(1)); // 1

    function foo(arg1,arg2) {
        const f = (...args) => args[1];
        return f(arg1,arg2);
    }
    console.log(foo(1,2));  //2

注意事项

方法函数

箭头函数表达式非方法函数是最合适的。如果试着把它们作为方法时发生了什么。

    'use strict';
    const obj = {
      i: 10,
      b: () => console.log(this.i, this),
      c: function() {
        console.log(this.i,this)
      }
    }
    console.log(obj.b());
    // undefined Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
    // undefined
    console.log(obj.c());
    // 10 {i: 10, b: ƒ, c: ƒ}
    // undefined

箭头函数没有定义this绑定。


Object.defineProperty()

涉及Object.defineProperty()的示例

    'use strict';
    const obj = {
      a: 10
    };

    Object.defineProperty(obj, "b", {
      get: () => {
        console.log(this.a, typeof this.a, this);
        return this.a+10; // NaN
        // 代表全局对象 'Window', 因此 'this.a' 返回 'undefined'
      }
    });

    console.log(obj.b); // undefined   "undefined"   Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

new 操作符

箭头函数不能用作构造器,和 new一起用会抛出错误。

    const Foo = () => {};
    const foo = new Foo(); // TypeError: Foo is not a constructor

prototype 属性

箭头函数没有prototype属性。

    const Foo = () => {};
    console.log(Foo.prototype); // undefined

yield

yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。因此,箭头函数不能用作生成器


返回值为对象字面量

记住用params => {object:literal}这种简单的语法返回对象字面量是行不通的。

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

    // 报错
    // let getTempItem = id => { id: id, name: "Temp" };

    // 不报错 但是没有取得指定返回值
    let getTempItem_1 = id => { id: id };

    // 不报错 可以取得指定返回值
    let getTempItem_2 = id => ({ id: id, name: "Temp" });

    console.log(getTempItem_1(123));// undefined
    console.log(getTempItem_2(12));// {id: 12, name: "Temp"}

上面代码getTempItem_1中,原始意图是返回一个对象{ id: id },但是由于引擎认为大括号代码块,所以执行了一行语句id: id

这时,id可以被解释为语句标签,因此实际执行的语句id;,然后函数就结束了,没有返回值。


省略大括号

如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

    let fn = () => void doesNotReturn();

换行

箭头函数参数箭头之间不能换行。

  const func = ()
            => 1;
  // SyntaxError: expected expression, got '=>'

解析顺序

虽然箭头函数中的箭头不是运算符,但箭头函数具有与常规函数不同的特殊[运算符优先级][Operator-Precedence]解析规则。

    let callback;

    callback = callback || function() {}; // ok

    callback = callback || () => {};
    // SyntaxError: invalid arrow-function arguments

    callback = callback || (() => {});    // ok

函数体

箭头函数可以有一个简写体或常见的块体

在一个简写体中,只需要一个表达式,并附加一个隐式返回值。在块体中,必须使用明确的return语句。

  var func = x => x * x;
  // 简写函数 省略return

  var func = (x, y) => { return x + y; };
  //常规编写 明确的返回值

与严格模式的关系

鉴于 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。

    function Person() {
      this.age = 0;
      const closure = "123"
      setInterval(function growUp() {
        this.age++;
        console.log(closure);
      }, 1000);
    }

    const p = new Person();

    function PersonX() {
      'use strict'
      this.age = 0;
      const closure = "123"
      setInterval(()=>{
        this.age++;
        console.log(closure);
      }, 1000);
    }

    const px = new PersonX();
    console.log(px);// PersonX {age: 0}

严格模式的其他规则依然不变。


call & apply

由于 箭头函数没有自己的this指针,通过 call()apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。(这种现象对于bind方法同样成立)

    const adder = {
      base : 1,
      add : function(a) {
        const f = v => v + this.base;
        return f(a);
      },

      addThruCall: function(a) {
        const f = v => v + this.base;
        const b = {
          base : 2
        };
        return f.call(b, a);
      }
    };

    console.log(adder.add(1));         // 输出 2
    console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3)

使用案例

简洁基础函数

箭头函数使得表达更加简洁。

  const isEven = n => n % 2 === 0;
  const square = n => n * n;

上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。


简洁回调函数

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

  // 正常函数写法
  [1,2,3].map(function (x) {
    return x * x;
  });

  // 箭头函数写法
  [1,2,3].map(x => x * x);

另一个例子是

  // 正常函数写法
  const result = values.sort(function (a, b) {
    return a - b;
  });

  // 箭头函数写法
  const result = values.sort((a, b) => a - b);

array相关

方便数组reduce,filter,和map;

    // Easy array filtering, mapping, ...

    const arr = [5, 6, 13, 0, 1, 18, 23];

    const sum = arr.reduce((a, b) => a + b);  
    console.log(sum); // 66

    const even = arr.filter(v => v % 2 == 0); 
    console.log(even); // [6, 0, 18]

    const double = arr.map(v => v * 2);
    console.log(double); // [10, 12, 26, 0, 2, 36, 46]

IIFE


    // 空的箭头函数返回 undefined
    const empty = () => {};

    console.log((() => 'foobar')());// foobar
    // (这是一个立即执行函数表达式,可参阅 'IIFE'术语表)


三元运算符

箭头函数也可以使用条件(三元运算符

    const simple = a => a > 15 ? 15 : a;
    console.log(simple(16)); // 15
    console.log(simple(10)); // 10

    let max = (a, b) => a > b ? a : b;
    console.log(max(4, 5));// 5


参考网站

  • arrow function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
  • Church_encoding: https://en.wikipedia.org/wiki/Church_encoding
  • combinators: https://en.wikipedia.org/wiki/Fixed-point_combinator#Strict_fixed_point_combinator
  • how-to-use-arrow: https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/fig1.png
  • Operator-Precedence: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

今日学习总结


今日心情

今日主要是基于搜索来基础学习function 箭头函数,感觉用了箭头函数内容主要就是简洁了很多,然后了解到了剩余参数,默认参数和解构,之后要再详细了解一下,希望明天学到更多的内容~~~~

本文使用 mdnice 排版