面试常考的高频手写js题汇总

  • 算法题
  • 涉及js原理的题以及ajax请求
  • 业务场景题: 实现一个具有某种功能的组件
  • 其他(进阶,对计算机综合知识的考察,考的相对较少):实现订阅发布者模式;分别用面向对象编程,面向过程编程,函数式编程实现把大象放进冰箱等等

优先掌握

  • instanceof (考察对原型链的理解)
  • new (对创建对象实例过程的理解)
  • call&apply&bind (对this指向的理解)
  • 手写promise (对异步的理解)
  • 手写原生ajax (对ajax原理和http请求方式的理解,重点是get和post请求的实现)
  • 其他:数组,字符串的api的实现,难度相对较低。只要了解数组,字符串的常用方法的用法,现场就能写出来个大概。(ps:笔者认为数组的reduce方法比较难,这块有余力可以单独看一些,即使面试没让你实现reduce,写其他题时用上它也是很加分的)

1、手写instanceof

判断原型的

判断一个实例是否是其父类或者祖先类型的实例。

[] instanceof Array //=> true

法一: 通过Object.prototype.toString

javascript 复制代码
Object.prototype.myInstanceOf = function(type){
   let result = Object.prototype.toString.call(this)
   let reg = /\[object ([\s\S]{5})\]/
   return result.match(reg)[1] === type
}
let a =[]
a.myInstanceOf('Array') // true

法二:通过原型链查找 通过whild 循环 依次查找是不是和要检索的原型相同

javascript 复制代码
function instanceOf(target,type){
   while(target){
      if(Object.getPrototypeOf(target) === type.prototype){
         return true
      }
     target = Object.getPrototypeOf(target)
   }
   return false
}
let abc = []
instanceOf(abc,Array)

2、实现数组的map方法

明确: 数组map 有两个参数,第一个是回调函数(参数分别为item, index , array), 第二个参数可选是回调函数中的this对象,注意一定这个时候需要是普通函数,箭头函数这个值设置没有用,this为window

巩固一下for...in 和 for...of 区别

for...in 一般在for循环的时候代替复杂的for循环三段式写法,对象数组都可以用,对象是key 数组为索引, 千万注意for...in 会把当前变量原型链上的属性都检索出来,要搭配Object.hasOwnProperty使用, 类似使用Obejct.keys

for...of 是es6中出现的 for item of array 只能迭代数组或者具有迭代器的 不能用于对象。 item是value值。类似Object.values

javascript 复制代码
Array.prototype.myMap=function(fn, thisValue){
   let res = []
   thisValue = thisValue || window;
   let arr = this
   for(let i in arr){
     if(arr.hasOwnProperty(i)){
        res.push(fn.call(thisValue,arr[i],i,arr))
     }
   }
   return res
}
let aa = [1,2,3,4]
let b = aa.myMap(function(item, index ,array){
   console.log(item, index ,array,this)
  return item + 1
},{a:1})

3、reduce实现数组的map方法

reduce(( )=> , init ). reduce 第一个参数返回的值 第二个参数是每一项的值 , 函数三个参数分别是index索引 第四个参数是整个array数组

javascript 复制代码
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;
}
let aa = [1,2,3,4]
aa.myMap(function(item, index ,array){
   console.log(item, index ,array,this)
  return item + 1
},{a:1})

4、手写数组的reduce方法

reduce 工作: 需要两个参数, 第一个是回调函数(从初始项开始累加值, item , index , array),第二个是初始依赖项。 (累加器) 注意 回调函数的第一个可能是initValue或者undefined

javascript 复制代码
 Array.prototype.myReduce = function (cb, initValue) {
   let _arr = this;
   let num = initValue == undefined ? _arr[0] : initValue;
   let i = initValue == undefined ? 1 : 0;
   for (i; i < _arr.length; i++) {
     num = cb(num, _arr[i], i, _arr);
   }
   return num;
  };
let arr = [1, 2, 3, 4];
    arr.myReduce(function (num, item, index, arr) {
    return num + item;
});

5、数组扁平化

javascript 复制代码
// 法一利用递归 push/cancat
Array.prototype.myFlat = function () {
  let _arr = this; 
  let array = [];
  for (let item of _arr) {
    if (Array.isArray(item)) {
      array.push(...item.myFlat(count));
      // array = array.concat(flatten(arr[i])); //concat 并不会改变原数组
    } else {
      array.push(item);
    }
  }
  return array;
};
let aa = [1, 2, [3, 4, [5]]];
console.log(aa.myFlat());
// 法二利用递归
let a = [1,[2,3,[4,[5]]]]; 
a.flat(Infinity); // [1,2,3,4,5]  a是4维数组

6、函数柯里化

柯里化定义,当传入足够多参数的时候, 执行原函数。 也就是满足参数与原函数的参数数量了就执行

javascript 复制代码
function curry(fn, len = fn.length, ...args) {
  let _this = this;
  return function (...params) {
    let _args = [...args, ...params];
    if (_args.length >= len) {
      fn.call(_this, ..._args);
    } else {
      return curry.call(_this, fn, len, ..._args);
    }
  };
}
// 验证
let _fn = curry(function test(a, b, c, d, e, f) {
  console.log(a, b, c, d, e, f);
});
_fn(1,2,3,4)(5)(6)

lodash 也提供了 curry 方法

7、实现深浅拷贝

浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用

深拷贝:拷贝多层,每一级别的数据都会拷贝。这样更改拷贝值就不影响另外的对象

实现浅拷贝:[...] , Object.assign 数组返回新数组的方法比如slice

实现深拷贝JSON.parse(JSON.stringfy(data))

遍历实现深拷贝

javascript 复制代码
function deepClone(newObj,oldObj){
   for(let k in oldObj){
      let item = oldObj[k]
      if(Array.isArray(item)){
          newObj[k] = [];
          deepClone(newObj[k],item)
      }else if(item instanceof Object){
          newObj[k] = {};
          deepClone(newObj[k],item)
      }else{
          newObj[k] = item;
      }
   }
}

8、手写call、apply、bind

9、手动实现new 操作 new一个对象 (继承构造函数的属性和方法)

  • 创建一个空对象 Object.create(null)
  • 将对象指针隐式指向构造函数的prototype (原型继承)
  • 使用call改变this指向
  • 判断构造函数执行返回的是不是对象,不是对象返回{},是返回新的值
javascript 复制代码
 function create() {
   let args = [...arguments];
   let fn = args.shift();
   // 创造一个空对象,隐式原型指向构造函数的原型(继承构造函数原型伤的属性方法)
   let obj = Object.create(fn.prototype);
   // 调用构造函数,绑定this为新对象
   fn.apply(obj, args);
   return typeof obj === "object" ? obj : {};
 }

function test(name, age) {
  this.name = name;
  this.age = age;
}

let t = create(test, "lisi", 123);
console.log(t); // {name:'lisi', age:'124'}

10、手写Promise (常见promise.all, promise.race)

javascript 复制代码
// Promise/A+ 规范规定的三种状态
const STATUS = {
 PENDING: 'pending',
 FULFILLED: 'fulfilled',
 REJECTED: 'rejected'
}

class MyPromise {
 // 构造函数接收一个执行回调
 constructor(executor) {
     this._status = STATUS.PENDING // Promise初始状态
     this._value = undefined // then回调的值
     this._resolveQueue = [] // resolve时触发的成功队列
     this._rejectQueue = [] // reject时触发的失败队列
    
 // 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this)
 const resolve = value => {
     const run = () => {
         // Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled
         if (this._status === STATUS.PENDING) {
             this._status = STATUS.FULFILLED // 更改状态
             this._value = value // 储存当前值,用于then回调
            
             // 执行resolve回调
             while (this._resolveQueue.length) {
                 const callback = this._resolveQueue.shift()
                 callback(value)
             }
         }
     }
     //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务)
     setTimeout(run)
 }

 // 同 resolve
 const reject = value => {
     const run = () => {
         if (this._status === STATUS.PENDING) {
         this._status = STATUS.REJECTED
         this._value = value
        
         while (this._rejectQueue.length) {
             const callback = this._rejectQueue.shift()
             callback(value)
         }
     }
 }
     setTimeout(run)
 }

     // new Promise()时立即执行executor,并传入resolve和reject
     executor(resolve, reject)
 }

 // then方法,接收一个成功的回调和一个失败的回调
 function then(onFulfilled, onRejected) {
  // 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行
  typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
  typeof onRejected !== 'function' ? onRejected = error => error : null

  // then 返回一个新的promise
  return new MyPromise((resolve, reject) => {
    const resolveFn = value => {
      try {
        const x = onFulfilled(value)
        // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }
  }
}

  const rejectFn = error => {
      try {
        const x = onRejected(error)
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }

    switch (this._status) {
      case STATUS.PENDING:
        this._resolveQueue.push(resolveFn)
        this._rejectQueue.push(rejectFn)
        break;
      case STATUS.FULFILLED:
        resolveFn(this._value)
        break;
      case STATUS.REJECTED:
        rejectFn(this._value)
        break;
    }
 })
 }
 catch (rejectFn) {
  return this.then(undefined, rejectFn)
}
// promise.finally方法
finally(callback) {
  return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
    MyPromise.resolve(callback()).then(() => error)
  })
}

 // 静态resolve方法
 static resolve(value) {
      return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  }

 // 静态reject方法
 static reject(error) {
      return new MyPromise((resolve, reject) => reject(error))
    }

 // 静态all方法
 static all(promiseArr) {
      let count = 0
      let result = []
      return new MyPromise((resolve, reject) =>       {
        if (!promiseArr.length) {
          return resolve(result)
        }
        promiseArr.forEach((p, i) => {
          MyPromise.resolve(p).then(value => {
            count++
            result[i] = value
            if (count === promiseArr.length) {
              resolve(result)
            }
          }, error => {
            reject(error)
          })
        })
      })
    }

 // 静态race方法
 static race(promiseArr) {
      return new MyPromise((resolve, reject) => {
        promiseArr.forEach(p => {
          MyPromise.resolve(p).then(value => {
            resolve(value)
          }, error => {
            reject(error)
          })
        })
      })
    }
}

11、手写原生AJAX

  1. 创建 XMLHttpRequest 实例
  2. 发出 HTTP 请求
  3. 服务器返回 XML 格式的字符串
  4. JS 解析 XML,并更新局部页面不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON
javascript 复制代码
// 1.0
function ajax() {
  let xhr = new XMLHttpRequest() //实例化,以调用方法
  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步
  xhr.onreadystatechange = () => {  //每当 readyState 属性改变时,就会调用该函数。
    if (xhr.readyState === 4) {  //XMLHttpRequest 代理当前所处状态。
      if (xhr.status >= 200 && xhr.status < 300) {  //200-300请求成功
        let string = request.responseText
        //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
        let object = JSON.parse(string)
      }
    }
  }
  request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}

// promise 版本
function ajax(url) {
  const p = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.onreadystatechange = () => {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve(JSON.parse(xhr.responseText))
        } else {
          reject('请求出错')
        }
      }
    }
    xhr.send()  //发送hppt请求
  })
  return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))

12、手写防抖节流函数

plain 复制代码
函数节流与函数防抖都是为了限制函数的执行频次,是一种性能优化的方案,比如应用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输入、自动完成的keyup事件。

防抖: n秒内只触发一次,多次执行清掉上次的定时器重新开始定时器

节流:连续触发事件但是在 n 秒中只执行一次函数

javascript 复制代码
 // 防抖
function debounce(fn, delay) {
  if (typeof fn !== "function") {
    throw new TypeError("fn不是函数");
  }
  let timer; // 维护一个 timer
  return function () {
    var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
    var args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
    }, delay);
  };
}
let app = document.querySelector("#app");
app.addEventListener(
  "keyup",
  debounce(() => console.log(11), 2000)
);
// 节流
function throttle(fn, delay) {
  let timer = null;
  return function () {
    var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
    var args = arguments;
    if (timer) return;
    timer = setTimeout(function () {
      fn.apply(_this, args);
      timer = null;
    }, delay);
  };
}
let app = document.querySelector("#app");
app.addEventListener(
  "keyup",
  throttle(() => console.log(11), 9000)
);

13、手写Promise加载图片

javascript 复制代码
function promiseImg(src){
   return new Promise((resolve,rejected)=>{
      $.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、实现一秒打印一个数

javascript 复制代码
function log(){
  let i=0
   window.setInterval(function(){
      console.log(i++)
   },1000)
}
log()

15、创建10个标签,点击弹出相应序号

javascript 复制代码
 function createLabel(el) {
   let a = ''
   for (let i = 0; i <= 10; i++) {
     a += `<a onclick="alert(${i})">${i}-</a><br/>`
   }
   document.querySelector(el).innerHTML=a
 }
createLabel("#app");

16、 手写一个睡眠函数 sleep

js 复制代码
function sleep(milliseconds) {
  const start = performance.now();
  while (performance.now() - start < milliseconds) {
    // 这里是空循环,直到达到指定的延迟时间为止
  }
}

// 使用示例
console.log("开始");
sleep(3000); // 等待 3 秒
console.log("结束");

17、 数据结构题

将下面的数据结构进行

js 复制代码
const o = {
  a: 1,
  b: [1, { c: true }, [3]],
  d: { e: 2, f: 3 },
  g: null,
};

把上面的数据结构改成下面的结构

js 复制代码
result = {
     a:1,
    'b[0]':1,
    'b[1].c': true,     
    'b[2][0]': 3,
    'd.e':2,
    'd.f': 3
}

解题思路主要是递归, 循环遍历去处理 key , key 处理好之后, 在实现就很简单了

js 复制代码
  let result = {}
  const trans = (o, keylink = '')=> {
     if(typeof o === 'object'){
        // 对象进入
        if(Array.isArray(o)){
          // 处理数组
          o.map((el,index)=>{
              trans(el, keylink + '[' + index + ']')
          })
        }else if(o !== null){
          // 处理对象
          Object.entries(o).map(([k, v])=>{
               trans(v, keylink + (keylink ? '.': '') + k)
          })
        }
     }else{
         console.log(o, keylink) // 打印值为非对象和key的拼接值, 也就是最终要的结果
         // 在这里进行收集
         result[keylink] = o
     }
     return result
  }

持续更新。。。

相关推荐
吕彬-前端41 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱44 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb