閉包產(chǎn)生的本質(zhì)
當(dāng)前環(huán)境中存在指向父級作用域的引用
手寫 bind伴挚、apply、call
// call
Function.prototype.call = function (context, ...args) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
context[fnSymbol](...args);
delete context[fnSymbol];
}
復(fù)制代碼
// apply
Function.prototype.apply = function (context, argsArr) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
context[fnSymbol](...argsArr);
delete context[fnSymbol];
}
復(fù)制代碼
// bind
Function.prototype.bind = function (context, ...args) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
return function (..._args) {
args = args.concat(_args);
context[fnSymbol](...args);
delete context[fnSymbol];
}
}
復(fù)制代碼
函數(shù)防抖
觸發(fā)高頻事件 N 秒后只會執(zhí)行一次缩滨,如果 N 秒內(nèi)事件再次觸發(fā)呐萨,則會重新計時。
簡單版:函數(shù)內(nèi)部支持使用 this 和 event 對象底哥;
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
復(fù)制代碼
使用:
var node = document.getElementById('layout')
function getUserAction(e) {
console.log(this, e) // 分別打恿啊:node 這個節(jié)點 和 MouseEvent
node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)
復(fù)制代碼
最終版:除了支持 this 和 event 外房官,還支持以下功能:
- 支持立即執(zhí)行;
- 函數(shù)可能有返回值续滋;
- 支持取消功能翰守;
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已經(jīng)執(zhí)行過,不再執(zhí)行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
} else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
復(fù)制代碼
使用:
var setUseAction = debounce(getUserAction, 10000, true);
// 使用防抖
node.onmousemove = setUseAction
// 取消防抖
setUseAction.cancel()
復(fù)制代碼
symbol
有什么用處
可以用來表示一個獨一無二的變量防止命名沖突疲酌。但是面試官問還有嗎蜡峰?我沒想出其他的用處就直接答我不知道了,還可以利用 symbol
不會被常規(guī)的方法(除了 Object.getOwnPropertySymbols
外)遍歷到朗恳,所以可以用來模擬私有變量湿颅。
主要用來提供遍歷接口,布置了 symbol.iterator
的對象才可以使用 for···of
循環(huán)粥诫,可以統(tǒng)一處理數(shù)據(jù)結(jié)構(gòu)油航。調(diào)用之后回返回一個遍歷器對象,包含有一個 next 方法臀脏,使用 next 方法后有兩個返回值 value 和 done 分別表示函數(shù)當(dāng)前執(zhí)行位置的值和是否遍歷完畢劝堪。
Symbol.for() 可以在全局訪問 symbol
如何判斷一個對象是不是空對象?
Object.keys(obj).length === 0
手寫題:在線編程揉稚,getUrlParams(url,key); 就是很簡單的獲取url的某個參數(shù)的問題秒啦,但要考慮邊界情況,多個返回值等等
什么是作用域搀玖?
ES5 中只存在兩種作用域:全局作用域和函數(shù)作用域余境。在 JavaScript 中,我們將作用域定義為一套規(guī)則灌诅,這套規(guī)則用來管理引擎如何在當(dāng)前作用域以及嵌套子作用域中根據(jù)標(biāo)識符名稱進(jìn)行變量(變量名或者函數(shù)名)查找
AJAX
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
復(fù)制代碼
實現(xiàn)數(shù)組原型方法
forEach
Array.prototype.forEach2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this) // this 就是當(dāng)前的數(shù)組
const len = O.length >>> 0 // 后面有解釋
let k = 0
while (k < len) {
if (k in O) {
callback.call(thisArg, O[k], k, O);
}
k++;
}
}
復(fù)制代碼
O.length >>> 0 是什么操作芳来?就是無符號右移 0 位,那有什么意義嘛猜拾?就是為了保證轉(zhuǎn)換后的值為正整數(shù)即舌。其實底層做了 2 層轉(zhuǎn)換,第一是非 number 轉(zhuǎn)成 number 類型挎袜,第二是將 number 轉(zhuǎn)成 Uint32 類型
map
基于 forEach 的實現(xiàn)能夠很容易寫出 map 的實現(xiàn):
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.map2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
- let k = 0
+ let k = 0, res = []
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ res[k] = callback.call(thisArg, O[k], k, O);
}
k++;
}
+ return res
}
復(fù)制代碼
filter
同樣顽聂,基于 forEach 的實現(xiàn)能夠很容易寫出 filter 的實現(xiàn):
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.filter2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
- let k = 0
+ let k = 0, res = []
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ if (callback.call(thisArg, O[k], k, O)) {
+ res.push(O[k])
+ }
}
k++;
}
+ return res
}
復(fù)制代碼
some
同樣,基于 forEach 的實現(xiàn)能夠很容易寫出 some 的實現(xiàn):
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.some2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
let k = 0
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ if (callback.call(thisArg, O[k], k, O)) {
+ return true
+ }
}
k++;
}
+ return false
}
復(fù)制代碼
reduce
Array.prototype.reduce2 = function(callback, initialValue) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
let k = 0, acc
if (arguments.length > 1) {
acc = initialValue
} else {
// 沒傳入初始值的時候盯仪,取數(shù)組中第一個非 empty 的值為初始值
while (k < len && !(k in O)) {
k++
}
if (k > len) {
throw new TypeError( 'Reduce of empty array with no initial value' );
}
acc = O[k++]
}
while (k < len) {
if (k in O) {
acc = callback(acc, O[k], k, O)
}
k++
}
return acc
}
復(fù)制代碼
深淺拷貝
淺拷貝:只考慮對象類型紊搪。
function shallowCopy(obj) {
if (typeof obj !== 'object') return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
復(fù)制代碼
簡單版深拷貝:只考慮普通對象屬性,不考慮內(nèi)置對象和函數(shù)全景。
function deepClone(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}
復(fù)制代碼
復(fù)雜版深克乱:基于簡單版的基礎(chǔ)上,還考慮了內(nèi)置對象比如 Date爸黄、RegExp 等對象和函數(shù)以及解決了循環(huán)引用的問題滞伟。
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;
function deepClone(target, map = new WeakMap()) {
if (map.get(target)) {
return target;
}
// 獲取當(dāng)前值的構(gòu)造函數(shù):獲取它的類型
let constructor = target.constructor;
// 檢測當(dāng)前對象target是否與正則揭鳞、日期格式對象匹配
if (/^(RegExp|Date)$/i.test(constructor.name)) {
// 創(chuàng)建一個新的特殊對象(正則類/日期類)的實例
return new constructor(target);
}
if (isObject(target)) {
map.set(target, true); // 為循環(huán)引用的對象做標(biāo)記
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
}
復(fù)制代碼
事件傳播機(jī)制(事件流)
冒泡和捕獲
為什么需要瀏覽器緩存?
對于瀏覽器的緩存诗良,主要針對的是前端的靜態(tài)資源鲁驶,最好的效果就是鉴裹,在發(fā)起請求之后,拉取相應(yīng)的靜態(tài)資源钥弯,并保存在本地径荔。如果服務(wù)器的靜態(tài)資源沒有更新,那么在下次請求的時候脆霎,就直接從本地讀取即可总处,如果服務(wù)器的靜態(tài)資源已經(jīng)更新,那么我們再次請求的時候睛蛛,就到服務(wù)器拉取新的資源鹦马,并保存在本地。這樣就大大的減少了請求的次數(shù)忆肾,提高了網(wǎng)站的性能荸频。這就要用到瀏覽器的緩存策略了。
所謂的瀏覽器緩存指的是瀏覽器將用戶請求過的靜態(tài)資源客冈,存儲到電腦本地磁盤中旭从,當(dāng)瀏覽器再次訪問時,就可以直接從本地加載场仲,不需要再去服務(wù)端請求了和悦。
使用瀏覽器緩存,有以下優(yōu)點:
- 減少了服務(wù)器的負(fù)擔(dān)渠缕,提高了網(wǎng)站的性能
- 加快了客戶端網(wǎng)頁的加載速度
- 減少了多余網(wǎng)絡(luò)數(shù)據(jù)傳輸
什么是作用域鏈鸽素?
首先要了解作用域鏈,當(dāng)訪問一個變量時亦鳞,編譯器在執(zhí)行這段代碼時馍忽,會首先從當(dāng)前的作用域中查找是否有這個標(biāo)識符,如果沒有找到蚜迅,就會去父作用域查找舵匾,如果父作用域還沒找到繼續(xù)向上查找,直到全局作用域為止,谁不,而作用域鏈坐梯,就是有當(dāng)前作用域與上層作用域的一系列變量對象組成,它保證了當(dāng)前執(zhí)行的作用域?qū)Ψ显L問權(quán)限的變量和函數(shù)的有序訪問刹帕。
函數(shù)中的arguments是數(shù)組嗎吵血?類數(shù)組轉(zhuǎn)數(shù)組的方法了解一下谎替?
是類數(shù)組,是屬于鴨子類型的范疇蹋辅,長得像數(shù)組钱贯,
- ... 運算符
- Array.from
- Array.prototype.slice.apply(arguments)
JS 數(shù)據(jù)類型
基本類型:Number、Boolean侦另、String秩命、null、undefined褒傅、symbol(ES6 新增的)弃锐,BigInt(ES2020)
引用類型:Object,對象子類型(Array殿托,F(xiàn)unction)
什么是文檔的預(yù)解析霹菊?
Webkit 和 Firefox 都做了這個優(yōu)化,當(dāng)執(zhí)行 JavaScript 腳本時支竹,另一個線程解析剩下的文檔旋廷,并加載后面需要通過網(wǎng)絡(luò)加載的資源。這種方式可以使資源并行加載從而使整體速度更快礼搁。需要注意的是饶碘,預(yù)解析并不改變 DOM 樹,它將這個工作留給主解析過程叹坦,自己只解析外部資源的引用熊镣,比如外部腳本、樣式表及圖片募书。
如何防御 XSS 攻擊绪囱?
可以看到XSS危害如此之大, 那么在開發(fā)網(wǎng)站時就要做好防御措施莹捡,具體措施如下:
- 可以從瀏覽器的執(zhí)行來進(jìn)行預(yù)防鬼吵,一種是使用純前端的方式,不用服務(wù)器端拼接后返回(不使用服務(wù)端渲染)篮赢。另一種是對需要插入到 HTML 中的代碼做好充分的轉(zhuǎn)義齿椅。對于 DOM 型的攻擊,主要是前端腳本的不可靠而造成的启泣,對于數(shù)據(jù)獲取渲染和字符串拼接的時候應(yīng)該對可能出現(xiàn)的惡意代碼情況進(jìn)行判斷涣脚。
- 使用 CSP ,CSP 的本質(zhì)是建立一個白名單寥茫,告訴瀏覽器哪些外部資源可以加載和執(zhí)行遣蚀,從而防止惡意代碼的注入攻擊。
- CSP 指的是內(nèi)容安全策略,它的本質(zhì)是建立一個白名單芭梯,告訴瀏覽器哪些外部資源可以加載和執(zhí)行险耀。我們只需要配置規(guī)則,如何攔截由瀏覽器自己來實現(xiàn)玖喘。
- 通常有兩種方式來開啟 CSP甩牺,一種是設(shè)置 HTTP 首部中的 Content-Security-Policy,一種是設(shè)置 meta 標(biāo)簽的方式
- 對一些敏感信息進(jìn)行保護(hù)累奈,比如 cookie 使用 http-only贬派,使得腳本無法獲取。也可以使用驗證碼费尽,避免腳本偽裝成用戶執(zhí)行一些操作赠群。
用過 TypeScript 嗎?它的作用是什么旱幼?
為 JS 添加類型支持,以及提供最新版的 ES 語法的支持突委,是的利于團(tuán)隊協(xié)作和排錯柏卤,開發(fā)大型項目
瀏覽器本地存儲方式及使用場景
(1)Cookie
Cookie是最早被提出來的本地存儲方式,在此之前匀油,服務(wù)端是無法判斷網(wǎng)絡(luò)中的兩個請求是否是同一用戶發(fā)起的缘缚,為解決這個問題,Cookie就出現(xiàn)了敌蚜。Cookie的大小只有4kb桥滨,它是一種純文本文件,每次發(fā)起HTTP請求都會攜帶Cookie弛车。
Cookie的特性:
- Cookie一旦創(chuàng)建成功齐媒,名稱就無法修改
- Cookie是無法跨域名的,也就是說a域名和b域名下的cookie是無法共享的纷跛,這也是由Cookie的隱私安全性決定的喻括,這樣就能夠阻止非法獲取其他網(wǎng)站的Cookie
- 每個域名下Cookie的數(shù)量不能超過20個,每個Cookie的大小不能超過4kb
- 有安全問題贫奠,如果Cookie被攔截了唬血,那就可獲得session的所有信息,即使加密也于事無補唤崭,無需知道cookie的意義拷恨,只要轉(zhuǎn)發(fā)cookie就能達(dá)到目的
- Cookie在請求一個新的頁面的時候都會被發(fā)送過去
如果需要域名之間跨域共享Cookie,有兩種方法:
- 使用Nginx反向代理
- 在一個站點登陸之后谢肾,往其他網(wǎng)站寫Cookie腕侄。服務(wù)端的Session存儲到一個節(jié)點,Cookie存儲sessionId
Cookie的使用場景:
- 最常見的使用場景就是Cookie和session結(jié)合使用,我們將sessionId存儲到Cookie中兜挨,每次發(fā)請求都會攜帶這個sessionId膏孟,這樣服務(wù)端就知道是誰發(fā)起的請求,從而響應(yīng)相應(yīng)的信息拌汇。
- 可以用來統(tǒng)計頁面的點擊次數(shù)
(2)LocalStorage
LocalStorage是HTML5新引入的特性柒桑,由于有的時候我們存儲的信息較大,Cookie就不能滿足我們的需求噪舀,這時候LocalStorage就派上用場了魁淳。
LocalStorage的優(yōu)點:
- 在大小方面,LocalStorage的大小一般為5MB与倡,可以儲存更多的信息
- LocalStorage是持久儲存界逛,并不會隨著頁面的關(guān)閉而消失,除非主動清理纺座,不然會永久存在
- 僅儲存在本地息拜,不像Cookie那樣每次HTTP請求都會被攜帶
LocalStorage的缺點:
- 存在瀏覽器兼容問題,IE8以下版本的瀏覽器不支持
- 如果瀏覽器設(shè)置為隱私模式净响,那我們將無法讀取到LocalStorage
- LocalStorage受到同源策略的限制少欺,即端口、協(xié)議馋贤、主機(jī)地址有任何一個不相同赞别,都不會訪問
LocalStorage的常用API:
// 保存數(shù)據(jù)到 localStorage
localStorage.setItem('key', 'value');
// 從 localStorage 獲取數(shù)據(jù)
let data = localStorage.getItem('key');
// 從 localStorage 刪除保存的數(shù)據(jù)
localStorage.removeItem('key');
// 從 localStorage 刪除所有保存的數(shù)據(jù)
localStorage.clear();
// 獲取某個索引的Key
localStorage.key(index)
復(fù)制代碼
LocalStorage的使用場景:
- 有些網(wǎng)站有換膚的功能,這時候就可以將換膚的信息存儲在本地的LocalStorage中配乓,當(dāng)需要換膚的時候仿滔,直接操作LocalStorage即可
- 在網(wǎng)站中的用戶瀏覽信息也會存儲在LocalStorage中,還有網(wǎng)站的一些不常變動的個人信息等也可以存儲在本地的LocalStorage中
(3)SessionStorage
SessionStorage和LocalStorage都是在HTML5才提出來的存儲方案犹芹,SessionStorage 主要用于臨時保存同一窗口(或標(biāo)簽頁)的數(shù)據(jù)崎页,刷新頁面時不會刪除,關(guān)閉窗口或標(biāo)簽頁之后將會刪除這些數(shù)據(jù)羽莺。
SessionStorage與LocalStorage對比:
- SessionStorage和LocalStorage都在本地進(jìn)行數(shù)據(jù)存儲实昨;
- SessionStorage也有同源策略的限制,但是SessionStorage有一條更加嚴(yán)格的限制盐固,SessionStorage只有在同一瀏覽器的同一窗口下才能夠共享荒给;
- LocalStorage和SessionStorage都不能被爬蟲爬取;
SessionStorage的常用API:
// 保存數(shù)據(jù)到 sessionStorage
sessionStorage.setItem('key', 'value');
// 從 sessionStorage 獲取數(shù)據(jù)
let data = sessionStorage.getItem('key');
// 從 sessionStorage 刪除保存的數(shù)據(jù)
sessionStorage.removeItem('key');
// 從 sessionStorage 刪除所有保存的數(shù)據(jù)
sessionStorage.clear();
// 獲取某個索引的Key
sessionStorage.key(index)
復(fù)制代碼
SessionStorage的使用場景
- 由于SessionStorage具有時效性刁卜,所以可以用來存儲一些網(wǎng)站的游客登錄的信息志电,還有臨時的瀏覽記錄的信息。當(dāng)關(guān)閉網(wǎng)站之后蛔趴,這些信息也就隨之消除了挑辆。
箭頭函數(shù)和普通函數(shù)有啥區(qū)別?箭頭函數(shù)能當(dāng)構(gòu)造函數(shù)嗎?
- 普通函數(shù)通過 function 關(guān)鍵字定義鱼蝉, this 無法結(jié)合詞法作用域使用洒嗤,在運行時綁定,只取決于函數(shù)的調(diào)用方式魁亦,在哪里被調(diào)用渔隶,調(diào)用位置。(取決于調(diào)用者洁奈,和是否獨立運行)
- 箭頭函數(shù)使用被稱為 “胖箭頭” 的操作
=>
定義间唉,箭頭函數(shù)不應(yīng)用普通函數(shù) this 綁定的四種規(guī)則,而是根據(jù)外層(函數(shù)或全局)的作用域來決定 this利术,且箭頭函數(shù)的綁定無法被修改(new 也不行)呈野。- 箭頭函數(shù)常用于回調(diào)函數(shù)中,包括事件處理器或定時器
- 箭頭函數(shù)和 var self = this印叁,都試圖取代傳統(tǒng)的 this 運行機(jī)制被冒,將 this 的綁定拉回到詞法作用域
- 沒有原型、沒有 this喉钢、沒有 super姆打,沒有 arguments,沒有 new.target
- 不能通過 new 關(guān)鍵字調(diào)用
- 一個函數(shù)內(nèi)部有兩個方法:[[Call]] 和 [[Construct]]肠虽,在通過 new 進(jìn)行函數(shù)調(diào)用時,會執(zhí)行 [[construct]] 方法玛追,創(chuàng)建一個實例對象税课,然后再執(zhí)行這個函數(shù)體,將函數(shù)的 this 綁定在這個實例對象上
- 當(dāng)直接調(diào)用時痊剖,執(zhí)行 [[Call]] 方法韩玩,直接執(zhí)行函數(shù)體
- 箭頭函數(shù)沒有 [[Construct]] 方法,不能被用作構(gòu)造函數(shù)調(diào)用陆馁,當(dāng)使用 new 進(jìn)行函數(shù)調(diào)用時會報錯找颓。
function foo() {
return (a) => {
console.log(this.a);
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1);
bar.call(obj2);
復(fù)制代碼
點擊刷新按鈕或者按 F5、按 Ctrl+F5 (強制刷新)叮贩、地址欄回車有什么區(qū)別击狮?
- 點擊刷新按鈕或者按 F5: 瀏覽器直接對本地的緩存文件過期,但是會帶上If-Modifed-Since益老,If-None-Match彪蓬,這就意味著服務(wù)器會對文件檢查新鮮度,返回結(jié)果可能是 304捺萌,也有可能是 200档冬。
- 用戶按 Ctrl+F5(強制刷新): 瀏覽器不僅會對本地文件過期,而且不會帶上 If-Modifed-Since,If-None-Match酷誓,相當(dāng)于之前從來沒有請求過披坏,返回結(jié)果是 200。
- 地址欄回車: 瀏覽器發(fā)起請求盐数,按照正常流程棒拂,本地檢查是否過期,然后服務(wù)器檢查新鮮度娘扩,最后返回內(nèi)容着茸。