手寫前端高頻面試題
建議優(yōu)先掌握:
- instanceof (考察對(duì)原型鏈的理解)
- new (對(duì)創(chuàng)建對(duì)象實(shí)例過(guò)程的理解)
- call&apply&bind (對(duì)this指向的理解)
- 手寫promise (對(duì)異步的理解)
- 手寫原生ajax (對(duì)http請(qǐng)求方式的理解,重點(diǎn)是get和post請(qǐng)求)
1、手寫 instanceof
instanceof作用:判斷一個(gè)實(shí)例是否是其父類或者祖先類型的實(shí)例。instanceof 在查找的過(guò)程中會(huì)遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype查找失敗,返回 false
let instanceof = (target, origin) => {
while (target) {
if(target.__proto__ === origin.prototype) {
return true
}
target = target.__proto__
}
return false
}
let a = [1, 2, 3]
console.log(instanceof(a, Array)); // true
console.log(instanceof(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、 手寫數(shù)組的reduce方法
reduce() 方法接收一個(gè)函數(shù)作為累加器,數(shù)組中的每個(gè)值(從左到右)開始縮減,最終為一個(gè)值,是ES5中新增的又一個(gè)數(shù)組逐項(xiàng)處理方法
參數(shù):
- 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ù)組扁平化
數(shù)組扁平化就是把多維數(shù)組轉(zhuǎn)化成一維數(shù)組
5.1、es6提供的新方法 flat(depth)
let a = [1, [2, 3]];
a.flat(); // [1, 2, 3]
a.flat(1); //[1, 2, 3]
5.2、無(wú)需知道數(shù)組的維度,直接將目標(biāo)數(shù)組變成1維數(shù)組。depth的值設(shè)置為Infinity。
let a = [1, [2, 3, [4, [5]]]];
a.flat(Infinity);
// [1, 2, 3, 4, 5] a是4維數(shù)組
5.3、利用concat
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ù)柯里化
柯里化的定義:接收一部分參數(shù),返回一個(gè)函數(shù)接收剩余參數(shù),接收足夠參數(shù)后,執(zhí)行原函數(shù)。當(dāng)柯里化函數(shù)接收到足夠參數(shù)后,就會(huì)執(zhí)行原函數(shù),如何去確定何時(shí)達(dá)到足夠的參數(shù)呢?
有兩種思路:
- 通過(guò)函數(shù)的 length 屬性,獲取函數(shù)的形參個(gè)數(shù),形參的個(gè)數(shù)就是所需的參數(shù)個(gè)數(shù)
- 在調(diào)用柯里化工具函數(shù)時(shí),手動(dòng)指定所需的參數(shù)個(gè)數(shù)
將這兩點(diǎn)結(jié)合一下,實(shí)現(xiàn)一個(gè)簡(jiǎn)單 curry 函數(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)
}
}
}
我們來(lái)驗(yàn)證一下:
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
7、實(shí)現(xiàn)深拷貝
淺拷貝和深拷貝的區(qū)別: 淺拷貝:只拷貝一層,更深層的對(duì)象級(jí)別的只拷貝引用 深拷貝:拷貝多層,每一級(jí)別的數(shù)據(jù)都會(huì)拷貝。這樣更改拷貝值就不影響另外的對(duì)象
ES6淺拷貝方法:Object.assign(target, ...sources)
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)單類型?
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ù)類型,直接賦值
newObj[k] = item
}
}
}
8、手寫call, apply
8.1、手寫call
// 函數(shù)的方法,所以寫在Function原型對(duì)象上
Function.prototype.myCall = function(context = window){
// 這里if其實(shí)沒(méi)必要,會(huì)自動(dòng)拋出錯(cuò)誤
if(typeof this !== "function"){
throw new Error("不是函數(shù)")
}
//這里可用ES6方法,為參數(shù)添加默認(rèn)值,js嚴(yán)格模式全局作用域this為undefined
const obj = context || window
//this為調(diào)用的上下文,this此處為函數(shù),將這個(gè)函數(shù)作為obj的方法
obj.fn = this
//第一個(gè)為obj所以刪除,偽數(shù)組轉(zhuǎn)為數(shù)組
const arg = [...arguments].slice(1)
res = obj.fn(...arg)
// 不刪除會(huì)導(dǎo)致context屬性越來(lái)越多
delete obj.fn
return res
}
//用法:f.call(obj, arg1)
function f(a, b){
console.log(a + b)
console.log(this.name)
}
let obj = {
name : 1
}
//否則this指向window
f.myCall(obj, 1, 2)
//打出來(lái)的是 Spike
obj.greet.call({name: 'Spike'})
8.2、手寫apply
// 箭頭函數(shù)從不具有參數(shù)對(duì)象!!!!!這里不能寫成箭頭函數(shù)
Function.prototype.myApply = function(context){
let obj=context || window
obj.fn = this
//若有參數(shù),得到的是數(shù)組
const arg = arguments[1] || []
let res = obj.fn(...arg)
delete obj.fn
return res
}
// 驗(yàn)證一下
function f(a, b){
console.log(a, b)
console.log(this.name)
}
let obj = {
name:'張三'
}
//arguments[1]
f.myApply(obj, [1,2])
8.3、手寫bind
Function.prototype.bind2 = function (context) {
const self = this;
const args = Array.prototype.slice.call(arguments, 1);
const NOOP = function(){};
NOOP.prototype = this.prototype;
const func = function () {
const args2 = Array.prototype.slice.call(arguments);
return self.apply(this instanceof NOOP ? this : context, args2.concat(args));
};
func.prototype = new NOOP();
return func;
}
9、手動(dòng)實(shí)現(xiàn)new
new的過(guò)程文字描述:
- 創(chuàng)建一個(gè)空對(duì)象 obj;
- 將空對(duì)象的隱式原型(proto)指向構(gòu)造函數(shù)的prototype。
- 使用 call 改變 this 的指向
- 如果無(wú)返回值或者返回一個(gè)非對(duì)象值,則將 obj 返回作為新對(duì)象;如果返回值是一個(gè)新對(duì)象的話那么直接直接返回該對(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ù)
//將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)
let fn = [].shift.call(arguments)
obj.__proto__ = fn.prototype
//改變this指向,為實(shí)例添加方法和屬性
let res = fn.apply(obj,arguments)
//確保返回的是一個(gè)對(duì)象(萬(wàn)一fn不是構(gòu)造函數(shù))
return typeof res === 'object' ? res:obj
}
let p2 = create(Person, '李四', 19)
p2.sayHi()
10、手寫promise(常見promise.all, promise.race)
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(['Promise'], factory);
} else {
window.Promise = factory();
}
})(this, function () {
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
var async = (function () {
if (typeof process === 'object' && process !== null && typeof process.nextTick === 'function') {
return process.nextTick;
} else if (typeof setImmediate === 'function') {
return setImmediate;
}
return setTimeout;
})();
function isFunction(func) {
return typeof func === 'function';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function Promise(executor) {
var self = this;
this._state = PENDING;
this._value = undefined;
this._onResolvedCallback = [];
this._onRejectedCallback = [];
function resolve(value) {
if (self._state === PENDING) {
self._state = FULFILLED;
self._value = value;
for (var i = 0; i < self._onResolvedCallback.length; i++) {
self._onResolvedCallback[i](value);
}
self._onResolvedCallback = [];
}
}
function reject(reason) {
if (self._state === PENDING) {
self._state = REJECTED;
self._value = reason;
for (var i = 0; i < self._onRejectedCallback.length; i++) {
self._onRejectedCallback[i](reason);
}
self._onRejectedCallback = [];
}
}
try {
// async(executor, null, resolve, reject);
executor(resolve, reject);
} catch (reason) {
// async(reject, null, reason);
reject(reason);
}
}
Promise.prototype.then = function (onResolved, onRejected) {
var self = this;
onResolved = isFunction(onResolved)
? onResolved
: function (v) {
return v;
};
onRejected = isFunction(onRejected)
? onRejected
: function (r) {
throw r;
};
return new self.constructor(function (resolve, reject) {
function _resolve(value) {
try {
var p = onResolved(value);
if (p instanceof Promise) {
p.then(resolve, reject);
} else {
resolve(p);
}
} catch (e) {
reject(e);
}
}
function _reject(reason) {
try {
var p = onRejected(reason);
if (p instanceof Promise) {
p.then(resolve, reject);
} else {
resolve(p);
}
} catch (e) {
reject(e);
}
}
if (self._state === PENDING) {
self._onResolvedCallback.push(_resolve);
self._onRejectedCallback.push(_reject);
} else if (self._state === FULFILLED) {
async(_resolve, null, self._value);
// _resolve(self._value);
} else if (self._state === REJECTED) {
async(_reject, null, self._value);
// _reject(self._value);
}
});
};
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
Promise.resolve = function (data) {
return new Promise(function (resolve) {
resolve(data);
});
};
Promise.reject = function (data) {
return new Promise(function (resolve, reject) {
reject(data);
});
};
Promise.all = function (promiseArr) {
if (!isArray(promiseArr)) {
throw new TypeError('Promise.all need Array object as argument');
}
return new Promise(function (resolve, reject) {
var count = (len = promiseArr.length);
var result = [];
for (var i = 0; i < len; i++) {
var promise = promiseArr[i];
promise.then(
(function (index) {
return function (value) {
result[index] = value;
if (--count === 0) {
resolve(result);
}
};
})(i),
reject
);
}
});
};
Promise.race = function (promiseArr) {
if (!isArray(promiseArr)) {
throw new TypeError('Promise.race need Array object as argument');
}
return new Promise(function (resolve, reject) {
var len = promiseArr.length;
for (var i = 0; i < len; i++) {
var promise = promiseArr[i];
promise.then(resolve, reject);
}
});
};
return Promise;
});
11、手寫原生AJAX并支持jsonp
步驟
- 創(chuàng)建 XMLHttpRequest 實(shí)例
- 發(fā)出 HTTP 請(qǐng)求
- 服務(wù)器返回 XML 格式的字符串
- JS 解析 XML,并更新局部頁(yè)面不過(guò)隨著歷史進(jìn)程的推進(jìn),XML 已經(jīng)被淘汰,取而代之的是 JSON。
了解了屬性和方法之后,根據(jù) AJAX 的步驟,手寫最簡(jiǎn)單的 GET 請(qǐng)求。
var ajax = (function () {
var GLOBAL = {};
//獲取XMLHttpRequest對(duì)象
var getXMLHttpReq = function () {
var xhr = null;
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP'); //IE高版本創(chuàng)建XMLHTTP
} catch (E) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP'); //IE低版本創(chuàng)建XMLHTTP
} catch (E) {
xhr = new XMLHttpRequest(); //兼容非IE瀏覽器,直接創(chuàng)建XMLHTTP對(duì)象
}
}
return xhr;
};
//格式化用戶輸入數(shù)據(jù)參數(shù),防止不合法參數(shù),返回json對(duì)象
//TODO 試圖格式化數(shù)據(jù)時(shí)使用eval,可能運(yùn)行不合法的腳本,造成安全問(wèn)題
var parseData = function (data) {
if (!data) return {};
if (typeof data != 'object') {
try {
data = JSON.parse(data);
} catch (e) {
data = eval('(' + data + ')');
}
}
return data;
};
//判斷空對(duì)象
var isEmptyObject = function (obj) {
var name;
for (name in obj) {
return false;
}
return true;
};
return (the = {
//ajax查詢方法
request: function (arg) {
var url = arg.url || null;
var dataType = arg.dataType || 'text';
var type = arg.type || 'post';
var async = arg.async || true;
var callback = arg.callback || null;
var data = arg.data || null;
if (!url) return null;
var XMLHttpReq = getXMLHttpReq();
data = parseData(data);
if (type.toLowerCase() === 'jsonp') {
if (!arg.jsonp) arg.jsonp = 'jsonp';
url += url.indexOf('?') == -1 ? '?' : '&';
if (!isEmptyObject(data)) {
for (var o in data) {
url += o + '=' + data[o] + '&';
}
url = url.substring(0, url.length - 1);
}
url += '&' + arg.jsonp + '=ajax.jsonpCallback';
var JSONP = document.createElement('script');
JSONP.type = 'text/javascript';
JSONP.id = 'jsonp';
JSONP.src = url;
document.getElementsByTagName('head')[0].appendChild(JSONP);
if (callback) GLOBAL.callback = callback;
return;
} else if (type.toLowerCase() === 'get') {
if (!isEmptyObject(data)) {
url += url.indexOf('?') == -1 ? '?' : '&';
for (var o in data) {
url += o + '=' + data[o] + '&';
}
url = url.substring(0, url.length - 1);
}
var sendData = null;
XMLHttpReq.open(type, url, async);
} else {
XMLHttpReq.open(type, url, async);
XMLHttpReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
if (!isEmptyObject(data)) {
var sendData = '';
for (var o in data) {
sendData += o + '=' + data[o] + '&';
}
sendData = sendData.substring(0, sendData.length - 1);
} else {
var sendData = null;
}
}
XMLHttpReq.send(sendData);
XMLHttpReq.onreadystatechange = function () {
//指定響應(yīng)函數(shù)
if (XMLHttpReq.readyState == 4) {
if (XMLHttpReq.status == 200) {
switch (dataType.toLowerCase()) {
case 'json':
var res = XMLHttpReq.responseText;
res = JSON.parse(res);
break;
case 'xml':
var res = XMLHttpReq.responseXML;
break;
default:
var res = XMLHttpReq.responseText;
}
if (callback) {
callback(res);
}
}
}
};
},
//jsonp回調(diào)函數(shù)
jsonpCallback: function (data) {
document.getElementsByTagName('head')[0].removeChild(document.getElementById('jsonp'));
if (GLOBAL.callback) {
GLOBAL.callback(data);
}
},
});
})();
12、手寫節(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)
節(jié)流:
function throttle(fn, delay) {
let timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
// 這里args接收的是外邊返回的函數(shù)的參數(shù),不能用arguments
fn.apply(_this, args);
// fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受類數(shù)組對(duì)象。如果傳入類數(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、手寫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ù)
// 用var打印的都是11
for(let i = 0; i <= 10; i++){
setTimeout(()=>{
console.log(i);
}, 1000*i)
}
文章轉(zhuǎn)載自知乎 ,原文地址: https://zhuanlan.zhihu.com/p/436920660
更多面試相關(guān)文章
