let
var
const
相關問題
var
: 只有函數做用域和全局做用域,沒有塊級做用域,變量提高,在全局定義的話,能夠經過 window.定義的變量名
找到,能夠重複聲明變量。
let
: 有塊級做用域,沒有變量提高(這裏網上不少不一樣意見),可是存在暫時性死區,聲明一個變量以後不能重複聲明。
const
: 和 let
同樣,可是 const
聲明的變量是常量,不能夠再賦值,而且 const
聲明變量的時候,必須初始化。node
遇到的題目git
請寫出如下 name 的值,而且說明爲何es6
var name = 'a';
(function() {
console.log(name);
})();
// 輸出 a, 由於在函數內並無聲明 name 屬性,因此沿着做用域鏈去查找 name, 在全局環境找到,輸出
var name = 'a';
(function(name) {
console.log(name);
name = 'd';
})();
// 輸出 undefined ,由於函數有形參 name 至關於在函數體內定義了 var name; 運行函數的時候並無傳參,而且在沒有賦值以前就輸出,因此輸出 undefined
var name = 'a';
(function() {
console.log(name);
var name = 'd';
})();
// 輸出 undefined, 由於 var name 提早,實際上和下面的題目同樣
var name = 'a';
(function() {
var name;
console.log(name);
name = 'd';
})();
// 輸出 undefined,沒有賦值以前就讀取值了
var name = 'a';
(function() {
console.log(name);
let name = 'd';
})();
// ReferenceError: name is not defined,由於 let name 聲明以前,name 是一個暫時性死區,沒法讀取它的值
var name = 'a';
function name() {
return 1;
}
(function() {
console.log(name);
})();
// 函數聲明解析先於變量聲明,因此 name 首先是一個函數,而後被從新定義成一個 字符串,輸出 a
複製代碼
esnext
經常使用的新特性github
let
const
箭頭函數 class
Promise
async/await
展開運算符 for...of
循環 rest
參數 精簡函數 等。開放性題目。面試
cookie
sessionStorage
localStorage
的區別編程
這三個都是本地存儲,能夠把信息記錄在客戶端。
cookie
永久儲存,能夠設置過時時間,存儲大小通常在 4K
左右,每次請求會附帶在 header
上,會影響性能。
sessionStorage
頁面關閉就清理,不能設置過時時間,不會附帶在 header
上,存儲大小 5M
左右。
localStorage
永久存儲,其餘和 sessionStorage
同樣。 建議: 最好不要用 cookie
做爲本地存儲了,有能夠完美代替的方案。跨域
es5
的 function
和 es6
的箭頭函數有什麼區別數組
function
內部可使用 arguments
對象, this
指向函數運行時的對象,能夠做爲構造器使用,能夠被 new
,可使用 prototype
屬性, 可使用 yield
關鍵字。瀏覽器
箭頭函數內部沒有 arguments
對象,若是想獲取參數列表請使用 rest
參數,沒有 this
,它只會從本身的做用域鏈的上一層繼承 this
,不能做爲構造器使用,因此也不能被 new
,不能使用 prototype
屬性, 不能使用 yield
關鍵字。緩存
實際上通常答 this
和 arguments
這兩個不一樣就行了。
this
的定義
在函數中,能夠引用運行環境中的變量。所以就須要一個機制來讓咱們能夠在函數體內部獲取當前的運行環境,這即是 this
。 所以要明白 this
指向,其實就是要搞清楚 函數的運行環境,說人話就是,誰調用了函數。例如:
obj.fn()
,即是 obj
調用了函數,既函數中的 this === obj
fn()
,這裏能夠當作 window.fn()
,所以 this === window
但這種機制並不徹底能知足咱們的業務需求,所以提供了三種方式能夠手動修改 this
的指向:
call: fn.call(target, 1, 2)
apply: fn.apply(target, [1, 2])
bind: fn.bind(target)(1,2)
箭頭函數沒有 this
, 它只會從本身的做用域鏈的上一層繼承 this
遇到的題目
function a() {
return () => {
return () => {
console.log(this);
};
};
}
console.log(a()()());
// 輸出 window 對象,由於箭頭函數沒有 this,因此一直延做用域鏈找到 a 函數定義的地方,this 和 a 函數內部的 this 是同樣的,a在全局環境調用,因此是 window
複製代碼
.call
.apply
.bind
三者之間的區別
簡單點來講,這三個均可以改變函數運行的做用域,第一個參數都是函數運行須要綁定的 this
, .call
後面的參數列表是函數運行時的參數列表, .apply
後面的參數是一個數組或者類數組,執行函數,.bind
和 .call
同樣,後面的參數列表是初始參數,執行後返回一個原函數的拷貝。
.call
根據輸入的參數運行函數
fun.call(thisArg, arg1, arg2, ...)
thisArg
在 fun 函數運行時指定的 this
值, 爲空的時候指向 window
arg1, arg2, ...
指定的參數列表。
.apply
根據輸入的參數運行函數
fun.apply(thisArg, [argsArray])
thisArg
在 fun 函數運行時指定的 this
值, 爲空的時候指向 window
argsArray ...
數組或者類數組,函數運行時的參數列表。
.bind
返回一個原函數的拷貝,並擁有指定的 this 值和初始參數。
function.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
調用綁定函數時做爲 this 參數傳遞給目標函數的值。 若是使用 new 運算符構造綁定函數,則忽略該值。
arg1, arg2, ...
當目標函數被調用時,預先添加到綁定函數的參數列表中的參數。
async/await
的錯誤處理機制
try...catch
在有 Promise
和 Generator
的條件下,爲何還加了 async/await
1.更好的語義,async
和 await
,比起 *
和 yield
,語義更清楚了。
2.async
函數內置執行器,函數調用以後,會自動執行。
3.async
函數的返回值是 Promise
對象。
4.更廣的適用性。co
模塊約定,yield
命令後面只能是 Thunk
函數或 Promise
對象,而 async
函數的 await
命令後面,能夠是 Promise
對象和原始類型的值。
瀏覽器中 JavaScript
的 eventloop
筆者的語言能力很是弱,我以爲我表述的不是很清楚。能夠參考這裏面的說法,相關題目也能夠找到:
常見異步筆試題
發現這個被提問的仍是比較多的,面試了很多公司都有相似的題目。
Common.js
和 ES6
的模塊處理的區別
require
支持 動態導入,import
不支持,正在提案 (babel
能夠經過插件得到支持)
require
是 同步 導入,import
屬於 異步 導入
require
是 值拷貝,導出值變化不會影響導入值;import
指向 內存地址,導入值會隨導出值而變化
JS
經常使用的跨越有哪些
img
標籤,JSONP
, CORS
, postMessage
思考題,JSONP
除了只能進行 get
請求之外,還有什麼缺點,面試被追問過。不提供答案了。
Debounce
手寫
防抖和節流問到的都挺多的
// 既然是手寫就寫最簡單的就好
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}
// 或者面試官說,若是要 ES5 環境下的
function debounce(fn, delay) {
var timer;
return function() {
var args = arguments;
var _this = this;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(_this, args);
}, delay);
};
}
複製代碼
try...catch
和拋錯相關的問題
請寫出這段代碼的問題,請至少寫出兩個。
function fn() {
try {
setTimeout(() => {
throw 'error';
}, 0);
} catch (e) {
console.error(e);
}
}
fn();
複製代碼
我筆試的時候寫出了兩個,真寫不出第三個是啥
1.直接拋字符型的錯誤,不用 Error
對象拋出,錯誤被截獲的時候,沒有任何堆棧信息。
2.這裏的拋錯,沒有被 catch
,其實很簡單的能懂的一個道理,setTimeout
裏面函數執行的時候,fn
早執行完了。
斐波那契數列
手寫
很簡單的遞歸題目,在紅寶書裏面有解
function fn(n) {
if (n < 3) {
return 1;
}
return fn(n - 1) + fn(n - 2);
// return arguments.callee(n - 1) + arguments.callee(n - 2);
}
// 面試官:若是不用遞歸呢?
function fn(n) {
let a = 0;
let b = 1;
for (let i = 0; i < n; i++) {
[a, b] = [b, a + b];
}
return a;
}
複製代碼
Throttle
手寫
防抖和節流問到的都挺多的,可是不多遇到兩個一塊兒出的,最多另一個問原理,一個實現。
// 既然是手寫就寫最簡單的就好
function throttle(fn, delay) {
let timer;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
}
};
}
// 或者面試官說,若是要 ES5 環境下的
function throttle(fn, delay) {
var timer;
return function() {
var args = arguments;
var _this = this;
if (!timer) {
timer = setTimeout(function() {
fn.apply(_this, args);
timer = null;
}, delay);
}
};
}
複製代碼
JS
類型轉換問題
對象轉基本類型
對象轉基本類型的問題,首先會調用對象的 [Symbol.toPrimitive]
,而後到 valueOf
,而後是 toString
。這三個方法均可以重寫。
const a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
};
1 + a; // => 3
'1' + a; // => '12'
複製代碼
四則運算符
除了加法之外,其餘運算都會把參與運算的值轉換成數字,轉換不了就 NaN
,若是是加法的話,運算中其中一方是字符串,也會把運算另一方轉成字符串。 +
-
運算符,也能夠把值轉換成數字,而且運算符等級比四則運算高。
1 + '1'; // '11'
2 * '2'; // 4
[1, 2] + [2, 1]; // '1,22,1'
'a' + +'b'; // -> 'aNaN' for + 'b' = NaN -> 'a' + NaN = 'aNaN'
1 + -[2, 1]; // -> NaN for -[2, 1] = NaN -> 1 + NaN = NaN
複製代碼
==
運算符
1.當比較數字和字符串時,字符串會轉換成數字值。 JavaScript
嘗試將數字字面量轉換爲數字類型的值。 首先, 一個數學上的值會從數字字面量中衍生出來,而後獲得被四捨五入後的數字類型的值。
2.若是其中一個操做數爲布爾類型,那麼布爾操做數若是爲true
,那麼會轉換爲1
,若是爲false
,會轉換爲整數0
,即0
。
3.若是一個對象與數字或字符串相比較,JavaScript
會嘗試返回對象的默認值。操做符會嘗試經過方法valueOf
和toString
將對象轉換爲其原始值(一個字符串或數字類型的值)。若是嘗試轉換失敗,會產生一個運行時錯誤。 4.注意:當且僅當與原始值比較時,對象會被轉換爲原始值。當兩個操做數均爲對象時,它們做爲對象進行比較,僅當它們引用相同對象時返回true
。
[0] == false
[1] == [1]
[] == ![]
// 試着求結果而且解釋爲何,真實面試題。
複製代碼
數組去重手寫
// 最簡單
function fn(arr) {
return [...new Set(...arr)];
}
// 每每面試官要的不是這個答案
// 實現方法不少,在不考慮時間複雜度的狀況下,怎麼寫都行,畢竟面向面試編程
複製代碼
數組鋪平實現
// 實際寫代碼
function fn(arr) {
return arr.flat(Infinity);
}
// 可是面試官說,這麼寫就沒意思了,因而能夠換一個遞歸實現的
function fn(arr) {
const res = [];
arr.forEach(item => {
if (Array.isArray(item)) {
res.push(...fn(item));
} else {
res.push(item);
}
});
return res;
}
// 面試官說,若是不用遞歸呢,那就迭代吧
function fn(arr) {
const res = [];
// 因爲會破壞原數組,最好淺複製一下,保護原數組
const tmp = [...arr];
while (tmp.length) {
const item = tmp.shift();
if (Array.isArray(item)) {
tmp.unshift(...item);
} else {
res.push(item);
}
}
return res;
}
// 方法不少,可是千萬別寫這種,面試官說若是我裏面什麼數據類型都有,下面的寫法只會返回字符串
function fn(arr) {
return arr.toString().split(',');
}
複製代碼
函數簽名的問題,怎麼處理
// 面試官問了這個問題,千萬別答 [1, 2, 3]
[1, 2, 3].map(parseInt); // [1, NaN, NaN]
// 看看 parseInt 的參數聲明,人家是有兩個參數的。
parseInt: (s: string, radix?: number): number
複製代碼
parseInt
接受了 map
的值做爲第一個參數,索引做爲第二個參數,而後數組自己這個參數沒用到,被忽略了,因此計算過程是
parseInt(1, 0); // 1
parseInt(2, 1); // 基數沒有 1 的,返回 NaN
parseInt(3, 2); // 二進制不可能出現 3 的,只可能有 0 和 1 ,因此仍是 NaN
複製代碼
想輸出預期結果很簡單 [1, 2, 3].map(i => parseInt(i))
或者 [1, 2, 3].map(Number)
面試官又說,若是我以上兩種都不想寫,而且要在 parseInt
上面修改,這時候,面試官的意圖就出來了,面試官想要的答案是函數柯里化。寫一個返回基數是 10
的方法就好。
function radix10ParseInt(s: string): number {
return parseInt(s, 10);
}
[1, 2, 3].map(radix10ParseInt);
複製代碼
實現一個函數柯里化
由於你答出了函數柯里化,面試官就會問你怎麼實現相似的效果。若是面試官出的題目,參數很少的狀況下,能夠不寫通用的,畢竟功能實現了,可是通用的更好。
function curry(fn, ...args) {
const len = fn.length;
return (...innerArgs) => {
const mergedArgs = [...args, ...innerArgs];
if (len !== mergedArgs.length) {
return curry(fn, ...mergedArgs);
}
return fn(...mergedArgs);
};
}
const addOrigin = (a, b, c, d, e) => a + b + c + d + e;
const add = curry(addOrigin);
const add2 = curry(addOrigin, 1, 2);
add(1)(2, 3)(4, 5);
add(1)(2)(3)(4)(5);
add2(3, 4)(5);
複製代碼
Vue
雙向綁定的原理,有什麼缺陷,Vue 3.0
中作了什麼處理,有什麼優勢
經過 Object.defineProperty
深度遍歷全部每一個須要雙向綁定的對象的屬性,重寫 get
, set
方法,在獲取屬性被獲取的時候添加訂閱者,在屬性修改的時候通知全部訂閱者更新。
寫一個簡單的實現代碼,別說,我還真遇到手寫簡單的,寫起來不算難。
function observe(obj) {
for (let prop in obj) {
define(obj, prop, obj[prop]);
}
}
function define(obj, prop, value) {
if (typeof obj[prop] === 'object') {
observe(obj[prop]);
}
const dep = new Dep();
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
get() {
// 當有獲取該屬性時,證實依賴於該對象,所以被添加進收集器中
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
// 從新設置值時,觸發收集器的通知機制
set(newVal) {
value = newVal;
dep.notify();
}
});
}
// 依賴收集器
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => {
sub.update();
});
}
}
Dep.target = null;
class Watcher {
constructor(obj, key, cb) {
Dep.target = this;
this.cb = cb;
this.key = key;
this.obj = obj;
// 觸發 get 方法,把這個觀察者添加到依賴列表
this.value = obj[key];
Dep.target = null;
}
update() {
this.value = this.obj[this.key];
this.cb(this.value);
}
}
// key
const data = { key: 1 };
observe(data);
new Watcher(data, 'key', val => {
console.log(val);
});
data.key = '2';
// 複製到 瀏覽器控制檯,或者直接用node能夠測試
複製代碼
Object.defineProperty
的缺陷,其實, Vue
教程列表渲染那章其實說的很清楚了。
1.Object.defineProperty
沒法監控到數組下標的變化,致使經過數組下標添加元素,不能實時響應;
補充 Vue 對 push,pop,splice 等八種方法進行了 hack 處理,這些方法引發的變化是能夠檢測的,因此其餘數組的屬性也是檢測不到的。
2.Object.defineProperty
只能劫持對象的屬性,從而須要對每一個對象,每一個屬性進行遍歷,若是,屬性值是對象,還須要深度遍歷。
Vue
3.0 用 Proxy
替代了 Object.defineProperty
Proxy
優點:
1.Proxy
能夠劫持整個對象,並返回一個新的對象。而且能夠代理動態增長的屬性。
2.能監聽數組的變化。
3.有 13 種挾持操做。
劣勢:
Proxy
是 ES6
新增的屬性,而且沒法 polyfill
。
List diff
實例問題
好比原來的 List
[a, b, c, d]
怎麼變成 [a, c, e, d, f]
這類問題,這裏就不提供答案了。
虛擬 DOM
的好處
一個很簡單的總結: 用 js
對象模擬 DOM
結構,經過 diff
在 JS
對象處理數據更新,避免頻繁操做 DOM
,提升性能。
參考連接
瀏覽器的渲染原理
渲染過程也是問的比較多的。
HTML
解析器解析 HTML
代碼, 生成 DOM
樹CSS
解析器解析 CSS
代碼,生成 CCSOM
樹DOM
樹和 CSSOM
樹合併,生成渲染樹總結: 構建DOM
-> 構建CSSOM
-> 構建渲染樹 -> 佈局 -> 繪製
構建 DOM
和構建 CSSOM
是並行的。
JavaScript
的加載、解析與執行會阻塞 DOM 的構建。 而且因爲 JavaScript
能夠操做 CSSOM
,不完整的 CSSOM 是沒法使用的 ,這時候就先等 CSSOM
構建完成纔會執行 JS
文件,所以在遇到有 JS
加載的時候 CSSOM
也會阻塞 DOM
構建。
面向面試的話,答出這些就行了。詳細請點擊這裏: 參考連接
DOMContentLoaded
和 Load
事件的差異
可能面試官問你的問題不是這個,多是 window.load
和 $(document).ready
的差異,其實問題差很少,固然,這個須要答的更多,核心仍是同樣的。 DOMContentLoaded
在 DOM
構建完成就觸發。
load
在頁面全部資源加載完成後才觸發,包括靜態資源。
Http
緩存策略
強緩存(Expires
和 Cache-Control
)和協商緩存(Last-Modified
和 Etag
)
強緩存表示在緩存期間不須要請求,state code
爲 200
Expires
// 表示文件 `Fri, 09 May 2019 09:00:00 GMT` 過時
Expires: Fri, 09 May 2019 09:00:00 GMT
複製代碼
Cache-control
// 表示文件 31536000 秒後過時,也就是一年
Cache-control: max-age=31536000
複製代碼
若是緩存過時了,咱們就可使用協商緩存來解決問題。協商緩存須要請求,若是緩存有效會返回 304
。
Last-Modified
(res header) 和 If-Modified-Since
(req header)
Last-Modified 表示本地文件最後修改日期,If-Modified-Since
會將 Last-Modified
的值發送給服務器,詢問服務器在該日期後資源是否有更新,有更新的話就會將新的資源發送回來。缺點是,精度只能到秒,秒內的修改無法檢測到,若是文件修改,內容沒變,緩存仍是會刷新。因此就有了 Etag
。
ETag
(res header) 和 If-None-Match
(req header)
ETag
相似於文件指紋,If-None-Match
會將當前 ETag
發送給服務器,詢問該資源 ETag
是否變更,有變更的話就將新的資源發送回來。而且 ETag
優先級比 Last-Modified
高。
觀察者和發佈訂閱模式的差異
這個在電話面試裏面被問到,能夠參考 這個連接。
能夠想想,Vue
雙向綁定用的是觀察者,仍是發佈訂閱。Vuex
呢。
Cors
如何實現同一個主域下所有子域均可以跨域
這個被問到,這裏就不提供答案了,
如何實現輪詢
不停的發請求和 WebSocket
等。
今年我遇到的面試題大概就這些,有一些零零碎碎的不寫了,一些比較熱門的好比對象,繼承這些沒遇到過,反而一些挺冷門的問題遇到了,面試,基礎仍是很重要,現場編程能力也很重要的,面試過程當中和麪試官聊天仍是很不錯的,溝通仍是很重要,必須鍛鍊本身的溝通能力。第一次寫面試總結,寫的很差請你們見諒,有錯誤請指出。裏面有些題目的答案沒給出,有些是故意的,有些是我自己總結的也很差,但願在評論區有人給出答案。