1. 程式人生 > >ES6隨筆--各數據類型的擴展(3)--函數

ES6隨筆--各數據類型的擴展(3)--函數

自己的 模式 無法執行 while 0ms foo 初始化 繼續 中間

ES6隨筆--各數據類型的擴展(3)--函數

1. 函數參數的默認值

可以在函數參數中指定默認值,函數內不能再用letconst聲明;

使用參數默認值時,不能出現同名參數;

參數默認值惰性求值,每次調用函數會重新計算參數默認值表達式;

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

可以與解構賦值默認值結合使用:

function ({x, y = 5} = {}) {
    console.log(x, y);
}
function(); // undefined 5
// 以下對比默認值設置不同方式
      function bar({x, y} = {x:0, y:0}) {
        console.log(x, y);
      }
      function bar1({x=0, y=0} = {}) {
        console.log(x, y);
      }
      bar();     //0  0
      bar1();    //0  0
      bar({x:2, y:3});    //2  3
      bar1({x:2, y:3});   //2  3
      bar({x:2});   // 2  undefined
      bar1({x:2});  // 2  0
      bar({y:2});   // undefined   2
      bar1({y:2});  // 0  2
      bar({z:4});   // undefined  undefined
      bar1({z:4});   // 0  0
      bar({});   // undefined  undefined
      bar1({});   // 0  0

設置了默認值的參數一般是函數的尾參數,該參數可以省略;如果位於中間,則無法只省略該參數繼續為後面的參數賦值,必須要設置為undefined,否則直接省略而為後面的參數賦值會報錯;
undefined可以觸發默認值;
函數的length屬性中不包含指定了默認值的參數。

指定了參數默認值的函數,函數進行聲明初始化時該參數會形成單獨的作用域;函數執行完畢後該作用域消失;

// 函數內部 let 聲明的變量與參數變量不同作用域
      var x = 1;
      function foo(y = x) {
        let x = 2;
        console.log(y); 
      }

      foo() // 1
// 參數的作用域與全局和函數內部都不同;
      var x = 1;
      function foo(x, y = x) {
        x = 2;
        console.log(y); 
      }
      foo(); // undefined
      foo(2); // 2
      console.log(x); // 1
// 參數設置默認值時指向全局變量x, 函數內部修改了全局變量,但並不影響參數y--不在同一個作用域;
      var x = 1;
      function foo(y = x) {
        x = 2;
        console.log(y); 
      }
      foo(); // 1
      console.log(x); // 2

// 暫時性死區報錯(參數默認值相當於 let x = x,此時會忽略全局裏的定義)
      var x = 1;
      function foo(x = x) {
        console.log(x); 
      }

      foo() // ReferenceError: x is not defined

函數的參數默認值可以用來設置哪些參數不能省略;

2. rest函數

形式為...varialbleName,獲取函數的多余參數放入數組中;

// 改寫 push 方法的例子 
      function push(array, ...items) {
        items.forEach(function(item) {
          array.push(item);
          console.log(item);
        });
        return array.length;
      }
      var a = [];
      console.log(push(a, 1, 2, 3)); // 1 2 3 3

rest參數只能是最後一個參數;函數的length屬性,不包含rest函數;

3. 嚴格模式

只要函數參數使用了默認值,解構賦值或者擴展運算符,函數內部就不能顯式設定嚴格模式;
替代的辦法 1) 使用全局性的嚴格模式;2) 使用立即執行的無參函數,使用嚴格模式並返回該函數;

4. name 屬性

函數的name屬性,返回該函數的函數名;

如果把匿名函數賦值給一個變量,匿名函數的name屬性返回該變量名(ES5 則為空字符串);

如果把一個具名函數賦值給一個變量,該函數的name屬性返回原本的函數名。

      const f = function () {};
      console.log(f.name); // f
      const f1 = function func() {};
      console.log(f1.name); // func

Function構造函數返回的函數實例, name屬性返回anonymous;

使用bind()返回的函數,name屬性會自動在函數名前面加bound

5. 箭頭函數

使用箭頭=>定義函數;funcName(args) => {code block}function funcName(args) = {code block}相同;

註意:
1. 箭頭函數中的this綁定函數定義時所在的作用域,而不是運行時的作用域;
2. 不能使用new命令;
3. 不可以使用argumets對象,可以使用rest參數代替;
4. 不可以使用yield命令,因此箭頭函數不能用作Generator函數;

箭頭函數本身沒有自己的this,在定義時指向外部作用域的對象;也不能通過call,bind,apply改變this的指向。

      var f = function () {console.log(this.id);}
      var f1 = () => {console.log(this.id)};
      f.call({id:11});  // 11
      f.call({id:22});  // 22
      f1.call({id:11}); // undefined
      f1.call({id:22}); // undefined
// 大括號被解釋為代碼塊,所以需要返回一個對象的情況下需要在大括號外加小括號;(再加一層大括號無效);
      var f = (x) => {a:x};
      console.log(f(3)); // undefined
      var f = (x) => ({a:x});
      console.log(f(3)); // a : 3

嵌套函數;

部署管道機制;

改寫λ演算;

6. 雙冒號運算符(提案)

用來取代bind, apply, call的作用,綁定對象作用域;

// 將左邊對象,作為上下文環境(即this 對象),綁定到右邊的函數上
object::function

// 雙冒號左邊為空,右邊為一個對象的方法,代表將此方法綁定到該對象上;
var method = obj::obj.foo;
// 等同於
var method = ::obj.foo;

7. 尾調用優化

尾調用(Tail Call)函數的最後一步是調用另一個函數。

// 這種情況不屬於尾調用,相當於調用g(x)後返回undefined;‘return g(x)’算是一種尾調用。
      function f(x){
        g(x);
      }

在函數內部調用其他函數時,所有當前被調用而未結束的函數的調用幀會形成調用棧;

尾調用由於是最後一步操作,所以不需要保留外部函數的調用幀,因為調用位置、內部變量等信息都不會再用到。

尾調用優化,即只保留最後調用函數的調用幀。但是需要做到內部調用函數不再使用外部函數的內部變量;

函數尾調用自身,即尾遞歸;

      //尾調用 Fibonacci數列案例
      function fib(n) {
        if (n<=1) return 1;
        return fib(n-1)+fib(n-2);
      }
      function fib1(n, ac1=1, ac2=1) {
        if (n<=1) return ac2;
        return fib1(n-1, ac2, ac1+ac2);
      }
      console.log(fib(10)); //~20ms 是優化函數運行時間至少10倍;
      console.log(fib1(10));  //~1ms
      console.log(fib(100));   // 無法計算,棧溢出
      console.log(fib1(100));   // <10ms

尾遞歸優化只能在嚴格模式下開啟,正常模式無法執行;

尾遞歸優化在非嚴格模式下無效;實現方法可以使用循環代替:

// 非嚴格模式下實現尾調用優化--循環
// 遞歸函數
        function test(x, y) {
          if (y > 0) {
            return test(x+1, y-1);
          } else {
            return x;
          }
        }
// 蹦床函數
        function trampoline(fn) {
            while (fn && fn instanceof Function) {
              fn = fn();
            }
            return fn;          
        }
//改寫遞歸函數
        function test1(x, y) {
          if (y > 0) {
            return test1.bind(null, x+1, y-1);
          } else {
            return x;
          }
        }
// 通過蹦床函數調用遞歸函數
        var a1 = trampoline(test1(1, 100000));
        console.log(a1); // 100001

8. 函數參數的尾逗號

ECMA2017允許函數的最後一個參數後面有逗號;

附加個人隨記:bind方法

bind可以直接指定綁定的對象,以及對應的參數,返回綁定後的函數,後面加括號才會執行;

function testbind(str) {
  return str + this;
}
var a1 = {name:"Bai"};
console.log(testbind.bind(a1)); 
// ? testbind(str) {
//   return str + this;
// }
console.log(testbind.bind(a1,"huahua")); 
// ? testbind(str) {
//   return str + this;
// }
console.log(testbind()); //undefined[object Window]
console.log(testbind.bind(a1,"huahua")()); //huahua[object Object]
console.log(testbind.bind(a1)("huahua")); //huahua[object Object]

ES6隨筆--各數據類型的擴展(3)--函數