js 函數講解

函數

來自《JavaScript 標準參考教程(alpha)》,by 阮一峯javascript

目錄

函數是一段能夠反覆調用的代碼塊。函數還能接受輸入的參數,不一樣的參數會返回不一樣的值。html

概述

函數的聲明

JavaScript 有三種聲明函數的方法。java

(1)function 命令express

function命令聲明的代碼區塊,就是一個函數。function命令後面是函數名,函數名後面是一對圓括號,裏面是傳入函數的參數。函數體放在大括號裏面。編程

function print(s) { console.log(s); } 

上面的代碼命名了一個print函數,之後使用print()這種形式,就能夠調用相應的代碼。這叫作函數的聲明(Function Declaration)。json

(2)函數表達式數組

除了用function命令聲明函數,還能夠採用變量賦值的寫法。瀏覽器

var print = function(s) { console.log(s); }; 

這種寫法將一個匿名函數賦值給變量。這時,這個匿名函數又稱函數表達式(Function Expression),由於賦值語句的等號右側只能放表達式。安全

採用函數表達式聲明函數時,function命令後面不帶有函數名。若是加上函數名,該函數名只在函數體內部有效,在函數體外部無效。閉包

var print = function x(){ console.log(typeof x); }; x // ReferenceError: x is not defined print() // function 

上面代碼在函數表達式中,加入了函數名x。這個x只在函數體內部可用,指代函數表達式自己,其餘地方都不可用。這種寫法的用處有兩個,一是能夠在函數體內部調用自身,二是方便除錯(除錯工具顯示函數調用棧時,將顯示函數名,而再也不顯示這裏是一個匿名函數)。所以,下面的形式聲明函數也很是常見。

var f = function f() {}; 

須要注意的是,函數的表達式須要在語句的結尾加上分號,表示語句結束。而函數的聲明在結尾的大括號後面不用加分號。總的來講,這兩種聲明函數的方式,差異很細微,能夠近似認爲是等價的。

(3)Function 構造函數

第三種聲明函數的方式是Function構造函數。

var add = new Function( 'x', 'y', 'return x + y' ); // 等同於 function add(x, y) { return x + y; } 

上面代碼中,Function構造函數接受三個參數,除了最後一個參數是add函數的「函數體」,其餘參數都是add函數的參數。

你能夠傳遞任意數量的參數給Function構造函數,只有最後一個參數會被當作函數體,若是隻有一個參數,該參數就是函數體。

var foo = new Function( 'return "hello world"' ); // 等同於 function foo() { return 'hello world'; } 

Function構造函數能夠不使用new命令,返回結果徹底同樣。

總的來講,這種聲明函數的方式很是不直觀,幾乎無人使用。

函數的重複聲明

若是同一個函數被屢次聲明,後面的聲明就會覆蓋前面的聲明。

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

上面代碼中,後一次的函數聲明覆蓋了前面一次。並且,因爲函數名的提高(參見下文),前一次聲明在任什麼時候候都是無效的,這一點要特別注意。

圓括號運算符,return 語句和遞歸

調用函數時,要使用圓括號運算符。圓括號之中,能夠加入函數的參數。

function add(x, y) { return x + y; } add(1, 1) // 2 

上面代碼中,函數名後面緊跟一對圓括號,就會調用這個函數。

函數體內部的return語句,表示返回。JavaScript 引擎遇到return語句,就直接返回return後面的那個表達式的值,後面即便還有語句,也不會獲得執行。也就是說,return語句所帶的那個表達式,就是函數的返回值。return語句不是必需的,若是沒有的話,該函數就不返回任何值,或者說返回undefined

函數能夠調用自身,這就是遞歸(recursion)。下面就是經過遞歸,計算斐波那契數列的代碼。

function fib(num) { if (num === 0) return 0; if (num === 1) return 1; return fib(num - 2) + fib(num - 1); } fib(6) // 8 

上面代碼中,fib函數內部又調用了fib,計算獲得斐波那契數列的第6個元素是8。

第一等公民

JavaScript 語言將函數看做一種值,與其它值(數值、字符串、布爾值等等)地位相同。凡是可使用值的地方,就能使用函數。好比,能夠把函數賦值給變量和對象的屬性,也能夠看成參數傳入其餘函數,或者做爲函數的結果返回。函數只是一個能夠執行的值,此外並沒有特殊之處。

因爲函數與其餘數據類型地位平等,因此在 JavaScript 語言中又稱函數爲第一等公民。

function add(x, y) { return x + y; } // 將函數賦值給一個變量 var operator = add; // 將函數做爲參數和返回值 function a(op){ return op; } a(add)(1, 1) // 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 

不能在條件語句中聲明函數

根據 ES5 的規範,不得在非函數的代碼塊中聲明函數,最多見的狀況就是iftry語句。

if (foo) { function x() {} } try { function x() {} } catch(e) { console.log(e); } 

上面代碼分別在if代碼塊和try代碼塊中聲明瞭兩個函數,按照語言規範,這是不合法的。可是,實際狀況是各家瀏覽器每每並不報錯,可以運行。

可是因爲存在函數名的提高,因此在條件語句中聲明函數,多是無效的,這是很是容易出錯的地方。

if (false) { function f() {} } f() // 不報錯 

上面代碼的原始意圖是不聲明函數f,可是因爲f的提高,致使if語句無效,因此上面的代碼不會報錯。要達到在條件語句中定義函數的目的,只有使用函數表達式。

if (false) { var f = function () {}; } f() // undefined 

函數的屬性和方法

name 屬性

函數的name屬性返回函數的名字。

function f1() {} f1.name // "f1" 

若是是經過變量賦值定義的函數,那麼name屬性返回變量名。

var f2 = function () {}; f2.name // "f2" 

可是,上面這種狀況,只有在變量的值是一個匿名函數時纔是如此。若是變量的值是一個具名函數,那麼name屬性返回function關鍵字以後的那個函數名。

var f3 = function myName() {}; f3.name // 'myName' 

上面代碼中,f3.name返回函數表達式的名字。注意,真正的函數名仍是f3,而myName這個名字只在函數體內部可用。

name屬性的一個用處,就是獲取參數函數的名字。

var myFunc = function () {}; function test(f) { console.log(f.name); } test(myFunc) // myFunc 

上面代碼中,函數test內部經過name屬性,就能夠知道傳入的參數是什麼函數。

length 屬性

函數的length屬性返回函數預期傳入的參數個數,即函數定義之中的參數個數。

function f(a, b) {} f.length // 2 

上面代碼定義了空函數f,它的length屬性就是定義時的參數個數。無論調用時輸入了多少個參數,length屬性始終等於2。

length屬性提供了一種機制,判判定義時和調用時參數的差別,以便實現面向對象編程的」方法重載「(overload)。

toString()

函數的toString方法返回一個字符串,內容是函數的源碼。

function f() { a(); b(); c(); } f.toString() // function f() { // a(); // b(); // c(); // } 

函數內部的註釋也能夠返回。

function f() {/* 這是一個 多行註釋 */} f.toString() // "function f(){/* // 這是一個 // 多行註釋 // */}" 

利用這一點,能夠變相實現多行字符串。

var multiline = function (fn) { var arr = fn.toString().split('\n'); return arr.slice(1, arr.length - 1).join('\n'); }; function f() {/* 這是一個 多行註釋 */} multiline(f); // " 這是一個 // 多行註釋" 

函數做用域

定義

做用域(scope)指的是變量存在的範圍。在 ES5 的規範中,Javascript 只有兩種做用域:一種是全局做用域,變量在整個程序中一直存在,全部地方均可以讀取;另外一種是函數做用域,變量只在函數內部存在。ES6 又新增了塊級做用域,本教程不涉及。

函數外部聲明的變量就是全局變量(global variable),它能夠在函數內部讀取。

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

上面的代碼代表,函數f內部能夠讀取全局變量v

在函數內部定義的變量,外部沒法讀取,稱爲「局部變量」(local variable)。

function f(){ var v = 1; } v // ReferenceError: v is not defined 

上面代碼中,變量v在函數內部定義,因此是一個局部變量,函數以外就沒法讀取。

函數內部定義的變量,會在該做用域內覆蓋同名全局變量。

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

上面代碼中,變量v同時在函數的外部和內部有定義。結果,在函數內部定義,局部變量v覆蓋了全局變量v

注意,對於var命令來講,局部變量只能在函數內部聲明,在其餘區塊中聲明,一概都是全局變量。

if (true) { var x = 5; } console.log(x); // 5 

上面代碼中,變量x在條件判斷區塊之中聲明,結果就是一個全局變量,能夠在區塊以外讀取。

函數內部的變量提高

與全局做用域同樣,函數做用域內部也會產生「變量提高」現象。var命令聲明的變量,無論在什麼位置,變量聲明都會被提高到函數體的頭部。

function foo(x) { if (x > 100) { var tmp = x - 100; } } // 等同於 function foo(x) { var tmp; if (x > 100) { tmp = x - 100; }; } 

函數自己的做用域

函數自己也是一個值,也有本身的做用域。它的做用域與變量同樣,就是其聲明時所在的做用域,與其運行時所在的做用域無關。

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的內部變量。

var x = function () { console.log(a); }; function y(f) { var a = 2; f(); } y(x) // ReferenceError: a is not defined 

上面代碼將函數x做爲參數,傳入函數y。可是,函數x是在函數y體外聲明的,做用域綁定外層,所以找不到函數y的內部變量a,致使報錯。

一樣的,函數體內部聲明的函數,做用域綁定函數體內部。

function foo() { var x = 1; function bar() { console.log(x); } return bar; } var x = 2; var f = foo(); f() // 1 

上面代碼中,函數foo內部聲明瞭一個函數barbar的做用域綁定foo。當咱們在foo外部取出bar執行時,變量x指向的是foo內部的x,而不是foo外部的x。正是這種機制,構成了下文要講解的「閉包」現象。

參數

概述

函數運行的時候,有時須要提供外部數據,不一樣的外部數據會獲得不一樣的結果,這種外部數據就叫參數。

function square(x) { return x * x; } square(2) // 4 square(3) // 9 

上式的x就是square函數的參數。每次運行的時候,須要提供這個值,不然得不到結果。

參數的省略

函數參數不是必需的,Javascript 容許省略參數。

function f(a, b) { return a; } f(1, 2, 3) // 1 f(1) // 1 f() // undefined f.length // 2 

上面代碼的函數f定義了兩個參數,可是運行時不管提供多少個參數(或者不提供參數),JavaScript 都不會報錯。省略的參數的值就變爲undefined。須要注意的是,函數的length屬性與實際傳入的參數個數無關,只反映函數預期傳入的參數個數。

可是,沒有辦法只省略靠前的參數,而保留靠後的參數。若是必定要省略靠前的參數,只有顯式傳入undefined

function f(a, b) { return a; } f( , 1) // SyntaxError: Unexpected token ,(…) f(undefined, 1) // undefined 

上面代碼中,若是省略第一個參數,就會報錯。

傳遞方式

函數參數若是是原始類型的值(數值、字符串、布爾值),傳遞方式是傳值傳遞(passes by value)。這意味着,在函數體內修改參數值,不會影響到函數外部。

var p = 2; function f(p) { p = 3; } f(p); p // 2 

上面代碼中,變量p是一個原始類型的值,傳入函數f的方式是傳值傳遞。所以,在函數內部,p的值是原始值的拷貝,不管怎麼修改,都不會影響到原始值。

可是,若是函數參數是複合類型的值(數組、對象、其餘函數),傳遞方式是傳址傳遞(pass by reference)。也就是說,傳入函數的原始值的地址,所以在函數內部修改參數,將會影響到原始值。

var obj = { p: 1 }; function f(o) { o.p = 2; } f(obj); obj.p // 2 

上面代碼中,傳入函數f的是參數對象obj的地址。所以,在函數內部修改obj的屬性p,會影響到原始值。

注意,若是函數內部修改的,不是參數對象的某個屬性,而是替換掉整個參數,這時不會影響到原始值。

var obj = [1, 2, 3]; function f(o) { o = [2, 3, 4]; } f(obj); obj // [1, 2, 3] 

上面代碼中,在函數f內部,參數對象obj被整個替換成另外一個值。這時不會影響到原始值。這是由於,形式參數(o)的值實際是參數obj的地址,從新對o賦值致使o指向另外一個地址,保存在原地址上的值固然不受影響。

同名參數

若是有同名的參數,則取最後出現的那個值。

function f(a, a) { console.log(a); } f(1, 2) // 2 

上面代碼中,函數f有兩個參數,且參數名都是a。取值的時候,之後面的a爲準,即便後面的a沒有值或被省略,也是以其爲準。

function f(a, a) { console.log(a); } f(1) // undefined 

調用函數f的時候,沒有提供第二個參數,a的取值就變成了undefined。這時,若是要得到第一個a的值,可使用arguments對象。

function f(a, a) { console.log(arguments[0]); } f(1) // 1 

arguments 對象

(1)定義

因爲 JavaScript 容許函數有不定數目的參數,因此須要一種機制,能夠在函數體內部讀取全部參數。這就是arguments對象的由來。

arguments對象包含了函數運行時的全部參數,arguments[0]就是第一個參數,arguments[1]就是第二個參數,以此類推。這個對象只有在函數體內部,纔可使用。

var f = function (one) { console.log(arguments[0]); console.log(arguments[1]); console.log(arguments[2]); } f(1, 2, 3) // 1 // 2 // 3 

正常模式下,arguments對象能夠在運行時修改。

var f = function(a, b) { arguments[0] = 3; arguments[1] = 2; return a + b; } f(1, 1) // 5 

上面代碼中,函數f調用時傳入的參數,在函數內部被修改爲32

嚴格模式下,arguments對象是一個只讀對象,修改它是無效的,但不會報錯。

var f = function(a, b) { 'use strict'; // 開啓嚴格模式 arguments[0] = 3; // 無效 arguments[1] = 2; // 無效 return a + b; } f(1, 1) // 2 

上面代碼中,函數體內是嚴格模式,這時修改arguments對象就是無效的。

經過arguments對象的length屬性,能夠判斷函數調用時到底帶幾個參數。

function f() { return arguments.length; } f(1, 2, 3) // 3 f(1) // 1 f() // 0 

(2)與數組的關係

須要注意的是,雖然arguments很像數組,但它是一個對象。數組專有的方法(好比sliceforEach),不能在arguments對象上直接使用。

若是要讓arguments對象使用數組方法,真正的解決方法是將arguments轉爲真正的數組。下面是兩種經常使用的轉換方法:slice方法和逐一填入新數組。

var args = Array.prototype.slice.call(arguments); // 或者 var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); } 

(3)callee 屬性

arguments對象帶有一個callee屬性,返回它所對應的原函數。

var f = function () { console.log(arguments.callee === f); } f() // true 

能夠經過arguments.callee,達到調用函數自身的目的。這個屬性在嚴格模式裏面是禁用的,所以不建議使用。

函數的其餘知識點

閉包

閉包(closure)是 Javascript 語言的一個難點,也是它的特點,不少高級應用都要依靠閉包實現。

理解閉包,首先必須理解變量做用域。前面提到,JavaScript 有兩種做用域:全局做用域和函數做用域。函數內部能夠直接讀取全局變量。

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

上面代碼中,函數f1能夠讀取全局變量n

可是,函數外部沒法讀取函數內部聲明的變量。

function f1() { var n = 999; } console.log(n) // Uncaught ReferenceError: n is not defined( 

上面代碼中,函數f1內部聲明的變量n,函數外是沒法讀取的。

若是出於種種緣由,須要獲得函數內的局部變量。正常狀況下,這是辦不到的,只有經過變通方法才能實現。那就是在函數的內部,再定義一個函數。

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

上面代碼中,函數f2就在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是 JavaScript 語言特有的」鏈式做用域」結構(chain scope),子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。

既然f2能夠讀取f1的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎!

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

上面代碼中,函數f1的返回值就是函數f2,因爲f2能夠讀取f1的內部變量,因此就能夠在外部得到f1的內部變量了。

閉包就是函數f2,即可以讀取其餘函數內部變量的函數。因爲在 JavaScript 語言中,只有函數內部的子函數才能讀取內部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」。閉包最大的特色,就是它能夠「記住」誕生的環境,好比f2記住了它誕生的環境f1,因此從f2能夠獲得f1的內部變量。在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。

閉包的最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量始終保持在內存中,即閉包可使得它誕生環境一直存在。請看下面的例子,閉包使得內部變量記住上一次調用時的運算結果。

function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7 

上面代碼中,start是函數createIncrementor的內部變量。經過閉包,start的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算。從中能夠看到,閉包inc使得函數createIncrementor的內部環境,一直存在。因此,閉包能夠看做是函數內部做用域的一個接口。

爲何會這樣呢?緣由就在於inc始終在內存中,而inc的存在依賴於createIncrementor,所以也始終在內存中,不會在調用結束後,被垃圾回收機制回收。

閉包的另外一個用處,是封裝對象的私有屬性和私有方法。

function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { name: name, getAge: getAge, setAge: setAge }; } var p1 = Person('張三'); p1.setAge(25); p1.getAge() // 25 

上面代碼中,函數Person的內部變量_age,經過閉包getAgesetAge,變成了返回對象p1的私有變量。

注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題。

當即調用的函數表達式(IIFE)

在 Javascript 中,圓括號()是一種運算符,跟在函數名以後,表示調用該函數。好比,print()就表示調用print函數。

有時,咱們須要在定義函數以後,當即調用該函數。這時,你不能在函數的定義以後加上圓括號,這會產生語法錯誤。

function(){ /* code */ }(); // SyntaxError: Unexpected token ( 

產生這個錯誤的緣由是,function這個關鍵字便可以看成語句,也能夠看成表達式。

// 語句 function f() {} // 表達式 var f = function f() {} 

爲了不解析上的歧義,JavaScript 引擎規定,若是function關鍵字出如今行首,一概解釋成語句。所以,JavaScript引擎看到行首是function關鍵字以後,認爲這一段都是函數的定義,不該該以圓括號結尾,因此就報錯了。

解決方法就是不要讓function出如今行首,讓引擎將其理解成一個表達式。最簡單的處理,就是將其放在一個圓括號裏面。

(function(){ /* code */ }()); // 或者 (function(){ /* code */ })(); 

上面兩種寫法都是以圓括號開頭,引擎就會認爲後面跟的是一個表示式,而不是函數定義語句,因此就避免了錯誤。這就叫作「當即調用的函數表達式」(Immediately-Invoked Function Expression),簡稱 IIFE。

注意,上面兩種寫法最後的分號都是必須的。若是省略分號,遇到連着兩個 IIFE,可能就會報錯。

// 報錯 (function(){ /* code */ }()) (function(){ /* code */ }()) 

上面代碼的兩行之間沒有分號,JavaScript 會將它們連在一塊兒解釋,將第二行解釋爲第一行的參數。

推而廣之,任何讓解釋器以表達式來處理函數定義的方法,都能產生一樣的效果,好比下面三種寫法。

var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); 

甚至像下面這樣寫,也是能夠的。

!function () { /* code */ }(); ~function () { /* code */ }(); -function () { /* code */ }(); +function () { /* code */ }(); 

一般狀況下,只對匿名函數使用這種「當即執行的函數表達式」。它的目的有兩個:一是沒必要爲函數命名,避免了污染全局變量;二是 IIFE 內部造成了一個單獨的做用域,能夠封裝一些外部沒法讀取的私有變量。

// 寫法一 var tmp = newData; processData(tmp); storeData(tmp); // 寫法二 (function () { var tmp = newData; processData(tmp); storeData(tmp); }()); 

上面代碼中,寫法二比寫法一更好,由於徹底避免了污染全局變量。

eval 命令

eval命令的做用是,將字符串看成語句執行。

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

上面代碼將字符串看成語句運行,生成了變量a

放在eval中的字符串,應該有獨自存在的意義,不能用來與eval之外的命令配合使用。舉例來講,下面的代碼將會報錯。

eval('return;'); 

eval沒有本身的做用域,都在當前做用域內執行,所以可能會修改當前做用域的變量的值,形成安全問題。

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

上面代碼中,eval命令修改了外部變量a的值。因爲這個緣由,eval有安全風險。

爲了防止這種風險,JavaScript 規定,若是使用嚴格模式,eval內部聲明的變量,不會影響到外部做用域。

(function f() { 'use strict'; eval('var foo = 123'); console.log(foo); // ReferenceError: foo is not defined })() 

上面代碼中,函數f內部是嚴格模式,這時eval內部聲明的foo變量,就不會影響到外部。

不過,即便在嚴格模式下,eval依然能夠讀寫當前做用域的變量。

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

上面代碼中,嚴格模式下,eval內部仍是改寫了外部變量,可見安全風險依然存在。

此外,eval的命令字符串不會獲得 JavaScript 引擎的優化,運行速度較慢。這也是一個不該該使用它的理由。

一般狀況下,eval最多見的場合是解析 JSON 數據字符串,不過正確的作法應該是使用瀏覽器提供的JSON.parse方法。

JavaScript 引擎內部,eval其實是一個引用,默認調用一個內部方法。這使得eval的使用分紅兩種狀況,一種是像上面這樣的調用eval(expression),這叫作「直接使用」,這種狀況下eval的做用域就是當前做用域。除此以外的調用方法,都叫「間接調用」,此時eval的做用域老是全局做用域。

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

上面代碼中,eval是間接調用,因此即便它是在函數中,它的做用域仍是全局做用域,所以輸出的a爲全局變量。

eval的間接調用的形式五花八門,只要不是直接調用,都屬於間接調用。

eval.call(null, '...') window.eval('...') (1, eval)('...') (eval, eval)('...') 

上面這些形式都是eval的間接調用,所以它們的做用域都是全局做用域。

eval做用相似的還有Function構造函數。利用它生成一個函數,而後調用該函數,也能將字符串看成命令執行。

var jsonp = 'foo({"id": 42})'; var f = new Function( 'foo', jsonp ); // 至關於定義了以下函數 // function f(foo) { // foo({"id":42}); // } f(function (json) { console.log( json.id ); // 42 }) 

上面代碼中,jsonp是一個字符串,Function構造函數將這個字符串,變成了函數體。調用該函數的時候,jsonp就會執行。這種寫法的實質是將代碼放到函數做用域執行,避免對全局做用域形成影響。

不過,new Function()的寫法也能夠讀寫全局做用域,因此也是應該避免使用它。

推薦連接:阮一峯http://javascript.ruanyifeng.com/grammar/function.html

相關文章
相關標籤/搜索