写点什么

20 道常被问到的 JavaScript 题目

  • 2022 年 9 月 26 日
    浙江
  • 本文字数:7264 字

    阅读完需:约 24 分钟

字符串最长的不重复子串

题目描述


给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = "abcabcbb"输出: 3解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"输出: 1解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"输出: 3解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
输入: s = ""输出: 0
复制代码


答案


const lengthOfLongestSubstring = function (s) {  if (s.length === 0) {    return 0;  }
let left = 0; let right = 1; let max = 0; while (right <= s.length) { let lr = s.slice(left, right); const index = lr.indexOf(s[right]);
if (index > -1) { left = index + left + 1; } else { lr = s.slice(left, right + 1); max = Math.max(max, lr.length); } right++; } return max;};
复制代码

图片懒加载

可以给 img 标签统一自定义属性data-src='default.png',当检测到图片出现在窗口之后再补充 src 属性,此时才会进行图片资源加载。


function lazyload() {  const imgs = document.getElementsByTagName('img');  const len = imgs.length;  // 视口的高度  const viewHeight = document.documentElement.clientHeight;  // 滚动条高度  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;  for (let i = 0; i < len; i++) {    const offsetHeight = imgs[i].offsetTop;    if (offsetHeight < viewHeight + scrollHeight) {      const src = imgs[i].dataset.src;      imgs[i].src = src;    }  }}
// 可以使用节流优化一下window.addEventListener('scroll', lazyload);
复制代码


前端手写面试题详细解答

实现 JSON.parse

var json = '{"name":"cxk", "age":25}';var obj = eval("(" + json + ")");
复制代码


此方法属于黑魔法,极易容易被 xss 攻击,还有一种new Function大同小异。

实现数组的 filter 方法

Array.prototype._filter = function(fn) {    if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        fn(this[i]) && res.push(this[i]);    }    return res;}
复制代码

手写 Promise.race

该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.


Promise.race = function (args) {  return new Promise((resolve, reject) => {    for (let i = 0, len = args.length; i < len; i++) {      args[i].then(resolve, reject)    }  })}
复制代码

实现类数组转化为数组

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


  • 通过 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);
复制代码

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = {  name: '姓名',  age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
复制代码


function render(template, data) {  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则  if (reg.test(template)) { // 判断模板里是否有模板字符串    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染    return render(template, data); // 递归的渲染并返回渲染后的结构  }  return template; // 如果模板没有模板字符串直接返回}
复制代码

判断对象是否存在循环引用

循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.


下面方法可以用来判断一个对象中是否已存在循环引用:


const isCycleObject = (obj,parent) => {    const parentArr = parent || [obj];    for(let i in obj) {        if(typeof obj[i] === 'object') {            let flag = false;            parentArr.forEach((pObj) => {                if(pObj === obj[i]){                    flag = true;                }            })            if(flag) return true;            flag = isCycleObject(obj[i],[...parentArr,obj[i]]);            if(flag) return true;        }    }    return false;}

const a = 1;const b = {a};const c = {b};const o = {d:{a:3},c}o.c.b.aa = a;
console.log(isCycleObject(o)
复制代码


查找有序二维数组的目标值:


var findNumberIn2DArray = function(matrix, target) {    if (matrix == null || matrix.length == 0) {        return false;    }    let row = 0;    let column = matrix[0].length - 1;    while (row < matrix.length && column >= 0) {        if (matrix[row][column] == target) {            return true;        } else if (matrix[row][column] > target) {            column--;        } else {            row++;        }    }    return false;};
复制代码


二维数组斜向打印:


function printMatrix(arr){  let m = arr.length, n = arr[0].length    let res = []
// 左上角,从0 到 n - 1 列进行打印 for (let k = 0; k < n; k++) { for (let i = 0, j = k; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } }
// 右下角,从1 到 n - 1 行进行打印 for (let k = 1; k < m; k++) { for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } return res}
复制代码

深克隆(deepclone)

简单版:


const newObj = JSON.parse(JSON.stringify(oldObj));
复制代码


局限性:


  1. 他无法实现对函数 、RegExp 等特殊对象的克隆

  2. 会抛弃对象的 constructor,所有的构造函数会指向 Object

  3. 对象有循环引用,会报错


面试版:


/** * deep clone * @param  {[type]} parent object 需要进行克隆的对象 * @return {[type]}        深克隆后的对象 */const clone = parent => {  // 判断类型  const isType = (obj, type) => {    if (typeof obj !== "object") return false;    const typeString = Object.prototype.toString.call(obj);    let flag;    switch (type) {      case "Array":        flag = typeString === "[object Array]";        break;      case "Date":        flag = typeString === "[object Date]";        break;      case "RegExp":        flag = typeString === "[object RegExp]";        break;      default:        flag = false;    }    return flag;  };
// 处理正则 const getRegExp = re => { var flags = ""; if (re.global) flags += "g"; if (re.ignoreCase) flags += "i"; if (re.multiline) flags += "m"; return flags; }; // 维护两个储存循环引用的数组 const parents = []; const children = [];
const _clone = parent => { if (parent === null) return null; if (typeof parent !== "object") return parent;
let child, proto;
if (isType(parent, "Array")) { // 对数组做特殊处理 child = []; } else if (isType(parent, "RegExp")) { // 对正则对象做特殊处理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, "Date")) { // 对Date对象做特殊处理 child = new Date(parent.getTime()); } else { // 处理对象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切断原型链 child = Object.create(proto); }
// 处理循环引用 const index = parents.indexOf(parent);
if (index != -1) { // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象 return children[index]; } parents.push(parent); children.push(child);
for (let i in parent) { // 递归 child[i] = _clone(parent[i]); }
return child; }; return _clone(parent);};
复制代码


局限性:


  1. 一些特殊情况没有处理: 例如 Buffer 对象、Promise、Set、Map

  2. 另外对于确保没有循环引用的对象,我们可以省去对循环引用的特殊处理,因为这很消耗时间


原理详解实现深克隆

字符串解析问题

var a = {    b: 123,    c: '456',    e: '789',}var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;// => 'a123aa456aa {a.d}aaaa'
复制代码


实现函数使得将 str 字符串中的{}内的变量替换,如果属性不存在保持原样(比如{a.d}


类似于模版字符串,但有一点出入,实际上原理大差不差


const fn1 = (str, obj) => {    let res = '';    // 标志位,标志前面是否有{    let flag = false;    let start;    for (let i = 0; i < str.length; i++) {        if (str[i] === '{') {            flag = true;            start = i + 1;            continue;        }        if (!flag) res += str[i];        else {            if (str[i] === '}') {                flag = false;                res += match(str.slice(start, i), obj);            }        }    }    return res;}// 对象匹配操作const match = (str, obj) => {    const keys = str.split('.').slice(1);    let index = 0;    let o = obj;    while (index < keys.length) {        const key = keys[index];        if (!o[key]) {            return `{${str}}`;        } else {            o = o[key];        }        index++;    }    return o;}
复制代码

模拟 Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。


// 模拟 Object.create
function create(proto) { function F() {} F.prototype = proto;
return new F();}
复制代码

判断是否是电话号码

function isPhone(tel) {    var regx = /^1[34578]\d{9}$/;    return regx.test(tel);}
复制代码

转化为驼峰命名

var s1 = "get-element-by-id"
// 转化为 getElementById
复制代码


var f = function(s) {    return s.replace(/-\w/g, function(x) {        return x.slice(1).toUpperCase();    })}
复制代码

手写类型判断函数

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;  }}
复制代码

event 模块

实现 node 中回调函数的机制,node 中回调函数其实是内部使用了观察者模式


观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 Subject 发生改变时,所有依赖它的对象 Observer 都会得到通知。


function EventEmitter() {  this.events = new Map();}
// 需要实现的一些方法:// addListener、removeListener、once、removeAllListeners、emit
// 模拟实现addlistener方法const wrapCallback = (fn, once = false) => ({ callback: fn, once });EventEmitter.prototype.addListener = function(type, fn, once = false) { const hanlder = this.events.get(type); if (!hanlder) { // 没有type绑定事件 this.events.set(type, wrapCallback(fn, once)); } else if (hanlder && typeof hanlder.callback === 'function') { // 目前type事件只有一个回调 this.events.set(type, [hanlder, wrapCallback(fn, once)]); } else { // 目前type事件数>=2 hanlder.push(wrapCallback(fn, once)); }}// 模拟实现removeListenerEventEmitter.prototype.removeListener = function(type, listener) { const hanlder = this.events.get(type); if (!hanlder) return; if (!Array.isArray(this.events)) { if (hanlder.callback === listener.callback) this.events.delete(type); else return; } for (let i = 0; i < hanlder.length; i++) { const item = hanlder[i]; if (item.callback === listener.callback) { hanlder.splice(i, 1); i--; if (hanlder.length === 1) { this.events.set(type, hanlder[0]); } } }}// 模拟实现once方法EventEmitter.prototype.once = function(type, listener) { this.addListener(type, listener, true);}// 模拟实现emit方法EventEmitter.prototype.emit = function(type, ...args) { const hanlder = this.events.get(type); if (!hanlder) return; if (Array.isArray(hanlder)) { hanlder.forEach(item => { item.callback.apply(this, args); if (item.once) { this.removeListener(type, item); } }) } else { hanlder.callback.apply(this, args); if (hanlder.once) { this.events.delete(type); } } return true;}EventEmitter.prototype.removeAllListeners = function(type) { const hanlder = this.events.get(type); if (!hanlder) return; this.events.delete(type);}
复制代码

手写 new 操作符

在调用 new 的过程中会发生以上四件事情:


(1)首先创建了一个新的空对象


(2)设置原型,将对象的原型设置为函数的 prototype 对象。


(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)


(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。


function objectFactory() {  let newObject = null;  let constructor = Array.prototype.shift.call(arguments);  let result = null;  // 判断参数是否是一个函数  if (typeof constructor !== "function") {    console.error("type error");    return;  }  // 新建一个空对象,对象的原型为构造函数的 prototype 对象  newObject = Object.create(constructor.prototype);  // 将 this 指向新建对象,并执行函数  result = constructor.apply(newObject, arguments);  // 判断返回对象  let flag = result && (typeof result === "object" || typeof result === "function");  // 判断返回结果  return flag ? result : newObject;}// 使用方法objectFactory(构造函数, 初始化参数);
复制代码

Object.is

Object.is解决的主要是这两个问题:


+0 === -0  // trueNaN === NaN // false
复制代码


const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}
复制代码

实现数组元素求和

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


let arr=[1,2,3,4,5,6,7,8,9,10]let sum = arr.reduce( (total,i) => total += i,0);console.log(sum);
复制代码


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


var = arr=[1,2,3,[[4,5],6],7,8,9]let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0);console.log(arr);
复制代码


递归实现:


let arr = [1, 2, 3, 4, 5, 6] 
function add(arr) { if (arr.length == 1) return arr[0] return arr[0] + add(arr.slice(1)) }console.log(add(arr)) // 21
复制代码

实现斐波那契数列

// 递归function fn (n){    if(n==0) return 0    if(n==1) return 1    return fn(n-2)+fn(n-1)}// 优化function fibonacci2(n) {    const arr = [1, 1, 2];    const arrLen = arr.length;
if (n <= arrLen) { return arr[n]; }
for (let i = arrLen; i < n; i++) { arr.push(arr[i - 1] + arr[ i - 2]); }
return arr[arr.length - 1];}// 非递归function fn(n) { let pre1 = 1; let pre2 = 1; let current = 2;
if (n <= 2) { return current; }
for (let i = 2; i < n; i++) { pre1 = pre2; pre2 = current; current = pre1 + pre2; }
return current;}
复制代码

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);  }}
复制代码


用户头像

还未添加个人签名 2022.07.31 加入

还未添加个人简介

评论

发布
暂无评论
20道常被问到的JavaScript题目_JavaScript_helloworld1024fd_InfoQ写作社区