定義:函數表達式區別於函數聲明,也是一種定義函數的方式,形似與變量賦值,這個值就是函數體,例如:數組
var a = function(){}; // 函數表達式之匿名函數 var a = function fn(){}; // 函數表達式之具名函數 (function(){})(); // 匿名函數之當即執行函數 // 目前知道的是這三種形式, 但願高人補充
定義:在一個函數中調用自身,遞歸必需要有結束條件階乘閉包
// fibonacci數列 function fibonacci(n){ if(n == 1 || n == 2){ // 結束條件 return 1; }else{ var num = fibonacci(n-1) + fibonacci(n-2); // 遞歸調用 return num // 每一層遞歸都返回和 } }; console.log(fibonacci(6)); // 8
特色:
1 . 調用匿名函數表達式自身,爲了便於維護,能夠經過arguments.callee(指向當前函數的指針)來調用當前函數,這樣作的好處是當遞歸函數換名稱時不用更換內部的函數名稱函數
function fibonacci(n){ if(n == 1){ return 1; }else{ var num = arguments.callee(n-1) * n ; return num } }; let a = fibonacci; fibonacci = null; console.log(a(6)); // 函數內在再次調用fibonacci就會報錯 Uncaught TypeError: fibonacci is not a function
一變this
function fibonacci(n){ if(n == 1){ return 1; }else{ var num = arguments.callee(n-1) * n ; return num } }; let a = fibonacci; fibonacci = null; console.log(a(6)); // 720 可是在嚴格模式下回報錯 Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed
二變prototype
'use strict'; let a = (function fibonacci(n){ if(n == 1){ return 1; }else{ var num = fibonacci(n-1) * n ; return num } }); let b = a; a=null; console.log(b(4)); // 24 ,這裏至關於包了一層,若是外邊的變量改變是不會影響函數內部調用的
注:在嚴格模式下arguments.callee會報錯,能夠經過命名函數表達式來實現指針
特色:關於閉包的特色都得先理解執行環境、做用域、活動對象的概念code
執行環境: 函數被調用時會建立當前函數的執行環境,能夠想象成一個對象,對象包含一個屬性,該屬性指向做用域(做用域鏈表)
做用域,也能夠當作是做用域鏈表,一個list,list的元素是指向活動對象,包括本做用域內的活動對象的指向和對上級的指向,當前函數執行完畢做用域就消失了
活動對象,包含當前函數內的全部屬性,當沒有做用域鏈引用時活動對象被銷燬
注:指向能夠理解爲引用,像 a=[], a就指向了內存中的一個數組對象
{ scope: scopeList ----| } | 執行環境(context) | [ | activeObj1: activeObjects1, --|--| activeObj2: activeObjects2, --|--|--| ... | | | ] | | | 做用域鏈(scopeList) <----| | | { | | var1: 1, | | var2: 1, | | var1: [], | | } | | 活動對象(activeObjects1) <--| | { | _var1: 1, | _var2: 1, | _var1: [], | } | 活動對象(activeObjects2) <--| // 能夠看出執行環境和做用域鏈是一對一的, 因此當執行完函數後執行環境就沒了,做用域沒有被引用了就也沒了,可是活動對象和做用域鏈是多對多的(途中只展現了一對多) ,因此就算做用域沒了,當前做用域的活動對象也可能被其它做用域引用(例如閉包),因此仍然存在於內存中
特色:閉包對外層活動對象是引用不是複製(也能夠說是複製了引用),這裏寫一個親身經歷的筆試題遞歸
var nAdd = null; function f(){ let n = 99; nAdd = () => { ++n; } return () => { console.log(n); } }; var f1 = f(); var f2 = f(); nAdd(); f1(); f2(); nAdd(); f1(); f2();
我認爲這個題挺有意思。這裏不給答案,讀者能夠本身先猜一下,而後本身跑一下和本身的猜測對對。
我認爲這個題目的關鍵在nAdd是在f函數的外層,也就是每次實例化這個f函數都會對nAdd從新賦值,從新賦值後執行環境中n會不一樣,屢次賦值取最後一個,只要能搞清楚執行環境、做用域、活動對象的關係其實不難,不會也不要緊,一開始看到我也懵。內存
特色:特色和定義息息相關,this和執行環境綁定,只要執行完畢執行環境不存在了就,例如:
var name = 'china,hebei'; var province = { name: 'hebei', getName: function(){ return function(){ return this.name; }; } }; console.log(province.getName()()); // china,hebei
從結果來看this指的是全局的那個this,這個和常規理解不太同樣,按說getName屬於province這個對象,this指向province纔對,想一想定義就明白了,province.getName()這個執行了getName函數,並返回了一個函數體,再次執行這個函數體的時候getName()已經執行完了,執行完了執行環境固然就不存在了,this就返回給執行的環境(全局環境),那如何改爲指向province呢?
var name = 'china,hebei'; var province = { name: 'hebei', getName: function(){ let that = this; return function(){ return that.name; }; } }; console.log(province.getName()()); // hebei
很容易理解,that在getName執行完畢後並不會消失,由於它所在的活動對象還被最後返回的函數的做用域鏈引用着,因此最後輸出的就是hebei
定義:經過建立一個當即執行函數來模仿模塊做用域的效果,普通的{}沒有塊級概念
for(var i=0; i<2; i++){ console.log(i); } console.log(i) // 2
塊級做用域,很簡單,經過函數做用域封裝一層便可,例如
(function(){ for(var i=0; i<2; i++){ console.log(i); } })() console.log(i) // Uncaught ReferenceError: i is not defined
定義:在函數定義的變量和方法均可以當作是私有變量,能夠經過在函數建立閉包實如今函數外部訪問私有變量,稱之爲共有方法(特權方法),例如:
function Student(){ var name = 'jiang'; this.getName = function(){ return name; }; }; var xiaoming = new Student(); console.log(xiaoming.getName()); // jiang 只有這種特權方法能夠訪問到
特色:私有變量只能經過特權方法在函數外部被訪問
解決的問題:加強函數的封裝性,函數做用域內得變量只能經過特權方法訪問
帶來的問題:每一個實例都會從新建立一個特權方法
定義: 在私有做用域內(當即執行函數)定義函數內的私有變量和全局的(變量沒有聲明就賦值時)匿名函數,爲匿名函數添加原型方法,原型方法內訪問函數內的變量,這樣在函數外部能夠能夠經過變量名稱直接訪問全局的匿名函數上的原型方法,方法內部能夠訪問函數私有變量
(function(){ let name = 'jiang'; student = function(){}; student.prototype.getName = function(){ return name; }; })() console.log(student.prototype.getName()); // jiang
單例模式,例如
var a = {}; var a = {}; // 老是隻有一個a對象
var a = (function(){ var name = 'jiang'; // 私有變量 return{ // 單例模式 getName: function(){ return name; } } })() console.log(a.getName());
定義:將函數內返回的對象經過構造函數的方式聲明,而後爲其添加特權方法和屬性,而後將對象返回,這樣的對象就能夠經過instanceof確認其類型了
var a = (function(){ var name = 'jiang'; function Student(){}; var xiaoming = new Student(); xiaoming.getName = function(){ return name; }; return xiaoming; })()