函數
參數默認值,以及一些關於arguments對象,如何使用表達式作為參數、參數的臨時死區。
以前設置默認值總是利用在含有邏輯或操作符的表達式中,前一個值是false時,總是返回後面一個的值,但如果我們給參數傳0時,就有些麻煩。需要去驗證一下類型
function makeRequest(url,timeout,callback){
timeout = timeout || 2000;
callback = callback || function(){};
// 邏輯
}
// 解決傳0問題
function makeRequest(url,timeout,callback){
timeout = typeof timeout !=='undefined'?timeout: 2000;
callback = typeof callback !=='undefined'?callback : function(){};
// 邏輯
}
默認參數
不傳或傳undefined。如果傳null不會用默認,會認為是一個合法的值。默認參數對arguments對象的影響。之前的參數傳遞會體現在arguments對象上。 在es5非嚴格模式下,命名參數的變化會同步更新到arguments對象上,但在es5的嚴格模式下,取消了這個行為,無論參數如何變化,arguments對象不再隨之改變。 如下
function mixArgs(first,second){
"use strict"
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = 'c';
second = 'd'
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs('a','b')
// true
// true
// false
// false
es6中,如果一個函數使用了默認參數值,則無論是否顯式定義了嚴格模式,arguments對象的行為都將與es5 嚴格模式下保持一致。
默認參數表達式
初次解析函數聲明時不會調用getValue()方法,只有當調用add()函數且不傳入第二個參數時才會調用。
let value = 5;
function getValue(){
return value++;
}
function add(first,second=getValue()){
return first+second;
})
console.log(add(1,1)) // 2
console.log(add(1)) // 6
console.log(add(1)) //7
先定義參數可以作為後定義參數的默認值,反過來不成立(first=second,second)因為second比first晚定義,因此其不能作為first的默認值。為了幫助你理解背後的原理,我們重溫一下臨時死區TDZ的概念。與let聲明類似,定義參數時會為每個參數創建一個新的標識符綁定,該綁定在初始化之前不可被引用,如果試圖訪問會導致程序拋出錯誤。
函數參數有自己的作用域和臨時死區,其與函數體的作用域是各自獨立的,也就是說參數的默認值不可訪問函數體內的變量
無論是是否使用不定參數,arguments對象總是包含所有傳入函數的參數
處理無命名參數--不定參數
// 函數的length屬性統計的是函數命名參數的數量,不定參數的加入不會影響length屬性的值。
function pick(object, ...keys){
let result = Object.create(null);
for(let i=0,len=keys.length;i<len;i++){
result[keys[i]] = object[keys[i]]
}
return result;
}
- 不定參數(當然在函數參數中使用)
- 每個函數最多只能聲明一個不定參數
- 而且一定要放在所有參數的未尾
- 不許在setter中使用不定參數,是因為對象字面量setter的參數有且只能有一個(在不定參數的定義中,參數的數量可以無限多,所以在當前上下文中允許使用不定參數。
let object = {
// 語法錯誤 不可以在Setter中使用不定參數
set name(...value){
// ...
}
}
無論是否使用不定參數,arguments對象總是包含所有傳入函數的參數
增強的Function構造函數
可以在動態創建函數時使用默認值及不定參數,唯一需要做的是在參數名後添加一個等號和一個默認值,定義不定參數,只需在最後一個參數前添加...。如下
var add = new Function('first','second=first','return first+second')
console.log(add(1,1)) // 2
console.log(add(1)) // 2
var pickFirst = new Function("...args",'return args[0]')
console.log(pickFirst(1,2)) // 1
展開運算符
展開運算符可以讓你指定一個數組,將它們打散後作為各自獨立的參數傳入函數。JavaScript內建的Math.max()方法可以接受任意數量的參數並返回值最大的那個,這是一個簡單的用例
自己理解,注意,前面那個是定義時用的,這個是調用時使用(參數是分開的,而你有一個數組)
如下,之前數組求最大值的做法
let values = [25,50,75,100]
console.log(Math.max.apply(Math,values)) // 100;
// max方法不允許傳入一個數組
console.log(Math.max(...values))
// 展開運算符後面可以繼續添加參數
console.log(Math.max(...values,110)) //110
函數的name屬性
聲明函數.name屬性是它聲明函數名,函數表達式如果函數是匿名的則就是其變量的名稱,如果不是則是其聲明的名字,
它只是協助調試用的額外信息,所以不能使用name屬性的值來獲取對於函數的引用。
function somethingDoing(){
// 空函數
}
var doSomeThing = function doSomethingElse() {
// 空函數
}
var person = {
get firstName(){
return 'Nicholas';
},
sayName:function(){
console.log(this.name)
}
}
console.log(somethingDoing.name) // somethingDoing
console.log(doSomeThing.name) // doSomethingElse 函數表達式有個名字這個名字比要賦值的變量權重高
console.log(person.sayName.name) // sayName
console.log(person.firstName.name) // get firstName
// 兩個特例
// 通過bind()創建的函數 會帶前綴bind
var doSomthing = function(){}
console.log(doSomthing.bind().name) // "bound doSomething"
console.log(new Function().name) // anonymous
明確函數的多重用途
es5 當new 時 函數內的this值指向一個新對象,函數最終返回一個新對象(沒有明確返回一個對象時)
function Person(name){
this.name = name;
}
var person = new Person('Nicholas');
var notPerson = Person('Nicholas');
console.log(person) // '[Object object]'
console.log(notPerson) // undefined
上例中,在es6中函數有兩個不同的內部訪問[[Call]]和[[Construct]]。當通過new關鍵字調用函數時,執行的是[[Construct]]函數,它負責創建一個通常被稱作實例的新對象,然後執行函數體,將this綁到實例上。而不使用new關鍵字時,則調用[[Call]]執行函數體,具有[[Construct]]方法的函數統稱為構造函數。切記不是所有的函數都有[[Construct]]方法 如:箭頭函數
es5判斷函數是否被new調用
function Person(name){
if(this instanceof Person){
this.name = name
}else {
throw new Error('必須通過new關鍵字來調用Person')
}
}
上面的方式在一定程度上可以避免,但不要忘了還有一個call和apply呢……
var person = new Person('Nicholas');
var person = Person('Nicholas'); // 報錯
var person = Person.call(person,'Michael'); // 通過
無屬性new.target
為瞭解決判斷函數是否通過new關鍵字調用的問題,es6引入了new.target這個元屬性,元屬性是指非對象的屬性,其可以提供非對象目標的補充信息。當調用函數[[Construct]]方法時,new.target上被賦為new的操作目標,通常是新創建對象實例,也就是函數體內this的構造函數;如果調用[[Call]]方法,則new.target值為undefined,有了這個元屬性,可以通過檢查new.target是否被定義過來安全地檢測是否通過new關鍵字調用的。
function Person(name){
if(typeof new.target!=='undefined'){
this.name = name;
}else {
throw new Error('必須通過new關鍵字來調用Person')
}
}
var person = new Person('Nicholas'); // 通過
var person = Person('Nicholas'); // 報錯
var person = Person.call(person,'Michael'); // 報錯
也可能通過new.target是否被某個特定的構造函數所調用。如下:
function Person(name){
// if(typeof new.target!=='undefined'){
if(new.target===Person)
this.name = name;
}else {
throw new Error('必須通過new關鍵字來調用Person')
}
}
function AnotherPerson(name){
Person.call(this,name)
}
var person = new Person('Nicholas');
var otherPerson = new AnotherPerson('Michael') // 拋出錯誤
塊級函數
es5的嚴格格式下,我們在代碼塊中聲明函數是不被允許的,而在es6中將doSomething()函數視作一個塊級聲明,從而可以在定義該函數的代碼內訪問和調用它:
"use strict"
if(true){
console.log(typeof doSomething) // "function"
function doSomething(){
// 空函數
}
doSomething() //
}
typeof doSomething // undefined
和let的使用差不多,唯一區別的是塊級是函數,會將聲明提到代碼塊的頂部。注意函數表達式聲明的區別。在es6中,即使處於非嚴格模式下,也可以聲明塊級函數,但其行為與嚴格模式下稍有不同。這些函數不再提升至代碼塊的頂部,而是提升至外圍函數或全局作用域的頂部,如下:
// ECMAScript 6中的行為
if(true){
console.log(typeof doSomething) // "function"
function doSomething(){
// 空函數
}
doSomething() //
}
typeof doSomething // function
箭頭函數
let PageHandler = {
id:"123456",
init: function(){
document.addEventListener("click",function(event){
this.doSomething(event.type); // 拋出錯誤
},false)
},
doSomething: function(type){
console.log("Handling "+ type + " for "+this.id)
}
}
上面的代碼並沒有如預期的正常運行。因為this的綁定是事件目標對象的引用(這裡是document),而沒有綁定在PageHandler,且由於this.doSomething()在目標document中不存在,所以無法正常執行,之前的做法是通過bind()方法顯示地將this綁定到PageHandler函數上來修正這個問題。如下
let PageHandler = {
id:"123456",
init: function(){
document.addEventListener("click",(function(event){
this.doSomething(event.type);
}).bind(this),false)
},
doSomething: function(type){
console.log("Handling "+ type + " for "+this.id)
}
}
調用bind(this)後事實上創建了一個新函數,它的this被綁定到當前的this即PageHandler。為了避免創建一個額外的函數,我們可以通過一個更好的方式來修正這段代碼,使用箭頭函數。箭頭函數中沒有this綁定,必須通過查找作用域鏈來決定其值。
let PageHandler = {
id:"123456",
init: function(){
document.addEventListener("click",(event)=>{
this.doSomething(event.type);
},false)
},
doSomething: function(type){
console.log("Handling "+ type + " for "+this.id)
}
}
- 沒有this、super、arguments和new.target綁定
- 不能通過new關鍵字調用 箭頭函數沒有
[[Construct]]方法,所以不能被用作構造函數 - 沒有原型
- 不可以改變this的綁定
- 不支持arguments對象
- 不支持重復的命名參數
箭頭函數也同樣有一個name屬性,這與其它函數的規則相同
this綁定
如果箭頭函數被非箭頭函數包含,則this綁定的是最近一層非箭頭函數的this;否則,this的值會被設置為undefined.
箭頭函數和數組
箭頭函數的語法簡潔,非常適用於數組處理。舉例來說,比如給數組排序,通過是
var result = values.sort(function(a,b){
return a - b;
})
// 我們可以將其簡寫至如下
var result = values.sort((a,b)=>a-b)
諸如
sort()、map()、reduce()這些可以接受回調函數的數組方法,
尾調用優化
函數做為另一個函數的最後一條語句被調用。
function doSomething(){
return doSomethingElse() // 尾調用;
}
在es5引擎中,尾調用的實現與其他函數調用的實現類似:創建一個新的棧幀(stack frame),將其推入調用棧來表示函數調用。也就是說,在循環調用中,每一個未用完的棧楨都會被保存在內存中,當調用棧變得過大時會造成程序問題。
為了縮減嚴格模式下尾調用棧的大小(非嚴格格式不受影響),如果滿足以下條件,尾調用不再創建新的棧楨,而是清除非重用當前棧楨
- 尾調用不訪問當前棧楨的變量(也就是說函數不是一個閉包)
- 在函數內部,尾調用是最後一條語句
- 尾調用的結果作為函數的返回值
"use strict"
function doSomething(){
return doSomethingElse() // 尾調用優化;
}
// 注意以上3個條件
如何利用尾調用優化
function factorial(n){
if(n<=1){
return 1
} else {
// 無法優化
return n * factorial(n-1)
}
}
// 可以利用傳遞第二個參數來保存每次階乘的結果(並且默認結果是1)
function factorial(n,p=1){
if(n<=1){
return 1*p
}else {
let result = n*p;
//優化後
return factorial(n-1,result)
}
}
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4310