自用前端面试题(手写代码)

125 阅读4分钟

0.手写代码大全

juejin.cn/post/694613…

1.手写 Object.create

function create(obj) {
//利用原型链让新对象原型指向参数对象
  function F() {}
  F.prototype = obj
//对象的隐式原型指向它构造函数的显式原型
  return new F()
}

6.手写 Promise.all

function promiseAll(promises) {
  // promiseAll的特点就是全部promises处理完后统一调用resolve,并且有一个promise出错就出发reject
  // 所以最外层就是一个Promise
  return new Promise(function (resolve, reject) {
    // 简单的数组判断
    if (!Array.isArray(promises)) {
      throw new TypeError(`argument must be a array`)
    }
    // 已处理的promise的计数器
    var resolvedCounter = 0;
    // 待处理的promise总数
    var promiseNum = promises.length;
    // 保存处理结果的数组
    var resolvedResult = [];

    for (let i = 0; i < promiseNum; i++) {
    // 当Promise.resolve()接收的是Promise对象时,直接返回这个Promise对象,什么都不做,所以这里就是包装一层,
      Promise.resolve(promises[i]).then(value => {
        // 已经处理的Promise计数+1
        resolvedCounter++;
        // 保存当前Promise处理结果
        resolvedResult[i] = value;
        // 判断全部Promise是否处理完毕,已处理数量等于总数量就是处理完
        if (resolvedCounter == promiseNum) {
          // 全部处理完,才调用做外层的Promise的resolve
          return resolve(resolvedResult)
        }
      }, error => {
        // 有一个promise出错就触发reject
        return reject(error)
      })
    }
  })
}
// test
let p1 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(1)
  }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(2)
  }, 2000)
})
let p3 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(3)
  }, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
  console.log(res) // [3, 1, 2]
})

7.手写 Promise.race

Promise.race = function (promises) {
  // 竞速函数意思就是待处理的全部Promise中有任意一个先处理完,就以这个先处理完的Promise的处理结果作为整体处理结果返回,剩余的Promise处理结果则不生效
  // 这就利用到Promise状态只能改变一次的特性
  // 所以最外层用Promise去封装,提供统一的resolve, reject回调
  return new Promise((resolve, reject) => {
    for (let i = 0, len = promises.length; i < len; i++) {
      // 将最外层统一的resolve, reject传入每个待处理的Promise中,这样谁先处理完,那就谁先调用resolve或reject
      // 这样最外层Promise会只返回这个第一Promise的处理结果,Promise状态只能改变一次,所以后续其他Promise的处理均不会得到响应
      promises[i].then(resolve, reject)
    }
  })
}

18.(3)手写实现深拷贝函数

// 深拷贝的实现
function deepCopy(object) {
  // 简单的类型判断
  if (!object || typeof object !== "object") return;

  // 按照数组或者普通对象来初始化结果Object
  let newObject = Array.isArray(object) ? [] : {};

  // 遍历原始Object的属性值,注意这里会将对象原型的属性和自有的属性都遍历到
  for (let key in object) {
    // 所以这里需要通过hasOwnProperty来判断这个属性是不是自有属性,拷贝的话只拷贝自有属性
    if (object.hasOwnProperty(key)) {
      // 给结果对象增加对应属性key和属性值,如果属性值本身还是个对象,那么就递归调用deepCopy,一层层复制
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  return newObject;
}

3. 实现数组的乱序输出-还有一方法就是倒序遍历

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let length = arr.length,
  randomIndex,
  temp;
while (length) {
  // 获取不是最后一个的一个随机的元素下标
  randomIndex = Math.floor(Math.random() * length--);
  // 下面三行就是进行元素位置调换,实现打乱的目的
  temp = arr[length];
  arr[length] = arr[randomIndex];
  arr[randomIndex] = temp;
}
console.log(arr)

4.实现数组元素求和

  • arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce( (total,i) => total += i,0);
console.log(sum);
  • arr=[1,2,3,[[4,5],6],7,8,9],求和
var arr=[1,2,3,[[4,5],6],7,8,9]
// arr.toString()会将嵌套数组都抹平输出,此时arr.toString()结果为 “1,2,3,4,5,6,7,8,9”
// .split(',')则是将全部数字都提取出来了,再用reduce进行加和计算
let arr = arr.toString().split(',').reduce( (total,i) => total += Number(i),0);
console.log(arr);

5.实现数组去重

ES5方法:使用map存储不重复的数字

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

uniqueArray(array); // [1, 2, 3, 5, 9, 8]

function uniqueArray(array) {
  //生命一个空的对象用于存储不重复的数字
  let map = {};
  // 结果数组
  let res = [];
  for(var i = 0; i < array.length; i++) {
    // array[i]获取当前数字,判断map是否包含key为该数字的属性,就是这里实现了去重
    if(!map.hasOwnProperty([array[i]])) {
      // map不包含key为该数字的属性,那就添加上,其中属性值赋为1,1没有特殊含义,只是为了有个值而已,也可以是10000
      map[array[i]] = 1;
      // 不重复的数存入结果数组
      res.push(array[i]);
    }
  }
  return res;
}

6.实现数组的flat方法

//首先明白flat函数的作用:flat() 函数提供了将一组数组项串联成一个全新的数组并在函数完成后返回新数组的能力,并且不改变原数组
// 多用于将嵌套数组抹平,举个例子
// var array1 = [1, 2, [3, 4], [[5, 6]], [[[7, 8]]], [[[[9, 10]]]]];
// var array2 = array1.flat(2);
// 那么array2 值为 [1, 2, 3, 4, 5, 6, [7, 8], [[9, 10]]]
// 由此可见在原始数组array1中深度最大为2的任何数组都将被完全展平,原始数组中深度为3或更大的任何数组的深度将减少2,并且这些数组中深度为1或2的任何单个数组项将单独连接到新数组
function _flat(arr, depth) {
  // 简单的入参校验
  if(!Array.isArray(arr) || depth <= 0) {
    return arr;
  }
  // 这里使用reduce来实现累加的效果,注意reduce是累加器的作用,不单纯可以用于数组求和,对数组元素进行任何形式累加都可以通过reduce实现
  // 此处的prev我们先暂称之为累积数组,就是前面元素操作后累积添加得到的新数组
  return arr.reduce((prev, cur) => {
    if (Array.isArray(cur)) {
      // 判断当前遍历到的数组元素是不是数组,如果是,就递归调用_flat进行展平,当然展平深度每展平一次就要消耗1
      // 将结果添加进累积数组里
      return prev.concat(_flat(cur, depth - 1))
    } else {
      // 当前遍历到的数组元素不是数组是数字,那么就直接累积添加进累积数组
      return prev.concat(cur);
    }
  }, []);
}

8. 实现数组的push方法


// 数组的push本质就是数组末尾添加上指定元素
// 如果一个数组arr已有5个元素,下标0~4,再push一个元素a,其实就是让arr[5]=a
let arr = [];
Array.prototype.push = function() {
	for( let i = 0 ; i < arguments.length ; i++){
    // 这里执行的就是开头注释说的逻辑
		this[this.length] = arguments[i] ;
	}
	return this.length;
}

1. 已知数据格式,实现函数fn找出链条中所有的父级id

const value = '112'
const fn = (value) => {
...
}
fn(value) // 输出 [1, 11, 112]

数据结构:

[
    {
        id:'1',
        name:'广东省',
        children:[
            {
                id:'11',
                name:"深圳市",
                children:[
                    {
                        id:'111',
                        name:'南山区'
                    },
                    {
                        id:'112',
                        name:'福田区'
                    }
                ]
            }
        ]
    }
]

答案:

const fn = (data, value) => {
  let res = []
  const dfs = (arr, temp = []) => {
    for (const node of arr) {
      if (node.children) {
        dfs(node.children, temp.concat(node.id))
      } else {
        if (node.id === value) {
          res = temp
        }
        return
      }
    }
  }
  dfs(data)
  return res
}

2.email格式校验

正则: /[A-Za-z0-9_]+@[A-Za-z0-9]+.[a-z]+/

3.用stack去创建tree

class Stack {
    constructor() {
        this.items = [];
    }
    push(element) {
	this.items.push(element);
    }
    pop() {
	return this.items.pop();
    }
    peek() {
        return this.items[this.items.length - 1];
    }
    isEmpty() {
	return !this.items.length;
    }
    clear() {
	this.items = [];
    }
    size() {
	return this.items.length;
    }
}

4.定义一个数据结构dom,实现一个render(dom)方法,渲染出<ul><li>1</li><li>2</li><li>3</li></ul>

数组,然后使用map循环渲染<li/>

5.实现一个高阶函数,让一个API请求函数失败时自动重试指定的次数

const axios = require('axios');
/**
 * 接口请求重试
 * @param {num} retryMax  请求次数
 */
async function autoRetry(retryMax) {
  try {
    if (retryMax <= 0) {
      return 'error';
    }
    let res = await axios.get('XXXXXXXXXXX')
    return res;
  } catch (error) {
    return autoRetry(retryMax - 1);
  }
}

6.矩阵操作

题干缺失,好像是实现矩阵的旋转

52-查看输出3.png

7.版本号对比实现(TODO)

实现diffVersion使

  • version1 = '1.2' version2='0.2' diffVersion(version1,version2) 返回1
  • version1 = '1.2' version2='3.2' diffVersion(version1,version2) 返回-2
  • version1 = '1.2' version2='1.2' diffVersion(version1,version2) 返回0

思路: 对版本号字符串使用spilt分割成低、中、高版本号,然后一一对比

7.实现大数相加

blog.csdn.net/ZHANGYANG_1…

8.实现Iterator

function myIterator(arr) {
    let index = 0;
    return {
        next: function () {
            if (index < arr.length) {
                return {
                    value: arr[index++],
                    done: false
                }
            }

            else {
                return {
                    value: undefined,
                    done: true
                }
            }
        }
    }
}

let iter = myIterator([1, 2, 3, 4]);
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

9.手写thunk函数并结合Generator实现异步编程

概念:
thunk函数的作用非常简单 其实就是把一个函数的 执行参数 和 回调分成两个函数(可以把函数本身也进行包装) 只要是有回调函数的 就可以用thunk进行包装

var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

//发现执行参数的函数在一起
var gen = function* () {
    var data1 = yield readFile('./a.txt');
    //用户获取数据后自定义写在这里
    console.log(data1.toString());
    var data2 = yield readFile('./b.txt');
    //用户获取数据后自定义写在这里
    console.log(data2.toString());
}


//写个执行函数
//发现callback在一起  而且调用的形式都一样
var g = gen();
//var d1 = g.next();

//执行value 实际为执行总函数 -->回调函数
// d1.value(function(err, data) {
// 	if (err) throw err;
// 	//传回数据
// 	var d2 = g.next(data);
// 	d2.value(function(err, data2) {
// 		if (err) throw err;
// 		g.next(data2);
// 	});
// });
//下面是利用thunk对函数进行总结书写
function run(gen) {

    const next = (err, data) => {

        let res = gen.next(data);

        if (res.done) return;

        res.value(next);

    }
    next();
}
run(g)

10.手写async函数

async实际是Generator的语法糖,所以就可以就Generator来实现async

function asyncToGenerator (genFunc) {
  return function () {
    const gen = genFunc.apply(this, arguments);
    return new Promise((resolve, reject) => {
      function step (key, arg) {
        let generatorResult;
        try {
          generatorResult = gen[key](arg);
        } catch (err) {
          return reject(err);
        }
        const { value, done } = generatorResult;
        if (done) {
          return resolve(value);
        } else {
          return Promise.resolve(value).then(val => {
            step("next", val)
          }, err => {
            step("throw", err)
          })
        }
      }
      step("next")
    })
  }
}
var gen = asyncToGenerator(testG)
gen().then(res => console.log(res))

11.为一组元素添加点击事件,获得每一个元素的UID

<div @click="Onclick($event)" id="999">点我<div>

Onclick(a){
   console.log(a.currentTarget.id) //999
}

12.