56 道高頻 JavaScript 與 ES6+ 的面試題及答案

yollyto_前端硬核面試專題

前言

本文講解 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)分兩個

  1. 作用域
  2. 運(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ù))仗扬。
  1. 異步函數(shù)通常具有以下的形式:A(args..., callbackFn)症概。
  2. 它可以叫做異步過程的發(fā)起函數(shù),或者叫做異步任務(wù)注冊函數(shù)早芭。
  3. 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)

2365152044-5d54ffb134e4d_articlex.png
  • 主線程在執(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)樹
  • 布局
  • 繪制

詳情:面試題之從敲入 URL 到瀏覽器渲染完成


是否了解公鑰加密和私鑰加密表悬。如何確保表單提交里的密碼字段不被泄露。

公鑰用于對數(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 的原型對象與原型鏈

對于新人來說主卫,JavaScript 的原型是一個很讓人頭疼的事情逃默,一來 prototype 容易與 ****proto**** 混淆,

一簇搅、prototype 和 ****proto**** 的區(qū)別

1525036996-5d54ffd25d039_articlex.png
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é)果:

930826649-5d54ffe3d6f0f_articlex.png
3926983889-5d54fff0d22ed_articlex.png
/*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é)果:

ex.png
2_articlex.png
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é)果:

0_articlex.png

閉包的理解 吟税?

一、變量的作用域

要理解閉包姿现,首先必須理解 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)典面試知識文章


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)贊的都是在耍流氓 -_-

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜡吧,隨后出現(xiàn)的幾起案子毫蚓,更是在濱河造成了極大的恐慌,老刑警劉巖昔善,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件元潘,死亡現(xiàn)場離奇詭異,居然都是意外死亡君仆,警方通過查閱死者的電腦和手機(jī)翩概,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門牲距,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钥庇,你說我怎么就攤上這事牍鞠。” “怎么了评姨?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵难述,是天一觀的道長。 經(jīng)常有香客問我吐句,道長龄广,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任蕴侧,我火速辦了婚禮,結(jié)果婚禮上两入,老公的妹妹穿的比我還像新娘净宵。我一直安慰自己,他們只是感情好裹纳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布择葡。 她就那樣靜靜地躺著,像睡著了一般剃氧。 火紅的嫁衣襯著肌膚如雪敏储。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天朋鞍,我揣著相機(jī)與錄音已添,去河邊找鬼。 笑死滥酥,一個胖子當(dāng)著我的面吹牛更舞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坎吻,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缆蝉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘦真?” 一聲冷哼從身側(cè)響起刊头,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诸尽,沒想到半個月后原杂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弦讽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年萌衬,在試婚紗的時候發(fā)現(xiàn)自己被綠了胯甩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片行冰。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖某宪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锐朴,我是刑警寧澤兴喂,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站焚志,受9級特大地震影響衣迷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酱酬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一壶谒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膳沽,春花似錦汗菜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痛阻,卻和暖如春菌瘪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阱当。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工俏扩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斗这。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓动猬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親表箭。 傳聞我的和親對象是個殘疾皇子赁咙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,104評論 1 32
  • 概要 64學(xué)時 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,209評論 0 3
  • 最全的iOS面試題及答案 iOS面試小貼士 ———————————————回答好下面的足夠了-----------...
    zweic閱讀 2,703評論 0 73
  • 請參看我github中的wiki,不定期更新免钻。https://github.com/ivonzhang/Front...
    zhangivon閱讀 7,131評論 2 19
  • 當(dāng)人們重新踏上這片土地的時候 殺戮就已經(jīng)開始了 他們的腳踩踏的是血流成河的古戰(zhàn)場 表面風(fēng)平浪靜 暗處卻波濤洶涌 歷...
    喬人先生閱讀 254評論 0 0