前言
本文講解 56 道 JavaScript 和 ES6+ 面試題的內(nèi)容膘婶。
復(fù)習(xí)前端面試的知識恒水,是為了鞏固前端的基礎(chǔ)知識倔幼,最重要的還是平時的積累继薛!
注意
:文章的題與題之間用下劃線分隔開修壕,答案僅供參考。
前端硬核面試專題的完整版在此:前端硬核面試專題遏考,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數(shù)據(jù)結(jié)構(gòu)與算法 + Git 慈鸠。
JavaScript
常見的瀏覽器內(nèi)核有哪些 ?
- Trident 內(nèi)核:IE, 360灌具,搜狗瀏覽器 MaxThon青团、TT譬巫、The World,等。[又稱 MSHTML]
- Gecko 內(nèi)核:火狐督笆,F(xiàn)F芦昔,MozillaSuite / SeaMonkey 等
- Presto 內(nèi)核:Opera7 及以上。[Opera 內(nèi)核原為:Presto娃肿,現(xiàn)為:Blink]
- Webkit 內(nèi)核:Safari烟零,Chrome 等。 [ Chrome 的:Blink(WebKit 的分支)]
mouseenter 和 mouseover 的區(qū)別
- 不論鼠標(biāo)指針穿過被選元素或其子元素咸作,都會觸發(fā) mouseover 事件锨阿,對應(yīng) mouseout。
- 只有在鼠標(biāo)指針穿過被選元素時记罚,才會觸發(fā) mouseenter 事件墅诡,對應(yīng) mouseleave。
用正則表達(dá)式匹配字符串桐智,以字母開頭末早,后面是數(shù)字、字符串或者下劃線说庭,長度為 9 - 20
var re=new RegExp("^[a-zA-Z][a-zA-Z0-9_]{9,20}$");
手機(jī)號碼校驗
function checkPhone(){
var phone = document.getElementById('phone').value;
if(!(/^1(3|4|5|7|8)\d{9}$/.test(phone))){
alert("手機(jī)號碼有誤然磷,請重填");
return false;
}
}
^1(3|4|5|7|8)d{9}$,表示以 1 開頭刊驴,第二位可能是 3/4/5/7/8 等的任意一個姿搜,在加上后面的 d 表示數(shù)字 [0-9] 的 9 位,總共加起來 11 位結(jié)束捆憎。
手機(jī)號碼格式驗證方法(正則表達(dá)式驗證)支持最新電信 199舅柜, 移動 198, 聯(lián)通 166
// 手機(jī)號碼校驗規(guī)則
let valid_rule = /^(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|19[8-9])[0-9]{8}$/;
if ( ! valid_rule.test(phone_number)) {
alert('手機(jī)號碼格式有誤');
return false;
}
這樣 phone_number 就是取到的手機(jī)號碼躲惰,即可致份!
js 字符串兩邊截取空白的 trim 的原型方法的實現(xiàn)
js 中本身是沒有 trim 函數(shù)的。
// 刪除左右兩端的空格
function trim(str){
return str.replace(/(^\s*)|(\s*$)/g, "");
}
// 刪除左邊的空格 /(^\s*)/g
// 刪除右邊的空格 /(\s*$)/g
介紹一下你對瀏覽器內(nèi)核的理解 ?
內(nèi)核主要分成兩部分:渲染引擎(layout engineer 或 Rendering Engine) 和 JS 引擎础拨。
渲染引擎
負(fù)責(zé)取得網(wǎng)頁的內(nèi)容(HTML氮块、XML、圖像等等)诡宗、整理訊息(例如加入 CSS 等)滔蝉,以及計算網(wǎng)頁的顯示方式,然后會輸出至顯示器或打印機(jī)僚焦。
瀏覽器的內(nèi)核的不同對于網(wǎng)頁的語法解釋會有不同锰提,所以渲染的效果也不相同。
所有網(wǎng)頁瀏覽器、電子郵件客戶端以及其它需要編輯立肘、顯示網(wǎng)絡(luò)內(nèi)容的應(yīng)用程序都需要內(nèi)核边坤。
JS 引擎
解析和執(zhí)行 javascript 來實現(xiàn)網(wǎng)頁的動態(tài)效果。
最開始渲染引擎和 JS 引擎并沒有區(qū)分的很明確谅年,后來 JS 引擎越來越獨(dú)立茧痒,內(nèi)核就傾向于只指渲染引擎。
哪些常見操作會造成內(nèi)存泄漏 融蹂?
內(nèi)存泄漏指任何對象在您不再擁有或需要它之后仍然存在旺订。
垃圾回收器定期掃描對象,并計算引用了每個對象的其他對象的數(shù)量超燃。如果一個對象的引用數(shù)量為 0(沒有其他對象引用過該對象)区拳,或?qū)υ搶ο蟮奈┮灰檬茄h(huán)的,那么該對象的內(nèi)存即可回收意乓。
- setTimeout 的第一個參數(shù)使用字符串而非函數(shù)的話樱调,會引發(fā)內(nèi)存泄漏。
- 閉包届良、控制臺日志笆凌、循環(huán)(在兩個對象彼此引用且彼此保留時,就會產(chǎn)生一個循環(huán))士葫。
線程與進(jìn)程的區(qū)別 乞而?
- 一個程序至少有一個進(jìn)程,一個進(jìn)程至少有一個線程慢显。
- 線程的劃分尺度小于進(jìn)程爪模,使得多線程程序的并發(fā)性高。
- 另外鳍怨,進(jìn)程在執(zhí)行過程中擁有獨(dú)立的內(nèi)存單元呻右,而多個線程共享內(nèi)存跪妥,從而極大地提高了程序的運(yùn)行效率鞋喇。
線程在執(zhí)行過程中與進(jìn)程還是有區(qū)別的。
- 每個獨(dú)立的線程有一個程序運(yùn)行的入口眉撵、順序執(zhí)行序列和程序的出口侦香。但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中纽疟,由應(yīng)用程序提供多個線程執(zhí)行控制罐韩。
- 從邏輯角度來看,多線程的意義在于一個應(yīng)用程序中污朽,有多個執(zhí)行部分可以同時執(zhí)行散吵。
但操作系統(tǒng)并沒有將多個線程看做多個獨(dú)立的應(yīng)用,來實現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配。這就是進(jìn)程和線程的重要區(qū)別矾睦。
eval() 函數(shù)有什么用 晦款?
eval() 函數(shù)可計算某個字符串,并執(zhí)行其中的的 JavaScript 代碼漆腌。
實現(xiàn)一個方法缴啡,使得:add(2, 5) 和 add(2)(5) 的結(jié)果都為 7
var add = function (x, r) {
if (arguments.length == 1) {
return function (y) { return x + y; };
} else {
return x + r;
}
};
console.log(add(2)(5)); // 7
console.log(add(2,5)); // 7
alert(1 && 2) 和 alert(1 || 0) 的結(jié)果是 洒沦?
alert(1 &&2 ) 的結(jié)果是 2
- 只要 “&&” 前面是 false,無論 “&&” 后面是 true 還是 false坛怪,結(jié)果都將返 “&&” 前面的值;
- 只要 “&&” 前面是 true,無論 “&&” 后面是 true 還是 false股囊,結(jié)果都將返 “&&” 后面的值;
alert(0 || 1) 的結(jié)果是 1
- 只要 “||” 前面為 false袜匿,不管 “||” 后面是 true 還是 false,都返回 “||” 后面的值稚疹。
- 只要 “||” 前面為 true沉帮,不管 “||” 后面是 true 還是 false,都返回 “||” 前面的值贫堰。
只要記住 0 與 任何數(shù)都是 0穆壕,其他反推。
下面的輸出結(jié)果是 其屏?
var out = 25,
inner = {
out: 20,
func: function () {
var out = 30;
return this.out;
}
};
console.log((inner.func, inner.func)());
console.log(inner.func());
console.log((inner.func)());
console.log((inner.func = inner.func)());
結(jié)果:25喇勋,20,20偎行,25
代碼解析:這道題的考點(diǎn)分兩個
- 作用域
- 運(yùn)算符(賦值預(yù)算川背,逗號運(yùn)算)
先看第一個輸出:25,因為 ( inner.func, inner.func ) 是進(jìn)行逗號運(yùn)算符蛤袒,逗號運(yùn)算符就是運(yùn)算前面的 ”,“ 返回最后一個熄云,舉個栗子
var i = 0, j = 1, k = 2;
console.log((i++, j++, k)) // 返回的是 k 的值 2 ,如果寫成 k++ 的話 這里返回的就是 3
console.log(i); // 1
console.log(j); // 2
console.log(k); // 2
回到原題 ( inner.func, inner.func ) 就是返回 inner.func 妙真,而 inner.func 只是一個匿名函數(shù)
function () {
var out = 30;
return this.out;
}
而且這個匿名函數(shù)是屬于 window 的缴允,則變成了
(function () {
var out = 30;
return this.out;
})()
此刻的 this => window
所以 out 是 25。
第二和第三個 console.log 的作用域都是 inner珍德,也就是他們執(zhí)行的其實是 inner.func();
inner 作用域中是有 out 變量的练般,所以結(jié)果是 20。
第四個 console.log 考查的是一個等號運(yùn)算 inner.func = inner.func 锈候,其實返回的是運(yùn)算的結(jié)果薄料,
舉個栗子
var a = 2, b = 3;
console.log(a = b) // 輸出的是 3
所以 inner.func = inner.func 返回的也是一個匿名函數(shù)
function () {
var out = 30;
return this.out;
}
此刻,道理就和第一個 console.log 一樣了泵琳,輸出的結(jié)果是 25摄职。
下面程序輸出的結(jié)果是 誊役?
if (!("a" in window)) {
var a = 1;
}
alert(a);
代碼解析:如果 window 不包含屬性 a,就聲明一個變量 a谷市,然后賦值為 1势木。
你可能認(rèn)為 alert 出來的結(jié)果是 1,然后實際結(jié)果是 “undefined”歌懒。
要了解為什么啦桌,需要知道 JavaScript 里的 3 個概念。
首先及皂,所有的全局變量都是 window 的屬性甫男,語句 var a = 1; 等價于 window.a = 1;
你可以用如下方式來檢測全局變量是否聲明:"變量名稱" in window。
第二验烧,所有的變量聲明都在范圍作用域的頂部板驳,看一下相似的例子:
alert("b" in window);
var b;
此時,盡管聲明是在 alert 之后碍拆,alert 彈出的依然是 true若治,這是因為 JavaScript 引擎首先會掃描所有的變量聲明,然后將這些變量聲明移動到頂部感混,最終的代碼效果是這樣的:
var a;
alert("a" in window);
這樣看起來就很容易解釋為什么 alert 結(jié)果是 true 了端幼。
第三,你需要理解該題目的意思是弧满,變量聲明被提前了婆跑,但變量賦值沒有,因為這行代碼包括了變量聲明和變量賦值庭呜。
你可以將語句拆分為如下代碼:
var a; //聲明
a = 1; //初始化賦值
當(dāng)變量聲明和賦值在一起用的時候滑进,JavaScript 引擎會自動將它分為兩部以便將變量聲明提前,
不將賦值的步驟提前募谎,是因為他有可能影響代碼執(zhí)行出不可預(yù)期的結(jié)果扶关。
所以,知道了這些概念以后数冬,重新回頭看一下題目的代碼节槐,其實就等價于:
var a;
if (!("a" in window)) {
a = 1;
}
alert(a);
這樣,題目的意思就非常清楚了:首先聲明 a吉执,然后判斷 a 是否在存在疯淫,如果不存在就賦值為1,很明顯 a 永遠(yuǎn)在 window 里存在戳玫,這個賦值語句永遠(yuǎn)不會執(zhí)行,所以結(jié)果是 undefined未斑。
提前這個詞語顯得有點(diǎn)迷惑了咕宿,你可以理解為:預(yù)編譯。
下面程序輸出的結(jié)果是 ?
var a = 1;
var b = function a(x) {
x && a(--x);
};
alert(a);
這個題目看起來比實際復(fù)雜府阀,alert 的結(jié)果是 1缆镣。
這里依然有 3 個重要的概念需要我們知道。
- 首先试浙,第一個是
變量聲明在進(jìn)入執(zhí)行上下文就完成了
董瞻; - 第二個概念就是
函數(shù)聲明也是提前的,所有的函數(shù)聲明都在執(zhí)行代碼之前都已經(jīng)完成了聲明田巴,和變量聲明一樣
钠糊。
澄清一下,函數(shù)聲明是如下這樣的代碼:
function functionName(arg1, arg2){
//函數(shù)體
}
如下不是函數(shù)壹哺,而是函數(shù)表達(dá)式抄伍,相當(dāng)于變量賦值:
var functionName = function(arg1, arg2){
//函數(shù)體
};
澄清一下,函數(shù)表達(dá)式?jīng)]有提前管宵,就相當(dāng)于平時的變量賦值截珍。
- 第三需要知道的是,
函數(shù)聲明會覆蓋變量聲明箩朴,但不會覆蓋變量賦值
岗喉。
為了解釋這個,我們來看一個例子:
function value(){
return 1;
}
var value;
alert(typeof value); //"function"
盡管變量聲明在下面定義炸庞,但是變量 value 依然是 function沈堡,也就是說這種情況下,函數(shù)聲明的優(yōu)先級高于變量聲明的優(yōu)先級燕雁,但如果該變量 value 賦值了诞丽,那結(jié)果就完全不一樣了:
function value(){
return 1;
}
var value = 1;
alert(typeof value); //"number"
該 value 賦值以后,變量賦值初始化就覆蓋了函數(shù)聲明拐格。
重新回到題目僧免,這個函數(shù)其實是一個有名函數(shù)表達(dá)式,函數(shù)表達(dá)式不像函數(shù)聲明一樣可以覆蓋變量聲明捏浊,但你可以注意到懂衩,變量 b 是包含了該函數(shù)表達(dá)式,而該函數(shù)表達(dá)式的名字是 a金踪。不同的瀏覽器對 a 這個名詞處理有點(diǎn)不一樣浊洞,在 IE 里,會將 a 認(rèn)為函數(shù)聲明胡岔,所以它被變量初始化覆蓋了法希,就是說如果調(diào)用 a(–x) 的話就會出錯,而其它瀏覽器在允許在函數(shù)內(nèi)部調(diào)用 a(–x)靶瘸,因為這時候 a 在函數(shù)外面依然是數(shù)字苫亦。
基本上毛肋,IE 里調(diào)用 b(2) 的時候會出錯,但其它瀏覽器則返回 undefined屋剑。
理解上述內(nèi)容之后润匙,該題目換成一個更準(zhǔn)確和更容易理解的代碼應(yīng)該像這樣:
var a = 1,
b = function(x) {
x && b(--x);
};
alert(a);
這樣的話,就很清晰地知道為什么 alert 的總是 1 了唉匾。
下面程序輸出的結(jié)果是 孕讳?
function a(x) {
return x * 2;
}
var a;
alert(a);
alert 的值是下面的函數(shù)
function a(x) {
return x * 2;
}
這個題目比較簡單:即函數(shù)聲明和變量聲明的關(guān)系和影響,遇到同名的函數(shù)聲明巍膘,不會重新定義厂财。
下面程序輸出的結(jié)果是 ?
function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2, 3);
結(jié)果為 10典徘。
活動對象是在進(jìn)入函數(shù)上下文時刻被創(chuàng)建的蟀苛,它通過函數(shù)的 arguments 屬性初始化。
三道判斷輸出的題都是經(jīng)典的題
var a = 4;
function b() {
a = 3;
console.log(a);
function a(){};
}
b();
明顯輸出是 3逮诲,因為里面修改了 a 這個全局變量帜平,那個 function a(){} 是用來干擾的,雖然函數(shù)聲明會提升梅鹦,就被 a 給覆蓋掉了裆甩,這是我的理解。
不記得具體的齐唆,就類似如下
var baz = 3;
var bazz ={
baz: 2,
getbaz: function() {
return this.baz
}
}
console.log(bazz.getbaz())
var g = bazz.getbaz;
console.log(g()) ;
第一個輸出是 2嗤栓,第二個輸出是 3。
這題考察的就是 this 的指向箍邮,函數(shù)作為對象本身屬性調(diào)用的時候茉帅,this 指向?qū)ο螅鳛槠胀ê瘮?shù)調(diào)用的時候锭弊,就指向全局了堪澎。
還有下面的題:
var arr = [1,2,3,4,5];
for(var i = 0; i < arr.length; i++){
arr[i] = function(){
alert(i)
}
}
arr[3]();
典型的閉包,彈出 5 味滞。
JavaScript 里有哪些數(shù)據(jù)類型
一樱蛤、數(shù)據(jù)類型
- undefiend 沒有定義數(shù)據(jù)類型
- number 數(shù)值數(shù)據(jù)類型,例如 10 或者 1 或者 5.5
- string 字符串?dāng)?shù)據(jù)類型用來描述文本剑鞍,例如 "你的姓名"
- boolean 布爾類型 true | false 昨凡,不是正就是反
- object 對象類型,復(fù)雜的一組描述信息的集合
- function 函數(shù)類型
解釋清楚 null 和 undefined
null 用來表示尚未存在的對象蚁署,常用來表示函數(shù)企圖返回一個不存在的對象便脊。 null 表示"沒有對象",即該處不應(yīng)該有值形用。
null 典型用法是:
- 作為函數(shù)的參數(shù)就轧,表示該函數(shù)的參數(shù)不是對象证杭。
- 作為對象原型鏈的終點(diǎn)田度。
當(dāng)聲明的變量還未被初始化時妒御,變量的默認(rèn)值為 undefined。 undefined 表示"缺少值"镇饺,就是此處應(yīng)該有一個值乎莉,但是還沒有定義。
- 變量被聲明了奸笤,但沒有賦值時惋啃,就等于 undefined。
- 調(diào)用函數(shù)時监右,應(yīng)該提供的參數(shù)沒有提供边灭,該參數(shù)等于 undefined。
- 對象沒有賦值的屬性健盒,該屬性的值為 undefined绒瘦。
- 函數(shù)沒有返回值時,默認(rèn)返回 undefined扣癣。
未定義的值和定義未賦值的為 undefined惰帽,null 是一種特殊的 object,NaN 是一種特殊的 number父虑。
講一下 1 和 Number(1) 的區(qū)別*
- 1 是一個原始定義好的 number 類型该酗;
- Number(1) 是一個函數(shù)類型,是我們自己聲明的一個函數(shù)(方法)士嚎。
講一下 prototype 是什么東西呜魄,原型鏈的理解,什么時候用 prototype 莱衩?
prototype 是函數(shù)對象上面預(yù)設(shè)的對象屬性爵嗅。
函數(shù)里的 this 什么含義,什么情況下膳殷,怎么用 操骡?
- this 是 Javascript 語言的一個關(guān)鍵字。
- 它代表函數(shù)運(yùn)行時赚窃,自動生成的一個內(nèi)部對象册招,只能在函數(shù)內(nèi)部使用。
- 隨著函數(shù)使用場合的不同勒极,this 的值會發(fā)生變化是掰。
- 但是有一個總的原則,那就是
this 指的是辱匿,調(diào)用函數(shù)的那個對象
键痛。
情況一:純粹的函數(shù)調(diào)用
這是函數(shù)的最通常用法炫彩,屬于全局性調(diào)用,因此 this 就代表全局對象 window
絮短。
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
為了證明 this 就是全局對象江兢,我對代碼做一些改變:
var x = 1;
function test(){
alert(this.x);
}
test(); // 1
運(yùn)行結(jié)果還是 1。
再變一下:
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x); // 0
情況二:作為對象方法的調(diào)用
函數(shù)還可以作為某個對象的方法調(diào)用丁频,這時 this 就指這個上級對象
杉允。
function test(){
alert(this.x);
}
var x = 2
var o = {};
o.x = 1;
o.m = test;
o.m(); // 1
情況三: 作為構(gòu)造函數(shù)調(diào)用
所謂構(gòu)造函數(shù),就是通過這個函數(shù)生成一個新對象(object)席里。這時的 this 就指這個新對象叔磷。
function Test(){
this.x = 1;
}
var o = new Test();
alert(o.x); // 1
運(yùn)行結(jié)果為 1。為了表明這時 this 不是全局對象奖磁,對代碼做一些改變:
var x = 2;
function Test(){
this.x = 1;
}
var o = new Test();
alert(x); // 2
運(yùn)行結(jié)果為 2改基,表明全局變量 x 的值沒變。
情況四: apply 調(diào)用
apply() 是函數(shù)對象的一個方法咖为,它的作用是改變函數(shù)的調(diào)用對象秕狰,它的第一個參數(shù)就表示改變后的調(diào)用這個函數(shù)的對象。因此案疲,this 指的就是這第一個參數(shù)封恰。
var x = 0;
function test(){
alert(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m.apply(); // 0
apply() 的參數(shù)為空時,默認(rèn)調(diào)用全局對象褐啡。因此诺舔,這時的運(yùn)行結(jié)果為 0,證明 this 指的是全局對象备畦。
如果把最后一行代碼修改為
o.m.apply(o); // 1
運(yùn)行結(jié)果就變成了 1低飒,證明了這時 this 代表的是對象 o。
apply 和 call 什么含義懂盐,什么區(qū)別 褥赊?什么時候用 ?
call莉恼,apply 都屬于 Function.prototype 的一個方法拌喉,它是 JavaScript 引擎內(nèi)在實現(xiàn)的,因為屬于 Function.prototype俐银,所以每個 Function 對象實例(就是每個方法)都有 call尿背,apply 屬性。
既然作為方法的屬性捶惜,那它們的使用就當(dāng)然是針對方法的了田藐,這兩個方法是容易混淆的,因為它們的作用一樣,只是使用方式不同汽久。
語法:
foo.call(this, arg1, arg2, arg3) == foo.apply(this, arguments) == this.foo(arg1, arg2, arg3);
- 相同點(diǎn):兩個方法產(chǎn)生的作用是完全一樣的鹤竭。
- 不同點(diǎn):方法傳遞的參數(shù)不同。
每個函數(shù)對象會有一些方法可以去修改函數(shù)執(zhí)行時里面的 this景醇,比較常見得到就是 call 和 apply臀稚,通過 call 和 apply 可以重新定義函數(shù)的執(zhí)行環(huán)境,即 this 的指向啡直。
function add(c, d) {
console.log(this.a + this.b + c + d);
}
var o = { a: 1, b: 3 };
add.call(o, 5, 7); //1+3+5+7=16
//傳參的時候是扁平的把每個參數(shù)傳進(jìn)去
add.apply(o, [10, 20]); //1+3+10+20=34
//傳參的時候是把參數(shù)作為一個數(shù)組傳進(jìn)去
//什么時候使用 call 或者 apply
function bar() {
console.log(Object.prototype.toString.call(this));
// 用來調(diào)用一些無法直接調(diào)用的方法
}
bar.call(7); // "[object Number]"
異步過程的構(gòu)成要素有哪些烁涌?和異步過程是怎樣的 苍碟?
總結(jié)一下酒觅,一個異步過程通常是這樣的:
- 主線程發(fā)起一個異步請求,相應(yīng)的工作線程接收請求并告知主線程已收到(異步函數(shù)返回)微峰;
- 主線程可以繼續(xù)執(zhí)行后面的代碼舷丹,同時工作線程執(zhí)行異步任務(wù);
- 工作線程完成工作后蜓肆,通知主線程颜凯;
- 主線程收到通知后,執(zhí)行一定的動作(調(diào)用回調(diào)函數(shù))仗扬。
- 異步函數(shù)通常具有以下的形式:A(args..., callbackFn)症概。
- 它可以叫做異步過程的發(fā)起函數(shù),或者叫做異步任務(wù)注冊函數(shù)早芭。
- args 和 callbackFn 是這個函數(shù)的參數(shù)彼城。
所以,從主線程的角度看退个,一個異步過程包括下面兩個要素:
- 發(fā)起函數(shù)(或叫注冊函數(shù)) A募壕。
- 回調(diào)函數(shù) callbackFn。
它們都是在主線程上調(diào)用的语盈,其中注冊函數(shù)用來發(fā)起異步過程舱馅,回調(diào)函數(shù)用來處理結(jié)果。
舉個具體的例子:
setTimeout(fn, 1000);
其中的 setTimeout 就是異步過程的發(fā)起函數(shù)刀荒,fn 是回調(diào)函數(shù)代嗤。
注意:前面說的形式 A(args..., callbackFn) 只是一種抽象的表示,并不代表回調(diào)函數(shù)一定要作為發(fā)起函數(shù)的參數(shù)缠借。
例如:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回調(diào)函數(shù)
xhr.open('GET', url);
xhr.send(); // 發(fā)起函數(shù)
發(fā)起函數(shù)和回調(diào)函數(shù)就是分離的干毅。
說說消息隊列和事件循環(huán)
- 主線程在執(zhí)行完當(dāng)前循環(huán)中的所有代碼后,就會到消息隊列取出這條消息(也就是 message 函數(shù))烈炭,并執(zhí)行它溶锭。
- 完成了工作線程對主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行符隙。
- 如果一開始主線程就沒有提供回調(diào)函數(shù)趴捅,AJAX 線程在收到 HTTP 響應(yīng)后垫毙,也就沒必要通知主線程,從而也沒必要往消息隊列放消息拱绑。
異步過程的回調(diào)函數(shù)综芥,一定不在當(dāng)前的這一輪事件循環(huán)中執(zhí)行。
session 與 cookie 的區(qū)別
- session 保存在服務(wù)器猎拨,客戶端不知道其中的信息膀藐;
- cookie 保存在客戶端,服務(wù)器能夠知道其中的信息红省。
- session 中保存的是對象额各,cookie 中保存的是字符串。
- session 不能區(qū)分路徑吧恃,同一個用戶在訪問一個網(wǎng)站期間虾啦,所有的 session 在任何一個地方都可以訪問到。
- 而 cookie 中如果設(shè)置了路徑參數(shù)痕寓,那么同一個網(wǎng)站中不同路徑下的 cookie 互相是訪問不到的傲醉。
cookies 是干嘛的,服務(wù)器和瀏覽器之間的 cookies 是怎么傳的呻率,httponly 的 cookies 和可讀寫的 cookie 有什么區(qū)別硬毕,有無長度限制 ?
- cookies 是一些存儲在用戶電腦上的小文件。
- 它是被設(shè)計用來保存一些站點(diǎn)的用戶數(shù)據(jù)礼仗,這樣能夠讓服務(wù)器為這樣的用戶定制內(nèi)容吐咳,后者頁面代碼能夠獲取到 cookie 值然后發(fā)送給服務(wù)器。
- 比如 cookie 中存儲了所在地理位置藐守,以后每次進(jìn)入地圖就默認(rèn)定位到改地點(diǎn)即可挪丢。
請描述一下 cookies,sessionStorage 和 localStorage 的區(qū)別
共同點(diǎn)
- 都是保存在瀏覽器端卢厂,且同源的乾蓬。
區(qū)別
- cookie 數(shù)據(jù)始終在同源的 http 請求中攜帶(即使不需要),即 cookie 在瀏覽器和服務(wù)器間來回傳遞慎恒。
- 而 sessionStorage 和 localStorage 不會自動把數(shù)據(jù)發(fā)給服務(wù)器任内,僅在本地保存。
- cookie 數(shù)據(jù)還有路徑(path)的概念融柬,可以限制 cookie 只屬于某個路徑下死嗦。
- 存儲大小限制也不同,cookie 數(shù)據(jù)不能超過 4k粒氧,同時因為每次 http 請求都會攜帶 cookie越除,所以 cookie 只適合保存很小的數(shù)據(jù),如會話標(biāo)識。
- sessionStorage 和 localStorage 雖然也有存儲大小的限制摘盆,但比 cookie 大得多翼雀,可以達(dá)到 5M 或更大。
- 數(shù)據(jù)有效期不同孩擂,sessionStorage:僅在當(dāng)前瀏覽器窗口關(guān)閉前有效狼渊,自然也就不可能持久保持;localStorage:始終有效类垦,窗口或瀏覽器關(guān)閉也一直保存狈邑,因此用作持久數(shù)據(jù);cookie 只在設(shè)置的 cookie 過期時間之前一直有效蚤认,即使窗口或瀏覽器關(guān)閉米苹。
- 作用域不同,sessionStorage 在不同的瀏覽器窗口中
不共享
烙懦,即使是同一個頁面驱入;cookie 和 localStorage 在所有同源窗口中都是共享的。
從敲入 URL 到渲染完成的整個過程氯析,包括 DOM 構(gòu)建的過程,說的約詳細(xì)越好
- 用戶輸入 url 地址莺褒,瀏覽器根據(jù)域名尋找 IP 地址
- 瀏覽器向服務(wù)器發(fā)送 http 請求掩缓,如果服務(wù)器段返回以 301 之類的重定向,瀏覽器根據(jù)相應(yīng)頭中的 location 再次發(fā)送請求
- 服務(wù)器端接受請求遵岩,處理請求生成 html 代碼你辣,返回給瀏覽器,這時的 html 頁面代碼可能是經(jīng)過壓縮的
- 瀏覽器接收服務(wù)器響應(yīng)結(jié)果尘执,如果有壓縮則首先進(jìn)行解壓處理舍哄,緊接著就是頁面解析渲染
- 解析渲染該過程主要分為以下步驟:解析 HTML、構(gòu)建 DOM 樹誊锭、DOM 樹與 CSS 樣式進(jìn)行附著構(gòu)造呈現(xiàn)樹
- 布局
- 繪制
是否了解公鑰加密和私鑰加密表悬。如何確保表單提交里的密碼字段不被泄露。
公鑰用于對數(shù)據(jù)進(jìn)行加密丧靡,私鑰用于對數(shù)據(jù)進(jìn)行解密蟆沫。
很直觀的理解:公鑰就是公開的密鑰,其公開了大家才能用它來加密數(shù)據(jù)温治。私鑰是私有的密鑰饭庞,誰有這個密鑰才能夠解密密文。
解決方案 1:
form 在提交的過程中熬荆,對密碼字段是不進(jìn)行加密而是以明碼的形式進(jìn)行數(shù)據(jù)傳輸?shù)摹?br> 如果要對數(shù)據(jù)進(jìn)行加密舟山,你可以自己寫一個腳本對內(nèi)容進(jìn)行編碼后傳輸,只是這個安全性也并不高。
解決方案 2:
如果想對數(shù)據(jù)進(jìn)行加密累盗,你可以使用 HTTPS 安全傳輸協(xié)議六孵,這個協(xié)議是由系統(tǒng)進(jìn)行密碼加密處理的,在數(shù)據(jù)傳輸中是絕對不會被攔截獲取的幅骄,只是 HTTPS 的架設(shè)會相對麻煩點(diǎn)劫窒。一些大型網(wǎng)站的登錄、銀行的在線網(wǎng)關(guān)等都是走這條路拆座。
驗證碼是干嘛的主巍,是為了解決什么安全問題嵌削。
所謂驗證碼道盏,就是將一串隨機(jī)產(chǎn)生的數(shù)字或符號姻灶,生成一幅圖片探赫, 圖片里加上一些干擾象素(防止OCR)染坯,由用戶肉眼識別其中的驗證碼信息累奈,輸入表單提交網(wǎng)站驗證榕莺,驗證成功后才能使用某項功能喷兼。
- 驗證碼一般是防止批量注冊的菇绵,人眼看起來都費(fèi)勁肄渗,何況是機(jī)器。
- 像百度貼吧未登錄發(fā)貼要輸入驗證碼大概是防止大規(guī)模匿名回帖的發(fā)生咬最。
- 目前翎嫡,不少網(wǎng)站為了防止用戶利用機(jī)器人自動注冊、登錄永乌、灌水惑申,都采用了驗證碼技術(shù)。
截取字符串 abcdefg 的 efg翅雏。
從第四位開始截取
alert('abcdefg'.substring(4));
alert ('abcdefg'.slice(4))
判斷一個字符串中出現(xiàn)次數(shù)最多的字符圈驼,統(tǒng)計這個次數(shù)
步驟
- 將字符串轉(zhuǎn)化數(shù)組
- 創(chuàng)建一個對象
- 遍歷數(shù)組,判斷對象中是否存在數(shù)組中的值望几,如果存在值 +1绩脆,不存在賦值為 1
- 定義兩個變量存儲字符值,字符出現(xiàn)的字?jǐn)?shù)
var str = 'abaasdffggghhjjkkgfddsssss3444343';
// 1.將字符串轉(zhuǎn)換成數(shù)組
var newArr = str.split("");
// 2.創(chuàng)建一個對象
var json = {};
// 3\. 所有字母出現(xiàn)的次數(shù)橄妆,判斷對象中是否存在數(shù)組中的值衙伶,如果存在值 +1,不存在賦值為 1
for(var i = 0; i < newArr.length; i++){
// 類似:json : { ‘a(chǎn)’: 3, ’b’: 1 }
if(json[newArr[i]]){
json[newArr[i]] +=1;
} else {
json[newArr[i]] = 1;
}
}
// 4 定義兩個變量存儲字符值害碾,字符出現(xiàn)的字?jǐn)?shù)
var num = 0 ; //次數(shù)
var element = ""; //最多的項
for(var k in json){
if(json[k] > num){
num = json[k];
element = k ;
}
}
console.log("出現(xiàn)次數(shù):"+num +"最多的字符:"+ element);
document.write 和 innerHTML 的區(qū)別
- document.write 是直接寫入到頁面的內(nèi)容流矢劲,如果在寫之前沒有調(diào)用 document.open, 瀏覽器會自動調(diào)用 open。每次寫完關(guān)閉之后重新調(diào)用該函數(shù)慌随,會導(dǎo)致頁面被重寫芬沉。
- innerHTML 則是 DOM 頁面元素的一個屬性躺同,代表該元素的 html 內(nèi)容。你可以精確到某一個具體的元素來進(jìn)行更改丸逸。如果想修改 document 的內(nèi)容蹋艺,則需要修改 document.documentElement.innerElement。
- innerHTML 將內(nèi)容寫入某個 DOM 節(jié)點(diǎn)黄刚,不會導(dǎo)致頁面全部重繪捎谨。
- innerHTML 很多情況下都優(yōu)于 document.write,其原因在于其允許更精確的控制要刷新頁面的那一個部分憔维。
- document.write 是重寫整個 document, 寫入內(nèi)容是字符串的 html涛救;innerHTML 是 HTMLElement 的屬性,是一個元素的內(nèi)部 html 內(nèi)容
JS 識別不同瀏覽器信息
function myBrowser() {
var userAgent = navigator.userAgent; //取得瀏覽器的userAgent字符串
var isOpera = userAgent.indexOf("Opera") > -1;
if (isOpera) {
return "Opera"
}; //判斷是否Opera瀏覽器
if (userAgent.indexOf("Firefox") > -1) {
return "Firefox";
} //判斷是否Firefox瀏覽器
if (userAgent.indexOf("Chrome") > -1) {
return "Chrome";
} //判斷是否Google瀏覽器
if (userAgent.indexOf("Safari") > -1) {
return "Safari";
} //判斷是否Safari瀏覽器
if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) {
return "IE";
}; //判斷是否IE瀏覽器
}
JavaScript 常見的內(nèi)置對象
有 Object业扒、Math检吆、String、Array程储、Number蹭沛、Function、Boolean章鲤、JSON 等摊灭,其中 Object 是所有對象的基類,采用了原型繼承方式咏窿。
編寫一個方法斟或,求一個字符串的字節(jié)長度
假設(shè):一個英文字符占用一個字節(jié),一個中文字符占用兩個字節(jié)
function getBytes(str){
var len = str.length;
var bytes = len;
for(var i = 0; i < len; i++){
if (str.charCodeAt(i) > 255) bytes++;
}
return bytes;
}
alert(getBytes("你好,as"));
JS 組成
- 核心(ECMAScript) 描述了該語言的語法和基本對象
- 文檔對象模型(DOM) 描述了處理網(wǎng)頁內(nèi)容的方法和接口
- 瀏覽器對象模型(BOM) 描述了與瀏覽器進(jìn)行交互的方法和接口
new 操作符具體干了什么呢 ?
- 創(chuàng)建一個空對象集嵌,并且 this 變量引用該對象,同時還繼承了該函數(shù)的原型御毅。
- 屬性和方法被加入到 this 引用的對象中根欧。
- 新創(chuàng)建的對象由 this 所引用,并且最后隱式的返回 this 端蛆。
JSON 的了解凤粗?
- JSON(JavaScript Object Notation) 是一種輕量級的數(shù)據(jù)交換格式。
- 它是基于 JavaScript 的一個子集今豆。
- 數(shù)據(jù)格式簡單嫌拣,易于讀寫,占用帶寬小呆躲。
- 格式:采用鍵值對异逐。例如:{ “age?: ?12?, ”name?: ?back? }
你有哪些性能優(yōu)化的方法 ?
web 前端是應(yīng)用服務(wù)器處理之前的部分插掂,前端主要包括:HTML灰瞻、CSS腥例、javascript、image 等各種資源酝润,針對不同的資源有不同的優(yōu)化方式燎竖。
內(nèi)容優(yōu)化
- 減少 HTTP 請求數(shù)。這條策略是最重要最有效的要销,因為一個完整的請求要經(jīng)過 DNS 尋址构回,與服務(wù)器建立連接,發(fā)送數(shù)據(jù)疏咐,等待服務(wù)器響應(yīng)纤掸,接收數(shù)據(jù)這樣一個消耗時間成本和資源成本的復(fù)雜的過程。
常見方法:合并多個 CSS 文件和 js 文件凳鬓,利用 CSS Sprites 整合圖像茁肠,Inline Images (使用 data:URL scheme 在實際的頁面嵌入圖像數(shù)據(jù) ),合理設(shè)置 HTTP 緩存等缩举。
- 減少 DNS 查找
- 避免重定向
- 使用 Ajax 緩存
- 延遲加載組件垦梆,預(yù)加載組件
- 減少 DOM 元素數(shù)量。頁面中存在大量 DOM 元素仅孩,會導(dǎo)致 javascript 遍歷 DOM 的效率變慢托猩。
- 最小化 iframe 的數(shù)量。iframes 提供了一個簡單的方式把一個網(wǎng)站的內(nèi)容嵌入到另一個網(wǎng)站中辽慕。但其創(chuàng)建速度比其他包括 JavaScript 和 CSS 的 DOM 元素的創(chuàng)建慢了 1-2 個數(shù)量級京腥。
- 避免 404。HTTP 請求時間消耗是很大的溅蛉,因此使用 HTTP 請求來獲得一個沒有用處的響應(yīng)(例如 404 沒有找到頁面)是完全沒有必要的公浪,它只會降低用戶體驗而不會有一點(diǎn)好處。
服務(wù)器優(yōu)化
- 使用內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)船侧。把網(wǎng)站內(nèi)容分散到多個欠气、處于不同地域位置的服務(wù)器上可以加快下載速度。
- GZIP 壓縮
- 設(shè)置 ETag:ETags(Entity tags镜撩,實體標(biāo)簽)是 web 服務(wù)器和瀏覽器用于判斷瀏覽器緩存中的內(nèi)容和服務(wù)器中的原始內(nèi)容是否匹配的一種機(jī)制预柒。
- 提前刷新緩沖區(qū)
- 對 Ajax 請求使用 GET 方法
- 避免空的圖像 src
Cookie 優(yōu)化
- 減小 Cookie 大小
- 針對 Web 組件使用域名無關(guān)的 Cookie
CSS 優(yōu)化
- 將 CSS 代碼放在 HTML 頁面的頂部
- 避免使用 CSS 表達(dá)式
- 使用 < link> 來代替 @import
- 避免使用 Filters
javascript 優(yōu)化
- 將 JavaScript 腳本放在頁面的底部。
- 將 JavaScript 和 CSS 作為外部文件來引用袁梗。
在實際應(yīng)用中使用外部文件可以提高頁面速度宜鸯,因為 JavaScript 和 CSS 文件都能在瀏覽器中產(chǎn)生緩存。
- 縮小 JavaScript 和 CSS
- 刪除重復(fù)的腳本
- 最小化 DOM 的訪問遮怜。使用 JavaScript 訪問 DOM 元素比較慢淋袖。
- 開發(fā)智能的事件處理程序
- javascript 代碼注意:謹(jǐn)慎使用 with,避免使用 eval Function 函數(shù)奈泪,減少作用域鏈查找适贸。
圖像優(yōu)化
- 優(yōu)化圖片大小
- 通過 CSS Sprites 優(yōu)化圖片
- 不要在 HTML 中使用縮放圖片
- favicon.ico 要小而且可緩存
JS 格式化數(shù)字(每三位加逗號)
從后往前取灸芳。
function toThousands(num) {
var num = (num || 0).toString(), result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num) { result = num + result; }
return result;
}
合并數(shù)組
如果你需要合并兩個數(shù)組的話,可以使用 Array.concat()
var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
console.log(array1.concat(array2)); // [1,2,3,4,5,6];
然而拜姿,這個函數(shù)并不適用于合并大的數(shù)組烙样,因為它需要創(chuàng)建一個新的數(shù)組,而這會消耗很多內(nèi)存蕊肥。
這時谒获,你可以使用 Array.push.apply(arr1, arr2) 來代替創(chuàng)建新的數(shù)組,它可以把第二個數(shù)組合并到第一個中壁却,從而較少內(nèi)存消耗批狱。
var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
console.log(array1.push.apply(array1, array2)); // [1, 2, 3, 4, 5, 6]
把節(jié)點(diǎn)列表 (NodeList) 轉(zhuǎn)換為數(shù)組
如果你運(yùn)行 document.querySelectorAll("p") 方法,它可能會返回一個 DOM 元素的數(shù)組 — 節(jié)點(diǎn)列表對象展东。
但這個對象并不具有數(shù)組的全部方法赔硫,如 sort(),reduce()盐肃, map()爪膊,filter()。
為了使用數(shù)組的那些方法砸王,你需要把它轉(zhuǎn)換為數(shù)組推盛。
只需使用 [].slice.call(elements) 即可實現(xiàn):
var elements = document.querySelectorAll("p"); // NodeList
var arrayElements = [].slice.call(elements); // 現(xiàn)在 NodeList 是一個數(shù)組
var arrayElements = Array.from(elements); // 這是另一種轉(zhuǎn)換 NodeList 到 Array 的方法
打亂數(shù)組元素的順序
不適用 Lodash 等這些庫打亂數(shù)組元素順序,你可以使用這個技巧:
var list = [1, 2, 3];
console.log(list.sort(function() { Math.random() - 0.5 })); // [2, 1, 3]
js 的 ready 和 onload 事件的區(qū)別
- onload 是等 HTML 的所有資源都加載完成后再執(zhí)行 onload 里面的內(nèi)容谦铃,所有資源包括 DOM 結(jié)構(gòu)耘成、圖片、視頻 等資源;
- ready 是當(dāng) DOM 結(jié)構(gòu)加載完成后就可以執(zhí)行了驹闰,相當(dāng)于 jQuery 中的 $(function(){ js 代碼 });
- 另外瘪菌,onload 只能有一個,ready 可以有多個嘹朗。
js 的兩種回收機(jī)制
標(biāo)記清除(mark and sweep)
從語義上理解就比較好理解了控嗜,大概就是當(dāng)變量進(jìn)入到某個環(huán)境中的時候就把這個變量標(biāo)記一下,比如標(biāo)記為“進(jìn)入環(huán)境”骡显,當(dāng)離開的時候就把這個變量的標(biāo)記給清除掉,比如是“離開環(huán)境”曾掂。而在這后面還有標(biāo)記的變量將被視為準(zhǔn)備刪除的變量惫谤。
- 垃圾收集器在運(yùn)行的時候會給存儲在內(nèi)存中的所有變量都加上標(biāo)記(可以使用任何標(biāo)記方式)。
- 然后珠洗,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記溜歪。
- 而在此之后再被加上的標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了许蓖。
- 最后蝴猪,垃圾收集器完成內(nèi)存清除工作调衰。銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。
這是 javascript 最常見的垃圾回收方式自阱。至于上面有說道的標(biāo)記嚎莉,到底該如何標(biāo)記 ?
好像是有很多方法沛豌,比如特殊位翻轉(zhuǎn)趋箩,維護(hù)一個列表什么的。
引用計數(shù)(reference counting)
- 引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)加派,當(dāng)聲明一個變量并將一個引用類型的值賦給該變量時叫确,這個時候的引用類型的值就會是引用次數(shù) +1 了。如果同一個值又被賦給另外一個變量芍锦,則該值的引用次數(shù)又 +1竹勉。
- 相反如果包含這個值的引用的變量又取得另外一個值,即被重新賦了值娄琉,那么這個值的引用就 -1 次乓。當(dāng)這個值的引用次數(shù)編程 0 時,表示沒有用到這個值车胡,這個值也無法訪問檬输,因此環(huán)境就會收回這個值所占用的內(nèi)存空間回收。
- 這樣匈棘,當(dāng)垃圾收集器下次再運(yùn)行時丧慈,它就會釋放引用次數(shù)為 0 的值所占用的內(nèi)存。
對于新人來說主卫,JavaScript 的原型是一個很讓人頭疼的事情逃默,一來 prototype 容易與 ****proto**** 混淆,
一簇搅、prototype 和 ****proto**** 的區(qū)別
var a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object {}
var b = function(){}
console.log(b.prototype); //b {}
console.log(b.__proto__); //function() {}
結(jié)果:
/*1完域、字面量方式*/
var a = {};
console.log("a.__proto__ :", a.__proto__); // Object {}
console.log("a.__proto__ === a.constructor.prototype:", a.__proto__ === a.constructor.prototype); // true
/*2、構(gòu)造器方式*/
var A = function(){};
var a2 = new A();
console.log("a2.__proto__:", a2.__proto__); // A {}
console.log("a2.__proto__ === a2.constructor.prototype:", a2.__proto__ === a2.constructor.prototype); // true
/*3瘩将、Object.create()方式*/
var a4 = { a: 1 }
var a3 = Object.create(a4);
console.log("a3.__proto__:", a3.__proto__); // Object {a: 1}
console.log("a3.__proto__ === a3.constructor.prototype:", a3.__proto__ === a3.constructor.prototype); // false(此處即為圖1中的例外情況)
結(jié)果:
var A = function(){};
var a = new A();
console.log(a.__proto__); // A {}(即構(gòu)造器 function A 的原型對象)
console.log(a.__proto__.__proto__); // Object {}(即構(gòu)造器 function Object 的原型對象)
console.log(a.__proto__.__proto__.__proto__); // null
結(jié)果:
閉包的理解 吟税?
一、變量的作用域
要理解閉包姿现,首先必須理解 Javascript 特殊的變量作用域肠仪。
變量的作用域無非就是兩種:全局變量和局部變量。
Javascript語言的特殊之處备典,就在于函數(shù)內(nèi)部可以直接讀取全局變量异旧。
var n = 999;
function f1(){
alert(n);
}
f1(); // 999
另一方面,在函數(shù)外部自然無法讀取函數(shù)內(nèi)的局部變量提佣。
function f1(){
var n = 999;
}
alert(n); // error
這里有一個地方需要注意吮蛹,函數(shù)內(nèi)部聲明變量的時候荤崇,一定要使用 var 命令。
如果不用的話潮针,你實際上聲明了一個全局變量术荤!
function f1(){
n = 999;
}
f1();
alert(n); // 999
二、如何從外部讀取局部變量 然低?
function f1() {
var n = 999;
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); // 999
既然 f2 可以讀取 f1 中的局部變量喜每,那么只要把 f2 作為返回值,我們不就可以在 f1 外部讀取它的內(nèi)部變量了嗎雳攘!
三带兜、閉包的概念
上一節(jié)代碼中的 f2 函數(shù),就是閉包吨灭。
我的理解是刚照,閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)
。
由于在 Javascript 語言中喧兄,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量无畔,因此可以把閉包簡單理解成 定義在一個函數(shù)內(nèi)部的函數(shù)
。
所以吠冤,在本質(zhì)上浑彰,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁
。
四拯辙、閉包的用途
閉包可以用在許多地方郭变。它的最大用處有兩個,一個是前面提到的可以讀取函數(shù)內(nèi)部的變量涯保,另一個就是讓這些變量的值始終保持在內(nèi)存中诉濒。
怎么來理解呢 ?請看下面的代碼夕春。
function f1() {
var n = 999;
nAdd = function () { n += 1 }
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
在這段代碼中未荒,result 實際上就是閉包 f2 函數(shù)。它一共運(yùn)行了兩次及志,第一次的值是 999片排,第二次的值是 1000。這證明了速侈,函數(shù) f1 中的局部變量 n 一直保存在內(nèi)存中划纽,并沒有在 f1 調(diào)用后被自動清除。
為什么會這樣呢 锌畸?
原因就在于 f1 是 f2 的父函數(shù),而 f2 被賦給了一個全局變量靖避,這導(dǎo)致 f2 始終在內(nèi)存中潭枣,而 f2 的存在依賴于 f1比默,因此 f1 也始終在內(nèi)存中,不會在調(diào)用結(jié)束后盆犁,被垃圾回收機(jī)制(garbage collection)回收命咐。
這段代碼中另一個值得注意的地方,就是
- "nAdd=function(){ n+=1 }" 這一行谐岁,首先在 nAdd 前面沒有使用 var 關(guān)鍵字醋奠,因此 nAdd 是一個全局變量,而不是局部變量伊佃。
- 其次窜司,nAdd 的值是一個匿名函數(shù)(anonymous function),而這個匿名函數(shù)本身也是一個閉包航揉,所以 nAdd 相當(dāng)于是一個 setter塞祈,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進(jìn)行操作。
五帅涂、使用閉包的注意點(diǎn)
- 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中议薪,內(nèi)存消耗很大,所以不能濫用閉包媳友,否則會造成網(wǎng)頁的性能問題斯议,在 IE 中可能導(dǎo)致內(nèi)存泄露。解決方法是醇锚,在退出函數(shù)之前哼御,將不使用的局部變量全部刪除。
- 閉包會在父函數(shù)外部搂抒,改變父函數(shù)內(nèi)部變量的值艇搀。所以,如果你把父函數(shù)當(dāng)作對象(object)使用求晶,把閉包當(dāng)作它的公用方法(Public Method)焰雕,把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時一定要小心芳杏,不要隨便改變父函數(shù)內(nèi)部變量的值矩屁。
閉包面試經(jīng)典問題
問題:想每次點(diǎn)擊對應(yīng)目標(biāo)時彈出對應(yīng)的數(shù)字下標(biāo) 0~4 ,但實際是無論點(diǎn)擊哪個目標(biāo)都會彈出數(shù)字 5爵赵。
function onMyLoad() {
var arr = document.getElementsByTagName("p");
for (var i = 0; i < arr.length; i++) {
arr[i].onclick = function () {
alert(i);
}
}
}
問題所在:arr 中的每一項的 onclick 均為一個函數(shù)實例(Function 對象)吝秕,這個函數(shù)實例也產(chǎn)生了一個閉包域,這個閉包域引用了外部閉包域的變量空幻,其 function scope 的 closure 對象有個名為 i 的引用烁峭,外部閉包域的私有變量內(nèi)容發(fā)生變化,內(nèi)部閉包域得到的值自然會發(fā)生改變。
解決辦法一
解決思路:增加若干個對應(yīng)的閉包域空間(這里采用的是匿名函數(shù))约郁,專門用來存儲原先需要引用的內(nèi)容(下標(biāo))缩挑,不過只限于基本類型(基本類型值傳遞,對象類型引用傳遞)鬓梅。
//聲明一個匿名函數(shù)供置,若傳進(jìn)來的是基本類型則為值傳遞,故不會對實參產(chǎn)生影響,
//該函數(shù)對象有一個本地私有變量 arg(形參) 绽快,該函數(shù)的 function scope 的 closure 對象屬性有兩個引用芥丧,一個是 arr,一個是 i
//盡管引用 i 的值隨外部改變 坊罢,但本地私有變量(形參) arg 不會受影響续担,其值在一開始被調(diào)用的時候就決定了
for (var i = 0; i < arr.length; i++) {
(function (arg) {
arr[i].onclick = function () {
// onclick 函數(shù)實例的 function scope 的 closure 對象屬性有一個引用 arg,
alert(arg);
//只要 外部空間的 arg 不變,這里的引用值當(dāng)然不會改變
}
})(i); //立刻執(zhí)行該匿名函數(shù)艘绍,傳遞下標(biāo) i (實參)
}
解決辦法二
解決思路:將事件綁定在新增的匿名函數(shù)返回的函數(shù)上赤拒,此時綁定的函數(shù)中的 function scope 中的 closure 對象的 引用 arg 是指向?qū)⑵浞祷氐哪涿瘮?shù)的私有變量 arg
for (var i = 0; i < arr.length; i++) {
arr[i].onclick = (function (arg) {
return function () {
alert(arg);
}
})(i);
}
解決辦法三
使用 ES6 新語法 let 關(guān)鍵字
for (var i = 0; i < arr.length; i++) {
let j = i; // 創(chuàng)建一個塊級變量
arr[i].onclick = function () {
alert(j);
}
}
JavaScript 判斷一個變量是對象還是數(shù)組 ?
typeof 都返回 object
在 JavaScript 中所有數(shù)據(jù)類型嚴(yán)格意義上都是對象诱鞠,但實際使用中我們還是有類型之分挎挖,如果要判斷一個變量是數(shù)組還是對象使用 typeof 搞不定,因為它全都返回 object航夺。
第一蕉朵,使用 typeof 加 length 屬性
數(shù)組有 length 屬性,object 沒有阳掐,而 typeof 數(shù)組與對象都返回 object始衅,所以我們可以這么判斷
var getDataType = function(o){
if(typeof o == 'object'){
if( typeof o.length == 'number' ){
return 'Array';
} else {
return 'Object';
}
} else {
return 'param is no object type';
}
};
第二,使用 instanceof
利用 instanceof 判斷數(shù)據(jù)類型是對象還是數(shù)組時應(yīng)該優(yōu)先判斷 array缭保,最后判斷 object汛闸。
var getDataType = function(o){
if(o instanceof Array){
return 'Array'
} else if ( o instanceof Object ){
return 'Object';
} else {
return 'param is no object type';
}
};
ES5 的繼承和 ES6 的繼承有什么區(qū)別 ?
ES5 的繼承時通過 prototype 或構(gòu)造函數(shù)機(jī)制來實現(xiàn)艺骂。
-
ES5 的繼承實質(zhì)上是先創(chuàng)建子類的實例對象诸老,然后再將父類的方法添加到 this 上(Parent.apply(this))
。 -
ES6 的繼承機(jī)制完全不同钳恕,實質(zhì)上是先創(chuàng)建父類的實例對象 this(所以必須先調(diào)用父類的 super()方法)别伏,然后再用子類的構(gòu)造函數(shù)修改 this
。
具體的:ES6 通過 class 關(guān)鍵字定義類忧额,里面有構(gòu)造方法厘肮,類之間通過 extends 關(guān)鍵字實現(xiàn)繼承。子類必須在 constructor 方法中調(diào)用 super 方法睦番,否則新建實例報錯类茂。因為子類沒有自己的 this 對象,而是繼承了父類的 this 對象,然后對其進(jìn)行加工大咱。如果不調(diào)用 super 方法恬涧,子類得不到 this 對象。
ps:super 關(guān)鍵字指代父類的實例碴巾,即父類的 this 對象。在子類構(gòu)造函數(shù)中丑搔,調(diào)用 super 后厦瓢,才可使用 this 關(guān)鍵字,否則報錯啤月。
翻轉(zhuǎn)一個字符串
先將字符串轉(zhuǎn)成一個數(shù)組煮仇,然后用數(shù)組的 reverse() + join() 方法。
let a = "hello word";
let b = [...str].reverse().join(""); // drow olleh
說說堆和棧的區(qū)別 谎仲?
一浙垫、堆棧空間分配區(qū)別
- 棧(操作系統(tǒng)):由操作系統(tǒng)自動分配釋放 郑诺,存放函數(shù)的參數(shù)值夹姥,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧辙诞;
- 堆(操作系統(tǒng)):一般由程序員分配釋放辙售, 若程序員不釋放,程序結(jié)束時可能由 OS 回收飞涂,分配方式倒是類似于鏈表旦部。
二、堆棧緩存方式區(qū)別
- 棧使用的是一級緩存较店, 他們通常都是被調(diào)用時處于存儲空間中士八,調(diào)用完畢立即釋放;
- 堆是存放在二級緩存中梁呈,生命周期由虛擬機(jī)的垃圾回收算法來決定(并不是一旦成為孤兒對象就能被回收)婚度。所以調(diào)用這些對象的速度要相對來得低一些。
三捧杉、堆棧數(shù)據(jù)結(jié)構(gòu)區(qū)別
- 堆(數(shù)據(jù)結(jié)構(gòu)):堆可以被看成是一棵樹陕见,如:堆排序;
- 棧(數(shù)據(jù)結(jié)構(gòu)):一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu)味抖。
js 經(jīng)典面試知識文章
- JS 是單線程评甜,你了解其運(yùn)行機(jī)制嗎 ?
- 7 分鐘理解 JS 的節(jié)流仔涩、防抖及使用場景
- JavaScript 常見的六種繼承方式
- 九種跨域方式實現(xiàn)原理(完整版)
- 常見六大 Web 安全攻防解析
- 一文讀懂 HTTP/2 及 HTTP/3 特性
- 深入理解 HTTPS 工作原理
- JavaScript 中的垃圾回收和內(nèi)存泄漏
- 你不知道的瀏覽器頁面渲染機(jī)制
- JavaScript 設(shè)計模式
- 深入 javascript——構(gòu)造函數(shù)和原型對象
ES6 +
ES6 聲明變量的六種方法
- ES5 只有兩種聲明變量的方法:var 和 function 忍坷。
- ES6 除了添加 let 和 const 命令。
- 還有兩種聲明變量的方法:import 命令和 class 命令。
Promise 的隊列與 setTimeout 的隊列有何關(guān)聯(lián) 佩研?
setTimeout(function(){ console.log(4) }, 0);
new Promise(function(resolve){
console.log(1)
for( var i = 0 ; i < 10000 ; i++ ){
i == 9999 && resolve()
}
console.log(2)
}).then(function(){
console.log(5)
});
console.log(3);
為什么結(jié)果是:1, 2, 3, 5, 4柑肴;而不是:1, 2, 3, 4, 5 ?
js 里面有宏任務(wù)(macrotask)和微任務(wù)(microtask)旬薯。
因為 setTimeout 是屬于 macrotask 的晰骑,而整個 script 也是屬于一個 macrotask,promise.then 回調(diào)是 microtask绊序,執(zhí)行過程大概如下:
- 由于整個 script 也屬于一個 macrotask硕舆,由于會先執(zhí)行 macrotask 中的第一個任務(wù),再加上 promise 構(gòu)造函數(shù)因為是同步的骤公,所以會先打印出 1 和 2抚官;
- 然后繼續(xù)同步執(zhí)行末尾的 console.log(3) 打印出 3;
- 此時 setTimeout 被推進(jìn)到 macrotask 隊列中阶捆, promise.then 回調(diào)被推進(jìn)到 microtask 隊列中凌节;
- 由于在第一步中已經(jīng)執(zhí)行完了第一個 macrotask ,所以接下來會順序執(zhí)行所有的 microtask洒试,也就是 promise.then 的回調(diào)函數(shù)倍奢,從而打印出 5;
- microtask 隊列中的任務(wù)已經(jīng)執(zhí)行完畢儡司,繼續(xù)執(zhí)行剩下的 macrotask 隊列中的任務(wù)娱挨,也就是 setTimeout,所以打印出 4捕犬。
ES6+ 面試知識文章
最后
前端硬核面試專題的完整版在此:前端硬核面試專題跷坝,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數(shù)據(jù)結(jié)構(gòu)與算法 + Git 。
如果覺得本文還不錯碉碉,記得給個 star 柴钻, 你的 star 是我持續(xù)更新的動力!垢粮。
聽說點(diǎn)收藏贴届,不點(diǎn)贊的都是在耍流氓 -_-