ES6函式的擴充套件
1.函式的預設值
es5
{
function logEvent(x, y) {
y = y || 3;
console.log(x + y)
}
logEvent('2', '3'); //2,3
logEvent('2'); //2,3
logEvent('2', ''); //2,3
}
這個例子中,前面兩個顯示是我們想要的結果,如果引數y賦值了,但是對應的布林值為false,則該賦值不起作用。
就像上面程式碼的最後一行,引數y等於空字元,結果被改為預設值。
解決方法,先判斷,
{ function logEvent(x, y) { if (typeof y === 'undefined') { y = 'World'; } console.log(x + y) } logEvent('2', '3'); //2,3 logEvent('2'); //2,World logEvent('2', ''); //2 }
ES6 允許為函式的引數設定預設值,即直接寫在引數定義的後面。
{
function logEvent(x, y = 'world') {
console.log(x + y)
}
logEvent('2', '3'); //2,3
logEvent('2'); //2,world
logEvent('2', ''); //2
}
{
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
console.log(p) // { x: 0, y: 0 }
}
ES6 的寫法還有兩個好處:首先,閱讀程式碼的人,可以立刻意識到哪些引數是可以省略的,
不用檢視函式體或文件;其次,有利於將來的程式碼優化,即使未來的版本在對外介面中,徹底拿掉這個引數,
也不會導致以前的程式碼無法執行。
引數變數是預設宣告的,所以不能用let或const再次宣告。
{
function logEvent(x = 5) {
var x = 7 //不報錯
let x = 4 //報錯
const x = 6 //報錯
}
}
使用引數預設值時,函式不能有同名引數。
{// 不報錯 function foo(x, x, y) { // ... } // 報錯 // function foo(x, x, y = 1) { // // ... // } }
另外,一個容易忽略的地方是,引數預設值不是傳值的,而是每次都重新計算預設值表示式的值。也就是說,引數預設值是惰性求值的。
{
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
}
2.與解構賦值預設值結合使用
{
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就不會生成,從而報錯。
所以
{
function foo({ x, y = 5 } = {}) {
console.log(x, y)
}
foo();
}
另一案例
{
function fetch(url, { body = ' ', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
// fetch('http://example.com')
// 報錯
}
如果函式fetch的第二個引數是一個物件,就可以為它的三個屬性設定預設值。這種寫法不能省略第二個引數,
{
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method);
}
fetch('http://example.com') //get
}
下面介紹下下面這兩種設定預設值的方法
{
// 寫法一
function m1({ x = 0, y = 0 } = {}) {
return [x, y];
}
// 寫法二
function m2({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}
函式沒有引數的情況
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的情況
m1({ x: 3, y: 8 }) // [3, 8]
m2({ x: 3, y: 8 }) // [3, 8]
// x 有值,y 無值的情況
m1({ x: 3 }) // [3, 0]
m2({ x: 3 }) // [3, undefined]
// x 和 y 都無值的情況
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({ z: 3 }) // [0, 0]
m2({ z: 3 }) // [undefined, undefined]
}
上面兩種寫法都對函式的引數設定了預設值,區別是寫法一函式引數的預設值是空物件,但是設定了物件解構賦值的預設值;
寫法二函式引數的預設值是一個有具體屬性的物件,但是沒有設定物件解構賦值的預設值。
3.引數預設值的位置
如果非尾部的引數設定預設值,實際上這個引數是沒法省略的。
例一
{
function f(x = 1, y) {
conosle.log([x, y]);
}
f() // [1, undefined]
f(2) // [2, undefined])
// f(, 1) // 報錯
f(undefined, 1) // [1, 1]
例二
function f(x, y = 5, z) {
console.log([x, y, z]);
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
// f(1, , 2) // 報錯
f(1, undefined, 2) // [1, 5, 2]
f(1, null, 2); //[1,null,1]
}
上面程式碼中,有預設值的引數都不是尾引數。這時,無法只省略該引數,而不省略它後面的引數,除非顯式輸入undefined。
如果傳入undefined,將觸發該引數等於預設值,null則沒有這個效果
4.函式的length屬性
指定了預設值以後,函式的length屬性,將返回沒有指定預設值的引數個數。也就是說,指定了預設值後,length屬性將失真。
// 如果設定了預設值的引數不是尾引數,那麼length屬性也不再計入後面的引數了。
{
console.log((function (a) { }).length); //1
console.log((function (a, b = 3, c) { }).length); //
console.log((function (a = 4, b, c) { }).length); //0
}
5.作用域
一旦設定了引數的預設值,函式進行宣告初始化時,引數會形成一個單獨的作用域(context)。等到初始化結束,
這個作用域就會消失。這種語法行為,在不設定引數預設值時,是不會出現的。
{
let 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。
6.rest引數
ES6 引入 rest 引數(形式為...變數名),用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。
rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。
求和
{
function Add(...value) {
let sum = 0;
for (let i of value) {
sum += i;
}
console.log(sum);
}
Add(2, 4, 5);
}
rest 引數代替arguments變數的例子
{
arguments變數的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
rest引數的寫法
// const sortNumbers = (...numbers) => numbers.sort();
}
arguments物件不是陣列,而是一個類似陣列的物件。所以為了使用陣列的方法,必須使用Array.prototype.slice.call先將其轉為陣列。
rest 引數就不存在這個問題,它就是一個真正的陣列,陣列特有的方法都可以使用。下面是一個利用 rest 引數改寫陣列push方法的例子。
注意,rest 引數之後不能再有其他引數(即只能是最後一個引數),否則會報錯。
// 報錯
// function f(a, ...b, c) {
// // ...
// }
函式的length屬性,不包括 rest 引數。
{
console.log((function (a) { }).length) // 1
console.log((function (...a) { }).length) // 0
console.log((function (a, ...b) { }).length) // 1
}
7.嚴格模式
ES2016 做了一點修改,規定只要函式引數使用了預設值、解構賦值、或者擴充套件運算子,那麼函式內部就不能顯式設定為嚴格模式,否則會報錯。
兩種方法可以規避這種限制。第一種是設定全域性性的嚴格模式,這是合法的。
{
'use strict';
function doSomething(a, b = a) {
// code
}
}
第二種是把函式包在一個無引數的立即執行函式裡面。
{
const doSomething = (function () {
'use strict';
return function (value = 42) {
return value;
};
}());
}
8.name 屬性
{
function f() {
console.log(f.name)
}
f()
let f1 = function () {
// es5
console.log(f1.name)//''
// es6
console.log(f1.name)//f1
}
f1();
let f2 = function func2() {
console.log(func2.name)
}
f2();
}
函式的name屬性,返回該函式的函式名。
Function建構函式返回的函式例項,name屬性的值為anonymous。
{
(new Function).name // "anonymous"
}
bind返回的函式,name屬性值會加上bound字首。
{
function foo() { };
// foo.bind({}).name // "bound foo"
(function () { }).bind({}).name // "bound "
}