<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] ## 概述 ### 函數的聲明 (1)function命令 函數就是使用function命令命名的代碼區塊,便于反復調用。 ~~~ function print(){ // ... } ~~~ 上面的代碼命名了一個print函數,以后使用print()這種形式,就可以調用相應的代碼。這叫做函數的聲明(Function Declaration)。 (2)函數表達式 除了用function命令聲明函數,還可以采用變量賦值的寫法。 ~~~ var print = function (){ // ... }; ~~~ 這種寫法將一個匿名函數賦值給變量。這時,這個匿名函數又稱函數表達式(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函數的參數。如果只有一個參數,該參數就是函數體。 ~~~ var foo = new Function('return "hello world"'); // 相當于定義了如下函數 // function foo() { // return "hello world"; // } ~~~ Function構造函數可以不使用new命令,返回結果完全一樣。 總的來說,這種聲明函數的方式非常不直觀,幾乎無人使用。 (4)函數的重復聲明 如果多次采用function命令,重復聲明同一個函數,則后面的聲明會覆蓋前面的聲明。 ~~~ 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 > 2) { return fib(num - 2) + fib(num - 1); } else { return 1; } } fib(6) // 8 ~~~ ### 第一等公民 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 ~~~ ### 不能在條件語句中聲明函數 根據ECMAScript的規范,不得在非函數的代碼塊中聲明函數,最常見的情況就是if和try語句。 ~~~ if (foo) { function x() { return; } } try { function x() {return; } } catch(e) { console.log(e); } ~~~ 上面代碼分別在if代碼塊和try代碼塊中聲明了兩個函數,按照語言規范,這是不合法的。但是,實際情況是各家瀏覽器往往并不報錯,能夠運行。 但是由于存在函數名的提升,所以在條件語句中聲明函數是無效的,這是非常容易出錯的地方。 ~~~ if (false){ function f(){} } f() // 不報錯 ~~~ 由于函數f的聲明被提升到了if語句的前面,導致if語句無效,所以上面的代碼不會報錯。要達到在條件語句中定義函數的目的,只有使用函數表達式。 ~~~ if (false){ var f = function (){}; } f() // undefined ~~~ ## 函數的屬性和方法 ### name屬性 name屬性返回緊跟在function關鍵字之后的那個函數名。 ~~~ function f1() {} f1.name // 'f1' var f2 = function () {}; f2.name // '' var f3 = function myName() {}; f3.name // 'myName' ~~~ 上面代碼中,函數的name屬性總是返回緊跟在function關鍵字之后的那個函數名。對于f2來說,返回空字符串,匿名函數的name屬性總是為空字符串;對于f3來說,返回函數表達式的名字(真正的函數名還是f3,myName這個名字只在函數體內部可用)。 ### 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(); // } ~~~ ## 函數作用域 ### 定義 作用域(scope)指的是變量存在的范圍。Javascript只有兩種作用域:一種是全局作用域,變量在整個程序中一直存在;另一種是函數作用域,變量只在函數內部存在。 在函數外部聲明的變量就是全局變量(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 ~~~ 函數內部定義的變量,會在該作用域內覆蓋同名全局變量。 ~~~ var v = 1; function f(){ var v = 2; console.log(v); } f() // 2 v // 1 ~~~ ### 函數內部的變量提升 與全局作用域一樣,函數作用域內部也會產生“變量提升”現象。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 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) // error f(undefined,1) // undefined ~~~ ### 默認值 通過下面的方法,可以為函數的參數設置默認值。 ~~~ function f(a){ a = a || 1; return a; } f('') // 1 f(0) // 1 ~~~ 上面代碼的||表示“或運算”,即如果a有值,則返回a,否則返回事先設定的默認值(上例為1)。 這種寫法會對a進行一次布爾運算,只有為true時,才會返回a。可是,除了undefined以外,0、空字符、null等的布爾值也是false。也就是說,在上面的函數中,不能讓a等于0或空字符串,否則在明明有參數的情況下,也會返回默認值。 為了避免這個問題,可以采用下面更精確的寫法。 ~~~ function f(a){ (a !== undefined && a != null)?(a = a):(a = 1); return a; } f('') // "" f(0) // 0 ~~~ ### 傳遞方式 JavaScript的函數參數傳遞方式是傳值傳遞(passes by value),這意味著,在函數體內修改參數值,不會影響到函數外部。 ~~~ // 修改原始類型的參數值 var p = 2; function f(p){ p = 3; } f(p); p // 2 // 修改復合類型的參數值 var o = [1,2,3]; function f(o){ o = [2,3,4]; } f(o); o // [1, 2, 3] ~~~ 上面代碼分成兩段,分別修改原始類型的參數值和復合類型的參數值。兩種情況下,函數內部修改參數值,都不會影響到函數外部。 需要十分注意的是,雖然參數本身是傳值傳遞,但是對于復合類型的變量來說,屬性值是傳址傳遞(pass by reference),也就是說,屬性值是通過地址讀取的。所以在函數體內修改復合類型變量的屬性值,會影響到函數外部。 ~~~ // 修改對象的屬性值 var o = { p:1 }; function f(obj){ obj.p = 2; } f(o); o.p // 2 // 修改數組的屬性值 var a = [1,2,3]; function f(a){ a[0]=4; } f(a); a // [4,2,3] ~~~ 上面代碼在函數體內,分別修改對象和數組的屬性值,結果都影響到了函數外部,這證明復合類型變量的屬性值是傳址傳遞。 某些情況下,如果需要對某個變量達到傳址傳遞的效果,可以將它寫成全局對象的屬性。 ~~~ var a = 1; function f(p){ window[p]=2; } f('a'); a // 2 ~~~ 上面代碼中,變量a本來是傳值傳遞,但是寫成window對象的屬性,就達到了傳址傳遞的效果。 ### 同名參數 如果有同名的參數,則取最后出現的那個值。 ~~~ 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 ~~~ 可以通過arguments對象的length屬性,判斷函數調用時到底帶幾個參數。 ~~~ function f(){ return arguments.length; } f(1,2,3) // 3 f(1) // 1 f() // 0 ~~~ (2)與數組的關系 需要注意的是,雖然arguments很像數組,但它是一個對象。某些用于數組的方法(比如slice和forEach方法),不能在arguments對象上使用。 但是,有時arguments可以像數組一樣,用在某些只用于數組的方法。比如,用在apply方法中,或使用concat方法完成數組合并。 ~~~ // 用于apply方法 myfunction.apply(obj, arguments). // 使用與另一個數組合并 Array.prototype.concat.apply([1,2,3], arguments) ~~~ 要讓arguments對象使用數組方法,真正的解決方法是將arguments轉為真正的數組。下面是兩種常用的轉換方法:slice方法和逐一填入新數組。 ~~~ 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對象帶有一個callee屬性,返回它所對應的原函數。 ~~~ var f = function(one) { console.log(arguments.callee === f); } f() // true ~~~ ## 函數的其他知識點 ### 閉包 閉包(closure)就是定義在函數體內部的函數。更理論性的表達是,閉包是函數與其生成時所在的作用域對象(scope object)的一種結合。 ~~~ function f() { var c = function (){}; } ~~~ 上面的代碼中,c是定義在函數f內部的函數,就是閉包。 閉包的特點在于,在函數外部可以讀取函數的內部變量。 ~~~ function f() { var v = 1; var c = function (){ return v; }; return c; } var o = f(); o(); // 1 ~~~ 上面代碼表示,原先在函數f外部,我們是沒有辦法讀取內部變量v的。但是,借助閉包c,可以讀到這個變量。 閉包不僅可以讀取函數內部變量,還可以使得內部變量記住上一次調用時的運算結果。 ~~~ function createIncrementor(start) { return function () { return start++; } } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7 ~~~ 上面代碼表示,函數內部的start變量,每一次調用時都是在上一次調用時的值的基礎上進行計算的。 ### 立即調用的函數表達式(IIFE) 在Javascript中,一對圓括號“()”是一種運算符,跟在函數名之后,表示調用該函數。比如,print()就表示調用print函數。 有時,我們需要在定義函數之后,立即調用該函數。這時,你不能在函數的定義之后加上圓括號,這會產生語法錯誤。 ~~~ function(){ /* code */ }(); // SyntaxError: Unexpected token ( ~~~ 產生這個錯誤的原因是,Javascript引擎看到function關鍵字之后,認為后面跟的是函數定義語句,不應該以圓括號結尾。 解決方法就是讓引擎知道,圓括號前面的部分不是函數定義語句,而是一個表達式,可以對此進行運算。你可以這樣寫: ~~~ (function(){ /* code */ }()); // 或者 (function(){ /* code */ })(); ~~~ 這兩種寫法都是以圓括號開頭,引擎就會認為后面跟的是一個表示式,而不是函數定義,所以就避免了錯誤。這就叫做“立即調用的函數表達式”(Immediately-Invoked Function Expression),簡稱IIFE。 > 注意,上面的兩種寫法的結尾,都必須加上分號。 推而廣之,任何讓解釋器以表達式來處理函數定義的方法,都能產生同樣的效果,比如下面三種寫法。 ~~~ var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); ~~~ 甚至像這樣寫 ~~~ !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); ~~~ new關鍵字也能達到這個效果。 ~~~ new function(){ /* code */ } new 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有安全風險,無法做到作用域隔離,最好不要使用。此外,eval的命令字符串不會得到JavaScript引擎的優化,運行速度較慢,也是另一個不應該使用它的理由。通常情況下,eval最常見的場合是解析JSON數據字符串,正確的做法是這時應該使用瀏覽器提供的JSON.parse方法。 ECMAScript 5將eval的使用分成兩種情況,像上面這樣的調用,就叫做“直接使用”,這種情況下eval的作用域就是當前作用域(即全局作用域或函數作用域)。另一種情況是,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)('...') (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的間接調用,因此它們的作用域都是全局作用域。 與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就會執行。這種寫法的實質是將代碼放到函數作用域執行,避免對全局作用域造成影響。 ## 參考鏈接 * Ben Alman,?[Immediately-Invoked Function Expression (IIFE)](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) * Mark Daggett,?[Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/) * Juriy Zaytsev,?[Named function expressions demystified](http://kangax.github.com/nfe/) * Marco Rogers polotek,?[What is the arguments object?](http://docs.nodejitsu.com/articles/javascript-conventions/what-is-the-arguments-object) * Juriy Zaytsev,?[Global eval. What are the options?](http://perfectionkills.com/global-eval-what-are-the-options/) * Axel Rauschmayer,?[Evaluating JavaScript code via eval() and new Function()](http://www.2ality.com/2014/01/eval.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看