閉包容許函數訪問並操做函數外部的變量,只要變量或函數存在於聲明函數時的做用域內,閉包就能夠訪問這些變量和函數javascript
//全局閉包 不明顯
var outerValue = "ninja";
function outerFunction(){
outerValue === "ninja"; //true
}
outerFunction();
複製代碼
//閉包例子
var outerValue = "samurai";
var later;
function outerFunction(){
var innerValue = "ninja";
function innerFunction(){
outerValue === 'samurai'; //true
innerValue === 'ninja'; //true
}
later = innerFunction();
}
outerFunction();
later();
複製代碼
當在外部函數中聲明內部函數時,不只定義了函數的聲明,還建立了一個閉包java
該閉包不只包含了函數的聲明,還包含了 函數聲明時該做用域中的全部變量數組
閉包建立了被定義時的做用域內的變量和函數的安全氣泡安全
每個經過閉包訪問變量的函數 都具備一個做用域鏈,做用域鏈包含閉包的所有信息。閉包
:star: 閉包全部的信息都存儲在內存中,直到 JavaScript 引擎確保這些信息再也不使用或頁面卸載時,纔會清理。函數
私有變量 是對外部隱藏的對象屬性post
function Ninja(){
var feints = 0;
this.getFeints = function(){
return feints;
}
this.feint = function(){
feints++;
}
}
var ninja1 = new Ninja();
ninja1.feint();
ninja1.feints //沒法直接從外部訪問屬性
ninja1.getFeints() == 1 //true 能夠經過方法來訪問
var ninja2 = new Ninja(); //建立一個新的對象實例,新對象實例做爲上下文 this指向新的對象實例
ninja2.getFeints() == 0 //true 新實例有本身的私有變量
複製代碼
:zap: 在構造器中隱藏變量,使其在外部做用域中不可訪問,可是可在閉包內部進行訪問動畫
//在 interval 的回調函數中使用閉包
//回調函數中 this 指向變了
function animateIt(elementId){
var elem = document.getElementById(elementId);
var tick = 0;
var timer = setInterval(function(){
if(tick < 100){
elem.style.left = elem.style.top = tick + 'px';
tick++;
}else{
clearInterval(timer);
tick === 100; //true
}
},10);
}
animateIt("box1");
//經過在函數內部定義變量,並基於閉包,使得在計時器的回調函數中能夠訪問這些變量,每一個動畫都可以得到屬於本身的"氣泡"中的私有變量
複製代碼
閉包內的函數 不只能夠在閉包建立時能夠訪問這些變量,並且能夠在閉包函數執行時,更改這些變量的值。ui
閉包不是在建立的那一時刻的快照,而是一個真實的狀態封裝,只要閉包存在,就能夠對變量進行修改。this
JavaScript 有兩種類型的代碼:一種是全局代碼,一種是函數代碼
上下文分爲兩種:全局執行上下文和函數執行上下文
全局執行上下文只有一個,當 JavaScript 程序開始執行時就已經建立了全局上下文;而函數執行上下文在每次 調用 函數時,就會建立一個新的。
JavaScript 是單線程的:一旦發生函數調用,當前的執行上下文必須中止執行,並建立新的函數執行上下文來執行函數。當函數執行完成後,將函數執行上下文銷燬,並從新回到發生調用時的執行上下文中。
棧 💉的活塞
//建立執行上下文
function skulk(ninja){
report(ninja + " skulking");
}
function report(message){
console.log(message);
}
skulk("Kuma");
skulk("Yoshi");
複製代碼
能夠經過 JavaScript 調試器中查看,在 JavaScript 調試器中能夠看到對應的調用棧(call stack)。
詞法環境 是 JavaScript 引擎內部用來跟蹤標識符與特定變量之間的映射關係
詞法環境 是 JavaScript 做用域的內部實現機制,稱爲 做用域 (scopes)
詞法環境主要基於代碼嵌套,經過代碼嵌套能夠實現代碼結構包含另外一代碼結構
在做用域範圍內,每次執行代碼時,代碼結構都得到與之關聯的詞法環境。
內部代碼結構能夠訪問外部代碼結構中定義的變量。
每一個執行上下文都有一個與之關聯的詞法環境,詞法環境中包含了在上下文中定義的標識符映射表。=> 做用域鏈
在特定的執行上下文中,咱們的程序不只直接訪問詞法環境中定義的局部變量,並且還會訪問外部環境中定義的變量。
不管什麼時候建立函數,都會建立一個與之相關聯的詞法環境,並存儲在名爲 [[Environment]]
的內部屬性上。兩個中括號表示是內部屬性。
函數都有詞法環境
var ninja = "Muneyoshi";
function skulk(){
var action = "Skulking";
function report(){
var intro = "Aha!";
assert(intro === "Aha!","Local");
assert(action === "Skulking","Outer");
assert(ninja === "Muneyoshi","Global");
}
report();
}
skulk();
複製代碼
:question: 爲何不直接跟蹤整個執行上下文搜索標識符,而是經過詞法環境來跟蹤呢?
JavaScript 函數能夠做爲任意對象進行傳遞,定義函數時的環境與調用函數的環境每每是不一樣的(閉包)
:zap: 不管什麼時候調用函數,都會建立一個新的執行環境,被推入執行上下文棧。此外,還會建立一個與之關聯的詞法環境。外部環境與新建的詞法環境,JavaScript 引擎將調用函數的內置[[Environment]]屬性與建立時的環境進行關聯。
3個關鍵字定義變量:var let const
不一樣之處:可變性、詞法環境
const 不可變,let var 可變
聲明時須要初始化,一旦聲明完成以後,其值不可更改。 => 指向不可更改
const firstConst = "samurai";
firstConst = "ninja"; //報錯
const secondConst = {};
secondConst.weapon = "wakizashi"; //不報錯
const thirdConst = [];
thirdConst.push("Yoshi"); //不報錯
複製代碼
若是 const 的值是 靜態變量,則不容許從新賦值;若是 const 的值是對象或者是數組類型,則能夠對其增長新元素,可是不能重寫。其實不可變的是引用,而不是值。
var 一組,let 和 const 一組
關鍵字 var :變量是在距離最近的 函數內部 或是在 全局詞法環境 中定義的。忽略塊級做用域
var 聲明的變量實際上老是在 距離最近的函數內 或 全局詞法環境中 註冊的,不關注塊級做用域
let 與 const 直接在最近的詞法環境中定義變量(能夠是塊級做用域內、循環內、函數內或全局環境內)。咱們能夠用 let 和 const 定義 塊級別、函數級別、全局級別的變量。
詞法做用域又叫靜態做用域,由於js的做用域在詞法解析階段就肯定了
動態做用域:區別於靜態做用域,即在函數調用時才決定做用域
JavaScript 代碼的執行 分兩個階段:
一旦建立了新的詞法環境,就會執行第一階段。在第一階段,沒有執行代碼,而是JavaScript引擎會訪問並註冊在當前詞法環境中所聲明的變量和函數。變量和函數聲明提高
第二階段的執行取決於變量的類型(let var const 函數聲明)以及環境類型(全局環境、函數環境或塊級做用域)
1.若是是建立一個函數環境,那麼建立形參及函數參數的默認值。若是是非函數環境,將跳過此步驟。
2.若是是建立全局或函數環境,就掃描當前代碼進行函數聲明(不會掃描其餘函數的函數體),可是不會掃描函數表達式或箭頭函數。對於所找到的函數聲明,將建立函數,並綁定到當前環境與函數名相同的標識符上。若該標識符已經存在,那麼該標識符的值將被重寫。若是是塊級做用域,將跳過此步驟。
3.掃描當前代碼進行變量聲明。在函數或全局環境中,找到全部當前函數以及其餘函數以外經過 var 聲明的變量,並找到全部在其餘函數或代碼塊以外經過 let 或 const 定義的變量。在塊級環境中,僅查找當前塊中經過 let 或 const 定義的變量。對於所查找到的變量,若該標識符不存在,進行註冊並將其初始化爲 undefined。若該標識符已經存在,將保留其值。
若函數是函數聲明進行定義的,則能夠在函數聲明以前訪問函數。
若函數是函數表達式或箭頭函數進行定義的,則不會在以前訪問到函數
變量的聲明會提高至函數頂部,函數的聲明會提高至全局代碼頂部。
其實,變量和函數的聲明並無實際發生移動,只是在代碼執行以前,先在詞法環境中進行註冊。
閉包能夠訪問建立函數時所在做用域內的所有變量
//經過函數訪問私有變量,而不經過對象訪問
function Ninja(){
var feints = 0;
this.getFeints = function(){
return feints;
}
this.feint = function(){
feints++;
};
}
var ninja1 = new Ninja();
ninja1.feint();
var imposter = {};
imposter.getFeints = ninja1.getFeints;
imposter.getFeints() == 1 //true
複製代碼
JavaScript 中沒有真正的私有對象屬性,可是能夠經過閉包實現一種可接受的「私有」變量的方案