面试常考的高频手写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**](https://link.juejin.cn?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fwww.json.org%2Fjson-zh.html "https://link.zhihu.com/?target=https%3A//www.json.org/json-zh.html")**。** ```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 += `${i}-
` } 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 } ``` # 持续更新。。。

相关推荐
LYFlied1 天前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
Bigger1 天前
Tauri(21)——窗口缩放后的”失焦惊魂”,游戏控制权丢失了
前端·macos·app
Bigger1 天前
Tauri (20)——为什么 NSPanel 窗口不能用官方 API 全屏?
前端·macos·app
bug总结1 天前
前端开发中为什么要使用 URL().origin 提取接口根地址
开发语言·前端·javascript·vue.js·html
一招定胜负1 天前
网络爬虫(第三部)
前端·javascript·爬虫
Shaneyxs1 天前
从 0 到 1 实现CloudBase云开发 + 低代码全栈开发活动管理小程序(13)
前端
半山烟雨半山青1 天前
微信内容emoji表情包编辑器 + vue3 + ts + WrchatEmogi Editor
前端·javascript·vue.js
码途潇潇1 天前
Vue 事件机制全面解析:原生事件、自定义事件与 DOM 冒泡完全讲透
前端·javascript
zmirror1 天前
Monorepo 在 Docker 中的构建方案方案
前端
用户47949283569151 天前
node_modules 太胖?用 Node.js 原生功能给依赖做一次大扫除
前端·后端·node.js