手寫其實在前端的面試過程當中必不可少,由於手寫是考驗你瞭解某一原理的最可觀的體現。下面我彙總了一些,我在面試複習過程當中遇到的手寫題。我將實現思路寫出來與你們共享,而實現只是一個參考,有興趣的能夠點擊參考答案,有問題請指正。前端
首先咱們須要瞭解new操做符幹了哪些事情面試
function myNew(fn) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj);
return res instanceof Object ? res : obj;
}
複製代碼
首先咱們須要知道instanceof是經過原型鏈來進行判斷的ajax
instanceof操做符是判斷原型鏈來生效的,因此只要你將左邊的_proto_和右邊的prototype作對比
複製代碼
function myInstance(left, right) {
// 當left是基礎類型的時候直接返回false
if(typeof left !== 'object' || left === null) return false;
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto === null) return false;
if(proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
複製代碼
實現apply主要是要注意如下幾個方面:數組
一、參數不能取第一個,由於第一個是上下文。promise
二、賦值到對象上的方法須要進行刪除,不能影響原對象。bash
三、對於上下文爲undefined的狀況須要把上下文指向window對象app
Function.prototype.myApply = function(context, [...args]) {
// 先判斷context是否爲空,若是爲空則指向window對象
context = context || window;
context.fn = this;
context.fn(...args);
delete context.fn;
}
複製代碼
call的實現和apply的實現相似,可是參數處理層面會略有不一樣。異步
Function.prototype.myCall = function(context) {
// 先判斷context是否爲空,若是爲空則指向window對象
context = context || window;
let args = [...arguments].slice(1);
context.fn = this;
context.fn(args);
delete context.fn;
}
複製代碼
bind的實現須要注意的是函數柯里化的狀況。async
Function.prototype.myBind = function(context) {
const self = this;
let args = [...arguments].slice(1);
return function() {
// 考慮函數柯里化的狀況
let newArgs = [...arguments];
this.apply(context, newArgs.concat(args))
}
}
複製代碼
promise的實現有必要着重看一下,我本身劃分幾個步驟來看:函數
一、先處理同步的邏輯,好比狀態值變化以後,將不在發生改變,以及有哪些屬性值等。
二、再處理異步邏輯,用回調的形式,以及存放的列表。
三、再處理鏈式調用的邏輯,鏈式調用比較複雜,多注意thenable的對象。
四、在處理promise的其餘方法。
最後我在這裏給你們推薦一篇文章,寫的很不錯!!promise文章
class Promise(exector) {
constructor() {
this.value = undefined;
this.reason = '';
this.state = 'pending';
this.onResolveList = [];
this.onRejectList = [];
const resolve = (value) => {
if(this.state === 'fulfilled') {
this.value = value
this.state = 'fulfilled';
this.onResolveList.forEach((fn) => {
fn();
})
}
};
const reject = (reason) => {
if(this.state === 'rejected') {
this.reason = reason
this.state = 'rejected';
this.onRejectList.forEach((fn) => {
fn();
})
}
}
try {
exector(resolve, reject);
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
const promise2 = new Promise((reslove, reject) => {
if(this.state === 'fulfilled') {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, reslove, reject);
}
if(this.state === 'rejected') {
onRejected(this.reason);
resolvePromise(promise2, x, reslove, reject);
}
if(this.state === 'pending') {
onResolveList.push(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, reslove, reject);
});
onRejectList.push(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, reslove, reject);
});
}
});
return promise2;
}
race(promises) {
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
})
}
all(promises) {
let arr = [];
let i = 0;
function processData(index, data) {
arr[index] = data;
i++;
if(i === promises.length) {
resolve(data);
}
}
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
promises[i].then((val) => {
processData(i, val);
}, reject)
}
})
}
resolve(val) {
return new Promise((resovle, reject) => {
resovle(val);
})
}
reject(val) {
return new Promise((resovle, reject) => {
reject(val);
})
}
}
// 這邊要處理的狀況有如下幾種:一、循環引用, 二、thenable對象,三、promise對象
resolvePromise(promise2, x, reslove, reject) {
if(promise2 === x) {
reject('循環引用');
return;
}
// 防止屢次調用
let called;
// 檢測x的值的類型,若是不是對象或者函數,直接返回resolve
if(x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 規範中then邏輯報錯也會進入catch
try {
if(called) return;
let then = x.then;
if(typeof then === 'function') {
then.call(x, (y) => {
if(called) return;
called = true;
resolvePromise(promise2, y ,reslove, reject)
}, (err) => {
if(called) return;
reject(err);
called = true;
})
} else {
resolve(x);
}
} catch(err) {
if(called) return;
reject(err);
called = true;
}
} else {
resolve(x);
}
}
複製代碼
寄生組合繼承其實須要注意的是子構造函數constructor的指向問題。以及繼承的弊病:超集會調用兩次。
function Super() {}
function Sub() {
Super.call(this)
}
Sub.prototype = new Super();
Sub.constructor = Sub;
複製代碼
對於防抖的理解,最好結合業務場景記憶:防抖通常用於輸入框場景。因此在實現層面會有如下兩個方面:
一、當必定時間內事件再次觸發時,定時器應該重置。
二、執行完畢後定時器重置。
function debounce(cb, delay, ...args) {
let time = null;
return function() {
if(time) {
clearTimeout(time);
}
time = setTimeout(() => {
cb.apply(this, ...args);
clearTimeout(time);
}, delay);
}
}
複製代碼
對於節流函數,也須要結合場景來記憶。通常用於滾動事件中,必定時間內只會觸發一次。實現層面也會有兩個須要注意的點:
一、用一個鎖變量來保證必定時間內只會觸發一次。
二、執行完畢以後,解開鎖便可
function tr(fn, time, ...args) {
let lock = false;
return function() {
if(lock) return;
lock = true;
setTimeout(() => {
fn.apply(this, ...args);
lock = false;
}, time)
}
}
複製代碼
這個其實主要考的是正則和replace方法。
function camelize(str) {
return (str + '').replace(/-\D/g, function(match) {
return match.charAt(1).toUpperCase()
})
}
複製代碼
function hyphenate(str) {
return (str + '').replace(/[A-Z]/g, function(match) {
return '-' + match.toLowerCase();
})
}
複製代碼
sleep函數實現的途徑有不少,promise,async/await等等。我在這裏就將一些最普通的。
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, time)
})
}
複製代碼
實現柯里化其實就是把多個參數長度很分開來調用的意思,好處在於能夠觀測你參數調用的一箇中間的過程,或者中間的變量。面試中常考的add(1, 2, 3)和add(1)(2)(3)就是這個問題
function curry(fn) {
const finalLen = fn.length
let args = [].slice.call(this,1)
return function currying () {
args = args.concat(Array.from(arguments))
const len = args.length
return len >= fn.length ? fn.apply(this, args) : currying
}
}
複製代碼
實現一個ajax其實主要是一個XMLHttpRequest對象以及其API方法的一個使用的問題。而在這裏我建議儘可能封裝成promise的形式,方便使用。
function ajax({url, methods, body, headers}) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open(url, methods);
for(let key in headers) {
let value = headers[key]
request.setRequestHeader(key, value);
}
request.onreadystatechange = () => {
if(request.readyState === 4) {
if(request.status >= '200' && request.status < 300) {
resolve(request.responeText);
} else {
reject(request)
}
}
}
request.send(body)
})
}
複製代碼
深拷貝也是面試中的一個高頻考點,通常的方法,JSON的序列化和反序列化,可是這種方法的弊病有兩個:
一、undefined、null和symbol類型的值會被刪除
二、遇見循環引用的時候會報錯。
那麼咱們在實現深拷貝的時候,也要時刻關注循環引用這個問題。
下列方法中,我主要是經過數組的形式去解決循環引用的問題。那爲何要有兩個數組呢?
主要是一個數組維護的是原來對象的引用,一個數組維護的是新對象的引用。
function deepClone(obj) {
const parents = [];
const children = [];
function helper(obj) {
if(obj === null) return null;
if(typeof obj !== 'object') return obj;
let child, proto;
if(Array.isArray(obj)) {
child = [];
} else {
proto = Object.getPrototypeOf(obj);
child = Object.create(proto);
}
// 處理循環引用
let index = parents.indexOf(obj)
if(index === -1) {
parents.push(obj);
children.push(child)
} else {
return children[index];
}
// for in迭代
for(let i in obj) {
child[i] = helper(obj[i]);
}
}
}
複製代碼