ES6學習筆記之函數擴展

一. 函數參數的默認值

ES6 允許為函數的參數設置默認值,即直接寫在參數定義的后面。

function log(x , y = 'world'){
   console.log(x, y);
}

log('hello'); //  Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

function Point(x = 0, y = 0){
    this.x = x;
    this.y = y;
}
var p = new Point();
p // {x : 0, y: 0}

Tips:參數變量是默認聲明的,所以不能用let或const再次聲明。

//下面代碼中,參數變量x是默認聲明的,在函數體中,不能用let或const再次聲明,否則會報錯。
function foo(x = 5){
    let x = 1;  //error
    const x =2; //error
 }

參數默認值不是傳值的,而是每次都重新計算默認值表達式的值。也就是說,參數默認值是惰性求值的。

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

foo(); // 100
x = 100;
foo(); // 101

Tips:參數p的默認值是x + 1,每次調用函數foo,都會重新計算x + 1,而不是默認p等于 100。

二 .與解構賦值默認值結合使用

function foo({x , y = 5}){
    console.log(x, y);
}

foo({}); // undefined 5
foo({x : 1}); //1, 5
foo({x : 1, y : 2}); // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

Tips:上面代碼只使用了對象的解構賦值默認值,沒有使用函數參數的默認值。只有當函數foo的參數是一個對象時,變量x和y才會通過解構賦值生成。如果函數foo調用時沒提供參數,變量x和y就不會生成,從而報錯。通過提供函數參數的默認值,就可以避免這種情況。

//下面代碼指定,如果沒有提供參數,函數foo的參數默認為一個空對象。
function foo({x, y = 5} = {}){
    console.log(x, y);
}

foo(); //undefined 5

  function fetch(url, { body = '', method = 'Get', headers = {}}){
  console.log(method);
}

fetch('http://example.com', {});
//'GET'

fetch('http://example.com');
//報錯

Tips: 上面代碼中,如果函數fetch的第二個參數是一個對象,就可以為它的三個屬性設置默認值。這種寫法不能省略第二個參數,如果結合函數參數的默認值,就可以省略第二個參數。這時,就出現了雙重默認值。

function fetch(url, {method = 'GET'} = {}){
  console.log(method);
}

fetch('http://example.com');
//'GET'

//寫法一
function m1({x = 0, y = 0} = {}){
    return [x, y];
 }

 //寫法二
 function m2({x , y} = { x : 0, y : 0}){
       return [x, y]
  }

Tips:上面兩種寫法都對函數的參數設定了默認值,區別是寫法一函數參數的默認值是空對象,但是設置了對象解構賦值的默認值;寫法二函數參數的默認值是一個有具體屬性的對象,但是沒有設置對象解構賦值的默認值。

//函數沒有參數
m1() // [0, 0]
m2() // [0, 0]

//x和y都有值的情況
m1({x : 3, y : 8}) // [3, 8]
m2({x: 3,  y : 8}) // [3, 8]

//x有值,y都無值的情況
m1({x : 3}) // [3, 0]
m2({x : 3}) // [3, undefined]

//x和y都無值
m1({}) // [0, 0]
m2({}) //[undefined, undefined]

m1({z : 3}) // [0, 0]
m2({z : 3}) // [undefined, undefined]

三. 參數默認值的位置

如果非尾部的參數設置默認值,實際上這個參數是沒法省略的。

//例一
function f(x = 1, y){
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined]
f(, 1)// 報錯
f(undefined, 1)// [1, 1]

//例二
function f(x, y = 5, z){
    return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報錯
f(1, undefined, 2) //[1, 5, 2]

Tips:上面代碼中,有默認值的參數都不是尾參數。

//如果傳入undefined,將觸發該參數等于默認值,null則沒有這個效果。
 function foo(x = 5, y =6){
    console.log(x, y);
 }

foo(undefined, null)
//5 null

Tips: 上面代碼中,x參數對應undefined,結果觸發了默認值,y參數等于null,就沒有觸發默認值。

四. 函數的length屬性

指定了默認值以后,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值后,length屬性將失真。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

五. 作用域

一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域(context)。等到初始化結束,這個作用域就會消失。這種語法行為,在不設置參數默認值時,是不會出現的。

var  x = 1;
function f(x, y = x){
    console.log(y);
}
f(2);  // 2

Tips:上面代碼中,參數y的默認值等于變量x。調用函數f時,參數形成一個單獨的作用域。在這個作用域里面,默認值變量x指向第一個參數x,而不是全局變量x,所以輸出是2。

let x = 1;
function f(y = x){
  let x = 2;
  console.log(y);
}
f() // 1

*Tips: 上面代碼中,函數f調用時,參數y = x形成一個單獨的作用域。這個作用域里面,變量x本身沒有定義,所以指向外層的全局變量x。函數調用時,函數體內部的局部變量x影響不到默認值變量x。*

//如果此時,全局變量x不存在,就會報錯。
function f(y = x){
  let x = 2;
  console.log(y);
 }

 f() // ReferenceError: x is not defined

//這樣寫,也會報錯。
function foo(x = x){
    // ...
}
foo() // ReferenceError: x is not defined

*Tips:上面代碼中,參數x = x形成一個單獨作用域。實際執行的是let x = x,由于暫時性死區的原因,這行代碼會報錯”x 未定義“。*

如果參數的默認值是一個函數,該函數的作用域也遵守這個規則。

let foo = 'outer';
function bar(func = () => foo){
    let foo =  'inner';
    console.log(func());
}
bar(); // outer

 function bar(func = () => foo){
      let foo = 'inner';
      console.log(func());
 }
 bar() //ReferenceError: foo is not defined

六. rest參數

ES6 引入 rest 參數(形式為...變量名),用于獲取函數的多余參數,這樣就不需要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多余的參數放入數組中。

function add(...values){
let sum = 0;
for(var val of values){
sum += val
}
return sum;
}

add(2, 5, 3);

rest 參數就是一個真正的數組,數組特有的方法都可以使用。

function push(array, ...items){
    items.forEach(function(item){
        array.push(item);
        console.log(item);
  });
}

var a =[];
push(a, 1, 2, 3);

注意,rest 參數之后不能再有其他參數(即只能是最后一個參數),否則會報錯。

//報錯
function(a, ...b, c){
    //...
}

函數的length屬性,不包括 rest 參數。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

七. name屬性

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

*Tips:如果將一個匿名函數賦值給一個變量,ES5 的name屬性,會返回空字符串,而 ES6 的name屬性會返回實際的函數名。 *

var f = function() {};

//ES5
f.name // ''''

//ES6
f.name // "f"

八 .箭頭函數

ES6 允許使用“箭頭”(=>)定義函數。

var f = v => v;

等同于

var f = function(v){
     return v;
  }

如果箭頭函數不需要參數或需要多個參數,就使用一個圓括號代表參數部分。

 var f = () => 5;
 var f = function(){return 5};
 var sum = (num1, num2) => num1 + num2;
  var sum = function(num1, num2){
      return num1 + num2;
  };

如果箭頭函數的代碼塊部分多于一條語句,就要使用大括號將它們括起來,并且使用return語句返回。

var sum = (num1, num2) => { return num1 + num2}

由于大括號被解釋為代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號,否則會報錯。

//報錯
let getTempItem = id => {id : id, name : "Temp"}

//不報錯
let getTempItem = id => ({id : id, name : "Temp"})

箭頭函數可以與變量解構結合使用。

const full = ({first, last}) => first + ' ' + last;

等同于

function full(person){
    return person.first + ' '+person.last;
}

rest 參數與箭頭函數結合

const numbers = (...nums) => nums;
numbers(1,2,3,4,5);
//[1, 2, 3, 4, 5]
const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
[1, [2, 3, 4, 5]]

注意:
1.函數體內的this對象,就是定義時所在的對象,而不是使用時所在對象
2.不可以當做構造函數,不能使用new命令,否則拋出錯誤
3.不可以使用arguments對象,用 rest參數代替
4.不能用yield命令

//在箭頭函數中,this對象的指向是可變的
function foo(){
    setTimeout(() => {
    console.log('id', this.id);
    }, 100);
}

var id = 21;
foo.call({id : 42});
//id :42

Tips:上面代碼中,setTimeout的參數是一個箭頭函數,這個箭頭函數的定義生效是在foo函數生成時,而它的真正執行要等到100毫秒后。如果是普通函數,執行時this應該指向全局對象window,這時應該輸出21。但是,箭頭函數導致this總是指向函數定義生效時所在的對象(本例是{id: 42}),所以輸出的是42。

function Timer(){
    this.s1 = 0;
    this.s2 = 0;
    setInterval(() => this.s1++, 1000);
    setInterval(function(){
          this.s2++;
    }, 1000);

 var timer = new Timer();
 setTimeout(() => console.log('s1: ', timer.s1), 3100);
  //s1 : 3

 setTimeout(() => console.log('s2: ', timer.s2), 3100);
  //s2 : 0

*Tips:Timer函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數。前者的this綁定定義時所在的作用域(即Timer函數),后者的this指向運行時所在的作用域(即全局對象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都沒更新。 *

九.尾調用優化

尾調用(Tail Call)是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數的最后一步是調用另一個函數。

function f(x){
    return g(x);
}

//以下三種情況,都不屬于尾調用。
//情況一
function f(x){
    let y = g(x);
    return y;
}

//情況二
function f(x){
   return g(x) + 1;
}

情況三
function f(x){
    g(x);
}

Tips:情況一是調用函數g之后,還有賦值操作,所以不屬于尾調用,即使語義完全一樣。情況二也屬于調用后還有操作,即使寫在一行內

尾調用不一定出現在函數尾部,只要是最后一步操作即可。

//函數m和n都屬于尾調用
function f(x) {
   if(x > 0){
     return m(x);
   }
  return n(x);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 函數參數的默認值 基本用法 在ES6之前,不能直接為函數的參數指定默認值,只能采用變通的方法。 上面代碼檢查函數l...
    呼呼哥閱讀 3,438評論 0 1
  • 第一章 塊級作用域綁定 let 和 const 都是不存在提升,聲明的都是塊級標識符都禁止重聲明 每個const聲...
    NowhereToRun閱讀 1,599評論 0 2
  • 1.函數參數的默認值 (1).基本用法 在ES6之前,不能直接為函數的參數指定默認值,只能采用變通的方法。
    趙然228閱讀 704評論 0 0
  • 三,字符串擴展 3.1 Unicode表示法 ES6 做出了改進,只要將碼點放入大括號,就能正確解讀該字符。有了這...
    eastbaby閱讀 1,561評論 0 8
  • 最近很多人都在問老師,自己的皮膚變得很嬌氣,覺得緊繃不舒服,是不是過敏了?為此老師給大家普及一下肌膚敏感的特征,原...
    王斌老師閱讀 595評論 0 1