• 20道常被问到的JavaScript题目


    字符串最长的不重复子串

    题目描述

    给定一个字符串 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));
      }
    }
    // 模拟实现removeListener
    EventEmitter.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  // true
    NaN === 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);
      }
    }
    
    
  • 相关阅读:
    vue elementUI table表格自定义样式滚动
    prometheus中PromQL查询语言
    模拟实现队列(顺序队列和链式队列)
    请立即修复!服务器巨头核心固件曝7个高危漏洞
    语音识别系列︱利用达摩院ModelScope进行语音识别+标点修复(四)
    day2-web安全漏洞攻防-基础-弱口令、HTML注入(米斯特web渗透测试)
    在线教育线上课堂知识付费源码 网络课堂在线课堂系统源码 含完整代码包和搭建教程
    【JavaScript面试】面向过程与面向对象
    The core spirit of sentences
    基于Vue的前端架构,我做了这15点
  • 原文地址:https://blog.csdn.net/helloworld1024fd/article/details/127046502