乐趣区

关于javascript:最近面试经常被问到的js手写题

实现 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_\-])[email protected]([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); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(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')
退出移动版