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

clipboard.png

前言

本文講解 56 道 JavaScript 和 ES6+ 面試題的內容。javascript

複習前端面試的知識,是爲了鞏固前端的基礎知識,最重要的仍是平時的積累!

注意:文章的題與題之間用下劃線分隔開,答案僅供參考。html

前端硬核面試專題的完整版在此:前端硬核面試專題,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數據結構與算法 + Git 。前端

JavaScript

常見的瀏覽器內核有哪些 ?java

  • Trident 內核:IE, 360,搜狗瀏覽器 MaxThon、TT、The World,等。[又稱 MSHTML]
  • Gecko 內核:火狐,FF,MozillaSuite / SeaMonkey 等
  • Presto 內核:Opera7 及以上。[Opera 內核原爲:Presto,現爲:Blink]
  • Webkit 內核:Safari,Chrome 等。 [ Chrome 的:Blink(WebKit 的分支)]

mouseenter 和 mouseover 的區別 git

  • 不論鼠標指針穿過被選元素或其子元素,都會觸發 mouseover 事件,對應 mouseout。
  • 只有在鼠標指針穿過被選元素時,纔會觸發 mouseenter 事件,對應 mouseleave。

用正則表達式匹配字符串,以字母開頭,後面是數字、字符串或者下劃線,長度爲 9 - 20 程序員

var re=new RegExp("^[a-zA-Z][a-zA-Z0-9_]{9,20}$");

手機號碼校驗github

function checkPhone(){ 
    var phone = document.getElementById('phone').value;
    if(!(/^1(3|4|5|7|8)\d{9}$/.test(phone))){ 
        alert("手機號碼有誤,請重填");  
        return false; 
    } 
}

^1(3|4|5|7|8)d{9}$,表示以 1 開頭,第二位多是 3/4/5/7/8 等的任意一個,在加上後面的 d 表示數字 [0-9] 的 9 位,總共加起來 11 位結束。web


手機號碼格式驗證方法(正則表達式驗證)支持最新電信 199, 移動 198, 聯通 166面試

// 手機號碼校驗規則
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('手機號碼格式有誤');
     return false;
}

這樣 phone_number 就是取到的手機號碼,便可!正則表達式


js 字符串兩邊截取空白的 trim 的原型方法的實現

js 中自己是沒有 trim 函數的。

// 刪除左右兩端的空格
function trim(str){
 return str.replace(/(^\s*)|(\s*$)/g, "");
}
// 刪除左邊的空格 /(^\s*)/g
// 刪除右邊的空格 /(\s*$)/g

介紹一下你對瀏覽器內核的理解 ?

內核主要分紅兩部分:渲染引擎(layout engineer 或 Rendering Engine) 和 JS 引擎。

渲染引擎

負責取得網頁的內容(HTML、XML、圖像等等)、整理訊息(例如加入 CSS 等),以及計算網頁的顯示方式,而後會輸出至顯示器或打印機。
瀏覽器的內核的不一樣對於網頁的語法解釋會有不一樣,因此渲染的效果也不相同。
全部網頁瀏覽器、電子郵件客戶端以及其它須要編輯、顯示網絡內容的應用程序都須要內核。

JS 引擎

解析和執行 javascript 來實現網頁的動態效果。

最開始渲染引擎和 JS 引擎並無區分的很明確,後來 JS 引擎愈來愈獨立,內核就傾向於只指渲染引擎。


哪些常見操做會形成內存泄漏 ?

內存泄漏指任何對象在您再也不擁有或須要它以後仍然存在。

垃圾回收器按期掃描對象,並計算引用了每一個對象的其餘對象的數量。若是一個對象的引用數量爲 0(沒有其餘對象引用過該對象),或對該對象的唯一引用是循環的,那麼該對象的內存便可回收。

  • setTimeout 的第一個參數使用字符串而非函數的話,會引起內存泄漏。
  • 閉包、控制檯日誌、循環(在兩個對象彼此引用且彼此保留時,就會產生一個循環)。

線程與進程的區別 ?

  • 一個程序至少有一個進程,一個進程至少有一個線程。
  • 線程的劃分尺度小於進程,使得多線程程序的併發性高。
  • 另外,進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率。

線程在執行過程當中與進程仍是有區別的。

  • 每一個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。可是線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
  • 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分能夠同時執行。

但操做系統並無將多個線程看作多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。


eval() 函數有什麼用 ?

eval() 函數可計算某個字符串,並執行其中的的 JavaScript 代碼。


實現一個方法,使得:add(2, 5) 和 add(2)(5) 的結果都爲 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) 的結果是 ?

alert(1 &&2 ) 的結果是 2

  • 只要 「&&」 前面是 false,不管 「&&」 後面是 true 仍是 false,結果都將返 「&&」 前面的值;
  • 只要 「&&」 前面是 true,不管 「&&」 後面是 true 仍是 false,結果都將返 「&&」 後面的值;

alert(0 || 1) 的結果是 1

  • 只要 「||」 前面爲 false,無論 「||」 後面是 true 仍是 false,都返回 「||」 後面的值。
  • 只要 「||」 前面爲 true,無論 「||」 後面是 true 仍是 false,都返回 「||」 前面的值。
只要記住 0 與 任何數都是 0,其餘反推。

下面的輸出結果是 ?

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)());

結果:25,20,20,25

代碼解析:這道題的考點分兩個

  1. 做用域
  2. 運算符(賦值預算,逗號運算)

先看第一個輸出:25,由於 ( inner.func, inner.func ) 是進行逗號運算符,逗號運算符就是運算前面的 」,「 返回最後一個,舉個栗子

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 只是一個匿名函數

function () {
    var out = 30;
    return this.out;
}

並且這個匿名函數是屬於 window 的,則變成了

(function () {
    var out = 30;
    return this.out;
})()

此刻的 this => window

因此 out 是 25。

第二和第三個 console.log 的做用域都是 inner,也就是他們執行的實際上是 inner.func();
inner 做用域中是有 out 變量的,因此結果是 20。

第四個 console.log 考查的是一個等號運算 inner.func = inner.func ,其實返回的是運算的結果,
舉個栗子

var a = 2, b = 3;
console.log(a = b) // 輸出的是 3

因此 inner.func = inner.func 返回的也是一個匿名函數

function () {
    var out = 30;
    return this.out;
}

此刻,道理就和第一個 console.log 同樣了,輸出的結果是 25。


下面程序輸出的結果是 ?

if (!("a" in window)) {
    var a = 1;
}
alert(a);

代碼解析:若是 window 不包含屬性 a,就聲明一個變量 a,而後賦值爲 1。

你可能認爲 alert 出來的結果是 1,而後實際結果是 「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 結果是 true 了。

第三,你須要理解該題目的意思是,變量聲明被提早了,但變量賦值沒有,由於這行代碼包括了變量聲明和變量賦值。

你能夠將語句拆分爲以下代碼:

var a;    //聲明
a = 1;    //初始化賦值

當變量聲明和賦值在一塊兒用的時候,JavaScript 引擎會自動將它分爲兩部以便將變量聲明提早,
不將賦值的步驟提早,是由於他有可能影響代碼執行出不可預期的結果。

因此,知道了這些概念之後,從新回頭看一下題目的代碼,其實就等價於:

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a);

這樣,題目的意思就很是清楚了:首先聲明 a,而後判斷 a 是否在存在,若是不存在就賦值爲1,很明顯 a 永遠在 window 裏存在,這個賦值語句永遠不會執行,因此結果是 undefined。

提早這個詞語顯得有點迷惑了,你能夠理解爲:預編譯。


下面程序輸出的結果是 ?

var a = 1;
var b = function a(x) {
  x && a(--x);
};
alert(a);

這個題目看起來比實際複雜,alert 的結果是 1。

這裏依然有 3 個重要的概念須要咱們知道。

  • 首先,第一個是 變量聲明在進入執行上下文就完成了
  • 第二個概念就是函數聲明也是提早的,全部的函數聲明都在執行代碼以前都已經完成了聲明,和變量聲明同樣

澄清一下,函數聲明是以下這樣的代碼:

function functionName(arg1, arg2){
    //函數體
}

以下不是函數,而是函數表達式,至關於變量賦值:

var functionName = function(arg1, arg2){
       //函數體
   };

澄清一下,函數表達式沒有提早,就至關於平時的變量賦值。

  • 第三須要知道的是,函數聲明會覆蓋變量聲明,但不會覆蓋變量賦值

爲了解釋這個,咱們來看一個例子:

function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"

儘管變量聲明在下面定義,可是變量 value 依然是 function,也就是說這種狀況下,函數聲明的優先級高於變量聲明的優先級,但若是該變量 value 賦值了,那結果就徹底不同了:

function value(){
    return 1;
}
var value = 1;
alert(typeof value);    //"number"

該 value 賦值之後,變量賦值初始化就覆蓋了函數聲明。

從新回到題目,這個函數實際上是一個有名函數表達式,函數表達式不像函數聲明同樣能夠覆蓋變量聲明,但你能夠注意到,變量 b 是包含了該函數表達式,而該函數表達式的名字是 a。不一樣的瀏覽器對 a 這個名詞處理有點不同,在 IE 裏,會將 a 認爲函數聲明,因此它被變量初始化覆蓋了,就是說若是調用 a(–x) 的話就會出錯,而其它瀏覽器在容許在函數內部調用 a(–x),由於這時候 a 在函數外面依然是數字。
基本上,IE 裏調用 b(2) 的時候會出錯,但其它瀏覽器則返回 undefined。

理解上述內容以後,該題目換成一個更準確和更容易理解的代碼應該像這樣:

var a = 1,
    b = function(x) {
      x && b(--x);
    };
alert(a);

這樣的話,就很清晰地知道爲何 alert 的老是 1 了。


下面程序輸出的結果是 ?

function a(x) {
    return x * 2;
}
var a;
alert(a);

alert 的值是下面的函數

function a(x) {
    return x * 2;
}

這個題目比較簡單:即函數聲明和變量聲明的關係和影響,遇到同名的函數聲明,不會從新定義。


下面程序輸出的結果是 ?

function b(x, y, a) {
        arguments[2] = 10;
        alert(a);
}
b(1, 2, 3);

結果爲 10。

活動對象是在進入函數上下文時刻被建立的,它經過函數的 arguments 屬性初始化。


三道判斷輸出的題都是經典的題

var a = 4;
function b() {
  a = 3;
  console.log(a);
  function a(){};
}
b();

明顯輸出是 3,由於裏面修改了 a 這個全局變量,那個 function a(){} 是用來干擾的,雖然函數聲明會提高,就被 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 的指向,函數做爲對象自己屬性調用的時候,this 指向對象,做爲普通函數調用的時候,就指向全局了。

還有下面的題:

var arr = [1,2,3,4,5];
for(var i = 0; i < arr.length; i++){
  arr[i] = function(){
    alert(i)
  }
}
arr[3]();

典型的閉包,彈出 5 。


JavaScript 裏有哪些數據類型

1、數據類型  

  • undefiend 沒有定義數據類型       
  • number 數值數據類型,例如 10 或者 1 或者 5.5       
  • string 字符串數據類型用來描述文本,例如 "你的姓名"       
  • boolean 布爾類型 true | false ,不是正就是反       
  • object 對象類型,複雜的一組描述信息的集合
  • function 函數類型

解釋清楚 null 和 undefined

null 用來表示還沒有存在的對象,經常使用來表示函數企圖返回一個不存在的對象。  null 表示"沒有對象",即該處不該該有值。
null 典型用法是: 

  • 做爲函數的參數,表示該函數的參數不是對象。 
  • 做爲對象原型鏈的終點。

當聲明的變量還未被初始化時,變量的默認值爲 undefined。 undefined 表示"缺乏值",就是此處應該有一個值,可是尚未定義。 

  • 變量被聲明瞭,但沒有賦值時,就等於 undefined。 
  • 調用函數時,應該提供的參數沒有提供,該參數等於 undefined。 
  • 對象沒有賦值的屬性,該屬性的值爲 undefined。 
  • 函數沒有返回值時,默認返回 undefined。

未定義的值和定義未賦值的爲 undefined,null 是一種特殊的 object,NaN 是一種特殊的 number。


講一下 1 和 Number(1) 的區別*

  • 1 是一個原始定義好的 number 類型;
  • Number(1) 是一個函數類型,是咱們本身聲明的一個函數(方法)。

講一下 prototype 是什麼東西,原型鏈的理解,何時用 prototype ?

prototype 是函數對象上面預設的對象屬性。


函數裏的 this 什麼含義,什麼狀況下,怎麼用 ?

  • this 是 Javascript 語言的一個關鍵字。
  • 它表明函數運行時,自動生成的一個內部對象,只能在函數內部使用。
  • 隨着函數使用場合的不一樣,this 的值會發生變化。
  • 可是有一個總的原則,那就是 this 指的是,調用函數的那個對象

狀況一:純粹的函數調用 

這是函數的最一般用法,屬於全局性調用,所以 this 就表明全局對象 window。      

function test(){       
  this.x = 1;        
  alert(this.x);      
}      
test(); // 1

爲了證實 this 就是全局對象,我對代碼作一些改變:      

var x = 1;      
function test(){        
  alert(this.x);      
}      
test(); // 1   

運行結果仍是 1。

再變一下:      

var x = 1;      
function test(){        
  this.x = 0;      
}      
test();
alert(x); // 0

狀況二:做爲對象方法的調用   

函數還能夠做爲某個對象的方法調用,這時 this 就指這個上級對象。   
  

function test(){        
  alert(this.x);      
}
var x = 2      
var o = {};      
o.x = 1;      
o.m = test;      
o.m(); // 1

狀況三: 做爲構造函數調用   

所謂構造函數,就是經過這個函數生成一個新對象(object)。這時的 this 就指這個新對象。

function Test(){        
  this.x = 1;      
}      
var o = new Test();
alert(o.x); // 1   

運行結果爲 1。爲了代表這時 this 不是全局對象,對代碼作一些改變: 
    

var x = 2;      
function Test(){        
  this.x = 1;      
}      
var o = new Test();      
alert(x); // 2

運行結果爲 2,代表全局變量 x 的值沒變。

狀況四: apply 調用   

apply() 是函數對象的一個方法,它的做用是改變函數的調用對象,它的第一個參數就表示改變後的調用這個函數的對象。所以,this 指的就是這第一個參數。      

var x = 0;      
function test(){        
  alert(this.x);      
}      
var o = {};      
o.x = 1;      
o.m = test;      
o.m.apply(); // 0   

apply() 的參數爲空時,默認調用全局對象。所以,這時的運行結果爲 0,證實 this 指的是全局對象。
   
若是把最後一行代碼修改成

o.m.apply(o); // 1

運行結果就變成了 1,證實了這時 this 表明的是對象 o。


apply 和 call  什麼含義,什麼區別 ?何時用 ?

call,apply 都屬於 Function.prototype 的一個方法,它是 JavaScript 引擎內在實現的,由於屬於 Function.prototype,因此每一個 Function 對象實例(就是每一個方法)都有 call,apply 屬性。

既然做爲方法的屬性,那它們的使用就固然是針對方法的了,這兩個方法是容易混淆的,由於它們的做用同樣,只是使用方式不一樣。

語法:

foo.call(this, arg1, arg2, arg3) == foo.apply(this, arguments) == this.foo(arg1, arg2, arg3);
  • 相同點:兩個方法產生的做用是徹底同樣的。
  • 不一樣點:方法傳遞的參數不一樣。

每一個函數對象會有一些方法能夠去修改函數執行時裏面的 this,比較常見獲得就是 call 和 apply,經過 call 和 apply 能夠從新定義函數的執行環境,即 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
//傳參的時候是扁平的把每一個參數傳進去

add.apply(o, [10, 20]);   //1+3+10+20=34
//傳參的時候是把參數做爲一個數組傳進去

//何時使用 call 或者 apply
function bar() {
  console.log(Object.prototype.toString.call(this));
  // 用來調用一些沒法直接調用的方法
}

bar.call(7); // "[object Number]"

異步過程的構成要素有哪些?和異步過程是怎樣的 ?

總結一下,一個異步過程一般是這樣的:

  • 主線程發起一個異步請求,相應的工做線程接收請求並告知主線程已收到(異步函數返回);
  • 主線程能夠繼續執行後面的代碼,同時工做線程執行異步任務;
  • 工做線程完成工做後,通知主線程;
  • 主線程收到通知後,執行必定的動做(調用回調函數)。
  1. 異步函數一般具備如下的形式:A(args..., callbackFn)。
  2. 它能夠叫作異步過程的發起函數,或者叫作異步任務註冊函數。
  3. args 和 callbackFn 是這個函數的參數。

因此,從主線程的角度看,一個異步過程包括下面兩個要素:

  • 發起函數(或叫註冊函數) A。
  • 回調函數 callbackFn。

它們都是在主線程上調用的,其中註冊函數用來發起異步過程,回調函數用來處理結果。

舉個具體的例子:

setTimeout(fn, 1000);

其中的 setTimeout 就是異步過程的發起函數,fn 是回調函數。

注意:前面說的形式 A(args..., callbackFn) 只是一種抽象的表示,並不表明回調函數必定要做爲發起函數的參數。

例如:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回調函數
xhr.open('GET', url);
xhr.send(); // 發起函數

發起函數和回調函數就是分離的。


說說消息隊列和事件循環

clipboard.png

  • 主線程在執行完當前循環中的全部代碼後,就會到消息隊列取出這條消息(也就是 message 函數),並執行它。
  • 完成了工做線程對主線程的通知,回調函數也就獲得了執行。
  • 若是一開始主線程就沒有提供回調函數,AJAX 線程在收到 HTTP 響應後,也就不必通知主線程,從而也不必往消息隊列放消息。
異步過程的回調函數,必定不在當前的這一輪事件循環中執行。

session 與 cookie 的區別

  • session 保存在服務器,客戶端不知道其中的信息;
  • cookie 保存在客戶端,服務器可以知道其中的信息。 
  • session 中保存的是對象,cookie 中保存的是字符串。   
  • session 不能區分路徑,同一個用戶在訪問一個網站期間,全部的 session 在任何一個地方均可以訪問到。
  • 而 cookie 中若是設置了路徑參數,那麼同一個網站中不一樣路徑下的 cookie 互相是訪問不到的。  

cookies 是幹嗎的,服務器和瀏覽器之間的 cookies 是怎麼傳的,httponly 的 cookies 和可讀寫的 cookie 有什麼區別,有無長度限制 ?

  • cookies 是一些存儲在用戶電腦上的小文件。
  • 它是被設計用來保存一些站點的用戶數據,這樣可以讓服務器爲這樣的用戶定製內容,後者頁面代碼可以獲取到 cookie 值而後發送給服務器。
  • 好比 cookie 中存儲了所在地理位置,之後每次進入地圖就默認定位到改地點便可。

請描述一下 cookies,sessionStorage 和 localStorage 的區別

共同點

  • 都是保存在瀏覽器端,且同源的。

區別

  • cookie 數據始終在同源的 http 請求中攜帶(即便不須要),即 cookie 在瀏覽器和服務器間來回傳遞。
  • 而 sessionStorage 和 localStorage 不會自動把數據發給服務器,僅在本地保存。
  • cookie 數據還有路徑(path)的概念,能夠限制 cookie 只屬於某個路徑下。
  • 存儲大小限制也不一樣,cookie 數據不能超過 4k,同時由於每次 http 請求都會攜帶 cookie,因此 cookie 只適合保存很小的數據,如會話標識。
  • sessionStorage 和 localStorage 雖然也有存儲大小的限制,但比 cookie 大得多,能夠達到 5M 或更大。
  • 數據有效期不一樣,sessionStorage:僅在當前瀏覽器窗口關閉前有效,天然也就不可能持久保持;localStorage:始終有效,窗口或瀏覽器關閉也一直保存,所以用做持久數據;cookie 只在設置的 cookie 過時時間以前一直有效,即便窗口或瀏覽器關閉。
  • 做用域不一樣,sessionStorage 在不一樣的瀏覽器窗口中不共享,即便是同一個頁面;cookie 和 localStorage 在全部同源窗口中都是共享的。

從敲入 URL 到渲染完成的整個過程,包括 DOM 構建的過程,說的約詳細越好

  • 用戶輸入 url 地址,瀏覽器根據域名尋找 IP 地址
  • 瀏覽器向服務器發送 http 請求,若是服務器段返回以 301 之類的重定向,瀏覽器根據相應頭中的 location 再次發送請求
  • 服務器端接受請求,處理請求生成 html 代碼,返回給瀏覽器,這時的 html 頁面代碼多是通過壓縮的
  • 瀏覽器接收服務器響應結果,若是有壓縮則首先進行解壓處理,緊接着就是頁面解析渲染
  • 解析渲染該過程主要分爲如下步驟:解析 HTML、構建 DOM 樹、DOM 樹與 CSS 樣式進行附着構造呈現樹
  • 佈局
  • 繪製

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


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

公鑰用於對數據進行加密,私鑰用於對數據進行解密。

很直觀的理解:公鑰就是公開的密鑰,其公開了你們才能用它來加密數據。私鑰是私有的密鑰,誰有這個密鑰纔可以解密密文。

解決方案 1:

form 在提交的過程當中,對密碼字段是不進行加密而是以明碼的形式進行數據傳輸的。
若是要對數據進行加密,你能夠本身寫一個腳本對內容進行編碼後傳輸,只是這個安全性也並不高。

解決方案 2:

若是想對數據進行加密,你可使用 HTTPS 安全傳輸協議,這個協議是由系統進行密碼加密處理的,在數據傳輸中是絕對不會被攔截獲取的,只是 HTTPS 的架設會相對麻煩點。一些大型網站的登陸、銀行的在線網關等都是走這條路。


驗證碼是幹嗎的,是爲了解決什麼安全問題。

所謂驗證碼,就是將一串隨機產生的數字或符號,生成一幅圖片, 圖片里加上一些干擾象素(防止OCR),由用戶肉眼識別其中的驗證碼信息,輸入表單提交網站驗證,驗證成功後才能使用某項功能。

  • 驗證碼通常是防止批量註冊的,人眼看起來都費勁,況且是機器。
  • 像百度貼吧未登陸發貼要輸入驗證碼大概是防止大規模匿名回帖的發生。
  • 目前,很多網站爲了防止用戶利用機器人自動註冊、登陸、灌水,都採用了驗證碼技術。

截取字符串 abcdefg 的 efg。

從第四位開始截取

alert('abcdefg'.substring(4));
alert ('abcdefg'.slice(4))

判斷一個字符串中出現次數最多的字符,統計這個次數

步驟

  • 將字符串轉化數組 
  • 建立一個對象 
  • 遍歷數組,判斷對象中是否存在數組中的值,若是存在值 +1,不存在賦值爲 1
  • 定義兩個變量存儲字符值,字符出現的字數
var str = 'abaasdffggghhjjkkgfddsssss3444343';
// 1.將字符串轉換成數組
var newArr = str.split("");
// 2.建立一個對象
var json = {};
// 3. 全部字母出現的次數,判斷對象中是否存在數組中的值,若是存在值 +1,不存在賦值爲 1
for(var i = 0; i < newArr.length; i++){
      // 相似:json : { ‘a’: 3, ’b’: 1 }
      if(json[newArr[i]]){
         json[newArr[i]] +=1;
      } else {
           json[newArr[i]] = 1;
      }
}
// 4 定義兩個變量存儲字符值,字符出現的字數
var num = 0 ; //次數
var element = ""; //最多的項
for(var k in json){
   if(json[k] > num){
     num = json[k];
     element = k ;
   }
}
console.log("出現次數:"+num +"最多的字符:"+ element);

document.write 和 innerHTML 的區別

  • document.write 是直接寫入到頁面的內容流,若是在寫以前沒有調用 document.open, 瀏覽器會自動調用 open。每次寫完關閉以後從新調用該函數,會致使頁面被重寫。
  • innerHTML 則是 DOM 頁面元素的一個屬性,表明該元素的 html 內容。你能夠精確到某一個具體的元素來進行更改。若是想修改 document 的內容,則須要修改 document.documentElement.innerElement。
  • innerHTML 將內容寫入某個 DOM 節點,不會致使頁面所有重繪。
  • innerHTML 不少狀況下都優於 document.write,其緣由在於其容許更精確的控制要刷新頁面的那一個部分。
  • document.write 是重寫整個 document, 寫入內容是字符串的 html;innerHTML 是 HTMLElement 的屬性,是一個元素的內部 html 內容 

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 常見的內置對象

有 Object、Math、String、Array、Number、Function、Boolean、JSON 等,其中 Object 是全部對象的基類,採用了原型繼承方式。


編寫一個方法,求一個字符串的字節長度

假設:一個英文字符佔用一個字節,一箇中文字符佔用兩個字節

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) 描述了處理網頁內容的方法和接口
  • 瀏覽器對象模型(BOM) 描述了與瀏覽器進行交互的方法和接口

new 操做符具體幹了什麼呢 ?

  • 建立一個空對象,而且 this 變量引用該對象,同時還繼承了該函數的原型。
  • 屬性和方法被加入到 this 引用的對象中。
  • 新建立的對象由 this 所引用,而且最後隱式的返回 this 。

JSON 的瞭解?

  • JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。
  • 它是基於 JavaScript 的一個子集。
  • 數據格式簡單,易於讀寫,佔用帶寬小。
  • 格式:採用鍵值對。例如:{ 「age‟: ‟12‟, 」name‟: ‟back‟ }

你有哪些性能優化的方法 ?

web 前端是應用服務器處理以前的部分,前端主要包括:HTML、CSS、javascript、image 等各類資源,針對不一樣的資源有不一樣的優化方式。

內容優化

  • 減小 HTTP 請求數。這條策略是最重要最有效的,由於一個完整的請求要通過 DNS 尋址,與服務器創建鏈接,發送數據,等待服務器響應,接收數據這樣一個消耗時間成本和資源成本的複雜的過程。

常見方法:合併多個 CSS 文件和 js 文件,利用 CSS Sprites 整合圖像,Inline Images (使用 data:URL scheme 在實際的頁面嵌入圖像數據 ),合理設置 HTTP 緩存等。

  • 減小 DNS 查找
  • 避免重定向
  • 使用 Ajax 緩存
  • 延遲加載組件,預加載組件
  • 減小 DOM 元素數量。頁面中存在大量 DOM 元素,會致使 javascript 遍歷 DOM 的效率變慢。
  • 最小化 iframe 的數量。iframes 提供了一個簡單的方式把一個網站的內容嵌入到另外一個網站中。但其建立速度比其餘包括 JavaScript 和 CSS 的 DOM 元素的建立慢了 1-2 個數量級。
  • 避免 404。HTTP 請求時間消耗是很大的,所以使用 HTTP 請求來得到一個沒有用處的響應(例如 404 沒有找到頁面)是徹底沒有必要的,它只會下降用戶體驗而不會有一點好處。

服務器優化

  • 使用內容分發網絡(CDN)。把網站內容分散到多個、處於不一樣地域位置的服務器上能夠加快下載速度。
  • GZIP 壓縮
  • 設置 ETag:ETags(Entity tags,實體標籤)是 web 服務器和瀏覽器用於判斷瀏覽器緩存中的內容和服務器中的原始內容是否匹配的一種機制。
  • 提早刷新緩衝區
  • 對 Ajax 請求使用 GET 方法
  • 避免空的圖像 src

Cookie 優化

  • 減少 Cookie 大小
  • 針對 Web 組件使用域名無關的 Cookie

CSS 優化

  • 將 CSS 代碼放在 HTML 頁面的頂部
  • 避免使用 CSS 表達式
  • 使用 < link> 來代替 @import
  • 避免使用 Filters

javascript 優化

  • 將 JavaScript 腳本放在頁面的底部。
  • 將 JavaScript 和 CSS 做爲外部文件來引用。

在實際應用中使用外部文件能夠提升頁面速度,由於 JavaScript 和 CSS 文件都能在瀏覽器中產生緩存。

  • 縮小 JavaScript 和 CSS
  • 刪除重複的腳本
  • 最小化 DOM 的訪問。使用 JavaScript 訪問 DOM 元素比較慢。
  • 開發智能的事件處理程序
  • javascript 代碼注意:謹慎使用 with,避免使用 eval Function 函數,減小做用域鏈查找。

圖像優化

  • 優化圖片大小
  • 經過 CSS Sprites 優化圖片
  • 不要在 HTML 中使用縮放圖片
  • favicon.ico 要小並且可緩存

JS 格式化數字(每三位加逗號)

從後往前取。

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;  
}  

合併數組

若是你須要合併兩個數組的話,可使用 Array.concat()

var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
console.log(array1.concat(array2)); // [1,2,3,4,5,6];

然而,這個函數並不適用於合併大的數組,由於它須要建立一個新的數組,而這會消耗不少內存。

這時,你可使用 Array.push.apply(arr1, arr2) 來代替建立新的數組,它能夠把第二個數組合併到第一個中,從而較少內存消耗。

var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
console.log(array1.push.apply(array1, array2)); // [1, 2, 3, 4, 5, 6]

把節點列表 (NodeList) 轉換爲數組

若是你運行 document.querySelectorAll("p") 方法,它可能會返回一個 DOM 元素的數組 — 節點列表對象。
但這個對象並不具備數組的所有方法,如 sort(),reduce(), map(),filter()。
爲了使用數組的那些方法,你須要把它轉換爲數組。

只需使用 [].slice.call(elements) 便可實現:

var elements = document.querySelectorAll("p"); // NodeList
var arrayElements = [].slice.call(elements); // 如今 NodeList 是一個數組

var arrayElements = Array.from(elements); // 這是另外一種轉換 NodeList 到 Array  的方法

打亂數組元素的順序

不適用 Lodash 等這些庫打亂數組元素順序,你可使用這個技巧:

var list = [1, 2, 3];
console.log(list.sort(function() { Math.random() - 0.5 })); // [2, 1, 3]

js 的 ready 和 onload 事件的區別

  • onload 是等 HTML 的全部資源都加載完成後再執行 onload 裏面的內容,全部資源包括 DOM 結構、圖片、視頻 等資源;
  • ready 是當 DOM 結構加載完成後就能夠執行了,至關於 jQuery 中的 $(function(){ js 代碼 });
  • 另外,onload 只能有一個,ready 能夠有多個。

js 的兩種回收機制

標記清除(mark and sweep)

從語義上理解就比較好理解了,大概就是當變量進入到某個環境中的時候就把這個變量標記一下,好比標記爲「進入環境」,當離開的時候就把這個變量的標記給清除掉,好比是「離開環境」。而在這後面還有標記的變量將被視爲準備刪除的變量。

  • 垃圾收集器在運行的時候會給存儲在內存中的全部變量都加上標記(可使用任何標記方式)。
  • 而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。
  • 而在此以後再被加上的標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。
  • 最後,垃圾收集器完成內存清除工做。銷燬那些帶標記的值並回收它們所佔用的內存空間。

這是 javascript 最多見的垃圾回收方式。至於上面有說道的標記,到底該如何標記 ?
好像是有不少方法,好比特殊位翻轉,維護一個列表什麼的。

引用計數(reference counting)

  • 引用計數的含義是跟蹤記錄每一個值被引用的次數,當聲明一個變量並將一個引用類型的值賦給該變量時,這個時候的引用類型的值就會是引用次數 +1 了。若是同一個值又被賦給另一個變量,則該值的引用次數又 +1。
  • 相反若是包含這個值的引用的變量又取得另一個值,即被從新賦了值,那麼這個值的引用就 -1 。當這個值的引用次數編程 0 時,表示沒有用到這個值,這個值也沒法訪問,所以環境就會收回這個值所佔用的內存空間回收。
  • 這樣,當垃圾收集器下次再運行時,它就會釋放引用次數爲 0 的值所佔用的內存。

三張圖搞懂 JavaScript 的原型對象與原型鏈

對於新人來講,JavaScript 的原型是一個很讓人頭疼的事情,一來 prototype 容易與 proto 混淆,

1、prototype 和 proto 的區別

clipboard.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() {}

結果:

clipboard.png

clipboard.png

/*一、字面量方式*/
var a = {};
console.log("a.__proto__ :", a.__proto__);  // Object {}
console.log("a.__proto__ === a.constructor.prototype:", a.__proto__ === a.constructor.prototype); // true

/*二、構造器方式*/
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

/*三、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中的例外狀況)

結果:

clipboard.png

clipboard.png

var A = function(){};
var a = new A();
console.log(a.__proto__); // A {}(即構造器 function A 的原型對象)
console.log(a.__proto__.__proto__); // Object {}(即構造器 function Object 的原型對象)
console.log(a.__proto__.__proto__.__proto__); // null

結果:

clipboard.png


閉包的理解 ?

1、變量的做用域

要理解閉包,首先必須理解 Javascript 特殊的變量做用域。
變量的做用域無非就是兩種:全局變量和局部變量。

Javascript語言的特殊之處,就在於函數內部能夠直接讀取全局變量。

var n = 999;
function f1(){
  alert(n);
}
f1(); // 999

另外一方面,在函數外部天然沒法讀取函數內的局部變量。

function f1(){    
  var n = 999;
}
alert(n); // error

這裏有一個地方須要注意,函數內部聲明變量的時候,必定要使用 var 命令。
若是不用的話,你實際上聲明瞭一個全局變量!

function f1(){
  n = 999;
}
f1();
alert(n); // 999

2、如何從外部讀取局部變量 ?

function f1() {
  var n = 999;
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999

既然 f2 能夠讀取 f1 中的局部變量,那麼只要把 f2 做爲返回值,咱們不就能夠在 f1 外部讀取它的內部變量了嗎!

3、閉包的概念

上一節代碼中的 f2 函數,就是閉包。
個人理解是,閉包就是可以讀取其餘函數內部變量的函數

因爲在 Javascript 語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成 定義在一個函數內部的函數
因此,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑

4、閉包的用途

閉包能夠用在許多地方。它的最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。

怎麼來理解呢 ?請看下面的代碼。

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 函數。它一共運行了兩次,第一次的值是 999,第二次的值是 1000。這證實了,函數 f1 中的局部變量 n 一直保存在內存中,並無在 f1 調用後被自動清除。

爲何會這樣呢 ?

緣由就在於 f1 是 f2 的父函數,而 f2 被賦給了一個全局變量,這致使 f2 始終在內存中,而 f2 的存在依賴於 f1,所以 f1 也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

這段代碼中另外一個值得注意的地方,就是

  • "nAdd=function(){ n+=1 }" 這一行,首先在 nAdd 前面沒有使用 var 關鍵字,所以 nAdd 是一個全局變量,而不是局部變量。
  • 其次,nAdd 的值是一個匿名函數(anonymous function),而這個匿名函數自己也是一個閉包,因此 nAdd 至關因而一個 setter,能夠在函數外部對函數內部的局部變量進行操做。

5、使用閉包的注意點

  • 因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在 IE 中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
  • 閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。

閉包面試經典問題

問題:想每次點擊對應目標時彈出對應的數字下標 0~4 ,但實際是不管點擊哪一個目標都會彈出數字 5。

function onMyLoad() {
  var arr = document.getElementsByTagName("p");
  for (var i = 0; i < arr.length; i++) {
    arr[i].onclick = function () {
      alert(i);
    }
  }
}

問題所在:arr 中的每一項的 onclick 均爲一個函數實例(Function 對象),這個函數實例也產生了一個閉包域,這個閉包域引用了外部閉包域的變量,其 function scope 的 closure 對象有個名爲 i 的引用,外部閉包域的私有變量內容發生變化,內部閉包域獲得的值天然會發生改變。

解決辦法一

解決思路:增長若干個對應的閉包域空間(這裏採用的是匿名函數),專門用來存儲原先須要引用的內容(下標),不過只限於基本類型(基本類型值傳遞,對象類型引用傳遞)。

//聲明一個匿名函數,若傳進來的是基本類型則爲值傳遞,故不會對實參產生影響,
//該函數對象有一個本地私有變量 arg(形參) ,該函數的 function scope 的 closure 對象屬性有兩個引用,一個是 arr,一個是 i
//儘管引用 i 的值隨外部改變 ,但本地私有變量(形參) arg 不會受影響,其值在一開始被調用的時候就決定了
for (var i = 0; i < arr.length; i++) {
  (function (arg) {
    arr[i].onclick = function () {
      // onclick 函數實例的 function scope 的 closure 對象屬性有一個引用 arg,
      alert(arg);
      //只要 外部空間的 arg 不變,這裏的引用值固然不會改變
    }
  })(i); //馬上執行該匿名函數,傳遞下標 i (實參)
}

解決辦法二

解決思路:將事件綁定在新增的匿名函數返回的函數上,此時綁定的函數中的 function scope 中的 closure 對象的 引用 arg 是指向將其返回的匿名函數的私有變量 arg

for (var i = 0; i < arr.length; i++) {
  arr[i].onclick = (function (arg) {
    return function () {
      alert(arg);
    }
  })(i);
}

解決辦法三

使用 ES6 新語法 let 關鍵字

for (var i = 0; i < arr.length; i++) {
  let j = i; // 建立一個塊級變量
  arr[i].onclick = function () {
    alert(j);
  }
}

JavaScript 判斷一個變量是對象仍是數組 ?

typeof 都返回 object

在 JavaScript 中全部數據類型嚴格意義上都是對象,但實際使用中咱們仍是有類型之分,若是要判斷一個變量是數組仍是對象使用 typeof 搞不定,由於它全都返回 object。

第一,使用 typeof 加 length 屬性

數組有 length 屬性,object 沒有,而 typeof 數組與對象都返回 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 判斷數據類型是對象仍是數組時應該優先判斷 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 的繼承有什麼區別 ?

ES5 的繼承時經過 prototype 或構造函數機制來實現。

  • ES5 的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到 this 上(Parent.apply(this))
  • ES6 的繼承機制徹底不一樣,實質上是先建立父類的實例對象 this(因此必須先調用父類的 super()方法),而後再用子類的構造函數修改 this

具體的:ES6 經過 class 關鍵字定義類,裏面有構造方法,類之間經過 extends 關鍵字實現繼承。子類必須在 constructor 方法中調用 super 方法,不然新建實例報錯。由於子類沒有本身的 this 對象,而是繼承了父類的 this 對象,而後對其進行加工。若是不調用 super 方法,子類得不到 this 對象。

ps:super 關鍵字指代父類的實例,即父類的 this 對象。在子類構造函數中,調用 super 後,纔可以使用 this 關鍵字,不然報錯。


翻轉一個字符串

先將字符串轉成一個數組,而後用數組的 reverse() + join() 方法。

let a = "hello word";
let b = [...str].reverse().join(""); // drow olleh

說說堆和棧的區別 ?
 

1、堆棧空間分配區別
  

  • 棧(操做系統):由操做系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧;  
  • 堆(操做系統):通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由 OS 回收,分配方式卻是相似於鏈表。  

2、堆棧緩存方式區別

  • 棧使用的是一級緩存, 他們一般都是被調用時處於存儲空間中,調用完畢當即釋放;  
  • 堆是存放在二級緩存中,生命週期由虛擬機的垃圾回收算法來決定(並非一旦成爲孤兒對象就能被回收)。因此調用這些對象的速度要相對來得低一些。  

3、堆棧數據結構區別

  • 堆(數據結構):堆能夠被當作是一棵樹,如:堆排序;  
  • 棧(數據結構):一種先進後出的數據結構。

js 經典面試知識文章


ES6 +

ES6 聲明變量的六種方法

  • ES5 只有兩種聲明變量的方法:var 和 function 。
  • ES6 除了添加 let 和 const 命令。
  • 還有兩種聲明變量的方法:import 命令和 class 命令。

Promise 的隊列與 setTimeout 的隊列有何關聯 ?

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);

爲何結果是:1, 2, 3, 5, 4;而不是:1, 2, 3, 4, 5 ?

js 裏面有宏任務(macrotask)和微任務(microtask)。

由於 setTimeout 是屬於 macrotask 的,而整個 script 也是屬於一個 macrotask,promise.then 回調是 microtask,執行過程大概以下:

  • 因爲整個 script 也屬於一個 macrotask,因爲會先執行 macrotask 中的第一個任務,再加上 promise 構造函數由於是同步的,因此會先打印出 1 和 2;
  • 而後繼續同步執行末尾的 console.log(3) 打印出 3;
  • 此時 setTimeout 被推動到 macrotask 隊列中, promise.then 回調被推動到 microtask 隊列中;
  • 因爲在第一步中已經執行完了第一個 macrotask ,因此接下來會順序執行全部的 microtask,也就是 promise.then 的回調函數,從而打印出 5;
  • microtask 隊列中的任務已經執行完畢,繼續執行剩下的 macrotask 隊列中的任務,也就是 setTimeout,因此打印出 4。

ES6+ 面試知識文章

最後

前端硬核面試專題的完整版在此:前端硬核面試專題,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數據結構與算法 + Git 。

若是以爲本文還不錯,記得給個 star , 你的 star 是我持續更新的動力!。

據說點收藏,不點讚的都是在耍流氓 -_-

圖片描述

相關文章
相關標籤/搜索