国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

淺談js內(nèi)存與閉包

dailybird / 601人閱讀

摘要:將他們放在堆中是為了不影響棧的效率。接著是臨時空間函數(shù)執(zhí)行的時候,會臨時開辟一塊內(nèi)存空間,這塊內(nèi)存空間長得和外面這個一樣,也有自己的棧堆,當(dāng)函數(shù)運行完就銷毀。中的內(nèi)存第一個部分還是和上面的一樣,有棧堆運行時環(huán)境,另外還有一個緩沖區(qū)存放。

0.前言

主要結(jié)合了內(nèi)存的概念講了js的一些的很簡單、但是又不小心就犯錯的地方。
結(jié)論:js執(zhí)行順序,先定義,后執(zhí)行,從上到下,就近原則。閉包可以讓外部訪問某函數(shù)內(nèi)部變量,而且會導(dǎo)致內(nèi)存泄漏。

1.先說類型

在ECMAscript數(shù)據(jù)類型有基本類型和引用類型,基本類型有Undefined、Null、Boolean、Number、String,引用類型有Object,所有的的值將會是6種的其中之一(數(shù)據(jù)類型具有動態(tài)性,沒有定義其他數(shù)據(jù)類型的必要了)
引用類型的值,也就是對象,一個對象是某個引用類型的一個實例,用new操作符創(chuàng)建也可以用字面量的方式(對象字面量創(chuàng)建var obj ={ })。ECMA里面有很多原生的引用類型,就是查文檔的時候看見的那些:Function、Number (是對于原始類型Number的引用類型)、String(是對于原始類型String的引用類型)、Date、Array、Boolean(...)、Math、RegExp等等。
在程序運行的時候,整塊內(nèi)存可以劃分為常量池(存放基本類型的值)、棧(存放變量)、很大的堆(存放對象)、運行時環(huán)境(函數(shù)運行時)

基本數(shù)據(jù)類型的值是直接在常量池里面可以拿到,而引用類型是拿到的是對象的引用

var a = 1;
var b = "hello";
var c = a;

c = a,這種基本數(shù)據(jù)類型的復(fù)制,只是重新復(fù)制一份獨立的副本,在變量的對象上創(chuàng)建一個新的值,再把值復(fù)制到新變量分配的位置上,a、c他們自己的操作不會影響到對方。

a++;console.log(a);console.log(c)

顯然是輸出2、1

obj1和obj2,拿到的是新創(chuàng)建的對象的引用(也就是家里的鑰匙,每個人帶一把),當(dāng)操作對象的時候,對象發(fā)生改變,另一個obj訪問的時候,發(fā)現(xiàn)對象也會改。就像,家里有一個人回去搞衛(wèi)生了,另一個回家發(fā)現(xiàn)家里很干凈了。

var obj1 = new Object();
obj1.name = "obj1"
var obj2 = obj1
console.log(obj2)  //{name: "obj1"}

對于vue,為什么data必須是一個返回一個對象的函數(shù),也是這個道理,避免所有的vue實例共用一套data。所以對于類似于這種情況,我們可以像vue那樣處理

//data是一個對象的時候,共用一套data
function D(){}
D.prototype.data =  {a:1,b:2}
var a = new D()
var b = new D()
a.data.a = 666
b.data.a //666
//data是一個函數(shù)的時候,各自維護自己的data
function D(){
    this.data = this.data()
}
D.prototype.data = function () {
    return {
        a:1,b:2
    }
}
var a = new D()
var b = new D()
a.data.a = 666
b.data.a //1

同樣的身為引用類型的函數(shù)也是同理

var a = function(){console.log(1)}
var b = a;
a = null;
b();a()
//b輸出1,a報錯:Uncaught TypeError: a is not a function
//a指向函數(shù),b拿到和a一樣的指針,然后讓a指向空

把a變成null,只是切斷了a和函數(shù)之間的引用關(guān)系,對b沒有影響

2.再說順序

大家常聽說的先定義后執(zhí)行,其實就是在棧中先開辟一塊內(nèi)存空間,然后在拿到他所對應(yīng)的值,基本類型去常量池,引用類型去堆拿到他的引用。大家常說的原始類型值在棧,其實就是這種效果。

2.1 為什么引用類型值要放在堆中,而原始類型值要放在棧

在計算機的數(shù)據(jù)結(jié)構(gòu)中,棧比堆的運算速度快,Object是一個復(fù)雜的結(jié)構(gòu)且可以擴展:數(shù)組可擴充,對象可添加屬性,都可以增刪改查。將他們放在堆中是為了不影響棧的效率。而是通過引用的方式查找到堆中的實際對象再進行操作。
因此又引出另一個話題,查找值的時候先去棧查找再去堆查找。

2.2 為什么先去棧查找再去堆查找

既然都講了,棧比堆的運算速度,堆存放的是復(fù)雜數(shù)據(jù)類型。那么簡單來說,寧愿大海撈針呢還是碗里撈針呢?

3.然后到了函數(shù)

先拋出一個問題

function a(){console.log(2)};
var a  = function(){console.log(1)};
a()

覆蓋?那么交換的結(jié)果又是什么呢?

var a  = function(){console.log(1)};
function a(){console.log(2)};
a()

都是1,然后有的人就說了,var優(yōu)先。好的,那為什么var優(yōu)先?

先定義后執(zhí)行,先去棧查找

變量提升,其實也是如此。先定義(開辟一塊內(nèi)存空間,此時值可以說是undefined)后執(zhí)行(從上到下,該賦值的就賦值,該執(zhí)行操作的就去操作),就近原則
函數(shù)聲明和函數(shù)表達式,有時候不注意,就不小心出錯了

 a(); function a(){console.log(666)}//666

另一種情況:

a(); var a = function (){console.log(666)}//a  is not a function

雖然第一種方法有變量提升,不會出錯,正常來說,還是按順序?qū)懀x語句放前面。如果想嚴格要求自己,就手動來個嚴格模式‘use strict’吧。對于框架的開發(fā),需要嚴謹遵守規(guī)則,所以一般會用嚴格模式。

4.接著是臨時空間

函數(shù)執(zhí)行的時候,會臨時開辟一塊內(nèi)存空間,這塊內(nèi)存空間長得和外面這個一樣,也有自己的棧堆,當(dāng)函數(shù)運行完就銷毀。

4.1 eg1:
var a = 10;
function() {
console.log(a);//undefined
var a = 1;
console.log(a)//1
}

宏觀來說,只有2步一和二,當(dāng)執(zhí)行第二步,就跳到函數(shù)內(nèi)部執(zhí)行②-⑧

函數(shù)外部的a=10完全就沒有關(guān)系,這里面造成undefined主要因為變量提升,其實準(zhǔn)確的順序是:

var a
console.log(a);//undefined
a = 1;
console.log(a)//1

為什么不出去找全局的a?
就近原則。為什么就近原則?都確定函數(shù)內(nèi)部有定義了,就不會再去外面白費力氣。其實是,函數(shù)在自己的作用域內(nèi)找到就不會再再繼續(xù)找,類似原型鏈一樣,在構(gòu)造函數(shù)里面找到某個屬性就不會去原型找,找不到才去,再找不到就再往上。函數(shù)也是,沿著作用域鏈查找。類似的一個例子,我們用函數(shù)聲明定義一個函數(shù)f,再用一個變量g拿到這個函數(shù)的引用,然后在外面用f是訪問不了這個函數(shù)的,但是在函數(shù)內(nèi)部是能找到f這個名字的:

var g = function f(){
   console.log(f)
    }
g()//打印整個函數(shù)
f()//報錯
4.2 eg2
function f(){
return function f1(){
       console.log(1)
   }
};
var res = f();
res();
f1()

res(),返回的是里面的函數(shù),如果直接f1()就報錯,因為這是window.f1()

函數(shù)聲明后,可以通過引用名稱查找或者內(nèi)存地址查找

局部作用域用function聲明,聲明不等于創(chuàng)建,只有調(diào)用函數(shù)的時候才創(chuàng)建

函數(shù)f有內(nèi)存地址的話,通過棧找f的內(nèi)存空間,如果找不到棧中f這個變量,就去堆中找

5.垃圾回收

進行前端開發(fā)時幾乎不需要關(guān)心內(nèi)存問題,V8限制的內(nèi)存幾乎不會出現(xiàn)用完的情況,而且我們只要關(guān)閉了瀏覽器,一切都結(jié)束。如果是node后端,后端程序往往進行更加復(fù)雜的操作,加上長期運行在服務(wù)器不重啟,如果不關(guān)注內(nèi)存管理,積少成多就會導(dǎo)致內(nèi)存泄漏。
node中的內(nèi)存第一個部分還是和上面的一樣,有棧、堆、運行時環(huán)境,另外還有一個緩沖區(qū)存放Buffer。你可以通過process.memoryUsage()查看node里面進程內(nèi)存使用情況。堆中的對象,被劃分為新生代和老生代,他們會被不同的垃圾回收機制清理掉。

5.1新生代

新生代用Scavenge算法進行垃圾回收,利用復(fù)制的方式實現(xiàn)內(nèi)存回收的算法。
他的過程是:

將新生代的總空間一分為二,只使用其中一個,另一個處于閑置,等待垃圾回收時使用。使用中的那塊空間稱為From,閑置的空間稱為To

當(dāng)觸發(fā)垃圾回收時,V8將From空間中所有存活下來的對象復(fù)制到To空間。

From空間所有應(yīng)該存活的對象都復(fù)制完成后,原本的From空間將被釋放,成為閑置空間,原本To空間則成為使用中空間,也就是功能交換。

如果某對象已經(jīng)經(jīng)歷一次新生代垃圾回收而且第二次依舊存活,或者To空間已經(jīng)使用了25%,都會晉升至老生代

5.2老生代

老生代利用了標(biāo)記-清除(后面又加上了標(biāo)記-整理)的方式進行垃圾回收。
在標(biāo)記階段(周期比較大)遍歷堆中的所有對象,標(biāo)記活著的對象,在隨后的清除階段中,只清除沒有被標(biāo)記的對象。每個內(nèi)存頁有一個用來標(biāo)記對象的位圖。這個位圖另外有兩位用來標(biāo)記對象的狀態(tài),這個狀態(tài)一共有三種:未被垃圾回收器發(fā)現(xiàn)、被垃圾回收器發(fā)現(xiàn)但鄰接對象尚未全部處理、不被垃圾回收器發(fā)現(xiàn)但鄰接對象全部被處理。分別對應(yīng)著三種顏色:白、灰、黑。

遍歷的時候,主要是利用DFS。剛剛開始的時候,所有的對象都是白色。從根對象開始遍歷,遍歷過的對象會變成灰色,放入一個額外開辟的雙端隊列中。標(biāo)記階段的每次循環(huán),垃圾回收器都會從雙端隊列中取出一個對象染成黑對象,并將鄰接的對象染色為灰,然后把其鄰接對象放入雙端隊列。一直循環(huán),最后所有的對象只有黑和白,白色的將會被清理。
假設(shè)全局根對象是root,那么活對象必然是被連接在對象樹上面的,如果是死對象,比如var a = {};a=null我們創(chuàng)建了一個對象,但把他從對象樹上面切斷聯(lián)系。這樣子,DFS必然找不到他,他永遠是白色。
此外,在過程中把垃圾對象刪除后,內(nèi)存空間是一塊一塊地零星散亂地分布,如果是遇到一個需要很大內(nèi)存空間的對象,需要連續(xù)一大片內(nèi)存存儲的對象,那就有問題了。所以還有一個整理的階段,把對象整理到在內(nèi)存上連續(xù)分布。

5.3 對比

新生代是經(jīng)常發(fā)生的,老生代發(fā)生的周期長

新生代占用的內(nèi)存小,老生代占用了大部分內(nèi)存

新生代需要把內(nèi)存分成兩塊進行操作,老生代不需要

新生代是基于對象復(fù)制,如果對象太多,復(fù)制消耗也會很大,所以需要和老生代相互合作。老生代基于DFS,深度遍歷每一個活對象

顯然老生代花銷大,所以他的周期也長,但是比較徹底

6.IIFE和閉包 6.1 IIFE

立即執(zhí)行函數(shù),形成一個沙盒環(huán)境,防止變量污染內(nèi)部,是做各種框架的好方法
先手寫一段假的jQuery

(function(root){
 var $ = function(){
//代碼
}
root.$ = $
})(this)

這樣子在內(nèi)部函數(shù)里面寫相關(guān)的表達式,我們就可以用美元符號使用jQuery(實際上jQuery第一個括號是全局環(huán)境判斷,真正的函數(shù)體放在第二個括號里面,號稱世界上最強的選擇器sizzle也里面)

6.2閉包

閉包的概念各有各的說法,平時人家問閉包是什么,大概多數(shù)人都是說在函數(shù)中返回函數(shù)、函數(shù)外面能訪問到里面的變量,這些顯而易見的現(xiàn)象,或者把一些長篇大論搬出來。簡單來說,就是外部訪問內(nèi)部變量,而且內(nèi)部臨時開辟的內(nèi)存空間不會被垃圾回收。查找值的時候沿著作用域鏈查找,找到則停止。
對于js各種庫,是一個龐大的IIFE包裹著,如果他被垃圾回收了,我們肯定不能利用了。而我們實際上就是能利用他,就是因為他暴露了接口,使得全局環(huán)境保持對IIFE內(nèi)部的函數(shù)和變量的引用,我們才得以利用。
各種書對于閉包的解釋:
《權(quán)威指南》:函數(shù)對象通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)內(nèi)部變量都可以保持在函數(shù)的作用域中,有權(quán)訪問另一個函數(shù)作用域中的變量
《忍者秘籍》:一個函數(shù)創(chuàng)建時允許自身訪問并操作該自身函數(shù)以外的變量所創(chuàng)建的作用域
《你不知道的js》:是基于詞法的作用域書寫代碼時所產(chǎn)生的結(jié)果,當(dāng)函數(shù)記住并訪問所在的詞法作用域,閉包就產(chǎn)生了
閉包的產(chǎn)生,會導(dǎo)致內(nèi)存泄漏。
前面已經(jīng)說到,js具有垃圾回收機制,如果發(fā)現(xiàn)變量被不使用將會被回收,而閉包相互引用,讓他不會被回收,一直占據(jù)著一塊內(nèi)存,長期持有一塊內(nèi)存的引用,所以導(dǎo)致內(nèi)存泄漏。

var b = 10
function a(){
    var b = 1
    return function c(){//暴露內(nèi)部函數(shù)的接口
        console.log(b)
    }
}
a()()//1,外部拿到內(nèi)部的引用,臨時開辟的內(nèi)存空間不會被回收

//改寫成IIFE形式
var b = 10
var a = (function(){
    var b = 1
    return function c(){
        console.log(b)
    }
})()
a()//1

//改成window對象的一個引用
var b = 10
(function(){
    var b = 1
    window.c =  function(){
        console.log(b)
    }
})()
c()//1

//多個閉包
function a(){
    var s = 1
    return function count(){
        s++
        console.log(s)
    }
}
var b = a()//相當(dāng)于賦值
var c = a()
b()//2
b()//3
c()//2,各自保持各自的”賦值結(jié)果”,互相不干擾

//r被垃圾回收
function a(){
        var r = 1
    var s = 1
    return function count(){
        s++
        console.log(s)
    }
}
var b = a()//我們可以打個斷點,在谷歌瀏覽器看他的調(diào)用棧,發(fā)現(xiàn)閉包里面沒有r了

對于最后一個例子,r、s并不是像一些人認為的那樣,有閉包了,r、s都會留下,其實是r已經(jīng)被回收了。在執(zhí)行的函數(shù)時候,將會為這個函數(shù)創(chuàng)建一個上下文ctx,最開始這個ctx是空的,從上到下執(zhí)行到函數(shù)a的閉包聲明b時,由于b函數(shù)依賴變量s ,因此會將 s 加入b的ctx——ctx2。a內(nèi)部所有的閉包,都會持有這個ctx2。(所以說,閉包之所以閉包,就是因為持有這個ctx)
每一個閉包都會引用其外部函數(shù)的ctx(這里是b的ctx2),讀取變量s的時候,被閉包捕捉,加入ctx中的變量,接著被分配到堆。而真正的局部變量是r ,保存在棧,當(dāng)b執(zhí)行完畢后出棧并且被垃圾回收。而a的ctx被閉包引用,如果有任何一個閉包存活,他對應(yīng)的ctx都將存活,變量也不會被銷毀。

我們也聽說一句話,盡量避免全局變量。其實也是這樣的道理,一個函數(shù)返回另一個函數(shù),也就是分別把兩個函數(shù)按順序壓入調(diào)用棧。我們知道棧是先進后出,那全局的變量(也處于棧底),越是不能得到垃圾回收,存活的時間越長。但也許全局變量在某個時候開始就沒有作用了,就不能被回收,造成了內(nèi)存泄漏。所以又引出另一個常見的注意事項:不要過度利用閉包。用得越多,棧越深,變量越不能被回收。

瀏覽器的全局對象為window,關(guān)閉瀏覽器自然一切結(jié)束。Node中全局對象為global,如果global中有屬性已經(jīng)沒有用處了,一定要設(shè)置為null,因為只有等到程序停止運行,才會銷毀。而我們的服務(wù)器當(dāng)然是長期不關(guān)機的,內(nèi)存泄漏積少成多,爆內(nèi)存是早晚的事情。

Node中,當(dāng)一個模塊被引入,這個模塊就會被緩存在內(nèi)存中,提高下次被引用的速度(緩存代理)。一般情況下,整個Node程序中對同一個模塊的引用,都是同一個實例(instance),這個實例一直存活在內(nèi)存中。所以,如果任意模塊中有變量已經(jīng)不再需要,最好手動設(shè)置為null,不然會白白占用內(nèi)存

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94417.html

相關(guān)文章

  • 云天視角-淺談閉包

    摘要:函數(shù)在執(zhí)行的時候執(zhí)行函數(shù),將當(dāng)前的變量對象由于當(dāng)前的環(huán)境是函數(shù),所以將其活動對象作為變量對象添加到作用域鏈的前端。此時,由于在執(zhí)行,而作用域鏈也存在,所以可以在作用域鏈上進行查找,去訪問的變量。 一、現(xiàn)狀 閉包是jser繞不過的坎,一直在都在說,套用 simpson 的話來說:JavaScript中閉包無處不在,你只需要能夠識別并擁抱它。 閉包是基于詞法作用域書寫代碼時的自然結(jié)果,你甚...

    nanfeiyan 評論0 收藏0
  • 淺談Javascript閉包中作用域及內(nèi)存泄漏問題

    摘要:將作用域賦值給變量這里的作用域是,而不是將作用域賦值給一個變量閉包返回瀏覽器中內(nèi)存泄漏問題大家都知道,閉包會使變量駐留在內(nèi)存中,這也就導(dǎo)致了內(nèi)存泄漏。 上一章我們講了匿名函數(shù)和閉包,這次我們來談?wù)勯]包中作用域this的問題。 大家都知道,this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的,如果this在全局就是[object window],如果在對象內(nèi)部就是指向這個對象,而閉包卻是在運行...

    source 評論0 收藏0
  • 淺談對JavaScript閉包的理解

    摘要:關(guān)于循環(huán)和閉包當(dāng)循環(huán)和閉包結(jié)合在一起時,經(jīng)常會產(chǎn)生讓初學(xué)者覺得匪夷所思的問題。閉包是一把雙刃劍是比較難以理解和掌握的部分,它十分強大,卻也有很大的缺陷,如何使用它完全取決于你自己。 在談閉包之前,我們首先要了解幾個概念: 什么是函數(shù)表達式? 與函數(shù)聲明有何不同? JavaScript查找標(biāo)識符的機制 JavaScript的作用域是詞法作用域 JavaScript的垃圾回收機制 先來...

    missonce 評論0 收藏0
  • 淺談JavaScript中的閉包

    摘要:在內(nèi)部,理所當(dāng)然能訪問到局部變量,但當(dāng)作為的返回值賦給外的全局變量時,神奇的事情發(fā)生了在全局作用域中訪問到了,這就是閉包。而閉包最神奇的地方就是能在一個函數(shù)外訪問函數(shù)中的局部變量,把這些變量用閉包的形式放在函數(shù)中便能避免污染。 一、閉包是什么? 《JavaScript高級程序設(shè)計》中寫道:閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù),如果用下定義的觀點看,這句話就是說閉包是函數(shù),我...

    Riddler 評論0 收藏0
  • 淺談reacthooks閉包陷阱

      本文不會過多講解基礎(chǔ)知識,更多說的是在使用useRef如何能擺脫 這個 閉包陷阱 ?  react hooks 的閉包陷阱 基本每個開發(fā)員都有遇見,這是很令人抓狂的。  (以下react示范demo,均為react 16.8.3 版本)  列一個具體的場景:  functionApp(){   const[count,setCount]=useState(1);   useEffect(()=...

    3403771864 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<