1.數(shù)據(jù)類型
1.1概念篇
7種原始數(shù)據(jù)類型
- boolean
- string
- number
- undefined
- null
- Symbol
- bigInt
引用類型
- 對象Object
- 普通對象-Object
- 數(shù)組對象-Array
- 正則對象-RegExp
- 日期對象-Date
- 數(shù)學(xué)函數(shù)-Math
- 函數(shù)對象-Function
- 基本包裝類型 Boolean String Number
null是對象嗎登淘?為什么?
- 結(jié)論: null不是對象
- 解釋: 雖然 typeof null 會輸出 object,但是這只是 JS 存在的一個悠久 Bug三妈。JS在運(yùn)行之前編譯成二進(jìn)制形式陪腌,在 JS 的最初版本中使用的是 32 位系統(tǒng)辱魁,為了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象然而 null 表示為全零诗鸭,所以將它錯誤的判斷為 object 染簇。
1 .toString()或者(1).toString為什么可以調(diào)用?
- 數(shù)字后面的第一個點(diǎn)會被解釋為小數(shù)點(diǎn)强岸,而不是點(diǎn)調(diào)用锻弓。只不過不推薦這種使用方法,而且這樣做也沒什么意義
- 基本包裝類型:為什么基本類型卻可以直接調(diào)用引用類型的方法呢蝌箍?其實(shí)是js引擎在解析上面的語句的時候青灼,會把這三種基本類型解析為包裝對象(就是下面的new String()),而包裝對象是引用類型可以調(diào)用Object.prototype上的方法妓盲。大概過程如下:
0.1+0.2為什么不等于0.3杂拨?
- 在JS中數(shù)字采用的IEEE 754的雙精度標(biāo)準(zhǔn)進(jìn)行存儲,到這里我們都理解只要采取IEEE 754 FP的浮點(diǎn)數(shù)編碼的語言均會出現(xiàn)上述問題悯衬,只是它們的標(biāo)準(zhǔn)類庫已經(jīng)為我們提供了解決方案而已
- 而對于像0.1這樣的數(shù)值用二進(jìn)制表示你就會發(fā)現(xiàn)無法整除弹沽,最后算下來會是 0.000110011….由于存儲空間有限(雙精度是64位存儲空間),最后根據(jù)IEEE 754的規(guī)則會舍棄后面的數(shù)值,所以我們最后就只能得到一個策橘,此時就已經(jīng)出現(xiàn)了精度的損失
- 簡單理解 0.1和02不能被二進(jìn)制浮點(diǎn)數(shù)精確表示
- 在0.1 + 0.2這個式子中炸渡,0.1和0.2都是近似表示的,在他們相加的時候丽已,兩個近似值進(jìn)行了計(jì)算偶摔,導(dǎo)致最后得到的值是0.30000000000000004,此時對于JS來說促脉,其不夠近似于0.3)辰斋,于是就出現(xiàn)了0.1 + 0.2 != 0.3 這個現(xiàn)象
既然十進(jìn)制0.1不能被二進(jìn)制浮點(diǎn)數(shù)精確存儲,那么為什么console.log(0.1)打印出來的確確實(shí)實(shí)是0.1這個精確的值瘸味?
- 實(shí)際IEEE 754標(biāo)準(zhǔn)就是采用一套規(guī)則去近視于0宫仗。1 雖然無法精確存儲,但是可以用一個近視值去表示旁仿,比如:0.100000000000000002 ==
0.100000000000000010 // true - 當(dāng)64bit的存儲空間無法存儲完整的無限循環(huán)小數(shù)藕夫,而IEEE 754 Floating-point采用round to nearest, tie to even的舍入模式,因此0.1實(shí)際存儲時的位模式是0-01111111011-1001100110011001100110011001100110011001100110011010枯冈;
解決浮點(diǎn)數(shù)運(yùn)算精度
- 換算成整數(shù)進(jìn)行運(yùn)算毅贮,整數(shù)運(yùn)算就不存在精度缺失問題,??我們可以把需要計(jì)算的數(shù)字升級(乘以10的n次冪)成計(jì)算機(jī)能夠精確識別的整數(shù),等計(jì)算完成后再進(jìn)行降級(除以10的n次冪)尘奏,這是大部分編程語言處理精度問題常用的方法滩褥。例如:
- 但是換算也是浮點(diǎn)數(shù)運(yùn)算的操作,同樣也會存在問題炫加,所以解決的方法就是采用字符串形式進(jìn)行換算 比如:3.14===> {times: 100, num: 314} ===>有點(diǎn)類似大數(shù)相加的原理
- 利用第三方庫:Math.js瑰煎,decimal.js
解題思路:
二進(jìn)制換算后(不會出現(xiàn)循環(huán)被截?cái)啵@是前提條件俗孝,這樣換算后值落在這個區(qū)間就保證了精度無誤酒甸,所以我們想辦法使運(yùn)算的數(shù)字落在這個區(qū)間 這個就是解搭問題的關(guān)鍵)由于僅位于Number.MIN_SAFE_INTEGER和Number.MAX_SAFE_INTEGER間的整數(shù)才能被精準(zhǔn)地表示,也就是只要保證運(yùn)算過程的操作數(shù)和結(jié)果均落在這個閥值內(nèi)赋铝,那么運(yùn)算結(jié)果就是精準(zhǔn)無誤的
問題的關(guān)鍵落在如何將小數(shù)和極大數(shù)轉(zhuǎn)換或拆分為Number.MIN_SAFE_INTEGER至Number.MAX_SAFE_INTEGER閥值間的數(shù)了
小數(shù)轉(zhuǎn)換為整數(shù)插勤,自然就是通過科學(xué)計(jì)數(shù)法表示,并通過右移小數(shù)點(diǎn)革骨,減小冪的方式處理农尖;(如0.000123 等價于 123 * 10-6)
Number.EPSILON實(shí)際上是 JavaScript 能夠表示的最小精度。誤差如果小于這個值苛蒲,就可以認(rèn)為已經(jīng)沒有意義了卤橄,即不存在誤差了。
可以這樣判斷:0.1+0.2-0.3<Number.EPSILON
1.2 檢測篇
typeof 是否能正確判斷類型臂外?
- 對于原始類型來說窟扑,除了 null 都可以調(diào)用typeof顯示正確的類型喇颁。
- 但對于引用數(shù)據(jù)類型,除了函數(shù)之外嚎货,都會顯示"object"橘霎。
instanceof能判斷基本數(shù)據(jù)類型
Object.is和===的區(qū)別?
- Object在嚴(yán)格等于的基礎(chǔ)上修復(fù)了一些特殊情況下的失誤殖属,具體來說就是+0和-0(false)姐叁,NaN和NaN(true)。
Object.prototype.toString
1.3 轉(zhuǎn)換篇
JS中類型轉(zhuǎn)換有哪幾種洗显?
- 轉(zhuǎn)換成數(shù)字
- 轉(zhuǎn)換成布爾值
- 轉(zhuǎn)換成字符串
== 和 ===有什么區(qū)別墨林?
===叫做嚴(yán)格相等厅目,是指:左右兩邊不僅值要相等,類型也要相等,例如'1'===1的結(jié)果是false菩收,因?yàn)橐贿吺莝tring船惨,另一邊是number障贸。
-
==不像===那樣嚴(yán)格胧辽,對于一般情況,只要值相等俄讹,就返回true哆致,但==還涉及一些類型轉(zhuǎn)換,它的轉(zhuǎn)換規(guī)則如下:(比較最終都是轉(zhuǎn)化為數(shù)字的)
- 兩邊的類型是否相同患膛,相同的話就比較值的大小摊阀,例如1==2,返回false - 判斷的是否是null和undefined剩瓶,是的話就返回true - 判斷的類型是否是String和Number驹溃,是的話,把String類型轉(zhuǎn)換成Number延曙,再進(jìn)行比較 - 判斷其中一方是否是Boolean,是的話就把Boolean轉(zhuǎn)換成Number亡哄,再進(jìn)行比較 - 如果其中一方為Object枝缔,且另一方為String、Number或者Symbol蚊惯,會將Object轉(zhuǎn)換成字符串愿卸,再進(jìn)行比較
對象轉(zhuǎn)原始類型是根據(jù)什么流程運(yùn)行的
- 如果存在Symbol.toPrimitive()方法,優(yōu)先調(diào)用再返回
- 調(diào)用valueOf()截型,如果轉(zhuǎn)換為原始類型趴荸,則返回
- 調(diào)用toString(),如果轉(zhuǎn)換為原始類型宦焦,則返回
- 如果都沒有返回原始類型发钝,會報(bào)錯
如何讓if(a == 1 && a == 2)條件成立顿涣?
- 其實(shí)就是上一個問題的應(yīng)用。
var a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
console.log(a == 1 && a == 2);// true
2.拷貝
2.1 淺拷貝:shallowClone
- 一個新的對象直接拷貝已存在的對象的對象屬性的引用酝豪,即淺拷貝涛碑。(對象的屬性)
- Object.assign
- ...展開運(yùn)算符
- concat淺拷貝數(shù)組
- slice淺拷貝
2.2 深拷貝:deepClone
- 深拷貝會另外拷貝一份一個一模一樣的對象,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,新對象跟原對象不共享內(nèi)存,修改新對象不會改到原對象孵淘。
JSON.parse(JSON.stringify());
- 無法解決循環(huán)引用的問題蒲障。舉個例子:
- 無法拷貝一寫特殊的對象,諸如 RegExp, Date, Set, Map等
- 無法拷貝函數(shù)(劃重點(diǎn))
動手實(shí)現(xiàn)一個深拷貝
-
普通類型
- 直接返回就行
-
引用類型
- 循環(huán)引用
-
解決循環(huán)引用問題瘫证,我們可以額外開辟一個存儲空間揉阎,來存儲當(dāng)前對象和拷貝對象的對應(yīng)關(guān)系,當(dāng)需要拷貝當(dāng)前對象時背捌,先去存儲空間中找毙籽,有沒有拷貝過這個對象,如果有的話直接返回载萌,如果沒有的話繼續(xù)拷貝惧财,這樣就巧妙化解的循環(huán)引用的問題。
-
可遍歷類型
- Set - Map - Array - Object
-
不可遍歷類型(考慮基本包裝類型(引用類型))
- Boolean - Number - String
- Date
- Unix時間戳:value
一個 Unix 時間戳(Unix Time Stamp)扭仁,它是一個整數(shù)值垮衷,表示自1970年1月1日00:00:00 UTC(the Unix epoch)以來的毫秒數(shù),忽略了閏秒乖坠。請注意大多數(shù) Unix 時間戳功能僅精確到最接近的秒搀突。
- 重新生成一個Date實(shí)例,參數(shù)傳入一個Unix
- Unix時間戳:value
- Date
Error
-
Symbol
- Object(Symbol('foo'))
- es6過后就不提倡用new 直接類似Symbol(xxx)這樣執(zhí)行就行
-
WeakMap熊泵、WeakSet仰迁、ArrayBuffer對象、TypedArray視圖和DataView視圖顽分、Float32Array徐许、Float64Array、Int8Array
Blob卒蘸、File雌隅、FileList、ImageData
拷貝函數(shù)
- lodash對函數(shù)的處理 因?yàn)榭截惡瘮?shù)沒有啥意義
- 函數(shù)(prototype來區(qū)分下箭頭函數(shù)和普通函數(shù)缸沃,箭頭函數(shù)是沒有prototype)
- 箭頭函數(shù)
- 我們可以直接使用eval和函數(shù)字符串來重新生成一個箭頭函數(shù)恰起,注意這種方法是不適用于普通函數(shù)的。
- 非箭頭函數(shù)
- 分別使用正則取出函數(shù)體和函數(shù)參數(shù)趾牧,然后使用new Function ([arg1[, arg2[, ...argN]],] functionBody)構(gòu)造函數(shù)重新構(gòu)造一個新的函數(shù)
賦值
基本數(shù)據(jù)類型:賦值检盼,賦值之后兩個變量互不影響
引用數(shù)據(jù)類型:賦址,兩個變量具有相同的引用翘单,指向同一個對象吨枉,相互之間有影響
-
為什么需要淺拷貝和深拷貝蹦渣?
- 對引用類型進(jìn)行賦址操作,兩個變量指向同一個對象东羹,改變變量 a 之后會影響變量 b剂桥,哪怕改變的只是對象 a 中的基本類型數(shù)據(jù)
- 通常在開發(fā)中并不希望改變變量 a 之后會影響到變量 b,這時就需要用到淺拷貝和深拷貝属提。
- 這就是為什么需要淺拷貝和深拷貝的緣由权逗,因?yàn)槲覀冊谫x值操作時候,操作引用類型的時候不想b影響a,所以需要淺拷貝或者深拷貝冤议,這也從中可以看出賦值與拷貝深拷貝的區(qū)別
- 通常深淺拷貝是解決引用類型之間互相影響的斟薇,要明白這點(diǎn)
??當(dāng)我們進(jìn)行賦值,考慮到引用類型賦值完做修改會相互影響恕酸,就引出了對應(yīng)的深淺拷貝方案去解決
this指向
其實(shí)JS中的this是一個非常簡單的東西堪滨,只需要理解它的??執(zhí)行規(guī)則就行
顯示綁定
- call
- apply
- bind
隱式綁定
-
全局上下文
- 全局上下文默認(rèn)this指向window, 嚴(yán)格模式下指向undefined。
-
直接調(diào)用函數(shù)
- this相當(dāng)于全局上下文的情況
-
對象.方法的形式調(diào)用
- 誰調(diào)用這個方法蕊温,它就指向誰
-
DOM事件綁定(特殊)
- onclick和addEventerListener中 this 默認(rèn)指向綁定事件的元素袱箱。
IE比較奇異,使用attachEvent义矛,里面的this默認(rèn)指向window发笔。
-
new構(gòu)造函數(shù)綁定
- 此時構(gòu)造函數(shù)中的this指向?qū)嵗龑ο?/li>
-
箭頭函數(shù)
- 箭頭函數(shù)沒有this, 因此也不能綁定。里面的this會指向當(dāng)前最近的非箭頭函數(shù)的this凉翻,找不到就是window(嚴(yán)格模式是undefined)
JS數(shù)組
函數(shù)的arguments為什么不是數(shù)組了讨?如何轉(zhuǎn)化成數(shù)組?
-
常見的類數(shù)組
- 用getElementsByTagName/ClassName()獲得的HTMLCollection
- 用querySelector獲得的nodeList
-
轉(zhuǎn)換成數(shù)組
- Array.prototype.slice.call()
- Array.from()
- ES6展開運(yùn)算符
- 利用concat+apply
forEach中return有效果嗎制轰?如何中斷forEach循環(huán)前计?
在forEach中用return不會返回,函數(shù)會繼續(xù)執(zhí)行垃杖。
-
中斷方法:
- 使用try監(jiān)視代碼塊男杈,在需要中斷的地方拋出異常
- 官方推薦方法(替換方法):用every和some替代forEach函數(shù)。every在碰到return false的時候调俘,中止循環(huán)势就。some在碰到return true的時候,中止循環(huán) (面試問到團(tuán)隊(duì)規(guī)范?)
JS判斷數(shù)組中是否包含某個值
- array.indexOf
- array.includes(searcElement[,fromIndex]) 推薦?
- array.find(callback[,thisArg])
- array.findeIndex(callback[,thisArg])
JS中flat---數(shù)組扁平化
- 遞歸
- reduce+遞歸
- 原型鏈上的flat方法(數(shù)組實(shí)例上的方法) [1,2,3].flat(2)
JS數(shù)組的高階函數(shù)
什么是高階函數(shù):一個函數(shù)就可以接收另一個函數(shù)作為參數(shù)或者返回值為一個函數(shù)脉漏,這種函數(shù)就稱之為高階函數(shù)。
-
數(shù)組中的高階函數(shù)
- map
- reduce
- filter
- sort
JS如何實(shí)現(xiàn)繼承
什么是繼承
- 繼承是使用已存在的類的定義作為基礎(chǔ)建立新類的技術(shù)袖牙,新類的定義可以增加新的數(shù)據(jù)或新的功能侧巨,也可以用父類的功能,但不能選擇性地繼承父類鞭达。通過使用繼承我們能夠非常方便地復(fù)用以前的代碼司忱,能夠大大的提高開發(fā)的效率
第一種: 借助call(構(gòu)造函數(shù)式繼承
)
第二種: 借助原型鏈(原型鏈繼承)
第三種:將前兩種組合(組合繼承)
第四種:原型式繼承(如果我要繼承的父類是一個普通對象而不是構(gòu)造函數(shù)(因?yàn)镴avaScript 語言中皇忿,生成實(shí)例對象的傳統(tǒng)方法是通過構(gòu)造函數(shù)),那么如何實(shí)現(xiàn))
- Object.create方法
第五種:寄生繼承
- 核心:在原型式繼承的基礎(chǔ)上坦仍,增強(qiáng)對象鳍烁,返回構(gòu)造函數(shù)(類似工廠函數(shù)進(jìn)行包裝)
第六種:寄生組合繼承
原型鏈
原型對象和構(gòu)造函數(shù)有何關(guān)系?
- 在JavaScript中繁扎,每當(dāng)定義一個函數(shù)數(shù)據(jù)類型(普通函數(shù)幔荒、類)時候,都會天生自帶一個prototype屬性梳玫,這個屬性指向函數(shù)的原型對象爹梁。
- 當(dāng)函數(shù)經(jīng)過new調(diào)用時,這個函數(shù)就成為了構(gòu)造函數(shù)提澎,返回一個全新的實(shí)例對象姚垃,這個實(shí)例對象有一個proto屬性,指向構(gòu)造函數(shù)的原型對象盼忌。
能不能描述一下原型鏈
- 首先要明白實(shí)例的proto屬性與構(gòu)造函數(shù)的protype屬性都是指向原型對象积糯,原型對象的constructor屬性又是指向構(gòu)造函數(shù)
- JavaScript對象通過proto 指向父類的原型對象,直到指向Object的原型對象為止谦纱,這樣就形成了一個原型指向的鏈條, 即原型鏈看成。
- 對象的 hasOwnProperty() 來檢查對象自身中是否含有該屬性
- 使用 in 檢查對象中是否含有某個屬性時,如果對象中沒有但是原型鏈中有服协,也會返回 true
DOM事件
綁定事件的?法
- HTML的內(nèi)聯(lián)屬性
- 元素的onXXX屬性添加事件
-
- addEventListener
-
標(biāo)準(zhǔn)方法
-
el.addEventListener(eventNam
e, handle, useCapture |
options)- {String} eventName 事件名稱
- {Function} handle 事件函數(shù)
- {Boolean} useCapture 是否在事
件捕獲階段觸發(fā)事件绍昂,true 代表
捕獲階段觸發(fā),false 代表在冒
泡階段觸發(fā) - {Object} options 選項(xiàng)對象
el.removeEventListener(eventN
ame, handle)
-
-
IE?法
- el.attachEvent(eventName,
handle) - el.detachEvent(eventName,
handle)
- el.attachEvent(eventName,
-
對?:
- 由于IE8不?持 事件捕獲 偿荷,所以
通過 attachEvent/detachEvent
綁定的時間也只能在 冒泡階段
觸發(fā) - 通過 attachEvent/detachEvent
綁定的事件函數(shù)會在全局作?域
中運(yùn)?窘游,即: this === window - 通過 attachEvent/detachEvent
綁定的事件函數(shù)以綁定時的先后
順序 “倒序” 被執(zhí)? - attachEvent/detachEvent 的第
?個參數(shù)要在事件名稱前?加
'on'
- 由于IE8不?持 事件捕獲 偿荷,所以
-
評價
- 違反最佳實(shí)踐
- 由于只能賦值?個handler,
因此會存在覆蓋的問題
- 由于只能賦值?個handler,
- 調(diào)?addEventListener時跳纳,要
注意銷毀組件時回收handler忍饰,
removeEventListener。但是這
樣的話寺庄,handler?必須??個變
量保持引?
- 調(diào)?addEventListener時跳纳,要
事件對象
-
標(biāo)準(zhǔn)
-
屬性
currentTarget:currentTarget
的值始終等于 this艾蓝,即指向事件
所綁定到的元素target:真正觸發(fā)事件的元素
bubbles:表示事件是否冒泡
cancelable:是否可以取消默認(rèn)
?為defaultPrevented:為真則被調(diào)
?了preventDefault()detail:描述事件的細(xì)節(jié)
-
eventPhase:描述事件處理函數(shù)
的階段- 1:捕獲
- 2:處于?標(biāo)
- 3:冒泡
trusted:為真則是瀏覽器原?事
件,為假則是?動添加的事件type:事件類型
-
方法
event.preventDefault():阻?默
認(rèn)事件event.stopIPropagation():阻?
冒泡 也可以阻止捕獲(根據(jù)dom事件流 捕獲階段被阻止了 處于目標(biāo)階段和事件冒泡也不會被觸發(fā)了)-
stopImmediatePropagation 既能阻止事件向父元素冒泡斗塘,也能阻止元素同事件類型的其它監(jiān)聽器被觸發(fā)赢织。而 stopPropagation 只能實(shí)現(xiàn)前者的效果
- react使用合成事件,如果出現(xiàn)點(diǎn)擊空白區(qū)域彈框消失馍盟,可以利用stopImmediatePropagation阻止事件的其它函數(shù)執(zhí)行 (只執(zhí)行我這個事件的回調(diào)函數(shù),其它不執(zhí)行)因?yàn)槲叶济芭莸絛ocument上了于置,阻止冒泡沒什么用了,另外一種解決方法關(guān)閉操作注冊到window上
- e.nativeEvent.stopImmediatePropagation 這個解決問題的不是阻止冒泡 而是不允許其他的事件回調(diào)觸發(fā) 因?yàn)槲覀冞@時候事件已經(jīng)冒泡到document上了 為document再綁定了一個click事件 此時我們想觸發(fā)按鈕這個click事件(實(shí)際綁定到document上)觸發(fā)以后贞岭,不允許再觸發(fā)其他document上click的事件回調(diào)函數(shù)
-
-
IE
-
屬性
- srcElement:與target的值相同
- returnValue:默認(rèn)為真八毯,若設(shè)置
為false搓侄,可以阻?默認(rèn)事件 - cancelBubble:默認(rèn)為假,設(shè)置
為true可以阻?冒泡
-
方法
- el.onclick = function () {
window.event
} - el.attachEvent:回調(diào)中的event
可以為傳?的event參數(shù)也可為
window.event
- el.onclick = function () {
-
DOM事件流(統(tǒng)一這兩種事件流)
事件流:描述的是從頁面中接收事件的順序话速。但有意思的是
-
執(zhí)行的三個階段
-
事件捕獲
- 當(dāng)事件發(fā)生時讶踪,首先發(fā)生的是事件捕獲,為父元素截獲事件提供了機(jī)會
-
處于?標(biāo)
- 事件到了具體元素時泊交,在具體元素上發(fā)生乳讥,并且被看成冒泡階段的一部分。
-
事件冒泡
- 冒泡階段發(fā)生活合,事件開始冒泡
-
-
注意點(diǎn)
- DOM事件流確實(shí)會按照這三個階段執(zhí)行雏婶,我們可以通過addEventListener注冊事件時候指定useCapture的值來規(guī)定事件在捕獲階段還是冒泡階段中執(zhí)行(如果該對象是目標(biāo)對象,則會在目標(biāo)階段執(zhí)行)
- 你會注意到按照DOM事件流這種執(zhí)行順序白指,事件不會被觸發(fā)兩次吧留晚,造成重復(fù)觸發(fā),并不是的告嘲,我們可以有選擇是在冒泡階段觸發(fā)還是捕獲階段错维,默認(rèn)是冒泡階段
- // 這段代碼表示該click事件會在事件捕獲階段執(zhí)行(??注意得判斷是不是目標(biāo)對象,如果是目標(biāo)對象就是表示它在處于目標(biāo)這個階段執(zhí)行)
// 如何判斷是否是目標(biāo)對象:最具體的元素(文檔中嵌套層次最深的那個節(jié)點(diǎn))
document.querySelector("#button").addEventListener(
"click",
function () {
console.log("處于目標(biāo)button click");
},
true
);
多種事件
-
UI 事件
-
load
- window上觸發(fā):??完全加載
完橄唬,包括所有圖像赋焕、js?件、css
?件仰楚、<object>內(nèi)嵌對象等等 - window上觸發(fā):??完全加載
完隆判,包括所有圖像、js?件僧界、css
?件侨嘀、<object>內(nèi)嵌對象等等 - <script>/<link>:腳本或css加
載成功后。注意:script標(biāo)簽只
?持內(nèi)聯(lián)屬性
- window上觸發(fā):??完全加載
resize
-
scroll
- window上觸發(fā):滾動??時
- 元素:可滾動元素滾動時
-
焦點(diǎn):可以捕獲捂襟,但不會冒泡
- focus
- blur
-
-
?標(biāo)與滾輪事件
click
dblclick
mousedown
mouseup
-
mouseenter/mouseleave 與
mouseover/mouseout-
觸發(fā)時機(jī)
- 不論?標(biāo)指針穿過被選元素或其
?元素咬腕,都會觸發(fā) mouseover
事件。 - 只有在?標(biāo)指針穿過被選元素
時葬荷,才會觸發(fā) mouseenter 事件
- 不論?標(biāo)指針穿過被選元素或其
-
是否冒泡
- mouseenter/leave不?持冒泡
- mouseover/mouseout?持冒泡
-
-
位置信息
- 客戶區(qū)坐標(biāo):event.clientX/Y
- ??坐標(biāo):event.pageX/Y
- 屏幕坐標(biāo):event.screenX/Y
-
修改鍵
- event.shiftKey
- event.ctrlKey
- event.altKey
- event.metaKey
-
鍵盤事件
- keydown
- keypress
- keyup
- 注意:通過 event.keyCode 獲取
鍵碼
-
?本事件
- ?本框插??字之前:textInput
-
HTML5事件
?標(biāo)右鍵:contextmenu
??卸載前:beforeunload
形成完成DOM樹:
DOMcontentLoaded-
頁面可見性
pageshow
pagehide
-
注意
- pageshow/pagehide 必須添加
到 window對象上 - pageshow可?來監(jiān)聽??前進(jìn)
后退:??顯示時觸發(fā)涨共,load 事
件只會在第?次加載??是觸
發(fā),之后??會被 bfcache(往
返緩存)管理宠漩,通過前進(jìn)后退按
鈕來顯示??時举反,load 事件并不
會觸發(fā),但是 pageshow 事件會
觸發(fā)
- pageshow/pagehide 必須添加
路由的哈希值變化:
hashchange
DOM事件模型
-
DOM0級事件
- on-event (HTML 屬性)
-
DOM1級事件
- 沒有1級DOM扒吁。DOM級別1于1998年10月1日成為W3C推薦標(biāo)準(zhǔn)照筑。1級DOM標(biāo)準(zhǔn)中并沒有定義事件相關(guān)的內(nèi)容,所以沒有所謂的1級DOM事件模型
-
DOM2級事件
- el.addEventListener(event-name, callback, useCapture)
- 規(guī)定DOM事件流
-
DOM 3級事件
- 在DOM 2級事件的基礎(chǔ)上添加了更多的事件類型。(同多種事件)
事件代理(事件委托)
- 由于事件會在冒泡階段向上傳播到父節(jié)點(diǎn)凝危,因此可以把子節(jié)點(diǎn)的監(jiān)聽函數(shù)定義在父節(jié)點(diǎn)上,由父節(jié)點(diǎn)的監(jiān)聽函數(shù)統(tǒng)一處理多個子元素的事件晨逝。這種方法叫做事件的代理(delegation)蛾默。
- 優(yōu)點(diǎn):減少內(nèi)存消耗,提高性能
- 通過e.currentTarget拿到目標(biāo)對象
JavaScript三大家族
client家族(只讀)
- Element.clientWidth:元素內(nèi)部的寬度(單位像素)捉貌,包含內(nèi)邊距(padding)支鸡,但不包括豎直滾動條、邊框(border)和外邊距(margin)
- Element.clientHeight:元素內(nèi)部的高度(單位像素)趁窃,包含內(nèi)邊距(padding)牧挣,但不包括水平滾動條、邊框(border)和外邊距(margin)
- MouseEvent.clientX:鼠標(biāo)距離可視區(qū)域左側(cè)距離
- MouseEvent.clientY:鼠標(biāo)距離可視區(qū)域上側(cè)距離
- Element.clientTop:表示一個元素頂部邊框的寬度
- Element.clientLeft:表示一個元素的左邊框的寬度
Scroll家族
- Element.scrollWidth(只讀):對內(nèi)容寬度的一種度量醒陆,包括由于overflow溢出而在屏幕上不可見的內(nèi)容瀑构,元素內(nèi)部的高度(單位像素),包含內(nèi)邊距(padding)刨摩,但不包括豎直滾動條寺晌、邊框(border)和外邊距(margin)
- Element.scrollHeight(只讀):對內(nèi)容高度的一種度量,包括由于overflow溢出而在屏幕上不可見的內(nèi)容澡刹,元素內(nèi)部的高度(單位像素)呻征,包含內(nèi)邊距(padding),但不包括水平滾動條罢浇、邊框(border)和外邊距(margin)
- Element.scrollTop(讀取或設(shè)置):一個元素的 scrollTop 值是這個元素的內(nèi)容頂部(卷起來的)到它的視口可見內(nèi)容(的頂部)的距離的度量陆赋。當(dāng)一個元素的內(nèi)容沒有產(chǎn)生垂直方向的滾動條,那么它的 scrollTop 值為0
- Element.scrollLeft(讀取或設(shè)置):一個元素的 scrollLeft 值是這個元素的內(nèi)容左部(卷起來的)到它的視口可見內(nèi)容(的左部)的距離的度量嚷闭。當(dāng)一個元素的內(nèi)容沒有產(chǎn)生垂直方向的滾動條攒岛,那么它的 scrollLeft 值為0
offset家族(只讀)
- Element.offsetWidth:通常,元素的offsetWidtht是一種元素CSS寬度度的衡量標(biāo)準(zhǔn)凌受,包含元素的邊框(border)阵子、水平線上的內(nèi)邊距(padding)、水平方向滾動條(scrollbar)(如果存在的話)胜蛉、以及CSS設(shè)置的寬度(width)的值挠进。
- Element.offsetHeight:通常,元素的offsetHeight是一種元素CSS寬度的衡量標(biāo)準(zhǔn)誊册,包含元素的邊框(border)领突、水平線上的內(nèi)邊距(padding)、豎直方向滾動條(scrollbar)(如果存在的話)案怯、以及CSS設(shè)置的寬度(width)的值君旦。
- Element.offsetLeft:返回當(dāng)前元素左上角相對于 Element.offsetParent 節(jié)點(diǎn)的左邊界偏移的像素值
- Element.offsetTop:返回當(dāng)前元素相對于其 Element.offsetParent 元素的頂部內(nèi)邊距的距離。
- Element.offsetParent:返回父系盒子中帶有定位的盒子節(jié)點(diǎn),1.返回該對象帶有定位的父級 2.如果當(dāng)前元素的父級元素沒有CSS定位金砍, offsetParent為body局蚀;如果當(dāng)前元素的父級元素中有CSS定位,offsetParent 取最近的那個有定位的父級元素恕稠。和盒子本身有無定位無關(guān)琅绅。
- Element.offsetX:規(guī)定了事件對象與目標(biāo)節(jié)點(diǎn)的內(nèi)填充邊(padding edge)在 X 軸方向上的偏移量。(目標(biāo)節(jié)點(diǎn)坐上角為原點(diǎn))
- Element.offsetY
拓展
- Element.getBoundingClientRect(): 方法返回元素的大小及其相對于視口的位置鹅巍。以CSSS設(shè)置寬高作為衡量標(biāo)準(zhǔn)
- MouseEvent.pageX: pageX 是一個由MouseEvent接口返回的相對于整個文檔的x(水平)坐標(biāo)以像素為單位的只讀屬性千扶。
(pageY一樣) - MouseEvent.creenX 是只讀屬性,他提供了鼠標(biāo)相對于屏幕坐標(biāo)系的水平偏移量骆捧。
GC回收機(jī)制
原始數(shù)據(jù)類型是存儲在椗煨撸空間中的,引用類型的數(shù)據(jù)是存儲在堆空間中的敛苇,也就是去分析如何回收這兩種類型的內(nèi)存空間
調(diào)用棧中的數(shù)據(jù)是如何回收的
- JS引擎中以棧的形式來處理執(zhí)行上下文妆绞,而原始數(shù)據(jù)類型就存儲在棧中,調(diào)用棧有一個記錄當(dāng)前執(zhí)行狀態(tài)的指針(稱為 ESP)接谨,指向當(dāng)前正在處理的執(zhí)行上下文
- 當(dāng)函數(shù)執(zhí)行完后摆碉,對應(yīng)的執(zhí)行上下文就可以銷毀了,JavaScript 引擎會通過向下移動 ESP 來銷毀該函數(shù)保存在棧中的執(zhí)行上下文脓豪。
- 如果存在內(nèi)部函數(shù)引用變量(基本類型或者引用類型的都行)巷帝,這時候是放入到閉包對象中的,閉包對象是儲存在堆內(nèi)存空間中的扫夜,這屬于堆內(nèi)存那塊的知識點(diǎn)了
堆中的數(shù)據(jù)是如何回收的
-
垃圾回收的策略:代際假說和分代收集
概念:不過在正式介紹 V8 是如何實(shí)現(xiàn)回收之前楞泼,你需要先學(xué)習(xí)下代際假說(The Generational Hypothesis)的內(nèi)容,這是垃圾回收領(lǐng)域中一個重要的術(shù)語笤闯,后續(xù)垃圾回收的策略都是建立在該假說的基礎(chǔ)之上的堕阔,所以很是重要。
-
代際假說有以下兩個特點(diǎn):
- 第一個是大部分對象在內(nèi)存中存在的時間很短颗味,簡單來說超陆,就是很多對象一經(jīng)分配內(nèi)存,很快就變得不可訪問浦马;
- 第二個是不死的對象时呀,會活得更久。
-
堆
在 V8 中會把堆分為新生代和老生代兩個區(qū)域晶默,新生代中存放的是生存時間短的對象谨娜,老生代中存放的生存時間久的對象
-
新生代
- 新生代中存放的是生存時間短的對象(新生區(qū)通常只支持 1~8M 的容量)
- 副垃圾回收器,主要負(fù)責(zé)新生代的垃圾回收
-
老生代
- 老生代中存放的生存時間久的對象
- 主垃圾回收器磺陡,主要負(fù)責(zé)老生代的垃圾回收
-
垃圾回收器的工作流程
- 現(xiàn)在你知道了 V8 把堆分成兩個區(qū)域——新生代和老生代趴梢,并分別使用兩個不同的垃圾回收器漠畜。其實(shí)不論什么類型的垃圾回收器,它們都有一套共同的執(zhí)行流程坞靶。
- 第一步是標(biāo)記空間中活動對象和非活動對象憔狞。所謂活動對象就是還在使用的對象,非活動對象就是可以進(jìn)行垃圾回收的對象滩愁。
- 第二步是回收非活動對象所占據(jù)的內(nèi)存躯喇。其實(shí)就是在所有的標(biāo)記完成之后,統(tǒng)一清理內(nèi)存中所有被標(biāo)記為可回收的對象硝枉。
- 第三步是做內(nèi)存整理。一般來說倦微,頻繁回收對象后妻味,內(nèi)存中就會存在大量不連續(xù)空間,我們把這些不連續(xù)的內(nèi)存空間稱為內(nèi)存碎片欣福。當(dāng)內(nèi)存中出現(xiàn)了大量的內(nèi)存碎片之后责球,如果需要分配較大連續(xù)內(nèi)存的時候,就有可能出現(xiàn)內(nèi)存不足的情況拓劝。所以最后一步需要整理這些內(nèi)存碎片雏逾,但這步其實(shí)是可選的,因?yàn)橛械睦厥掌鞑粫a(chǎn)生內(nèi)存碎片郑临,比如接下來我們要介紹的副垃圾回收器栖博。
-
副垃圾回收器
- 副垃圾回收器主要負(fù)責(zé)新生區(qū)的垃圾回收。而通常情況下厢洞,大多數(shù)小的對象都會被分配到新生區(qū)仇让,所以說這個區(qū)域雖然不大,但是垃圾回收還是比較頻繁的躺翻。
- 新生代中用 Scavenge 算法來處理丧叽。所謂 Scavenge 算法,是把新生代空間對半劃分為兩個區(qū)域公你,一半是對象區(qū)域踊淳,一半是空閑區(qū)域
- 在垃圾回收過程中,首先要對對象區(qū)域中的垃圾做標(biāo)記陕靠;標(biāo)記完成之后迂尝,就進(jìn)入垃圾清理階段,副垃圾回收器會把這些存活的對象復(fù)制到空閑區(qū)域中懦傍,同時它還會把這些對象有序地排列起來雹舀,所以這個復(fù)制過程,也就相當(dāng)于完成了內(nèi)存整理操作,復(fù)制后空閑區(qū)域就沒有內(nèi)存碎片了特咆。
- 完成復(fù)制后,對象區(qū)域與空閑區(qū)域進(jìn)行角色翻轉(zhuǎn)怠肋,也就是原來的對象區(qū)域變成空閑區(qū)域签财,原來的空閑區(qū)域變成了對象區(qū)域串慰。這樣就完成了垃圾對象的回收操作,同時這種角色翻轉(zhuǎn)的操作還能讓新生代中的這兩塊區(qū)域無限重復(fù)使用下去唱蒸。
- 由于新生代中采用的 Scavenge 算法邦鲫,所以每次執(zhí)行清理操作時,都需要將存活的對象從對象區(qū)域復(fù)制到空閑區(qū)域神汹。但復(fù)制操作需要時間成本庆捺,如果新生區(qū)空間設(shè)置得太大了,那么每次清理的時間就會過久屁魏,所以為了執(zhí)行效率滔以,一般新生區(qū)的空間會被設(shè)置得比較小。
-
對象晉升策略
- 也正是因?yàn)樾律鷧^(qū)的空間不大氓拼,所以很容易被存活的對象裝滿整個區(qū)域你画。為了解決這個問題,JavaScript 引擎采用了對象晉升策略桃漾,也就是經(jīng)過兩次垃圾回收依然還存活的對象坏匪,會被移動到老生區(qū)中。
-
主垃圾回收器
垃圾回收器是采用標(biāo)記 - 清除(Mark-Sweep)的算法進(jìn)行垃圾回收的
首先是標(biāo)記過程階段撬统。標(biāo)記階段就是從一組根元素開始适滓,遞歸遍歷這組根元素,在這個遍歷過程中宪摧,能到達(dá)的元素稱為活動對象粒竖,沒有到達(dá)的元素就可以判斷為垃圾數(shù)據(jù)。然后就是擦除這些垃圾數(shù)據(jù)
-
標(biāo)記 - 整理(Mark-Compact)
- 上面的標(biāo)記過程和清除過程就是標(biāo)記 - 清除算法几于,不過對一塊內(nèi)存多次執(zhí)行標(biāo)記 - 清除算法后蕊苗,會產(chǎn)生大量不連續(xù)的內(nèi)存碎片。而碎片過多會導(dǎo)致大對象無法分配到足夠的連續(xù)內(nèi)存沿彭,于是又產(chǎn)生了另外一種算法——標(biāo)記 - 整理(Mark-Compact)朽砰,這個標(biāo)記過程仍然與標(biāo)記 - 清除算法里的是一樣的,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理喉刘,而是讓所有存活的對象都向一端移動瞧柔,然后直接清理掉端邊界以外的內(nèi)
-
增量標(biāo)記算法
- 在 V8 新生代的垃圾回收中,因其空間較小睦裳,且存活對象較少造锅,所以全停頓的影響不大,但老生代就不一樣了廉邑。如果在執(zhí)行垃圾回收的過程中哥蔚,占用主線程時間過久倒谷,就像上面圖片展示的那樣,花費(fèi)了 200 毫秒糙箍,在這 200 毫秒內(nèi)渤愁,主線程是不能做其他事情的。比如頁面正在執(zhí)行一個 JavaScript 動畫深夯,因?yàn)槔厥掌髟诠ぷ鞫陡瘢蜁?dǎo)致這個動畫在這 200 毫秒內(nèi)無法執(zhí)行的,這將會造成頁面的卡頓現(xiàn)象咕晋。
- 為了降低老生代的垃圾回收而造成的卡頓雹拄,V8 將標(biāo)記過程分為一個個的子標(biāo)記過程,同時讓垃圾回收標(biāo)記和 JavaScript 應(yīng)用邏輯交替進(jìn)行掌呜,直到標(biāo)記階段完成办桨,我們把這個算法稱為增量標(biāo)記(Incremental Marking)算法
- 使用增量標(biāo)記算法,可以把一個完整的垃圾回收任務(wù)拆分為很多小的任務(wù)站辉,這些小的任務(wù)執(zhí)行時間比較短,可以穿插在其他的 JavaScript 任務(wù)中間執(zhí)行损姜,這樣當(dāng)執(zhí)行上述動畫效果時饰剥,就不會讓用戶因?yàn)槔厥杖蝿?wù)而感受到頁面的卡頓了。(??將大的任務(wù)拆分成小 減少卡頓)
??注意什么時候進(jìn)行標(biāo)記 什么時候進(jìn)行清除
- 當(dāng)執(zhí)行上下文創(chuàng)建時摧阅,變量進(jìn)入該環(huán)境汰蓉,我們就可以對該變量對應(yīng)的內(nèi)存進(jìn)行標(biāo)記。如果執(zhí)行上下文執(zhí)行完畢棒卷,這個時候顾孽,就可以將所有進(jìn)入該環(huán)境的變量標(biāo)記為可清除狀態(tài)。我們通俗的說法就是比规,當(dāng)一份內(nèi)存失去了引用若厚,那么它就會被垃圾回收工具回收。
- 不過還有兩個需要注意的地方蜒什。
一個是全局上下文测秸。在程序結(jié)束之前,全局上下文始終存在灾常。通常來說霎冯,JS程序運(yùn)行期間,全局上下文不會有執(zhí)行結(jié)束的時間節(jié)點(diǎn)钞瀑。因此定義在全局上下文的狀態(tài)永遠(yuǎn)都不會被標(biāo)記沈撞。除非我們手動將變量設(shè)置為null,它對應(yīng)的內(nèi)存都不會被回收
- JS引擎執(zhí)行代碼是邊解釋邊執(zhí)行雕什,對于未執(zhí)行的函數(shù)代碼段 都還沒到到編譯階段呢缠俺,也不會分配變量內(nèi)存給他們了显晶,執(zhí)行到這個階段才會的
閉包
什么是閉包
- 在 JavaScript 中,根據(jù)詞法作用域的規(guī)則晋修,內(nèi)部函數(shù)總是可以訪問其外部函數(shù)中聲明的變量吧碾,當(dāng)通過調(diào)用一個外部函數(shù)返回一個內(nèi)部函數(shù)后,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束了墓卦,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中倦春,我們就把這些變量的集合稱為閉包。比如外部函數(shù)是 foo落剪,那么這些變量的集合就稱為 foo 函數(shù)的閉包
從內(nèi)存模型的角度來分析閉包
- 代碼例子:
function foo() {
var myName = "極客時間"
let test1 = 1
const test2 = 2
var innerBar = {
setName:function(newName){
myName = newName
},
getName:function(){
console.log(test1)
return myName
}
}
return innerBar
}
var bar = foo()
bar.setName("極客邦")
bar.getName()
console.log(bar.getName()) - 當(dāng) JavaScript 引擎執(zhí)行到 foo 函數(shù)時睁本,首先會編譯,并創(chuàng)建一個空執(zhí)行上下文
- 在編譯過程中忠怖,遇到內(nèi)部函數(shù) setName呢堰,JavaScript 引擎還要對內(nèi)部函數(shù)做一次快速的詞法掃描,發(fā)現(xiàn)該內(nèi)部函數(shù)引用了 foo 函數(shù)中的 myName 變量凡泣,由于是內(nèi)部函數(shù)引用了外部函數(shù)的變量枉疼,所以 JavaScript 引擎判斷這是一個閉包,于是在堆空間創(chuàng)建換一個“closure(foo)”的對象(這是一個內(nèi)部對象鞋拟,JavaScript 是無法訪問的)骂维,用來保存 myName 變量。
- 接著繼續(xù)掃描到 getName 方法時贺纲,發(fā)現(xiàn)該函數(shù)內(nèi)部還引用變量 test1航闺,于是 JavaScript 引擎又將 test1 添加到“closure(foo)”對象中。這時候堆中的“closure(foo)”對象中就包含了 myName 和 test1 兩個變量了猴誊。
- 由于 test2 并沒有被內(nèi)部函數(shù)引用潦刃,所以 test2 依然保存在調(diào)用棧中。
- ??閉包對象的創(chuàng)建在編譯的時候創(chuàng)建懈叹,在編譯過程中乖杠,遇到內(nèi)部函數(shù) setName,JavaScript 引擎還要對內(nèi)部函數(shù)做一次快速的詞法掃描项阴,所以這也解釋了閉包對象的名稱是外部函數(shù)滑黔,這是對的 (外面的文章寫內(nèi)部函數(shù)是閉包,這是錯誤的?)
- ??總的來說环揽,產(chǎn)生閉包的核心有兩步:第一步是需要預(yù)掃描內(nèi)部函數(shù)略荡;第二步是把內(nèi)部函數(shù)引用的外部變量保存到堆中,在編譯階段就會產(chǎn)生閉包對象(前提是函數(shù)跟函數(shù)之前)歉胶,如果閉包對象存在引用汛兜,就不會被銷毀,這也是要注意到內(nèi)存泄漏的地方(??總注意形成閉包的前提是 外部函數(shù)和內(nèi)部函數(shù))
閉包對象是編譯階段就產(chǎn)生的通今,如果存在引用則不會被GC回收機(jī)制回收
閉包的應(yīng)用
- 模仿塊級作用域
- 私有變量
執(zhí)行上下文
概念:JavaScript代碼在執(zhí)行時粥谬,會進(jìn)入一個執(zhí)行上下文中肛根。執(zhí)行上下文可以理解為當(dāng)前代碼的運(yùn)行環(huán)境
執(zhí)行上下文的三種類型
- 全局環(huán)境:代碼運(yùn)行起來后會首先進(jìn)入全局環(huán)境
- 函數(shù)環(huán)境:當(dāng)函數(shù)被調(diào)用執(zhí)行時,會進(jìn)入當(dāng)前函數(shù)中的執(zhí)行代碼
- eval環(huán)境:不建議使用漏策,不做介紹
執(zhí)行上下文的生命周期
-
編譯階段(創(chuàng)建階段)
- 經(jīng)過JS引擎編譯后派哲,會生成兩部分內(nèi)容:執(zhí)行上下文(Execution context)和可執(zhí)行代碼
- 在這個階段,執(zhí)行上下文會分別創(chuàng)建變量對象掺喻、確定作用域鏈芭届,以及this指向,明白這個階段也就會明白變量提升的現(xiàn)象,(也就是在編譯階段我們就確定了作用域鏈和this指向等)
-
執(zhí)行階段
- 創(chuàng)建階段之后感耙,就會開始執(zhí)行代碼褂乍,這個時候會完成變量賦值,函數(shù)引用即硼,以及執(zhí)行其它可執(zhí)行代碼逃片,如圖所示
變量對象
在JavaScript代碼中聲明的所有變量都保存在變量對象中,除此之外只酥,變量對象中還可能包含以下內(nèi)容
函數(shù)的所有參數(shù)(Firefox中為參數(shù)對象arguments)
當(dāng)前上下文中的所有函數(shù)聲明(通過function聲明的函數(shù))
當(dāng)前上下文的所有變量聲明(通過var聲明的變量)
-
變量對象創(chuàng)建過程
- 在Chrome瀏覽器中褥实,變量對象會首先獲得函數(shù)的參數(shù)變量及其值;在Firefox瀏覽器中裂允,是直接將參數(shù)對象arguments保存在變量對象中
- 依次獲取當(dāng)前上下文中所有的函數(shù)聲明性锭,也就是使用function關(guān)鍵字聲明的函數(shù)。在變量中會以函數(shù)名建立一個屬性叫胖,屬性值為指向該函數(shù)所在的內(nèi)存地址引用。如果函數(shù)名的屬性已經(jīng)存在她奥,那么該屬性的值會被新的引用覆蓋
- 依次獲取當(dāng)前上下文的變量聲明瓮增,也就是以var關(guān)鍵字聲明的變量。每找到一個變量聲明哩俭,就在變量對象中就以變量名建立一個屬性绷跑,屬性值為undefined,如果該變量名的屬性已經(jīng)存在凡资,為了防止同名的函數(shù)被修改為undefined,則會直接跳過砸捏,原屬性不會被修改,也就是如果變量與函數(shù)同名,則在這個階段隙赁,以函數(shù)值為準(zhǔn)
作用域和作用域鏈
-
作用域
作用域規(guī)定了如何查找變量垦藏,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。
-
詞法作用域
-
詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來決定的伞访,所以詞法作用域是靜態(tài)的作用域掂骏,通過它就能夠預(yù)測代碼在執(zhí)行過程中如何查找標(biāo)識符。(簡單理解書寫??代碼的時候厚掷,詞法作用域是代碼階段就決定好的弟灼,和函數(shù)是怎么調(diào)用的沒有關(guān)系)
-
全局作用域
- 最外層函數(shù)和在最外層函數(shù)外面定義的變量
- 沒有通過關(guān)鍵字"var"聲明的變量(包括嵌套的函數(shù)內(nèi))
- 瀏覽器中级解,window對象的屬性
函數(shù)作用域
塊級作用域
-
-
動態(tài)作用域
-
作用域鏈
- 作用域鏈,是由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對象組成田绑,它保證了當(dāng)前執(zhí)行環(huán)境對符合訪問權(quán)限的變量和函數(shù)的有序訪問勤哗。
- 編譯階段就創(chuàng)造了作用域鏈,內(nèi)部函數(shù)copy拷貝外部函數(shù)的作用域掩驱,在函數(shù)內(nèi)部有[[scope]]屬性就是表示作用域鏈
- ??理解作用域鏈?zhǔn)抢斫忾]包的基礎(chǔ)
V8
高級語言
-
編譯型語言在程序執(zhí)行之前芒划,需要經(jīng)過編譯器的編譯過程,并且編譯之后會直接保留機(jī)器能讀懂的二進(jìn)制文件昙篙,這樣每次運(yùn)行程序時腊状,都可以直接運(yùn)行該二進(jìn)制文件,而不需要再次重新編譯了苔可。比如 C/C++缴挖、GO 等都是編譯型語言。
- 在編譯型語言的編譯過程中焚辅,編譯器首先會依次對源代碼進(jìn)行詞法分析映屋、語法分析,生成抽象語法樹(AST)同蜻,然后是優(yōu)化代碼棚点,最后再生成處理器能夠理解的機(jī)器碼。如果編譯成功湾蔓,將會生成一個可執(zhí)行的文件瘫析。但如果編譯過程發(fā)生了語法或者其他的錯誤,那么編譯器就會拋出異常默责,最后的二進(jìn)制文件也不會生成成功
-
而由解釋型語言編寫的程序贬循,在每次運(yùn)行時都需要通過解釋器對程序進(jìn)行動態(tài)解釋和執(zhí)行。比如 Python桃序、JavaScript 等都屬于解釋型語言杖虾。
- 在解釋型語言的解釋過程中,同樣解釋器也會對源代碼進(jìn)行詞法分析媒熊、語法分析奇适,并生成抽象語法樹(AST),不過它會再基于抽象語法樹生成字節(jié)碼芦鳍,最后再根據(jù)字節(jié)碼來執(zhí)行程序嚷往、輸出結(jié)果。
V8 是如何執(zhí)行一段 JavaScript 代碼的
-
生成抽象語法樹(AST)和執(zhí)行上下文
將源代碼轉(zhuǎn)換為抽象語法樹柠衅,并生成執(zhí)行上下文间影,主要是代碼在執(zhí)行過程中的環(huán)境信息
高級語言是開發(fā)者可以理解的語言,但是讓編譯器或者解釋器來理解就非常困難了。對于編譯器或者解釋器來說魂贬,它們可以理解的就是 AST 了巩割。所以無論你使用的是解釋型語言還是編譯型語言,在編譯過程中付燥,它們都會生成一個 AST(解釋器和編譯器先把源代碼編譯成AST)
-
如何生成AST
- 第一階段是分詞(tokenize)宣谈,又稱為詞法分析,其作用是將一行行的源碼拆解成一個個 token键科。所謂 token闻丑,指的是語法上不可能再分的、最小的單個字符或字符串勋颖。你可以參考下圖來更好地理解什么 token
- 第二階段是解析(parse)嗦嗡,又稱為語法分析,其作用是將上一步生成的 token 數(shù)據(jù)饭玲,根據(jù)語法規(guī)則轉(zhuǎn)為 AST侥祭。如果源碼符合語法規(guī)則,這一步就會順利完成茄厘。但如果源碼存在語法錯誤矮冬,這一步就會終止,并拋出一個“語法錯誤”次哈。(??有了 AST 后胎署,那接下來 V8 就會生成該段代碼的執(zhí)行上下文)
-
生成字節(jié)碼
- 有了 AST 和執(zhí)行上下文后,那接下來的第二步窑滞,解釋器 Ignition 就登場了(解釋器也負(fù)責(zé)了解釋源代碼到AST任務(wù))琼牧,它會根據(jù) AST 生成字節(jié)碼,并解釋執(zhí)行字節(jié)碼哀卫。
- 字節(jié)碼就是介于 AST 和機(jī)器碼之間的一種代碼障陶。但是與特定類型的機(jī)器碼無關(guān),字節(jié)碼需要通過解釋器將其轉(zhuǎn)換為機(jī)器碼后才能執(zhí)行聊训。
-
執(zhí)行代碼
- 生成字節(jié)碼之后,接下來就要進(jìn)入執(zhí)行階段了恢氯。
- 通常带斑,如果有一段第一次執(zhí)行的字節(jié)碼,解釋器 Ignition 會逐條解釋執(zhí)行勋拟。到了這里勋磕,相信你已經(jīng)發(fā)現(xiàn)了,解釋器 Ignition 除了負(fù)責(zé)生成字節(jié)碼之外敢靡,它還有另外一個作用挂滓,就是解釋執(zhí)行字節(jié)碼。在 Ignition 執(zhí)行字節(jié)碼的過程中啸胧,如果發(fā)現(xiàn)有熱點(diǎn)代碼(HotSpot)赶站,比如一段代碼被重復(fù)執(zhí)行多次幔虏,這種就稱為熱點(diǎn)代碼,那么后臺的編譯器 TurboFan 就會把該段熱點(diǎn)的字節(jié)碼編譯為高效的機(jī)器碼贝椿,然后當(dāng)再次執(zhí)行這段被優(yōu)化的代碼時想括,只需要執(zhí)行編譯后的機(jī)器碼就可以了,這樣就大大提升了代碼的執(zhí)行效率烙博。
內(nèi)存機(jī)制
三種類型內(nèi)存空間
代碼空間
-
椛冢空間
- 原始類型的數(shù)據(jù)值都是直接保存在“棧”中的
-
堆空間
- 引用類型的值是存放在“堆”中的
事件循環(huán)
起因:在 JS 中渣窜,大部分的任務(wù)都是在主線程上執(zhí)行铺根,常見的任務(wù)有渲染事件,用戶交互事件乔宿,js腳本執(zhí)行位迂,網(wǎng)絡(luò)請求、文件讀寫完成事件等等予颤,為了讓這些事件有條不紊地進(jìn)行囤官,JS引擎需要對之執(zhí)行的順序做一定的安排,V8 其實(shí)采用的是一種隊(duì)列的方式來存儲這些任務(wù)蛤虐, 即先進(jìn)來的先執(zhí)行党饮。
注意??事件循環(huán)不單單是為了解決JS是單線程(渲染主線程)解決異步的原因,而是更大更全的去理解他是瀏覽器渲染主線程的調(diào)度系統(tǒng)驳庭,通過這個調(diào)度系統(tǒng)去有條不紊的安排任務(wù)執(zhí)行(JavaScript沒有自己循環(huán)系統(tǒng)刑顺,它依賴的就是瀏覽器的循環(huán)系統(tǒng),也就是渲染進(jìn)程提供的循環(huán)系統(tǒng)K浅!)
宏任務(wù)
- 渲染事件(如解析 DOM蹲堂、計(jì)算布局、繪制)
- 用戶交互事件(如鼠標(biāo)點(diǎn)擊贝淤、滾動頁面柒竞、放大縮小等)
- JavaScript 腳本執(zhí)行事件;
- 網(wǎng)絡(luò)請求完成播聪、文件讀寫完成事件朽基。
微任務(wù)
-
起因
-
宏任務(wù)的時間粒度比較大,執(zhí)行的時間間隔是不能精確控制的离陶,對一些高實(shí)時性的需求就不太符合了稼虎,比如后面要介紹的監(jiān)聽 DOM 變化的需求
- 如何處理高優(yōu)先級的任務(wù)。
-
監(jiān)聽 DOM 變化技術(shù)方案的演化史
- 從輪詢到 Mutation Event 再到最新使用的 MutationObserver招刨。MutationObserver 方案的核心就是采用了微任務(wù)機(jī)制霎俩,有效地權(quán)衡了實(shí)時性和執(zhí)行效率的問題
-
我們知道當(dāng) JavaScript 執(zhí)行一段腳本的時候,V8 會為其創(chuàng)建一個全局執(zhí)行上下文,在創(chuàng)建全局執(zhí)行上下文的同時打却,V8 引擎也會在內(nèi)部創(chuàng)建一個微任務(wù)隊(duì)列杉适。顧名思義,這個微任務(wù)隊(duì)列就是用來存放微任務(wù)的学密,因?yàn)樵诋?dāng)前宏任務(wù)執(zhí)行的過程中淘衙,有時候會產(chǎn)生多個微任務(wù),這時候就需要使用這個微任務(wù)隊(duì)列來保存這些微任務(wù)了腻暮。不過這個微任務(wù)隊(duì)列是給 V8 引擎內(nèi)部使用的彤守,所以你是無法通過 JavaScript 直接訪問的
??也就是說每個宏任務(wù)都關(guān)聯(lián)了一個微任務(wù)隊(duì)列
-
微任務(wù)的工作流程
- 微任務(wù)和宏任務(wù)是綁定的,每個宏任務(wù)在執(zhí)行時哭靖,會創(chuàng)建自己的微任務(wù)隊(duì)列
- 微任務(wù)的執(zhí)行時長會影響到當(dāng)前宏任務(wù)的時長具垫。比如一個宏任務(wù)在執(zhí)行過程中,產(chǎn)生了 100 個微任務(wù)试幽,執(zhí)行每個微任務(wù)的時間是 10 毫秒筝蚕,那么執(zhí)行這 100 個微任務(wù)的時間就是 1000 毫秒,也可以說這 100 個微任務(wù)讓宏任務(wù)的執(zhí)行時間延長了 1000 毫秒铺坞。所以你在寫代碼的時候一定要注意控制微任務(wù)的執(zhí)行時長起宽。
- 在一個宏任務(wù)中,分別創(chuàng)建一個用于回調(diào)的宏任務(wù)和微任務(wù)济榨,無論什么情況下坯沪,微任務(wù)都早于宏任務(wù)執(zhí)行。
MutationObserver
Promise.then(或.reject) 以及以 Promise 為基礎(chǔ)開發(fā)的其他技術(shù)(比如fetch API)
消息隊(duì)列
-
基于不同的場景來動態(tài)調(diào)整消息隊(duì)列的優(yōu)先級
- 可以創(chuàng)建輸入事件的消息隊(duì)列擒滑,用來存放輸入事件腐晾。
- 可以創(chuàng)建合成任務(wù)的消息隊(duì)列,用來存放合成事件丐一。
- 可以創(chuàng)建默認(rèn)消息隊(duì)列藻糖,用來保存如資源加載的事件和定時器回調(diào)等事件。
- 還可以創(chuàng)建一個空閑消息隊(duì)列库车,用來存放 V8 的垃圾自動垃圾回收這一類實(shí)時性不高的事件巨柒。
事件循環(huán)的流程
- 宏任務(wù)==>清空所有的微任務(wù)===>UI渲染
rAF
window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫柠衍。該方法需要傳入一個回調(diào)函數(shù)作為參數(shù)洋满,該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行
-
VSync
- 當(dāng)顯示器將一幀畫面繪制完成后,并在準(zhǔn)備讀取下一幀之前拧略,顯示器會發(fā)出一個垂直同步信號(vertical synchronization)給 GPU,簡稱 VSync
-
為什么需要用rAF代替setTimeout
- 我們知道 CSS 動畫是由渲染進(jìn)程自動處理的瘪弓,所以渲染進(jìn)程會讓 CSS 渲染每幀動畫的過程與 VSync 的時鐘保持一致, 這樣就能保證 CSS 動畫的高效率執(zhí)行垫蛆。
- 用戶體驗(yàn)動畫流暢的幀率大概是60FPS,使用setTimout很難精確控制,可所以使用rAF交由系統(tǒng)控制袱饭,保持跟顯示器的幀率大概一致
- 但是 JavaScript 是由用戶控制的川无,如果采用 setTimeout 來觸發(fā)動畫每幀的繪制,那么其繪制時機(jī)是很難和 VSync 時鐘保持一致的虑乖,所以 JavaScript 中又引入了 window.requestAnimationFrame懦趋,用來和 VSync 的時鐘周期同步
- VSync 和系統(tǒng)的時鐘不同步就會造成掉幀、卡頓疹味、不連貫等問題
-
requestAnimationFrame 在 EventLoop 中是一個什么位置仅叫?
- rAF會在UI渲染之前
問題
-
UI渲染也會產(chǎn)生宏任務(wù),那么按照實(shí)際循環(huán)流程糙捺,是會無限遞歸的那種
- 消息隊(duì)列也是分為優(yōu)先級的(雖然微任務(wù)是高優(yōu)先級任務(wù) 但是依賴于宏任務(wù) 比如交互操作 點(diǎn)擊事件產(chǎn)生的回調(diào)是個宏任務(wù) )也就是這個每次執(zhí)行一次宏任務(wù)诫咱,觸發(fā)UI渲染 這個宏任務(wù)應(yīng)該屬于交互消息隊(duì)列的類型的, 應(yīng)該是根據(jù)消息隊(duì)列類別來判斷的 (本身屬于合成消息隊(duì)列就不會再觸發(fā)UI渲染了)
-
觸發(fā)一次宏任務(wù)就一定會執(zhí)行UI渲染嗎
- 進(jìn)入更新渲染階段洪灯,判斷是否需要渲染坎缭,這里有一個 rendering opportunity 的概念,也就是說不一定每一輪 event loop 都會對應(yīng)一次瀏覽 器渲染签钩,要根據(jù)屏幕刷新率掏呼、頁面性能、頁面是否在后臺運(yùn)行來共同決定铅檩,通常來說這個渲染間隔是固定的憎夷。(所以多個 task 很可能在一次渲染之間執(zhí)行)
-
靜止不動的頁面需要每隔16ms觸發(fā)一次UI渲染嗎
- 我覺得完全沒必要,因?yàn)闆]啥意義柠并,都靜止不動了岭接,根據(jù)瀏覽器FPS觀察幾乎為0,所以說瀏覽器不一定非跟顯示器保持百分百的幀率一致
-
16ms
渲染幀是指瀏覽器一次完整繪制過程臼予,幀之間的時間間隔是 DOM 視圖更新的最小間隔鸣戴。 由于主流的屏幕刷新率都在 60Hz,那么渲染一幀的時間就必須控制在 16ms 才能保證不掉幀粘拾。 也就是說每一次渲染都要在 16ms 內(nèi)頁面才夠流暢不會有卡頓感
-
為什么需要這個判斷窄锅,為了動畫順暢性,所以不存在時間基點(diǎn)判定缰雇,你交互開始時候就算入偷,保持一幀16ms左右就是流暢届惋,也就是滿足這個幀率間隔就不會卡頓 (我這個交互或者動畫是幀率60左右就是流暢 靜止的頁面都不需要流暢這個概念 幀率為0就行 所以不要有絕對的那種時間線)
- scroll
- resize
-
這段時間內(nèi)瀏覽器需要完成如下事情
- 腳本執(zhí)行(JavaScript):腳本造成了需要重繪的改動痕届,比如增刪 DOM惨恭、請求動畫等
- 樣式計(jì)算(CSS Object Model):級聯(lián)地生成每個節(jié)點(diǎn)的生效樣式杆故。
- 布局(Layout):計(jì)算布局歌粥,執(zhí)行渲染算法
- 重繪(Paint):各層分別進(jìn)行繪制(比如 3D 動畫)
- 合成(Composite):合成各層的渲染結(jié)果
函數(shù)式編程
高階函數(shù)
高階函數(shù)(higher-order function)指操作函數(shù)的函數(shù)诈豌,一般地婆翔,有以下兩種情況
函數(shù)可以作為參數(shù)被傳遞
函數(shù)可以作為返回值輸出
-
作用
- 增強(qiáng)函數(shù)的功能姥宝,Redux中間件就是高階函數(shù)的產(chǎn)物
純函數(shù)
- 定義:純函數(shù)是這樣一種函數(shù),即相同的輸入其骄,永遠(yuǎn)會得到相同的輸出亏镰,而且沒有任何可觀察的副作用。
- 場景:比如 slice 和 splice拯爽,這兩個函數(shù)的作用并無二致——但是注意索抓,它們各自的方式卻大不同,但不管怎么說作用還是一樣的毯炮。我們說 slice 符合純函數(shù)的定義是因?yàn)閷ο嗤妮斎胨WC能返回相同的輸出逼肯。而 splice 卻會嚼爛調(diào)用它的那個數(shù)組,然后再吐出來否副;這就會產(chǎn)生可觀察到的副作用汉矿,即這個數(shù)組永久地改變了。
函數(shù)柯里化
概念:在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中备禀,柯里化是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)洲拇。
-
作用
參數(shù)復(fù)用
-
提前返回
- 比如判斷一次類型以后下次直接使用該類型對應(yīng)的特性就行
延遲計(jì)算/運(yùn)行
/** 利用遞歸加函數(shù)的length熟悉實(shí)現(xiàn)柯里化 */
const curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (arg) => judge(...args, arg)
偏函數(shù)
概念:在計(jì)算機(jī)科學(xué)中,局部應(yīng)用是指固定一個函數(shù)的一些參數(shù)曲尸,然后產(chǎn)生另一個更小元的函數(shù)赋续。
-
柯里化與局部應(yīng)用
- 柯里化是將一個多參數(shù)函數(shù)轉(zhuǎn)換成多個單參數(shù)函數(shù),也就是將一個 n 元函數(shù)轉(zhuǎn)換成 n 個一元函數(shù)另患。
- 局部應(yīng)用則是固定一個函數(shù)的一個或者多個參數(shù)纽乱,也就是將一個 n 元函數(shù)轉(zhuǎn)換成一個 n - x 元函數(shù)。
-
實(shí)現(xiàn)
- 使用bind:add.bind(null, 1),然而使用 bind 我們還是改變了 this 指向昆箕,我們要寫一個不改變 this 指向的方法鸦列。
- function partial(fn) {
var args = [].slice.call(arguments, 1);
return function() {
var newArgs = args.concat([].slice.call(arguments));
return fn.apply(this, newArgs);
};
};
惰性函數(shù)
概念:惰性載入表示函數(shù)執(zhí)行的分支只會在函數(shù)第一次調(diào)用的時候執(zhí)行,在第一次調(diào)用過程中鹏倘,該函數(shù)會被覆蓋為另一個按照合適方式執(zhí)行的函數(shù)薯嗤,這樣任何對原函數(shù)的調(diào)用就不用再經(jīng)過執(zhí)行的分支了。
const foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
}; // 重寫覆蓋foo函數(shù)-
應(yīng)用
- DOM 事件添加中纤泵,為了兼容現(xiàn)代瀏覽器和 IE 瀏覽器骆姐,我們需要對瀏覽器環(huán)境進(jìn)行一次判斷
函數(shù)組合
概念:函數(shù)組合就是組合兩到多個函數(shù)來生成一個新函數(shù)的過程。 將函數(shù)組合在一起捏题,就像將一連串管道扣合在一起玻褪,讓數(shù)據(jù)流過一樣。 簡而言之公荧,函數(shù) f 和 g 的組合可以被定義為 f(g(x)) 带射,從內(nèi)到外(從右到左)求值
-
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}if (funcs.length === 1) {
return funcs[0]
}return funcs.reduce((a, b) => (...args) => a(b(...args)))
} f1(f2(f3(..args))) 從右到左 compose(f1,f2,f3)(...args) 函數(shù)f3執(zhí)行過后把值return給f2
-
reduce方法
-
callback:執(zhí)行數(shù)組中每個值 (如果沒有提供 initialValue則第一個值除外)的函數(shù),包含四個參數(shù):
- accumulator:累計(jì)器累計(jì)回調(diào)的返回值; 它是上一次調(diào)用回調(diào)時返回的累積值循狰,或initialValue(見于下方)
- currentValue:數(shù)組中正在處理的元素窟社。
- index 可選
數(shù)組中正在處理的當(dāng)前元素的索引捻浦。 如果提供了initialValue,則起始索引號為0桥爽,否則從索引1起始。 - array:調(diào)用reduce()的數(shù)組
- initialValue:作為第一次調(diào)用 callback函數(shù)時的第一個參數(shù)的值昧识。 如果沒有提供初始值钠四,則將使用數(shù)組中的第一個元素。 在沒有初始值的空數(shù)組上調(diào)用 reduce 將報(bào)錯跪楞。沒有提供initialValue的話累計(jì)計(jì)算就從數(shù)組下標(biāo)1開始
-
函數(shù)記憶
- 函數(shù)記憶是指將上次的計(jì)算結(jié)果緩存起來缀去,當(dāng)下次調(diào)用時,如果遇到相同的參數(shù)甸祭,就直接返回緩存中的數(shù)據(jù)缕碎。(簡單點(diǎn)講就是緩存函數(shù))
- let memoize = function (func, content) {
let cache = Object.create(null)
content = content || this
return (...key) => {
if (!cache[key]) {
cache[key] = func.apply(content, key)
}
return cache[key]
}
}
XMind - Trial Version