前端面試JS高頻手寫(xiě)大全
來(lái)自:SegmentFault ,作者:晚起的蟲(chóng)兒 鏈接:https://segmentfault.com/a/1190000038910420
點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)
回復(fù)加群,加入前端Q技術(shù)交流群
本文涵蓋了前端面試常考的各種重點(diǎn)手寫(xiě)。
建議優(yōu)先掌握:
instanceof (考察對(duì)原型鏈的理解) new (對(duì)創(chuàng)建對(duì)象實(shí)例過(guò)程的理解) call&apply&bind (對(duì)this指向的理解) 手寫(xiě)promise (對(duì)異步的理解) 手寫(xiě)原生ajax (對(duì)http請(qǐng)求方式的理解,重點(diǎn)是get和post請(qǐng)求)
1、手寫(xiě)instanceof
let myInstanceof = (target,origin) => {
while(target) {
if(target.__proto__===origin.prototype) {
return true
}
target = target.__proto__
}
return false
}
let a = [1,2,3]
console.log(myInstanceof(a,Array)); // true
console.log(myInstanceof(a,Object)); // true
2. 實(shí)現(xiàn)數(shù)組的map方法
Array.prototype.myMap = function(fn, thisValue) {
let res = []
thisValue = thisValue||[]
let arr = this
for(let i in arr) {
res.push(fn(arr[i]))
}
return res
}
3. reduce實(shí)現(xiàn)數(shù)組的map方法
Array.prototype.myMap = function(fn,thisValue){
var res = [];
thisValue = thisValue||[];
this.reduce(function(pre,cur,index,arr){
return res.push(fn.call(thisValue,cur,index,arr));
},[]);
return res;
}
var arr = [2,3,1,5];
arr.myMap(function(item,index,arr){
console.log(item,index,arr);
})
4. 手寫(xiě)數(shù)組的reduce方法
callback(一個(gè)在數(shù)組中每一項(xiàng)上調(diào)用的函數(shù),接受四個(gè)函數(shù):) previousValue(上一次調(diào)用回調(diào)函數(shù)時(shí)的返回值,或者初始值) currentValue(當(dāng)前正在處理的數(shù)組元素) currentIndex(當(dāng)前正在處理的數(shù)組元素下標(biāo)) array(調(diào)用reduce()方法的數(shù)組) initialValue(可選的初始值。作為第一次調(diào)用回調(diào)函數(shù)時(shí)傳給previousValue的值)
function reduce(arr, cb, initialValue){
var num = initValue == undefined? num = arr[0]: initValue;
var i = initValue == undefined? 1: 0
for (i; i< arr.length; i++){
num = cb(num,arr[i],i)
}
return num
}
function fn(result, currentValue, index){
return result + currentValue
}
var arr = [2,3,4,5]
var b = reduce(arr, fn,10)
var c = reduce(arr, fn)
console.log(b) // 24
5. 數(shù)組扁平化
let a = [1,[2,3]];
a.flat(); // [1,2,3]
a.flat(1); //[1,2,3]
let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4維數(shù)組
var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
function flatten(arr) {
var res = [];
for (let i = 0, length = arr.length; i < length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])); //concat 并不會(huì)改變?cè)瓟?shù)組
//res.push(...flatten(arr[i])); //擴(kuò)展運(yùn)算符
} else {
res.push(arr[i]);
}
}
return res;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
6. 函數(shù)柯里化
通過(guò)函數(shù)的 length 屬性,獲取函數(shù)的形參個(gè)數(shù),形參的個(gè)數(shù)就是所需的參數(shù)個(gè)數(shù) 在調(diào)用柯里化工具函數(shù)時(shí),手動(dòng)指定所需的參數(shù)個(gè)數(shù)
/**
* 將函數(shù)柯里化
* @param fn 待柯里化的原函數(shù)
* @param len 所需的參數(shù)個(gè)數(shù),默認(rèn)為原函數(shù)的形參個(gè)數(shù)
*/
function curry(fn,len = fn.length) {
return _curry.call(this,fn,len)
}
/**
* 中轉(zhuǎn)函數(shù)
* @param fn 待柯里化的原函數(shù)
* @param len 所需的參數(shù)個(gè)數(shù)
* @param args 已接收的參數(shù)列表
*/
function _curry(fn,len,...args) {
return function (...params) {
let _args = [...args,...params];
if(_args.length >= len){
return fn.apply(this,_args);
}else{
return _curry.call(this,fn,len,..._args)
}
}
}
let _fn = curry(function(a,b,c,d,e){
console.log(a,b,c,d,e)
});
_fn(1,2,3,4,5); // print: 1,2,3,4,5
_fn(1)(2)(3,4,5); // print: 1,2,3,4,5
_fn(1,2)(3,4)(5); // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

/**
* @param fn 待柯里化的函數(shù)
* @param length 需要的參數(shù)個(gè)數(shù),默認(rèn)為函數(shù)的形參個(gè)數(shù)
* @param holder 占位符,默認(rèn)當(dāng)前柯里化函數(shù)
* @return {Function} 柯里化后的函數(shù)
*/
function curry(fn,length = fn.length,holder = curry){
return _curry.call(this,fn,length,holder,[],[])
}
/**
* 中轉(zhuǎn)函數(shù)
* @param fn 柯里化的原函數(shù)
* @param length 原函數(shù)需要的參數(shù)個(gè)數(shù)
* @param holder 接收的占位符
* @param args 已接收的參數(shù)列表
* @param holders 已接收的占位符位置列表
* @return {Function} 繼續(xù)柯里化的函數(shù) 或 最終結(jié)果
*/
function _curry(fn,length,holder,args,holders){
return function(..._args){
//將參數(shù)復(fù)制一份,避免多次操作同一函數(shù)導(dǎo)致參數(shù)混亂
let params = args.slice();
//將占位符位置列表復(fù)制一份,新增加的占位符增加至此
let _holders = holders.slice();
//循環(huán)入?yún)ⅲ芳訁?shù) 或 替換占位符
_args.forEach((arg,i)=>{
//真實(shí)參數(shù) 之前存在占位符 將占位符替換為真實(shí)參數(shù)
if (arg !== holder && holders.length) {
let index = holders.shift();
_holders.splice(_holders.indexOf(index),1);
params[index] = arg;
}
//真實(shí)參數(shù) 之前不存在占位符 將參數(shù)追加到參數(shù)列表中
else if(arg !== holder && !holders.length){
params.push(arg);
}
//傳入的是占位符,之前不存在占位符 記錄占位符的位置
else if(arg === holder && !holders.length){
params.push(arg);
_holders.push(params.length - 1);
}
//傳入的是占位符,之前存在占位符 刪除原占位符位置
else if(arg === holder && holders.length){
holders.shift();
}
});
// params 中前 length 條記錄中不包含占位符,執(zhí)行函數(shù)
if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
return fn.apply(this,params);
}else{
return _curry.call(this,fn,length,holder,params,_holders)
}
}
}
let fn = function(a, b, c, d, e) {
console.log([a, b, c, d, e]);
}
let _ = {}; // 定義占位符
let _fn = curry(fn,5,_); // 將函數(shù)柯里化,指定所需的參數(shù)個(gè)數(shù),指定所需的占位符
_fn(1, 2, 3, 4, 5); // print: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1); // print: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2); // print: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5); // print: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5); // print: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5); // print: 1,2,3,4,5
7. 實(shí)現(xiàn)深拷貝
let obj={
id:1,
name:'Tom',
msg:{
age:18
}
}
let o={}
//實(shí)現(xiàn)深拷貝 遞歸 可以用于生命游戲那個(gè)題對(duì)二維數(shù)組的拷貝,
//但比較麻煩,因?yàn)橐阎囟际侵担苯訌?fù)制就行,無(wú)需判斷
function deepCopy(newObj,oldObj){
for(var k in oldObj){
let item=oldObj[k]
//判斷是數(shù)組?對(duì)象?簡(jiǎn)單類(lèi)型?
if(item instanceof Array){
newObj[k]=[]
deepCopy(newObj[k],item)
}else if(item instanceof Object){
newObj[k]={}
deepCopy(newObj[k],item)
}else{ //簡(jiǎn)單數(shù)據(jù)類(lèi)型,直接賦值
newObj[k]=item
}
}
}
8. 手寫(xiě)call, apply, bind
Function.prototype.myCall=function(context=window){ // 函數(shù)的方法,所以寫(xiě)在Fuction原型對(duì)象上
if(typeof this !=="function"){ // 這里if其實(shí)沒(méi)必要,會(huì)自動(dòng)拋出錯(cuò)誤
throw new Error("不是函數(shù)")
}
const obj=context||window //這里可用ES6方法,為參數(shù)添加默認(rèn)值,js嚴(yán)格模式全局作用域this為undefined
obj.fn=this //this為調(diào)用的上下文,this此處為函數(shù),將這個(gè)函數(shù)作為obj的方法
const arg=[...arguments].slice(1) //第一個(gè)為obj所以刪除,偽數(shù)組轉(zhuǎn)為數(shù)組
res=obj.fn(...arg)
delete obj.fn // 不刪除會(huì)導(dǎo)致context屬性越來(lái)越多
return res
}
//用法:f.call(obj,arg1)
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.myCall(obj,1,2) //否則this指向window
obj.greet.call({name: 'Spike'}) //打出來(lái)的是 Spike
Function.prototype.myApply=function(context){ // 箭頭函數(shù)從不具有參數(shù)對(duì)象!!!!!這里不能寫(xiě)成箭頭函數(shù)
let obj=context||window
obj.fn=this
const arg=arguments[1]||[] //若有參數(shù),得到的是數(shù)組
let res=obj.fn(...arg)
delete obj.fn
return res
}
function f(a,b){
console.log(a,b)
console.log(this.name)
}
let obj={
name:'張三'
}
f.myApply(obj,[1,2]) //arguments[1]
this.value = 2
var foo = {
value: 1
};
var bar = function(name, age, school){
console.log(name) // 'An'
console.log(age) // 22
console.log(school) // '家里蹲大學(xué)'
}
var result = bar.bind(foo, 'An') //預(yù)置了部分參數(shù)'An'
result(22, '家里蹲大學(xué)') //這個(gè)參數(shù)會(huì)和預(yù)置的參數(shù)合并到一起放入bar中
Function.prototype.bind = function(context, ...outerArgs) {
var fn = this;
return function(...innerArgs) { //返回了一個(gè)函數(shù),...rest為實(shí)際調(diào)用時(shí)傳入的參數(shù)
return fn.apply(context,[...outerArgs, ...innerArgs]); //返回改變了this的函數(shù),
//參數(shù)合并
}
}
// 聲明一個(gè)上下文
let thovino = {
name: 'thovino'
}
// 聲明一個(gè)構(gòu)造函數(shù)
let eat = function (food) {
this.food = food
console.log(`${this.name} eat ${this.food}`)
}
eat.prototype.sayFuncName = function () {
console.log('func name : eat')
}
// bind一下
let thovinoEat = eat.bind(thovino)
let instance = new thovinoEat('orange') //實(shí)際上orange放到了thovino里面
console.log('instance:', instance) // {}
function thovinoEat (...innerArgs) {
eat.call(thovino, ...outerArgs, ...innerArgs)
}
Function.prototype.bind = function (context, ...outerArgs) {
let that = this;
function res (...innerArgs) {
if (this instanceof res) {
// new操作符執(zhí)行時(shí)
// 這里的this在new操作符第三步操作時(shí),會(huì)指向new自身創(chuàng)建的那個(gè)簡(jiǎn)單空對(duì)象{}
that.call(this, ...outerArgs, ...innerArgs)
} else {
// 普通bind
that.call(context, ...outerArgs, ...innerArgs)
}
}
res.prototype = this.prototype //!!!
return res
}
9. 手動(dòng)實(shí)現(xiàn)new
創(chuàng)建一個(gè)空對(duì)象 obj; 將空對(duì)象的隱式原型(proto)指向構(gòu)造函數(shù)的prototype。 使用 call 改變 this 的指向 如果無(wú)返回值或者返回一個(gè)非對(duì)象值,則將 obj 返回作為新對(duì)象;如果返回值是一個(gè)新對(duì)象的話(huà)那么直接直接返回該對(duì)象。
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.sayHi=function(){
console.log('Hi!我是'+this.name)
}
let p1=new Person('張三',18)
////手動(dòng)實(shí)現(xiàn)new
function create(){
let obj={}
//獲取構(gòu)造函數(shù)
let fn=[].shift.call(arguments) //將arguments對(duì)象提出來(lái)轉(zhuǎn)化為數(shù)組,arguments并不是數(shù)組而是對(duì)象 !!!這種方法刪除了arguments數(shù)組的第一個(gè)元素,!!這里的空數(shù)組里面填不填元素都沒(méi)關(guān)系,不影響arguments的結(jié)果 或者let arg = [].slice.call(arguments,1)
obj.__proto__=fn.prototype
let res=fn.apply(obj,arguments) //改變this指向,為實(shí)例添加方法和屬性
//確保返回的是一個(gè)對(duì)象(萬(wàn)一fn不是構(gòu)造函數(shù))
return typeof res==='object'?res:obj
}
let p2=create(Person,'李四',19)
p2.sayHi()
[].shift.call(arguments) 也可寫(xiě)成:
let arg=[...arguments]
let fn=arg.shift() //使得arguments能調(diào)用數(shù)組方法,第一個(gè)參數(shù)為構(gòu)造函數(shù)
obj.__proto__=fn.prototype
//改變this指向,為實(shí)例添加方法和屬性
let res=fn.apply(obj,arg)
10. 手寫(xiě)promise(常見(jiàn)promise.all, promise.race)
// Promise/A+ 規(guī)范規(guī)定的三種狀態(tài)
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
class MyPromise {
// 構(gòu)造函數(shù)接收一個(gè)執(zhí)行回調(diào)
constructor(executor) {
this._status = STATUS.PENDING // Promise初始狀態(tài)
this._value = undefined // then回調(diào)的值
this._resolveQueue = [] // resolve時(shí)觸發(fā)的成功隊(duì)列
this._rejectQueue = [] // reject時(shí)觸發(fā)的失敗隊(duì)列
// 使用箭頭函數(shù)固定this(resolve函數(shù)在executor中觸發(fā),不然找不到this)
const resolve = value => {
const run = () => {
// Promise/A+ 規(guī)范規(guī)定的Promise狀態(tài)只能從pending觸發(fā),變成fulfilled
if (this._status === STATUS.PENDING) {
this._status = STATUS.FULFILLED // 更改狀態(tài)
this._value = value // 儲(chǔ)存當(dāng)前值,用于then回調(diào)
// 執(zhí)行resolve回調(diào)
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(value)
}
}
}
//把resolve執(zhí)行回調(diào)的操作封裝成一個(gè)函數(shù),放進(jìn)setTimeout里,以實(shí)現(xiàn)promise異步調(diào)用的特性(規(guī)范上是微任務(wù),這里是宏任務(wù))
setTimeout(run)
}
// 同 resolve
const reject = value => {
const run = () => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._value = value
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(value)
}
}
}
setTimeout(run)
}
// new Promise()時(shí)立即執(zhí)行executor,并傳入resolve和reject
executor(resolve, reject)
}
// then方法,接收一個(gè)成功的回調(diào)和一個(gè)失敗的回調(diào)
function then(onFulfilled, onRejected) {
// 根據(jù)規(guī)范,如果then的參數(shù)不是function,則忽略它, 讓值繼續(xù)往下傳遞,鏈?zhǔn)秸{(diào)用繼續(xù)往下執(zhí)行
typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
typeof onRejected !== 'function' ? onRejected = error => error : null
// then 返回一個(gè)新的promise
return new MyPromise((resolve, reject) => {
const resolveFn = value => {
try {
const x = onFulfilled(value)
// 分類(lèi)討論返回值,如果是Promise,那么等待Promise狀態(tài)變更,否則直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
}
}
const rejectFn = error => {
try {
const x = onRejected(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
case STATUS.PENDING:
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
break;
case STATUS.FULFILLED:
resolveFn(this._value)
break;
case STATUS.REJECTED:
rejectFn(this._value)
break;
}
})
}
catch (rejectFn) {
return this.then(undefined, rejectFn)
}
// promise.finally方法
finally(callback) {
return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
MyPromise.resolve(callback()).then(() => error)
})
}
// 靜態(tài)resolve方法
static resolve(value) {
return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
}
// 靜態(tài)reject方法
static reject(error) {
return new MyPromise((resolve, reject) => reject(error))
}
// 靜態(tài)all方法
static all(promiseArr) {
let count = 0
let result = []
return new MyPromise((resolve, reject) => {
if (!promiseArr.length) {
return resolve(result)
}
promiseArr.forEach((p, i) => {
MyPromise.resolve(p).then(value => {
count++
result[i] = value
if (count === promiseArr.length) {
resolve(result)
}
}, error => {
reject(error)
})
})
})
}
// 靜態(tài)race方法
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
promiseArr.forEach(p => {
MyPromise.resolve(p).then(value => {
resolve(value)
}, error => {
reject(error)
})
})
})
}
}
11. 手寫(xiě)原生AJAX
創(chuàng)建 XMLHttpRequest 實(shí)例 發(fā)出 HTTP 請(qǐng)求 服務(wù)器返回 XML 格式的字符串 JS 解析 XML,并更新局部頁(yè)面 不過(guò)隨著歷史進(jìn)程的推進(jìn),XML 已經(jīng)被淘汰,取而代之的是 JSON。
myButton.addEventListener('click', function () {
ajax()
})
function ajax() {
let xhr = new XMLHttpRequest() //實(shí)例化,以調(diào)用方法
xhr.open('get', 'https://www.google.com') //參數(shù)2,url。參數(shù)三:異步
xhr.onreadystatechange = () => { //每當(dāng) readyState 屬性改變時(shí),就會(huì)調(diào)用該函數(shù)。
if (xhr.readyState === 4) { //XMLHttpRequest 代理當(dāng)前所處狀態(tài)。
if (xhr.status >= 200 && xhr.status < 300) { //200-300請(qǐng)求成功
let string = request.responseText
//JSON.parse() 方法用來(lái)解析JSON字符串,構(gòu)造由字符串描述的JavaScript值或?qū)ο?br> let object = JSON.parse(string)
}
}
}
request.send() //用于實(shí)際發(fā)出 HTTP 請(qǐng)求。不帶參數(shù)為GET請(qǐng)求
}
function ajax(url) {
const p = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status <= 300) {
resolve(JSON.parse(xhr.responseText))
} else {
reject('請(qǐng)求出錯(cuò)')
}
}
}
xhr.send() //發(fā)送hppt請(qǐng)求
})
return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
.catch(reason => console.log(reason))
12. 手寫(xiě)節(jié)流防抖函數(shù)
function debounce(fn, delay) {
if(typeof fn!=='function') {
throw new TypeError('fn不是函數(shù)')
}
let timer; // 維護(hù)一個(gè) timer
return function () {
var _this = this; // 取debounce執(zhí)行作用域的this(原函數(shù)掛載到的對(duì)象)
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args); // 用apply指向調(diào)用debounce的對(duì)象,相當(dāng)于_this.fn(args);
}, delay);
};
}
input1.addEventListener('keyup', debounce(() => {
console.log(input1.value)
}), 600)
function throttle(fn, delay) {
let timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
fn.apply(_this, args); // 這里args接收的是外邊返回的函數(shù)的參數(shù),不能用arguments
// fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受類(lèi)數(shù)組對(duì)象。如果傳入類(lèi)數(shù)組對(duì)象,它們會(huì)拋出異常。
timer = null; // 在delay后執(zhí)行完fn之后清空timer,此時(shí)timer為假,throttle觸發(fā)可以進(jìn)入計(jì)時(shí)器
}, delay)
}
}
div1.addEventListener('drag', throttle((e) => {
console.log(e.offsetX, e.offsetY)
}, 100))
13. 手寫(xiě)Promise加載圖片
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data)
},
error(err) {
reject(err)
}
})
})
}
const url1 = './data1.json'
const url2 = './data2.json'
const url3 = './data3.json'
getData(url1).then(data1 => {
console.log(data1)
return getData(url2)
}).then(data2 => {
console.log(data2)
return getData(url3)
}).then(data3 =>
console.log(data3)
).catch(err =>
console.error(err)
)
14. 函數(shù)實(shí)現(xiàn)一秒鐘輸出一個(gè)數(shù)
for(let i=0;i<=10;i++){ //用var打印的都是11
setTimeout(()=>{
console.log(i);
},1000*i)
}
15. 創(chuàng)建10個(gè)標(biāo)簽,點(diǎn)擊的時(shí)候彈出來(lái)對(duì)應(yīng)的序號(hào)?
var a
for(let i=0;i<10;i++){
a=document.createElement('a')
a.innerHTML=i+'<br>'
a.addEventListener('click',function(e){
console.log(this) //this為當(dāng)前點(diǎn)擊的<a>
e.preventDefault() //如果調(diào)用這個(gè)方法,默認(rèn)事件行為將不再觸發(fā)。
//例如,在執(zhí)行這個(gè)方法后,如果點(diǎn)擊一個(gè)鏈接(a標(biāo)簽),瀏覽器不會(huì)跳轉(zhuǎn)到新的 URL 去了。我們可以用 event.isDefaultPrevented() 來(lái)確定這個(gè)方法是否(在那個(gè)事件對(duì)象上)被調(diào)用過(guò)了。
alert(i)
})
const d=document.querySelector('div')
d.appendChild(a) //append向一個(gè)已存在的元素追加該元素。
}

往期推薦



最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專(zhuān)業(yè)的技術(shù)人...


評(píng)論
圖片
表情
