說一下JSON.stringify有什么缺點?
1.如果obj里面有時間對象,則JSON.stringify后再JSON.parse的結(jié)果衰腌,時間將只是字符串的形式克伊,而不是對象的形式
2.如果obj里有RegExp(正則表達式的縮寫)踩衩、Error對象衙传,則序列化的結(jié)果將只得到空對象;
3菱父、如果obj里有函數(shù),undefined剑逃,則序列化的結(jié)果會把函數(shù)或 undefined丟失浙宜;
4、如果obj里有NaN蛹磺、Infinity和-Infinity粟瞬,則序列化的結(jié)果會變成null
5、JSON.stringify()只能序列化對象的可枚舉的自有屬性萤捆,例如 如果obj中的對象是有構(gòu)造函數(shù)生成的裙品, 則使用JSON.parse(JSON.stringify(obj))深拷貝后,會丟棄對象的constructor俗或;
6市怎、如果對象中存在循環(huán)引用的情況也無法正確實現(xiàn)深拷貝;
new 操作符
題目描述:手寫 new 操作符實現(xiàn)
實現(xiàn)代碼如下:
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj, ...args);
if (res && (typeof res === "object" || typeof res === "function")) {
return res;
}
return obj;
}
用法如下:
// // function Person(name, age) {
// // this.name = name;
// // this.age = age;
// // }
// // Person.prototype.say = function() {
// // console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
事件總線(發(fā)布訂閱模式)
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
off(name, fn) {
let tasks = this.cache[name]
if (tasks) {
const index = tasks.findIndex(f => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 創(chuàng)建副本辛慰,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊相同事件区匠,會造成死循環(huán)
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
}
}
// 測試
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布蘭', 12)
// '布蘭 12'
// 'hello, 布蘭 12'
數(shù)組扁平化
數(shù)組扁平化就是將 [1, [2, [3]]] 這種多層的數(shù)組拍平成一層 [1, 2, 3]。使用 Array.prototype.flat 可以直接將多層數(shù)組拍平成一層:
[1, [2, [3]]].flat(2) // [1, 2, 3]
現(xiàn)在就是要實現(xiàn) flat 這種效果帅腌。
ES5 實現(xiàn):遞歸驰弄。
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
return result;
}
ES6 實現(xiàn):
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
正向代理和反向代理的區(qū)別
- 正向代理:
客戶端想獲得一個服務(wù)器的數(shù)據(jù),但是因為種種原因無法直接獲取速客。于是客戶端設(shè)置了一個代理服務(wù)器戚篙,并且指定目標服務(wù)器,之后代理服務(wù)器向目標服務(wù)器轉(zhuǎn)交請求并將獲得的內(nèi)容發(fā)送給客戶端挽封。這樣本質(zhì)上起到了對真實服務(wù)器隱藏真實客戶端的目的已球。實現(xiàn)正向代理需要修改客戶端,比如修改瀏覽器配置辅愿。
- 反向代理:
服務(wù)器為了能夠?qū)⒐ぷ髫撦d分不到多個服務(wù)器來提高網(wǎng)站性能 (負載均衡)等目的智亮,當其受到請求后,會首先根據(jù)轉(zhuǎn)發(fā)規(guī)則來確定請求應(yīng)該被轉(zhuǎn)發(fā)到哪個服務(wù)器上点待,然后將請求轉(zhuǎn)發(fā)到對應(yīng)的真實服務(wù)器上阔蛉。這樣本質(zhì)上起到了對客戶端隱藏真實服務(wù)器的作用。
一般使用反向代理后癞埠,需要通過修改 DNS 讓域名解析到代理服務(wù)器 IP状原,這時瀏覽器無法察覺到真正服務(wù)器的存在聋呢,當然也就不需要修改配置了。
正向代理和反向代理的結(jié)構(gòu)是一樣的颠区,都是 client-proxy-server 的結(jié)構(gòu)削锰,它們主要的區(qū)別就在于中間這個 proxy 是哪一方設(shè)置的。在正向代理中毕莱,proxy 是 client 設(shè)置的器贩,用來隱藏 client;而在反向代理中朋截,proxy 是 server 設(shè)置的蛹稍,用來隱藏 server。
同步和異步的區(qū)別
- 同步指的是當一個進程在執(zhí)行某個請求時部服,如果這個請求需要等待一段時間才能返回唆姐,那么這個進程會一直等待下去,直到消息返回為止再繼續(xù)向下執(zhí)行廓八。
- 異步指的是當一個進程在執(zhí)行某個請求時奉芦,如果這個請求需要等待一段時間才能返回,這個時候進程會繼續(xù)往下執(zhí)行瘫想,不會阻塞等待消息的返回仗阅,當消息返回時系統(tǒng)再通知進程進行處理。
代碼輸出問題
function A(){
}
function B(a){
this.a = a;
}
function C(a){
if(a){
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);
輸出結(jié)果:1 undefined 2
解析:
- console.log(new A().a)国夜,new A()為構(gòu)造函數(shù)創(chuàng)建的對象减噪,本身沒有a屬性,所以向它的原型去找车吹,發(fā)現(xiàn)原型的a屬性的屬性值為1筹裕,故該輸出值為1;
- console.log(new B().a)窄驹,ew B()為構(gòu)造函數(shù)創(chuàng)建的對象朝卒,該構(gòu)造函數(shù)有參數(shù)a,但該對象沒有傳參乐埠,故該輸出值為undefined;
- console.log(new C(2).a)抗斤,new C()為構(gòu)造函數(shù)創(chuàng)建的對象,該構(gòu)造函數(shù)有參數(shù)a丈咐,且傳的實參為2瑞眼,執(zhí)行函數(shù)內(nèi)部,發(fā)現(xiàn)if為真棵逊,執(zhí)行this.a = 2,故屬性a的值為2伤疙。
對類數(shù)組對象的理解,如何轉(zhuǎn)化為數(shù)組
一個擁有 length 屬性和若干索引屬性的對象就可以被稱為類數(shù)組對象,類數(shù)組對象和數(shù)組類似徒像,但是不能調(diào)用數(shù)組的方法黍特。常見的類數(shù)組對象有 arguments 和 DOM 方法的返回結(jié)果,函數(shù)參數(shù)也可以被看作是類數(shù)組對象锯蛀,因為它含有 length屬性值灭衷,代表可接收的參數(shù)個數(shù)。
常見的類數(shù)組轉(zhuǎn)換為數(shù)組的方法有這樣幾種:
- 通過 call 調(diào)用數(shù)組的 slice 方法來實現(xiàn)轉(zhuǎn)換
Array.prototype.slice.call(arrayLike);
- 通過 call 調(diào)用數(shù)組的 splice 方法來實現(xiàn)轉(zhuǎn)換
Array.prototype.splice.call(arrayLike, 0);
- 通過 apply 調(diào)用數(shù)組的 concat 方法來實現(xiàn)轉(zhuǎn)換
Array.prototype.concat.apply([], arrayLike);
- 通過 Array.from 方法來實現(xiàn)轉(zhuǎn)換
Array.from(arrayLike);
代碼輸出結(jié)果
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
輸出結(jié)果如下:
"then: " "Error: error!!!"
返回任意一個非 promise 的值都會被包裹成 promise 對象谬墙,因此這里的return new Error('error!!!')
也被包裹成了return Promise.resolve(new Error('error!!!'))
今布,因此它會被then捕獲而不是catch。
原型鏈指向
p.__proto__ // Person.prototype
Person.prototype.__proto__ // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor // Person
代碼輸出問題
window.number = 2;
var obj = {
number: 3,
db1: (function(){
console.log(this);
this.number *= 4;
return function(){
console.log(this);
this.number *= 5;
}
})()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number); // 15
console.log(window.number); // 40
這道題目看清起來有點亂拭抬,但是實際上是考察this指向的:
- 執(zhí)行db1()時,this指向全局作用域侵蒙,所以window.number * 4 = 8造虎,然后執(zhí)行匿名函數(shù), 所以window.number * 5 = 40纷闺;
- 執(zhí)行obj.db1();時算凿,this指向obj對象,執(zhí)行匿名函數(shù)犁功,所以obj.numer * 5 = 15氓轰。
迭代查詢與遞歸查詢
實際上,DNS解析是一個包含迭代查詢和遞歸查詢的過程浸卦。
- 遞歸查詢指的是查詢請求發(fā)出后署鸡,域名服務(wù)器代為向下一級域名服務(wù)器發(fā)出請求,最后向用戶返回查詢的最終結(jié)果限嫌。使用遞歸 查詢靴庆,用戶只需要發(fā)出一次查詢請求。
- 迭代查詢指的是查詢請求后怒医,域名服務(wù)器返回單次查詢的結(jié)果炉抒。下一級的查詢由用戶自己請求。使用迭代查詢稚叹,用戶需要發(fā)出 多次的查詢請求焰薄。
一般我們向本地 DNS 服務(wù)器發(fā)送請求的方式就是遞歸查詢,因為我們只需要發(fā)出一次請求扒袖,然后本地 DNS 服務(wù)器返回給我 們最終的請求結(jié)果塞茅。而本地 DNS 服務(wù)器向其他域名服務(wù)器請求的過程是迭代查詢的過程,因為每一次域名服務(wù)器只返回單次 查詢的結(jié)果僚稿,下一級的查詢由本地 DNS 服務(wù)器自己進行凡桥。
寫代碼:實現(xiàn)函數(shù)能夠深度克隆基本類型
淺克隆:
function shallowClone(obj) {
let cloneObj = {};
for (let i in obj) {
cloneObj[i] = obj[i];
}
return cloneObj;
}
深克率赐:
- 考慮基礎(chǔ)類型
- 引用類型
- RegExp缅刽、Date啊掏、函數(shù) 不是 JSON 安全的
- 會丟失 constructor,所有的構(gòu)造函數(shù)都指向 Object
- 破解循環(huán)引用
function deepCopy(obj) {
if (typeof obj === 'object') {
var result = obj.constructor === Array ? [] : {};
for (var i in obj) {
result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
} else {
var result = obj;
}
return result;
}
Virtual Dom 的優(yōu)勢在哪里衰猛?
Virtual Dom 的優(yōu)勢」其實這道題目面試官更想聽到的答案不是上來就說「直接操作/頻繁操作 DOM 的性能差」迟蜜,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到今天啡省。所以面試官更想聽到 VDOM 想解決的問題以及為什么頻繁的 DOM 操作會性能差娜睛。
首先我們需要知道:
DOM 引擎、JS 引擎 相互獨立卦睹,但又工作在同一線程(主線程) JS 代碼調(diào)用 DOM API 必須 掛起 JS 引擎畦戒、轉(zhuǎn)換傳入?yún)?shù)數(shù)據(jù)、激活 DOM 引擎结序,DOM 重繪后再轉(zhuǎn)換可能有的返回值障斋,最后激活 JS 引擎并繼續(xù)執(zhí)行若有頻繁的 DOM API 調(diào)用,且瀏覽器廠商不做“批量處理”優(yōu)化徐鹤, 引擎間切換的單位代價將迅速積累若其中有強制重繪的 DOM API 調(diào)用垃环,重新計算布局、重新繪制圖像會引起更大的性能消耗返敬。
其次是 VDOM 和真實 DOM 的區(qū)別和優(yōu)化:
- 虛擬 DOM 不會立馬進行排版與重繪操作
- 虛擬 DOM 進行頻繁修改遂庄,然后一次性比較并修改真實 DOM 中需要改的部分,最后在真實 DOM 中進行排版與重繪劲赠,減少過多DOM節(jié)點排版與重繪損耗
- 虛擬 DOM 有效降低大面積真實 DOM 的重繪與排版涛目,因為最終與真實 DOM 比較差異,可以只渲染局部
JavaScript有哪些內(nèi)置對象
全局的對象( global objects )或稱標準內(nèi)置對象经磅,不要和 "全局對象(global object)" 混淆泌绣。這里說的全局的對象是說在
全局作用域里的對象。全局作用域中的其他對象可以由用戶的腳本創(chuàng)建或由宿主程序提供预厌。
標準內(nèi)置對象的分類:
(1)值屬性阿迈,這些全局屬性返回一個簡單值,這些值沒有自己的屬性和方法轧叽。例如 Infinity苗沧、NaN、undefined炭晒、null 字面量
(2)函數(shù)屬性待逞,全局函數(shù)可以直接調(diào)用,不需要在調(diào)用時指定所屬對象网严,執(zhí)行結(jié)束后會將結(jié)果直接返回給調(diào)用者识樱。例如 eval()、parseFloat()、parseInt() 等
(3)基本對象怜庸,基本對象是定義或使用其他對象的基礎(chǔ)当犯。基本對象包括一般對象割疾、函數(shù)對象和錯誤對象嚎卫。例如 Object、Function宏榕、Boolean拓诸、Symbol、Error 等
(4)數(shù)字和日期對象麻昼,用來表示數(shù)字奠支、日期和執(zhí)行數(shù)學(xué)計算的對象。例如 Number抚芦、Math胚宦、Date
(5)字符串,用來表示和操作字符串的對象燕垃。例如 String、RegExp
(6)可索引的集合對象井联,這些對象表示按照索引值來排序的數(shù)據(jù)集合卜壕,包括數(shù)組和類型數(shù)組,以及類數(shù)組結(jié)構(gòu)的對象烙常。例如 Array
(7)使用鍵的集合對象轴捎,這些集合對象在存儲數(shù)據(jù)時會使用到鍵,支持按照插入順序來迭代元素蚕脏。
例如 Map侦副、Set、WeakMap驼鞭、WeakSet
(8)矢量集合秦驯,SIMD 矢量集合中的數(shù)據(jù)會被組織為一個數(shù)據(jù)序列。
例如 SIMD 等
(9)結(jié)構(gòu)化數(shù)據(jù)挣棕,這些對象用來表示和操作結(jié)構(gòu)化的緩沖區(qū)數(shù)據(jù)译隘,或使用 JSON 編碼的數(shù)據(jù)。例如 JSON 等
(10)控制抽象對象
例如 Promise洛心、Generator 等
(11)反射固耘。例如 Reflect、Proxy
(12)國際化词身,為了支持多語言處理而加入 ECMAScript 的對象。例如 Intl、Intl.Collator 等
(13)WebAssembly
(14)其他审轮。例如 arguments
總結(jié): js 中的內(nèi)置對象主要指的是在程序執(zhí)行前存在全局作用域里的由 js 定義的一些全局值屬性、函數(shù)和用來實例化其他對象的構(gòu)造函數(shù)對象葫笼。一般經(jīng)常用到的如全局變量值 NaN、undefined嗤锉,全局函數(shù)如 parseInt()渔欢、parseFloat() 用來實例化對象的構(gòu)造函數(shù)如 Date、Object 等瘟忱,還有提供數(shù)學(xué)計算的單體內(nèi)置對象如 Math 對象奥额。
對AJAX的理解,實現(xiàn)一個AJAX請求
AJAX是 Asynchronous JavaScript and XML 的縮寫访诱,指的是通過 JavaScript 的 異步通信垫挨,從服務(wù)器獲取 XML 文檔從中提取數(shù)據(jù),再更新當前網(wǎng)頁的對應(yīng)部分触菜,而不用刷新整個網(wǎng)頁九榔。
創(chuàng)建AJAX請求的步驟:
- 創(chuàng)建一個 XMLHttpRequest 對象。
- 在這個對象上使用 open 方法創(chuàng)建一個 HTTP 請求涡相,open 方法所需要的參數(shù)是請求的方法哲泊、請求的地址、是否異步和用戶的認證信息催蝗。
- 在發(fā)起請求前切威,可以為這個對象添加一些信息和監(jiān)聽函數(shù)。比如說可以通過 setRequestHeader 方法來為請求添加頭信息丙号。還可以為這個對象添加一個狀態(tài)監(jiān)聽函數(shù)先朦。一個 XMLHttpRequest 對象一共有 5 個狀態(tài),當它的狀態(tài)變化時會觸發(fā)onreadystatechange 事件犬缨,可以通過設(shè)置監(jiān)聽函數(shù)喳魏,來處理請求成功后的結(jié)果。當對象的 readyState 變?yōu)?4 的時候怀薛,代表服務(wù)器返回的數(shù)據(jù)接收完成刺彩,這個時候可以通過判斷請求的狀態(tài),如果狀態(tài)是 2xx 或者 304 的話則代表返回正常乾戏。這個時候就可以通過 response 中的數(shù)據(jù)來對頁面進行更新了迂苛。
- 當對象的屬性和監(jiān)聽函數(shù)設(shè)置完成后,最后調(diào)用 sent 方法來向服務(wù)器發(fā)起請求鼓择,可以傳入?yún)?shù)作為發(fā)送的數(shù)據(jù)體三幻。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創(chuàng)建 Http 請求
xhr.open("GET", url, true);
// 設(shè)置狀態(tài)監(jiān)聽函數(shù)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當請求成功時
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 設(shè)置請求失敗時的監(jiān)聽函數(shù)
xhr.onerror = function() {
console.error(this.statusText);
};
// 設(shè)置請求頭信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 Http 請求
xhr.send(null);
使用Promise封裝AJAX:
// promise 封裝實現(xiàn):
function getJSON(url) {
// 創(chuàng)建一個 promise 對象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一個 http 請求
xhr.open("GET", url, true);
// 設(shè)置狀態(tài)的監(jiān)聽函數(shù)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當請求成功或失敗時,改變 promise 的狀態(tài)
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 設(shè)置錯誤監(jiān)聽函數(shù)
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 設(shè)置響應(yīng)的數(shù)據(jù)類型
xhr.responseType = "json";
// 設(shè)置請求頭信息
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 http 請求
xhr.send(null);
});
return promise;
}
說一說你用過的css布局
gird布局呐能,layout布局念搬,flex布局抑堡,雙飛翼,圣杯布局等
說一下HTTP 3.0
HTTP/3基于UDP協(xié)議實現(xiàn)了類似于TCP的多路復(fù)用數(shù)據(jù)流朗徊、傳輸可靠性等功能首妖,這套功能被稱為QUIC協(xié)議。
流量控制爷恳、傳輸可靠性功能:QUIC在UDP的基礎(chǔ)上增加了一層來保證數(shù)據(jù)傳輸可靠性有缆,它提供了數(shù)據(jù)包重傳、擁塞控制温亲、以及其他一些TCP中的特性棚壁。
集成TLS加密功能:目前QUIC使用TLS1.3,減少了握手所花費的RTT數(shù)栈虚。
多路復(fù)用:同一物理連接上可以有多個獨立的邏輯數(shù)據(jù)流袖外,實現(xiàn)了數(shù)據(jù)流的單獨傳輸,解決了TCP的隊頭阻塞問題魂务。
快速握手:由于基于UDP曼验,可以實現(xiàn)使用0 ~ 1個RTT來建立連接。