总结了一些面面试中常见的手写代码题,如有更好见解欢迎评论指正~
手动实现map
Array.prototype._map = function (callbackFn, thisArg) {
// 判断this值是否合法,否则抛出异常
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'map' of null");
}
// 判断回调函数是否合法
if (typeof callbackFn !=='function') {
throw new TypeError(callbackFn + ' is not a function')
}
// 将this值(调用_map的数组)具象化成变量O
let O = Object(this);
let receiver = thisArg;
// 使用无符号右移运算符对length值右移0位,相当于向下取整,丢弃小数部分
let len = O.length >>> 0;
// 创建一个长度相同的结果数组
let A = new Array(len);
for (let k = 0; k < len; k++) {
// 以0-length的顺序处理元素,跳过空值
if (k in O) {
let kValue = O[k];
// 依次传入this, 当前项,当前索引,整个数组
let mappedValue = callbackFn.call(receiver, kValue, k, O);
A[k] = mappedValue;
}
}
// 返回结果
return A;
}
const list = [1,2,3]
const mapRes = list._map(e=>e)
获取元素的Z-index
function getZIndex(){
return [...document.all].reduce((r, e) => Math.max(r, +window.getComputedStyle(e).zIndex || 0), 0)
}
翻转DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>反转DOM结构</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<button onclick="reverseChildrenNodes()">反转DOM</button>
<script>
function reverseChildrenNodes() {
let ele = document.querySelector('ul')
let len = ele.childNodes.length
while (len-- > 0) {
// 原节点会被自动移除
ele.appendChild(ele.childNodes[len])
}
}
</script>
</body>
</html>
或者
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>反转DOM结构</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<button onclick="reverseChildrenNodes()">反转DOM</button>
<script>
function reverseChildrenNodes() {
let ele = document.querySelector('ul')
let children = Array.from(ele.childNodes)
for (const child of children) {
ele.removeChild(child)
}
children.reverse()
for (const child of children) {
ele.appendChild(child)
}
}
</script>
</body>
</html>
每间隔1s打印
function sleep(wait, val) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(val)
}, wait)
})
}
async function test() {
for (let i = 0; i < 5;) {
i++
console.log(await sleep(1000,i))
}
}
test()
手写代码实现红绿灯效果
红灯3秒,绿灯1秒,黄灯2秒,循环重复
function sleep(time, value) {
return new Promise(res => {
setTimeout(() => {
res(value);
}, time)
})
};
const lightDict = [
{ value: 'red', time: 2000 },
{ value: 'green', time: 3000 },
{ value: 'yellow', time: 1000 },
];
async function test() {
while (true) {
for (let item of lightDict) {
console.log(await sleep(item.time, item.value), new Date().getTime())
}
}
};
test();
或者
async function light() {
console.log('红灯', Date.now());
setTimeout(() => {
console.log('绿灯', Date.now());
setTimeout(() => {
console.log('黄灯', Date.now());
setTimeout(() => {
light()
}, 2000)
}, 1000)
}, 3000)
};
数组"取"重排序
- 取得两个数组⾥相同的部分, 并去重
- 然后按照从⼩到⼤顺序排序, 最后结果返回 (注意, 是返回结果, 不是把结果打印出来)
const arrayA = [4, 2, 1, 2, 5];
const arrayB = [2, 3, 1, 6];
function process(arrayA, arrayB) {
let res = []
for (let i = 0; i < arrayA.length; i++) {
if (arrayB.includes(arrayA[i])&&!res.includes(arrayA[i])) {
res.push(arrayA[i])
}
}
return res.sort((a, b) => a - b)
}
/*
应该返回 [1, 2]
*/
console.log(process(arrayA, arrayB))
数组转树状结构
const data = [
{ id: '01', name: '张大大', pid: '00', job: '项目经理' },
{ id: '02', name: '小亮', pid: '01', job: '产品leader' },
{ id: '03', name: '小美', pid: '01', job: 'UIleader' },
{ id: '04', name: '老马', pid: '01', job: '技术leader' },
{ id: '05', name: '老王', pid: '01', job: '测试leader' },
{ id: '06', name: '老李', pid: '01', job: '运维leader' },
{ id: '07', name: '小丽', pid: '02', job: '产品经理' },
{ id: '08', name: '大光', pid: '02', job: '产品经理' },
{ id: '09', name: '小高', pid: '03', job: 'UI设计师' },
{ id: '10', name: '小刘', pid: '04', job: '前端工程师' },
{ id: '11', name: '小华', pid: '04', job: '后端工程师' },
{ id: '12', name: '小李', pid: '04', job: '后端工程师' },
{ id: '13', name: '小赵', pid: '05', job: '测试工程师' },
{ id: '14', name: '小强', pid: '05', job: '测试工程师' },
{ id: '15', name: '小涛', pid: '06', job: '运维工程师' }
]
// 递归:代码简洁,效率低
function transToTree(list, root) {
return list
.filter(item => item.pid === root)
.map(item => ({ ...item, children: transToTree(list, item.id) }))
}
// console.log(JSON.stringify(transToTree(data, '00')))
// 循环:利用引用数据类型的特性
function transToTree1(list, root) {
let res = [];
let map = {};
for (let item of list) {
map[item.id] = { ...item }
}
for (let item of list) {
const { pid, id } = item
if (pid === root) {
res.push(map[id])
} else {
map[pid].children ? map[pid].children.push(map[id]) : map[pid].children = [map[id]]
}
}
return res
}
console.log(JSON.stringify(transToTree1(data, '00')))
函数柯里化
function curry(fn, ...rest) {
const length = fn.length
return function () {
const args = [...rest, ...arguments]
console.log([...rest], [...arguments])
if (args.length < length) {
return curry.call(this, fn, ...args)
} else {
return fn.apply(this, args)
}
}
}
const fn = (a, b, c) => {
return a + b + c
}
const f = curry(fn)
console.log(f(1)(2)(3))
console.log(f(1)(2, 3))
console.log(f(1, 2, 3))
实现emitterEvent
class EventEmitter {
constructor() {
this.events = {}
}
on(name, cb) {
this.events[name] = this.events[name] ?? []
this.events[name].push(cb)
}
emit(name, ...rest) {
if(!this.events[name]){
console.log('不存在emit事件名称')
return
}
this.events[name].forEach(fn => fn(...rest))
}
off(name, cb) {
if(!this.events[name]){
console.log('不存在off事件名称')
return
}
this.events[name] = this.events[name].filter(e => e !== cb)
}
once(name, cb) {
const fn = (...args) => {
cb(...args)
this.off(name, fn)
}
this.on(name, fn)
}
}
const eventBus = new EventEmitter()
// eventBus.off('不存在','data')
// eventBus.once("once", (params) => { console.log(1, params) })
// eventBus.on("once", (params) => { console.log(2, params) })
// eventBus.on("not once", (params) => { console.log(3, params) })
// eventBus.on("not once", (params) => { console.log(4, params) })
// eventBus.emit("once", "once emit")
// eventBus.emit("once", "once emit")
// eventBus.emit("once", "once emit")
// eventBus.emit("not once", "not once emit")
function testOff(params) {
console.log(5, params)
}
function testOff1(params) {
console.log(6, params)
}
eventBus.on("testOff", testOff)
eventBus.on("testOff", testOff1)
eventBus.emit('testOff', 'off1')
eventBus.off('testOff', testOff)
eventBus.emit('testOff', 'off2')
异步加法
// 异步加法
function asyncAdd(a, b, cb) {
setTimeout(() => {
cb(null, a + b)
}, Math.random() * 1000)
}
async function total() {
const res1 = await sum(1, 2, 3, 4, 5, 6, 4)
const res2 = await sum(1, 2, 3, 4, 5, 6, 4)
console.log([res1, res2])
return [res1, res2]
}
// 实现下 sum 函数。注意不能使用加法,在 sum 中借助 asyncAdd 完成加法。尽可能的优化这个方法的时间。
function sum(){}
// 异步加法
function asyncAdd(a, b, cb) {
setTimeout(() => {
cb(null, a + b)
}, Math.random() * 1000)
}
async function total() {
const res1 = await sum(1, 2, 3, 4, 5, 6, 4)
const res2 = await sum(1, 2, 3, 4, 5, 6, 4)
console.log([res1, res2])
return [res1, res2]
}
let caches = {}//缓存不能定义在sum内部,不然每次计算都会声明,永远不会有缓存
total()
// 实现下 sum 函数。注意不能使用加法,在 sum 中借助 asyncAdd 完成加法。尽可能的优化这个方法的时间。
async function sum(...rests) {
let res = 0;
let key = rests.join('+')
if (caches.hasOwnProperty(key)) {
console.log('有缓存直接读取缓存')
return caches[key]
}
console.log('没有缓存')
for (let item of rests) {
res = await calculate(res, item)
}
caches[key] = res
return res
}
function calculate(num1, num2) {
return new Promise((res, rej) => {
asyncAdd(num1, num2, (err, result) => {
if (err) {
rej(err)
return
}
res(result)
})
})
}
非立即执行防抖函数
function debounce(fn, wait) {
var timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
立即执行防抖函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
function debounce(fn, wait) {
let timeout = null
let flag = 0
return function () {
let context = this
let args = [...arguments]
if (timeout) {
clearTimeout(timeout)
}
if (flag) {
timeout = setTimeout(() => {
fn.apply(context, args)
console.log('非立即执行')
}, wait)
} else {
fn.apply(context, args)
console.log('立即执行')
flag++
}
}
}
function con() {
console.log('log--->', Date.now())
}
document.addEventListener('scroll', debounce(con, 1000))
</script>
</head>
<body>
<div style="background-color: red;height:30000px">
</div>
</body>
</html>
立即执行节流函数
function throttle(fn, wait) {
let previous = 0;
return function () {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
}
}
非立即执行节流函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
function throttle(fn, wait) {
let previous = 0;
return function () {
let now = Date.now();
let context = this;
let args = arguments;
if (previous === 0) {
previous = now
} else if (now - previous > wait) {
fn.apply(context, args);
previous = now;
}
}
}
function con() {
console.log('log--->', Date.now())
}
document.addEventListener('scroll', throttle(con, 2000))
</script>
</head>
<body>
<div style="background-color: red;height:30000px">
</div>
</body>
</html>
浅拷贝
function clone(o) {
const obj = {};
for (i in o) {
if(o.hasOwnProperty(i)){ // hasOwnProperty忽略继承属性length toString等
obj[i] = o[i]
}
}
return obj
};
深拷贝
function deepCopy(obje) {
if(typeof obje === 'object'){
let afterClone = Array.isArray(obje) ? [] : {};
for(let item in obje){
if(obje.hasOwnProperty(item)){
afterClone[item] = deepCopy(obje[item]);
}
}
return afterClone;
}else{
return obje;
}
}
var theObj = {
name:'jerry',
age:15,
a:undefined,
b:{
c:456
}
};
深拷贝的循环引用问题
但是上述方法循环引用会报错,for example:
var A = {
name:'jerry',
age:15,
a:undefined,
b:{
c:456
}
};
A.A=A
利用Map的key可以是引用数据类型,将要拷贝的对象当做key存起来,value是深拷贝后的对象:
function deepCopy(obje,map=new Map()) {
if(typeof obje === 'object'){
let afterClone = Array.isArray(obje) ? [] : {};
if(map.get(obje)){
return map.get(obje);
}
map.set(obje,afterClone);
for(let item in obje){
if(obje.hasOwnProperty(item)){
afterClone[item] = deepCopy(obje[item],map);
}
}
// return afterClone;
return map.get(obje);
}else{
return obje;
}
}
如上代码解决了循环引用的问题。
在node环境下会打印如下内容:
afterClone <ref *1> {
name: 'jerry',
age: 15,
a: undefined,
b: { c: 456 },
A: [Circular *1]
}
node通过cirular来标识是循环引用,而浏览器会正常输出处理完的循环引用对象。
解决循环引用的原理是:在每次对复杂数据类型进行深拷贝前保存其值,如果下次又出现了该值,就不再进行拷贝,直接截止。
Date和RegExp以及null
function deepClone(target, map = new Map()) {
if (typeof target !== 'object' || !target) {
return target
} else {
let res = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target)
}
map.set(target, res)
for (let k in target) {
if (target.hasOwnProperty(k)) {
const element = target[k]
if (element instanceof Date) {
// 日期类型
let time = new Date(element.getTime()); // 与被拷贝时间达到一致
res[k] = time;
} else if (element instanceof RegExp) {
// 正则类型
res[k] = new RegExp(element);
} else {
res[k] = deepClone(target[k], map)
}
}
}
return map.get(target)
}
}
let obj = {
strAttr: '字符串',
numAttr: 6,
booleanAttr: false,
nullAttr: null,
undefinedAttr: undefined,
symbolAttr: Symbol(1),
objAttr: {
a: 1,
b: 2,
},
arrAttr: [3, 4, 5],
dateAttr: new Date(),
funAttr: function () { console.log('1') },
regAttr: new RegExp()
}
obj.objAttr.c = obj.objAttr
let cloneObj = deepClone(obj)
cloneObj.objAttr.a = 6
console.log(obj, '克隆结果', cloneObj)
Object.create()
Object.mycreate = function (proto, properties) {
function F() { };
F.prototype = proto;
let res = new F()
if (properties) {
Object.defineProperties(res, properties);
}
return res;
}
var hh = Object.mycreate({ a: 1 }, {mm:{value:'isme'}});
console.dir(hh);
创建对象不同方式及存在的差异
日常开发中,经常会使用如下方式创建对象:
- 字面量
- new构造函数
- Object.create
那么上述三种方式存在什么区别呢?
- 字面量创建和new关键字创建并没有区别,创建的新对象的__proto__都指向Object.prototype,只是字面量创建更高效一些。
- Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。———— MDN
-
也就是说Object.create接受两个参数,第一个参数是新创建对象的原型(新创建的对象__proto__指向第一个参数),第二个对象指的是像新创建的对象中添加的属性。
-
若是第一个参数数是null,那新对象就彻彻底底是个空对象,没有继承Object.prototype上的任何属性和方法,如hasOwnProperty()、toString()等。
-
排序
选择
选择排序的大体思路是每次选出最小的一个数字,放在最前边。前i个元素是已经选出来的最小的。
function selectSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let min_index = i;
for (let j = i + 1; j < arr.length; j++) {
min_index = arr[j] < arr[min_index] ? j : min_index;
}
[arr[i], arr[min_index]] = [arr[min_index], arr[i]];
}
return arr;
}
冒泡
冒泡排序的具体思路是前后两个数相比较,把较大的一个放到后边。后边i个元素是已经冒泡到后边的较大的元素。
function bubbleSort(arr) {
for (let i = arr.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr
}
快排
选择一个基准,将数组基于基准分为两个部分,递归进行排序。
function quickSort(arr) {
// 递归结束条件
if (arr.length <= 1) {
return arr
}
let pivotIndex = Math.floor(arr.length / 2);
let pivot = arr.splice(pivotIndex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
new函数
function _new(fn) {
// 1、创建一个对象
let res = {};
// 2、连接到原型
if (fn.prototype !== null) {
res.__proto__ = fn.prototype;
}
//3、绑定this
let ret = fn.apply(res, Array.prototype.slice.call(arguments, 1));
// 4、返回对象(构造函数返回的是基本类型或没有返回值---返回res;返回的是引用类型---返回构造函数返回的值)
if ((typeof ret === "object" || typeof ret === "function") && ret != null) {
return ret;
}
return res;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
let obj = _new(Person, "jerry", 22);
console.log(obj); //Person { name: 'jerry', age: 22 }
function Another(name, age) {
return {
name: name,
age: age,
class: 3,
};
}
let obj2 = _new(Another, "tom", 23);
console.log(obj2) //{ name: 'tom', age: 23, class: 3 }
new的过程都干了什么
- 创建空对象
- 链接原型
- 改变this指向,并向对象中添加属性
- 返回对象(若构造函数中返回的是引用数据类型,则返回构造函数返回的内容,否则返回新创建的对象)
instanceof
function instanceOf(left, right) {
let prototype = right.prototype;
left = left.__proto__;
while (true) {
if (left === null) {
return false;
}
if (prototype === left) {
return true;
}
left = left.__proto__;
}
}
console.log(instanceOf([], Object));
call、apply、bind
以下实现的主要思路是对象中的方法,this指向对象。
call
Function.prototype._call = function (obj) {
obj = obj ? obj : window; // 不传入入参,this指向window
obj.fn = this; //this是调用call的方法
let args = [...arguments].slice(1);
let result = obj.fn(...args);
delete obj.fn;
return result;
};
//测试
function console_() {
console.log(this.name);
}
let name = 'windowwwww'
let obj1 = {
name: "obj",
};
console_._call(obj1) //obj
console_.call() //windowwwww
apply
Function.prototype._apply = function (obj, arr) {
obj = obj ? obj : window;
obj.fn = this;
let result;
if (!arr) {
result = obj.fn();
} else {
result = obj.fn(...arr);
}
delete obj.fn;
return result;
};
function console_() {
console.log(this.name);
}
let name = 'windowwwww'
let obj1 = {
name: "obj",
};
console_._apply(obj1) //obj
console_._apply() //windowwwww
bind
Function.prototype._bind = function (obj) {
var args = [...arguments].slice(1);
var fn = this; //保存this
return function F(){
if(this instanceof F){
return new fn(...args,...argumrnts)
}
return fn.apply(obj,[...args,...arguments])
}
};
sleep
setTimeout
function sleep(fn, wait) {
setTimeout(fn, wait)
}
let testFun = () => { console.log('sleep',Date.now()) }
sleep(testFun,1000)
console.log('time',Date.now())
/*
time 1661666516858
sleep 1661666517860
*/
promise
function sleep(wait) {
return new Promise(res => {
setTimeout(res, wait)
})
}
sleep(1000).then(() => {
console.log('sleep', Date.now())
})
console.log('time', Date.now())
/*
time 1661666684579
sleep 1661666685582
*/
反转字符串
function reverseStr(str){
if (str === '') return '';
return reverseStr(str.substr(1)) + str.charAt(0);
}
console.log(reverseStr('123'))
function reverseStr(str){
return str.split('').reverse().join('')
}
console.log(reverseStr('123'))
let reverseString = function (s) {
let res = s.split('')
let left = 0, right = res.length - 1;
while (left < right) {
let t = res[right]
res[right] = res[left]
res[left] = t
left++
right--
}
return res.join('')
};
console.log(reverseString('1234567'))
二叉树
数组节点生成二叉树
array = [1, 2, 3, 4, 5];
function creatTree(arr) {
//创建节点数组
function Node(key) {
this.key = key;
this.left = null;
this.right = null;
}
var nodeArr = [];
arr.forEach((e) => {
nodeArr.push(new Node(e));
});
console.log(nodeArr);
<!-- /* 节点数组创建完成-->
<!-- [-->
<!-- Node { key: 1, left: null, right: null },-->
<!-- Node { key: 2, left: null, right: null },-->
<!-- Node { key: 3, left: null, right: null },-->
<!-- Node { key: 4, left: null, right: null },-->
<!-- Node { key: 5, left: null, right: null }-->
<!--]-->
<!--*/-->
// 生成二叉树
<!--找到最后一个父节点-->
var lastParent = parseInt(nodeArr.length / 2 - 1);
for (let i = 0; i < lastParent; i++) {
nodeArr[i].left = nodeArr[i * 2 + 1];
nodeArr[i].right = nodeArr[i * 2 + 2];
}
nodeArr[lastParent].left = nodeArr[2 * lastParent + 1];
<!--看最后一个父节点又没有有孩子-->
if (nodeArr.length % 2 === 1) {
nodeArr[lastParent].right = nodeArr[2 * lastParent + 2];
}
return nodeArr;
}
console.log('结果--->',creatTree(array));
<!--/*-->
<!--[-->
<!-- Node {-->
<!-- key: 1,-->
<!-- left: Node { key: 2, left: [Node], right: [Node] },-->
<!-- right: Node { key: 3, left: null, right: null }-->
<!-- },-->
<!-- Node {-->
<!-- key: 2,-->
<!-- left: Node { key: 4, left: null, right: null },-->
<!-- right: Node { key: 5, left: null, right: null }-->
<!-- },-->
<!-- Node { key: 3, left: null, right: null },-->
<!-- Node { key: 4, left: null, right: null },-->
<!-- Node { key: 5, left: null, right: null }-->
<!--]-->
<!--*/-->
二叉树的层次遍历(广度遍历)
二叉树的广度遍历借助队列完成
function levelOrder(root) {
var que = [];
que.push(root);
while (que.length != 0) {
var tmp = que.shift();
console.log(tmp.data);
if (tmp.left != null) {
que.push(tmp.left);
}
if (tmp.right != null) {
que.push(tmp.right);
}
}
}
栈
var stack = function () {
this.data = [];
this.push = push;
this.pop = pop;
this.clear = clear;
this.length = length;
};
var push = function (e) {
this.data.push(e);
};
var pop = function () {
this.data.pop();
};
var clear = function () {
this.length = 0;
};
var length = function () {
return this.data.length;
};
队列
var que = function () {
this.data = [];
this.enQuw = enQuw;
this.deQue = deQue;
this.clear = clear;
this.length = length;
};
var enQuw = function (e) {
this.data.push(e);
};
var deQue = function () {
this.data.shift();
};
var clear = function () {
this.length = 0;
};
var length = function () {
return this.data.length;
};
栈实现队列
var stackA = [];
var stackB = [];
function push(node){
stackA.push(node);
}
function pop(){
if(stackB.length){
return stackB.pop();
}else{
if(stackA.length){
var len = stackA.length;
for(i=0;i<len;i++){
stackB.push(stackA.pop());
}
return stackB.pop()
}else{
return null;
}
}
}
push(1);
push(2);
push(3);
console.log(stackA); //[ 1, 2, 3 ]
console.log(pop()) //1