最近出去面了幾家試試水,也在整理一些面試題。我已經總結在gitbook/github裡了,主要作用就是總結和分享一下自己的心得體會,現在每天還在持續更新中,歡迎大家star,有問題請隨時提issue
1. 手寫new操作符
function newClass(obj, args) {
let newObj = {};
newObj.__proto__ = obj.prototype
obj.call(newObj, args);
return newObj
}
function a(text) {
this.text = text;
}
let b = newClass(a, 'test');
console.log(b) // {text: "test"}
複製程式碼
2. 手寫防抖/節流
// 防抖
function debounceHandle(fn) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(function () {
fn.call(this, arguments);
}, 300)
}
}
複製程式碼
// 節流
function throttle(fn, delay) {
var timer = null;
var lastTime = Date.now();
return function() {
var curTime = Date.now();
var interval = delay - (curTime - lastTime); // 計算間隔
var context = this;
var args = arguments;
clearTimeout(timer);
if (interval <= 0) {
fn.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(fn, interval);
}
}
}
複製程式碼
3. 手寫promise
首先明確三種狀態
- pending - 進行中
- fulfilled - 成功
- rejected - 失敗
function NewPromise(executor) {
let _this = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledFunc = [];//儲存成功回撥
this.onRejectedFunc = [];//儲存失敗回撥
executor(resolve, reject);
function resolve(value) {
if (_this.state === 'pending') {
_this.value = value;
//依次執行成功回撥
_this.onFulfilledFunc.forEach(fn => fn(value));
_this.state = 'fulfilled';
}
}
function reject(reason) {
if (_this.state === 'pending') {
_this.reason = reason;
//依次執行失敗回撥
_this.onRejectedFunc.forEach(fn => fn(reason));
_this.state = 'rejected';
}
}
}
NewPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (self.state === 'pending') {
if (typeof onFulfilled === 'function') {
return new NewPromise((resolve, reject) => {
self.onFulfilledFunc.push(() => {
let x = onFulfilled(self.value);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
});
})
}
if (typeof onRejected === 'function') {
return new NewPromise((resolve, reject) => {
self.onRejectedFunc.push(() => {
let x = onRejected(self.value);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
});
})
}
}
if (self.state === 'fulfilled') {
if (typeof onFulfilled === 'function') {
return new NewPromise((resolve, reject) => {
let x = onFulfilled(self.value);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
})
}
}
if (self.state === 'rejected') {
if (typeof onRejected === 'function') {
return new NewPromise((resolve, reject) => {
let x = onRejected(self.reason);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
})
}
}
};
let p = new NewPromise((resolve, reject) => {
console.log(1) // 輸出 1
resolve(2);
});
p.then(x => {
console.log(x); // 輸出 2
return 3
}).then(x => {
console.log(x); // 輸出3
return 4;
}).then(x => {
console.log(x) // 輸出4
console.log('輸出完畢')
});
複製程式碼
執行結果
4. 箭頭函式
ES6新增箭頭函式,總結起來有如下幾個注意點
this指向
let obj = {
name: 'ronaldo',
getName: function () {
return this.name;
},
getName2: () => {
return this.name;
}
}
console.log(obj.getName()) // 輸出ronaldo
console.log(obj.getName2()) // 輸出空,此時this等於window
複製程式碼
無法當建構函式
var Person = (name, age) => {
this.name = name;
this.age = age;
}
var p = new Person('messi', 18); // Uncaught TypeError: Person is not a constructor
複製程式碼
arguments引數無法獲取當前傳入的引數
let func1 = function () {
console.log(arguments);
}
let func2 = () => {
console.log(arguments);
}
func1(1, 2, 3); // Arguments(3) [1, 2, 3] 引數有1,2,3
func2(1, 2, 3); //Arguments() [] 無引數
複製程式碼
但是可以通過剩餘引數來獲取箭頭函式傳入引數
let func1 = function () {
console.log(arguments);
}
let func2 = (...args) => {
console.log(args);
}
func1(1, 2, 3); // Arguments(3) [1, 2, 3] 引數有1,2,3
func2(1, 2, 3); // [1,2,3] 注:純陣列,不再是Arguments物件
複製程式碼
5. 解構
個人理解,解構就是ES6新增對陣列和物件實現分離內部元素/屬性對快速操作
解構還是很好理解的,下面一段程式碼理解了就足夠了
let obj = { d: 'aaaa', e: { f: 'bbbb' }, g: 100 };
let { d, ...a } = obj;
console.log(d);
console.log(a);
a.e.f = 'cccc';
console.log(a);
console.log(obj);
複製程式碼
執行結果
- 用...的時候是解構出來的是剩下的所有屬性
- 解構是淺拷貝!!!!!
6. 手寫EventBus
function EventBusClass() {
this.msgQueues = {}
}
EventBusClass.prototype = {
// 將訊息儲存到當前的訊息佇列中
on: function (msgName, func) {
if (this.msgQueues.hasOwnProperty(msgName)) {
if (typeof this.msgQueues[msgName] === 'function') {
this.msgQueues[msgName] = [this.msgQueues[msgName], func]
} else {
this.msgQueues[msgName] = [...this.msgQueues[msgName], func]
}
} else {
this.msgQueues[msgName] = func;
}
},
// 訊息佇列中僅儲存一個訊息
one: function (msgName, func) {
// 無需檢查msgName是否存在
this.msgQueues[msgName] = func;
},
// 傳送訊息
emit: function (msgName, msg) {
if (!this.msgQueues.hasOwnProperty(msgName)) {
return
}
if (typeof this.msgQueues[msgName] === 'function') {
this.msgQueues[msgName](msg)
} else {
this.msgQueues[msgName].map((fn) => {
fn(msg)
})
}
},
// 移除訊息
off: function (msgName) {
if (!this.msgQueues.hasOwnProperty(msgName)) {
return
}
delete this.msgQueues[msgName]
}
}
// 將EventBus放到window物件中
const EventBus = new EventBusClass()
EventBus.on('first-event', function (msg) {
console.log(`訂閱的訊息是:${msg}`);
});
EventBus.emit('first-event', 123213)
// 輸出結果
// 訂閱的訊息是:123213
複製程式碼
7. 手寫LazyMan
實現一個LazyMan,可以按照以下方式呼叫:
LazyMan(“Hank”)輸出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)輸出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)輸出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)輸出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
以此類推。
複製程式碼
function _lazyman(name) {
this.tasks = [];
var that = this;
var fn = (function (name) {
return function () {
console.log("Hello I'm " + name);
that.next();
}
})(name);
this.tasks.push(fn);
setTimeout(function () { that.next() }, 0) // setTimeout延遲0ms也未必是立刻執行哦
}
_lazyman.prototype = {
constructor: _lazyman,
//next是實現函式在佇列中順序執行功能的函式
next: function () {
var fn = this.tasks.shift();
fn && fn();
},
sleep: function (time) {
var that = this;
var fn = (function (time) {
return function () {
console.log("sleep......." + time);
setTimeout(function () {
that.next();
}, time)
}
})(time);
this.tasks.push(fn);
return this; //return this是為了實現鏈式呼叫
},
sleepFirst: function (time) {
var that = this;
var fn = (function (time) {
return function () {
console.log("sleep......." + time);
setTimeout(function () {
that.next();
}, time)
}
})(time);
this.tasks.unshift(fn);
return this;
},
eat: function (something) {
var that = this;
var fn = (function (something) {
return function () {
console.log("Eat " + something);
that.next();
}
})(something)
this.tasks.push(fn);
return this;
}
}
function LazyMan(name) {
return new _lazyman(name);
}
LazyMan("Joe").sleepFirst(3000).eat("breakfast").sleep(1000).eat("dinner");
// LazyMan('Hank').sleepFirst(5).eat('supper')
// sleep.......3000
// Hello I'm Joe
// Eat breakfast
// sleep.......1000
// Eat dinner
複製程式碼
實現思路
- LazyMan()不是new出來的,需要在其內部封裝一下return new _lazyman,_lazyman等同於建構函式,這樣我執行一次LazyMan(),就會建立一個物件,是不是有點工廠模式的感覺
- 內部用tasks陣列儲存所有任務
- next()用於執行tasks陣列中第一任務,並將其從tasks陣列中刪除
- sleepFirst()方法,內部將建立的閉包函式,將建立的sleepFirst任務加入tasks陣列第一個
- eat,sleep,sleepFirst內部是用閉包執行,這樣就能保留傳入的引數,待後續tasks取出任務執行
- 重點:
_lazyman()
中的setTimeout(function () { that.next() }, 0)
最後執行時,tasks裡按執行順序存放所有任務,是不是很巧妙,並且每個任務都會執行that.next()
這個面試題綜合了原型,工廠模式,非同步佇列,閉包知識。含金量很高呦