ES6:函數(shù)

標(biāo)簽: rest spread 箭頭函數(shù) JavaScript ES6 前端 web
本博客版權(quán)歸本人和饑人谷所有,轉(zhuǎn)載需說明來源
內(nèi)容轉(zhuǎn)載自阮一峰老師的ES6入門

1.rest參數(shù)

ES6 引入 rest 參數(shù)(形式為“...變量名”),用于獲取函數(shù)的多余參數(shù),這樣就不需要使用arguments對象了。rest 參數(shù)搭配的變量是一個數(shù)組,該變量將多余的參數(shù)放入數(shù)組中。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

上面代碼的add函數(shù)是一個求和函數(shù),利用 rest 參數(shù),可以向該函數(shù)傳入任意數(shù)目的參數(shù)。

下面是一個 rest 參數(shù)代替arguments變量的例子。

// arguments變量的寫法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest參數(shù)的寫法
const sortNumbers = (...numbers) => numbers.sort();

上面代碼的兩種寫法,比較后可以發(fā)現(xiàn),rest 參數(shù)的寫法更自然也更簡潔。

rest 參數(shù)中的變量代表一個數(shù)組,所以數(shù)組特有的方法都可以用于這個變量。下面是一個利用 rest 參數(shù)改寫數(shù)組push方法的例子。

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

注意,rest 參數(shù)之后不能再有其他參數(shù)(即只能是最后一個參數(shù)),否則會報錯。

// 報錯
function f(a, ...b, c) {
  // ...
}

函數(shù)的length屬性,不包括 rest 參數(shù)。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

2.擴展運算符

含義

擴展運算符(spread)是三個點(...)。它好比 rest 參數(shù)的逆運算,將一個數(shù)組轉(zhuǎn)為用逗號分隔的參數(shù)序列。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

該運算符主要用于函數(shù)調(diào)用。

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

var numbers = [4, 38];
add(...numbers) // 42

上面代碼中,array.push(...items)和add(...numbers)這兩行,都是函數(shù)的調(diào)用,它們的都使用了擴展運算符。該運算符將一個數(shù)組,變?yōu)閰?shù)序列。

擴展運算符與正常的函數(shù)參數(shù)可以結(jié)合使用,非常靈活。

function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...[3]);

替代數(shù)組apply方法

由于擴展運算符可以展開數(shù)組,所以不再需要apply方法,將數(shù)組轉(zhuǎn)為函數(shù)的參數(shù)了。

// ES5的寫法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的寫法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f(...args);

下面是擴展運算符取代apply方法的一個實際的例子,應(yīng)用Math.max方法,簡化求出一個數(shù)組最大元素的寫法。

// ES5的寫法
Math.max.apply(null, [14, 3, 77])

// ES6的寫法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

上面代碼表示,由于JavaScript不提供求數(shù)組最大元素的函數(shù),所以只能套用Math.max函數(shù),將數(shù)組轉(zhuǎn)為一個參數(shù)序列,然后求最大值。有了擴展運算符以后,就可以直接用Math.max了。

另一個例子是通過push函數(shù),將一個數(shù)組添加到另一個數(shù)組的尾部。

// ES5的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

上面代碼的ES5寫法中,push方法的參數(shù)不能是數(shù)組,所以只好通過apply方法變通使用push方法。有了擴展運算符,就可以直接將數(shù)組傳入push方法。

下面是另外一個例子。

// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);    

3.箭頭函數(shù)

基本用法

ES6允許使用“箭頭”(=>)定義函數(shù)。

var f = v => v;

上面的箭頭函數(shù)等同于:

var f = function(v) {
  return v;
};

如果箭頭函數(shù)不需要參數(shù)或需要多個參數(shù),就使用一個圓括號代表參數(shù)部分。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭頭函數(shù)的代碼塊部分多于一條語句,就要使用大括號將它們括起來,并且使用return語句返回。

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

由于大括號被解釋為代碼塊,所以如果箭頭函數(shù)直接返回一個對象,必須在對象外面加上括號。

var getTempItem = id => ({ id: id, name: "Temp" });

箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

箭頭函數(shù)使得表達更加簡潔。

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

上面代碼只用了兩行,就定義了兩個簡單的工具函數(shù)。如果不用箭頭函數(shù),可能就要占用多行,而且還不如現(xiàn)在這樣寫醒目。

箭頭函數(shù)的一個用處是簡化回調(diào)函數(shù)。

// 正常函數(shù)寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數(shù)寫法
[1,2,3].map(x => x * x);

另一個例子是

// 正常函數(shù)寫法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭頭函數(shù)寫法
var result = values.sort((a, b) => a - b);

下面是rest參數(shù)與箭頭函數(shù)結(jié)合的例子。

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]

使用注意點

箭頭函數(shù)有幾個使用注意點。

(1)函數(shù)體內(nèi)的this對象,就是定義時所在的對象,而不是使用時所在的對象。

(2)不可以當(dāng)作構(gòu)造函數(shù),也就是說,不可以使用new命令,否則會拋出一個錯誤。

(3)不可以使用arguments對象,該對象在函數(shù)體內(nèi)不存在。如果要用,可以用Rest參數(shù)代替。

(4)不可以使用yield命令,因此箭頭函數(shù)不能用作Generator函數(shù)。

上面四點中,第一點尤其值得注意。this對象的指向是可變的,但是在箭頭函數(shù)中,它是固定的。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

上面代碼中,setTimeout的參數(shù)是一個箭頭函數(shù),這個箭頭函數(shù)的定義生效是在foo函數(shù)生成時,而它的真正執(zhí)行要等到100毫秒后。如果是普通函數(shù),執(zhí)行時this應(yīng)該指向全局對象window,這時應(yīng)該輸出21。但是,箭頭函數(shù)導(dǎo)致this總是指向函數(shù)定義生效時所在的對象(本例是{id: 42}),所以輸出的是42。

箭頭函數(shù)可以讓setTimeout里面的this,綁定定義時所在的作用域,而不是指向運行時所在的作用域。下面是另一個例子。

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭頭函數(shù)
  setInterval(() => this.s1++, 1000);
  // 普通函數(shù)
  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函數(shù)內(nèi)部設(shè)置了兩個定時器,分別使用了箭頭函數(shù)和普通函數(shù)。前者的this綁定定義時所在的作用域(即Timer函數(shù)),后者的this指向運行時所在的作用域(即全局對象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都沒更新。

箭頭函數(shù)可以讓this指向固定化,這種特性很有利于封裝回調(diào)函數(shù)。下面是一個例子,DOM事件的回調(diào)函數(shù)封裝在一個對象里面。

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

上面代碼的init方法中,使用了箭頭函數(shù),這導(dǎo)致這個箭頭函數(shù)里面的this,總是指向handler對象。否則,回調(diào)函數(shù)運行時,this.doSomething這一行會報錯,因為此時this指向document對象。

this指向的固定化,并不是因為箭頭函數(shù)內(nèi)部有綁定this的機制,實際原因是箭頭函數(shù)根本沒有自己的this,導(dǎo)致內(nèi)部的this就是外層代碼塊的this。正是因為它沒有this,所以也就不能用作構(gòu)造函數(shù)。

所以,箭頭函數(shù)轉(zhuǎn)成ES5的代碼如下。

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

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

上面代碼中,轉(zhuǎn)換后的ES5版本清楚地說明了,箭頭函數(shù)里面根本沒有自己的this,而是引用外層的this。

請問下面的代碼之中有幾個this?

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代碼之中,只有一個this,就是函數(shù)foo的this,所以t1、t2、t3都輸出同樣的結(jié)果。因為所有的內(nèi)層函數(shù)都是箭頭函數(shù),都沒有自己的this,它們的this其實都是最外層foo函數(shù)的this。

除了this,以下三個變量在箭頭函數(shù)之中也是不存在的,指向外層函數(shù)的對應(yīng)變量:arguments、super、new.target。

function foo() {
  setTimeout(() => {
    console.log('args:', arguments);
  }, 100);
}

foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]

上面代碼中,箭頭函數(shù)內(nèi)部的變量arguments,其實是函數(shù)foo的arguments變量。

另外,由于箭頭函數(shù)沒有自己的this,所以當(dāng)然也就不能用call()、apply()、bind()這些方法去改變this的指向。

(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' });
// ['outer']

上面代碼中,箭頭函數(shù)沒有自己的this,所以bind方法無效,內(nèi)部的this指向外部的this。

長期以來,JavaScript語言的this對象一直是一個令人頭痛的問題,在對象方法中使用this,必須非常小心。箭頭函數(shù)”綁定”this,很大程度上解決了這個困擾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,708評論 0 1
  • 1.函數(shù)參數(shù)的默認(rèn)值 (1).基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法。
    趙然228閱讀 831評論 0 0
  • ES6函數(shù)的擴展 1.函數(shù)默認(rèn)值 定義:ES6允許為函數(shù)設(shè)定默認(rèn)值,即直接寫在參數(shù)定義的后面 示例function...
    lijaha閱讀 508評論 0 0
  • 三,字符串?dāng)U展 3.1 Unicode表示法 ES6 做出了改進,只要將碼點放入大括號,就能正確解讀該字符。有了這...
    eastbaby閱讀 1,672評論 0 8
  • 姓名:徐群杰 公司:寧波大發(fā) 【日精進打卡第20天】 【知~學(xué)習(xí)】 《六項精進》大綱1遍 ...
    徐群杰閱讀 239評論 0 0

友情鏈接更多精彩內(nèi)容