js函數探索

該文章以收錄: 《JavaScript深刻探索之路》


前言

函數是這樣的一段JavaScript代碼,它只定義一次,可是可能被執行或調用任意次。你可能已經從諸如子例程或者過程這些名字裏對函數的概念有所瞭解。<!-- more --> JavaScript函數時參數化的:函數的定義會包括一個稱爲形參的表示符列表,這些參數在函數體中像局部變量同樣工做。函數調用會爲形參提供實參的值,函數使用它們實參的值來計算返回值,成爲該函數調用表達式的值。除了實參外,每次調用還會擁有另外一個值 —— 本次調用的上下文 —— 這就是this關鍵字的值。web

參數有形參(parameter)和實參(argument)的區別,形參至關於函數中定義的變量,實參是運行時的函數調用時傳入的參數。json

建立函數

通常咱們有三種方法去建立函數:數組

  • 函數聲明:function foo(){}瀏覽器

  • 函數表達式:var foo = function(){}閉包

  • 函數構造法:var foo = new Function('a','b',"return a+b")app

函數聲明和函數表達式

1.什麼是函數聲明,什麼是函數表達式函數

ECMA規範說:函數聲明必須帶有標示符(Identifier)(就是你們常說的函數名稱),而函數表達式則能夠省略這個標示符:this

// 函數聲明
function 函數名(可選參數){}

// 函數表達式
function 可選函數名(可選參數){}

咱們能夠很清楚的看出,若是沒有標識符(函數名),該函數就必定是函數表達式,若是有標識符咱們該怎麼區分函數聲明和函數表達式呢。prototype

函數聲明咱們不用太多的考慮,很容易看出來,咱們來看看函數表達式都有哪些天然就知道哪些是函數聲明瞭。code

第一種函數表達式:var聲明的函數咱們很常見

// 沒有標識符必定是表達式
var fun = function(){}

//咱們還能夠這樣寫 

var fun = function foo(){}

第二種咱們帶有標識符,但將函數賦值給變量後,該函數就成了函數表達式。在這裏foo標識符在該函數體外訪問時並不起做用

var fun = function foo(a){
  
    if(a){
      console.log("外部調用了");  
      foo(false); //這會執行函數fun。
    }else{
      console.log("內部調用了");
    }

}

fun(true); //外部調用了 ,內部調用了

foo(); // 報錯 ReferenceError: foo is not defined

咱們能夠看出,函數表達式中foo標識符只能在函數內部使用,去訪問該函數,在函數外部訪問不到任何東西。上面咱們加判斷是爲了防止函數一直遞歸。

第二種函數表達式:()括號包住的函數

// 這是函數表達式
(function foo(){})

括號包住的函數是函數表達式是由於,()是一個分組操做符,分組操做符內只能是表達式。

例如:

//Unexpected token var
(var a = 0)

上面報錯,這是由於var是一個語句,而分組操做符內只能是表達式。還有咱們在使用evel()時這樣寫 var ev = eval("(" + json + ")") ,這裏是強制將{}解析爲表達式而不是代碼塊。

2.函數聲明和函數表達式的特色

函數聲明在咱們的代碼執行前,會被整個提高,也就是說咱們在函數聲明以前就能夠調用該函數,而函數表達式不可以被提高。

a() // 結果 我是函數a"
function a(){
    console.log("我是函數a");
}

fun() //結果 fun is not a function
var fun = function b(){
    console.log("我是函數b");
}

爲何會出現這種狀況咱們在之後的文章中會詳細的討論。同時,咱們應該養成一種習慣,儘可能不要在函數聲明以前使用函數。這只是一種良好的代碼書寫習慣。

其次咱們須要注意的是在if判斷語句中咱們不該該出現函數聲明,即使是沒有報錯,也是不推薦這樣去寫的。

當即執行函數(表達式)和自執行函數

1. 當即執行函數(表達式)

咱們來看一下:

(function (){
    console.log("one");
})();


(function (){
    console.log("two");

}());

上邊兩個函數均可以本身去執行,而值有個共同的特色就是括號左邊的函數必須是一個函數表達式,而不是函數聲明。

// 若是括號左邊是函數聲明,報錯
function fun(){
    console.log()
}()



// 括號左邊是函數時表達式,(函數表達式均可以自執行)

var F = function(){
    return 10;
}();

true && function (){}();

0 , function (){}();

//你甚至看還能夠這樣

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

new function(){}();

其實上邊的些自執行函數表達式咱們爲了方便咱們的閱讀咱們通常會用到第一個和第二個,固然若是你非要搞個性化,使用其餘的也是能夠。只不過你的代碼並不美觀,後期也不容易維護。

2. 什麼是自執行函數,像這樣

function fun1(){
    //code
}
fun1();


function fun2(){
    fun2() // 遞歸
}

// 這是一個自執行匿名函數
var foo = function () { arguments.callee(); };

看了這些例子,咱們應該清楚了什麼是自執行,什麼是當即調用。

函數構造器

Function()構造函數並不須要經過傳入實參以指定函數名,就像函數直接量同樣,Function()構造函數建立一個匿名函數。

關於Function()構造函數有幾點須要特別注意

  • Function()構造函數容許JavaScript在運行時動態的建立並編譯函數。

  • Function()構造函數每次執行時都會解析函數主體,並建立一個新的函數對象,因此當在一個循環或頻繁執行的函數中調用Function()構造函數效率是很是低的。而函數字面量(函數表達式)卻不是每次遇到都會從新編譯的,用Function()構造函數建立一個函數時並不遵循典型的做用域,它一直把它看成是頂級函數來執行

  • 它所建立的函數並非使用詞法做用域,相反,函數體代碼的編譯老是會在頂層函數執行,

用函數構造器建立的函數不會在上下文中建立閉包,它們老是被建立在全局做用域中,當執行被建立的函數時,它們只能使用本身的局部變量或者全局變量。例以下面:

var scop = 'global';
function fun(){
    var scop  = 'fun';
    return new Function('return scope') // global
}

函數的屬性、方法和構造函數

1. length屬性

函數體中,arguments.length表示傳入函數的實參個數。而函數自己的length表示該函數的形參。

function fun(x,y,z){
     console.log(arguments.length);// 2
     console.log(arguments.callee.length); //3
 }

 fun(1,2);

咱們這裏提一下,在函數中的arguments,函數中arguments包含所用實參,它並非一個真正的數組,而是一個實參的對象。另外他還有兩個特別的屬性calleecaller屬性。

callee屬性只帶黨慶正在執行的函數。
caller屬性時非標準的,但大多數瀏覽器都實現了這個屬性它指帶調用當前正在執行的函數的函數。

經過caller屬性能夠訪問調用棧。callee屬性在某些時候會很是的有用,好比在匿名函數中經過callee來遞歸調用自身,上面咱們已經提到過。

2. prototype屬性

每個函數都包含一個prototype屬性,這個屬性是指向一個對象的引用,這個對象稱做"原型對象"。每個函數都包含不一樣的原型對象,當將函數用做構造函數的時候,新建立的對象會從原型對象上繼承屬性。

3. call()方法和apply()方法

call 和 apply 是爲了改變某個函數運行時的 context 即上下文而存在的,換句話說,就是爲了改變函數體內部 this 的指向。由於 JavaScript 的函數存在「定義時上下文」和「運行時上下文」以及「上下文是能夠改變的」這樣的概念。

它們的用法也是很簡單的。例如:

var obj = {
        msg:"我是obj對象",
    fun:function(){
       console.log(this.msg);
    },
}

var objTwo = {
    msg:"我是objTwo對象"
};

// 這裏咱們想讓經過objTwo 對象調用 obj對象的fun方法,
// 此時obj的fun方法中的this已經改變了指向。
obj.fun.call(objTwo) // 我是objTwo對象

call 和 apply 的區別在於他們傳參的形式不一樣:

var obj = {
        msg:"我是obj對象",
    fun:function(one,two){
       console.log(this.msg);
       console.log("我是參數:"+ one + ',' +two);
    },
}

var objTwo = {
    msg:"我是objTwo對象"
};

// 這裏咱們想讓經過objTwo 對象調用 obj對象的fun方法,
// 此時obj的fun方法中的this已經改變了指向。
obj.fun.call(objTwo,1,2) // 我是objTwo對象 ,我是參數:1,2

obj.fun.apply(objTwo,[1,2]) // 我是objTwo對象 ,我是參數:1,2

高階函數

所謂高階函數就是操做函數的函數,它接收一個或多個函數做爲參數,並返回一個新函數。咱們來看一個例子:

這個例子的主要做用是,fun函數接收f()g()兩個函數,並返回一個新的函數用以計算f(g())

function fun(f,g){
    return function(){
      return f.call(this,g.apply(this,arguments));
    }
}

var square = function(x){
    return x * x
}

var sum = function(x,y){
    return x + y;
}

var suqareOfSum = compose(square,sum)

squareOfSum(2,3) // 25

在這裏函數fun() 就是高階函數。

不徹底函數

不徹底函數是一種函數變換技巧,即把一次完整的函數調用折成屢次函數調用,每次傳入的實參都是完整實參的一部分,每一個拆分開的函數叫作不徹底函數,每次函數調用叫作不徹底調用,這種函數變換的特色是每次調用都反一個函數,知道獲得最終運行結果爲止,例如:將函數f(1,2,3,4,5)的調用修改成等價的f(1,2)(3,4)(5)後者包含三次調用,和每次調用相關的函數就是 不徹底函數」。

結束

參考文獻: 《JavaScript權威指南》

相關文章
相關標籤/搜索