前端面试真题宝典(二)

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})
  })
}

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,
                 }
             }
        })

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')
  }

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;

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
}
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;
};

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

输入: [1,2,3,null,5,null,4]
输出: [1,3,4]

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
function exposedElement(root) {
    
};

[外链图片转存中…(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;
}

【代码题】 输出顺序题

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

【代码题】 作用域

var a=3;
function c(){
  alert(a);
}
(function(){
  var a=4;
  c();
})();

// 3  c 定义在全局

【代码题】 输出题

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. 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. 请求和响应拦截器在接口报错时进行处理

  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. 全局变量(尽可能不要写window.obj = { } )
  2. 定时器和回调函数(记得关闭)
  3. 闭包(减少无意义的使用)
  4. DOM引用
var elements= {
    image: document.getElementById('111');
}

elements.image = null;
  • 避免死循环等持续执行的操作(例如 边界判断不清晰的 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);

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

输入一个数组 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);
};
  • 补充全排列
输入: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;
}

普通函数和箭头函数的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

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

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));

【代码题】 虚拟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 );
    }
}

前端History路由配置 nginx

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

location  / {
     root local/web/dist
}

当你在 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;
        }
}

$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);
}

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

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

知乎答案

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

给定一个二叉树,返回该二叉树层序遍历的结果,(从左到右,一层一层地遍历)
例如:
给定的二叉树是{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));

【代码题】 实现一个函数, 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];
};

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

/**
 * 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;
};

快慢指针解析

截图怎么实现

将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()
    }

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)
};

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

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

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

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

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

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

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

参考文献

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

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MaxLoongLvs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值