OLD函數默認參數
// 缺點:布爾值為false的變量都會被賦為默認值
function fn(x) {
x = x || 'hello'
}
// 比較麻煩
function fn (x) {
if (typefo x === 'undefined') {
x = 'hello'
}
}
基本用法
在 ES2017 中,允許定義和調用函數時,最后一個參數有
,
惰性求值
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
報錯情景
- 當函數參數
x
有默認值,再在函數中聲明就會報錯 - 當函數參數都沒有默認值,允許參數同名。只要函數參數有一個就沒有默認值,就不允許函數參數同名。
- 當函數參數有對象解構的情況,函數對象中的屬性不能和其它參數同名
// 情景3 ---- 報錯
function fn (x,{x = 1,n = 2}={}) {
console.log(x,n)
}
fn('yy');
函數參數對象
function fn({x,y=1}) {
console.log(x,y)
}
fn({}) // undefined,1
fn() // 報錯
- 報錯原因,當沒有參數時,其實默認參數為
undefined
。對象和undefined
發生解構報錯。 - 正確原因,傳入對象,發生解構,x沒有默認解構值,則為undefined,y有默認解構值,則為1
function fn({x,y=1} = {}) {
console.log(x,y)
}
fn({}) // undefined,1
fn() // undefined,1
-
fn()
執行過程如下
- 調用
fn
,沒有參數,使用函數默認參數{}
- 發生對象解構,x沒有解構默認值,y有默認解構值
// 分析以下案例
// 寫法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 寫法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
默認值位置
- 應該時函數的尾參數
- 有默認值,會影響
fn.length
- 一般是
fn.length - 默認參數個數
- 當默認參數不是尾參數,
fn.length
是第一個默認參數之前的參數的個數 - 當參數是
...rest
,fn.length
是0
作用域
當函數有默認值時,參數會形成一個獨立的作用域
簡單案例
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
function ff (y = x) {
console.log(y);
}
- 函數
f
的參數形成一個默認作用域。函數初始化過程
- 參數
x
被賦值為2 - 參數
y
被賦值為x
,在當前作用域中找x
,找到x = 2
,因此y = 2
- 函數初始化過程
- 沒有參數
x
- 參數
y
被賦值為x
,在當前作用域中沒有x
,找到全局變量x
,因此y = 1
參數為函數的案例
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
- 函數初始化
- 參數
x
沒有默認值,因此,在函數foo
中再聲明x
不會報錯 - 在函數
foo
的參數作用域中,x
先為undefined
,在調用y
時,x
是參數而不是全局變量x
,此時參數x
改為2 - 但是由于,在函數再次聲明了
x
,這個x
完全不是參數,因此函數foo
打印x
為3
- 當去掉
var x = 3
,函數foo
的參數,其實相當于聲明并賦值參數x
,沒有函數內部變量x
,參數x
就會被打印。
rest參數
- 形式,
function fn(...rest)
- rest參數是數組,之后不允許有參數
嚴格模式
- 函數中可以使用
use strict
設置嚴格模式 - 當函數參數有默認值,解構賦值,擴展運算符時,不允許使用嚴格模式
- 有兩種方法可以規避以上規則
- 全局嚴格模式
- 在立即調用的函數中使用嚴格模式
name屬性
-
name
屬性使用方式fnName.name
function fn() {} // fn.name--->fn
var fn = function () {} // fn.name--->fn
var fn = function fun() {} // fn.name----->fun
fn.bind({},1) // fn.name---->bound fn
(new Function).name // ----> anonymous
(function () {}).name //---->''
箭頭函數
箭頭函數結構
functionName = (arg1,arg2) => {arg1 + arg2};
- 其中函數名省略,則為匿名函數
- 根據參數情況也可以省略
- 當沒有參數或者兩個及兩個以上參數時,
(
小括號不可省略 - 當有一個參數時,小括號可以省略
- 當函數體只有一條語句,可以省略
{
大括號,并默認有return
返回。當不需要返回值時
- 即使一條語句也加上
{
,這樣就沒有返回值 - 使用
void (一條語句)
,這樣也沒有返回值
var fn = (x,y) => x + y;
// 等價于
function fn (x,y) {
return x + y;
}
箭頭函數
-
this
固定,指向定義時的this - 箭頭函數不能做構造函數
- 不能使用
arguments
- 不能使用
yield
,也就是箭頭函數不能做Generator
注意點
- 箭頭函數中沒有自己的
this
,只是引用外層的this
- 箭頭函數無法使用
call()
,apply()
,bind()
改變this
執行
分析過程(一)
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// result: 42
- 當執行
foo.call({id: 42})
內部的this
指向{id: 42}
- 此時,箭頭函數沒有自己的
this
。外部的this
就是{id:42}
- 即使100毫秒后,在
setTimeout
中,this
也不改變為window
分析過程(二)
function foo() {
setTimeout(function() {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// result: 21
- 普通函數,
this
指向運行時的上下文環境 -
setTimeout
偽代碼function setTimeout() {//delay... callback();}
,可以看到callback
函數,也就是普通函數的沒有綁定到其它對象上
尾調用
- 最后一步調用其它函數,稱為尾調用
- 尾調用函數定義時,不使用外層函數的變量
- 尾調用,有利于節約內存
// 這種情況沒有使用外層函數的變量
function f() {
let m = 1;
let n = 2;
return g(m + n); // 最后一步用
}
f();
// 這種情況使用了外層函數的變量
function f() {
let m = 1;
function g(n) {
return m + n;
}
return g(2); // 最后一步用
}
f();