• 前端面试真题宝典(二)


    Promise then 第二个参数和catch的区别是什么?

    如果是 Promise 内部报错,reject 抛出错误后,由于就近原则,then 的第二个参数会先捕获到异常,catch 则无法获取异常。但如果是 then 的第一个参数抛出错误,then 的第二个参数会捕获不到,只有 catch 能捕获。都用catch就可以了

    Promise finally 怎么实现的

    • 调用finally传入的callback函数,callback不论返回什么,都转换为Promise对象,并且与当前调用对象的状态值是一样的。
    finally(callback){
      return this.then(value=>{
        return MyPromise.resolve(callback()).then(()=>value)
      },reason=>{
        return MyPromise.resolve(callback()).then(()=>{throw 		reason})
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    webpack5 模块联邦

    使用场景:试想一下,你有一个组件包通过 npm 发布后,你的10个业务项目引用这个组件包。当这个组件包更新了版本,你的10个项目想要使用最新功能就必须一一升级版本、编译打包、部署,这很繁琐。但是模块联邦让组件包利用CDN的方式共享给其他项目,这样一来,当你到组件包更新了,你的10个项目中的组件也自然更新了。

    他和利用 npm 发包来实现的方案的区别在于,npm 发布的组件库从 1.0.1 升级到 1.0.2 的时候,必须要把业务线项目重新构建,打包,发布才能使用到最新的特性,而模块联邦可以实现实时动态更新而无需打包业务线项目。

    模块联邦是webpack的内置模块,使用起来也是相当的简单,做好相关配置就可以了,首先要保障项目webpack是5.0及以上。然后在对应的项目的webpack.config.js进行配置,ModuleFederationPlugin有几个重要的参数:

    1、name: 当前应用的名称,需要唯一性;
    2、exposes: 需要导出的模块,用于提供给外部其他项目进行使用;
    3、remotes: 需要依赖的远程模块,用于引入外部其他模块;
    4、filename: 入口文件名称,用于对外提供模块时候的入口文件名;
    5、shared: 配置共享的组件,一般是对第三方库做共享使用;

    // webpack 配置
    new ModuleFederationPlugin({
                name: "main_app",
                filename: "remoteEntry.js",
                exposes: {
                    "./search": "./src/search/search.vue"
                },
                remotes: {
                    lib_remote: "lib_remote@http://localhost:8085/remoteEntry.js",
                },
                 shared: {
                     vue: {
                         eager: true,
                         singleton: true,
                     }
                 }
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    webworker

    JavaScript是单线程的语言,如果在浏览器中需要执行一些大数据量的计算,页面上的其他操作就会因为来不及响应而出现卡顿的情况,Web Worker的出现为js提供了一个多线程的环境,让更多耗时的复杂计算拿到另一个环境中去完成,计算完之后再提供给主进程使用,前端页面可以只负责界面渲染,让用户体验更流畅。

    使用限制

    • 同源限制
    • 接口限制(window作用域下的部分方法不可使用,如DOM对象、window.alert和window.confirm等方法。)
    • 文件限制(无法加载本地js文件,必须使用线上地址。)
    • 记得关闭(worker会占用一定的系统资源,在相关的功能执行完之后,一定要记得关闭worker。)
    • 使用时注意this指向

    webworker的使用

    import React, { Component } from 'react';
     
    import worker_script from './worker-script';
     
    export default class PageHome extends Component {
      constructor(props) {
        super(props);
        this.state = {};
      }
     
      componentDidMount() {
        const worker = new Worker(worker_script);
        console.log(worker);
        worker.onmessage = function (event) {
          console.log(`Received message ${event.data}`);
        };
        // worker.postMessage('dadada')
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    const workercode = () => {
      self.onmessage = function (e) {
        console.log('Message received from main script');
        let workerResult = `Received from main: ${e.data}`;
        console.log('Posting message back to main script');
        self.postMessage(workerResult);
      };
     
      self.postMessage('workerResult');
    };
     
    let code = workercode.toString();
    code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
     
    const blob = new Blob([code], { type: 'application/javascript' });
    const worker_script = URL.createObjectURL(blob);
     
    export default worker_script;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Symbol

    ES 6 引入了一个新的数据类型 Symbol,创建独一无二的值

    useRef / ref / forwardsRef 的区别是什么

    ref可以用来获取真实dom,来操作dom节点。ref还可以用来获取React组件的实例,ref不能直接应用于函数组件,ref 经常用来做dom操作以及子组件向父组件通信,还可以作为内部数据存储使用。

    forwardRef用于解决上面函数组件无法直接使用ref的问题,使用forwardRef可以使得函数组件也可以使用ref做上面的操作。

    useRef 函数接收一个初始值,并返回一个可变的(mutable) ref 对象,也就是我们的变量refContainer。这个对象上面会存在一个属性——current,它的默认值就是initalValue。这里有个比较重要的点就是useRef 创建返回的对象是可变的,并且它会在组件的整个生命周期中都存在,这也就以为着无论你在何时何地访问这个对象,它的值永远都是最新的值。

    扩展下createRef和useRef是不一样的,

    • 类组件有一个实例 instance 能够维护像 ref 这种信息
    • 函数组件每次更新都是一次新的开始,所有变量重新声明,ref 就会随着函数组件执行被重置

    hooks 和函数组件对应的 fiber 对象建立起关联,将 useRef 产生的 ref 对象挂到函数组件对应的 fiber 上,函数组件每次执行,只要组件不被销毁,函数组件对应的 fiber 对象一直存在,所以 ref 等信息就会被保存下来。

    各种缓存的优先级, memory disk http2 push?

    缓存的四个位置按照优先级顺序依次为Service Worker、Memory Cache、Disk Cache和Push Cache。

    聊一聊浏览器缓存

    小程序为什么会有两个线程? 怎么设计?(类似webworker?不太了解)

    我们都知道,传统web的架构模型是单线程架构,其渲染线程和脚本线程是互斥的,这也就是说为什么长时间的脚本运行可能会使页面失去响应,而小程序的架构模型有别于传统web,小程序为双线程架构,其渲染线程和脚本线程是分开运行的。

    传统web开发者可以使用各种浏览器暴露出来的DOM API来进行DOM操作,但由于小程序的逻辑层和渲染层是分开的,逻辑层并没有一个完整的浏览器对象,因而缺少相关的DOM API和BOM API

    传统web开发者需要面对的是各式各样的浏览器,PC 端需要面对 IE、Chrome、火狐浏览器等,在移动端需要面对Safari、Chrome以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具。

    通信使用websocket

    今天是学习前端的第一天之小程序的双线程架构

    如果大量日志堆在内存里怎么办?

    【代码题】 深拷贝

    const deepCopy = (obj, hash = new WeekMap()) => {
    	if(obj instanceof Date) {
    		return new Date(obj)
    	}
    	if(obj instanceof RepExp) {
    		return new RegExp(obj)
    	}
    	if(hash.has(obj) {
    		return hash.get(obj)
    	}
    	let property = Object.getOwnPropertyDescriptors(obj)
    	let cloneObj = Object,create(Object.getPrototypeOf(obj), property)
    	
    	hash.set(obj, cloneObj)
    	for(let key of Reflect.ownKeys(obj)) {
    		if(obj[key] && typeof obj[key] === 'object') {
    			cloneObj[key] = deepCopy(obj[key], hash)
    		} else {
    			cloneObj[key] = obj[key]
    		}
    	}
    	return cloneObj
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    const deepClone = (obj, m) => {
    	if(obj ??'' === '') return obj;
      if(obj === document) return;
      if(!Object.prototype.isPrototypeOf(m)) {
        if(HTMLElement.prototype.isProtoTypeOf(source)) {
          target = document.createElement(source);
        } else if(source.constructor === RegExp) {
          target = new RegExp(source.source, source.flags);
        } else if(source.constructor === Date) {
          target = new Date(source);
        } else if(source.constructor === Function) {
          var arr = source.toString().replace(/\n|\r/g, "").trim().match(/\((.*?)\)\s*\{(.*)\}/).slice(1);  //进行source处理
          target = new Function(arr[0].trim(), arr[1]);
        } else if(source.constructor === Set) {
          target = new Set(deepClone(Array.from(source.values())));
        } else if(source.constructor === Map) {
          target = new Map();
          for(var [key, value] of source.entries()) {
            if(Object.prototype.isPrototypeOf(key)) {
              target.set(deepClone(key), deepClone(value));
            } else {
              target.set(deepClone(key), value);
            }
          } else {
            if(Obejct.prototype.isPrototypeOf(value)) {
              target.set(key, deepClone(value));
            } else {
              target.set(key, value);
            }
          }
        }
      } else {
        target = new source.constructor();
      }
      var names = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source));
      for(var i = 0; i < names.length; i++) {
        if(source.constructor === Function && names[i] === 'prototype') continue;
      	var desc = Object.getownPropertyDescriptor(source, names[i]);
        if(Object.prototype.isPrototypeOf(desc.value)) {
          Object.defineProperty(target, names[i], {
            enumerable: desc.enumerable,
            configurable: desc.configurable,
            writable: desc.writable,
            value: deepClone(desc.value)
          });
        } else {
          Object.defineProperty(taget, names[i], desc);
        }
      }
      return target;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    【代码题】 二叉树光照,输出能被光照到的节点, dfs能否解决?

    输入: [1,2,3,null,5,null,4]
    输出: [1,3,4]
    
    /**
     * @param {TreeNode} root
     * @return {number[]}
     */
    function exposedElement(root) {
        
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    [外链图片转存中…(img-TLxlzkps-1667398190178)]

    // 将list转化为树结构
    class TreeNode {
      constructor(val, left, right) {
        this.val = (val === undefined ? 0 : val);
        this.left = (left === undefined ? 0 : left);
        this.right = (right === undefined ? 0 : right);
      }
    }
    
    function buildTree(arr) {
        if (!arr || arr.length === 0) {
            return null;
        }
        let root = new TreeNode(arr.shift());
      
        let nodeQueue = [root];
        
        while (arr.length > 0) {
            let node = nodeQueue.shift();
            if (!arr.length) {
                break;
            }
            let left = new TreeNode(arr.shift());
            node.left = left;
            nodeQueue.push(left);
            
            if (!arr.length) {
                break;
            }
            
            // 左侧叶子拼完,右边一样的操作
            let right = new TreeNode(arr.shift());
            node.right = right;
            nodeQueue.push(right);
        }
        
        // 最后返回根结点,通过根结点就能得到整个二叉树的结构
        return root;
    }
    
    const rightSideView = (root) => {
    	const res = [];
     	const dfs = (node, level) => {
        if(!node) return;
        res[level] = node.val;
        dfs(node.left, level + 1);
        dfs(node.right, level + 1);
      }
      dfs(root, 0);
      return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    【代码题】 输出顺序题

    setTimeout(function () {
      console.log(1);
    }, 100);
    
    new Promise(function (resolve) {
      console.log(2);
      resolve();
      console.log(3);
    }).then(function () {
      console.log(4);
      new Promise((resove, reject) => {
        console.log(5);
        setTimeout(() =>  {
          console.log(6);
        }, 10);
      })
    });
    console.log(7);
    console.log(8);
    
    // 23784561
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    【代码题】 作用域

    var a=3;
    function c(){
      alert(a);
    }
    (function(){
      var a=4;
      c();
    })();
    
    // 3  c 定义在全局
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    【代码题】 输出题

    function Foo() {
        Foo.a = function(){
            console.log(1);
        }
        this.a = function(){
            console.log(2)
        }
    }
    
    Foo.prototype.a = function() {
        console.log(3);
    }
    
    Foo.a = function() {
        console.log(4);
    }
    
    Foo.a();
    let obj = new Foo();
    obj.a();
    Foo.a();
    
    // 4 2 1
    // 补充 Foo.a 为 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    错误捕捉

    教你如何捕获前端错误

    1. window.onerror与window.addEventListener(‘error’)捕获js运行时错误
    2. 资源加载错误使用addEventListener去监听error事件捕获
    3. 未处理的promise错误处理方式(.catch来避免promise抛出的异常)
    window.addEventListener('rejectionhandled', event => { // 错误的详细信息在reason字段 // demo:settimeout error console.log(event.reason); });
    
    • 1
    1. 请求和响应拦截器在接口报错时进行处理

    2. 经常在处理对象时或者数组长度时,因为类型或者找不到改对象会导致页面崩溃等问题,可以使用ts类型检查 以及 es6的?.方法来避免页面崩溃

    前端稳定性监控

    实现一个前端监控系统,应该考虑什么?如何去实现

    通过埋点来监控整个前端的稳定性,例如首屏加载速度,性能监控等

    首屏加载时间:times = (performance.timing.domComplete - performance.timing.navigationStart) / 1000

    // 方案一:
    document.addEventListener('DOMContentLoaded', (event) => {
        console.log('first contentful painting');
    });
    // 方案二:
    performance.getEntriesByName("first-contentful-paint")[0].startTime
    
    // performance.getEntriesByName("first-contentful-paint")[0]
    // 会返回一个 PerformancePaintTiming的实例,结构如下:
    {
      name: "first-contentful-paint",
      entryType: "paint",
      startTime: 507.80000002123415,
      duration: 0,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    前端内存处理

    1. 全局变量(尽可能不要写window.obj = { } )
    2. 定时器和回调函数(记得关闭)
    3. 闭包(减少无意义的使用)
    4. DOM引用
    var elements= {
        image: document.getElementById('111');
    }
    
    elements.image = null;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 避免死循环等持续执行的操作(例如 边界判断不清晰的 for 或 while 循环)
    • 多使用 WeakSet / WeakMap 特性

    【代码题】 好多请求, 耗时不同, 按照顺序输出, 尽可能保证快, 写一个函数.

    同:手写题: 实现一个批量请求函数, 能够限制并发量?

    const promiseList = [
    	new Promise((resolve) => {
    		setTimeout(resolve, 1000)
    	}),
    	new Promise((resolve) => {
    		setTimeout(resolve, 1000)
    	}),
    	new Promise((resolve) => {
    		setTimeout(resolve, 1000)
    	})
    ]
    
    fn(promiseList);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    【代码题】 一个数组的全排列

    输入一个数组 arr = [1,2,3,4]
    输出全排列
    
    [[1], [2], [3], [1, 2], [1, 3], ...., [1,2,3], [1,2,4] ....]
    
    /**
     * @param {number[]} nums
     * @return {number[][]}
     */
    var permute = function(data) {
      let res = [];
      let group = [];
      let index = 0;
      res.push(data[index]);
      for(var i = 0; i < group.length; i++) {
        res.push(group[i] + data[index]);
      }
      group.push.apply(group, res);
      
      if(index + 1 >= data.length) return group;
      else return getGroup(data, index + 1, group);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 补充全排列
    输入:nums = [1,2,3]
    输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
    
    function permute(nums) {
        const res = [], path = [];
        const used = new Array(nums.length).fill(false);
        const dfs =()=> {
            if (path.length === nums.length) { 
                res.push(path.slice()); 
                return;
            }
            for (let i = 0; i < nums.length; i++) {
                if (used[i]) continue; // 如果之前已经出现过了,直接跳
                path.push(nums[i]);
                used[i] = true; // 表示这个位置已经用过了
                dfs(); // 递归 回溯
                path.pop(); // 回溯的过程中,将当前的节点从 path 中删除
                used[i] = false;
            }
        }
        dfs();
        return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    普通函数和箭头函数的this指向问题

    const obj = {
    	fn1: () => console.log(this),
    	fn2: function() {console.log(this)}
    }
    
    obj.fn1();
    obj.fn2();
    
    const x = new obj.fn1(); // undefined/window
    const y = new obj.fn2(); // obj
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    promise相关的特性

    promise的特性
    1.对象的状态不受外界影响。promise对象代表一个异步操作,有3种状态: pending(初始状态),fulfilled(成功状态),rejected(失败状态)。只有异步操作的结果才能决定当前的状态,任何其它操作都不能改变这个状态。

    2.一旦状态改变就不会再变,任何时候都可以得到这个结果。promise状态的改变只有两种情况: pending 到 fulfillted, pending 到 rejected。

    promise优缺点
    优点:有了promise对象,就可以把异步操作以同步操作的流程表达出来,避免层层嵌套回调函数,此外promise对象提供统一的接口,使得控制异步操作更加容易。

    缺点:一旦新建就无法取消,会立即执行。如果不设置回调函数,promise 内部抛出的错误,不会反应到外部。当前处于pending状态,无法得知进行到哪一步了(刚刚开始还是即将结束)。

    vue父子组件, 生命周期执行顺序 created mounted

    父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
    
    • 1

    created(在模板渲染成html前调用) => computed => mounted(在模板渲染成html后调用)

    vue3添加了哪些新特性?

    同上

    xss 的特点以及如何防范?

    CSP 同上

    Http 2.0和http3.0对比之前的版本, 分别做了哪些改进? HTTP 3.0基于udp的话, 如何保证可靠的传输?

    HTTP协议:最常用的应用层

    TCP和UDP最大的区别是什么?

    最重要的传输层

    CSP除了能防止加载外域脚本, 还能做什么?

    ??做权限

    typescript is这个关键字是做什么呢?

    is 关键字一般用于函数返回值类型中,判断参数是否属于某一类型,并根据结果返回对应的布尔类型。用了is,ts就可以根据is后面的类型做类型判断。比如is Element, 就可以对参数用element的属性和方法,querySelector之类的。

    【代码题】 多叉树, 获取每一层的节点之和

    const res = {
      value: 2,
      children: [
        { value: 6, children: [ { value: 1 } ] },
        { value: 3, children: [ { value: 2 }, { value: 3 }, { value: 4 } ] },
        { value: 5, children: [ { value: 7 }, { value: 8 } ] }
      ]
    };
      
    const layerSum = function(root) {
        let result = [], index = 0;
        const level = (root, index) => {
          if (!root) return;
          if (!result[index]) result[index] = 0;
          result[index] += root.value;
          if(root.children) root.children.forEach(child => level(child, index+1))
        };
        level(root, index);
        return result;
      };
      
    console.log(levelOrder(res));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    【代码题】 虚拟dom转真实dom

    const vnode = {
        tag: 'DIV',
        attrs: {
            id: 'app'
        },
        children: [{
                tag: 'SPAN',
                children: [{
                    tag: 'A',
                    children: []
                }]
            },
            {
                tag: 'SPAN',
                children: [{
                        tag: 'A',
                        children: []
                    },
                    {
                        tag: 'A',
                        children: []
                    }
                ]
            }
        ]
    }
    
    function render ( vnode, container ){
        return container.appendChild( _render( vnode ) );
    }
    function _render( vnode ){
        if ( typeof vnode === 'number' ) {
            vnode = String( vnode );
        }
        //处理文本节点
        if( typeof vnode === 'string'){
            const textNode = document.createTextNode( vnode )
            return textNode;
        }
        //处理组件
        if ( typeof vnode.tag === 'function' ) {
            const component = createComponent( vnode.tag, vnode.attrs );
            setComponentProps( component, vnode.attrs );
            return component.base;
        }
        //普通的dom
        const dom = document.createElement( vnode.tag );
        if( vnode.attrs ){
            Object.keys( vnode.attrs ).forEach( key => {
                const value = vnode.attrs[ key ];
                setAttribute( dom, key, value );    // 设置属性
            } );
        }
        vnode.children.forEach( child => render( child, dom ) );    // 递归渲染子节点
        return dom ;    // 返回虚拟dom为真正的DOM
    }
    //实现dom挂载到页面某个元素
    const ReactDOM = {
        render: ( vnode, container ) => {
            container.innerHTML = '';
            return render( vnode, container );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    前端History路由配置 nginx

    try_files 解决的是:当 nginx 找不到客户端需要的资源时该怎么办的问题。以 history 路由为例:假如你的页面 url 是 http://www.example.com/post,你的 nginx 配置如下:

    location  / {
         root local/web/dist
    }
    
    • 1
    • 2
    • 3

    当你在 post 路由下刷新页面时,nginx 会返回 404。这是什么原因呢?因为我们没有告诉nginx找不到某个文件时该怎么做。root 指定了 / 对应的单页静态资源目录,从而使url映射到dist目录下。

    这个配置可以让你项目的 css,js 被顺利加载,但是碰到上面的 URL,nginx 就不知所措了。因为我们的 dist 文件夹下面并没有 post 这个文件或者文件夹,所以 nginx 会给你个 404 页面。try_files 就是为了解决这个问题的,try_files 语法如下:

    server {
    
            listen      4010;
            server_name  localhost;
            location / {
                root  '/root/static';
                index  /index.html;
                try_files $uri $uri/ /index.html;
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    $uri 会匹配到 post,nginx 发现 dist 目录下下面没有 post 这个文件,也没有 post 这个文件夹,所以最后会返回 dist 目录下的 index.html。这样,index.html 被浏览器加载之后,前端路由就会工作,将用户需要的资源加载出来。而我们 build 出来的 css,js 文件,由于可以被 nginx 正确找到,则不会受到影响。

    【代码题】 有序数组原地去重

    // 快慢指针
    const res = [0,0,1,1,2,2,2,2,4,4,5,5,6]
    
    const removeDuplicates = (nums) => {
      let slow = 0, fast = 1;
    	if(nums.length === 0) return
      while (fast < nums.length) {
        if (nums[fast] !== nums[slow]) {
          slow++;
          nums[slow] = nums[fast];
        }
        fast++;
      }
      return nums.splice(0, slow + 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如何估算一个城市中的井盖数量

    井盖和什么相关,增加不同的权重,通过一两个区去统计推算整个城市的井盖数量。

    知乎答案

    【代码题】 二叉树层序遍历, 每层的节点放到一个数组里

    给定一个二叉树,返回该二叉树层序遍历的结果,(从左到右,一层一层地遍历)
    例如:
    给定的二叉树是{3,9,20,#,#,15,7},
    该二叉树层序遍历的结果是[[3],[9,20],[15,7]]
    
    var levelOrder = function(root) {
        let res = [];
        if(root === null) return res;
        let list = [];
        list.push(root);
        while(list.length) {
            let curLen = list.length;//上一轮剩下的节点,全属于下一层
            let newLevel = [];
            for(let i = 0; i < curLen; i++) {//同层所有节点
                let node = list.shift();//出列
                newLevel.push(node.val);//push进newLevel数组
                //左右子节点push进队列
                if(node.left) list.push(node.left);
                if(node.right) list.push(node.right);
            }
            res.push(newLevel);//push到res数组
        };
        return res;
    };
    
    console.log(levelOrder(res));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    【代码题】 实现一个函数, fetchWithRetry 会自动重试3次,任意一次成功直接返回

    const fetchWithRetry = async (
      url, 
      options, 
      retryCount = 0,
    ) => {
      const { MAXRET = 3, ...remainingOptions } = options;
      try {
        return await fetch(url, remainingOptions);
      } catch (error) {
        // 如果重试次数没有超过,那么重新调用
        if (retryCount < maxRetries) {
          return fetchWithRetry(url, options, retryCount + 1);
        }
        // 超过最大重试次数
        throw error;
      }
    }
    
    // 补充超时和取消
    // 创建一个 reject 的 promise 
    // `timeout` 毫秒
    const throwOnTimeout = (timeout) => 
      new Promise((_, reject) => 
        setTimeout(() => 
         reject(new Error("Timeout")),
         timeout
        ),
      );
    
    const fetchWithTimeout = (
      url, 
      options = {},
    ) => {
      const { timeout, ...remainingOptions } = options;
      // 如果超时选项被指定,那么 fetch 调用和超时通过 Promise.race 竞争
      if (timeout) {
        return Promise.race([
          fetch(url, remainingOptions), 
          throwOnTimeout(timeout),
        ]);
      }
      return fetch(url, remainingOptions);
    }
    
    // 取消
    const fetchWithCancel = (url, options = {}) => {
      const controller = new AbortController();
      const call = fetch(
        url, 
        { ...options, signal: controller.signal },
      );
      const cancel = () => controller.abort();
      return [call, cancel];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    【代码题】 链表中环的入口节点

    /**
     * Definition for singly-linked list.
     * function ListNode(val) {
     *     this.val = val;
     *     this.next = null;
     * }
     */
    
    /**
     * @param {ListNode} head
     * @return {ListNode}
     */
    var detectCycle = function (head) {
        var hashSet = new Set()
        while (head) {
            if(hashSet.has(head)){
                return head
            }
            hashSet.add(head)
            head = head.next
        }
        return null
    };
    
    // 快慢指针
    var detectCycle = function(head) {
      if(!head) return null;
      let fast = head, slow = head;
      while(fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        if(fast === slow){
          fast = head;
          while(fast !== slow){
            fast = fast.next;
            slow = slow.next;
          }
          return slow;
        }
      }
      return null;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    快慢指针解析

    截图怎么实现

    将dom转换为canvas

    puppeteer html2canvas dom-to-image等工具

    初探JavaScript的截屏实现

    js实现纯前端截屏

    qps达到峰值了,怎么去优化(前端优化qps ?)

    RT(Response Time): 1个请求所完成的时间
    QPS(Query Per Second): 1秒钟内所完成的请求数量
    TPS(Transactions Pre Second):tps为事务每秒的请求次数

    在集群化的架构下,可以采用池化(内存池,连接池,线程池),分布式缓存,分布式消息队列,流控技术(服务降级,应用拆分,限流)和数据库高并发(分库分表,读写分离等)提高并发能力

    1. 使用缓存 & 为高量级的数据读取做缓存预加载机制目的:利用内存读取替代磁盘IO,减少磁盘IO次数,从而降低读取耗时

    2. 读写分离 目的:通过对用户访问的数据分析,一定是读数据库的量要远远大于写数据库的量,这时读就成为瓶颈。读请求一定程度上会阻塞写请求。
      而读写的可靠性级别也是不一样的,写的可靠性要求会更高,针对读写的不同的要求,进行数据库的读写分离

    3. 分库分表

      水平分表: 业务并发量不大, 单表数据大,检索数据慢
      垂直分表: 业务并发量不大, 大表,存大文本
      水平分库:业务并发量大,单表数据大
      垂直分库: 业务并发量大, 业务模块分库,单模块业务量大

    百万 QPS 前端性能监控系统设计与实现

    谷歌图片, 如果要实现一个类似的系统或者页面, 你会怎么做?

    首先要了解google图片或者百度图片是什么样的。他是自动填充满每一行的宽度,但是没张图片并不是固定宽度,同时支持无限下拉以及图片懒加载。还有一点刷新后仍然是之前的排列效果

    那么我们一定是要对图片进行筛选才能达到我们想要的效果,从数据中筛选出符合条件的图片排列返回(涉及到算法)

    看这个: 图像布局算法 - Google Plus

    css grid布局实现瀑布流

    vue 基于原生JS实现等宽瀑布流布局

    无限下拉其实就是分页查询

    懒加载

        var arr = [{url: ''} , ...]
    
        const div = document.querySelector('div')
    
        arr.forEach(function (item ) {
          const imgTag = new Image()
          imgTag.dataset.src = item.url
          imgTag.dataset.load = 0
          imgTag.style.height = '300px'
          imgTag.style.width = '300px'
          imgTag.style.display = 'block'
          div.appendChild(imgTag)
        })
    
        function lazyload() {
          for (let i = 0; i < div.children.length; i++) {
            if (div.children[i].dataset.load == 1) continue
            if (div.children[i].offsetTop <= document.documentElement.scrollTop + document.documentElement.clientHeight) {
              div.children[i].src = div.children[i].dataset.src
              div.children[i].dataset.load = 1
            }
          }
        }
    
        window.onscroll = function () {
          lazyload()
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    js超过Number最大值的数怎么处理?

    Js 有个新类型BigInt

    借助第三方库实现

    将数字转换为字符串处理

    最小的k个数

    来源: 力扣 Riddle

    // 最简洁
    const getLeastNumbers = (arr, k) => {
        return arr.sort((a, b) => a - b).slice(0, k)
    };
    
    // 快排
    const getLeastNumbers = (arr, k) => {
        function qSort(arr, low, height) {
            if (low >= height) return
            let flag = arr[low]
            let left = low, right = height
            while (left < right) {
                while (arr[right] >= flag && left < right) right--
                while (arr[left] <= flag && left < right) left++
                if (left < right) {
                    let temp = arr[left]
                    arr[left] = arr[right]
                    arr[right] = temp
                }
            }
            arr[low] = arr[right]
            arr[right] = flag
            qSort(arr, low, right - 1)
            qSort(arr, right + 1, height)
        }
        qSort(arr, 0, arr.length - 1)
        return arr.slice(0, k)
    };
    
    // 优化版
    const getLeastNumbers = (arr, k) => {
        if (k >= arr.length) return arr;
        function qSort(arr, low, height) {
            if (low >= height) return
            let flag = arr[low]
            let left = low, right = height
            while (left < right) {
                while (arr[right] >= flag && left < right) right--
                while (arr[left] <= flag && left < right) left++
                if (left < right) {
                    let temp = arr[left]
                    arr[left] = arr[right]
                    arr[right] = temp
                }
            }
            arr[low] = arr[right]
            arr[right] = flag
            if (right == k) return
            if (k < right) qSort(arr, low, right - 1)
            else qSort(arr, right + 1, height)
        }
        qSort(arr, 0, arr.length - 1)
        return arr.slice(0, k)
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    64个运动员, 8个跑道, 如果要选出前四名, 至少跑几次?

    首先分开跑8次,这样就取到了32位剩下的运动员,将每一组的第一组在一起跑一次,剩下四个,分别叫老大,老二,老三,老四

    这样淘汰掉后四名第一次所在的组,剩下16人。

    可以得出结论第二轮跑第一的人一定是第一。

    那么还剩3个名额,可见老四所在的组除了老四其他人都被pass,老三所在的组除了老三和小老三其他都被pass,老二所在的组除了老二,小老二,小小老二 其他pass,老大在的组前四个都留下。剩下9人

    除了老大 老二剩下人一起跑(因为小老二和老三一定没老二快),在这次比赛中,如果小老二或者老三排名第一或者第二,那从该次比赛中取出前两个,和老大,老二,一起作为前4名。如果小老二或者老三没排名或者排在第三或者第四,那么将老大组下的三兄弟和老二比赛一次,取前三名和老大一起作为前4名。剩下4人

    经典面试题:64匹马,8个赛道,找出前4名最少比赛多少场?

    参考文献

    [面经] 5年前端 - 历时1个月收获7个offer🔥

  • 相关阅读:
    企业应用架构研究系列二:MSF&Scrum 项目管理
    Bash脚本基础
    C高级day5(Makefile)
    新发现,新挑战,技术出海的机遇与挑战丨PingCAP DevCon 2022 出海专场
    Kafka 面试套路居然这样多!读完大神的 Kafka 核心手册,秒杀面试官!全网最强!!
    提升演讲口才,助青少年踏上成功之路
    @Async注解的坑,小心
    synchronized同步以及双重检索
    《深入理解Linux内核中文第三版》学习笔记——第7章 进程调度
    一次线上OOM问题的个人复盘
  • 原文地址:https://blog.csdn.net/shadowfall/article/details/127660228