函數(shù)的擴展

函數(shù)參數(shù)的默認值

ES6 允許為函數(shù)的參數(shù)設置默認值访锻,即直接寫在參數(shù)定義的后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

參數(shù)變量是默認聲明的,所以不能用let或const再次聲明仇味。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

上面代碼中,參數(shù)變量x是默認聲明的雹顺,在函數(shù)體中丹墨,不能用let或const再次聲明,否則會報錯嬉愧。
使用參數(shù)默認值時贩挣,函數(shù)不能有同名參數(shù)。

// 不報錯
function foo(x, x, y) {
  // ...
}

// 報錯
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

另外没酣,一個容易忽略的地方是王财,參數(shù)默認值不是傳值的,而是每次都重新計算默認值表達式的值裕便。也就是說绒净,參數(shù)默認值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代碼中闪金,參數(shù)p的默認值是x + 1疯溺。這時,每次調用函數(shù)foo哎垦,都會重新計算x + 1囱嫩,而不是默認p等于 100。

參數(shù)默認值的位置

// 例一
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]

上面代碼中漏设,有默認值的參數(shù)都不是尾參數(shù)墨闲。這時,無法只省略該參數(shù)郑口,而不省略它后面的參數(shù)鸳碧,除非顯式輸入undefined盾鳞。
如果傳入undefined,將觸發(fā)該參數(shù)等于默認值瞻离,null則沒有這個效果腾仅。

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

foo(undefined, null)
// 5 null

上面代碼中,x參數(shù)對應undefined套利,結果觸發(fā)了默認值推励,y參數(shù)等于null,就沒有觸發(fā)默認值肉迫。

函數(shù)的 length 屬性

指定了默認值以后验辞,函數(shù)的length屬性,將返回沒有指定默認值的參數(shù)個數(shù)喊衫。也就是說跌造,指定了默認值后,length屬性將失真族购。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

這是因為length屬性的含義是壳贪,該函數(shù)預期傳入的參數(shù)個數(shù)。某個參數(shù)指定默認值以后寝杖,預期傳入的參數(shù)個數(shù)就不包括這個參數(shù)了撑碴。同理,后文的 rest 參數(shù)也不會計入length屬性朝墩。

(function(...args) {}).length // 0

如果設置了默認值的參數(shù)不是尾參數(shù)醉拓,那么length屬性也不再計入后面的參數(shù)了。

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

作用域

一旦設置了參數(shù)的默認值收苏,函數(shù)進行聲明初始化時亿卤,參數(shù)會形成一個單獨的作用域(context)。等到初始化結束鹿霸,這個作用域就會消失排吴。這種語法行為,在不設置參數(shù)默認值時懦鼠,是不會出現(xiàn)的钻哩。

var x = 1;

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

f(2) // 2

上面代碼中,參數(shù)y的默認值等于變量x肛冶。調用函數(shù)f時街氢,參數(shù)形成一個單獨的作用域。在這個作用域里面睦袖,默認值變量x指向第一個參數(shù)x珊肃,而不是全局變量x,所以輸出是2。
再看下面的例子伦乔。

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

上面代碼中厉亏,函數(shù)f調用時,參數(shù)y = x形成一個單獨的作用域烈和。這個作用域里面爱只,變量x本身沒有定義,所以指向外層的全局變量x招刹。函數(shù)調用時虱颗,函數(shù)體內部的局部變量x影響不到默認值變量x。

如果此時蔗喂,全局變量x不存在,就會報錯高帖。

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // ReferenceError: x is not defined

下面這樣寫缰儿,也會報錯。

var x = 1;

function foo(x = x) {
  // ...
}

foo() // ReferenceError: x is not defined

上面代碼中散址,參數(shù)x = x形成一個單獨作用域乖阵。實際執(zhí)行的是let x = x,由于暫時性死區(qū)的原因预麸,這行代碼會報錯”x 未定義“瞪浸。

應用

function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter

上面代碼的foo函數(shù),如果調用的時候沒有參數(shù)吏祸,就會調用默認值throwIfMissing函數(shù)对蒲,從而拋出一個錯誤。
從上面代碼還可以看到贡翘,參數(shù)mustBeProvided的默認值等于throwIfMissing函數(shù)的運行結果(注意函數(shù)名throwIfMissing之后有一對圓括號)蹈矮,這表明參數(shù)的默認值不是在定義時執(zhí)行,而是在運行時執(zhí)行鸣驱。如果參數(shù)已經(jīng)賦值泛鸟,默認值中的函數(shù)就不會運行。
另外踊东,可以將參數(shù)默認值設為undefined北滥,表明這個參數(shù)是可以省略的。

function foo(optional = undefined) { ··· }

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();

arguments對象不是數(shù)組,而是一個類似數(shù)組的對象豁遭。所以為了使用數(shù)組的方法叭喜,必須使用Array.prototype.slice.call先將其轉為數(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ù)),否則會報錯盯腌。
函數(shù)的length屬性溉知,不包括 rest 參數(shù)。

嚴格模式

ES2016 做了一點修改腕够,規(guī)定只要函數(shù)參數(shù)使用了默認值级乍、解構賦值、或者擴展運算符帚湘,那么函數(shù)內部就不能顯式設定為嚴格模式玫荣,否則會報錯。
這樣規(guī)定的原因是大诸,函數(shù)內部的嚴格模式捅厂,同時適用于函數(shù)體和函數(shù)參數(shù)。但是资柔,函數(shù)執(zhí)行的時候恒傻,先執(zhí)行函數(shù)參數(shù),然后再執(zhí)行函數(shù)體建邓。這樣就有一個不合理的地方盈厘,只有從函數(shù)體之中,才能知道參數(shù)是否應該以嚴格模式執(zhí)行官边,但是參數(shù)卻應該先于函數(shù)體執(zhí)行沸手。
兩種方法可以規(guī)避這種限制。第一種是設定全局性的嚴格模式注簿,這是合法的契吉。

'use strict';

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

第二種是把函數(shù)包在一個無參數(shù)的立即執(zhí)行函數(shù)里面。

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

name 屬性

函數(shù)的name屬性诡渴,返回該函數(shù)的函數(shù)名捐晶。

function foo() {}
foo.name // "foo"

這個屬性早就被瀏覽器廣泛支持菲语,但是直到 ES6,才將其寫入了標準惑灵。

需要注意的是山上,ES6 對這個屬性的行為做出了一些修改。如果將一個匿名函數(shù)賦值給一個變量英支,ES5 的name屬性佩憾,會返回空字符串,而 ES6 的name屬性會返回實際的函數(shù)名干花。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

如果將一個具名函數(shù)賦值給一個變量妄帘,則 ES5 和 ES6 的name屬性都返回這個具名函數(shù)原本的名字。

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

Function構造函數(shù)返回的函數(shù)實例池凄,name屬性的值為anonymous抡驼。

(new Function).name // "anonymous"

bind返回的函數(shù),name屬性值會加上bound前綴肿仑。

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

箭頭函數(shù)

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

// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });

如果箭頭函數(shù)只有一行語句蚣录,且不需要返回值割择,可以采用下面的寫法,就不用寫大括號了萎河。

let fn = () => void doesNotReturn();

箭頭函數(shù)的一個用處是簡化回調函數(shù)荔泳。

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

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

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

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ù)體內的this對象玛歌,就是定義時所在的對象,而不是使用時所在的對象擎椰。

(2)不可以當作構造函數(shù)支子,也就是說,不可以使用new命令达舒,否則會拋出一個錯誤值朋。

(3)不可以使用arguments對象,該對象在函數(shù)體內不存在巩搏。如果要用昨登,可以用 rest 參數(shù)代替。

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

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

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ù)內部設置了兩個定時器杖玲,分別使用了箭頭函數(shù)和普通函數(shù)。前者的this綁定定義時所在的作用域(即Timer函數(shù))淘正,后者的this指向運行時所在的作用域(即全局對象)摆马。所以,3100毫秒之后鸿吆,timer.s1被更新了3次囤采,而timer.s2一次都沒更新。
箭頭函數(shù)可以讓this指向固定化惩淳,這種特性很有利于封裝回調函數(shù)蕉毯。下面是一個例子,DOM 事件的回調函數(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ù),這導致這個箭頭函數(shù)里面的this激蹲,總是指向handler對象棉磨。否則,回調函數(shù)運行時学辱,this.doSomething這一行會報錯乘瓤,因為此時this指向document對象。

this指向的固定化策泣,并不是因為箭頭函數(shù)內部有綁定this的機制衙傀,實際原因是箭頭函數(shù)根本沒有自己的this,導致內部的this就是外層代碼塊的this萨咕。正是因為它沒有this统抬,所以也就不能用作構造函數(shù)。

除了this危队,以下三個變量在箭頭函數(shù)之中也是不存在的蓄喇,指向外層函數(shù)的對應變量:arguments、super交掏、new.target妆偏。

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

嵌套的箭頭函數(shù)

下面是一個部署管道機制(pipeline)的例子见秽,即前一個函數(shù)的輸出是后一個函數(shù)的輸入愉烙。

const pipeline = (...funcs) =>
  val => funcs.reduce((a, b) => b(a), val);

const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);

addThenMult(5)
// 12

bind綁定 this

函數(shù)綁定運算符是并排的兩個冒號(::),雙冒號左邊是一個對象解取,右邊是一個函數(shù)步责。該運算符會自動將左邊的對象,作為上下文環(huán)境(即this對象)禀苦,綁定到右邊的函數(shù)上面蔓肯。

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

如果雙冒號左邊為空,右邊是一個對象的方法振乏,則等于將該方法綁定在該對象上面蔗包。

var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

let log = ::console.log;
// 等同于
var log = console.log.bind(console);

尾調用優(yōu)化

尾調用(Tail Call)是函數(shù)式編程的一個重要概念,本身非常簡單慧邮,一句話就能說清楚调限,就是指某個函數(shù)的最后一步是調用另一個函數(shù)。

function f(x){
  return g(x);
}

以下三種情況误澳,都不屬于尾調用耻矮。

// 情況一
function f(x){
  let y = g(x);
  return y;
}

// 情況二
function f(x){
  return g(x) + 1;
}

// 情況三
function f(x){
  g(x);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市忆谓,隨后出現(xiàn)的幾起案子裆装,更是在濱河造成了極大的恐慌,老刑警劉巖陪毡,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米母,死亡現(xiàn)場離奇詭異勾扭,居然都是意外死亡毡琉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門妙色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桅滋,“玉大人,你說我怎么就攤上這事身辨∝つ保” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵煌珊,是天一觀的道長号俐。 經(jīng)常有香客問我,道長定庵,這世上最難降的妖魔是什么吏饿? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任踪危,我火速辦了婚禮,結果婚禮上猪落,老公的妹妹穿的比我還像新娘贞远。我一直安慰自己,他們只是感情好笨忌,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布蓝仲。 她就那樣靜靜地躺著,像睡著了一般官疲。 火紅的嫁衣襯著肌膚如雪袱结。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天袁余,我揣著相機與錄音擎勘,去河邊找鬼。 笑死颖榜,一個胖子當著我的面吹牛棚饵,可吹牛的內容都是我干的。 我是一名探鬼主播掩完,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼噪漾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了且蓬?” 一聲冷哼從身側響起欣硼,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恶阴,沒想到半個月后诈胜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡冯事,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年焦匈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昵仅。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缓熟,死狀恐怖,靈堂內的尸體忽然破棺而出摔笤,到底是詐尸還是另有隱情够滑,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布吕世,位于F島的核電站彰触,受9級特大地震影響,放射性物質發(fā)生泄漏命辖。R本人自食惡果不足惜况毅,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一晚伙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俭茧,春花似錦咆疗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毡们,卻和暖如春迅皇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背衙熔。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工登颓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人红氯。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓框咙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痢甘。 傳聞我的和親對象是個殘疾皇子喇嘱,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容

  • 函數(shù)參數(shù)的默認值 基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認值塞栅,只能采用變通的方法者铜。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,384評論 0 1
  • 1、函數(shù)參數(shù)的默認值1)基本用法在ES6之前放椰,不能直接為函數(shù)的參數(shù)指定默認值作烟,為了避免這個問題,通常需要先判斷一下...
    秋天de童話閱讀 383評論 0 0
  • 參數(shù)默認值 ES5中設置默認值非常不方便, 我們這樣寫: 以上寫法, 如果傳入了參數(shù), 但這個參數(shù)對應值的布爾型是...
    faremax閱讀 203評論 0 0
  • OLD函數(shù)默認參數(shù) 基本用法 在 ES2017 中砾医,允許定義和調用函數(shù)時拿撩,最后一個參數(shù)有, 惰性求值 報錯情景 當...
    我_巨可愛閱讀 111評論 0 0
  • 《通往財富自由之路》心得之三:付費就是占便宜 凡是能用錢買的其實都是便宜的。 凡是能用時間換來的...
    曉紅記錄庫閱讀 298評論 0 0