1. 程式人生 > >JavaScript學習筆記-函式

JavaScript學習筆記-函式

函式

1.函式本身的作用域

函式本身也是一個值,也有自己的作用域。它的作用域與變數一樣,就是其宣告時所在的作用域,與其執行時所在的作用域無關。

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

上面程式碼中,函式x是在函式f的外部宣告的,所以它的作用域繫結外層,內部變數a不會到函式f體內取值,所以輸出1,而不是2。
總之,函式執行時所在的作用域,是定義時的作用域,而不是呼叫時所在的作用域。
很容易犯錯的一點是,如果函式A呼叫函式B,卻沒考慮到函式B不會引用函式A的內部變數。

2.函式名的提升 (變數提升)

JavaScript 引擎將函式名視同變數名,所以採用function命令宣告函式時,整個函式會像變數宣告一樣,被提升到程式碼頭部。所以,下面的程式碼不會報錯。

f();

function f() {}

表面上,上面程式碼好像在宣告之前就呼叫了函式f。但是實際上,由於“變數提升”,函式f被提升到了程式碼頭部,也就是在呼叫之前已經聲明瞭。但是,如果採用賦值語句定義函式,JavaScript 就會報錯。

f();
var f = function (){};
// TypeError: undefined is not a function

上面的程式碼等同於下面的形式。

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

上面程式碼第二行,呼叫f的時候,f只是被聲明瞭,還沒有被賦值,等於undefined,所以會報錯。因此,如果同時採用function命令和賦值語句宣告同一個函式,最後總是採用賦值語句的定義。

    var f = function () {
      console.log('1');
    }
    
    function f() {
      console.log('2');
    }
    
    f() // 1

4.arguments 物件

定義:arguments物件包含了函式執行時的所有引數,arguments[0]就是第一個引數,arguments[1]就是第二個引數,以此類推。這個物件只有在函式體內部,才可以使用。

function f() {
  return arguments.length;
}

f(1, 2, 3) // 3
f(1) // 1
f() // 0

5.閉包

理解閉包,首先必須理解變數作用域。前面提到,JavaScript 有兩種作用域:全域性作用域和函式作用域。函式內部可以直接讀取全域性變數。

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

上面程式碼中,函式f1的返回值就是函式f2,由於f2可以讀取f1的內部變數,所以就可以在外部獲得f1的內部變量了。

定義:能夠讀取其他函式內部變數的函式,定義在一個函式內部的函式。
特點:“記住”誕生的環境,比如f2記住了它誕生的環境f1,所以從f2可以得到f1的內部變數。
用處:一個是可以讀取函式內部的變數。另一個是讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在
注意:外層函式每次執行,都會生成一個新的閉包,而這個閉包又會保留外層函式的內部變數,所以記憶體消耗很大。因此不能濫用閉包,否則會造成網頁的效能問題。

6. eval命令

eval命令接受一個字串作為引數,並將這個字串當作語句執行。

eval('var a = 1;');
a // 1

eval沒有自己的作用域,都在當前作用域內執行,因此可能會修改當前作用域的變數的值,造成安全問題。

var a = 1;
eval('a = 2');
a // 2

使用場景:解析 JSON 資料的字串,不過正確的做法應該是使用原生的JSON.parse方法
注意:當eval是別名呼叫時,它的作用域講變成全域性作用域。如下

var a = 1;
function f() {
  var a = 2;
  var e = eval;
  e('console.log(a)');
}

f() // 1`

7.Number()函式

使用Number函式,可以將任意型別的值轉化成數值
1. 原始型別值

// 數值:轉換後還是原來的值
Number(324) // 324

// 字串:如果可以被解析為數值,則轉換為相應的數值
Number('324') // 324

// 字串:如果不可以被解析為數值,返回 NaN
Number('324abc') // NaN

// 空字串轉為0
Number('') // 0

// 布林值:true 轉成 1,false 轉成 0
Number(true) // 1
Number(false) // 0

// undefined:轉成 NaN
Number(undefined) // NaN

// null:轉成0
Number(null) // 0

對比parseInt函式,Number函式嚴格很多,有一個字元不能轉化為數值,則整個字串就被轉為NaN

parseInt('42 cats') // 42
Number('42 cats') // NaN

2. 物件

轉換規則:Number方法的引數是物件時,將返回NaN,除非是包含單個數值的陣列。

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

轉換步驟:
1. 用物件自身的valueOf方法。如果返回原始型別的值,則直接對該值使用Number函式,不再進行後續步驟。
2. 如果valueOf方法返回的還是物件,則改為呼叫物件自身的toString方法。如果toString方法返回原始型別的值,則對該值使用Number函式,不再進行後續步驟。
3. 如果toString方法返回的是物件,就報錯。

8. String()函式

1. 原始型別值

String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"

2. 物件
String方法的引數若是物件,則返回一個型別字串,若是陣列,則返回陣列的字串形式

String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

轉換規則:先執行valueOf方法,再執行toString方法
轉換步驟:
1. 先呼叫物件自身的toString方法。如果返回原始型別的值,則對該值使用String函式,不再進行以下步驟。
2. 如果toString方法返回的是物件,再呼叫原物件的valueOf方法。如果valueOf方法返回原始型別的值,則對該值使用String函式,不再進行以下步驟。
3. 如果valueOf方法返回的是物件,就報錯。

參考:阮一峰的JavaScript 教程