面试常考的高频手写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 } ``` # 持续更新。。。

相关推荐
水银嘻嘻几秒前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
小嘟嚷ovo23 分钟前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i1 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观1 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter
周胡杰1 小时前
组件导航 (HMRouter)+flutter项目搭建-混合开发+分栏效果
前端·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
敲代码的小吉米1 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
是千千千熠啊1 小时前
vue使用Fabric和pdfjs完成合同签章及批注
前端·vue.js
九月TTS2 小时前
TTS-Web-Vue系列:组件逻辑分离与模块化重构
前端·vue.js·重构
我是大头鸟2 小时前
SpringMVC 内容协商处理
前端
Humbunklung2 小时前
Visual Studio 2022 中添加“高级保存选项”及解决编码问题
前端·c++·webview·visual studio