前端面试手写题

1.深拷贝

概念:深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响

const deepCopy = obj => {
  let result;

  // 判断参数的数据类型是不是对象?
  if (typeof obj === 'object') {
    //参数是数组还是对象
    result = Array.isArray(obj) ? [] : {};

    //for in , i 是下标,不管是数组还是对象
    for (let i in obj) {
      //如果obj[i] 类型是object继续递归,不是就直接赋值
      result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
    }
    return result;
  } else {
    return obj;
  }
};

2.观察订阅者模式

概念:观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

class obsver {
  constructor() {
    //事件类型的集合
    this.events = {};
  }
  // 实现订阅
  on(type, callBack) {
    if (!this.events[type]) {
      // 没有这个事件类型,默认赋值一个数组格式的
      this.events[type] = [callBack];
    } else {
      this.events[type].push(callBack);
    }
  }
  // 触发事件
  emit(type, ...rest) {
    //对应的事件遍历执行
    this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
  }

  // 删除订阅
  off(type, callBack) {
    if (!this.events[type]) return;
    // 过滤掉callBack,重新赋值
    this.events[type] = this.events[type].filter(item => {
      return item !== callBack;
    });
  }
  // 只执行一次订阅事件
  once(type, callBack) {
    function fn() {
      // 立马执行完,就清除该事件
      callBack();
      this.off(type, fn);
    }
    //先订阅
    this.on(type, fn);
  }
}
// 使用如下
const newObsver = new obsver();

const handle = rest => {
  console.log(rest);
};

newObsver.on('click', handle);

newObsver.emit('click', 1);

newObsver.off('click', handle);

newObsver.emit('click', 1);

newObsver.once('dbClick', () => {
  console.log(123456);
});
newObsver.emit('dbClick');
newObsver.emit('dbClick');

3.手写reduce

概念:reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。对空数组是不会执行回调函数的

Array.prototype.reduce = function(fn, defaultValue) {
  //调用的数组
  const arr = this;

  //检验是不是空的数组
  if (Array.isArray(arr) && !arr.length) {
    throw Error('Reduce of empty array with no initial value');
    return;
  }

  // 累加器的初始值,有默认值就用默认值,没有用数组的第一项
  let result = defaultValue || arr[0];

  // 循环遍历数组,有默认值,从数组的第0项开始遍历,没有默认值,从数组的第1项开始遍历,0项作为累加器的初始值。
  for (let i = defaultValue ? 0 : 1; i < arr.length; i++) {
    result = fn(result, arr[i], i, arr);
  }
  // 每次返回累加器的值
  return result;
};

4.找出三个数组的相同元素

let a = ['1', '2', '3', '5'];
let b = ['2', '3', '5'];
let c = ['4', '5', '2'];

console.log(getCommon(a, b, c)); // ['2', '5']

function getCommon(arrA, arrB, ...others) {
  // 如果没有其他项,就直接返回两个数组的相同值
  if (others.length == 0) {
    return _two(arrA, arrB);
  } else {
    // 有其他参数,继续递归调用,两个数组的交集和其他数组接着比较
    return getCommon(_two(arrA, arrB), ...others);
  }

  // 两个数组取交集
  function _two(arr1, arr2) {
    return arr1.filter(el => arr2.includes(el));
  }
}

5.平铺的数据转树形结构

var list = [
  { id: 1, name: '司令', pid: 0 },
  { id: 2, name: '军长', pid: 1 },
  { id: 3, name: '副军长', pid: 1 },
  { id: 4, name: '旅长', pid: 3 },
  { id: 5, name: '团长', pid: 4 },
];

function arrToTree(list) {
  // 定义最终需要返回的树形结构数据
  let treeData = [];
  // 对传入的数组进行遍历
  list.forEach(item => {
    // 如果pid没有值或者为0,那就是顶级对象,直接添加进数组
    if (!item.pid) {
      treeData.push(item);
    }
    // 理解为二次遍历 :每个对象都找一遍自己的孩子添加到children
    let objList = list.filter(data => data.pid === item.id);
    if (objList.length) {
      item.children = objList;
    }
  });
  return treeData;
}

6.数组去重

const arr = [1, 1, 2, 2, 3, 4, 4];
//es6写法
[...new Set(arr)];
//es5的写法
function removeRrepeat(arr) {
  const result = [];

  arr.forEach(item => {
    //在 result 中没有找到arr 的每一项,就添加进去
    if (result.indexOf(item) === -1) {
      result.push(item);
    }
  });

  return result;
}

7.防抖

概念:不管触发多少次事件,都会等到事件触发 **n 秒后 **才会执行,如果在事件触发的 n 秒内 又触发了这个事件,那么就以新的事件的时间为准重新计算,总之,就是要等你触发事件 n 秒内 不再触发事件,我才会执行,一般用在用户表单输入实时搜索上

function debounce(fn, delay) {
    let timer = null;
    return function () {
      const that = this;
      const args = arguments;

      clearTimeout(timer);
      timer = settimeout(() => {
        fn.apply(that, args);
      }, delay);
    };
  }

8.节流

概念:顾名思义,可以减少一段时间内事件的触发频率。就是指连续触发事件但是在 n 秒中只执行一次函数。

  function throttle(fn, delay) {
    let timer = null;
    return function () {
      const that = this;
      const args = arguments;
      if (!timer) {
        timer = setTimeout(function () {
          fn.apply(that, args);
          timer = null;
        }, delay);
      }
    };
  }

9.new 操作符

function myNew(Fn, ...args) {
  //新生成一个对象
  const obj = {};
  //链接到原型
  obj.__proto__ = Fn.prototype;
  //绑定this
  const res = Fn.call(obj, ...args);
  // 若有返回值且是一个对象,返回执行的结果,否则返回新创建的对象
  return res instanceof Object ? res : obj;
}

10.冒泡排序

概念:依次比较相邻的两个值,如果后面的比前面的小,则将小的元素排到前面。依照这个规则进行多次并且递减的迭代,直到顺序正确

function bubbleSort(arr) {
  if (!Array.isArray(arr)) {
    throw Error('The argument to the function should be an array');
    return;
  }

  //比较轮数,最后一位不需要比较
  for (i = 0; i < arr.length - 1; i++) {
    //每轮比较次数,次数=长度-1-此时的轮数
    for (j = 0; j < arr.length - 1 - i; j++) {
      // 相邻元素两两对比,元素交换,大的元素交换到后面
      if (arr[j] > arr[j + 1]) {
        var temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
}

11.call

作用:改变this的指向,函数立即执行,参数是数据列表,也可以实现继承

function add(...res) {
  console.log('this.name', this.name, ...res);
}

var obj = {
  name: '小鱼',
};

Function.prototype.myCall = function(ctx, ...rest) {
  // ctx 是不是对象,不是就赋值为Window
  ctx = ctx instanceof Object ? ctx : window;
  // this就是调用的函数
  ctx.fn = this;
  //执行函数
  ctx.fn(...rest);
  // 删除添加的fn属性,保证不改变ctx
  delete ctx.fn;
};

add.myCall(1, 123);

12.bind

作用:改变this指向,参数是数据列形式,同call的传参。回返一个新的函数。

function add(...res) {
  console.log('this.name', this.name, ...res);
}

var obj = {
  name: '小鱼',
};

Function.prototype.myBind = function(ctx, ...arg1) {
  // 储蓄this,this是调用的myBind的函数
  const that = this;
  return function(...arg2) {
    //ctx 是不是对象,不是赋值window
    ctx = ctx instanceof Object ? ctx : window;
    ctx.fn = that;
    //执行函数,保证参数不丢失。参数也是数据列传入
    ctx.fn(...[...arg1, ...arg2]);
    // 删除ctx的fn属性,保证不改变自身
    delete ctx.fn;
  };
};

add.myBind(obj, 1)(2, 3);

13.Promise

概念:Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大

class MyPromise {
    constructor(executor) {
        // 定义三种状态 进行中、成功、失败
        this.status = 'pending';
        // 保存成功的值
        this.value = undefined;
        // 保存失败的原因
        this.reason = undefined;
        // 成功回调函数的集合
        this.onFulFnCallBack = [];
        // 失败回调函数的集合
        this.onFailFnCallBack = [];

        const resolve = value=>{
            // 如果在pending 的状态下,才能去修改状态的值,promise的状态是不可逆的
            if (this.status === 'pending') {
                this.status = 'fulfilled';
                this.value = value;
                // 循环遍历成功的回调函数
                this.onFulFnCallBack.forEach(fn=>fn(value));
            }
        }
        ;

        const reject = reason=>{
            // 如果在pending 的状态下,才能去修改状态的值,promise的状态是不可逆的
            if (this.status === 'pending') {
                this.status = 'rejected';
                this.reason = reason;
                // 循环遍历失败的回调函数
                this.onFailFnCallBack.forEach(fn=>fn(reason));
            }
        }
        ;

        // 捕获执行器的错误
        try {
            executor(resolve, reject);
        } catch (error) {
            // 有错误就改变状态为失败
            reject(error);
        }
    }

    then(onFulFn, onFailFn) {
        // promise2 解决的是promise的链式问题,需要return 出去
        const promise2 = new MyPromise((resolve,reject)=>{
            // 成功状态的时候
            if (this.status === 'fulfilled') {
                const that = this;
                // queueMicrotask 模拟微任务
                queueMicrotask(()=>{
                    try {
                        const onFailFnReturn = onFulFn(that.value);
                        that.resolvePromise(onFailFnReturn, resolve, reject, promise2);
                    } catch (error) {
                        reject(error);
                    }
                }
                );
            }
            if (this.status === 'rejected') {
                reject(this.reason);
            }
            // pending 状态的时候记录成功、失败的函数
            if (this.status === 'pending') {
                this.onFulFnCallBack.push(onFulFn);
                this.onFailFnCallBack.push(onFailFn);
            }
        }
        );
        return promise2;
    }
    resolvePromise(onFailFnReturn, resolve, reject, promise2) {
        // 解决循环调用的问题,不能是同一个promise,并且上面加入微任务,才能保证promise2有值
        if (promise2 === onFailFnReturn) {
            return reject(new TypeError('Chaining cycle detected for promise #<Promise>'), );
        }
        // 检测 then的返回值是不是promise
        if (onFailFnReturn instanceof MyPromise) {
            onFailFnReturn.then(resolve, reject);
        } else {
            // 检测 then的返回值不是promise,就直接捕获成功
            resolve(onFailFnReturn);
        }
    }
}

const p = new MyPromise((resolve,reject)=>{
    //   reject(500);
    setTimeout(()=>{
        resolve(200);
    }
    , 0);
}
);

p.then(res=>{
    console.log('res', res);
    return 600;
}
, error=>console.log('error', error), ).then(res=>console.log('res2', res));

14.Promse.all

概念:Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,要所有的Promsie都成功才回返成功,有一个是失败,就返回失败的那个Promsie的原因。

Promise.all = function(arr) {
  // 记录成功的值
  const result = [];
  // 计数
  let count = 0;

  // 返回promise
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      arr[i].then(res => {
        // 每一个promise 成功的返回值,按顺序存在result中
        result[i] = res;
        // 没执行了一个promise,计数器自增1
        count++;
        // 如果计数器和传入的Promise个数相等,说明所有promise都执行完了,直接成功捕获result就可以了。
        if (count === arr.length) {
          resolve(result);
        }
      }, reject);
    }
  });
};

const a1 = Promise.resolve(200);
const a2 = Promise.resolve(300);
const a3 = Promise.resolve(400);

Promise.all([a1, a2, a3])
  .then(res => {
    console.log('res', res);
  })
  .catch(error => console.log('error', error));

15.Promise.race

概念:Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

成功还是失败,决定于,那个Promise先执行,成功就是成功失败就是失败。

Promise.race = function(arr) {
  // 返回Promise
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      // 直接捕获成功或者失败,Promsie的状态是不可逆的
      arr[i].then(resolve, reject);
    }
  });
};
const a1 = Promise.resolve(200);
const a2 = Promise.resolve(300);
const a3 = Promise.resolve(400);

Promise.race([a1, a2, a3])
  .then(res => {
    console.log('res', res);
  })
  .catch(error => console.log('error', error));

16.URL 中的 Query 部分做拆解,返回一个 Key - value 形式的 Object。

const handleUrlQueryToParse = arg => {
  if (typeof arg !== 'string') return arg;

  const arr = arg.split('&');

  const result = {};

  arr.forEach(item => {
    const key = item.split('=')[0];
    const value = item.split('=')[1];

    if (key in result) {
      result[key] = [result[key], value];
    } else {
      result[key] = value;
    }
  });

  return result;
};

17.Object的参数拼接到一个已有的URL之上,返回拼接后的URL

const handleQueryToUrl = (url, query) => {
  if (!url) return '';

  if (query) {
    let queryArr = [];

    for (const key in query) {
      queryArr.push(`${key}=${query[key]}`);
    }

    if (url.includes('?')) {
      url = `${url}&${queryArr.join('&')}`;
    } else {
      url = `${url}?${queryArr.join('&')}`;
    }
  }

  return url;
};

18.判断一个字符串中出现次数最多的字符,输出出现次数最多的字符和出现次数

const str = "abcddddefffedddg";
const maxstringload = (str) => {
  var obj = {};
  for (var i = 0; i < str.length; i++) {
    var key = str[i];
    if (obj[key]) {
      obj[key]++;
    } else {
      obj[key] = 1;
    }
  }

  var maxCount = 0; 
  var maxString = ""; 
  for (var key in obj) {
    if (maxCount < obj[key]) {
      maxCount = obj[key]; 
      maxString = key;
    }
  }
  return "出现次数最多的字符:" + maxString + "出现了" + maxCount + "次";
};

19.编写一个函数 stringify,它的用途是把参数进行序列化

function getType(attr) {
  const type = Object.prototype.toString.call(attr);
  const newType = type.substr(8, type.length - 9);
  return newType;
}

const stringify = obj => {
  if (typeof obj !== 'object' || getType(obj) === null) {
    return String(obj);
  }
  const json = [];
  const isArr = obj ? getType(obj) === 'Array' : false;

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      let item = obj[key];
      if (getType(obj) === 'Object') {
        item = stringify(item);
      } else if (getType(obj) === 'Array') {
        item = stringify(item);
      }
      json.push((isArr ? '"' : '"' + key + '": "') + String(item) + '"');
    }
  }

  return (isArr ? '[' : '{') + String(json) + (isArr ? ']' : '}');
};

20.匈牙利命名字符串转换成下划线命名,将驼峰命名的字符串(例如personFirstName转成匈牙利命名字符串(例如:person_first_name)

const str2 = 'personFirstName';

const formatStr = str => {
  return str.replace(/[A-Z]/g, s => `_${s.toLocaleLowerCase()}`);
};

21. 给定一个编码字符,按编码规则进行解码,输出字符串。编码规则是count[letter],将letter的内容count次输出,count是0或正整数,letter是区分大小写的纯字母,支持嵌套形式

// 例子
//const s1 = '10[a]2[bc]'; decodeString(s); // 返回'aaaaaaaaaabcbc'
//const s2 = '2[3[a]2[bc]]'; decodeString(s); // 返回 'aaabcbcaaabcbc'

function decodeString(str) {
    let stack = []
    for (let i = 0; i < str.length; i++) {
        const cell = str[i]
        if (cell !== ']') {
            stack.push(cell) //进栈
        } else {
            let count = []
            let popCell = '' //循环个数
            let loopArr = []
            let loopStr = '' //结果
            while ((popCell = stack.pop()) !== '[') {
                loopArr.unshift(popCell)
            }
            // 循环输出count
            while(stack[stack.length -1] >= 0 && stack[stack.length -1] <= 9){
                count.unshift(stack.pop())
            }
            count = parseInt(count.join(''))
            for (let j = 0; j < count; j++) {
                loopStr += loopArr.join('')
            }
            stack.push(...(loopStr.split(''))) //转换结果入栈
        }
    }
    return stack.join('')
}

const s2 = '2[3[a]10[bc]]'; 
console.log(decodeString(s2))

22.找出两个数组不同的元素?

var arr1=[1,3,6,8,9,10,20]

var arr2=[11,32,61,58,9,20,10]


arr1.filter(item=>!arr2.includes(item)).concat(arr2.filter(item=>!arr1.includes(item))) // [1, 3, 6, 8, 11, 32, 61, 58]

23.调用接口,网络请求延时,怎么容错处理?

// fetchData()模拟接口在6秒后才返回数据。handleTimeout()模拟接口超时后的处理逻辑。
// 假定接口响应超过5秒(时间可以任意指定),就表示接口已经延时了。

const fetchData=()=>{
    const data=[1,2,3];
    // 模拟接口请求数据在6秒后返回。
    return new Promise((res,rej)=>{
        setTimeout(()=>{
            res(data)
        },6000)
    })
}


// 比如接口请求大于5秒,表示请求已经延时。直接返回错误信息。
const handleTimeout=(delay=5000)=>{
    return new Promise((res,rej)=>{
        setTimeout(()=>{
            rej('网络请求超时')
        },delay)
    })
}

// 使用到Promise.race的特性,谁先跑完,返回谁的值。显然fetchData跑完需要6秒才能拿到数据,
//  handleTimeout跑完指定是5秒跑完,所以Promise.race拿到的是handleTimeout返回的 '网络请求超时'

Promise.race([fetchData(),handleTimeout()]).then((res)=>console.log(res)).catch((error)=>console.log(error))

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 实现一个数组去重的函数 思路:使用对象来存储数组中的元素,遍历数组,若元素在对象中不存在,则存储到对象中,并将其推入新数组中。 2. 实现一个函数,判断一个字符串是否是回文字符串 思路:将字符串翻转,与原字符串比较是否相等。 3. 实现一个函数,可以将多维数组转化为一维数组 思路:使用递归来遍历多维数组,将每个元素推入新数组中,直到遍历完所有元素。 4. 实现一个函数,统计一个字符串中出现次数最多的字符 思路:使用对象来存储每个字符出现的次数,遍历字符串,将每个字符存储到对象中,找到出现次数最多的字符。 5. 实现一个函数,实现数组的冒泡排序 思路:使用双重循环遍历数组,比较相邻两个元素的大小,如果前者大于后者,则交换两个元素的位置,直到遍历完数组。 6. 实现一个函数,实现数组的快速排序 思路:选择数组中的一个元素作为基准点,将数组分为两个部分,一部分大于基准点,一部分小于基准点,递归处理两个部分。 7. 实现一个函数,实现数组的深拷贝 思路:使用递归遍历数组中的每个元素,判断元素类型,如果是对象或数组,则进行深拷贝,如果是基本类型,则直接复制。 8. 实现一个函数,实现函数的柯里化 思路:使用闭包保存参数,当参数个数达到预设值时,执行函数。 9. 实现一个函数,实现函数的节流 思路:使用定时器来控制函数执行的频率,每次函数执行时,清除定时器并重新设置一个定时器。 10. 实现一个函数,实现函数的防抖 思路:使用定时器来延迟函数执行,每次函数执行时,清除定时器并重新设置一个定时器。如果在定时器延迟时间内再次触发函数,则重新计时。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

见光就死123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值