?
Dokumen ini menggunakan Manual laman web PHP Cina Lepaskan
概述
函數(shù)的聲明
圓括號(hào)運(yùn)算符和return語(yǔ)句
第一等公民
函數(shù)名的提升
不能在條件語(yǔ)句中聲明函數(shù)
函數(shù)的屬性和方法
name屬性
length屬性
toString()
函數(shù)作用域
定義
函數(shù)內(nèi)部的變量提升
函數(shù)本身的作用域
參數(shù)
概述
參數(shù)的省略
默認(rèn)值
傳遞方式
同名參數(shù)
arguments對(duì)象
函數(shù)的其他知識(shí)點(diǎn)
閉包
立即調(diào)用的函數(shù)表達(dá)式(IIFE)
eval命令
參考鏈接
(1)function命令
函數(shù)就是使用function命令命名的代碼區(qū)塊,便于反復(fù)調(diào)用。
function print(){ // ... }
上面的代碼命名了一個(gè)print函數(shù),以后使用print()這種形式,就可以調(diào)用相應(yīng)的代碼。這叫做函數(shù)的聲明(Function Declaration)。
(2)函數(shù)表達(dá)式
除了用function命令聲明函數(shù),還可以采用變量賦值的寫(xiě)法。
var print = function (){ // ... };
這種寫(xiě)法將一個(gè)匿名函數(shù)賦值給變量。這時(shí),這個(gè)匿名函數(shù)又稱(chēng)函數(shù)表達(dá)式(Function Expression),因?yàn)橘x值語(yǔ)句的等號(hào)右側(cè)只能放表達(dá)式。
采用函數(shù)表達(dá)式聲明函數(shù)時(shí),function命令后面不帶有函數(shù)名。如果加上函數(shù)名,該函數(shù)名只在函數(shù)體內(nèi)部有效,在函數(shù)體外部無(wú)效。
var print = function x(){ console.log(typeof x); }; x // ReferenceError: x is not defined print() // function
上面代碼在函數(shù)表達(dá)式中,加入了函數(shù)名x。這個(gè)x只在函數(shù)體內(nèi)部可用,指代函數(shù)表達(dá)式本身,其他地方都不可用。這種寫(xiě)法的用處有兩個(gè),一是可以在函數(shù)體內(nèi)部調(diào)用自身,二是方便除錯(cuò)(除錯(cuò)工具顯示函數(shù)調(diào)用棧時(shí),將顯示函數(shù)名,而不再顯示這里是一個(gè)匿名函數(shù))。因此,需要時(shí),可以采用下面的形式聲明函數(shù)。
var f = function f(){};
需要注意的是,函數(shù)的表達(dá)式需要在語(yǔ)句的結(jié)尾加上分號(hào),表示語(yǔ)句結(jié)束。而函數(shù)的聲明在結(jié)尾的大括號(hào)后面不用加分號(hào)??偟膩?lái)說(shuō),這兩種聲明函數(shù)的方式,差別很細(xì)微(參閱后文《變量提升》一節(jié)),這里可以近似認(rèn)為是等價(jià)的。
(3)Function構(gòu)造函數(shù)
還有第三種聲明函數(shù)的方式:通過(guò)Function構(gòu)造函數(shù)聲明。
var add = new Function("x","y","return (x+y)"); // 相當(dāng)于定義了如下函數(shù) // function add(x, y) { // return (x+y); // }
在上面代碼中,F(xiàn)unction對(duì)象接受若干個(gè)參數(shù),除了最后一個(gè)參數(shù)是add函數(shù)的“函數(shù)體”,其他參數(shù)都是add函數(shù)的參數(shù)。如果只有一個(gè)參數(shù),該參數(shù)就是函數(shù)體。
var foo = new Function('return "hello world"'); // 相當(dāng)于定義了如下函數(shù) // function foo() { // return "hello world"; // }
Function構(gòu)造函數(shù)可以不使用new命令,返回結(jié)果完全一樣。
總的來(lái)說(shuō),這種聲明函數(shù)的方式非常不直觀(guān),幾乎無(wú)人使用。
(4)函數(shù)的重復(fù)聲明
如果多次采用function命令,重復(fù)聲明同一個(gè)函數(shù),則后面的聲明會(huì)覆蓋前面的聲明。
function f(){ console.log(1); } f() // 2 function f(){ console.log(2); } f() // 2
上面代碼說(shuō)明,由于存在函數(shù)名的提升,前面的聲明在任何時(shí)候都是無(wú)效的,這一點(diǎn)要特別注意。
調(diào)用函數(shù)時(shí),要使用圓括號(hào)運(yùn)算符。圓括號(hào)之中,可以加入函數(shù)的參數(shù)。
function add(x,y) { return x+y; } add(1,1) // 2
函數(shù)體內(nèi)部的return語(yǔ)句,表示返回。JavaScript引擎遇到return語(yǔ)句,就直接返回return后面的那個(gè)表達(dá)式的值,后面即使還有語(yǔ)句,也不會(huì)得到執(zhí)行。也就是說(shuō),return語(yǔ)句所帶的那個(gè)表達(dá)式,就是函數(shù)的返回值。return語(yǔ)句不是必需的,如果沒(méi)有的話(huà),該函數(shù)就不返回任何值,或者說(shuō)返回undefined。
函數(shù)可以調(diào)用自身,這就是遞歸(recursion)。下面就是使用遞歸,計(jì)算斐波那契數(shù)列的代碼。
function fib(num) { if (num > 2) { return fib(num - 2) + fib(num - 1); } else { return 1; } } fib(6) // 8
JavaScript的函數(shù)與其他數(shù)據(jù)類(lèi)型處于同等地位,可以使用其他數(shù)據(jù)類(lèi)型的地方就能使用函數(shù)。比如,可以把函數(shù)賦值給變量和對(duì)象的屬性,也可以當(dāng)作參數(shù)傳入其他函數(shù),或者作為函數(shù)的結(jié)果返回。這表示函數(shù)與其他數(shù)據(jù)類(lèi)型的地方是平等,所以又稱(chēng)函數(shù)為第一等公民。
function add(x,y){ return x+y; } // 將函數(shù)賦值給一個(gè)變量 var operator = add; // 將函數(shù)作為參數(shù)和返回值 function a(op){ return op; } a(add)(1,1) // 2
JavaScript引擎將函數(shù)名視同變量名,所以采用function命令聲明函數(shù)時(shí),整個(gè)函數(shù)會(huì)被提升到代碼頭部。所以,下面的代碼不會(huì)報(bào)錯(cuò)。
f(); function f(){}
表面上,上面代碼好像在聲明之前就調(diào)用了函數(shù)f。但是實(shí)際上,由于“變量提升”,函數(shù)f被提升到了代碼頭部,也就是在調(diào)用之前已經(jīng)聲明了。但是,如果采用賦值語(yǔ)句定義函數(shù),JavaScript就會(huì)報(bào)錯(cuò)。
f(); var f = function (){}; // TypeError: undefined is not a function
上面的代碼等同于
var f;f();f = function (){};
當(dāng)調(diào)用f的時(shí)候,f只是被聲明,還沒(méi)有被賦值,等于undefined,所以會(huì)報(bào)錯(cuò)。因此,如果同時(shí)采用function命令和賦值語(yǔ)句聲明同一個(gè)函數(shù),最后總是采用賦值語(yǔ)句的定義。
var f = function() { console.log ('1'); } function f() { console.log('2'); } f()// 1
根據(jù)ECMAScript的規(guī)范,不得在非函數(shù)的代碼塊中聲明函數(shù),最常見(jiàn)的情況就是if和try語(yǔ)句。
if (foo) { function x() { return; } } try { function x() {return; } } catch(e) { console.log(e); }
上面代碼分別在if代碼塊和try代碼塊中聲明了兩個(gè)函數(shù),按照語(yǔ)言規(guī)范,這是不合法的。但是,實(shí)際情況是各家瀏覽器往往并不報(bào)錯(cuò),能夠運(yùn)行。
但是由于存在函數(shù)名的提升,所以在條件語(yǔ)句中聲明函數(shù)是無(wú)效的,這是非常容易出錯(cuò)的地方。
if (false){ function f(){} } f()// 不報(bào)錯(cuò)
由于函數(shù)f的聲明被提升到了if語(yǔ)句的前面,導(dǎo)致if語(yǔ)句無(wú)效,所以上面的代碼不會(huì)報(bào)錯(cuò)。要達(dá)到在條件語(yǔ)句中定義函數(shù)的目的,只有使用函數(shù)表達(dá)式。
if (false){ var f = function (){}; } f() // undefined
name屬性返回緊跟在function關(guān)鍵字之后的那個(gè)函數(shù)名。
function f1() {} f1.name // 'f1' var f2 = function () {}; f2.name // '' var f3 = function myName() {}; f3.name // 'myName'
上面代碼中,函數(shù)的name屬性總是返回緊跟在function關(guān)鍵字之后的那個(gè)函數(shù)名。對(duì)于f2來(lái)說(shuō),返回空字符串,匿名函數(shù)的name屬性總是為空字符串;對(duì)于f3來(lái)說(shuō),返回函數(shù)表達(dá)式的名字(真正的函數(shù)名還是f3,myName這個(gè)名字只在函數(shù)體內(nèi)部可用)。
length屬性返回函數(shù)定義中參數(shù)的個(gè)數(shù)。
function f(a,b) {} f.length // 2
上面代碼定義了空函數(shù)f,它的length屬性就是定義時(shí)參數(shù)的個(gè)數(shù)。不管調(diào)用時(shí)輸入了多少個(gè)參數(shù),length屬性始終等于2。
length屬性提供了一種機(jī)制,判斷定義時(shí)和調(diào)用時(shí)參數(shù)的差異,以便實(shí)現(xiàn)面向?qū)ο缶幊痰摹狈椒ㄖ剌d“(overload)。
函數(shù)的toString方法返回函數(shù)的源碼。
function f() { a(); b(); c(); } f.toString() // function f() { // a(); // b(); // c(); // }
作用域(scope)指的是變量存在的范圍。Javascript只有兩種作用域:一種是全局作用域,變量在整個(gè)程序中一直存在;另一種是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。
在函數(shù)外部聲明的變量就是全局變量(global variable),它可以在函數(shù)內(nèi)部讀取。
var v = 1; function f(){ console.log(v); } f() // 1
上面的代碼表明,函數(shù)f內(nèi)部可以讀取全局變量v。
在函數(shù)內(nèi)部定義的變量,外部無(wú)法讀取,稱(chēng)為“局部變量”(local variable)。
function f(){ var v = 1; } v // ReferenceError: v is not defined
函數(shù)內(nèi)部定義的變量,會(huì)在該作用域內(nèi)覆蓋同名全局變量。
var v = 1; function f(){ var v = 2; console.log(v); } f() // 2 v // 1
與全局作用域一樣,函數(shù)作用域內(nèi)部也會(huì)產(chǎn)生“變量提升”現(xiàn)象。var命令聲明的變量,不管在什么位置,變量聲明都會(huì)被提升到函數(shù)體的頭部。
function foo(x) { if (x > 100) { var tmp = x - 100; } }
上面的代碼等同于
function foo(x) { var tmp; if (x > 100) { tmp = x - 100; }; }
函數(shù)本身也是一個(gè)值,也有自己的作用域。它的作用域綁定其聲明時(shí)所在的作用域。
var a = 1; var x = function (){ console.log(a); }; function f(){ var a = 2; x(); } f() // 1
上面代碼中,函數(shù)x是在函數(shù)f的外部聲明的,所以它的作用域綁定外層,內(nèi)部變量a不會(huì)到函數(shù)f體內(nèi)取值,所以輸出1,而不是2。
很容易犯錯(cuò)的一點(diǎn)是,如果函數(shù)A調(diào)用函數(shù)B,卻沒(méi)考慮到函數(shù)B不會(huì)引用函數(shù)A的內(nèi)部變量。
var x = function (){ console.log(a); }; function y(f){ var a = 2; f(); } y(x) // ReferenceError: a is not defined
上面代碼將函數(shù)x作為參數(shù),傳入函數(shù)y。但是,函數(shù)x是在函數(shù)y體外聲明的,作用域綁定外層,因此找不到函數(shù)y的內(nèi)部變量a,導(dǎo)致報(bào)錯(cuò)。
函數(shù)運(yùn)行的時(shí)候,有時(shí)需要提供外部數(shù)據(jù),不同的外部數(shù)據(jù)會(huì)得到不同的結(jié)果,這種外部數(shù)據(jù)就叫參數(shù)。
function square(x){ return x*x; } square(2) // 4 square(3) // 9
上式的x就是square函數(shù)的參數(shù)。每次運(yùn)行的時(shí)候,需要提供這個(gè)值,否則得不到結(jié)果。
參數(shù)不是必需的,Javascript語(yǔ)言允許省略參數(shù)。
function f(a,b){ return a; } f(1,2,3) // 1 f(1) // 1 f() // undefined f.length // 2
上面代碼的函數(shù)f定義了兩個(gè)參數(shù),但是運(yùn)行時(shí)無(wú)論提供多少個(gè)參數(shù)(或者不提供參數(shù)),JavaScript都不會(huì)報(bào)錯(cuò)。被省略的參數(shù)的值就變?yōu)閡ndefined。需要注意的是,函數(shù)的length屬性與實(shí)際傳入的參數(shù)個(gè)數(shù)無(wú)關(guān),只反映定義時(shí)的參數(shù)個(gè)數(shù)。
但是,沒(méi)有辦法只省略靠前的參數(shù),而保留靠后的參數(shù)。如果一定要省略靠前的參數(shù),只有顯式傳入undefined。
function f(a,b){ return a; } f(,1) // error f(undefined,1) // undefined
通過(guò)下面的方法,可以為函數(shù)的參數(shù)設(shè)置默認(rèn)值。
function f(a){ a = a || 1; return a; } f('') // 1 f(0) // 1
上面代碼的||表示“或運(yùn)算”,即如果a有值,則返回a,否則返回事先設(shè)定的默認(rèn)值(上例為1)。
這種寫(xiě)法會(huì)對(duì)a進(jìn)行一次布爾運(yùn)算,只有為true時(shí),才會(huì)返回a。可是,除了undefined以外,0、空字符、null等的布爾值也是false。也就是說(shuō),在上面的函數(shù)中,不能讓a等于0或空字符串,否則在明明有參數(shù)的情況下,也會(huì)返回默認(rèn)值。
為了避免這個(gè)問(wèn)題,可以采用下面更精確的寫(xiě)法。
function f(a){ (a !== undefined && a != null)?(a = a):(a = 1); return a; } f('') // "" f(0) // 0
JavaScript的函數(shù)參數(shù)傳遞方式是傳值傳遞(passes by value),這意味著,在函數(shù)體內(nèi)修改參數(shù)值,不會(huì)影響到函數(shù)外部。
// 修改原始類(lèi)型的參數(shù)值 var p = 2; function f(p){ p = 3; } f(p); p // 2 // 修改復(fù)合類(lèi)型的參數(shù)值 var o = [1,2,3]; function f(o){ o = [2,3,4]; } f(o); o // [1, 2, 3]
上面代碼分成兩段,分別修改原始類(lèi)型的參數(shù)值和復(fù)合類(lèi)型的參數(shù)值。兩種情況下,函數(shù)內(nèi)部修改參數(shù)值,都不會(huì)影響到函數(shù)外部。
需要十分注意的是,雖然參數(shù)本身是傳值傳遞,但是對(duì)于復(fù)合類(lèi)型的變量來(lái)說(shuō),屬性值是傳址傳遞(pass by reference),也就是說(shuō),屬性值是通過(guò)地址讀取的。所以在函數(shù)體內(nèi)修改復(fù)合類(lèi)型變量的屬性值,會(huì)影響到函數(shù)外部。
// 修改對(duì)象的屬性值 var o = { p:1 }; function f(obj){ obj.p = 2; } f(o); o.p // 2 // 修改數(shù)組的屬性值 var a = [1,2,3]; function f(a){ a[0]=4; } f(a); a // [4,2,3]
上面代碼在函數(shù)體內(nèi),分別修改對(duì)象和數(shù)組的屬性值,結(jié)果都影響到了函數(shù)外部,這證明復(fù)合類(lèi)型變量的屬性值是傳址傳遞。
某些情況下,如果需要對(duì)某個(gè)變量達(dá)到傳址傳遞的效果,可以將它寫(xiě)成全局對(duì)象的屬性。
var a = 1; function f(p){ window[p]=2; } f('a'); a // 2
上面代碼中,變量a本來(lái)是傳值傳遞,但是寫(xiě)成window對(duì)象的屬性,就達(dá)到了傳址傳遞的效果。
如果有同名的參數(shù),則取最后出現(xiàn)的那個(gè)值。
function f(a, a){ console.log(a); } f(1,2)// 2
上面的函數(shù)f有兩個(gè)參數(shù),且參數(shù)名都是a。取值的時(shí)候,以后面的a為準(zhǔn)。即使后面的a沒(méi)有值或被省略,也是以其為準(zhǔn)。
function f(a, a){ console.log(a); } f(1)// undefined
調(diào)用函數(shù)f的時(shí)候,沒(méi)有提供第二個(gè)參數(shù),a的取值就變成了undefined。這時(shí),如果要獲得第一個(gè)a的值,可以使用arguments對(duì)象。
function f(a, a){ console.log(arguments[0]); } f(1)// 1
(1)定義
由于JavaScript允許函數(shù)有不定數(shù)目的參數(shù),所以我們需要一種機(jī)制,可以在函數(shù)體內(nèi)部讀取所有參數(shù)。這就是arguments對(duì)象的由來(lái)。
arguments對(duì)象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù),arguments[0]就是第一個(gè)參數(shù),arguments[1]就是第二個(gè)參數(shù),依次類(lèi)推。這個(gè)對(duì)象只有在函數(shù)體內(nèi)部,才可以使用。
var f = function(one) { console.log(arguments[0]); console.log(arguments[1]); console.log(arguments[2]); } f(1, 2, 3) // 1 // 2 // 3
arguments對(duì)象除了可以讀取參數(shù),還可以為參數(shù)賦值(嚴(yán)格模式不允許這種用法)。
var f = function(a,b) { arguments[0] = 3; arguments[1] = 2; return a+b; } f(1, 1) // 5
可以通過(guò)arguments對(duì)象的length屬性,判斷函數(shù)調(diào)用時(shí)到底帶幾個(gè)參數(shù)。
function f(){ return arguments.length; } f(1,2,3) // 3 f(1) // 1 f() // 0
(2)與數(shù)組的關(guān)系
需要注意的是,雖然arguments很像數(shù)組,但它是一個(gè)對(duì)象。某些用于數(shù)組的方法(比如slice和forEach方法),不能在arguments對(duì)象上使用。
但是,有時(shí)arguments可以像數(shù)組一樣,用在某些只用于數(shù)組的方法。比如,用在apply方法中,或使用concat方法完成數(shù)組合并。
// 用于apply方法 myfunction.apply(obj, arguments). // 使用與另一個(gè)數(shù)組合并 Array.prototype.concat.apply([1,2,3], arguments)
要讓arguments對(duì)象使用數(shù)組方法,真正的解決方法是將arguments轉(zhuǎn)為真正的數(shù)組。下面是兩種常用的轉(zhuǎn)換方法:slice方法和逐一填入新數(shù)組。
var args = Array.prototype.slice.call(arguments); // or var args = []; for(var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
(3)callee屬性
arguments對(duì)象帶有一個(gè)callee屬性,返回它所對(duì)應(yīng)的原函數(shù)。
var f = function(one) { console.log(arguments.callee === f); } f() // true
閉包(closure)就是定義在函數(shù)體內(nèi)部的函數(shù)。更理論性的表達(dá)是,閉包是函數(shù)與其生成時(shí)所在的作用域?qū)ο螅╯cope object)的一種結(jié)合。
function f() { var c = function (){}; }
上面的代碼中,c是定義在函數(shù)f內(nèi)部的函數(shù),就是閉包。
閉包的特點(diǎn)在于,在函數(shù)外部可以讀取函數(shù)的內(nèi)部變量。
function f() { var v = 1; var c = function (){ return v; }; return c; } var o = f(); o(); // 1
上面代碼表示,原先在函數(shù)f外部,我們是沒(méi)有辦法讀取內(nèi)部變量v的。但是,借助閉包c(diǎn),可以讀到這個(gè)變量。
閉包不僅可以讀取函數(shù)內(nèi)部變量,還可以使得內(nèi)部變量記住上一次調(diào)用時(shí)的運(yùn)算結(jié)果。
function createIncrementor(start) { return function () { return start++; } } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7
上面代碼表示,函數(shù)內(nèi)部的start變量,每一次調(diào)用時(shí)都是在上一次調(diào)用時(shí)的值的基礎(chǔ)上進(jìn)行計(jì)算的。
在Javascript中,一對(duì)圓括號(hào)“()”是一種運(yùn)算符,跟在函數(shù)名之后,表示調(diào)用該函數(shù)。比如,print()就表示調(diào)用print函數(shù)。
有時(shí),我們需要在定義函數(shù)之后,立即調(diào)用該函數(shù)。這時(shí),你不能在函數(shù)的定義之后加上圓括號(hào),這會(huì)產(chǎn)生語(yǔ)法錯(cuò)誤。
function(){ /* code */ }(); // SyntaxError: Unexpected token (
產(chǎn)生這個(gè)錯(cuò)誤的原因是,Javascript引擎看到function關(guān)鍵字之后,認(rèn)為后面跟的是函數(shù)定義語(yǔ)句,不應(yīng)該以圓括號(hào)結(jié)尾。
解決方法就是讓引擎知道,圓括號(hào)前面的部分不是函數(shù)定義語(yǔ)句,而是一個(gè)表達(dá)式,可以對(duì)此進(jìn)行運(yùn)算。你可以這樣寫(xiě):
(function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
這兩種寫(xiě)法都是以圓括號(hào)開(kāi)頭,引擎就會(huì)認(rèn)為后面跟的是一個(gè)表示式,而不是函數(shù)定義,所以就避免了錯(cuò)誤。這就叫做“立即調(diào)用的函數(shù)表達(dá)式”(Immediately-Invoked Function Expression),簡(jiǎn)稱(chēng)IIFE。
注意,上面的兩種寫(xiě)法的結(jié)尾,都必須加上分號(hào)。
推而廣之,任何讓解釋器以表達(dá)式來(lái)處理函數(shù)定義的方法,都能產(chǎn)生同樣的效果,比如下面三種寫(xiě)法。
var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }();
甚至像這樣寫(xiě)
!function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }();
new關(guān)鍵字也能達(dá)到這個(gè)效果。
new function(){ /* code */ } new function(){ /* code */ }() // 只有傳遞參數(shù)時(shí),才需要最后那個(gè)圓括號(hào)。
通常情況下,只對(duì)匿名函數(shù)使用這種“立即執(zhí)行的函數(shù)表達(dá)式”。它的目的有兩個(gè):一是不必為函數(shù)命名,避免了污染全局變量;二是IIFE內(nèi)部形成了一個(gè)單獨(dú)的作用域,可以封裝一些外部無(wú)法讀取的私有變量。
// 寫(xiě)法一 var tmp = newData; processData(tmp); storeData(tmp); // 寫(xiě)法二 (function (){ var tmp = newData; processData(tmp); storeData(tmp); }());
上面代碼中,寫(xiě)法二比寫(xiě)法一更好,因?yàn)橥耆苊饬宋廴救肿兞俊?/p>
eval命令的作用是,將字符串當(dāng)作語(yǔ)句執(zhí)行。
eval('var a = 1;'); a // 1
上面代碼將字符串當(dāng)作語(yǔ)句運(yùn)行,生成了變量a。
放在eval中的字符串,應(yīng)該有獨(dú)自存在的意義,不能用來(lái)與eval以外的命令配合使用。舉例來(lái)說(shuō),下面的代碼將會(huì)報(bào)錯(cuò)。
eval('return;');
由于eval沒(méi)有自己的作用域,都在當(dāng)前作用域內(nèi)執(zhí)行,因此可能會(huì)修改其他外部變量的值,造成安全問(wèn)題。
var a = 1;eval('a = 2'); a // 2
上面代碼中,eval命令修改了外部變量a的值。由于這個(gè)原因,所以eval有安全風(fēng)險(xiǎn),無(wú)法做到作用域隔離,最好不要使用。此外,eval的命令字符串不會(huì)得到JavaScript引擎的優(yōu)化,運(yùn)行速度較慢,也是另一個(gè)不應(yīng)該使用它的理由。通常情況下,eval最常見(jiàn)的場(chǎng)合是解析JSON數(shù)據(jù)字符串,正確的做法是這時(shí)應(yīng)該使用瀏覽器提供的JSON.parse方法。
ECMAScript 5將eval的使用分成兩種情況,像上面這樣的調(diào)用,就叫做“直接使用”,這種情況下eval的作用域就是當(dāng)前作用域(即全局作用域或函數(shù)作用域)。另一種情況是,eval不是直接調(diào)用,而是“間接調(diào)用”,此時(shí)eval的作用域總是全局作用域。
var a = 1;function f(){ var a = 2; var e = eval; e('console.log(a)'); } f() // 1
上面代碼中,eval是間接調(diào)用,所以即使它是在函數(shù)中,它的作用域還是全局作用域,因此輸出的a為全局變量。
eval的間接調(diào)用的形式五花八門(mén),只要不是直接調(diào)用,幾乎都屬于間接調(diào)用。
eval.call(null, '...') window.eval('...') (1, eval)('...') (eval, eval)('...') (1 ? eval : 0)('...') (__ = eval)('...') var e = eval; e('...') (function(e) { e('...') })(eval) (function(e) { return e })(eval)('...') (function() { arguments[0]('...') })(eval) this.eval('...') this['eval']('...') [eval][0]('...') eval.call(this, '...') eval('eval')('...')
上面這些形式都是eval的間接調(diào)用,因此它們的作用域都是全局作用域。
與eval作用類(lèi)似的還有Function構(gòu)造函數(shù)。利用它生成一個(gè)函數(shù),然后調(diào)用該函數(shù),也能將字符串當(dāng)作命令執(zhí)行。
var jsonp = 'foo({"id":42})'; var f = new Function( "foo", jsonp ); // 相當(dāng)于定義了如下函數(shù) // function f(foo) { // foo({"id":42}); // } f(function(json){ console.log( json.id ); // 42 })
上面代碼中,jsonp是一個(gè)字符串,F(xiàn)unction構(gòu)造函數(shù)將這個(gè)字符串,變成了函數(shù)體。調(diào)用該函數(shù)的時(shí)候,jsonp就會(huì)執(zhí)行。這種寫(xiě)法的實(shí)質(zhì)是將代碼放到函數(shù)作用域執(zhí)行,避免對(duì)全局作用域造成影響。
Ben Alman,
Mark Daggett,
Juriy Zaytsev,
Marco Rogers polotek,
Juriy Zaytsev,
Axel Rauschmayer,