本文講解 56 道 JavaScript 和 ES6+ 面試題的內容。javascript
複習前端面試的知識,是爲了鞏固前端的基礎知識,最重要的仍是平時的積累!
注意
:文章的題與題之間用下劃線分隔開,答案僅供參考。html
前端硬核面試專題的完整版在此:前端硬核面試專題,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數據結構與算法 + Git 。前端
常見的瀏覽器內核有哪些 ?java
mouseenter 和 mouseover 的區別 git
用正則表達式匹配字符串,以字母開頭,後面是數字、字符串或者下劃線,長度爲 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(沒有其餘對象引用過該對象),或對該對象的唯一引用是循環的,那麼該對象的內存便可回收。
線程與進程的區別 ?
線程在執行過程當中與進程仍是有區別的。
但操做系統並無將多個線程看作多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。
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
alert(0 || 1) 的結果是 1
只要記住 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
代碼解析:這道題的考點分兩個
先看第一個輸出: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、數據類型
解釋清楚 null 和 undefined
null 用來表示還沒有存在的對象,經常使用來表示函數企圖返回一個不存在的對象。 null 表示"沒有對象",即該處不該該有值。
null 典型用法是:
當聲明的變量還未被初始化時,變量的默認值爲 undefined。 undefined 表示"缺乏值",就是此處應該有一個值,可是尚未定義。
未定義的值和定義未賦值的爲 undefined,null 是一種特殊的 object,NaN 是一種特殊的 number。
講一下 1 和 Number(1) 的區別*
講一下 prototype 是什麼東西,原型鏈的理解,何時用 prototype ?
prototype 是函數對象上面預設的對象屬性。
函數裏的 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]"
異步過程的構成要素有哪些?和異步過程是怎樣的 ?
總結一下,一個異步過程一般是這樣的:
因此,從主線程的角度看,一個異步過程包括下面兩個要素:
它們都是在主線程上調用的,其中註冊函數用來發起異步過程,回調函數用來處理結果。
舉個具體的例子:
setTimeout(fn, 1000);
其中的 setTimeout 就是異步過程的發起函數,fn 是回調函數。
注意:前面說的形式 A(args..., callbackFn) 只是一種抽象的表示,並不表明回調函數必定要做爲發起函數的參數。
例如:
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = xxx; // 添加回調函數 xhr.open('GET', url); xhr.send(); // 發起函數
發起函數和回調函數就是分離的。
說說消息隊列和事件循環
異步過程的回調函數,必定不在當前的這一輪事件循環中執行。
session 與 cookie 的區別
cookies 是幹嗎的,服務器和瀏覽器之間的 cookies 是怎麼傳的,httponly 的 cookies 和可讀寫的 cookie 有什麼區別,有無長度限制 ?
請描述一下 cookies,sessionStorage 和 localStorage 的區別
共同點
區別
不共享
,即便是同一個頁面;cookie 和 localStorage 在全部同源窗口中都是共享的。從敲入 URL 到渲染完成的整個過程,包括 DOM 構建的過程,說的約詳細越好
是否瞭解公鑰加密和私鑰加密。如何確保表單提交裏的密碼字段不被泄露。
公鑰用於對數據進行加密,私鑰用於對數據進行解密。
很直觀的理解:公鑰就是公開的密鑰,其公開了你們才能用它來加密數據。私鑰是私有的密鑰,誰有這個密鑰纔可以解密密文。
解決方案 1:
form 在提交的過程當中,對密碼字段是不進行加密而是以明碼的形式進行數據傳輸的。
若是要對數據進行加密,你能夠本身寫一個腳本對內容進行編碼後傳輸,只是這個安全性也並不高。
解決方案 2:
若是想對數據進行加密,你可使用 HTTPS 安全傳輸協議,這個協議是由系統進行密碼加密處理的,在數據傳輸中是絕對不會被攔截獲取的,只是 HTTPS 的架設會相對麻煩點。一些大型網站的登陸、銀行的在線網關等都是走這條路。
驗證碼是幹嗎的,是爲了解決什麼安全問題。
所謂驗證碼,就是將一串隨機產生的數字或符號,生成一幅圖片, 圖片里加上一些干擾象素(防止OCR),由用戶肉眼識別其中的驗證碼信息,輸入表單提交網站驗證,驗證成功後才能使用某項功能。
截取字符串 abcdefg 的 efg。
從第四位開始截取
alert('abcdefg'.substring(4)); alert ('abcdefg'.slice(4))
判斷一個字符串中出現次數最多的字符,統計這個次數
步驟
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 的區別
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 組成
new 操做符具體幹了什麼呢 ?
JSON 的瞭解?
你有哪些性能優化的方法 ?
web 前端是應用服務器處理以前的部分,前端主要包括:HTML、CSS、javascript、image 等各類資源,針對不一樣的資源有不一樣的優化方式。
內容優化
常見方法:合併多個 CSS 文件和 js 文件,利用 CSS Sprites 整合圖像,Inline Images (使用 data:URL scheme 在實際的頁面嵌入圖像數據 ),合理設置 HTTP 緩存等。
服務器優化
Cookie 優化
CSS 優化
javascript 優化
在實際應用中使用外部文件能夠提升頁面速度,由於 JavaScript 和 CSS 文件都能在瀏覽器中產生緩存。
圖像優化
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 事件的區別
js 的兩種回收機制
標記清除(mark and sweep)
從語義上理解就比較好理解了,大概就是當變量進入到某個環境中的時候就把這個變量標記一下,好比標記爲「進入環境」,當離開的時候就把這個變量的標記給清除掉,好比是「離開環境」。而在這後面還有標記的變量將被視爲準備刪除的變量。
這是 javascript 最多見的垃圾回收方式。至於上面有說道的標記,到底該如何標記 ?
好像是有不少方法,好比特殊位翻轉,維護一個列表什麼的。
引用計數(reference counting)
對於新人來講,JavaScript 的原型是一個很讓人頭疼的事情,一來 prototype 容易與 proto 混淆,
1、prototype 和 proto 的區別
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() {}
結果:
/*一、字面量方式*/ 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中的例外狀況)
結果:
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
結果:
閉包的理解 ?
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)回收。
這段代碼中另外一個值得注意的地方,就是
5、使用閉包的注意點
閉包面試經典問題
問題:想每次點擊對應目標時彈出對應的數字下標 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、堆棧空間分配區別
2、堆棧緩存方式區別
3、堆棧數據結構區別
ES6 聲明變量的六種方法
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,執行過程大概以下:
前端硬核面試專題的完整版在此:前端硬核面試專題,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 數據結構與算法 + Git 。
若是以爲本文還不錯,記得給個 star , 你的 star 是我持續更新的動力!。
據說點收藏,不點讚的都是在耍流氓 -_-