自我介绍
面试官,你好,我叫刘艳兰。 21 年毕业于湖南工学院,软件工程专业,有 3 年前端工作经验,之前在四只悟空公司工作。技术栈主要是 React 生态相关。开发的项目主要是 gocart 购物网站平台,是包括 web、wap 端和 cms、oms 的,前台网站使用 Nextjs 框架搭建,后台使用 Umi 框架搭建。目前项目已经成功上线使用。ops 和一套关联的公司内部管理系统,运营系统,包括权限管理、订单管理、商品管理等运营相关内容。在过去的团队中,主要承担的任务是进行项目需求的开发和维护工作。
1. 介绍一下项目
2. 前端权限怎么做,token 的作用
添加一个 src/access.js 文件为我们的权限定义文件,在 routes.js 文件里面添加一些权限字段(比如:access 字段、authority 字段)
cms 中使用 umi max 的 useAccess 和 Access
使用 Access 包裹元素,使用 useAccess 获取权限相关信息
比如 access.canAuthority、 access.canBuAdmin 为 true 或 false
3. js 事件循环机制
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。
在 Chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,其他线程只需要在合适的时候将任务加入队列末尾即可。
过去讲消息队列简单的分为宏队列和微队列,这种说法已经无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。
根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在一个队列,不同的任务可以属于不同的队列。不同的任务队列有不同的优先级。在一次事件循环中,由浏览器自行决定,取哪个队列的任务执行。但是浏览器必须有一个微队列,微队列的任务的优先级是最高的,必须优先调度执行。
常见的微任务有: Promise.then MutationObserver Object.observer(目前已废弃,Proxy 对象替代) process.nextTick (nodejs)
常见的宏任务: script(可以理解为外层同步代码) setTimeout / setInterval UI rendering / UI 事件 postMessage、MessageChannel setImmediate、I/O(输入输出事件)(nodejs)
内存泄露
内存泄露是指由于疏忽或错误造成程序没有及时释放已经不再使用的内存。对于持续运行的服务进程,必须及时释放不再用到的内存,否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
自动内存管理,减轻程序员的负担,这被称为“垃圾回收机制”
不再需要的内存 - 垃圾 无法触达的内存 - 回收 不再需要但还能触达的内存 - 内存泄露 无法触达但也回收不了的内存 - 内存泄露
闭包会不会导致内存泄露?会 为什么? 因为:
持有了不再需要的函数引用,会导致函数关联的词法环境无法销毁,从而导致内存泄露。
当多个函数共享词法环境时,会导致词法环境膨胀,从而导致出现无法触达也无法回收的内存空间,从而导致内存泄露。
垃圾回收机制
JS 具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中使用的内存
原理:垃圾收集器会定期(周期性)找出那些不再继续使用的变量,然后释放其内存
通常情况下有两种实现方式:
标记清除(主要使用)
引用计数(效率高,有缺陷)
标记清除
当变量进入执行环境,就标记这个变量为“进入环境”。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境”。
垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文的变量引用的变量的标记去掉。
在此之后再被加上标记的变量就是待删除的了,原因是任何上下文中的变量都访问不到它们了。
随后垃圾回收程序做一次内存清理,销毁带标记的所有值并回收它们的内存。
var m = 0,
n = 19; // 把 m、n、add() 标记为进入环境
add(m, n); // 把 a、b、c 标记进入环境
console.log(n); // a、b、c 标记为离开环境,等待垃圾回收
function add(a, b) {
a++;
var c = a + b;
return c;
}
引用计数
语言引擎有一张“引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是 0,就表示这个值不再用到了,因此可以将这块内存释放。
如果一个值不再需要了,引用数却不为 0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。
const arr = [1, 2, 3];
console.log('hello world');
上面代码中,数组[1, 2, 3]是一个值,会占用内存。变量 arr 是仅有的对这个值的引用,因此引用次数为 1。尽管后面的代码没有用到 arr,它还是会持续占用内存。
如果需要这块被垃圾回收机制释放,只需要设置如下:
arr = null;
通过设置 arr 为 null,就解除了对数组[1, 2, 3]的引用,引用次数为 0,就被垃圾回收了。
常见的内存泄漏情况
- 意外的全局变量
function foo(tag) {
bar = 'hello';
}
另一种意外的全局变量可能有 this 创建
function foo() {
this.val = 'hello';
}
// foo 调用自己,this 指向了全局对象(window)
foo();
以上使用严格模式,可以避免意外的全局变量
- 定时器也可能造成内存泄漏
var someResource = getData();
setInterval(function () {
var node = document.getElementById('Node');
if (node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource);
}
}, 1000);
如果 id 为 Node 的元素从 DOM 中移除,定时器仍会存在。同时,因为回调函数中对 someResource 的引用,定时器外面的 someResource 也不会被释放。
包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放。
function bindEvent() {
var obj = document.createElement('xxx');
var unused = function () {
console.log(obj, '闭包内引用obj,obj不会被释放');
};
obj = null; // 解决办法
}
没有清理对 DOM 元素的引用同样造成内存泄漏
const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom 删除了
console.log(refA, 'refA'); // 但是还存在引用,能输出整个 div,没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用
包括使用事件监听 addEventListener 监听的时候,在不监听的情况下使用 removeEventListener 取消事件监听。
4. 你对闭包的理解
一个函数对其周围状态(词法环境)的引用捆绑,这样的组合就是闭包。
也就是说,闭包可以让你在一个内层函数中访问到其外层函数的作用域。
在 js 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁。
function init() {
var name = 'joy'; // name 是一个被 init 函数创建的局部变量
function displayName() {
// displayName 函数是内部函数,一个闭包
alert(name); // 使用了 init 函数中声明的变量(函数外部变量)
}
displayName();
}
init();
displayName() 没有自己的局部变量。然而,由于闭包的特性,它可以访问到外部函数的变量。
5. react 渲染流程,怎么实现可中断渲染 fiber 架构
6. useEffect 依赖项不同的区别
7. css 3 动画属性有哪些?怎么在 js 中实现动画
transition
在 js 中实现动画 requestAnimationFrame 在下一次重绘之前调用指定的回调函数更新动画
cancelAnimationFrame 取消动画