一,數(shù)據(jù)類型
1,基本數(shù)據(jù)類型(值類型):
number,string,boolean,null,undefined。
2,引用數(shù)據(jù)類型:
對象,數(shù)組,函數(shù)。
如:{ },[ ], 日期對象new Date(), Math,實例對象...
3,ES6中新增,Symbol(唯一值)
4,ES11(ES2020)新增,bigInt(一種方法來表示大于 2?3-1 的整數(shù),BigInt 可用于任意大整數(shù))。
例如:let a = Symbol('1')
? ? ? ? ? let b = Symbol('1')
? ? ? ? ? a != b? // true
另:NaN == NaN? // false,NaN和誰都不相等。
二,變量
創(chuàng)建一個變量會有三步操作
例如:let a = 1
1,創(chuàng)建變量:聲明 let a
2,創(chuàng)建值: 基本值直接在棧中創(chuàng)建和存儲? 1
3,讓變量和值關(guān)聯(lián)起來(賦值):定義 defined。當然未賦值是undefined(ps:終于知道undefined怎么來的了)。
在第2步中,當引用值是復雜的結(jié)構(gòu)時特殊處理。
如:let obj = { a: 1 }
function Fn(){ let a = 1 }
(ps:函數(shù)也是變量,和let創(chuàng)建的變量本質(zhì)上是一樣的,區(qū)別是存儲的值是個函數(shù)類型的值。)
1,開辟一個存儲對象中鍵值對(存儲函數(shù)中的代碼)的內(nèi)存空間“堆內(nèi)存”。
2,所有的堆內(nèi)存都有一個可被后續(xù)查找的16進制地址。
3,后續(xù)關(guān)聯(lián)賦值的時候,是把堆內(nèi)存地址給與變量操作。
如下圖所示存儲:
問題:
1,let 和 var的區(qū)別?
let 語句創(chuàng)建了自己的作用域,這個作用域里的變量與外面的變量無關(guān)。
2,變量提升是怎么回事?
JavaScript會把作用域里的所有變量和函數(shù)提到函數(shù)的頂部聲明,帶著這個原理看以下的例子。
上圖中的值是undefined說明變量已經(jīng)提前聲明,只是沒有被賦值。由此解釋,變量提升指的是變量聲明的提升,不會提升變量的初始化和賦值。
let的情況不同:
瀏覽器認為c沒有聲明,是否意味著沒有變量提升呢?再看一個例子,
這里同樣認為c沒有聲明,但是,如果c沒有變量提升,執(zhí)行到console.log時應該是輸出全局作用域中的c,而不是出現(xiàn)錯誤。
可以推知let也出現(xiàn)了變量提升。出現(xiàn)這種情況我們稱為“暫時性死區(qū)”(temporal dead zone,簡稱 TDZ),在代碼塊內(nèi),使用let、const命令聲明變量之前,該變量都是不可用的。
3,函數(shù)會被優(yōu)先提升到上面
4,語句塊是沒有局部作用域的,在下面的語句塊中,與外部是完全一致的。if里面的變量和函數(shù)也會被提升到函數(shù)頂部
三,js引擎是怎么執(zhí)行代碼的
js引擎想要執(zhí)行代碼,一定會創(chuàng)建一個執(zhí)行棧ECStack(執(zhí)行上下文環(huán)境棧)。
棧內(nèi)存,我們簡單的理解為執(zhí)行代碼所用,那么函數(shù)執(zhí)行就是個進棧和出棧的過程。
步驟如下:
1,把創(chuàng)建的上下文壓縮到棧中執(zhí)行 => 進棧
2,執(zhí)行完有的上下文就沒用了 => 出棧
3,有的還要用,會把它壓縮到棧底 => 閉包
我們執(zhí)行下面函數(shù)分析其過程:
四,閉包的理解
《JavaScript高級程序設計》里面對閉包的定義是,閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。對定義中的另一個函數(shù)作用域理解可能有點模糊。
而上面三中簡單的說了下當函數(shù)進棧執(zhí)行后,還要用到該函數(shù)不能出棧,就形成閉包。結(jié)合《JavaScript高級程序設計》分析閉包,如圖:
五,關(guān)于作用域
創(chuàng)建一個函數(shù)的時候,會創(chuàng)建一個堆,存儲代碼字符串和對應的鍵值對,初始化當前函數(shù)的作用域[[scope]] = 所在上下文EC中的變量對象VO/AO。
執(zhí)行函數(shù)的時候,會創(chuàng)建一個新的執(zhí)行上下文EC,初始化this的指向,初始化作用域鏈[[scopeChain]]:<AO(A), VO(G)>,創(chuàng)建AO變量對象來存儲變量。(對比上圖四)
六,this執(zhí)行主體的簡單判斷
第一種,函數(shù)執(zhí)行,看前面是否有“點”,有點前面是誰this就是誰 如:obj.fn()? ?this->obj, obj._proto_.fn? this->obj._proto
沒有點,this是window(嚴格模式下是undefined)? 相當window.fn()? ?this->window
自執(zhí)行函數(shù)一般this都是window。
第二種,給元素的事件行為綁定方法(DOM0/DOM2),事件觸發(fā),方法會執(zhí)行,此時方法中的this一般都是當前元素本身,如:
? box.onclick = function() {
this-> box
? }
? box.addEventListener("click", function(){
this->box
? })
七,原型和原型鏈
原型(prototype),每個函數(shù)都有一個prototype屬性,他默認指向一個Object空對象,原形對象有個constructor, 他指向函數(shù)對象。
我們給原型對象添加屬性(一般是方法)時,函數(shù)的所有實例對象會自動擁有原型中的屬性。
例如:
function A() {}? ? //? 內(nèi)部語句:this.prototype = {}
A.prototype.test= function() {
? console.log(test)
}
let a? = new A()? //? 內(nèi)部語句:this(fn).__proto__ = Fn.prototype
a.test()? // test
原型有顯式原型和隱式原型。
每個函數(shù)都有個prototype,即顯示原型。在定義函數(shù)時自動添加,默認值是一個空Object對象。
每個實例對象都有一個__proto__,稱隱式原型。在創(chuàng)建對象時自動添加,默認值是構(gòu)造函數(shù)的prototype屬性。函數(shù)也是對象,故也創(chuàng)建了__proto__,下圖所示
對象的隱式原型的值為其對應構(gòu)造函數(shù)的顯示原型的值,fn.__proto__ === Fn.prototype
在ES6之前,可以直接操作顯式原型,但不能直接操作隱式原型。
原型鏈,訪問一個對象時,先在自身屬性中查找,找到返回。如果沒找到,沿__proto__這條鏈向上查找,找到返回。如果最終沒找到,返回undefined。所以別名又叫隱式原型鏈 。
作用:查找對象的屬性和方法(備注:讀取對象屬性值是,會自動查找原型鏈;如果是設置對象屬性時,不會查找原型鏈)
我們執(zhí)行下面函數(shù)分析其查找過程:(備注:創(chuàng)建對象 let obj = {}相當 new Object(),function Fn(){}相當 Fn = new Function() )
理解上圖查找后可以對照下面的原型鏈萬能表加深理解:
八,js事件循環(huán)
先引進概念:
1,js是單線程的,也就是說,同一個時間只能做一件事。(ps:為啥是單線程?假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準)。
2,瀏覽器是多線程的:
3,setTimeout的執(zhí)行:setTimeout在棧中執(zhí)行后觸發(fā)瀏覽器的定時觸發(fā)器線程,定時器觸發(fā)線程倒計時在setTimeout設置的時間(如300ms)后把回調(diào)函數(shù)里面的數(shù)據(jù)加到消息隊列中。
4,回調(diào)函數(shù)的執(zhí)行:回調(diào)函數(shù)會加到微任務隊列中等待執(zhí)行。如:promise執(zhí)行后會把then的回調(diào)函數(shù)加到微任務隊列中。
5,先執(zhí)行宏任務,在執(zhí)行微任務隊列,最后執(zhí)行消息隊列。
js事件循環(huán)機制解決單線程阻塞問題。那js是怎么處理異步(setTimeout)和回調(diào)函數(shù)(promise.then(()=>{...}))的呢?以下面代碼例子:
。。。如理解有誤望指點學習。