解析 ES6 新增語法:奇妙的箭頭函數

  • 什么是箭頭函數东跪?看下面的語法畸陡。
  • 為什么要用箭頭函數鹰溜?一個字:短。
    • 1 箭頭函數最大的優(yōu)點就是簡短丁恭。
    • 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] 
    // 你看看,你看看盒揉,多么簡潔晋被。簡潔明了,
    // 節(jié)省打字時間刚盈,節(jié)省閱讀時間羡洛,節(jié)省理解時間。
  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 居然和外面的不一樣〗е粒看了高手的代碼腹躁,才發(fā)現使用下面這一招就解決了。
  • 大家習慣用: var self = this;
function Person() {
  var that = this;//在 ActionScript 中南蓬,我經常這么干 
  that.age = 0;//讓局部變量 that 指向 this

  setInterval(function growUp() {
    //  回調引用的是`that`變量, 其值是預期的對象. 
    // 也就是說纺非,內部函數可以訪問父級函數的局部變量,這樣就保證了同一個 that 指向同一個 this 
    that.age++;
  }, 1000);
}
  • 箭頭函數不會創(chuàng)建自己的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 的方法掸鹅,當訪問該屬性時,該方法會被執(zhí)行拦赠,
      // 方法執(zhí)行時沒有參數傳入巍沙,但是會傳入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. 解析順序
  • 雖然箭頭函數中的箭頭不是運算符,但箭頭函數具有與常規(guī)函數不同的特殊運算符優(yōu)先級解析規(guī)則炊邦。
    var callback;
    callback = callback || function(){};
    // foo = foo || () => {}; // Failed to compile.
    callback = callback || (() => {});
    // 很多情況下编矾,需要這種寫法,用于處理默認情況 
    callback();
    // 有時會這樣寫 
    callback && callback();
  1. 空的箭頭函數返回 undefined
    // 什么情況下會用到空的箭頭函數呢馁害?
    var empty = () => {};
    a = empty(); // a= undefined
  1. 立即執(zhí)行函數表達式 IIFE
    // 前面是箭頭函數定義窄俏,加上括號就是執(zhí)行
    a = (() => '箭頭函數')();// a= 箭頭函數
  • 詳解 javascript 立即執(zhí)行函數表達式(IIFE) https://www.cnblogs.com/zichi/p/4401755.html
    • 使用立即執(zhí)行函數鎖住變量保存狀態(tài)。
    • 立即執(zhí)行函數在模塊化中也大有用處碘菜。用立即執(zhí)行函數處理模塊化可以減少全局變量造成的空間污染凹蜈,構造更多的私有變量。
  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 眷蚓?如果用傳統(tǒng)的回調函數來處理異步鼻种,存在缺陷:
    • 隨著業(yè)務邏輯變復雜,回調層級會越來越深溪椎。
    • 代碼耦合度比較高普舆,不易修改。
    • 每一步操作都需要手動進行異常處理校读,比較麻煩沼侣。不科學。自動化才科學歉秫,才靠譜蛾洛。
  • Promise 的鏈式調用與中止 https://segmentfault.com/a/1190000007598894
    • 使用 promise 編碼之前,可以先思考兩個問題雁芙。一是如何鏈式調用轧膘,二是如何中止鏈式調用。
    // promise 鏈式調用開頭兔甘,生成一個 promise 對象
    function start(){
      return new Promise((resolve, reject) => {
        resolve('家里蹲大學歡迎您谎碍!');
        reject('嬰兒不能上大學');
      })
    }
    start().then(data => { //執(zhí)行 start() 函數返回的 promise 
      console.log("result of start: ", data);
      return Promise.resolve('大一過關'); // 返回一個 promise 對象 p1
    }).then(data => { //執(zhí)行 p1
      console.log("result of p1: ", data);
      return Promise.reject('大二失敗'); // 返回一個 promise 對象 p2
    }).then(data => { //執(zhí)行 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 => { //執(zhí)行 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. 箭頭函數內定義的變量及其作用域
    // 常規(guī)寫法
    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 ? '揍嘛里?' : '睡嘍邁?') + '老鄉(xiāng)';
    a = greeting();
    console.log("a=", a); // a= 揍嘛里?老鄉(xiāng)
    // 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閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件围橡,死亡現場離奇詭異,居然都是意外死亡缕贡,警方通過查閱死者的電腦和手機翁授,發(fā)現死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來善绎,“玉大人黔漂,你說我怎么就攤上這事≠鹘矗” “怎么了炬守?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剂跟。 經常有香客問我减途,道長,這世上最難降的妖魔是什么曹洽? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任鳍置,我火速辦了婚禮,結果婚禮上送淆,老公的妹妹穿的比我還像新娘税产。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布辟拷。 她就那樣靜靜地躺著撞羽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衫冻。 梳的紋絲不亂的頭發(fā)上诀紊,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音隅俘,去河邊找鬼邻奠。 笑死,一個胖子當著我的面吹牛为居,可吹牛的內容都是我干的碌宴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蒙畴,長吁一口氣:“原來是場噩夢啊……” “哼唧喉!你這毒婦竟也來了?” 一聲冷哼從身側響起忍抽,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤八孝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鸠项,有當地人在樹林里發(fā)現了一具尸體干跛,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年祟绊,在試婚紗的時候發(fā)現自己被綠了楼入。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡牧抽,死狀恐怖嘉熊,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情扬舒,我是刑警寧澤阐肤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站讲坎,受9級特大地震影響孕惜,放射性物質發(fā)生泄漏。R本人自食惡果不足惜晨炕,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一衫画、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓮栗,春花似錦削罩、人聲如沸瞄勾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丰榴。三九已至,卻和暖如春秆撮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背换况。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工职辨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人戈二。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓舒裤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親觉吭。 傳聞我的和親對象是個殘疾皇子腾供,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容

  • 函數參數的默認值 基本用法 在ES6之前,不能直接為函數的參數指定默認值鲜滩,只能采用變通的方法伴鳖。 上面代碼檢查函數l...
    呼呼哥閱讀 3,363評論 0 1
  • 函數參數的默認值 基本用法 在ES6之前,不能直接為函數的參數指定默認值徙硅,只能采用變通的方法榜聂。 上面代碼檢查函數l...
    陳老板_閱讀 447評論 0 1
  • 素材來源于手機壁紙管家里的一副手繪作品,很贊嗓蘑。
    迷醉233閱讀 425評論 0 3
  • 開發(fā)Android時须肆,我們通常會為了更合理,高效桩皿,優(yōu)質的開發(fā)項目豌汇,并不是上來就直接開始開發(fā)功能,而是會編寫一些暫時...
    磨礪營閱讀 527評論 0 3
  • 是時候放下了泄隔,放下那個不成熟的自己拒贱,放下懶惰的自己,放下一直給別人添麻煩的自己佛嬉。 對于自己來說自己比想象中更加強大...
    長頸鹿感恩每一天閱讀 204評論 0 2