1. 程式人生 > >函式的實參 函式的形參 閉包 js

函式的實參 函式的形參 閉包 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)此時另外指定變數儲存其結果,無論指定多少個變數儲存其結果,都是新的空間的執行,沒有任何的干擾,詳細瞭解看下面,繼續討論

  1. ps;如果是()()則代表其會被其垃圾回收
  2. 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中