7函數的擴展

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

報錯情景

  1. 當函數參數x有默認值,再在函數中聲明就會報錯
  2. 當函數參數都沒有默認值,允許參數同名。只要函數參數有一個就沒有默認值,就不允許函數參數同名。
  3. 當函數參數有對象解構的情況,函數對象中的屬性不能和其它參數同名
// 情景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() // 報錯
  1. 報錯原因,當沒有參數時,其實默認參數為undefined。對象和undefined發生解構報錯。
  2. 正確原因,傳入對象,發生解構,x沒有默認解構值,則為undefined,y有默認解構值,則為1
function fn({x,y=1} = {}) {
    console.log(x,y)
}
fn({}) // undefined,1
fn() // undefined,1
  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];
}

默認值位置

  1. 應該時函數的尾參數
  2. 有默認值,會影響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);
}
  1. 函數f的參數形成一個默認作用域。函數初始化過程
  • 參數x被賦值為2
  • 參數y被賦值為x,在當前作用域中找x,找到x = 2,因此y = 2
  1. 函數初始化過程
  • 沒有參數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
  1. 函數初始化
  • 參數x沒有默認值,因此,在函數foo中再聲明x不會報錯
  • 在函數foo的參數作用域中,x先為undefined,在調用y時,x是參數而不是全局變量x,此時參數x改為2
  • 但是由于,在函數再次聲明了x,這個x完全不是參數,因此函數foo打印x為3
  1. 當去掉var x = 3,函數foo的參數,其實相當于聲明并賦值參數x,沒有函數內部變量x,參數x就會被打印。

rest參數

  1. 形式,function fn(...rest)
  2. rest參數是數組,之后不允許有參數

嚴格模式

  1. 函數中可以使用use strict設置嚴格模式
  2. 當函數參數有默認值,解構賦值,擴展運算符時,不允許使用嚴格模式
  3. 有兩種方法可以規避以上規則
  • 全局嚴格模式
  • 在立即調用的函數中使用嚴格模式

name屬性

  1. 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};
  1. 其中函數名省略,則為匿名函數
  2. 根據參數情況也可以省略
  • 當沒有參數或者兩個及兩個以上參數時,(小括號不可省略
  • 當有一個參數時,小括號可以省略
  1. 當函數體只有一條語句,可以省略{大括號,并默認有return返回。當不需要返回值時
  • 即使一條語句也加上{,這樣就沒有返回值
  • 使用void (一條語句),這樣也沒有返回值
var fn = (x,y) => x + y;
// 等價于
function fn (x,y) {
    return x + y;
} 

箭頭函數

  1. this固定,指向定義時的this
  2. 箭頭函數不能做構造函數
  3. 不能使用arguments
  4. 不能使用yield,也就是箭頭函數不能做Generator

注意點

  1. 箭頭函數中沒有自己的this,只是引用外層的this
  2. 箭頭函數無法使用call(),apply(), bind()改變this執行

分析過程(一)

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// result: 42
  1. 當執行foo.call({id: 42})內部的this指向{id: 42}
  2. 此時,箭頭函數沒有自己的this。外部的this就是{id:42}
  3. 即使100毫秒后,在setTimeout中,this也不改變為window

分析過程(二)

function foo() {
  setTimeout(function() {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// result: 21
  1. 普通函數,this指向運行時的上下文環境
  2. setTimeout偽代碼function setTimeout() {//delay... callback();},可以看到callback函數,也就是普通函數的沒有綁定到其它對象上

尾調用

  1. 最后一步調用其它函數,稱為尾調用
  2. 尾調用函數定義時,不使用外層函數的變量
  3. 尾調用,有利于節約內存
// 這種情況沒有使用外層函數的變量
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();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容