函式的實參 函式的形參 閉包 js
函式的實參和形參
可選形參
if(a === undefined) a = [];
等價於
a = a || [];
這兩句是完全等價的,只不過後者需要提前宣告a而已
如果引數沒有傳入,其餘的填充undefined
可選的形式引數:通過註釋/optional/來強調引數可選,並且要將其放在最後,否則就要使用null或者undefined來作為佔位符來進行傳入
可變長的實參列表
callee和caller
callee為指代當前正在執行的函式
caller指代當前正在執行函式的函式
將物件屬性用作實參
>
> function e(o){
... return o.Object;
... }
undefined
> e;
[Function: e]
> var a = {Object:33};
undefined
> e(a);
33
>
作為值的函式
函式能作為值傳入另外一個函式
自定義函式屬性
函式屬性可以自定義
o.a = 3;
function o() {
return o.a;
}
作為名稱空間的函式
在函式中宣告的變數在整個函式體內都是可見的(包括在巢狀函式中),在函式外部是不可見的。不在任何函式內宣告的變數為全域性變數。在整個js程式中都是可見的。在js中無法宣告只在一個程式碼塊內可見的變數的。所以常常簡單的定義一個函式用作臨時的名稱空間。在這個名稱空間內定義的變數都不會汙染到全域性名稱空間。
正是因為如果將變數定義在全域性的時候,會出現變數的汙染,汙染到全域性變數(好吧,這是動態語言的坑)導致出現一些未知的錯誤。所以呢將變數放置在函式中,在進行呼叫,這樣放置其變數汙染其全域性空間,出現變數的衝突(尤其是在瀏覽器的環境下,很容易的,導致各種未知錯誤,所以必須要這樣做)
定義完函式後直接呼叫函式
(
function() {
return 333;
}()
);
加()是必須的,因為如果不加()會讓js直譯器認為其為函式宣告,function按照函式宣告來進行解釋,js直譯器不允許建立一個匿名的函式宣告,所以會報錯。
加()變成一個函式表示式,js直譯器執行建立一個匿名的函式表示式
閉包
終於到閉包了。(正經點Σ( ° △ °|||)︴)
(這是最難的地方,是函數語言程式設計的基礎,也是能否學好js的最關鍵的地方。。。。當然了,es6還有一個令人討厭的箭頭函式)
閉包是其函數語言程式設計的重要的基礎
和其他語言一樣js採用的詞法作用域,即函式的執行依賴於變數的作用域,作用域是在函式定義時確定的,不是在其呼叫所決定的
即js的函式物件的內部狀態不僅僅包含函式的程式碼邏輯,還必須引用當前的作用域鏈(變數的作用域向下傳遞的,變數的作用域鏈在進行尋找的時候往上尋找,直到函式的頂部)函式物件可以通過作用域鏈相互關聯起來,函式體內部的變數可以儲存在函式作用域內
很古老滴術語,指函式變數可以被隱藏於作用域鏈之內,因此看起來函式將變數包裹起來。
如何定義作用域鏈
作用域鏈為一個物件的列表,每次呼叫js函式的時候,都會建立一個新的物件來儲存其區域性變數,把這個物件新增到作用域鏈中,如果函式返回,就從作用域鏈中將繫結的物件刪除,如果不存在巢狀函式,也不存在其引用指向這個繫結的物件,會被js直譯器的垃圾回收機制不定時的回收,是不定時的,不是在沒有完全引用的時候立馬刪除,如果定義了巢狀函式,每個巢狀函式都各自對應著一個作用域鏈,並且這個作用域鏈指向一個變數繫結的物件。如果這些巢狀函式物件在外部函式中儲存下來,那麼他們也會和所指向的變數繫結物件一樣當做垃圾進行回收,如果這個函式定義了巢狀的函式,並將它作為返回值返回,或者儲存在某處屬性裡,會有外部引用指向這個巢狀函式,即不會被當做垃圾回收,其變數所繫結的物件也不會當做垃圾進行回收。
函式執行完畢以後相關的作用域鏈不會刪除,只有當不在有引用的時候,才會進行刪除操作
關於棧的說明
原始棧
棧頂 window
執行下列js指令碼
function a() {
function f() {
return 333;
}
return f;
}
a()();
棧頂 a → window
開始呼叫,執行到return
發現需要呼叫f
繼續加棧
棧頂 f → a → window
執行完f彈出f
繼續執行a,執行完畢彈出a
最後全部執行完畢彈出window
算了文字解釋太無力,直接上程式碼
var scope = "global scope"; // 一個全域性變數
function checkscope()
{
var scope = "local scope"; // 定義一個區域性變數
function f()
{
return scope; // 返回變數作用域中的scope的值
}
return f(); // 返回這個函式
}
呼叫一下這個函式
checkscope();
"local scope"
接著這樣執行
var scope = "global scope"; // 一個全域性變數
function checkscope()
{
var scope = "local scope"; // 定義一個區域性變數
function f()
{
return scope; // 返回變數作用域中的scope的值
}
return f; // 返回這個函式
}
繼續呼叫函式
checkscope()();
"local scope"
閉包有什麼用
先看一個函式uniqueInteger()使用這個函式能夠跟蹤上次的返回值
var uniqueInteger = (
function() {
var count = 0;
return function() {return count++}
}()
);
這樣子就使用閉包
uniqueInteger();
0
uniqueInteger();
1
每次返回是其上一次的值,並隨便直接將值加1
至於為什麼要這樣寫,如果不使用閉包,那麼惡意程式碼就可以隨便的將計數器重置了。。
uniqueInteger.count = 0;
function uniqueInteger() {
return uniqueInteger.count++;
}
類似這樣的,完全可以做到直接通過賦值,將其count的值重置。
而如果使用閉包,沒有辦法進行修改,為私有狀態,也不會導致其一個頁面內變數的衝突,或者是其覆蓋。
立即呼叫的函式
var a = (function c(){
var a = 1;
a++;
console.log('已經執行');
return function b(){return a++};
}())
額,我大概解釋一下這段程式碼。
首先呢,解釋最外層的圓括號,因為如果沒有圓括號,則這個是一個賦值語句,將一個匿名函式賦值給變數a,實際上是在記憶體中完成了棧中變數a指向匿名函式儲存的空間的地址,如果有圓括號,實際上是告訴js直譯器這是一個語句,需要js執行,消除了其function帶來的影響。(ps;貌似火狐上不加也可以,也可以正常的執行)執行和引用的關係下方有。
然後呢,最後的圓括號,代表著其執行這個函式,因為js解析器將()解析為呼叫前方的函式名稱,類似於運算子吧。但是實際上並不是運算子,因為能往其內傳值,注意,這點是將其執行的結果儲存在堆中,並完成其指向
其後,當直接輸入a;,實際上執行並完成了一次呼叫,其返回值為函式b,將函式b完成一次引用,即變數a引用函式b,由於其存在引用關係,即棧中變數a儲存的為其函式a的返回結果,(因為其不是不是物件,如果寫a()()表示將函式a呼叫後返回的物件儲存在棧中,然後將棧中的內容再次呼叫,由於是儲存,並不存在其應用關係,所以執行完畢後直接垃圾回收)由於其儲存的是函式b的作用域鏈,而函式b的作用域鏈是繼承自函式a的作用域鏈,但是由於函式a的作用域鏈並沒有引用導致其執行完後被垃圾回收(當不在有變數指向的時候)。所以呢,函式其值是在函式b中進行儲存,如果修改函式c此時函式c並不會影響到函式b中的儲存,因為其函式c的變數列表已被銷燬,
最後,繼續討論起巢狀函式的引用,由於其父函式已被銷燬,但是巢狀函式被引用,(注意:因為其父已經沒有,所以是另開闢一塊新的堆空間,用於儲存其函式c的返回結果,注意是返回結果,而不是函式b)此時另外指定變數儲存其結果,無論指定多少個變數儲存其結果,都是新的空間的執行,沒有任何的干擾,詳細瞭解看下面,繼續討論
- ps;如果是()()則代表其會被其垃圾回收
- ps 還需要注意一點點的是由於其引用的是result的值,並不是其
最後,這樣就能完成其變數儲存在函式中,貌似叫做記憶?
所以呢,藉助堆和棧就很好的能理解了閉包
再繼續看程式碼
function count() {
var n = 0;
return {
count: function() { return n++; },
reset: function() { n = 0; }
};
}
var c = count(); var d = count();
undefined
在分別執行一下下
c.count();
0
d.count();
0
c.count();
1
d.count();
1
c.reset();
undefined
c.count();
0
d.count();
2
這一點體現了其互不影響性,表明其由於其父被回收,導致其子分別開創了一塊在堆中新的記憶體空間,並完成其指向,互相不干擾。
其作用域鏈互不干擾
使用getter和setter完成其閉包
function count(n) {
return {
get count() { return n++; },
set count(m) {
if ( m >= n)
n = m;
else
throw new Error( '請輸入一個正確的值' );
},
};
}
這個就不用解釋啦,很簡單啦
同一個作用域鏈中定義兩個閉包
function test1() {
val = value = 111;
this.test = function() { return value - 1; };
this.test2 = function() { return value + 1; };
}
這同樣是兩個作用鏈域
不過這樣寫需要先執行其o.test1(),因為其方法在其函式內部,必須先執行一下,完成其方法的新增,否則會報錯,
ee.test is not a function
提示找不到這個方法,
因為執行
ee.test1 = test1;
function test1()
只是簡單的進行賦值,並不能進行檢視,所以導致其無法使用
所以嘛,要先執行一遍,讓其方法新增進去
ee.test1();
undefined
ee.test();
110
ee.test2();
112
這就是兩個閉包,這兩個閉包互相平行,同時繼承於其父,但是又不受其父影響,很神奇吧,(@ο@)
關於this的問題
this在父閉包顯示的即為使用該方法的物件。
但是子就不一定了。
function test1() {
val = value = 111;
this.test = function() { return this.x - 1; };
this.test2 = function() { return this.x + 1; };
}
執行一下
ee.test();
4443
這就尷尬了。
好吧。只能說是一般不這樣用
一般這樣寫
var self = this;
將其值儲存進一個self中