1.有關閉包定義
閉包是指有權訪問另一個函數作用域中變量的函數,創建閉包的最常見的
方式就是在一個函數內創建另一個函數,通過另一個函數訪問這個函數的局部變量
閉包的特性:
函數內再嵌套函數
內部函數可以引用外層的參數和變量
參數和變量不會被垃圾回收機制回收
說說你對閉包的理解
使用閉包主要是為了設計私有的方法和變量。閉包的優點是可以避免全局變量的污染,
缺點是閉包會常駐內存,會增大內存使用量,使用不當很容易造成內存泄露。在js中,
函數即閉包,只有函數才會產生作用域的概念
閉包 的最大用處有兩個,一個是可以讀取函數內部的變量,另一個就是讓這些
變量始終保持在內存中
閉包的另一個用處,是封裝對象的私有屬性和私有方法
好處:能夠實現封裝和緩存等;
壞處:就是消耗內存、不正當使用會造成內存溢出的問題
使用閉包的注意點
由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用
閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露
解決方法是,在退出函數之前,將不使用的局部變量全部刪除
閉包的定義其實很簡單:函數 A 內部有一個函數 B,函數 B 可以訪問到函數 A 中的變量,那么函數 B 就是閉包
function A() {
let a = 1
window.B = function () {
console.log(a)
}
}
A()
B() // 1
閉包會產生一個很經典的問題:
多個子函數的[[scope]]都是同時指向父級,是完全共享的。因此當父級的
變量對象被修改時,所有子函數都受到影響。
解決:
變量可以通過 函數參數的形式 傳入,避免使用默認的[[scope]]向上查找
使用setTimeout包裹,通過第三個參數傳入
使用 塊級作用域,讓變量成為自己上下文的屬性,避免共享
2.閉包簡單例子
指的是有權訪問另一個函數作用域中變量的函數,
創建閉包的常見方式,就是在一個函數內部創建另一個函數。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
3.閉包的用處:
閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
4.使用必閉包的問題:
由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題。
閉包的例子:
function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
return innerFun; //注意這里
}
var obj=outerFun();
obj(); //結果為1
obj(); //結果為2
var obj2=outerFun();
obj2(); //結果為1
obj2(); //結果為2
function outerFun()
{
//沒有var
a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
結果為 0,0 真是奇怪,為什么呢?
作用域鏈是描述一種路徑的術語,沿着該路徑可以確定變量的值 .當執行a=0時,因
為沒有使用var關鍵字,因此賦值操作會沿着作用域鏈到var a=4; 並改變其值.
5.閉包內的微觀世界
參考學習:https://www.cnblogs.com/goloving/p/7062212.html
如果要更加深入的了解閉包以及函數a和嵌套函數b的關系,我們需要引入另外幾個概念:函數的執行環境(excution context)、活動對象(call object)、作用域(scope)、作用域鏈(scope chain)。以函數a從定義到執行的過程為例闡述這幾個概念。
1.當定義函數a的時候,js解釋器會將函數a的作用域鏈(scope chain)設置為定義a時a所在的“環境”,如果a是一個全局函數,則scope chain中只有window對象。
當執行函數a的時候,a會進入相應的執行環境(excution context)。
2.在創建執行環境的過程中,首先會為a添加一個scope屬性,即a的作用域,其值就為第1步中的scope chain。即a.scope=a的作用域鏈。
3.然后執行環境會創建一個活動對象(call object)。活動對象也是一個擁有屬性的對象,但它不具有原型而且不能通過Javascript代碼直接訪問。創建完活動對象后,把活動對象添加到a的作用域鏈的最頂端。此時a的作用域鏈包含了兩個對象:a的活動對象和window對象。
4.下一步是在活動對象上添加一個arguments屬性,它保存着調用函數a時所傳遞的參數。
5.最后把所有函數a的形參和內部的函數b的引用也添加到a的活動對象上。在這一步中,完成了函數b的的定義,因此如同第3步,函數b的作用域鏈被設置為b所被定義的環境,即a的作用域。
當在函數b中訪問一個變量的時候,搜索順序是:
先搜索自身的活動對象,如果存在則返回,如果不存在將繼續搜索函數a的活動對象,
依次查找,直到找到為止。
如果函數b存在prototype原型對象,則在查找完自身的活動對象后先查找自身的原型
對象,再繼續查找。這就是Javascript中的變量查找機制。
如果整個作用域鏈上都無法找到,則返回undefined。
函數的定義與執行。文中提到函數的作用域是在定義函數時候就已經確定,而不是在執行的時候確定
6.有關閉包經典案例
經典面試題,循環中使用閉包解決 var 定義函數的問題
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
首先因為 setTimeout 是個異步函數,所有會先把循環全部執行完畢,這時候 i 就是 6 了,所以會輸出一堆 6。
解決辦法兩種,第一種使用閉包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
第二種就是使用 setTimeout 的第三個參數
for ( var i=1; i<=5; i++) {
setTimeout( function timer(j) {
console.log( j );
}, i*1000, i);
}
第三種就是使用 let 定義 i 了
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
有關內存溢出與內存泄漏
1. 內存溢出
* 一種程序運行出現的錯誤
* 當程序運行需要的內存超過了剩余的內存時, 就出拋出內存溢出的錯誤
2. 內存泄露
* 占用的內存沒有及時釋放
* 內存泄露積累多了就容易導致內存溢出
* 常見的內存泄露:
* 意外的全局變量
* 沒有及時清理的計時器或回調函數
* 閉包
// 1. 內存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
// 2. 內存泄露
// 意外的全局變量
function fn() {
a = new Array(10000000)
console.log(a)
}
fn()
// 沒有及時清理的計時器或回調函數
var intervalId = setInterval(function () { //啟動循環定時器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
// 閉包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
// f = null
7.js垃圾回收機制
轉載:https://www.cnblogs.com/zhwl/p/4664604.html
由於字符串、對象和數組沒有固定大小,當他們的大小已知時,才能對他們進行動態的存儲分配。JavaScript程序每次創建字符串、數組或對象時,解釋器都必須分配內存來存儲那個實體。只要像這樣動態地分配了內存,最終都要釋放這些內存以便他們能夠被再用,否則,JavaScript的解釋器將會消耗完系統中所有可用的內存,造成系統崩潰。
現在各大瀏覽器通常用采用的垃圾回收有兩種方法:標記清除、引用計數。
標記清除
這是javascript中最常用的垃圾回收方式。當變量進入執行環境是,就標記這個變量為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到他們。當變量離開環境時,則將其標記為“離開環境”。
引用計數
另一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量並將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數就減1。當這個引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其所占的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數為0的值所占的內存。