函數拓展
1.參數變量是默認聲明的,所以不能用let
或const
再次聲明。
function a(x = 5){
let x = 1; //error
}
2.使用參數默認值時,函數不能有同名參數。
function a(x,x,y){} //正確
function a(x,x,y=1){} //錯誤
3.參數默認值不是傳值的,而是每次都重新計算默認值表達式的值。
let a = 90;
function x( p = b + 1){
console.log(p);
}
x() //91
a = 100;
x() //101
每次調用函數x,都會重新計算b + 1
與解構賦值默認值結合使用
1.參數默認值與解構賦值並用,可以避免因未傳入參數導致的不生成變量報錯狀況
//只使用解構賦值,未定默認值:
function foo({x, y = 5}) {
console.log(x, y);
}
foo() // TypeError: Cannot read property 'x' of undefined
//默認值 + 解構賦值結合
function foo({x,y=5} = {}){
console.log(x,y);
}
foo() //undefined 5
2.如果設置默認值是一個具體對象:
function a({x, y} = { x: 0, y: 0 })
代表:默認值是一個有屬性的對象,並未設置解構賦值,所以需要按照該默認值對象個數進行傳值
- 傳入空對象時沒有值的顯示:[undefined,undefined]
- 傳入參數個數不相同時:a({x:1}) [1,undefined]
- 傳入參數不存在時:[undefined,undefined]
參數默認值的位置
1.參數默認值應該設在尾部,如果是非尾部的位置,則這個參數無法省略,想省略可以傳入undefined,null不可以。
函數的 length 屬性
1.指定默認值後,lenth屬性將失真(從指定默認值的參數位置開始往後所有的參數,將不計入length)。
作用域
1.一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域,初始化結束後作用域會消失。
let x = 1;
function f(x,y = x){
console.log(x);
}
f(2) //2 默認值變量x指向第一個參數x,而不是全局變量x
2.默認值中的參數未定義,由於單獨作用域原因會去全局中找該參數,故函數內部無法影響該參數,如果全局中不存在該參數,則報錯。
let a = 10;
function f(x = a){
let a = 2;
console.log(x);
}
f() //10
function f2(x = b){
let b = 2;
console.log(x);
}
f2() //error
3.暫時性死區的狀況:(由於單獨作用域原因,x=x實際執行的是let x = x)
let x = 1;
function f(x = x){
...
}
f(); //x is not defined
4.默認值是函數的時候同樣如此
5.複雜示例
var x = 1;
function foo( x , y = function (x){ x = 2;} ){
var x= 3;
y();
console.log(x);
}
foo();//3
x ;//1
解析:參數爲單獨作用域,匿名函數y與參數x在同一作用域,所以不影響全局中的x,函數中單獨定義了x,由於作用域不同,所以匿名函
數y並不能影響函數內部x,故調用函數時輸出3,全局中x還爲1。
6.當去掉var:
var x = 1;
function foo( x , y = function (x){ x = 2;} ){
x= 3;
y();
console.log(x);
}
foo();//2
x ;//1
去掉var後,函數內x指向參數x,故匿名y的執行會影響函數內部,所以輸出2,由於作用域不同,全局仍不受影響。
7.應用--設置一個函數必須傳參才能調用:
function a(){
throw new Errow('請傳入參數再調用')
}
function foo(b = a()){
return b;
}
foo(); //Error:請傳入參數再調用
8.參數的默認值不是在定義時執行,而是在運行時執行。如果參數已經賦值,默認值中的函數就不會運行。
9.將參數默認值設爲undefined
,表明這個參數是可以省略的。
Rest參數
1. rest 參數(形式爲...變量名
),用於獲取函數的多餘參數,rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
2.是一個真正的數組,數組特有的方法都可以使用。
3.rest 參數之後不能再有其他參數(即只能是最後一個參數),否則會報錯。
4.函數的length
屬性,不包括 rest 參數。
嚴格模式
1.只要函數參數使用了默認值、解構賦值、或者擴展運算符,那麼函數內部就不能顯式設定爲嚴格模式,否則會報錯。
2.兩種方法可以規避這種限制。第一種是設定全局性的嚴格模式,這是合法的。
3.第二種是把函數包在一個無參數的立即執行函數裏面。
name屬性
1.函數的name
屬性,返回該函數的函數名。
2.如果將一個匿名函數賦值給一個變量,ES5 的name
屬性,會返回空字符串,而 ES6 的name
屬性會返回實際的函數名。
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
3.Function
構造函數返回的函數實例,name
屬性的值爲anonymous
。
4.bind
返回的函數,name
屬性值會加上bound
前綴。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
箭頭函數
1.由於大括號被解釋爲代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號,否則會報錯。
let getTempItem = id => ({ id: id, name: "Temp" });
下面是一種特殊情況,雖然可以運行,但會得到錯誤的結果。
let foo = () => { a: 1 };
foo() // undefined
//a可以被解釋爲語句的標籤,因此實際執行的語句是1;,然後函數就結束了,沒有返回值。
2.箭頭函數有幾個使用注意點:
(1)函數體內的this
對象,就是定義時所在的對象,而不是使用時所在的對象。
(2)不可以當作構造函數,也就是說,不可以使用new
命令,否則會拋出一個錯誤。
(3)不可以使用arguments
對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。
(4)不可以使用yield
命令,因此箭頭函數不能用作 Generator 函數。
上面四點中,第一點尤其值得注意。this
對象的指向是可變的,但是在箭頭函數中,它是固定的。
3.this
指向的固定化,並不是因爲箭頭函數內部有綁定this
的機制,實際原因是箭頭函數根本沒有自己的this
,導致內部的this
就是外層代碼塊的this
4.由於箭頭函數沒有自己的this
,所以當然也就不能用call()
、apply()
、bind()
這些方法去改變this
的指向。
5.除了this
,以下三個變量在箭頭函數之中也是不存在的,指向外層函數的對應變量:arguments
、super
、new.target
。
6.不適應場景:
- 第一個場合是定義函數的方法,且該方法內部包括
this,調用函數時,this指向了全局對象,因此不會得到預期結果。
let cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
cat.jumps(); //NaN 全局中並沒有定義lives
- 第二個場合是需要動態
this
的時候,也不應使用箭頭函數。
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
this -> Window
因爲button
的監聽函數是一個箭頭函數,導致裏面的this
就是全局對象。
動態this -- 動態綁定事件 -- xx.addEventListener
- 如果函數體很複雜,不單純是爲了計算值,這時也不應該使用箭頭函數,而是要使用普通函數,這樣可以提高代碼可讀性。
部署管道機制(pipeline)的例子詳解:
在解析之前需要的知識點:
1.reduce()方法:
按照菜鳥教程所說:
語法和參數設定:
接下來看原函數:
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)
看起來並不好理解,用ES5轉換一下:
const pipeline = function (...funcs) {
return function (val){
return funcs.reduce(
function(a,b){
return b(a);
},val);
}
}
const plus1 = function (a){
return a + 1;
}
const mult2 = function (a){
return a * 2;
}
這樣看起來會清楚一些,部署管道機制意思就是將上一個函數的輸出傳遞給下一個函數使用:
我們來輸出一下addThenMult(它是上一個pipeline的輸出):
//當將參數傳入pipeline調用後,我們輸出一下:
ƒ (val) {
return funcs.reduce(
function (a, b) {
return b(a)
}, val
)
}
這個時候pipeline的參數(兩個function)和上面的輸出即爲addThenMult所用,然後執行addThenMult(5)
由於該示例非常具有誤導性(可能是我腦子不太好使= =),所以分佈寫出來:
//步驟1:
ƒ (5) {
return funcs.reduce(
function (a=>a+1(total初始值), a=>a*2) {
return b(a)
}, 5 //傳遞給初始值
)
}
//步驟2:
ƒ (5) {
return funcs.reduce(
function (a=>5+1(total初始值), a=>a*2) {
return b(a)
}, 5 //傳遞給初始值
)
}
//步驟3:
ƒ (5) {
return funcs.reduce(
function (a=>5+1(total初始值), a=>a*2) {
return b(6)
}, 5 //傳遞給初始值
)
}
//步驟4:
ƒ (5) {
return funcs.reduce(
function (a=>5+1(total初始值), a=>a*2) {
return 6=>6 * 2 //12
}, 5 //傳遞給初始值
)
}
可讀性強的寫法:
const plus1 = a => a + 1;
const mult2 = a => a * 2;
mult2(plus1(5))// 12
//ES5
const plus1 = function(a){
return a+1;
}
const mult2 = function(a){
return a*2;
}
步驟:
- plus1先傳入5調用 返回 5+1爲mult2所用
- mult2使用6作爲參數帶入調用,返回12
雙冒號運算符
Emmm,到目前應該還是個提案
尾調用優化
1.某個函數的最後一步是調用另一個函數爲尾調用。
2.當最後一步調用函數後還有操作時不叫尾調用,沒有最後一個return確認尾部時也不屬於尾調用。
// 情況一
function f(x){
let y = g(x);
return y;
}
// 情況二
function f(x){
return g(x) + 1;
}
// 情況三
function f(x){
g(x);
}
3.尾調用不一定出現在函數尾部,只要是最後一步操作即可。
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
//都屬於尾調用 都是函數f的最後一步操作
4.函數調用會在內存形成一個“調用記錄”,又稱“調用幀”,保存調用位置和內部變量等信息,如果A調B,則在A幀上方會形成一個B幀,B運
行結束後返回結果B幀纔會消失,以此類推會形成調用棧。
這個過程的優化叫做尾調用優化,即只保留內層函數的調用幀。
function g(x){
return x;
}
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同於
function f() {
return g(3);
}
f();
// 等同於
g(3);
1.只有不再用到外層函數的內部變量,內層函數的調用幀纔會取代外層函數的調用幀,否則就無法進行“尾調用優化”。
function addOne(a){
var one = 1;
function inner(b){
return b + one; //用到了外層one , 所以不會進行優化
}
return inner(a);
}
尾遞歸
1.遞歸非常耗費內存,因爲需要同時保存成千上百個調用幀,很容易發生“棧溢出”錯誤(stack overflow)
2.尾遞歸,由於只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤。
下面是例子:
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
上面函數會保存很多調用記錄,改寫:
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120 total的默認值爲1
斐波那契數列:
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出
//尾遞歸優化過的 Fibonacci 數列實現如下。
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
/*
Fibonacci2(3)的調用過程
return Fibonacci2(2,1,2)
return Fibonacci2(1,2,3) n<=1 輸出3
*/
遞歸函數的改寫
.....