8.1 函數(shù)參數(shù)的默認(rèn)值
8.1.1 基本用法
ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法
function log(x , y){
y = y || 'World';
console.log(x, y)
}
log('Hello', '') // Hello World
上面做法的問(wèn)題在于,如果y本身就是一個(gè)空值,那么就會(huì)自動(dòng)將其變成'World',但是我們需要的是展示這個(gè)空值
為避免這種問(wèn)題,可以這樣做:
if( typeof y == 'undefined' ){
y = 'World';
}
ES6的寫(xiě)法
function log(x, y = 'World'){
console.log(x, y)
}
log('Hello', '') // Hello
8.1.2 與解構(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
為防止foo()調(diào)用時(shí)報(bào)錯(cuò),可以設(shè)置默認(rèn)值
function foo({x,y = 5} = {} ){
console.log(x,y)
}
foo()
//寫(xiě)法一
function m1( {x = 0, y = 0} = {} ){
console.log([x,y])
}
//寫(xiě)法二
function m2( {x,y} = {x:0, y:0} ){
console.log([x,y])
}
//函數(shù)沒(méi)有參數(shù)情況下
m1() //[0,0]
m2() //[0,0]
// x 和 y 都有值的情況
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 無(wú)值的情況
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都無(wú)值的情況
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
8.1.3 參數(shù)默認(rèn)值的位置
通常情況下,定義了默認(rèn)值的參數(shù),應(yīng)該是函數(shù)的尾參數(shù)。因?yàn)檫@樣比較容易看出來(lái),到底省略了哪些參數(shù)。如果非尾部的參數(shù)設(shè)置默認(rèn)值,實(shí)際上這個(gè)參數(shù)是沒(méi)法省略的
function f(x, y = 5, z){
console.log([x,y,z])
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報(bào)錯(cuò)
f(1, undefined, 2) // [1, 5, 2]
上面代碼中,有默認(rèn)值的參數(shù)都不是尾參數(shù)。這時(shí),無(wú)法只省略該參數(shù),而不省略它后面的參數(shù),除非顯式輸入undefined。
8.1.4 函數(shù)的length屬性
指定了默認(rèn)值以后,函數(shù)的length屬性,將返回沒(méi)有指定默認(rèn)值的參數(shù)格式,也就是說(shuō),指定了默認(rèn)值后,length屬性將失真。
(function(a){}).length // 1
(function(){a=5}).length // 0
(function(){a,b,c = 5}).length // 2
為什么呢?
因?yàn)閘ength屬性的含義是,該函數(shù)預(yù)期傳入的參數(shù)個(gè)數(shù)。某個(gè)參數(shù)指定默認(rèn)值以后,預(yù)期傳入的參數(shù)個(gè)數(shù)就不包括這個(gè)參數(shù)了。
注意:如果設(shè)置了默認(rèn)值的參數(shù)不是尾參數(shù),那么length屬性也不再計(jì)入后面的參數(shù)了
(function(a = 0, b, c){}).length //0
(function(a, b = 1, c){}).length //1
8.1.5 作用域
一旦設(shè)置了參數(shù)的默認(rèn)值,函數(shù)進(jìn)行聲明初始化時(shí),參數(shù)會(huì)形成一個(gè)單獨(dú)的作用域(context)。等到初始化結(jié)束,這個(gè)作用域就會(huì)消失。這種語(yǔ)法行為,在不設(shè)置參數(shù)默認(rèn)值時(shí),是不會(huì)出現(xiàn)的。
var x = 1;
function f(x, y = x ){
console.log(y);
}
f(2) // 2
再看下面的例子
let x = 1;
function f(y = x){
let x = 2;
console.log(y)
}
f() // 1
8.1.6 應(yīng)用
利用參數(shù)默認(rèn)值,可以指定某一個(gè)參數(shù)不得省略,如果省略就拋出一個(gè)錯(cuò)誤
function throwIfMissing(){
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()){
return mustBeProvided;
}
foo() //Uncaught Error: Missing parameter
另外,可以將參數(shù)默認(rèn)值設(shè)為undefined,表明這個(gè)參數(shù)是可以省略的。
function foo(options = undefined){
...
}
8.2 rest參數(shù)
ES6 引入rest參數(shù)(形式為...變量名),用于獲取函數(shù)的多余參數(shù),這樣就不需要使用arguments對(duì)象了,rest參數(shù)搭配的變量是一個(gè)數(shù)組,該變量將多余的參數(shù)放入數(shù)組中。
// arguments變量的寫(xiě)法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest參數(shù)的寫(xiě)法
const sortNumbers = (...numbers) => numbers.sort();
arguments對(duì)象不是數(shù)組,而是一個(gè)類(lèi)似數(shù)組的對(duì)象。所以為了使用數(shù)組的方法,必須使用Array.prototype.slice.call先將其轉(zhuǎn)為數(shù)組。rest 參數(shù)就不存在這個(gè)問(wèn)題,它就是一個(gè)真正的數(shù)組,數(shù)組特有的方法都可以使用。下面是一個(gè)利用 rest 參數(shù)改寫(xiě)數(shù)組push方法的例子。
function push(array, ...items){
items.forEach(function(item){
array.push(item);
console.log(item)
})
}
var a = [];
push(a, 1,2,3,4)
注意:rest參數(shù)之后不能再有其他參數(shù)(即只能是最后一個(gè)參數(shù)),否則會(huì)報(bào)錯(cuò)
//報(bào)錯(cuò)
function f(a, ...b, c){
// ...
}
函數(shù)的length屬性,不包括 rest 參數(shù)
(function(a){}).length // 1
(function(...a){}).length // 0
(function(a, ...b) {}).length // 1
8.3 嚴(yán)格模式
從ES5開(kāi)始,函數(shù)內(nèi)部可以設(shè)定為嚴(yán)格模式
function doSomething(a, b){
'use strict';
// code
}
ES6 做了一點(diǎn)修改,規(guī)定只要函數(shù)參數(shù)使用了默認(rèn)值、解構(gòu)賦值、或者擴(kuò)展運(yùn)算符,那么函數(shù)內(nèi)部就不能顯示設(shè)定為嚴(yán)格模式,否則會(huì)報(bào)錯(cuò)。
這樣規(guī)定的原因是,函數(shù)內(nèi)部的嚴(yán)格模式,同時(shí)適用于函數(shù)體和函數(shù)參數(shù)。但是,函數(shù)執(zhí)行的時(shí)候,先執(zhí)行函數(shù)參數(shù),然后再執(zhí)行函數(shù)體。這樣就有一個(gè)不合理的地方,只有從函數(shù)體之中,才能知道參數(shù)是否應(yīng)該以嚴(yán)格模式執(zhí)行,但是參數(shù)卻應(yīng)該先于函數(shù)體執(zhí)行。
有兩種方法可以規(guī)避這種限制,第一種是設(shè)定全局性的嚴(yán)格模式,這是合法的
'use strict';
function doSomething(a, b = a){
// code
}
第二種是把函數(shù)包在一個(gè)無(wú)參數(shù)的立即執(zhí)行函數(shù)里面
const doSomething = (function(){
'use strict';
return function(value = 42){
return value
}
}());
8.4 name屬性
函數(shù)的name屬性,返回該函數(shù)的函數(shù)名
function foo(){}
foo.name
如果將一個(gè)匿名函數(shù)賦值給一個(gè)變量
var f = function(){};
// ES5
f.name // ''
// ES6
f.name // 'f'
const bar = function baz() {};
// ES5
bar.name // 'baz'
//ES6
bar.name // 'baz'
8.5 箭頭函數(shù)(重點(diǎn))
8.5.1 基本用法
ES6 允許使用"箭頭"( => )定義函數(shù)
var f = v => v;
上面的箭頭函數(shù)等同于
var f = function(v){
return v;
}
如果箭頭函數(shù)不需要參數(shù)或需要多個(gè)參數(shù),就使用一個(gè)圓括號(hào)代表參數(shù)部分
var f = () => 5
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
//等同于
var sum = function(num1, num2){
return num1 + num2;
}
如果箭頭函數(shù)的代碼塊部分多于一條語(yǔ)句,就要使用大括號(hào)將它們括起來(lái),并且使用return語(yǔ)句返回
var sum = (num1, num2) => { return num1 + num2 }
重點(diǎn):由于大括號(hào)被解釋為代碼塊,所以如果箭頭函數(shù)直接返回一個(gè)對(duì)象,必須在對(duì)象外面加上括號(hào),否則會(huì)報(bào)錯(cuò)。
//報(bào)錯(cuò)
let getTempItem = id => { id: id, name: 'Temp'};
//不報(bào)錯(cuò)
let getTempItem = id => ({ id: id, name: 'Temp'});
如果箭頭函數(shù)只有一行語(yǔ)句,且不需要返回值,則:
let fn = () => void doesNotReturn();
箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用
const full = ({ first, last }) => first + ' ' + last;
//等同于
function full(person){
return person.first + ' ' + person.last;
}
箭頭函數(shù)使得表達(dá)更加簡(jiǎn)潔
const isEvent = n => n % 2 == 0;
箭頭函數(shù)的另一個(gè)用處是簡(jiǎn)化回調(diào)函數(shù)
[1,2,3].map(function(x){
return x * x;
})
//箭頭函數(shù)寫(xiě)法
[1,2,3].map(x => x * x);
另一個(gè)例子:
var result = values.sort(function(a,b){
return a - b;
})
//箭頭函數(shù)寫(xiě)法
var result = values.sort((a,b) => a - b);
8.5.2 使用注意點(diǎn)
箭頭函數(shù)可以讓this指向固定化,這種特性很有利于封裝回調(diào)函數(shù)
var handler = {
id: '123456',
init: function(){
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(){
console.log('Handling' + type + 'for' + this.id);
}
}
上面代碼的init方法中,使用了箭頭函數(shù),這導(dǎo)致這個(gè)箭頭函數(shù)里面的this,總是指向handler對(duì)象。否則,回調(diào)函數(shù)運(yùn)行時(shí),this.doSomething這一行會(huì)報(bào)錯(cuò),因?yàn)榇藭r(shí)this指向document對(duì)象。
this指向的固定化,并不是因?yàn)榧^函數(shù)內(nèi)部有綁定this的機(jī)制,實(shí)際原因是箭頭函數(shù)根本沒(méi)有自己的this,導(dǎo)致內(nèi)部的this就是外層代碼塊的this。正是因?yàn)樗鼪](méi)有this,所以也就不能用作構(gòu)造函數(shù)。
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
上面代碼之中,只有一個(gè)this,就是函數(shù)foo的this
除了this,以下三個(gè)變量在箭頭函數(shù)之中也是不存在的,指向最外層函數(shù)的對(duì)應(yīng)變量
arguments, super, new.target
另外,由于箭頭函數(shù)沒(méi)有自己的this,所以當(dāng)然也就不能用call()、apply()、bind()這些方法來(lái)改變this的指向
8.5.3 嵌套的箭頭函數(shù)
8.6 雙冒號(hào)運(yùn)算符
函數(shù)綁定運(yùn)算符是并排的兩個(gè)冒號(hào)( :: ),雙冒號(hào)左邊是一個(gè)對(duì)象,右邊是一個(gè)函數(shù)。該運(yùn)算符會(huì)自動(dòng)將左邊的對(duì)象,做為上下文環(huán)境(即this對(duì)象),綁定到右邊的函數(shù)上面
foo::bar
//等同于
bar.bind(foo);
foo:bar(...arguments);
//等同于
bar.apply(foo, arguments);
如果雙冒號(hào)左邊為空,右邊是一個(gè)對(duì)象的方法,則等于將該方法綁定在該對(duì)象上面。
var method = obj::obj.foo;
//等同于
var method = ::obj.foo
let log = ::console.log
// 等同于
var log = console.log()
8.7 尾調(diào)用優(yōu)化
8.7.1 什么是尾調(diào)用?
概念:就是指某個(gè)函數(shù)的最后一步是調(diào)用另一個(gè)函數(shù)。
function f(x){
return g(x);
}
以下三種情況,都不屬于尾調(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)用不一定出現(xiàn)在函數(shù)尾部,只要是最后一步操作即可。
function f(x){
if( x > 0 ){
return m(x);
}
return n(x);
}
8.7.2 尾調(diào)用優(yōu)化
尾調(diào)用之所以與其它調(diào)用不同,就在于它的特殊的調(diào)用位置。
我們知道,函數(shù)調(diào)用會(huì)在內(nèi)存形成一個(gè)調(diào)用幀,保存調(diào)用位置和內(nèi)部變量等信息。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B,那么在A的調(diào)用幀上方,還會(huì)形成一個(gè)B的調(diào)用幀。等到B運(yùn)行結(jié)束,將結(jié)果返回到A,B的調(diào)用幀才會(huì)消失。如果函數(shù)B內(nèi)部還調(diào)用函數(shù)C,那么就還有一個(gè)C的調(diào)用幀,以此類(lèi)推。所有的調(diào)用幀,就會(huì)形成一個(gè)調(diào)用棧。
尾調(diào)用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調(diào)用幀,因?yàn)檎{(diào)用位置,內(nèi)部變量等信息不會(huì)再用到,只要直接使用內(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)
上面的g(3),就叫做尾調(diào)用優(yōu)化,可以大大節(jié)省內(nèi)存。
注意,只有不再用到外層函數(shù)的內(nèi)部變量,內(nèi)層函數(shù)的調(diào)用幀才會(huì)取代外層函數(shù)的調(diào)用幀,否則就無(wú)法進(jìn)行“尾調(diào)用優(yōu)化”。
function addOne(a){
var one = 1;
function inner(b){
return b + one;
}
return inner(a);
}
上面的函數(shù)不會(huì)進(jìn)行尾調(diào)用優(yōu)化,因?yàn)閮?nèi)層函數(shù)inner用到了外層函數(shù)addOne的內(nèi)部變量one。
8.7.3 尾遞歸
函數(shù)調(diào)用自身,稱(chēng)為遞歸。如果尾調(diào)用自身,就稱(chēng)為尾遞歸。
遞歸非常耗費(fèi)內(nèi)存,因?yàn)樾枰瑫r(shí)保存成千上百個(gè)調(diào)用幀,很容易發(fā)生“棧溢出”錯(cuò)誤(stack overflow)。但對(duì)于尾遞歸來(lái)說(shuō),由于只存在一個(gè)調(diào)用幀,所以永遠(yuǎn)不會(huì)發(fā)生“棧溢出”錯(cuò)誤。
function factorial(n){
if (n === 1) return 1;
return n * factorial(n - 1)
}
將其改為尾遞歸:
function factorial(n, total){
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1)
8.7.4 遞歸函數(shù)的改寫(xiě)
8.7.5 尾遞歸優(yōu)化的實(shí)現(xiàn)
8.7.6 函數(shù)尾部的尾逗號(hào)
ES7 允許函數(shù)的最后一個(gè)參數(shù)有尾逗號(hào)
function clownsEverywhere(
param1,
param2
) { /* ... */ }
clownsEverywhere(
'foo',
'bar'
);
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
這樣的規(guī)定也使得,函數(shù)參數(shù)與數(shù)組和對(duì)象的尾逗號(hào)規(guī)則,保持一致了。