# JavaScript筆試部分
點擊關注本[公眾號](http://www.hmoore.net/book/dsh225/javascript_vue_css/edit#_118)獲取文檔最新更新,并可以領取配套于本指南的《**前端面試手冊**》以及**最標準的簡歷模板**.
[TOC]
## 實現防抖函數(debounce)
防抖函數原理:在事件被觸發n秒后再執行回調,如果在這n秒內又被觸發,則重新計時。
那么與節流函數的區別直接看這個動畫實現即可。
https://codesandbox.io/s/static-ce05g?from-embed
手寫簡化版:
~~~
// 防抖函數
let?deBounce?=?(fn,?delay)?=>?{
let?timer?=?null;
return?function?(...args)?{
if?(timer)?{
clearTimeout(timer);
}
timer?=?setTimeout(()=>?{
fn(...args);
},?delay)
}
}
//手動實現防抖函數
function?debounce(fn,?wait,?immedite){
let timer;
let debounced?=?function(){
let context?=?this
if(timer){
clearTimeout(timer)
?}
if(immedite){?//?是否立即執行
let callNow?=?!timer
if(callNow){
fn.apply(context,arguments)
?}
timer?=?setTimeout(()?=>?{
timer?=?null//閉包引用了timer,手動置空使其能被垃圾回收機制回收
},?wait);
}else{
timer?=?setTimeout(()=>{
fn.apply(context,arguments)
},wait)
}
}
debounced.cancel?=?function(){?//取消立即執行
clearTimeout(timer)
timer?=?null
}
return debounced
}
~~~
適用場景:
* 按鈕提交場景:防止多次提交按鈕,只執行最后提交的一次
* 服務端驗證場景:表單驗證需要服務端配合,只執行一段連續的輸入事件的最后一次,還有搜索聯想詞功能類似
生存環境請用lodash.debounce
## 實現節流函數(throttle)
節流函數原理:規定在一個單位時間內,只能觸發一次函數。如果這個單位時間內觸發多次函數,只有一次生效。
手寫簡化版:
~~~
// 節流函數
//定時器方案
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
//節流函數
// 時間戳方案
function?throttle(fn,?wait){
let pre = 0;
return (...args) => {
let now = new Date();
if (now - pre > wait) {
fn(...args);
pre = now;
}
};
}
~~~
適用場景:
* 拖拽場景:固定時間內只執行一次,防止超高頻次觸發位置變動
* 縮放場景:監控瀏覽器resize
* 動畫場景:避免短時間內多次觸發動畫引起性能問題
*****
總結:
防抖:控制事件觸發的次數;節流:控制事件觸發的頻率
## 深克隆(deepclone)
簡單版:
~~~
const newObj = JSON.parse(JSON.stringify(oldObj));
~~~
局限性:
1. 他無法實現對函數 、RegExp等特殊對象的克隆
2. 會拋棄對象的constructor,所有的構造函數會指向Object
3. 對象有循環引用,會報錯
面試版:
~~~
/**
* deep clone
* @param {[type]} parent object 需要進行克隆的對象
* @return {[type]} 深克隆后的對象
*/
const clone = parent => {
// 判斷類型
const isType = (obj, type) => {
if (typeof obj !== "object") return false;
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case "Array":
flag = typeString === "[object Array]";
break;
case "Date":
flag = typeString === "[object Date]";
break;
case "RegExp":
flag = typeString === "[object RegExp]";
break;
default:
flag = false;
}
return flag;
};
// 處理正則
const getRegExp = re => {
var flags = "";
if (re.global) flags += "g";
if (re.ignoreCase) flags += "i";
if (re.multiline) flags += "m";
return flags;
};
// 維護兩個儲存循環引用的數組
const parents = [];
const children = [];
const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== "object") return parent;
let child, proto;
if (isType(parent, "Array")) {
// 對數組做特殊處理
child = [];
} else if (isType(parent, "RegExp")) {
// 對正則對象做特殊處理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, "Date")) {
// 對Date對象做特殊處理
child = new Date(parent.getTime());
} else {
// 處理對象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切斷原型鏈
child = Object.create(proto);
}
// 處理循環引用
const index = parents.indexOf(parent);
if (index != -1) {
// 如果父數組存在本對象,說明之前已經被引用過,直接返回此對象
return children[index];
}
parents.push(parent);
children.push(child);
for (let i in parent) {
// 遞歸
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
//深拷貝方法
function?deeClone(obj){
let newObj
if(typeof obj?===?'object'?&&?obj?!==?null){?//如果是引用類型
newObj?=?obj.constructor?===?Array??[]?:?{}
for(i in obj){?//遍歷對象屬性的key值
newObj[i]?=?type of obj[i]?===?'object'???deeClone(obj[i])?:?obj[i]
????}
}else?{?//如果是基本數據類型
newObj?=?obj
?}
return newObj
}
~~~
局限性:
1. 一些特殊情況沒有處理: 例如Buffer對象、Promise、Set、Map
2. 另外對于確保沒有循環引用的對象,我們可以省去對循環引用的特殊處理,因為這很消耗時間
> 原理詳解[實現深克隆](https://www.cxymsg.com/guide/jsWritten.html#deepclone)
## 實現Event(event bus)
event bus既是node中各個模塊的基石,又是前端組件通信的依賴手段之一,同時涉及了訂閱-發布設計模式,是非常重要的基礎。
簡單版:
~~~
class EventEmeitter {
constructor() {
this._events = this._events || new Map(); // 儲存事件/回調鍵值對
this._maxListeners = this._maxListeners || 10; // 設立監聽上限
}
}
// 觸發名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 從儲存事件鍵值對的this._events中獲取對應事件回調函數
handler = this._events.get(type);
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
return true;
};
// 監聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 將type事件以及對應的fn函數放入this._events中儲存
if (!this._events.get(type)) {
this._events.set(type, fn);
}
};
~~~
面試版:
~~~
class EventEmeitter {
constructor() {
this._events = this._events || new Map(); // 儲存事件/回調鍵值對
this._maxListeners = this._maxListeners || 10; // 設立監聽上限
}
}
// 觸發名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
handler = this._events.get(type);
if (Array.isArray(handler)) {
// 如果是一個數組說明有多個監聽者,需要依次此觸發里面的函數
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args);
} else {
handler[i].call(this);
}
}
} else {
// 單個函數的情況我們直接觸發即可
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
}
return true;
};
// 監聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
const handler = this._events.get(type); // 獲取對應事件名稱的函數清單
if (!handler) {
this._events.set(type, fn);
} else if (handler && typeof handler === "function") {
// 如果handler是函數說明只有一個監聽者
this._events.set(type, [handler, fn]); // 多個監聽者我們需要用數組儲存
} else {
handler.push(fn); // 已經有多個監聽者,那么直接往數組里push函數即可
}
};
EventEmeitter.prototype.removeListener = function(type, fn) {
const handler = this._events.get(type); // 獲取對應事件名稱的函數清單
// 如果是函數,說明只被監聽了一次
if (handler && typeof handler === "function") {
this._events.delete(type, fn);
} else {
let postion;
// 如果handler是數組,說明被監聽多次要找到對應的函數
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
postion = i;
} else {
postion = -1;
}
}
// 如果找到匹配的函數,從數組中清除
if (postion !== -1) {
// 找到數組對應的位置,直接清除此回調
handler.splice(postion, 1);
// 如果清除后只有一個函數,那么取消數組,以函數形式保存
if (handler.length === 1) {
this._events.set(type, handler[0]);
}
} else {
return this;
}
}
};
~~~
> 實現具體過程和思路見[實現event](https://www.cxymsg.com/guide/jsWritten.html#event)
## 實現instanceOf
~~~
// 模擬 instanceof
function instance_of(L, R) {
//L 表示左表達式,R 表示右表達式
var O = R.prototype; // 取 R 的顯示原型
L = L.__proto__; // 取 L 的隱式原型
while (true) {
if (L === null) return false;
if (O === L)
// 這里重點:當 O 嚴格等于 L 時,返回 true
return true;
L = L.__proto__;
}
}
~~~
## 模擬new
new操作符做了這些事:
* 創建一個空對象,且讓該對象繼承了Function.prototype;
* 執行我們的構造函數,改變this指向(指向剛剛創建的新對象);
* 拋出我們的新對象;
~~~
// objectFactory(name, 'cxk', '18')
function objectFactory() {
const obj = new Object();
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;
}
~~~
## 實現一個call
call做了什么:
* 將函數設為對象的屬性
* 執行&刪除這個函數
* 指定this到函數并傳入給定參數執行函數
* 如果不傳入參數,默認指向為 window
~~~
// 模擬 call bar.mycall(null);
//實現一個call方法:
Function.prototype.myCall = function(context) {
//此處沒有考慮context非object情況
context.fn = this;
let args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
context.fn(...args);
let result = context.fn(...args);
delete context.fn;
return result;
};
~~~
> 具體實現參考[JavaScript深入之call和apply的模擬實現](https://github.com/mqyqingfeng/Blog/issues/11)
## 實現apply方法
apply原理與call很相似,不多贅述
~~~
// 模擬 apply
Function.prototype.myapply = function(context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
~~~
## 實現bind
實現bind要做什么
* 返回一個函數,綁定this,傳遞預置參數
* bind返回的函數可以作為構造函數使用。故作為構造函數時應使得this失效,但是傳入的參數依然有效
~~~
// mdn的實現
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true時,說明返回的fBound被當做new的構造函數調用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 獲取調用時(fBound)的傳參.bind 返回的函數入參往往是這么傳遞的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 維護原型關系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的代碼使fBound.prototype是fNOP的實例,因此
// 返回的fBound若作為new的構造函數,new生成的新對象作為this傳入fBound,新對象的__proto__就是fNOP的實例
fBound.prototype = new fNOP();
return fBound;
};
}
~~~
> 詳解請移步[JavaScript深入之bind的模擬實現 #12](https://github.com/mqyqingfeng/Blog/issues/12)
## 模擬Object.create
Object.create()方法創建一個新對象,使用現有的對象來提供新創建的對象的\_\_proto\_\_。
~~~
// 模擬 Object.create
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
~~~
## JS繼承的實現方式
https://www.cnblogs.com/humin/p/4556820.html
https://www.jianshu.com/p/cf5543b77b36
## 實現類的繼承
類的繼承在幾年前是重點內容,有n種繼承方式各有優劣,es6普及后越來越不重要,那么多種寫法有點『回字有四樣寫法』的意思,如果還想深入理解的去看紅寶書即可,我們目前只實現一種最理想的繼承方式。
~~~
function Parent(name) {
this.parent = name
}
Parent.prototype.say = function() {
console.log(`${this.parent}: 你打籃球的樣子像kunkun`)
}
function Child(name, parent) {
// 將父類的構造函數綁定在子類上
Parent.call(this, parent)
this.child = name
}
/**
1. 這一步不用Child.prototype =Parent.prototype的原因是怕共享內存,修改父類原型對象就會影響子類
2. 不用Child.prototype = new Parent()的原因是會調用2次父類的構造方法(另一次是call),會存在一份多余的父類實例屬性
3. Object.create是創建了父類原型的副本,與父類原型完全隔離
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
console.log(`${this.parent}好,我是練習時長兩年半的${this.child}`);
}
// 注意記得把子類的構造指向子類本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打籃球的樣子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是練習時長兩年半的cxk
~~~
## 實現JSON.parse
~~~
var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");
~~~
此方法屬于黑魔法,極易容易被xss攻擊,還有一種`new Function`大同小異。
簡單的教程看這個[半小時實現一個 JSON 解析器](https://zhuanlan.zhihu.com/p/28049617)
## 實現Promise
> 我很早之前實現過一版,而且注釋很多,但是居然找不到了,這是在網絡上找了一版帶注釋的,目測沒有大問題,具體過程可以看這篇[史上最易讀懂的 Promise/A+ 完全實現](https://zhuanlan.zhihu.com/p/21834559)
> https://blog.csdn.net/weixin_43299180/article/details/122827147
簡化版:
```
let PromiseTest = function (resolve,reject) {
this.status = 'pending'
this.msg = '';
let self = this,process = arguments[0];
process(function () {
self.status = 'resolve'
self.msg = arguments[0]
},function() {
self.status = 'reject'
self.msg = arguments[0]
})
return this;
}
PromiseTest.prototype.then = function (resolve,reject) {
if(this.status === 'resolve') {
arguments[0](this.msg)
}
if(this.status === 'reject' && arguments[1]) {
arguments[1](this.msg)
}
}
let test = new PromiseTest(function(resolve,reject) {
resolve('124')
})
test.then(function(result) {
console.log(result)
console.log('ok')
},function(result) {
console.log(result)
console.log('no ok !')
})
```
復雜版:
~~~
var PromisePolyfill = (function () {
// 和reject不同的是resolve需要嘗試展開thenable對象
function tryToResolve (value) {
if (this === value) {
// 主要是防止下面這種情況
// let y = new Promise(res => setTimeout(res(y)))
throw TypeError('Chaining cycle detected for promise!')
}
// 根據規范2.32以及2.33 對對象或者函數嘗試展開
// 保證S6之前的 polyfill 也能和ES6的原生promise混用
if (value !== null &&
(typeof value === 'object' || typeof value === 'function')) {
try {
// 這里記錄這次then的值同時要被try包裹
// 主要原因是 then 可能是一個getter, 也也就是說
// 1. value.then可能報錯
// 2. value.then可能產生副作用(例如多次執行可能結果不同)
var then = value.then
// 另一方面, 由于無法保證 then 確實會像預期的那樣只調用一個onFullfilled / onRejected
// 所以增加了一個flag來防止resolveOrReject被多次調用
var thenAlreadyCalledOrThrow = false
if (typeof then === 'function') {
// 是thenable 那么嘗試展開
// 并且在該thenable狀態改變之前this對象的狀態不變
then.bind(value)(
// onFullfilled
function (value2) {
if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
tryToResolve.bind(this, value2)()
}.bind(this),
// onRejected
function (reason2) {
if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', reason2)()
}.bind(this)
)
} else {
// 擁有then 但是then不是一個函數 所以也不是thenable
resolveOrReject.bind(this, 'resolved', value)()
}
} catch (e) {
if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', e)()
}
} else {
// 基本類型 直接返回
resolveOrReject.bind(this, 'resolved', value)()
}
}
function resolveOrReject (status, data) {
if (this.status !== 'pending') return
this.status = status
this.data = data
if (status === 'resolved') {
for (var i = 0; i < this.resolveList.length; ++i) {
this.resolveList[i]()
}
} else {
for (i = 0; i < this.rejectList.length; ++i) {
this.rejectList[i]()
}
}
}
function Promise (executor) {
if (!(this instanceof Promise)) {
throw Error('Promise can not be called without new !')
}
if (typeof executor !== 'function') {
// 非標準 但與Chrome谷歌保持一致
throw TypeError('Promise resolver ' + executor + ' is not a function')
}
this.status = 'pending'
this.resolveList = []
this.rejectList = []
try {
executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
} catch (e) {
resolveOrReject.bind(this, 'rejected', e)()
}
}
Promise.prototype.then = function (onFullfilled, onRejected) {
// 返回值穿透以及錯誤穿透, 注意錯誤穿透用的是throw而不是return,否則的話
// 這個then返回的promise狀態將變成resolved即接下來的then中的onFullfilled
// 會被調用, 然而我們想要調用的是onRejected
if (typeof onFullfilled !== 'function') {
onFullfilled = function (data) {
return data
}
}
if (typeof onRejected !== 'function') {
onRejected = function (reason) {
throw reason
}
}
var executor = function (resolve, reject) {
setTimeout(function () {
try {
// 拿到對應的handle函數處理this.data
// 并以此為依據解析這個新的Promise
var value = this.status === 'resolved'
? onFullfilled(this.data)
: onRejected(this.data)
resolve(value)
} catch (e) {
reject(e)
}
}.bind(this))
}
// then 接受兩個函數返回一個新的Promise
// then 自身的執行永遠異步與onFullfilled/onRejected的執行
if (this.status !== 'pending') {
return new Promise(executor.bind(this))
} else {
// pending
return new Promise(function (resolve, reject) {
this.resolveList.push(executor.bind(this, resolve, reject))
this.rejectList.push(executor.bind(this, resolve, reject))
}.bind(this))
}
}
// for prmise A+ test
Promise.deferred = Promise.defer = function () {
var dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// for prmise A+ test
if (typeof module !== 'undefined') {
module.exports = Promise
}
return Promise
})()
PromisePolyfill.all = function (promises) {
return new Promise((resolve, reject) => {
const result = []
let cnt = 0
for (let i = 0; i < promises.length; ++i) {
promises[i].then(value => {
cnt++
result[i] = value
if (cnt === promises.length) resolve(result)
}, reject)
}
})
}
PromisePolyfill.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; ++i) {
promises[i].then(resolve, reject)
}
})
}
PromisePolyfill.any = (promises) => {
let arr = [];
let count = 0;
return new Promise((resolve, reject) => {
promises.forEach(item => {
Promise.resolve(item).then(res => resolve(res), err => {
count += 1;
if (count === promises.length) {
reject('AggregateError: All promises were rejected')
}
})
})
})
}
~~~
* * *
## 實現some、every、flat
```
// 實現some
Array.prototype.some = function(fn, value){
if (typeof fn !== "function") {
return false;
}
let arr = this;
for (var i = 0; i < arr.length; i++) {
var result = fn.call(value, arr[i], i, arr);
if (result) return true;
}
return false;
}
// 實現every
Array.prototype.every = function(fn, value){
if (typeof fn !== "function") {
return false;
}
let arr = this;
for (var i = 0; i < arr.length; i++) {
var result = fn.call(value, arr[i], i, arr);
if (!result) return false;
}
return true;
}
// 實現flat
Array.prototype.myFlat = function (dep = 1) {
return this.reduce((acc, val) => {
return acc.concat(Array.isArray(val) && dep > 0 ?
// 這里的三目就是防止這個現象:[3].concat([4]) // 結果為[3, 4]
val.myFlat(--dep) : Array.isArray(val) ? [val] : val);
}, [])
}
```
## 代碼中有很多console.log,如何做到不改動代碼的提前下,做到log內容的上報
### 重寫console.log函數
```
var lastLog;
console.oldLog?=?console.log;
console.log?=?function(str,fn)?{
console.oldLog(str);
lastLog?=?str;
if(fn)?{
fn();
????}
}
console.log("Hello,?Neo",function()?{
alert("1111")
});
document.write(lastLog);
```
## 實現buildTree
```
const flatTreeData = [
{ id: '1', parentId: '0' },
{ id: '1-1', parentId: '1' },
{ id: '1-1-1', parentId: '1-1' },
{ id: '1-2', parentId: '1' },
{ id: '1-3', parentId: '1' },
{ id: '2', parentId: '0' },
{ id: '2-1', parentId: '2' },
{ id: '2-2', parentId: '2' },
{ id: '2-3', parentId: '2' },
];
// 實現函數buildTree,輸出:
const buildTree = {
id: '0',
children: [
{
id: '1',
children: [{ id: '1-1', children: [{ id: '1-1-1' }] }, { id: '1-2' }, { id: '1-3' }],
},
{
id: '2',
children: [{ id: '2-1' }, { id: '2-2' }, { id: '2-3' }],
},
],
};
// 遞歸
function buildTreeFn(data, parentId) {
const tree = { id: parentId, children: [] };
data.forEach(item => {
if (item.parentId === parentId) {
const childNode = { id: item.id };
const grandchildren = buildTreeFn(data, item.id);
if (grandchildren.children.length > 0) {
childNode.children = grandchildren.children;
}
tree.children.push(childNode);
}
});
return tree;
}
const treeData = buildTreeFn(flatTreeData, '0');
console.log(treeData);
// 不使用遞歸
function buildTreeFn2(data = []) {
let _data = JSON.parse(JSON.stringify(data))
return _data.filter((p) => {
const _arr = _data.filter((c) => c.parentId == p.id)
_arr.length && (p.children = _arr)
return p.parentId == 0
})
}
const treeData = buildTreeFn2(flatTreeData);
console.log(treeData);
```
# 用fetch封裝最大并發請求函數
```
方案一: 遞歸
function handleFetchQueue(urls, max, callback) {
let urlsCopy = [... urls];//防止影響外部urls變量
function request() {
function Handle () {
count--;
console.log('end 當前并發數為: '+count);
if(urlsCopy.length) {//還有未請求的則遞歸
request();
} else if (count === 0) {//并發數為0則表示全部請求完成
callback()
}
}
count++;
console.log('start 當前并發數為: '+count);
//請求
fetch(urlsCopy.shift()).then(Handle).catch(Handle);
//并發數不足時遞歸
count < max && request();
}
let count = 0;//幾率并發數
request();
}
方案二: Promise
function sendResquest(urls, max, callback) {
let pending_count = 0, //并發數
idx = 0;//當前請求的位置
while (pending_count < max) {
_fetch(urls[idx++])
}
async function _fetch(url) {
if (!url) return;
pending_count++;
console.log(url + ':start','并發數: '+pending_count);
await fetch(url)
pending_count--;
console.log(url + ':done','并發數: '+pending_count);
_fetch(urls[idx++]);
pending_count || callback && callback()
}
}
```
## 二維和多維數組變成一維數組
方法一、apply結合concat拉平數組
```
let arr=[[1,2,3],[4,5],[6]];
console.log([].concat.apply([],arr));
//輸出 [1, 2, 3, 4, 5, 6]
```
方法二、ES6新增數組擴展 flat()
~~~
[1, 2, [3, 4]].flat()
~~~
flat()默認只會“拉平”一層,如果想要“拉平”多層的嵌套數組,可以將flat()方法的參數寫成一個整數,表示想要拉平的層數,默認為1。
如果我們不知道數組究竟層級有多深我們可以用Infinity關鍵字作為參數
~~~
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
~~~
方法三、遞歸實現
```
function doWhile(array) {
let newArray = [];
for (var i = 0; i < array.length - 1; i++) {
if (array[i] instanceof Array) {//判斷是不是數組
newArray = newArray.concat(doWhile(array[i]))
} else {
newArray.push(array[i])
}
}
return newArray;
}
```
## 面試題總結
```
1.
var obj = {
name: 'baidu',
arr: ['a', 'b', 'c']
}
var obj2 = obj
var arr = obj.arr
obj2.arr = ['a', 'b', 'c', 'd']
obj2.name = 'inke'
console.log(obj.arr) //['a', 'b', 'c', 'd']
console.log(arr)// ['a', 'b', 'c']
console.log(obj.name) // inke
console.log(obj === obj2) // true
console.log(obj.arr === obj2.arr) // true
console.log(obj.arr === arr)//false
2.
var MAP = {
onclick: function () {
},
curry: function (val) {
return function (z) {
return val++ + z
}
}
}
var getInfo = function (val) {
return MAP[val]
}
var fn = getInfo('curry')
var a = fn(100)
console.log(a(200)) // 300
console.log(a(300)) // 401
console.log(fn(100)(200)) // 300
console.log(getInfo('curry')(100)(300)) //400
3.
var name = 'oop'
var Person = function (options) {
this.name = options.name
}
Person.prototype.name = 'iron man'
Person.prototype.getName = function () {
return this.name
}
Person.getName = ()=> {
return this.name;
}
var p = new Person({ name: 'inke' })
console.log(p.constructor === Person) // true
console.log(p instanceof Person) // true
console.log(p.__proto__ === Person.prototype) // true
console.log(p.hasOwnProperty('name')) // true
console.log(p.hasOwnProperty('getName')) // false
var getName = p.getName
console.log(getName === Person.prototype.getName) // true?
console.log(getName()) // oop?
console.log(Person.prototype.getName()) // iron man?
console.log(p.getName()) // inke?
console.log(Person.getName()) // oop?
```
## 公眾號
想要實時關注筆者最新的文章和最新的文檔更新請關注公眾號**程序員面試官**,后續的文章會優先在公眾號更新.
**簡歷模板**:關注公眾號回復「模板」獲取
《**前端面試手冊**》:配套于本指南的突擊手冊,關注公眾號回復「fed」獲取

- 前言
- 指南使用手冊
- 為什么會有這個項目
- 面試技巧
- 面試官到底想看什么樣的簡歷?
- 面試回答問題的技巧
- 如何通過HR面
- 推薦
- 書籍/課程推薦
- 前端基礎
- HTML基礎
- CSS基礎
- JavaScript基礎
- 瀏覽器與新技術
- DOM
- 前端基礎筆試
- HTTP筆試部分
- JavaScript筆試部分
- 前端原理詳解
- JavaScript的『預解釋』與『變量提升』
- Event Loop詳解
- 實現不可變數據
- JavaScript內存管理
- 實現深克隆
- 如何實現一個Event
- JavaScript的運行機制
- 計算機基礎
- HTTP協議
- TCP面試題
- 進程與線程
- 數據結構與算法
- 算法面試題
- 字符串類面試題
- 前端框架
- 關于前端框架的面試須知
- Vue面試題
- React面試題
- 框架原理詳解
- 虛擬DOM原理
- Proxy比defineproperty優劣對比?
- setState到底是異步的還是同步的?
- 前端路由的實現
- redux原理全解
- React Fiber 架構解析
- React組件復用指南
- React-hooks 抽象組件
- 框架實戰技巧
- 如何搭建一個組件庫的開發環境
- 組件設計原則
- 實現輪播圖組件
- 性能優化
- 前端性能優化-加載篇
- 前端性能優化-執行篇
- 工程化
- webpack面試題
- 前端工程化
- Vite
- 安全
- 前端安全面試題
- npm
- 工程化原理
- 如何寫一個babel
- Webpack HMR 原理解析
- webpack插件編寫
- webpack 插件化設計
- Webpack 模塊機制
- webpack loader實現
- 如何開發Babel插件
- git
- 比較
- 查看遠程倉庫地址
- git flow
- 比較分支的不同并保存壓縮文件
- Tag
- 回退
- 前端項目經驗
- 確定用戶是否在當前頁面
- 前端下載文件
- 只能在微信中訪問
- 打開新頁面-被瀏覽器攔截
- textarea高度隨內容變化 vue版
- 去掉ios原始播放大按鈕
- nginx在MAC上的安裝、啟動、重啟和關閉
- 解析latex格式的數學公式
- 正則-格式化a鏈接
- 封裝的JQ插件庫
- 打包問題總結
- NPM UI插件
- 帶你入門前端工程
- webWorker+indexedDB性能優化
- 多個相鄰元素切換效果出現邊框重疊問題的解決方法
- 監聽前端storage變化