es6 函式擴充套件,引數作用域和箭頭函式
函式的擴充套件
函式引數的預設值
基本用法
ES6 之前,不能直接為函式的引數指定預設值,只能採用變通的方法。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
與解構賦值預設值結合使用
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
引數預設值的位置
通常情況下,定義了預設值的引數,應該是函式的尾引數。因為這樣比較容易看出來,到底省略了哪些引數。如果非尾部的引數設定預設值,實際上這個引數是沒法省略的。
// 例一
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 報錯
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報錯
f(1, undefined, 2) // [1, 5, 2]
函式的 length 屬性
指定了預設值以後,函式的length
屬性,將返回沒有指定預設值的引數個數。也就是說,指定了預設值後,length
屬性將失真。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
- fn.length 返回形參個數
- arguments.length 返回實參個數
*作用域
一旦設定了引數的預設值,函式進行宣告初始化時,引數會形成一個單獨的作用域。等到初始化結束,這個作用域就會消失。這種語法行為,在不設定引數預設值時,是不會出現的。
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
上面程式碼中,引數y
的預設值等於變數x
。呼叫函式f
時,引數形成一個單獨的作用域。在這個作用域裡面,預設值變數x
指向第一個引數x
,而不是全域性變數x
,所以輸出是2
。
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
上面程式碼中,函式f
呼叫時,引數y = x
形成一個單獨的作用域。這個作用域裡面,變數x
本身沒有定義,所以指向外層的全域性變數x
。函式呼叫時,函式體內部的區域性變數x
影響不到預設值變數x
。
var x = 1;
function foo(x = x) {
// ...
}
foo() // ReferenceError: x is not defined
上面程式碼中,引數x = x
形成一個單獨作用域。實際執行的是let x = x
,由於暫時性死區的原因,這行程式碼會報錯”x 未定義“。
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo()//3
x//1
如果將var x = 3
的var
去除,函式foo
的內部變數x
就指向第一個引數x
,與匿名函式內部的x
是一致的,所以最後輸出的就是2
,而外層的全域性變數x
依然不受影響。
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}
foo() // 2
x // 1
rest 引數
ES6 引入 rest 引數(形式為...變數名
),用於獲取函式的多餘引數,這樣就不需要使用arguments
物件了。rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
arguments
物件不是陣列,而是一個類似陣列的物件。所以為了使用陣列的方法,必須使用Array.prototype.slice.call
先將其轉為陣列。rest 引數就不存在這個問題,它就是一個真正的陣列,陣列特有的方法都可以使用。下面是一個利用 rest 引數改寫陣列push
方法的例子。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
注意,rest 引數之後不能再有其他引數(即只能是最後一個引數),否則會報錯。
// 報錯
function f(a, ...b, c) {
// ...
}
函式的length
屬性,不包括 rest 引數。
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
嚴格模式
從 ES5 開始,函式內部可以設定為嚴格模式。
ES2016 做了一點修改,規定只要函式引數使用了預設值、解構賦值、或者擴充套件運算子,那麼函式內部就不能顯式設定為嚴格模式,否則會報錯
name 屬性
返回函式名。
function foo() {}
foo.name // "foo"
var f = function () {}; // "f"
箭頭函式
基本用法
ES6 允許使用“箭頭”(=>
)定義函式。
var f = v => v;
//上面的箭頭函式等同於
var f = function(v) {
return v;
};
如果箭頭函式不需要引數或需要多個引數,就使用一個圓括號代表引數部分。
var f = () => 5;
// 等同於
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return
語句返回。
var sum = (num1, num2) => { return num1 + num2; }
由於大括號被解釋為程式碼塊,所以如果箭頭函式直接返回一個物件,必須在物件外面加上括號,否則會報錯。
// 報錯
let getTempItem = id => { id: id, name: "Temp" };
// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });
使用注意點
箭頭函式有幾個使用注意點。
- 函式體內的
this
物件,就是定義時所在的物件,而不是使用時所在的物件。 - 不可以當作建構函式,也就是說,不可以使用
new
命令,否則會丟擲一個錯誤。 - 不可以使用
arguments
物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替。 - 不可以使用
yield
命令,因此箭頭函式不能用作 Generator 函式。
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);
}
//轉換後的 ES5 版本清楚地說明了,箭頭函式裡面根本沒有自己的this,而是引用外層的this。
由於箭頭函式沒有自己的this
,所以當然也就不能用call()
、apply()
、bind()
這些方法去改變this
的指向。
函式引數的尾逗號
ES2017 允許函式的最後一個引數有尾逗號(trailing comma)。
這樣的規定也使得,函式引數與陣列和物件的尾逗號規則,保持一致了。
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);