解析 ES6 新增语法:奇妙的箭头函数

  • 什么是箭头函数?看下面的语法。
  • 为什么要用箭头函数?一个字:短。
    • 1 箭头函数最大的优点就是简短。
    • 2 箭头函数不能用作构造函数。适用于那些本来需要匿名函数的地方。
    • 3 引入箭头函数有两个方面的作用:更简短的函数并且不绑定this。

1. 箭头函数语法

  1. 标准形式:块体

(参数1, 参数2, …, 参数N) => { 函数体 }

    var arrowFunc1 = (x, y, z) => {
      if (x > 0){
        return y + z;
      }else{
        return y -z;
      }
    }
    a = arrowFunc1(1, 2, 3);//5
    b = arrowFunc1(-1, 2, 3);//-1
  1. 如果函数体只有一个表达式,可以写成:简写体,省略 return

(参数1, 参数2, …, 参数N) => 表达式

    // 你看看,多么简洁
    var arrowFunc = (x, y) => x + y;
    a = arrowFunc(1, 2);//3
  1. 如果只有一个参数,可以写成:

单一参数 => {函数体}

单一参数 => 表达式

    var arrowFunc = x => x * 2;
    a = arrowFunc(2);//4
  1. 如果没有参数,应该写成一对圆括号。

() => {函数体}

() => 表达式

    var arrowFunc = () => console.log('hello world');
    arrowFunc(); //hello world
  1. 支持剩余参数和默认参数

(参数1, 参数2, ...rest) => {函数体}

(参数1 = 默认值1,参数2 = 默认值2, …, 参数N = 默认值N) => {函数体}

  1. 支持参数列表解构
  • ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
    var arrowFunc = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
    a = arrowFunc(); // 6

2. 深入理解箭头函数

  1. 更短的函数
    var materials = [
      'Hydrogen',
      'Helium',
      'Lithium',
      'Beryllium'
    ];

    // 使用普通函数: 求每个元素的长度
    a = materials.map(function(elem){
      return elem.length;
    }); //[8, 6, 7, 9] 
    
    // 使用箭头函数: 求每个元素的长度
    a = materials.map(elem => elem.length); //[8, 6, 7, 9] 
    // 你看看,你看看,多么简洁。简洁明了,
    // 节省打字时间,节省阅读时间,节省理解时间。
  1. 不绑定this
  • 在箭头函数出现之前,每个新定义的函数都有它自己的 this 值。
  • This 被证明是令人厌烦的面向对象风格的编程。(我倒是很喜欢 this,看见引用类属性而不加 this 的代码,我就想给加上。)
  • this 确实会带来一些问题,比如下面的内部函数中的 this 和外面的 this 分别指向不同的对象。但是我们希望它们指向同一个对象。
    function Person() {
      // Person() 构造函数定义 `this`作为它自己的实例.
      // 也就是说,这个 this 指向 Person() 的实例。
      this.age = 10;
      console.log("this.age=", this.age); //输出 10
      setInterval(function growUp() {
        // 在非严格模式, growUp()函数定义 `this`作为全局对象, 
        // 与在 Person()构造函数中定义的 `this`并不相同.
        // 也就是说,这个 this 指向全局对象而不是 Person() 的实例,所以计算结果是 NaN
        this.age++;
        console.log("this.age=", this.age);
      }, 1000);
    }
    a = new Person();
  • 在ECMAScript 3/5中,通过将this值分配给封闭的变量(局部变量),可以解决this问题。
  • 还记得 2008 年第一次遇到 this 引起的 bug 时,查了半天,函数闭包中的 this 居然和外面的不一样。看了高手的代码,才发现使用下面这一招就解决了。
  • 大家习惯用: var self = this;
function Person() {
  var that = this;//在 ActionScript 中,我经常这么干 
  that.age = 0;//让局部变量 that 指向 this

  setInterval(function growUp() {
    //  回调引用的是`that`变量, 其值是预期的对象. 
    // 也就是说,内部函数可以访问父级函数的局部变量,这样就保证了同一个 that 指向同一个 this 
    that.age++;
  }, 1000);
}
  • 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:
  • 箭头函数很好地解决了 this 问题,this 就是 this,只有一个 this,不必再为不同的 this 头疼了。
  • 不绑定 this,也就是说作用域不把 this 据为己有,而是继承作用域链上一层的 this,大家共用一个 this 。
    function Person(){
      this.age = 0;//这个 this 指向 Person 实例
      console.log("Person() this.age=", this.age); 
      // 下面的 this 继承父级函数的 this 也指向 Person 实例
      setInterval(() => {
        this.age++;
        console.log("箭头函数 this.age=", this.age);
      }, 1000);
    }
    
    a = new Person();
    console.log("全局对象 this.age=", this.age);//这个 this 指向全局对象 
    /** 输出如下
Person() this.age= 0
全局对象 this.age= undefined
箭头函数 this.age= 1
箭头函数 this.age= 2
箭头函数 this.age= 3 每秒输出一条
    */
  1. 通过 call 或 apply 调用箭头函数,thisArg 会被忽略
    var adder = {
      base: 1,
      add: function(a){
        // 箭头函数 this 能访问到上层的 base 
        var f = v => v + this.base;
        return f(a);//返回的是箭头函数的计算结果 
      },
      addThruCall: function(a){
        var f = v => v + this.base;
        var b = {
          base: 2
        };
        // 通过 call() 调用箭头函数 f 指定参数 thisArg 的值是 b,
        // 普通函数的 this 会指向 b, 但是箭头函数会忽略 b 仍然用作用域链上层的 this 
        // 箭头函数中的 this.base 指向 adder 中的 1 而不是 b 中的 2 
        return f.call(b, a);
      },
    };
    //通过 . 语法调用,则 this 指向 adder 
    console.log(adder.add(1));         // 输出 2
    console.log(adder.addThruCall(1)); // 仍然输出 2
  1. 箭头函数不绑定 arguments 对象
  • 也就是说,在箭头函数内调用的 arguments 对象并不是箭头函数自身的,而是外层函数的。
  • 或者说,箭头函数没有自身的 arguments 对象。
    function foo(n) {
      //普通函数内部的箭头函数:arguments[0] 是 n
      var f = () => arguments[0] + n;
      return f();//不是返回箭头函数 f 而是返回 f 的计算结果 
    }
    console.log(foo(3));//输出:6
    // 不在普通函数内部,箭头函数调用 arguments 对象报错
    var arr = () => arguments[0];
    arr();
  • 如果确实需要实现箭头函数与外层函数参数对象的分离,那么最好使用剩余参数(rest parameter)。
    function foo(n) { 
      // 使用剩余参数实现箭头函数与外层函数参数对象的分离
      // args[0] 是箭头函数的第一个剩余参数 2,而不是 n 
      var f = (...args) => args[0] + n; 
      return f(2); 
    }
    console.log(foo(1));//输出:3
  1. 像函数一样使用箭头函数
  • 箭头函数本来就是函数,但它是特殊的函数,所以有些特殊行为:不绑定 this,不绑定 arguments 。
    var obj = {
      i: 10,
      // 箭头函数的 this 指向哪里? App 实例(调用方所在的类)
      b: () => console.log(this.i, this),
      // 普通函数的 this 指向哪里? obj(调用方)
      c: function(){
        console.log(this.i, this);
      }
    }
    //调用方法函数 b() 输出异常,c() 输出正确。
    obj.b(); // undefined App {props: Object, context: Object…}
    obj.c(); // 10 Object {i: 10}
  • 非方法函数用箭头函数来定义,是最佳选择。方法函数不要用箭头函数定义,否则 this 指向错误。
    var obj = {
      a: 10
    };
    // 默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
    Object.defineProperty(obj, 'b', {
      // 一个给属性提供 getter 的方法,当访问该属性时,该方法会被执行,
      // 方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的 this 并不一定是定义该属性的对象)。
      get:() => { // 使用箭头函数,this 指向哪里?App 实例(调用方所在的类)。
        // 我们不希望这样,那么就应该用普通函数。
        console.log(this.a, typeof this.a, this); // undefined "undefined" App {props: Object…}
        return this.a+10;
      }
    });
    Object.defineProperty(obj, 'c', {
      get:function () { // 使用普通函数,this 指向调用方 obj 这才是我们想要的
        console.log(this.a, typeof this.a, this); // 10 "number" Object {a: 10}
        return this.a+10;
      }
    });
    a = obj.b; // a= NaN
    b = obj.c; // b= 20 
  1. 箭头函数的 prototype 属性:箭头函数原型
    var Foo = () => {console.log('箭头函数 Foo')};
    console.log("Foo.prototype=", Foo.prototype);
    // Foo.prototype= Foo {} constructor: Foo() __proto__: Object
    Foo();// 箭头函数 Foo
    // 下面这样调用,和直接调用 Foo() 效果相同
    Foo.prototype.constructor(); // 箭头函数 Foo
  1. 返回对象字面量:要用圆括号把对象字面量包起来
    var func = () => ({foo:1});
    a = func(); // a= Object {foo: 1}
    
    // 用这种简单的语法返回对象字面量是行不通的。
    var func2 = () => { foo: 1 }; // Failed to compile.
    b = func2();
  1. yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。
  2. 箭头函数在参数和箭头之间不能换行。
  3. 解析顺序
  • 虽然箭头函数中的箭头不是运算符,但箭头函数具有与常规函数不同的特殊运算符优先级解析规则。
    var callback;
    callback = callback || function(){};
    // foo = foo || () => {}; // Failed to compile.
    callback = callback || (() => {});
    // 很多情况下,需要这种写法,用于处理默认情况 
    callback();
    // 有时会这样写 
    callback && callback();
  1. 空的箭头函数返回 undefined
    // 什么情况下会用到空的箭头函数呢?
    var empty = () => {};
    a = empty(); // a= undefined
  1. 立即执行函数表达式 IIFE
    // 前面是箭头函数定义,加上括号就是执行
    a = (() => '箭头函数')();// a= 箭头函数
  • 详解 javascript 立即执行函数表达式(IIFE) https://www.cnblogs.com/zichi/p/4401755.html
    • 使用立即执行函数锁住变量保存状态。
    • 立即执行函数在模块化中也大有用处。用立即执行函数处理模块化可以减少全局变量造成的空间污染,构造更多的私有变量。
  1. 使用箭头函数实现数组过滤、映射。。。简洁明了
    var arr = [3, 1, 2, 8, 1, 12, 21];
    //元素求和:前驱值 + 当前值 = 结果,作为下次运算的前驱值  
    var sum = arr.reduce((a, b) => a + b);// 48
    // 过滤,留下奇数,不会自动去重
    var even = arr.filter(v => v % 2 == 1); // [3, 1, 1, 21]
    // 映射:所有元素 2 倍 
    var double = arr.map(v => v * 2); // [6, 2, 4, 16, 2, 24, 42]
  1. 实现更简明的 promise 链
  • 什么是 Promise ?Promise 其实很简单,就是一个处理异步的方法。
  • 为什么要用 Promise ?如果用传统的回调函数来处理异步,存在缺陷:
    • 随着业务逻辑变复杂,回调层级会越来越深。
    • 代码耦合度比较高,不易修改。
    • 每一步操作都需要手动进行异常处理,比较麻烦。不科学。自动化才科学,才靠谱。
  • Promise 的链式调用与中止 https://segmentfault.com/a/1190000007598894
    • 使用 promise 编码之前,可以先思考两个问题。一是如何链式调用,二是如何中止链式调用。
    // promise 链式调用开头,生成一个 promise 对象
    function start(){
      return new Promise((resolve, reject) => {
        resolve('家里蹲大学欢迎您!');
        reject('婴儿不能上大学');
      })
    }
    start().then(data => { //执行 start() 函数返回的 promise 
      console.log("result of start: ", data);
      return Promise.resolve('大一过关'); // 返回一个 promise 对象 p1
    }).then(data => { //执行 p1
      console.log("result of p1: ", data);
      return Promise.reject('大二失败'); // 返回一个 promise 对象 p2
    }).then(data => { //执行 p2
      console.log("result of p2: ", data);
      return Promise.resolve('大三过关'); // 返回一个 promise 对象 p3
    }).catch(ex => { // 捕获异常
      console.log('ex: ', ex);
      return Promise.resolve('大二重修'); // 返回一个 promise 对象 p4
    }).then(data => { //执行 p4
      console.log('result of p4: ', data);
    })
    /** 上面的代码最终会输出:
    result of start:  家里蹲大学欢迎您!
    result of p1:  大一过关
    ex:  大二失败
    result of p4:  大二重修
    */ 

你看看,你-看-看,使用箭头函数多么简明!!!

  1. 箭头函数也可以使用条件(三元)运算符:
    // 定义箭头函数 simple: 条件表达式作为函数体
    var simple = a => a > 15 ? 15 : a; 
    a = simple(16); 
    b = simple(10); 
    console.log("a=", a,"b=", b); // a= 15 b= 10
    
    // 定义箭头函数 max
    let max = (a, b) => a > b ? a : b;
    a = max(1,2); 
    b = max(4,3); 
    console.log("a=", a,"b=", b); // a= 2 b= 4
  1. 箭头函数内定义的变量及其作用域
    // 常规写法
    var greeting = () => {
      let now = new Date();
      return (((now.getHours() > 22) ? '睡了吗?' : '嘎哈呢?') + '老铁');
    };
    a = greeting();
    console.log("a=", a); // a= 嘎哈呢?老铁
    // console.log("now=", now); //标准的 let 作用域:'now' is not defined 
  • 参数括号内定义的变量是局部变量(默认参数)
    var greeting = (now=new Date()) => (now.getHours() < 22 ? '揍嘛里?' : '睡喽迈?') + '老乡';
    a = greeting();
    console.log("a=", a); // a= 揍嘛里?老乡
    // console.log(now);    // 编译失败:'now' is not defined
  • 函数体内{} 用 var 定义的变量是局部变量
    var greeting = () => {
      var now = new Date(); 
      return (now.getHours() > 22 ? '睡了吗?' : '嘎哈呢?') + '老铁';
    };
    a = greeting();
    console.log("a=", a); // a= 嘎哈呢?老铁
    // console.log(now);    // 编译失败:'now' is not defined
  1. 箭头函数也可以使用闭包:
  • 标准的闭包函数
    function foo(){
      var i=0;
      // 返回一个闭包函数:函数内的函数
      return function bar(){
        // 可以访问函数 foo() 内的局部变量
        return (++i);
      };
    };
    a = foo(); //得到一个闭包函数:bar()
    b = a();   //得到计算结果:1
    c = a();   //得到计算结果:2
  • 箭头函数体的闭包
    // 定义箭头函数,返回一个箭头函数:闭包。一行代码实现,够简洁吧?
    var subtract = (i=100) => {return () => --i};
    a = subtract(); // 得到一个闭包函数:() 能访问 subtract() 的局部变量 i
    b = a(); // 得到计算结果:99
    c = a(); // 得到计算结果:98

    // 还能更简洁,但是可读性略差。可以用来装逼。。。
    var subtract2 = (i=100) => () => --i;
    a = subtract2(); 
    b = a(); // 得到计算结果:99
    c = a(); // 得到计算结果:98
  1. 箭头函数递归
    // 定义箭头函数,接收参数 x ,递归调用
    var fact = (x) => x === 0 ? 1 : x * fact(x - 1);
    a = fact(4); // 24

是不是很简洁,是不是很奇妙,是不是很吊。。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    呼呼哥阅读 3,353评论 0 1
  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    陈老板_阅读 445评论 0 1
  • 素材来源于手机壁纸管家里的一副手绘作品,很赞。
    迷醉233阅读 420评论 0 3
  • 开发Android时,我们通常会为了更合理,高效,优质的开发项目,并不是上来就直接开始开发功能,而是会编写一些暂时...
    磨砺营阅读 525评论 0 3
  • 是时候放下了,放下那个不成熟的自己,放下懒惰的自己,放下一直给别人添麻烦的自己。 对于自己来说自己比想象中更加强大...
    长颈鹿感恩每一天阅读 200评论 0 2