前端高频手写面试题

315 阅读5分钟

写在前头,笔者大概工作三年吧,从毕业后一直在上家公司待了三年,直到今年8.19我正式交接离职了(裸辞),因为我相信我值得更好的

休息了三个星期后,我开始了我的求职之路,在面试了四家公司后我荣幸的收到了三家公司的offer,期间也碰到了一些手写代码的问题,现在我就将面试中问到的,以及我面试准备到的一起贴出来,希望每位同学都能够day day up!

call

// call
Function.prototype.call = function(context,...arg) {
    // console.log(arg)
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    context = context == undefined ? window : context;
    let type = typeof context;
    if(!/^(object|fucntion)$/.test(type)){
        if(/^(symbol|bigint)$/.test(type)){
            context = Object(context)
        }else{
            context = new context.constructor(context)
        }
    }
    let key = Symbol('key'),
        result;
    context[key] = this;
    result = context[key](...arg)
    delete context[key]
    return result
}

function fn(){
    console.log(this,arguments)
}
console.log("----------call----------")
fn.call(1,1,2,3)
fn.call(1)
console.log("----------call----------")

apply

// apply
Function.prototype.apply = function(context,arg) {
    // console.log(arg)
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    context = context == undefined ? window : context;
    let type = typeof context;
    if(!/^(object|fucntion)$/.test(type)){
        if(/^(symbol|bigint)$/.test(type)){
            context = Object(context)
        }else{
            context = new context.constructor(context)
        }
    }
    let key = Symbol('key'),
        result;
    context[key] = this;
    // 处理参数和 call 有区别
    if (arg) {
        result = context[key](...arg)
    } else {
        result = context[key]()
    }
    delete context[key]
    return result
}

function fn(){
    console.log(this,arguments)
}
console.log("----------apply----------")
fn.apply(1,[1,2,3])
fn.apply(1,[])
fn.apply(1)
console.log("----------apply----------")

bind

// bind
Function.prototype.bind = function(context,...arg) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    let _this = this;
    context = context == undefined ? window : context;
    let type = typeof context;
    if (!/^(object|function)$/.test(type)) {
        if (/^(symbol|bigint)$/.test(type)) {
            context = Object(context);
        } else {
            context = new context.constructor(context);
        }
    }
    return function anonymous(...arg1){
        _this.call(context,...arg,...arg1)
    }
};

function fn(){
    console.log(this, arguments)
}

let fn1 = fn.bind(1,2,3,4);
console.log("----------bind----------")
fn1(5,6,7)
console.log("----------bind----------")

new

// new
// var person = new Person("a","b")
function myNew(fn,...arg) {
    // let obj = {};
    // obj.__proto__ = fn.prototype
    let obj = Object.create(fn.prototype)
    let result = fn.apply(obj,arg)
    return result instanceof Object ? result : obj;
}

Object.create

Object.create = function(obj) {
    function F(){}
    F.prototype = obj
    return new F()
}

instanceOf

function instance_of(L,R){
    L = L.__proto__;  // 隐式原型
    R = R.prototype;  // 显式原型
    while(true){
        if(L === null){
            return false
        }else if(L === R){
             return true
        }
        L = L.__proto__
    }
}
console.log(instance_of([],Array))
console.log(instance_of([],Object))

页面用了多少标签

var tags = [...document.getElementsByTagName("*")].map(el=>el.tagName);
console.log(new Set(tags))

获取绝对位置

function getPos(el){
    var pos = {
        x:0,
        y:0
    }
    while(el){
        pos.x += el.offsetLeft;
        pos.y += el.offsetTop;
        el = el.offsetParent
    }
    return pos
}

url参数获取

// http://localhost:1995/order?a=1&b=2&c=3
const search = window.location.search.substring(1);
const func1 = (search)=>{
	let obj = {}
    search.split("&").forEach(item=>{
    	const arg = item.split("=");
        obj[arg[0]] = arg[1]
    })
    return obj
}
const func2 = (search)=>{
	let obj = new URLSearchParams(`?${search}`)
    return obj
    // obj.get('a')
}

//URLSearchParams 兼容性不好需要polyfill
class URLSearchParams {
  constructor(searchName) {
    this._initName(searchName)
    this._init()
  }
  _initName(searchName) {
    if (typeof searchName === "string") {
      if (searchName, indexOf('?') === 0) {
        this._searchName = searchName.substring(1);
      } else {
        this._searchName = searchName;
      }
      return
    }
    if (Array.isArray(searchName)) {
      this._searchName = searchName.map(item => {
        return `${item[0]}=${item[1]}`
      }).join('&')
      return
    }
    if (typeof searchName === "object") {
      this._searchName = Object.keys(searchName).map(item => {
        return `${item}=${searchName[item]}`
      }).join('&')
    }
  }
  _init() {
    this._result = {}
    this._searchName.split("&").forEach(item => {
      const arg = item.split("=");
      this._result[arg[0]] = arg[1]
    })
  }
  get(key) {
    return this._result[key]
  }
  append(key, value) {
    this._result[key] = value
  }
  delete(key) {
    delete this._result[key]
  }
  has(key) {
    if (key in this._result) {
      return true
    }
    return false
  }
  keys() {
    return Object.keys(this._result)
  }
  values() {
    return Object.keys(this._result).map(item => {
      return this._result[item]
    })
  }
  toString() {
    return Object.keys(this._result).map(item => {
      return `${item}=${this._result[item]}`
    }).join('&')
  }
}

懒加载

// 实现懒加载
// <ul>
//   <li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
//   <li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
// </ul>

let imgs =  document.querySelectorAll('img')
// 可视区高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
/*function lazyLoad () {
  // 滚动卷去的高度
  let scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
  for (let i = 0; i < imgs.length; i ++) {
    // 图片在可视区冒出的高度
    let x = clientHeight + scrollTop - imgs[i].offsetTop
    // 图片在可视区内
    if (x > 0 && x < clientHeight+imgs[i].height) {
      imgs[i].src = imgs[i].getAttribute('data')
    }
  }
}*/
function lazyLoad () {
  imgs.forEach(item=>{
    if(item.getBoundingClientRect().top < clientHeight){
        imgs[i].src = imgs[i].getAttribute('data')
    }
  })
}
window.addEventListener('scroll', lazyLoad);

深拷贝

/*JSON.parse(JSON.stringify())
这种方式存在一定的问题:
正则 => 空对象
BigInt => 报错
日期 => 转换为字符串就转换不回来了
Symbol/undefined/function => 没了*/
// 递归拷贝
function deepCopy(source){
    if(typeof source !== "object" || source === null){
        return source
    }
    let target;
    if(source instanceof Array){
        target = []
    }else{
        target = {}
    }
    for(let attr in source){
        target[attr] = deepCopy(source[attr])
    }
    return target
}

function cloneDeep(source) {
  // 验证类型
  if (source === null) return null;
  if (typeof source !== "object") return source;
  if (source instanceof RegExp) return new RegExp(source);
  if (source instanceof Date) return new Date(source);
  // 对于对象和数组我们再进行循环克隆
  let target = new source.constructor();
  Object.keys(source).forEach(key => {
    target[key] = cloneDeep(source[key]);
  });
  return target;
}

let source = {
    a: 1,
    b: '1',
    c: true,
    d: null,
    e: undefined,
    f: [1,2,3],
    h: {a:1},
    i: function (){},
    j: /\d+/,
    k: new Date(),
    l: Symbol('key'),
    m: BigInt(10)
}
console.log("----------clone----------")
let result1 = deepCopy(source)
console.log(result1)
let result2 = cloneDeep(source)
console.log(result2)
delete source['m']
let result3 = JSON.parse(JSON.stringify(source))
console.log(result3)
console.log("----------clone----------")

函数节流

function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            console.log(this,11)
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

函数防抖

function debounce(func, wait) {
    console.log(this,11)
    let timeout;
    return function () {
        console.log(this,22)
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);

        timeout = setTimeout(() => {
            func.apply(context, args)
        }, wait);
    }
}

sleep延迟函数

function sleep(time){
	return new Promise((resolve, reject)=>{
    	setTimeout(resolve, time, 'success')
    })
}

实现一个自定义事件

class MyEvent {
    constructor() {
        this.handle = {};
    }
    addEvent(eventName, fn) {
        if (!(eventName in this.handle)) {
            this.handle[eventName] = [];
        }
        this.handle[eventName].push(fn);
    }
    trigger(eventName) {
        if (!this.handle[eventName]) {
            return;
        }
        this.handle[eventName].forEach(cb => {
            cb();
        })
    }
    removeEvent(eventName, fn) {
        if(!(eventName in this.handle)) {
            return;
        }
        var stack = this.handle[eventName]
        for (let i = 0, l = stack.length; i < l; i++) {
            if (stack[i] === fn) {
                stack.splice(i, 1);
                // return this.removeEvent(eventName, fn);
                break;
            }
        }
    }
}

几种排序算法的实现

// 冒泡排序
// N-1 N-2 N-3 N-4 ... 1  N*(N-1)/2 N^2/2-N/2 O(N^2)
Array.prototype.bubble = function bubble(){
    let _this = this;
    let len = _this.length;
    // 控制循环多少轮
    for(let i = 0; i < len-1; i++){
        // 控制每一轮循环多少次
        for(let j = 0; j < len-1-i; j++){
            if(_this[j] > _this[j+1]){
                let temp = _this[j];
                _this[j] = _this[j+1];
                _this[j+1] = temp;
            }
        }
    }
    return _this;
}
// 冒泡优化
Array.prototype.bubble1 = function bubble1(){
    let _this = this;
    let len = _this.length;
    let flag = false;
    // 控制循环多少轮
    for(let i = 0; i < len-1; i++){
        // 控制每一轮循环多少次
        for(let j = 0; j < len-1-i; j++){
            if(_this[j] > _this[j+1]){
                let temp = _this[j];
                _this[j] = _this[j+1];
                _this[j+1] = temp;
                flag = true;
            }
        }
        if(!flag) break;
        flag = false;  // 每一轮false
    }
    return _this;
}

// 选择
Array.prototype.select = function select() {
    for (let j = 0; j < this.length - 1; j++) {
        let min = j,
            temp = null;
        // 找到比当前项还小的这一项索引
        for (let i = min + 1; i < this.length; i++) {
            if (this[i] < this[min]) {
                min = i;
            }
        }
        // 让最小的项和当前首位交换位置
        swap(this,min,j);
    }
    return this;
};
let ary = [12, 8, 24, 16, 1];
ary.select();
console.log(ary);

// 插入 O(N^2)
Array.prototype.insert = function insert(){
    // 取一张牌放手里
    let _this = this;
    let len = _this.length;
    let handle = [_this[0]];
    // 开始抓牌
    for(let i = 1; i < len; i++){
        // A每一次新抓的牌
        let A = _this[i];
        for(let j = handle.length; j >= 0; j--){
            // B手里的这张牌
            let B = handle[j];
            if( A > B ){
                // 新抓的牌A比B要大,则放在B的后面
                handle.splice(j + 1, 0, A)
                break;  // 没必要和手里的牌继续比较了
            }
            // 都比到最开始,A都没有比任何的牌大,则A是最小的,插入到开始
            if( j === 0 ){
                handle.unshift(A)
            }
        }
    }
    return handle
}

// 快速 O(n*log2(n)) 类似二分法
Array.prototype.quick = function quick(){
    let _this = this;
    // 如果处理的数组只有一项或者空的,则无需处理了
    if(  _this.length <= 1) {
        return _this
    }
    // 获取中间项,并且把中间项在数组中删除
    let middleIndex = Math.floor(_this.length / 2);
    let middleValue = _this.splice(middleIndex, 1)[0];
    let len = _this.length;
    let leftArr = [];
    let rightArr = [];
    for(let i = 0; i < len; i++ ){
        _this[i] < middleValue ? leftArr.push(_this[i]) : rightArr.push(_this[i])
    }
    return quick.call(leftArr).concat(middleValue, quick.call(rightArr))
}
var arr = [7,3,9,5,1,4]
console.log(arr.quick())

实现一个栈结构

// 栈
class Stack {
    container = [];
    // 进栈
    enter(element) {
        this.container.unshift(element);
    }
    // 出栈
    leave() {
        return this.container.shift();
    }
    // 栈的长度
    size() {
        return this.container.length;
    }
    // 获取栈中的结果
    value() {
        return this.container;
    }
}
Number.prototype.decimal2binary = function decimal2binary() {
    let sk = new Stack,
        decimalNum = this.valueOf();
    if (decimalNum === 0) return '0';
    while (decimalNum > 0) {
        sk.enter(decimalNum % 2);
        decimalNum = Math.floor(decimalNum / 2);
    }
    return sk.value().join('');
};
console.log((10).decimal2binary());
console.log((10).toString(2));

实现一个队列结构

// 队列
class Queue {
    container = [];
    // 进入
    enter(element) {
        this.container.push(element);
    }
    // 离开
    leave() {
        return this.container.shift();
    }
    // 队列的长度
    size() {
        return this.container.length;
    }
    // 获取队列中的结果
    value() {
        return this.container;
    }
}
function game(n, m) {
    let qe = new Queue;
    for (let i = 0; i < n; i++) {
        qe.enter(i + 1);
    }

    while (qe.size() > 1) {
        for (let i = 0; i < m - 1; i++) {
            qe.enter(qe.leave());
        }
        qe.leave();
    }

    return qe.value().toString();
}
console.log(game(8, 5));

扁平化数组

let arr = [
    [1, 2, 2],
    [3, 4, 5, 5],
    [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];

/*方案1:使用 Array.prototype.flat 处理*/
arr = arr.flat(Infinity);

/*方案2:把数组直接变为字符串即可*/
arr = arr.toString().split(',').map(item => {
    return Number(item);
});

/*方案3:JSON.stringify*/
arr = JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(item => Number(item));

/*方案4:基于数组的some方法进行判断检测*/
while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr);
}

/*方案5:基于递归深度遍历*/
Array.prototype.myFlat = function myFlat() {
    let result = [];
    //=>循环数组中的每一项,把不是数组的存储到新数组中
    let fn = (arr) => {
        for (let i = 0; i < arr.length; i++) {
            let item = arr[i];
            if (Array.isArray(item)) {
                fn(item);
                continue;
            }
            result.push(item);
        }
    };
    fn(this);
    return result;
}

斐波那契数列

// [1,1,2,3,5,8,13,21,34,...]
// 经典题:爬楼梯,可以一步爬1层或者2层,请问要爬到45层有多少中方案
// 这一题的思路是:45层可以从43层和44层一步上来,那他就是43层和44层方案的总和,而44层可以从42层和43层一步上来,那他就是42层和43层方案的总和】
function fib(n){
  if(n==1 || n==2){
    return 1
  }
  return fib(n-1) + fib(n-2)
}
// 优化
function fib1(n){
  let arr = []
  return fibCache(arr, n)
  // 带临时存储的
}
function fibCache(arr, n){
  if(n==1 || n==2){
    return 1
  }
  if(arr[n]) return arr[n]
  arr[n] = fibCache(arr, n-1) + fibCache(arr, n-2)
  return arr[n]
}
function fib3(n){
  // 递归是从上到下
  // 动态规划,从下到上
  let dp = [1,1]
  for(let i=3;i<=n;i++){
    dp[i] = dp[i-1] + dp[i-2]
  }
  return dp[n]
}

今天就到这吧,后续想到的我会再更新上来!