前端

90 阅读13分钟

1.海纳ai

1.前端架构

  • 业务层面 image.png

  • 项目层面

    1. 基础层设计
    • gitLab
    • 版本管理
    • 发布jenkins
    • 脚手架
    • node中间层
    • 埋点
    • 监控
    • eslint
    • 灰度
    1. 应用层设计
    • 以应用为单位划分前端项目
    • 基础组件库
    • 浏览器兼容
    • 权限平台
    • CDN
  1. 封装一个组件从设计到实现需要考虑哪些
  • 需求分析
    • 明确组件的功能
    • 需要的输入,输出
  • 组件设计
    • 界面设计
    • 交互设计
  • 组件结构
    • 拆分子组件
    • 状态管理
    • 生命周期
  • 属性和状态
    • props
    • state
  • 事件处理
    • 事件监听
    • 事件传递
  • 样式
  • 可重用性
    • 通用性
    • 配置性
  • 可测试性
    • 单端测试
    • 集成测试
  • 性能
    • 避免不必要的渲染
    • 懒加载
  • 文档
    • 使用说明
    • api文档
  1. 手写防抖
  • 防抖是指在一段连续的时间内,如果事件频繁触发,只执行一次相应的操作。
  • 应用场景:搜索框实时搜索,滚动加载。在这种情况下,我们不希望用户正在输入或滚动时触发相应的操作,而是等待一段时间再执行。
  • 实现:设置一个定时器,在事件被触发后的指定时间间隔内没有再次触发,则执行相应的操作,如果在这个时间间隔内又触发了相同的事件,则重新计时。
  const debounce = (fn, delay) => {
    let timer;
    return (...args) => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        fn(...args);
      }, delay);
    };
  };
  1. 输出
for(var i = 0; i < 10; i++) {
    console.log(i)
}
- var变量声明,都在执行任何代码之前进行处理
for(var i = 0; i < 10; i++) {
    setTimeout(() => console.log(i), 10)
}
- 当前的同步代码执行完成后开始运行
- 解决1:使用块级作用域,let
- 解决2:闭包
    - 每次循环中,立即执行函数表达式 (IIFE) 立即执行,并将当前的 `i` 值作为参数传递给该函数。
  1. 发布订阅模式
  • 发布者不会将消息直接发送给订阅者,而是将消息分给不同的类别,同样订阅者可以表达对一个或者多个类别的兴趣,只接受感兴趣的消息,无需了解有哪些发布者
class EventBus {
    constructor(){
        this.event = {};
    }
    
    subscribe(event, listener) { 
        if (!this.events[event]) { 
            this.events[event] = []; 
        } 
        this.events[event].push(listener);
    }

    unsubscribe(event, listenerToRemove) { 
        if (!this.events[event]) return; 
        this.events[event] = this.events[event].filter(listener => listener !== listenerToRemove); 
    }
    
    publish(event, data) { 
        if (!this.events[event]) return; 
        this.events[event].forEach(listener => listener(data)); 
    }
    
    // 创建一个事件总线实例 
    const eventBus = new EventBus();
    
    // 订阅事件 
    const logData = (data) => console.log(`Received data: ${data}`); 
    eventBus.subscribe('dataReceived', logData);
    
    // 发布事件 
    eventBus.publish('dataReceived', 'Hello, World!'); // 控制台输出: Received data: Hello, World! 
    // 取消订阅事件 
    eventBus.unsubscribe('dataReceived', logData); 
    // 发布事件(无订阅者,不会有输出) 
    eventBus.publish('dataReceived', 'This will not be logged');
}
  • 观察者模式
// 主题对象 
class Subject {  
constructor() {    
    this.observers = [];
}
addObserver(observer) {    
    this.observers.push(observer); 
}
removeObserver(observer) {    
    this.observers = this.observers.filter(obs => obs !== observer); 
} 
notify(data) {   
    this.observers.forEach(observer => observer.update(data)); } 
}
// 观察者对象
class Observer { 
constructor(name) {   
    this.name = name; 
}
update(data) {  
    console.log(`${this.name} received data: ${data}`); } 
}
// 创建主题对象 
const subject = new Subject();
const observer1 = new Observer('Observer 1'); 
const observer2 = new Observer('Observer 2');
// 添加观察者 
subject.addObserver(observer1); 
subject.addObserver(observer2);
// 通知观察者 
subject.notify('Hello, observers!');
// 移除观察者 
subject.removeObserver(observer2);
// 再次通知观察者 
subject.notify('Hello again, observers!');

2.伴鱼 1面

  1. 输出
console.log('1')
async function asyncTask(){
    console.log('2');
    await new Promise(res => setTimeout(res, 2000))
    console.log('3');
}
setTimeout(()=> console.log('4'), 0);
asyncTask()
new Promise(res => {
    console.log('5')
    res();
}).then(() => {
    console.log('6');
})
console.log('7')

-   **同步代码**:`1``2``5``7`

-   **微任务**:`6`

-   **宏任务**:`4`

-   **延迟的 `asyncTask` 代码**:`3`(因 `await` 暂停了 2 秒)

  1. 输出
var name = 'Tom';
(function(){
    if(typeof name === 'undefined'){
        var name = 'jack'
        console.log('goodbye' + name)
    } else {
        console.log('hello' + name)
    }
})()

考察IIFEthis
goodbyejack
  1. 输出
function Person(firstName, lastName){
     this.firstName = firstName
     this.lastName = lastName
}
const lydia = new Person('aa','bb')
const sarah = Person('cc','dd')
console.log(lydia) // { firstName: 'aa', lastName: 'bb' }
console.log(sarah) // undefined

- 当通过new调用构造函数时,会创建一个新的对象,将对象的__proto__属性设置为构造函数的protoType属性,
并将构造函数的this绑定到新创建的对象上。
- 实例化lydia: new Person('aa','bb')
- 使用new操作符
- 创建一个新对象
- 将this绑定到新对象上
- 执行构造函数,将firstName、lastName赋值给新对象
- 返回新对象并赋值给lydia
  1. 输出
var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a.x)
console.log(b.x)
- JavaScript 的赋值表达式从右到左进行评估和赋值
- 右侧赋值 `a = {n:2}`
- 左侧的属性赋值 `a.x =`
  - 在执行a.x =时,JS会解析a的引用,在进行赋值
  - 在开始这一行代码的执行时,`a` 仍然引用旧的对象 `{n:1}`。
  - 虽然 `a` 之后被重新赋值为 `{n:2}`,但在左侧属性赋值 `a.x =` 时,它仍指向旧对象。
  - 因此,`a.x =` 其实是 `b.x =`,因为此时 `b` 也指向旧对象 `{n:1}`。
  - 因此,在旧对象上添加了一个新属性 `x`,其值是新对象 `{n:2}`
  1. 实现convert方法,把原始list转换成树形结构,要求尽可能降低事件复杂度,以下数据结构中,id代表部门编号,name是部门名称,parentId是父部门编号吗,为0代表一级部门,现在要求实现一个convert方法,把原始list转换成树形结构,partentId为多少就挂载在该id的属性children数组下
const list = [ 
    { id: 1, name: '部门A', parentId: 0 }, 
    { id: 2, name: '部门B', parentId: 1 }, 
    { id: 3, name: '部门C', parentId: 1 }, 
    { id: 4, name: '部门D', parentId: 2 }, 
    { id: 5, name: '部门E', parentId: 2 }, 
    { id: 6, name: '部门F', parentId: 3 } 
];

image.png

function convert(list) {
    const map = {}
    // 把当前的list初始化 加入children
    list.forEach(item => {
        map[item.id] = {...item , children: [] }
    })
    const tree = []
    list.forEach(item => {
        const node = map[item.id]
        // 父节点
        if(item.parent === 0){
            tree.push(node)
        } else {
        // 添加到父节点的children中
            const parent = map[item.id]
            if(parent) parent.children.push(node)
        }
    })
    return tree 
}
  1. 从输入url到页面展示经历了什么
  • url解析(协议、域名、端口)
  • DNS解析
  • TCP连接(用解析出的IP地址与服务器进行连接,3次握手)
  • 发送http请求
  • 服务器处理请求(根据相应数据类型,返回对应的字节流类型)
  • 渲染页面
  1. 4次挥手
  2. http和https的区别
  • http:不加密,明文传输,容易被篡改,性能较好,适用于不涉及敏感信息的网站
  • https:加密,传输安全,性能稍差,安全性高,适用于涉及敏感信息的网站,对SEO有利
  1. 使用react框架和使用js原生有什么区别
  • 使用React
    • 组件化开发
    • 虚拟DOM
    • 单向数据流
    • 状态管理(Redux,Mobx)
    • 生态(React Router、Redux、Antd)
    • JSX(允许在js中直接编写html),js需要使用createElement
  • 使用JS
    • 可以在服务端运行,Node.js是一个基于js服务器端的运行环境。
    • 跨平台(windows、mac、linux)
  1. 页面加载优化
  • 减少http请求
  • gzip压缩文件
  • 图片懒加载
  • 缓存
  • 异步加载js(async、defer)
  • 预加载关键资源:使用 <link rel="preload">
  • 减少重排、重绘
  • CDN(CDN提供负载均衡,将请求分配给不同的服务器)
  • 使用http/2(多路复用:允许多个请求共用一个tcp连接,头部压缩:可以压缩头部信息)
  1. 强缓存、协商缓存
  • 在缓存有效期内,浏览器不需要向服务器发送请求,直接从缓存中读取资源,强缓存通过 HTTP 头中的 ExpiresCache-Control 字段来实现。
  • 当强缓存失效时,浏览器会向服务器发送请求,服务器会根据请求头信息判断资源是否更新,如果没更新,继续使用缓存,如果更新了服务器会返回新资源。协商缓存通过 Last-Modified/If-Modified-SinceETag/If-None-Match 实现。
  1. React18新特性
  • state更新进行批处理(批处理范围扩大:promise、settimeout)
  • useTransition:react中用于区分高优更新、非高优更新的新概念
    • 高优:鼠标点击等交互性比较强
    • 非高优:ui更新
  • suspense
  • ReactDom.createRoot替代ReactDom.render
  • React 18 最重要的更新就是全面启用了 concurrent rendering。
    • 不算是新功能,而是react内部工作方式发生了变化。
    • 页面元素多,需要频繁re-render的场景下,React 15会掉帧,因为大量的同步计算阻塞了浏览器的渲染。state触发re-render时,react会遍历应用的所有节点,一旦开始就无法中断,所以第一种解决办法是re-render变成可中断的。
    • 思路:将re-render的js计算拆分成更小粒度的任务,可以随时暂停、丢弃的任务;在浏览器空闲的时候继续执行之前没执行完的任务。当js计算到16ms时使其暂停,把主线程让给ui渲染。
  1. React Hook需要注意什么
  • 仅在函数组件或自定义 Hook 中使用
  • 不要在循环、条件或嵌套函数中调用 Hook
  • 确保依赖项数组正确配置
  • 尽量避免在一个组件中管理过多的状态,可以通过拆分成多个小组件来减轻管理负担
  • use开头
  1. useState异步取值
  2. class组件、hook组件使用场景
  3. 线上出问题怎么处理
  4. promise的api

3. match Up

  1. px转rem原理
  2. 代码体积优化,以及成果
  • 使用webpack-bundle-analyzer分析代码体积
  • 压缩JavaScript代码
  • 删除无用代码
  • 资源合并bff
  1. react hook怎么进行性能优化
  • useMemo 和 useCallback
  • React.memo
  1. 在didMount之前请求会怎样
  • 可能导致重复请求
  • 请求响应时间可能会影响性能

4. 老虎国际

  1. 数据类型
  • 基础数据类型
    • null
    • undefined
    • boolean
    • number
    • String
    • Symbol
  • 复杂数据类型
    • Object
    • Array
    • Function
    • date
    • regexp
    • map
    • set
    • WeakMap
    • WeakSet
    • JSON
  • 区别
    • 基础数据类型直接存储在变量中,变量中存储的是实际值
    • 当将基础数据类型赋值给另一个变量时,实际值被复制,两个变量互不影响
    • 复杂数据类型的变量中存储的是对象的引用
    • 当将复杂数据类型赋值给另一个变量时,赋值的是对象的引用,两个变量指向同一个对象
  1. 变量提升
  • 在代码执行前,将变量和函数提升到作用域的顶部。
  • var声明的变量会被提升到作用域顶部,但未初始化。
  • let、const声明的变量也会被提升,但在声明之前访问会报错。
  • 使用函数声明的函数会被提升到作用域顶部,可以提前使用
  • 使用函数表达式定义的函数不会被提升。
  1. ||和??
  • ??:专门用于在值为 nullundefined 时提供默认值
  1. 箭头函数跟普通函数的区别
  • 箭头函数继承定义函数时的上下文,不会被call、bind、apply改变
  • 箭头函数没有arguments对象,可以使用...args
  • 箭头函数不能作构造函数,不能使用new
  1. 输出
5 + "2" // 52
"2" * "3" // 6
true + 1 // 2
"false" == false // false
null == undefined  // true
[] == '' // true
if ([]) return true; else return false; // true
  1. 输出
let num = 1; num.name = 'age'; console.log(num.name);
基本数据类型是不可变的,添加属性会失败
  1. 什么包装类型 包装类型是一种特殊的对象,用于将基本数据类型转换为对象。
  • new Number、String、Boolean
  1. 闭包及作用
  • 定义:它允许函数访问其词法作用域中的变量。
  • 作用:
    • 数据隐藏和封装:可以创建私有变量和函数,防止外部作用域直接修改、访问
    • 维持函数执行上下文:
    • 函数工厂:可以创建参数化的函数工厂,生成不同功能的函数
  1. 手写
编写js代码,实现奇数调用时,打印1,偶数调用时打印2,使用闭包完成
// 1 foo(); 
// 2 foo(); 
// 1 foo(); 
// 2 foo(); 
// 定义一个函数,返回一个闭包函数 
function createCounter() { 
    let count = 0; // 初始计数器值 
    // 返回一个闭包函数 
    return function() { 
    // 检查计数器值的奇偶性 
    if (count % 2 === 0) { 
            console.log(1); 
        } else { 
            console.log(2); 
        } 
        // 更新计数器值 
        count++; 
    }; 
}
  1. 闭包的问题
  • 内存泄露:设置为null
  1. 原型链
  • 每个对象都有原型对象protoType,原型对象有自己的原型,这样就形成了原型链
  1. 如何访问一个对象的原型
  • Object.getPrototypeOf()
  • proto
  • constructor.prototype
  1. 判断一个对象是另一个对象的原型
可以使用 `Object.getPrototypeOf()` 方法来获取一个对象的原型
console.log(Object.getPrototypeOf(obj) === Person.prototype); // 输出:true
  1. Object.create和new的区别
  • 创建对象的方式不同
    • Object.create接受一个原型对象proto作为参数,并可以选择的接受一个属性
    • new用于调用constructor,并创建一个新对象,执行构造函数中的代码,并将将this指向新对象,返回新对象。
    • Object.create不需要构造函数。new必须与构造函数一起使用。
  1. 构造函数可以写返回值吗
  • 构造函数不应该返回值的,因为其主要目的是用来初始化创建对象的。
  • 如果return了非对象,则忽略,如果返回了对象,则覆盖
  1. 原生方式查询dom
  • getElementById
  • getElementsByClassName
  • getElementsByTagName
  • querySelector
  • querySelectorAll
  1. 操作DOM的api
  • 创建元素:document.createElement()
  • 添加元素:appendChild(), insertBefore()
  • 移除元素:removeChild
  • 修改元素属性:element.className、element.style.color
  • 修改元素内容:innerHTML, textContent, innerText
  1. 获取一个节点在文档流中的位置
  • getBoundingClientRect
  1. 监听表单input的输入的事件
let inputElement = document.getElementById("myInput"); // 添加输入事件监听器 
inputElement.addEventListener("input", function(event) { 
    // 在控制台中输出输入框的值 
    console.log("Input value:", event.target.value); 
});
  1. input、change的区别
  • change:在表单元素的值发生变化并失去焦点时触发
  1. 浏览器本地存储方案
  • cookie:可以持久化存储在客户端的数据,每次请求都会被发送到服务器,4kb
  • localStorage:浏览器关闭后依然保留,5-10MB,可以存储键值对
  • sessionStorage:会话关闭后销毁,小于5MB,可以存储键值对
  1. 事件的捕获与冒泡
  • 事件捕获:是指事件从顶层元素(根元素)开始向下传播到目标元素的过程。
  • 事件冒泡:从目标元素开始向上传播到顶层元素的过程。
  1. 事件委托
  • 将事件处理添加到一个父元素上,而不是直接添加到每个子元素上。
  • 当子元素上的事件触发时,事件会向上传播到父元素,父元素事件被触发。
  1. 确认点击的是哪个元素
// 添加事件委托 
list.addEventListener("click", function(event) { 
    // 检查点击的是否是列表项 
    if (event.target.tagName === "LI") { 
    // 在控制台中输出点击的列表项文本内容 
    console.log("Clicked item:", event.target.textContent); 
    } 
});
  1. target和currentTarget的区别
  • target属性返回触发事件的目标元素,最初发生的元素
  • 在冒泡阶段,target始终不变
  • currentTarget返回绑定了事件处理程序的当前元素,即事件处理程序所附加的元素
  • 在事件传播过程中,currentTarget始终指向当前正在处理的事件元素
  • 冒泡阶段,currentTarget会随着时间传播的过程变化
  1. 加载css会阻塞js的执行
  • js文件加载和执行是异步的,不会直接受到css文件加载的影响
  • 虽然css可能会阻塞页面渲染,但不会阻塞js执行
  1. bfc
  • 会计格式化上下文,用来描述盒子布局发生变化的区域
  • 内部的盒子在垂直方向上排列
  • 内部的盒子相互之间存在margin边距折叠
  • 浮动元素的高度需要计算
  • float
  • position:absolute、fixed
  • overflow:不是visible
  • display:flex
  1. css module
  • 解决命名冲突,支持导入导出
  1. useState设置的初始值
  • 设置函数的话需要设置函数的返回值
  1. react中key
  • 用于帮助react识别列表中的各个元素的唯一标识。当列表顺序变化时,react根据key确定哪些元素新增,尽可能减少dom操作