JavaScript 函式用法詳解【函式定義、引數、繫結、作用域、閉包等】
本文例項講述了JavaScript 函式用法。分享給大家供大家參考,具體如下:
初始函式
Function型別,即函式的型別。
典型的JavaScript函式定義:
function 函式名稱(引數表){ //函式執行部分 return ; } //注意:引數列表直接寫形參名即可
return語句:return返回函式的返回值並結束函式執行
函式也可以看做資料來進行傳遞
引數列表相當於函式入口,return 語句相當於函數出口
函式可以作為引數來傳遞。
function test ( a ) { a(); } test(function () { alert(1); });
函式可以巢狀定義
function test2(){ function test3(){ alert(1); } test3(); } test2();
定義函式
三種定義函式的方式:
function語句形式
函式直接量形式
通過Function建構函式形式定義函式
//1 function 語句式 function test1 () { alert(1); } //2 函式直接量 (ECMAScript 推薦) var test2 = function () { alert(2); } //3 function 建構函式式 var test3 = new Function('a','b','return a+b;'); //最後一個引數是函式結構體 test3(10,20);
function語句 | Function建構函式 | 函式直接量 | |
---|---|---|---|
相容 | 完全 | js1.1以上 | js1.2以上版本 |
形式 | 句子 | 表示式 | 表示式 |
名稱 | 有名 | 匿名 | 匿名 |
性質 | 靜態 | 動態 | 靜態 |
解析時機 | 優先解析 | 順序解析 | 順序解析 |
作用域 | 具有函式的作用域 | 頂級函式(頂級作用域) | 具有函式作用域 |
靜態動態的區別
var d1 = new Date(); var t1 = d1.getTime(); for ( var i=0; i<10000000; i++ ) { // function test1 () {} // 342ms //在函式體只會被宣告一次 ,其它地方並不再解析,就可以呼叫 。 //靜態 //只會編譯一次,放入記憶體中。 var test2 = function () {} //354ms // var test3 = new Function(); //8400ms //每次執行,都是重新new一個 函式。 //new 完之後, 就銷燬了。不佔用記憶體。動態建立一次。 } var d2 = new Date(); var t2 = d2.getTime(); console.log( t2 - d1 );
解析順序
function f () { return 1; } // 函式1 alert( f() ); //返回值為4 說明第1個函式被第4個函式覆蓋 var f = new Function("return 2;"); // 函式2 alert( f() ); //返回值為2 說明第4個函式被第2個函式覆蓋 var f = function () { return 3; } // 函式3 alert( f() ); //返回值為3 說明第2個函式被第3個函式覆蓋 function f () { return 4; } // 函式4 alert(f()); //返回值為3 說明第4個函式被第3個函式覆蓋 var f = new Function("return 5"); // 函式5 alert(f()); //返回值為5 說明第3個函式被第5個函式覆蓋 var f = function(){ return 6; } // 函式6 alert(f()); //返回值為6 說明第5個函式被第6個函式覆蓋
函式作用域
var k = 1 ; function t1(){ var k = 2 ; // function test(){return k ;} //2 // var test = function(){ return k}; //2 var test = new Function('return k;'); //1 //new Function(); 是會有頂級作用域 alert(test()); } t1();
函式的引數arguments
arguments物件,是實參的副本
//js 中 函式分為 : 形參,實參 function test ( a,b,c,d ) { // console.log( test.length ); //獲取形參的個數 4 //函式的實際引數 內部就是使用一個數組去接收實際引數。 類陣列物件 //arguments 物件,只能在函式內部使用。 //arguments 物件, 可以訪問函式的實際引數 (實參的副本) // console.log( arguments.length ); //2 // console.log( arguments[0] ); //10 // // 第一種方式: // if ( test.length === arguments.length ) { // // return a + b; // // } //使用第二種 方式: if ( arguments.callee.length === arguments.length ) { return a + b; } //arguments物件, 使用得最多的還是使用遞迴操作 // arguments.callee; // 指向函式本身,函式體 } test(10,20);
this物件的簡單理解
this物件是在執行時基於函式的執行環境繫結的
在全域性函式中,this等於window,而當函式被作為某個物件的方法呼叫時,this等於那個物件。
也就是說this關鍵字總是指代呼叫者(誰呼叫了我,就指向誰)。
//this: this物件是指在執行時期基於執行環境所繫結的 var k = 10; function test () { this.k = 20; } test(); console.log( test.k ); //undefined
call和apply方法
每一個函式都包含兩個非繼承而來的方法:call、apply。這倆個方法的用途都是在特定的作用域中呼叫函式,實際上等於設定函式體內this物件的值。
call、apply的用途之一就是傳遞引數,但事實上,它們真正強大的地方式能夠擴充函式賴以執行的作用域(使你的作用域不斷的去變化)。
使用call()、aplly()來擴充作用域的最大好處就是物件不需要與方法有任何耦合關係。
fn.call(obj);
讓fn以執行,並且fn中的this以obj身份執行
將一個函式繫結到一個特定的作用域中,然後傳遞特定作用域中的引數。
//call, apply 簡單 用法: 繫結一些函式 用於傳遞引數 呼叫 function sum ( x,y ) { return x + y; } function call1 ( num1,num2 ) { return sum.call(this,num1,num2); } function apply1 ( num1,num2 ) { return sum.apply(this,[num1,num2]) } //將一個函式繫結到一個特定的作用域中,然後傳遞特定作用域中的引數。 console.log( call1(10,10) ); console.log( apply1(20,10) );
//擴充作用域,底層也經常使用這兩個方法,用於繫結不同的作用域。 //把一個函式賦給一個物件, 賦完之後,還可以重用,賦給另外一個物件。 window.color = 'pink'; var obj = { color: 'tan' } function showColor () { console.log( this.color ); } showColor.call(window); // showColor.call(this); showColor.apply(obj);
call方法簡單的實現
function test1 ( a,b ) { return a + b; } //自定義物件 function Obj ( x,y ) { return x * y; } var obj = new Obj(); //掛載到物件上 obj.method = test1; //執行該函式 obj.method(10,20); //執行完後刪除 delete obj.method;
bind
ES5中提供一個bind()方法。
為函式繫結一個執行時候的作用域。
將該方法繫結Function的原型上,因此定義一個function就有該方法,bind()新增作用域的時候,方法沒有執行,在方法執行的時候,作用域會變成繫結兌現的作用域。
function b () { console.log(this.title); } function Book ( title ) { this.title = title; } var book = new Book('javascript'); // apply,call 特點:呼叫就執行 // b.call(book); // b.apply(book); // 當執行一個函式的時候更改作用域 var bBindfn = b.bind(book); // 執行更改作用域 bBindfn();
執行環境和作用域鏈概念
執行環境(execution context)是javascript中最為重要的一個概念。執行環境定義了變數或函式有權訪問的其他資料,決定了它們各自的行為。每一個執行環境都有一個與之關聯的變數物件,環境中定義的所有變數和函式都儲存在這個物件中。雖然我們的程式碼無法訪問這個物件,但是解析器在處理資料時會在後臺執行它。
全域性執行環境是最外圍的一個執行環境。根據ECMScript實現所在的宿主環境不同,表示執行環境的物件也不一樣。
每一個函式都有自己的執行環境。當執行流進一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返還給之前的執行環境。當代碼在一個環境中執行時,會建立變數物件的一個作用域鏈(scope chain)。作用域鏈的用途,是保證對執行環境有權訪問的所有變數和函式的有序訪問(控制程式碼的訪問許可權)。
var color1 = "blue"; function changeColor () { var color2 = "red"; function swapColor () { var color3 = color2; //color3 = 'red' color2 = color1; //color2 = 'blue' color1 = color3; //color1 = 'red' console.log( color1,color2,color3 ); } swapColor(); } changeColor(); //環境變數 可以一層一層的向上進行追溯 可以訪問它的上級 環境(變數和函式) // 作用域鏈 具有層級關係 //在大型程式中,全域性變數,儘量少使用,因為全域性變數總是最後一次搜尋。 防止全域性變數汙染。//很少去定義全域性變數,效率比較慢。
垃圾收集和塊級作用域的概念
垃圾收集
javascript是一門具有自動垃圾收集機制的程式語言。開發人員不必關心記憶體分配和回收問題。
垃圾回收器也是每隔一段時間去進行回收。
離開作用域的值將被自動標記為可以回收,因此將在垃圾收集期間被刪除。標記清除是目前主流的垃圾收集演算法。這種思想是給當前不使用的值加上標記,然後回收其記憶體。
//垃圾收集 ,標記清除 (模擬) function test () { var a = 10; //mark - 被使用 var b = 20; //mark - 被使用 } test(); //執行完畢 之後 ,a,b又被標記使用。 mark - 沒有被使用 //在間隔時間中 回收。 如果mark 沒有被使用, 則回收。
//引用計數(模擬) //如果變數被引用 , count = 1; function test2 () { var a = 10; //count = 1; var b = 20; var c; c = a; //count++ = 2; //a 被 c 所使用 ,引用。 a = 50; //count--; //重新被賦值 count-- //等待 count 為 0 的時候, 垃圾回收機制 就回收 }
塊級作用域
javascript裡面沒有塊級作用域的概念,所以在使用if、for時候要格外的小心。
javascript模擬塊級作用域 (塊級作用域,相當於內部的執行體,一個執行環境)
利用 IIEF的特性
//當函式執行之後, 變數就被回收 function test () { (function () { //函式有一個單獨的作用域,外面無法訪問到 i for ( var i=0; i<=5; i++ ) { console.log( i ); } })(); console.log( i ); //報錯,無法找到 } test();
閉包 Closure
閉包與函式有著緊密的關係,它是函式的程式碼在執行過程中的一個動態環境,是一個執行期的、動態的概念。
所謂閉包,是指詞法表示包括不必計算的變數的函式。也就是說,該函式能夠使用函式外定義的變數。
在程式語言中,所謂閉包,是指語法域位於某個特定的區域,具有持續參照(讀寫)位於該區域內自身範圍之外的執行域上的非持久型變數值能力的段落。這些外部執行域的非持久型變數神奇地保留它們在閉包最初定義(或建立)時的值
理解閉包,必須要對於作用域鏈的概念非常的清楚。
var name = "xiao A"; var obj = { name : "xiao B",getName: function(){ return function(){ return this.name; } } }; console.log(obj.getName()()); //xiao A //類似: //var zf = obj.getName();//全域性作用域 //zf();
var name = "xiao A"; var obj = { name : "xiao B",getName: function(){ var self = this; return function(){ return self.name; } } }; //console.log( obj.getName().call(obj) ); console.log( obj.getName()() ); //閉包: 一個函式, 可以訪問另外一個作用域中的變數 //封閉性,(類似食品包裝袋一樣,封閉起來,保質期延長,變數的訪問範圍的延長) //private 起到一個保護變數的作用
//1 level function f(x){ //2 level var temp = x; //區域性變數 //temp 標記 已經沒有被使用 return function(x){ //3 level (function 有一個執行域) temp += x; //temp 下一級作用域仍然被引用 , 標記為 使用 alert(temp); } } //js 垃圾回收機制,當函式執行完畢後,內部所有的區域性變數都集體的被回收。 var a = f(50); a(5); //55 a(10); //65 a(20); //85
回撥函式
-
回撥函式執行
-
回撥函式中的this
-
回撥函式的返回值
forEach
// forEach:用來遍歷陣列中的每一項 // 1. 陣列中有幾項,那麼傳遞進去的匿名回撥函式就需要執行幾次。 // 2. 每一次執行匿名函式的時候,還傳遞了三個引數值:陣列中的當前項item,當前項的索引index,原始的陣列input // forEach方法中的this是arr,匿名函式回撥函式的this預設是window var arr = [10,234,23,76,7666,34]; arr.forEach(function(item,index,input) { input[index] = item * 10; // 操作之後,修改了原陣列 console.log(arguments); }); var obj = {name: 'zf'}; // arr.forEach(function(item,index) { // console.log(this); // }.call(obj)); // 給forEach賦值的是時候,首先把匿名函式執行,把匿名函式中的this變為obj,把匿名函式執行的返回結果undefined賦值給foreach arr.forEach(function(item,index) { console.log(this,'---'); }.bind(obj)); // bind 只是預先處理,先把this轉為引數的物件,到後續該執行的時候才執行。 // 不管是forEach,還是map都支援第二個引數,第二個引數表示:把匿名函式中的this進行修改。 arr.forEach(function() { console.log(this,'this'); },obj);
forEach相容處理
// 相容處理 Array.prototype._forEach = function(callback,context) { content = content || window; if ('forEach' in Array.prototype) { this.forEach(callback,context); return; } // IE6-8,執行回撥邏輯 for (var i=0; i<this.length; i++) { callback || callback.call(context,this[i],i,this); } }
map
var arr = [10,34]; arr.map(function(item,input) { // 原有陣列不變 console.log(arguments); return item * 10; }); // map和forEach非常相似,都是用來遍歷陣列中的每一項的值 // 區別:map的回撥函式中支援return返回值,return的是什麼,相當於把陣列中的這一項改變為什麼(但是並不影響原來的陣列,只是相當於把原陣列克隆一份,把克隆的這一份的陣列中的對應項改變)
map相容處理
Array.prototype._map = function(callback,context) { context = context || window; if ('map' in Array.prototype) { this.map(callback,執行回撥邏輯 var resArr = []; for (var i=0; i<this.length; i++) { if (typeof callback === 'function') { resArr[resArr.length] = callback.call(context,this); } } return resArr; } var arr = [10,34]; arrMap = arr._map(function(item,input) { // 原有陣列不變 // console.log(arguments,'_map'); return item * 100; }); console.log(arrMap);
柯理化函式思想
柯理化函式思想:一個JS預處理思想
核心:利用函式執行可以形成一個不銷燬的私有作用域的原理,把需要預先處理的內容都儲存在這個不銷燬的作用域中,並且返回一個匿名函式,執行的都是匿名函式,把匿名函式中,把之前的預先儲存的值進行相關操作處理即可。
var obj = {name: 'zf'}; function fn(num1,num2) { console.log(this,num2) } // 把傳遞進來的callback這個方法中的this預先處理為context function bind(callback,context) { context = context || window; var outArg = Array.prototype.slice.call(arguments,2); return function(ev) { var innerArg = Array.prototype.slice.call(arguments,0); callback.apply(context,outArg.concat(innerArg)); } } // document.body.onclick = fn; // fn 中的 this是document.body. num1是 MouseEven物件 document.body.onclick = fn.bind(obj,100,200); // 除了預先處理了this和需要手動傳遞的引數值以外,把瀏覽器預設給傳遞的滑鼠事件物件也進行預先處理,傳遞到最後一個引數。 document.body.onclick = bind(obj,200) // document.body.onclick = function() { // console.log(this); // this --> document.body // } // window.setTimeout(fn.bind(obj),0); // window.setTimeout(bind(fn,obj,20),0); // 給定時器繫結方法,然後定時器到達時間的時候,讓fn執行,並且讓fn中的this變為obj
感興趣的朋友可以使用線上HTML/CSS/JavaScript程式碼執行工具:http://tools.jb51.net/code/HtmlJsRun測試上述程式碼執行效果。
更多關於JavaScript相關內容可檢視本站專題:《JavaScript常用函式技巧彙總》、《javascript面向物件入門教程》、《JavaScript錯誤與除錯技巧總結》、《JavaScript資料結構與演算法技巧總結》及《JavaScript數學運算用法總結》
希望本文所述對大家JavaScript程式設計有所幫助。