写点什么

最近面试经常被问到的 js 手写题

  • 2022-11-08
    浙江
  • 本文字数:6169 字

    阅读完需:约 20 分钟

实现 apply 方法

思路: 利用this的上下文特性。apply其实就是改一下参数的问题


Function.prototype.myApply = function(context = window, args) {  // this-->func  context--> obj  args--> 传递过来的参数
// 在context上加一个唯一值不影响context上的属性 let key = Symbol('key') context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法 // let args = [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组
let result = context[key](...args); // 这里和call传参不一样
// 清除定义的this 不删除会导致context属性越来越多 delete context[key];
// 返回结果 return result;}
复制代码


// 使用function f(a,b){ console.log(a,b) console.log(this.name)}let obj={ name:'张三'}f.myApply(obj,[1,2])  //arguments[1]
复制代码

实现防抖函数(debounce)

防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算



防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行


eg. 像百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。


手写简化版:


// func是用户传入需要防抖的函数// wait是等待时间const debounce = (func, wait = 50) => {  // 缓存一个定时器id  let timer = 0  // 这里返回的函数是每次用户实际调用的防抖函数  // 如果已经设定过定时器了就清空上一次的定时器  // 开始一个新的定时器,延迟执行用户传入的方法  return function(...args) {    if (timer) clearTimeout(timer)    timer = setTimeout(() => {      func.apply(this, args)    }, wait)  }}
复制代码


适用场景:


  • 文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好

  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次

  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

实现每隔一秒打印 1,2,3,4

// 使用闭包实现for (var i = 0; i < 5; i++) {  (function(i) {    setTimeout(function() {      console.log(i);    }, i * 1000);  })(i);}// 使用 let 块级作用域for (let i = 0; i < 5; i++) {  setTimeout(function() {    console.log(i);  }, i * 1000);}
复制代码

查找字符串中出现最多的字符和个数

例: abbcccddddd -> 字符最多的是 d,出现了 5 次


let str = "abcabcabcbbccccc";let num = 0;let char = '';
// 使其按照一定的次序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"
// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; }});console.log(`字符最多的是${char},出现了${num}次`);

复制代码


参考:前端手写面试题详细解答

使用 reduce 求和

arr = [1,2,3,4,5,6,7,8,9,10],求和


let arr = [1,2,3,4,5,6,7,8,9,10]arr.reduce((prev, cur) => { return prev + cur }, 0)
复制代码


arr = [1,2,3,[[4,5],6],7,8,9],求和


let arr = [1,2,3,4,5,6,7,8,9,10]arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)
复制代码


arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和


let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] 
arr.reduce((prev, cur) => { return prev + cur["a"];}, 0)
复制代码

实现字符串翻转

在字符串的原型链上添加一个方法,实现字符串翻转:


String.prototype._reverse = function(a){    return a.split("").reverse().join("");}var obj = new String();var res = obj._reverse ('hello');console.log(res);    // olleh
复制代码


需要注意的是,必须通过实例化对象之后再去调用定义的方法,不然找不到该方法。

instanceof

instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。


const myInstanceof = (left, right) => {  // 基本数据类型都返回false  if (typeof left !== 'object' || left === null) return false;  let proto = Object.getPrototypeOf(left);  while (true) {    if (proto === null) return false;    if (proto === right.prototype) return true;    proto = Object.getPrototypeOf(proto);  }}
复制代码

Array.prototype.forEach()

Array.prototype.forEach = function(callback, thisArg) {  if (this == null) {    throw new TypeError('this is null or not defined');  }  if (typeof callback !== "function") {    throw new TypeError(callback + ' is not a function');  }  const O = Object(this);  const len = O.length >>> 0;  let k = 0;  while (k < len) {    if (k in O) {      callback.call(thisArg, O[k], k, O);    }    k++;  }}
复制代码

实现一个 call

call 做了什么:


  • 将函数设为对象的属性

  • 执行 &删除这个函数

  • 指定 this 到函数并传入给定参数执行函数

  • 如果不传入参数,默认指向为 window


// 模拟 call bar.mycall(null);//实现一个call方法:Function.prototype.myCall = function(context) {  //此处没有考虑context非object情况  context.fn = this;  let args = [];  for (let i = 1, len = arguments.length; i < len; i++) {    args.push(arguments[i]);  }  context.fn(...args);  let result = context.fn(...args);  delete context.fn;  return result;};
复制代码

实现类数组转化为数组

类数组转换为数组的方法有这样几种:


  • 通过 call 调用数组的 slice 方法来实现转换


Array.prototype.slice.call(arrayLike);
复制代码


  • 通过 call 调用数组的 splice 方法来实现转换


Array.prototype.splice.call(arrayLike, 0);
复制代码


  • 通过 apply 调用数组的 concat 方法来实现转换


Array.prototype.concat.apply([], arrayLike);
复制代码


  • 通过 Array.from 方法来实现转换


Array.from(arrayLike);
复制代码

实现数组去重

给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。


ES6 方法(使用数据结构集合):


const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
复制代码


ES5 方法:使用 map 存储不重复的数字


const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) { let map = {}; let res = []; for(var i = 0; i < array.length; i++) { if(!map.hasOwnProperty([array[i]])) { map[array[i]] = 1; res.push(array[i]); } } return res;}
复制代码

手写 Promise

const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";
function MyPromise(fn) { // 保存初始化状态 var self = this;
// 初始化状态 this.state = PENDING;
// 用于保存 resolve 或者 rejected 传入的值 this.value = null;
// 用于保存 resolve 的回调函数 this.resolvedCallbacks = [];
// 用于保存 reject 的回调函数 this.rejectedCallbacks = [];
// 状态转变为 resolved 方法 function resolve(value) { // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变 if (value instanceof MyPromise) { return value.then(resolve, reject); }
// 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变, if (self.state === PENDING) { // 修改状态 self.state = RESOLVED;
// 设置传入的值 self.value = value;
// 执行回调函数 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); }
// 状态转变为 rejected 方法 function reject(value) { // 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变 if (self.state === PENDING) { // 修改状态 self.state = REJECTED;
// 设置传入的值 self.value = value;
// 执行回调函数 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); }
// 将两个方法传入函数执行 try { fn(resolve, reject); } catch (e) { // 遇到错误时,捕获错误,执行 reject 函数 reject(e); }}
MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; };
onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; };
// 如果是等待状态,则将函数加入对应列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); }
// 如果状态已经凝固,则直接执行对应状态的函数
if (this.state === RESOLVED) { onResolved(this.value); }
if (this.state === REJECTED) { onRejected(this.value); }};
复制代码

实现防抖函数(debounce)

防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。


那么与节流函数的区别直接看这个动画实现即可。


手写简化版:


// 防抖函数const debounce = (fn, delay) => {  let timer = null;  return (...args) => {    clearTimeout(timer);    timer = setTimeout(() => {      fn.apply(this, args);    }, delay);  };};
复制代码


适用场景:


  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次

  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似


生存环境请用 lodash.debounce

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {  if (this == undefined) {    throw new TypeError('this is null or not defined');  }  if (typeof callback !== 'function') {    throw new TypeError(callback + ' is not a function');  }  const res = [];  // 同理  const O = Object(this);  const len = O.length >>> 0;  for (let i = 0; i < len; i++) {    if (i in O) {      // 调用回调函数并传入新数组      res[i] = callback.call(thisArg, O[i], i, this);    }  }  return res;}
复制代码

手写 bind 函数

bind 函数的实现步骤:


  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  2. 保存当前函数的引用,获取其余传入参数值。

  3. 创建一个函数返回

  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。


// bind 函数实现Function.prototype.myBind = function(context) {  // 判断调用对象是否为函数  if (typeof this !== "function") {    throw new TypeError("Error");  }  // 获取参数  var args = [...arguments].slice(1),      fn = this;  return function Fn() {    // 根据调用方式,传入不同绑定值    return fn.apply(      this instanceof Fn ? this : context,      args.concat(...arguments)    );  };};
复制代码

手写类型判断函数

function getType(value) {  // 判断数据是 null 的情况  if (value === null) {    return value + "";  }  // 判断数据是引用类型的情况  if (typeof value === "object") {    let valueClass = Object.prototype.toString.call(value),      type = valueClass.split(" ")[1].split("");    type.pop();    return type.join("").toLowerCase();  } else {    // 判断数据是基本数据类型的情况和函数的情况    return typeof value;  }}
复制代码

验证是否是邮箱

function isEmail(email) {    var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;    return regx.test(email);}

复制代码

实现一个函数判断数据类型

function getType(obj) {   if (obj === null) return String(obj);   return typeof obj === 'object'    ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase()   : typeof obj;}
// 调用getType(null); // -> nullgetType(undefined); // -> undefinedgetType({}); // -> objectgetType([]); // -> arraygetType(123); // -> numbergetType(true); // -> booleangetType('123'); // -> stringgetType(/123/); // -> regexpgetType(new Date()); // -> date
复制代码

实现 (5).add(3).minus(2) 功能

例: 5 + 3 - 2,结果为 6


Number.prototype.add = function(n) {  return this.valueOf() + n;};Number.prototype.minus = function(n) {  return this.valueOf() - n;};
复制代码


实现 add(1)(2) =3


// 题意的答案const add = (num1) => (num2)=> num2 + num1;
// 整了一个加强版 可以无限链式调用 add(1)(2)(3)(4)(5)....function add(x) { // 存储和 let sum = x;
// 函数调用会相加,然后每次都会返回这个函数本身 let tmp = function (y) { sum = sum + y; return tmp; };
// 对象的toString必须是一个方法 在方法中返回了这个和 tmp.toString = () => sum return tmp;}
alert(add(1)(2)(3)(4)(5))
复制代码


无限链式调用实现的关键在于 对象的 toString 方法 : 每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。


也就是我在调用很多次后,他们的结果会存在add函数中的sum变量上,当我alert的时候 add会自动调用 toString方法 打印出 sum, 也就是最终的结果

实现一个队列

基于链表结构实现队列


const LinkedList = require('./实现一个链表结构')
// 用链表默认使用数组来模拟队列,性能更佳class Queue { constructor() { this.ll = new LinkedList() } // 向队列中添加 offer(elem) { this.ll.add(elem) } // 查看第一个 peek() { return this.ll.get(0) } // 队列只能从头部删除 remove() { return this.ll.remove(0) }}
var queue = new Queue()
queue.offer(1)queue.offer(2)queue.offer(3)var removeVal = queue.remove(3)
console.log(queue.ll,'queue.ll')console.log(removeVal,'queue.remove')console.log(queue.peek(),'queue.peek')
复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
最近面试经常被问到的js手写题_JavaScript_helloworld1024fd_InfoQ写作社区