天天看點

ES6學習-函數的擴充概要

說明:本文參考阮一峰的ECMAScript 6 入門

概要

  1. 函數參數預設值(直接指派、解構預設指派);
  2. rest參數:(…參數名)将函數多餘參數放進一個數組,可使用數組特有方法;
  3. 嚴格模式:隻要函數參數使用了預設值、解構指派、rest參數就不能在在函數内部顯示指定嚴格模式;
  4. 箭頭函數: => ,箭頭函數沒有自己的this,其内部this指向永遠都是其定義時所在的對象。兩種不适用場合:定義對象的方法,該方法内部包含this;需要使用動态this,例如事件監聽的回調函數如果使用了箭頭函數,則該箭頭函數内部this會指向全局對象window,而不會指向監聽對象。

1.函數參數的預設值

function add(x = 1, y = 2 ){
	return x + y
}
add() //3
           

與結構指派預設值結合使用

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
           

上面代碼隻使用了對象的解構指派預設值,沒有使用函數參數的預設值。隻有當函數foo的參數是一個對象時,變量x和y才會通過解構指派生成。如果函數foo調用時沒提供參數,變量x和y就不會生成,進而報錯。通過提供函數參數的預設值,就可以避免這種情況。

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5
           

2.rest參數

ES6引入了rest參數(形式為…變量名),用于擷取函數的多餘參數,這樣就不需要使用arguments對象了。rest參數搭配的變量是一個數組,該變量将多餘的參數放進數組中。

function add(...values) {
	let sum = 0
	for (val of values) {
		sum += val
	}
	return sum
}
add(2, 3, 5) //10
           

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

下面是一個rest參數代替arguments對象的一個例子。

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

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

上面代碼的兩種寫法,很明顯rest參數的寫法更簡潔些。

arguments對象,不是一個真正的數組,它是一個類似數組的對象,不具有數組特有的方法。是以需要使用call()方法将其轉化為數組,再使用數組的方法。而rest參數是一個真正的數組,數組特有的方法都可以使用。

注意,rest參數隻能是最後一個參數,否則會報錯。

//報錯 SyntaxError: Rest parameter must be last formal parameter
function f(a, ...b,  c) {
	//...
}
           

函數的length屬性,不包括rest參數。

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

3.嚴格模式

從ES5開始,函數内部可以設定為嚴格模式。

function foo (a, b) {
	'use strict'
	//code
}
           

ES6對此做了修改,隻要函數參數使用了預設值、解構指派或者擴充運算符,就不能顯示指定嚴格模式,否則會報錯。

//報錯
function d(a = 1){
	'use strict'
}
//報錯
function e({a, b}) {
	'use strict'
}
//報錯
function e(...a) {
	'use strict'
}
           

這樣規定的原因是,函數内部指定嚴格模式,同時适用于函數參數和函數體。但函數參數先于函數體執行,這就會導緻函數參數不符合嚴格模式下時,隻會在進入函數體内部,發現需要使用嚴格模式,才會報錯。

兩種方法可以規避這種限制。

第一種是設定全局的嚴格模式。

'use strict'

function doSomething(a, b = a) {
  // code
}
           

第二種是放在立即執行函數裡。

const doSomething = (function () {
	'use strict'
	return function (value = 2) {
		return value
	}
}())
           

4.箭頭函數

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

var f = v => v

//等同于
var f = function (v) {
	return v
}

           

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

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

var sum = (a, b) => a + b
//等同于
var sum  = function (a, b) {
	return a + b
}
           

如果箭頭函數的代碼塊部分多于一條語句,那麼需要使用大括号它們括起來,并使用return語句傳回。

由于大括号被解釋為代碼塊,當函數傳回一個對象時,就需要使用圓括号将對象括起來,否則會報錯。

//報錯
let item = id => {id: id, name: "temp"}

//不報錯
let item = id => ({id: id, name: "temp"})
           

如果箭頭函數隻有一條語句,且沒有傳回值,可以采用下面的寫法,就不用寫大括号了。

箭頭函數可以與變量解構結合使用。

var sum = ({ x,  y }) => x + y
           

下面是rest參數和箭頭函數結合使用。

const numbers = (...nums) => nums
numbers(1, 2, 3, 4, 5)  // [1, 2, 3, 4, 5]
           

使用注意點

箭頭函數有幾個使用注意點。

  1. 函數體内的this對象,不是使用時所在的對象,而是函數定義時所在的對象。
  2. 不可以當做構造函數,使用new指令會報錯。
  3. 不可以使用arguments對象,該對象在箭頭函數裡不存在。如果要使用,可以用rest參數代替。
  4. 不可以使用yield指令,是以箭頭函數不能用作 Generator 函數。

上面四點中,尤其第一點需要注意。this對象的指向是可變的,但是在箭頭函數中,它的指向是固定的。

function foo() {
	setTimeout(() => {
		console.log(this.id)
	}, 100)
}
let id = 21

foo.call({id: 42})
           

上面代碼中,setTimeout的參數是一個箭頭函數,這個箭頭函數的定義生效在foo函數生成時,而它的真正執行要等到 100 毫秒後。如果是普通函數,執行時this指向應該是window,這時應該輸出21。但是,箭頭函數this指向函數定義生效時所在的對象(本例是{id: 42}),是以輸出的是42。

this指向的固定化,并不是箭頭函數内部有綁定this的機制,而是因為箭頭函數根本沒有自己的this,導緻内部的this就是外層代碼塊的this。正是因為它沒有自己的this,是以就不能當做構造函數使用。

是以,箭頭函數轉成 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);
}
           

另外由于箭頭函數沒有自己的this,是以也就不能使用call()、apply()、bind()這些方法改變this的指向。

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

上面代碼中,箭頭函數沒有自己的this,是以bind方法無效,内部的this指向外部的this。

不适用場合

由于箭頭函數的this指向是固定的,下面兩種場合就不适合使用箭頭函數。

第一個場合是定義對象的方法,且該方法内部包含this。

const cat = {
	lives: 9,
	jumps: () => {
		this.lives--
	}
}
           

上面代碼中,cat.jumps()方法是一個箭頭函數,這是錯誤的。調用cat.jumps()時,如果是普通函數,該方法内部的this指向cat;如果寫成上面那樣的箭頭函數,使得this指向全局對象,是以不會得到預期結果。這是因為對象不構成單獨的作用域,導緻jumps箭頭函數定義時的作用域就是全局作用域。

第二個場合是需要動态this的時候,也不應使用箭頭函數。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});
           

上面代碼運作時,點選按鈕會報錯,因為button的監聽函數是一個箭頭函數,導緻裡面的this就是全局對象。如果改成普通函數,this就會動态指向被點選的按鈕對象。

繼續閱讀