JavaScript篇 深入理解JavaScript函式
JavaScript中的函式
1. 函式的定義
-
兩種定義形式:
- 通過函式定義表示式 來定義
- 通過函式宣告語句 來定義
函式宣告語句定義一個函式
//計算階乘的遞迴函式 function factorial(x){ if (x<=1) return 1; return x*factotial(x-1); }
函式定義表示式定義一個函式
var s= function sum(x,y){ return x+y; }
tips:以表示式方式定義的函式(特別適合用來定義那些只會用到一次的函式),函式名是可選的,
也就可以直接寫成這樣
var s= function(x,y){ return x+y; }
2.函式命名
- 函式的名稱通常是動詞或者以動詞為字首的片語 如:funciton saveMessage(){}
- 函式名的第一個字元通常為小寫
-
當我們命名的函式名比較長時,一種是駝峰式命名 如:function readSystemFile(){}
另一種是以下劃線分割單詞,如:function make_products_iPhones(){} - 有一些函式是用作內部函式或者私有函式的,通常以一條下劃線為字首。 如: _login()
3.函式的呼叫
- 普通的函式呼叫
example:
printprops({x:1}) var total = distance(0,1,2)+ distance(3,4,5) var probability = factorial(5)/factoral(10)
- 物件方法的呼叫
概念:如果函式表示式是一個屬性訪問表示式(即該函式是一個物件的屬性或者陣列的一個函式),
那麼該呼叫表示式就是一個方法呼叫表用表示式。
example:
//定義一個物件 var person = { name: lihua , age:18 , sex:女 , send: function(){//返回 person的name、age、sex this.message = this.name + this.age + this.age; } }; person.send();//這條語句就是函式的方法呼叫 person.message; //得到name、age、sex相關資訊
tips:
1.函式返回的值倘若是因為直譯器到達結尾,返回值就是undefined;倘若函式返回是因為直譯器執行到一條return語句,
則返回return語句後面的值;如果return語句後面沒有值則返回undefined。
2.在ECMAScript3 和在非嚴格模式下的ECMAScript5 的函式呼叫規定中,this的值 (呼叫上下文)的值是全域性物件,但是在
嚴格模式 下this的值卻是undefined,因此可也用this來判斷當前環境是否為嚴格模式,此外ECMAScript2015(簡稱es6)嚴格模式請參考以下地址。
ECMAScipt的第六個版本 https://developer.mozilla.org/zh-CN/
3.方法呼叫和普通的函式呼叫的一個重要區別就是:呼叫上下文(即this的值),通常this關鍵字指向成為呼叫上下文 的物件,
屬性訪問表示式:物件名.方法名(通常使用.運算子訪問屬性)或者"[]"進行屬性訪問操作。(this關鍵字的相關內容是十分重要的)
更多this相關內容請參看此連結
- 建構函式的呼叫
建構函式就是用來初始化先建立的物件,通常使用關鍵字new 來呼叫建構函式,當使用new 關鍵字來呼叫建構函式的時候就會自動
建立一個新的的空物件,而建構函式只需要初始化這個新物件的狀態(屬性和方法),呼叫建構函式的話,新的物件的原型(prototype)等於
建構函式的原型(prototype)屬性 ,由此引出一個特性:通過同一個建構函式建立的所有物件都繼承同一個相同的物件。
凡是沒有形參的建構函式都可以省略圓括號,以下兩行程式碼是等價的。
var fn = new Object();
var fn = new Object;
tips:
1.建構函式通常不使用return關鍵字,進行初始化新物件,執行完函式體,就呼叫建構函式表示式的計算結果作為新物件的值,顯示返回。
2.倘若建構函式使用return語句返回一個物件,那麼呼叫表示式的值就是這個物件,而不是this指向的物件。
3.建構函式裡沒有顯式呼叫return時,預設是返回this物件,也就是新建立的例項物件。
4.return的是五種簡單資料型別:String,Number,Boolean,Null,Undefined。這種情況下,忽視return值,依然返回this物件
-
間接呼叫
JavaScript中的函式也是物件,所以函式物件也可以包含方法。函式的間接呼叫用到的call()和apply()方法。
this這兩個方法都能顯示指定呼叫所需的this 的值,這就引出一個特性:任何函式都可以作為物件的方法來呼叫,
this的值並不一定就是該函式執行真正的this值,在非嚴格模式下,倘若this的值指向null 和undefined 的話
會自動指向全域性物件(瀏覽器中就是window物件),同時值為原始值(數字,字串,布林值)的this會指向該原始值的自動包裝物件。
2.apply()方法呼叫一個具有給定this值的函式,以及作為一個陣列 (或類似陣列物件)提供的引數。
為this指定一個物件,使用apply時只需要寫一次這個方法然後再另一個物件中繼承它,繼而不用在這個新的物件重新來寫它。
tips:call()方法的作用和 apply() 方法類似,區別就是call()方法接受的是引數列表,而apply()方法接受的是一個引數陣列。
4.函式中的形參與實參
- 可選的形參
- 可變長的實參列表:實參物件 arguments 它是一個類陣列物件,通過數字下表就能夠訪問傳入函式的實參值,它包含length屬性,讓函式可以操作任意數量的實參。
-
callee和caller屬性
callee屬性指代當前正在執行的函式,caller指代呼叫當前正在執行的函式的函式,它可以訪問棧。而callee可以來遞迴呼叫自身。
5.函式的閉包(!important)
-
概念:通俗地講函式的閉包就是在一個函式內部定義另一個函式,而這個內部的函式(子函式)可以呼叫包裹它的函式(父函式等、"爺爺、太爺爺...")的變數。
也可以認為閉包就是能夠讀取其他函式內部變數的函式。 -
變數作用域:
全域性變數:任何函式內部都可以訪問全域性作用域
區域性變數:在函式外部無法讀取變數
tips
javascript是沒有像Java、C++那樣用一對“{}”括起來的塊級作用域,但是在es6中可以使用let關鍵字實現塊級作用域。
-
詞法作用域:變數的作用域是在定義時決定而不是執行時決定,也就是說詞法作用域取決於原始碼,通過靜態分析就能確定,因此詞法作用域也叫做靜態作用域。
with和eval除外,所以只能說JS的作用域機制非常接近詞法作用域(Lexical scope)。
var scope = "global scope"; function checkScope(){ var scope = "local scope"; function f(){return scope;} return f(); } checkScope();//輸出可以得到 local scope
-
閉包的用途
i. 讀取函式內部的變數、函式巢狀函式
ii. 讓這些變數的值始終保持在記憶體中(全域性變數的值不會在被函式呼叫過後自動清除,由GC回收)
iii. 避免全域性變數的汙染、讓私有成員存在
-
閉包的注意事項
i. 由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體洩露 。
解決方法是,在退出函式之前,將不使用的區域性變數全部刪除。
ii. 閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),
把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值。
以下例子來檢驗自己是否已經掌握了閉包的執行機制。
example 0ne:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
example Two:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());
6.函式的屬性、方法以及建構函式
-
length屬性
argument.length屬性表示傳入函式的實參的個數,而函式本身的length屬性則有不同含義,它表示函式定義時的實際形參個數。 -
prototype(原型)屬性
這個屬性指向物件的引用,而這個物件就被稱為原型物件 (prototype object),每個函式都包含這個屬性,都包含不同的原型物件;
在JavaScript中每個函式都有一個特殊的屬性叫作原型(prototype)
function doSomething(){} console.log(doSomething.prototype); ----------------------------------- 結果:這就是 原型物件 { constructor: ƒ doSomething(), //建構函式 /* 這些又是這個建構函式裡面的方法或者屬性 arguments: null caller: null length: 0 name: "doSomething" prototype: {constructor: ƒ} 建構函式的原型屬性 __proto__: ƒ ()*/ __proto__: { //原型屬性 constructor: ƒ Object(), hasOwnProperty: ƒ hasOwnProperty(), isPrototypeOf: ƒ isPrototypeOf(), propertyIsEnumerable: ƒ propertyIsEnumerable(), toLocaleString: ƒ toLocaleString(), toString: ƒ toString(), valueOf: ƒ valueOf() } } ------------------------------- function doSomething(){} doSomething.prototype.eat = "food" //doSomething函式的原型(prototype)屬性物件新增eat的屬性 var doSomeInstancing=new doSomething() //建立doSomething函式的例項通過new關鍵字來呼叫該函式,它返回這個函式的例項化物件給doSomeInstancing doSomeInstancing.prop = "add value";//給物件doSomeInstancing新增一個“name為prop值為add value”的屬性 console.log(doSomeInstancing);//輸出這個doSomeIntancing物件 結果現實這個物件有兩個屬性,一個是:prop: "add value" 另一個是:__proto__: Object console.log(doSomething.prototype); //輸出doSomething.prototype的值是一個物件{eat: "food",constructor: ƒ doSomething(), __proto__: Object 顯然doSomeInstancing.__proto__屬性與doSomething.prototype(建構函式的原型屬性的值是相同)它們的值是相等的。(可以通過===來判斷 __ptototype__為隱式原型)
- call()、apply()和bind()方法 在前面的函式的間接呼叫中已經介紹了call()以及apply(),在這裡就不再敘說,就詳細介紹ECMAScript5新增的bind()方法
bind()方法中的bind 翻譯過來就是捆綁、繫結 之意,作用就是將函式繫結至某個物件中,倘若一個函式呼叫了bind()方法並傳入一個物件作為引數,那麼這個方法將返回新的函式。
function f(y){return this.x+y;} //待繫結的函式 var o = {x :1};//將要被繫結的物件 var g = f.bind(o); //將函式f繫結至物件o相當於var o={x:1, f:function f(y){return this.x+y;}} console.log(g(2));//通過g(y)呼叫o.f(y) 輸出3
bind()方法除了除了第一個實參之外,傳入bind()的實參也會繫結至this.這種應用是一種常見的函數語言程式設計技術,被稱為柯里化(currying)
var sum = function(x,y){return x+y;} var succ = sum.bind(null,1); succ(2)
-
toString()方法
該方法返回字串 - Function()建構函式
-
高階函式
- 記憶(memorization)