ES6 函數(shù)的擴(kuò)展

  • 1楼吃、函數(shù)參數(shù)默認(rèn)值
  • 2、rest參數(shù)
  • 3妄讯、嚴(yán)格模式
  • 4孩锡、name屬性
  • 5、箭頭函數(shù)
  • 6捞挥、雙冒號運算符
  • 7、尾調(diào)用優(yōu)化
  • 8忧吟、函數(shù)參數(shù)的尾逗號

函數(shù)參數(shù)默認(rèn)值

1砌函、基本用法

ES6 之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值溜族,只能采用變通的方法讹俊。

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

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

這種寫法的缺點在于,如果參數(shù)y賦值了煌抒,但是對應(yīng)的布爾值為false仍劈,則該賦值不起作用。就像上面代碼的最后一行寡壮,參數(shù)y等于空字符贩疙,結(jié)果被改為默認(rèn)值

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

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

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

2这溅、與解構(gòu)賦值默認(rèn)值聯(lián)合使用

參數(shù)默認(rèn)值可以與解構(gòu)賦值的默認(rèn)值组民,結(jié)合起來使用

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

上面代碼只使用了對象的解構(gòu)賦值默認(rèn)值,沒有使用函數(shù)參數(shù)的默認(rèn)值悲靴。只有當(dāng)函數(shù)foo的參數(shù)是一個對象時臭胜,變量xy才會通過解構(gòu)賦值生成。如果函數(shù)foo調(diào)用時沒提供參數(shù)癞尚,變量x和y就不會生成耸三,從而報錯

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

foo() // undefined 5

下面是另一個解構(gòu)賦值默認(rèn)值的例子。

function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 報錯

上面代碼中浇揩,如果函數(shù)fetch的第二個參數(shù)是一個對象仪壮,就可以為它的三個屬性設(shè)置默認(rèn)值。這種寫法不能省略第二個參數(shù)临燃,如果結(jié)合函數(shù)參數(shù)的默認(rèn)值睛驳,就可以省略第二個參數(shù)。這時膜廊,就出現(xiàn)了雙重默認(rèn)值乏沸。

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

3、參數(shù)默認(rèn)值的位置

通常情況下爪瓜,定義了默認(rèn)值的參數(shù)蹬跃,應(yīng)該是函數(shù)的尾參數(shù)。因為這樣比較容易看出來铆铆,到底省略了哪些參數(shù)蝶缀。如果非尾部的參數(shù)設(shè)置默認(rèn)值,實際上這個參數(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]

4翁都、應(yīng)用

利用參數(shù)默認(rèn)值,可以指定某一個參數(shù)不得省略谅猾,如果省略就拋出一個錯誤柄慰。

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

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

foo()
// Error: Missing parameter

上面代碼的foo函數(shù),如果調(diào)用的時候沒有參數(shù)税娜,就會調(diào)用默認(rèn)值throwIfMissing函數(shù)坐搔,從而拋出一個錯誤。

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

嚴(yán)格模式

嚴(yán)格模式(strict mode)即在嚴(yán)格的條件下運行

use strict 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增凳忙。
它不是一條語句业踏,但是是一個字面量表達(dá)式,在 JavaScript 舊版本中會被忽略消略。use strict 的目的是指定代碼在嚴(yán)格條件下執(zhí)行堡称。嚴(yán)格模式下你不能使用未聲明的變量。

嚴(yán)格模式通過在腳本或函數(shù)的頭部添加 use strict; 表達(dá)式來聲明艺演。

為什么使用嚴(yán)格模式:
消除Javascript語法的一些不合理却紧、不嚴(yán)謹(jǐn)之處,減少一些怪異行為;

  • 消除代碼運行的一些不安全之處胎撤,保證代碼運行的安全晓殊;
  • 提高編譯器效率,增加運行速度伤提;
  • 為未來新版本的Javascript做好鋪墊巫俺。
"use strict";
x = 3.14;       // 報錯 (x 未定義)
"use strict";
myFunction();

function myFunction() {
    y = 3.14;   // 報錯 (y 未定義)
}
x = 3.14;       // 不報錯 
myFunction();

function myFunction() {
   "use strict";
    y = 3.14;   // 報錯 (y 未定義)
}

從 ES5 開始,函數(shù)內(nèi)部可以設(shè)定為嚴(yán)格模式肿男。

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

ES2016 做了一點修改介汹,規(guī)定只要函數(shù)參數(shù)使用了默認(rèn)值、解構(gòu)賦值舶沛、或者擴(kuò)展運算符嘹承,那么函數(shù)內(nèi)部就不能顯式設(shè)定為嚴(yán)格模式,否則會報錯

// 報錯
function doSomething(a, b = a) {
  'use strict';
  // code
}

// 報錯
const doSomething = function ({a, b}) {
  'use strict';
  // code
};

// 報錯
const doSomething = (...a) => {
  'use strict';
  // code
};

const obj = {
  // 報錯
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

name屬性

函數(shù)的name屬性如庭,返回該函數(shù)的函數(shù)名叹卷。

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

這個屬性早就被瀏覽器廣泛支持,但是直到 ES6坪它,才將其寫入了標(biāo)準(zhǔn)骤竹。

需要注意的是,ES6 對這個屬性的行為做出了一些修改往毡。如果將一個匿名函數(shù)賦值給一個變量蒙揣,ES5 的name屬性,會返回空字符串开瞭,而 ES6 的name屬性會返回實際的函數(shù)名懒震。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

上面代碼中,變量f等于一個匿名函數(shù)惩阶,ES5 和 ES6 的name屬性返回的值不一樣挎狸。

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

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

箭頭函數(shù)

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

如果箭頭函數(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ù)直接返回一個對象,必須在對象外面加上括號响牛,否則會報錯玷禽。

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

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

注意點

  • 1、函數(shù)體內(nèi)的this對象呀打,就是定義時所在的對象矢赁,而不是使用時所在的對象。
  • 2贬丛、不可以當(dāng)作構(gòu)造函數(shù)撩银,也就是說,不可以使用new命令豺憔,否則會拋出一個錯誤额获。
  • 3、不可以使用arguments對象恭应,該對象在函數(shù)體內(nèi)不存在抄邀。如果要用,可以用 rest 參數(shù)代替
  • 4暮屡、不可以使用yield命令撤摸,因此箭頭函數(shù)不能用作 Generator 函數(shù)。

雙冒號運算符

函數(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);
}

尾調(diào)用優(yōu)化

什么是尾調(diào)用彻秆?

尾調(diào)用(Tail Call)是函數(shù)式編程的一個重要概念楔绞,本身非常簡單,一句話就能說清楚唇兑,就是指某個函數(shù)的最后一步是調(diào)用另一個函數(shù)酒朵。

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

上面代碼中,函數(shù)f的最后一步是調(diào)用函數(shù)g扎附,這就叫尾調(diào)用蔫耽。

以下三種情況,都不屬于尾調(diào)用留夜。

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

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

// 情況三
function f(x){
  g(x);
}

上面代碼中匙铡,情況一是調(diào)用函數(shù)g之后图甜,還有賦值操作,所以不屬于尾調(diào)用鳖眼,即使語義完全一樣黑毅。情況二也屬于調(diào)用后還有操作,即使寫在一行內(nèi)钦讳。情況三等同于下面的代碼矿瘦。

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

尾調(diào)用不一定出現(xiàn)在函數(shù)尾部,只要是最后一步操作即可愿卒。

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

上面代碼中匪凡,函數(shù)m和n都屬于尾調(diào)用,因為它們都是函數(shù)f的最后一步操作掘猿。

尾調(diào)用優(yōu)化

尾調(diào)用之所以與其他調(diào)用不同病游,就在于它的特殊的調(diào)用位置。

我們知道稠通,函數(shù)調(diào)用會在內(nèi)存形成一個“調(diào)用記錄”衬衬,又稱“調(diào)用幀”(call frame),保存調(diào)用位置和內(nèi)部變量等信息改橘。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B滋尉,那么在A的調(diào)用幀上方,還會形成一個B的調(diào)用幀飞主。等到B運行結(jié)束狮惜,將結(jié)果返回到A,B的調(diào)用幀才會消失碌识。如果函數(shù)B內(nèi)部還調(diào)用函數(shù)C碾篡,那就還有一個C的調(diào)用幀,以此類推筏餐。所有的調(diào)用幀开泽,就形成一個“調(diào)用棧”(call stack)魁瞪。

尾調(diào)用由于是函數(shù)的最后一步操作穆律,所以不需要保留外層函數(shù)的調(diào)用幀,因為調(diào)用位置导俘、內(nèi)部變量等信息都不會再用到了峦耘,只要直接用內(nèi)層函數(shù)的調(diào)用幀,取代外層函數(shù)的調(diào)用幀就可以了旅薄。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

上面代碼中辅髓,如果函數(shù)g不是尾調(diào)用,函數(shù)f就需要保存內(nèi)部變量m和n的值、g的調(diào)用位置等信息利朵。但由于調(diào)用g之后,函數(shù)f就結(jié)束了猎莲,所以執(zhí)行到最后一步绍弟,完全可以刪除f(x)的調(diào)用幀,只保留g(3)的調(diào)用幀著洼。

這就叫做“尾調(diào)用優(yōu)化”(Tail call optimization)樟遣,即只保留內(nèi)層函數(shù)的調(diào)用幀。如果所有函數(shù)都是尾調(diào)用身笤,那么完全可以做到每次執(zhí)行時豹悬,調(diào)用幀只有一項,這將大大節(jié)省內(nèi)存液荸。這就是“尾調(diào)用優(yōu)化”的意義瞻佛。

注意,只有不再用到外層函數(shù)的內(nèi)部變量娇钱,內(nèi)層函數(shù)的調(diào)用幀才會取代外層函數(shù)的調(diào)用幀伤柄,否則就無法進(jìn)行“尾調(diào)用優(yōu)化”。

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

尾遞歸

函數(shù)調(diào)用自身文搂,稱為遞歸适刀。如果尾調(diào)用自身,就稱為尾遞歸煤蹭。

遞歸非常耗費內(nèi)存笔喉,因為需要同時保存成千上百個調(diào)用幀,很容易發(fā)生“棧溢出”錯誤(stack overflow)硝皂。但對于尾遞歸來說常挚,由于只存在一個調(diào)用幀,所以永遠(yuǎn)不會發(fā)生“棧溢出”錯誤

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代碼是一個階乘函數(shù)稽物,計算n的階乘待侵,最多需要保存n個調(diào)用記錄,復(fù)雜度 O(n) 姨裸。

如果改寫成尾遞歸秧倾,只保留一個調(diào)用記錄,復(fù)雜度 O(1)傀缩。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

還有一個比較著名的例子那先,就是計算 Fibonacci 數(shù)列,也能充分說明尾遞歸優(yōu)化的重要性赡艰。

非尾遞歸的 Fibonacci 數(shù)列實現(xiàn)如下售淡。

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出

尾遞歸優(yōu)化過的 Fibonacci 數(shù)列實現(xiàn)如下。

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

函數(shù)參數(shù)的尾逗號

ES2017 允許函數(shù)的最后一個參數(shù)有尾逗號(trailing comma)。

此前揖闸,函數(shù)定義和調(diào)用時揍堕,都不允許最后一個參數(shù)后面出現(xiàn)逗號。

function clownsEverywhere(
  param1,
  param2
) { /* ... */ }

clownsEverywhere(
  'foo',
  'bar'
);

上面代碼中汤纸,如果在param2bar后面加一個逗號衩茸,就會報錯。

如果像上面這樣贮泞,將參數(shù)寫成多行(即每個參數(shù)占據(jù)一行)楞慈,以后修改代碼的時候,想為函數(shù)clownsEverywhere添加第三個參數(shù)啃擦,或者調(diào)整參數(shù)的次序囊蓝,就勢必要在原來最后一個參數(shù)后面添加一個逗號。這對于版本管理系統(tǒng)來說令蛉,就會顯示添加逗號的那一行也發(fā)生了變動聚霜。這看上去有點冗余,因此新的語法允許定義和調(diào)用時珠叔,尾部直接有一個逗號俯萎。

function clownsEverywhere(
  param1,
  param2,
) { /* ... */ }

clownsEverywhere(
  'foo',
  'bar',
);

文章轉(zhuǎn)載:阮一峰的函數(shù)的擴(kuò)展

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市运杭,隨后出現(xiàn)的幾起案子夫啊,更是在濱河造成了極大的恐慌,老刑警劉巖辆憔,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撇眯,死亡現(xiàn)場離奇詭異,居然都是意外死亡虱咧,警方通過查閱死者的電腦和手機(jī)熊榛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腕巡,“玉大人玄坦,你說我怎么就攤上這事』娉粒” “怎么了煎楣?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長车伞。 經(jīng)常有香客問我择懂,道長,這世上最難降的妖魔是什么另玖? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任困曙,我火速辦了婚禮表伦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慷丽。我一直安慰自己蹦哼,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布要糊。 她就那樣靜靜地躺著纲熏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杨耙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天飘痛,我揣著相機(jī)與錄音珊膜,去河邊找鬼。 笑死宣脉,一個胖子當(dāng)著我的面吹牛车柠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塑猖,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼竹祷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羊苟?” 一聲冷哼從身側(cè)響起塑陵,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜡励,沒想到半個月后令花,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡凉倚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年兼都,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稽寒。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡扮碧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杏糙,到底是詐尸還是另有隱情慎王,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布宏侍,位于F島的核電站柬祠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏负芋。R本人自食惡果不足惜漫蛔,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一嗜愈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莽龟,春花似錦蠕嫁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搂赋,卻和暖如春赘阀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脑奠。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工基公, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宋欺。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓轰豆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親齿诞。 傳聞我的和親對象是個殘疾皇子酸休,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值祷杈,只能采用變通的方法斑司。 上面代碼檢查函數(shù)l...
    陳老板_閱讀 449評論 0 1
  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值但汞,只能采用變通的方法陡厘。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,398評論 0 1
  • OLD函數(shù)默認(rèn)參數(shù) 基本用法 在 ES2017 中,允許定義和調(diào)用函數(shù)時特占,最后一個參數(shù)有, 惰性求值 報錯情景 當(dāng)...
    我_巨可愛閱讀 113評論 0 0
  • 現(xiàn)實生活里我的名字很普通糙置,不起眼到我自己都不一定能記起。我的生活很無聊是目,無聊到早上起床就可以預(yù)見我這一天會做些什么...
    匯杉閱讀 153評論 0 0
  • 逃學(xué)而來的那一年谤饭,就像生命盡頭的最后一年,每一天都會很珍惜懊纳,每天的快樂都銘記在心揉抵。內(nèi)心封存所有美好的記憶,像一壇記...
    玉米金閱讀 628評論 3 1