前端社招面试题+答案

2,275 阅读19分钟

题目一(前端社招Zoom一面)

题目来源: www.nowcoder.com/discuss/957…

1. 说说你常用的ES6命令

  • const let
  • 展开运算符
  • 箭头函数
  • set map WeakSet WeakMap
  • symbol
  • bigint
  • async await
  • promise
  • class
  • proxy
  • reflect

2. 说说Symbol你在平时使用是如何使用的

  1. 消除魔法字符
  2. 作为对象属性,避免重复
  3. 作为类的私有方法,ts有private, es10有#

3. Set转Array有哪些方式

  1. Array.from() 可转化可迭代对象为数组
  2. const array = [...set]
  3. 循环遍历

4. 写一道函数柯里化( sum ( 1 )( 2 )( 3 )( 4 )( 5 )( 6 ) sum ( 1 , 2 )( 3 , 4 )( 5 ) 注意参数个数不确定,不是纯柯里化)

柯里化: 将是可以将 f(a,b,c) 转换为可以被以 f(a)(b)(c)或 f(a,b)(c)的形式进行调用

const curry = (fn, n = fn.length, args = []) =>
  n === 0
    ? fn(...args)
    : (...args1) => curry(fn, n - args1.length, [...args, ...args1]);

function sum(a, b, c) {
  return a + b + c;
}

const currySum = curry(sum);

console.log(currySum(1)(2, 3));

5. 你知道requestAnimationFrame么

requestAnimationFrame浏览器会在下次重绘前执行回调函数,一般用来执行动画。

fps: 浏览器的刷新率一般等于显示器的刷新率,正常60FPS, 浏览器一秒渲染60次,间隔为16.7ms.

我们知道setInterval可以按照时间间隔执行,但是由于是宏任务,执行的时间间隔并不是严格按照我们所设置的,会带来卡顿,体验不好,requestAnimationFrame就是解决这个问题

优点:

  1. CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,requestAnimationFrame则不会
  2. 函数节流:按照屏幕帧数渲染,保证流畅的同时不会造成额外渲染

6. 看一道宏任务微任务(其中包含 requestAnimationFrame)

  1. 宏任务: setTimeout setInterval requestAnimationFrame
  2. 微任务: Promise.then catch finally 注意点:
  3. await会执行跟在后面的表达式,下面的代码会放到微任务队列
  4. 多个script时执行完微任务就会执行下一个script里的代码,等都执行完才会执行第二次宏任务
  5. requestAnimationFrame的执行时机的是慢于同步任务,但是和宏任务的执行时机是不确定的。多次执行两种结果都有,
setTimeout(function onTimeout() {
  console.log("timeout");

  requestIdleCallback(function onIdle2() {
    console.log("idle2");
  });
}, 0);

Promise.resolve().then(function onFulfill1() {
  console.log("promise1");
});

requestAnimationFrame(function onAf() {
  console.log("raf");

  Promise.resolve().then(function onFulfill2() {
    console.log("promise2");
  });
});

requestIdleCallback(function onIdle1() {
  console.log("idle1");
});

// 输出            
promise1
raf
promise2
idle1
timeout
idle2

7. React中 Class组件中的this和Hook中的this分别指向什么

class中指组件实例 如点击事件需要绑定this,Hook中是undefined

8. Hook和Class组件分别对应生命周期的情况有哪些

useEffect 相当于 componentDidMount componentDidUpdate componentWillUnmount

// componentDidMount
useEffect(()=>{console.log('第一次渲染时调用')},[])

// componentDidUpdate
useEffect(()=>{console.log('每次更新')}) 

// componentWillUnmount
  useEffect(() => {
    return () => {
      console.log('组件销毁');
    };
  });

9. useEffect原理,他用了什么数据结构

数据结构: 单向循环链表
原理: 每个hook会生成生成hook对象,按顺序挂载到hooks链表上,useEffect 以及 useLayoutEffect比较特殊,会额外生成effect对象,一个单向循环链表

为什么是单向循环

为了实现两个逻辑,一个是新增节点,一个是从头部开始遍历,一般情况我们会生成两个变量记录头结点和尾节点,单向循环可以只用一个变量实现

hooks原理

  1. 每个hook内部方法都有mountXxx和updateXxx, 会在首次加载和更新时调用, 如useEffect内部叫mountEffect和updateEffect, useRef有mountRef何updateRef,
  2. 主要是fiber.memoizedStatefiber.updateQueue这两个属性,上面说的mountXxx方法就是生成一个hook对象,挂载到memoizedState上,形成一个单向链表,而有处理副作用的方法useEffect, useLayoutEffect会额外生成一个effect对象,是一个单向循环链表,挂载到updateQueue上,
为什么是链表,而不是数组或map
  1. 有个替代方案是限制一个组件调用多次 useState(), 会导致自定义hook无法使用
  2. 如果使用key做标识,会有命名冲突
  3. symbol做标识,会导致同一个hook无法调用两次
  4. 多重继承问题,多个hook同时调用同一一个hook ...
    作者说过key做标识可以实现,但是同样需要遵守额外的规则,而且比遵守Hooks规则的阻力会更大

10. 说一下Fiber和React事件调度机制

React 15 架构

  • Reconciler(协调器)—— 负责找出变化的组件;
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上;

React 16 架构

  • Scheduler(调度器)—— 调度任务的优先级,expirationTime
  • Reconciler(调和器)—— 负责找出变化的组件:更新工作从递归变成了可以中断的循环过程。Reconciler内部采用了Fiber的架构
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

React 17 :

  • Scheduler(调度器)—— Lane 模型

fiber

之前Reconciler阶段采用递归的方式创建虚拟DOM并提交Dom Mutation,整个过程同步并且无法中断工作或将其拆分为块, 会消耗大量的时间,复杂情况下容易阻塞线程,造成卡顿, fiber就是让Reconciler阶段变成可以打断的,本质是环形链表

React事件调度机制

调度: 利用浏览器提供的queueMicrotask把遍历fiber树这个任务压入浏览器微任务队列等待执行,requestIdleCallback 貌似是个不错的选择,但是它存在兼容和触发不稳定的原因,react17 中采用 MessageChannel 来实现

expiration time
const task; 
// 优先级越高,delayTimeByTaskPriority越低
task.expirationTime = MAX_INT_31 - (currentTime + delayTimeByTaskPriority)
  1. 两种任务队列
    • timerQueue: 过期时间大于现在的时间
    • taskQueue: 过期时间小于现在的时间 都是小顶堆,最上面就是优先级最高的

缺点: expirationTime字段耦合了优先级批次这两个概念,
批处理问题: 在expiration time架构里,有两次更新拥有相同的expirationTime时,这时候它们本来应该同时处理。但实际上因为js是单线程的,这两个更新必须要区分个先后出来, 但如果这两个事件还有联系,会出现不可预测的问题,如异步Suspense问题。

Lane 模型
  • 优先级: Lane
  • 批次: Lanes,Lanes 是一个整数,该整数所有二进制位为 1 对应的优先级任务都将被执行。例如 Lanes 为 17 时,表示将批量更新 SyncLane(值为 1)和 DefaultLane(值为 16)的任务。类比车道
// 同步事件
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; 

// 连续触发优先级,例如:滚动事件,拖动事件等 
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010; 
export const InputContinuousLane: Lanes = /* */ 0b0000000000000000000000000000100;
插队

高优先级会打断低优先级的任务,假如执行了一半,那么会将Fiber树还原

饥饿问题

低优先级被连续插队后,如果过期了,会将当前lane添加到expiredLanes上,在后续执行该任务的时候使用同步渲染,避免任务饥饿的问题

11. Flutter你有遇到过什么性能或者兼容性的问题么?

12. 算法:括号匹配

实现一个算法,字符串包含"[]" , "()" , "{}",判断是否正确闭合
解法: 栈,先进后出,如果最后栈内长度为0,说明刚好匹配

思路: 栈,先进后出

function brackets(str) {
    const arr = [];
    const map = {
        '{': '}',
        '[': ']',
        '(': ')'
    }
    for(let i = 0, l = str.length; i < l; i++) {
        let s = str[i];
        if(map[s]) {
            arr.push(s);
        }
        else if(map[arr.at(-1)] === s) {
            arr.pop();
        }
    }
    return arr.length === 0;
}

题目2(欧科云链)

来源: juejin.cn/post/710098…

1. hooks为什么不能放在if里

单向链表顺序存储

2. useContext

const ThemeContext = React.createContext(themes.light);

 <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>

const theme = useContext(ThemeContext);

3. useMemo

缓存计算结果

4. 自定义hook

// 判断是否是首次加载, 比如useEffect首次不加载
export default () => {
  const isMountRef = useRef(false);

  useEffect(() => {
    isMountRef.current = true;
  }, []);

  return isMountRef.current;
};


// 异步更新下获取最新状态
const useSyncCallback = (callback) => {
  const [proxyState, setProxyState] = useState({ current: false });

  const Func = useCallback(() => {
    setProxyState({ current: true });
  }, [proxyState]);

  useEffect(() => {
    if (proxyState.current === true) setProxyState({ current: false });
  }, [proxyState]);

  useEffect(() => {
    proxyState.current && callback();
  });

  return Func;
};

5. 数字精度问题

IEEE754标准,数字二进制表示, 0.1是个无限小数,导致误差 0.1 + 0.2 === 0.3 // false 解决方法:

1. 放大后比较 
(0.1*1000+0.2*1000)/1000==0.3
2. Number.EPSILON 它表示 1 与大于 1 的最小浮点数之间的差
0.1 + 0.2 - 0.3 <  Number.EPSILON // true

6. 检测数据类型

1. typeof 
typeof null // object
typeof (()=>{}) // function

2. instanceof 
根据原型链查找,如果网页中包含多个框架,会有问题, isArray就是因此出现,使用判断方法是toString

3. consructor
const a = []
a.constructor === Array // true

4. Object.prototype.toString.call
Object.prototype.toString.call('')  // [object String]

7. Promise链式调用,以下分别输出什么

1.
Promise.reject(1)
  .then((num) => {
    console.log(num);
  })
  .catch((num) => {
    return num + 1;
  })
  .then((num) => {
    console.log(num);
  });
  
// 2

2.
Promise.resolve(1)
        .then((num) => {
            console.log(num);
        }).catch((num) => {
            return num + 1;
        }).then((num) => {
            console.log(num);
        });
        
// 1
// undefined

8. 输入框获取焦点边框高亮

input[type=text]:focus, select:focus{
	border:1px solid red;
	outline:none;
}

9. 列表表头吸顶

stiky 粘性布局 relative和fixed的结合使用

.sticky {
  position: sticky;
  top: 0;
}

10. 一个父级div,有两个子div,一个固定高度,另一个高度填充满

display: flex
flex-direction: colom

flex: 1

11. 手写算法

// [{ price: 1, size: 2 }, { price: 2, size: 2 }, { price: 1, size: 1 }]] 依次按照price、size降序排序

let arr = [
  { price: 1, size: 2 },
  { price: 2, size: 2 },
  { price: 1, size: 1 },
];

arr.sort((a, b) => {
  return a.price === b.price ? b.size - a.size : b.price - a.price;
});

12. 重绘&重排,那些操作可以导致

重绘: 不影响布局 color background visibility border-radius

重排: 影响元素布局 width height display margin padding border

13. 浏览器渲染过程

dom + cssom -> render -> 布局 -> 绘制 -> 渲染层合并

14. component、PureComponent区别

PureComponent相当于自动帮我们在shouldComponentUpdate对propsstate进行浅比较

15. 箭头函数this指向

箭头函数this无法被修改

var name = "global";
var obj = {
  name: "ngnce",
  log: () => {
    console.log(this.name);
  },
};
obj.log(); // global

16. 哪些样式可以继承

font: font-family font-size
text: text-align line-height 
color
visibility

17. e.target e.currentTarget有什么区别

  • target:触发事件的元素。
  • currentTarget:事件绑定的元素 事件冒泡阶段,e.currenttargete.target是不相等的,但是在事件的捕获阶段,e.currenttargete.target是相等的。

18. 图片懒加载,路由懒加载实现原理是什么

图片懒加载

监听元素是否处于可视范围内,也就是innerHeight是否大于getBoundingClientRect().top,如果大于,赋值src

路由懒加载

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')

基于es6的import()动态引入和webpack的代码分割,Webpack编译打包时,会把每个路由组件的代码分割成一个个js文件,初始化时不会加载这些js文件,只当激活路由组件才会去加载对应的js文件。

19. hooks你们实践的经验是什么

setCount(count+1) 
setCount((prevCount) => prevCount + 1) // 依赖上一个值

useEffect 注意依赖

useReducer 管理复杂对象的修改
useMemo 缓存计算结果,仅在复杂计算下和传递对象时使用
useCallback 包裹传递给子组件的方法,避免导致子组件重新渲染

20. 手写深拷贝,循环引用问题如何解决

function clonefunc(func) {
  let paramReg = /\((.*?)\)/;
  let bodyReg = /\{(.*)\}/;
  let funcString = func.toString();

  if (func.prototype) {
    let param = paramReg.exec(funcString);
    let body = bodyReg.exec(funcString);

    if (body) {
      if (param) {
        let arrParam = param[1].split(",");
        return new Function(...arrParam, body[1]);
      } else {
        return new Function(body[1]);
      }
    }
  } else {
    return eval(funcString);
  }
}

function deepclone(obj, hash = new WeakMap()) {
  if (hash.get(obj)) return hash.get(obj);

  const type = [Date, RegExp, Set, Map]
  if (type.includes(obj.constructor)) return obj.constructor(obj);

  let allDesc = Object.getOwnPropertyDescriptors(obj);
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);

  hash.set(obj, cloneObj);

  for (let key of Reflect.ownKeys(obj)) {
    if (obj[key] !== null && typeof obj[key] === "object") {
      cloneObj[key] = deepclone(obj[key], hash);
    } else if (typeof obj[key] === "function") {
      cloneObj[key] = clonefunc(obj[key]);
    } else {
      cloneObj[key] = obj[key];
    }
  }

  return cloneObj;
}

let obj = {
  func: function () {
    console.log("func");
  },
  arrow: () => {
    console.log("arrow");
  },
  symbol: Symbol(),
  date: new Date(),
  array: [1, 2, 3],
};
obj.loop = obj;

newObj = deepclone(obj);

obj.array[0] = 100;

console.log(newObj);

题目3 (蚂蚁金服)

题目来源: juejin.cn/post/695791…

js 有哪些基本类型,说说 typeof 与 instanceof

// 基本类型
string number boolean null undefined symbol bigint

// typeof
typeof null // object
typeof function(){} // function
// instanceof

function myInstanceof(left, right) {
  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);
  }
}

说说new 操作符;

function _new(obj, ...rest) {
  // 基于obj的原型创建一个新的对象
  const newObj = Object.create(obj.prototype);

  // 获取
  const result = obj.apply(newObj, rest);

  // 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
  return result instanceof Object ? result : newObj;
}

什么是 event loop;

事件循环机制, 模拟实现多线程,

  1. 宏任务: setTimeout setInterval requestAnimationFrame
  2. 微任务: Promise.then catch finally requestAnimationFrame 是在渲染前执行,与setTimeout的执行顺序不确定

Promise 的用法?了解 allSettled 方法么,怎么实现?

Promise 异步解决方案,三个状态pending、fulfilled、rejected的转换, 状态不可逆。

  • race: 只要一个请求完成就返回,不管成功还是失败
  • all: 一个失败就结束,
  • allSettled: 等待全部执行完,不管成功还是失败
const allSettled = (values) => {
  let promises = [].slice.call(values);
  return new Promise((resolve, reject) => {
    let result = [],
      count = 0;
    promises.forEach((promise) => {
      Promise.resolve(promise)
        .then((value) => {
          result.push({ status: "fulfilled", value });
        })
        .catch((err) => {
          result.push({ status: "rejected", value: err });
        })
        .finally(() => {
          count++;
          if (count === promises.length) {
            resolve(result);
          }
        });
    });
  });
};

说说闭包;

闭包是指有权访问另一个函数作用域中变量的函数,本质 就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。

ES5 实现继承的方法

// 组合寄生式继承
function inherit(children, parent) {
  // 创建对象
  let prototype = Object.create(parent.prototype);
  // 增强对象
  prototype.constructor = children;
  // 指定对象
  children.prototype = prototype;
}

function Parent(name) {
  this.name = name;
  this.num = [0, 1, 2];
}

Parent.prototype.sayName = function () {
  alert(this.name);
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

inherit(Child, Parent);

Child.prototype.sayAge = function () {
  console.log(this.age);
};

说说跨域

浏览器的一种安全限制手段, 域名不同,协议不同都是跨域,

  • jsonp
  • cors
// 前端 
axios.defaults.withCredentials = true 
// 服务端 
跨域参数 Access-Control-Allow-Origin不能设置为*, 必须设置具体域名
  • 服务器代理
  • postmessage
  • weksocket

commonJS 与 ES6 模块化区别;

  1. 语法不同,commonjs是module.exports,exports导出,require导入;ES6是export导出,import导入。
  2. commonjs是运行时加载模块,ES6是在静态编译期间就确定模块的依赖。
  3. ES6在编译期间会将所有import提升到顶部和function提升,commonjs不会提升require。
  4. commonjs: 当module.exports的值是字符串、数字等原始类型时,赋值是值拷贝才会产生导出值改变不会导致导入值改变的现象。ES6是导出的一个引用,内部修改可以同步到外部。
  5. 两者的循环导入的处理不同,commonjs是每个模块都会个默认导出空对象,后续在这个对象上添加导出的属性,es6的导入则是指向被加载模块的引用,需要用户自己保证取值正确
  6. commonjs中顶层的this指向这个模块本身,而ES6中顶层this指向undefined。

webpack 了解么?loader、plugin 分别是干嘛的?如何实现一个 loader?

  • webpack: 打包工具
  • loader:转换器
  • plugin: 基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
// 简单的loader
module.exports = function (source) {
  return source.replace(/var/g, "const");
};

webpack 如何优化打包速度;

  • 忽略解析第三方模块:noParse, 如lodash等不依赖其它模块的库
  • ignorePlugin: 例如moment会包含多个语言包,我们可以忽略,并手动导入需要的
  • 缓存: cacheDirectory
  • 多线程打包: thread-loader
  • 多进程压缩jswebpack-parallel-uglify-plugin
  • babel-loader: 也可以开启缓存
  • DLLPlugin: 动态链接库,如vue, react, 只构建一次,后面直接使用
  • 开发时开启热更新

说一下 css 盒模型,border-box 和 content-box 区别;

  • content-box: 标准盒模型 宽高只包括content
  • border-box: 怪异盒模型 宽高包括content, padding, border

说说 BFC;

块级格式上下文,一个独立的区域,让处于BFC内部的元素与外部的元素不会相互影响

约束规则:

  • box会在垂直方向排列
  • margin塌陷
  • 不会与float元素重叠
  • 计算高度包括float元素
  • 与外界元素互不干扰

触发条件: 根元素 position: absoluted/ fixed display: inline-box / table float overflow不为visible flex grid

应用: margin塌陷的问题, 不属于同一个BFC可以解决 float高度塌陷 自适应两栏布局

移动端响应式布局怎么实现的;

设置视图大小,禁止缩放

<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />

css单位

  1. 使用pxToRem转换函数
$rootFontSize: 375 / 10;
// 定义 px 转化为 rem 的函数
@function px2rem ($px) {
    @return $px / $rootFontSize + rem;
}

.demo {
    width: px2rem(100);
    height: px2rem(100);
}
  1. postcss-pxtorem插件
// input
h1 {
    margin: 0 0 20px;
    font-size: 32px;
    line-height: 1.2;
    letter-spacing: 1px;
}

// output
h1 {
    margin: 0 0 20px;
    font-size: 2rem;
    line-height: 1.2;
    letter-spacing: 0.0625rem;
}

说一说 flex 布局,有了解 么;

justify-content: flex-start | center | flex-end | space-between | space-around
align-content: flex-start | center | flex-end | space-between | space-around 多行生效,整体
align-items: flex-start | center | flex-end
flex-direction: row | colom 方向
flex-wrap: nowrap | wrap 换行
order 排序
flex: flex-grow | flex-shrink | flex-basis 默认值为 0 1 0
flex:1,等价于flex: 1 1 0%
align-self: auto | flex-start | flex-end | center | baseline | stretch 单个属性

Grid网格布局: 可以方便的在行和列上进行排列

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  grid-template-rows: 100px 100px 100px;
}

有兼容 retina 屏幕的经历吗?如何在移动端实现 1 px 的线;

与dpr无关,dpr影响的是精细度。主要原因是设备不支持< 1px的值. 假设设计图是750px,有一条1px的边框,到375px宽度的手机显示,需要缩小一倍,也就是0.5px,然而css里最低只支持1px大小,不足1px就以1px显示。

小数:
.border { border: 0.5px solid #999 }

缺点: ios 8+ 支持, 安卓不支持

伪类+transform
.scale-1px{
  position: relative;
  border:none;
}
.scale-1px:after{
  content: '';
  position: absolute;
  bottom: 0;
  background: #000;
  width: 100%;
  height: 1px;
  transform: scaleY(0.5);
  transform-origin: 0 0;
}

说一下 react 组件的生命周期

constructor
static getDerivedStateFromProps
render
componentDidMount

static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()

componentWillUnmount()

react 组件如何做性能优化,说说 pureComponent;

  • key
  • useCallback
  • useMemo
  • Memo
    pureComponent: 设置了浅比较的shouComponentUpdate

调用 setState 之后发生了什么;

  1. React 会为当前节点创建一个更新队列。
  2. 然后会触发reconciliation 过程,在这个过程中,会使用名为 Fiber 的调度算法,开始生成新的 Fiber 树, Fiber 算法的最大特点是可以做到异步可中断的执行。
  3. 然后 React Scheduler 会根据优先级高低,先执行优先级高的节点,具体是执行 doWork 方法。
  4. 在 doWork 方法中,React会执行一遍更新列队中的方法,以获得新的节点。然后对比新旧节点,为老节点打上 更新、插入、替换 等 Tag。当前节点 doWork 完成后,会执行 performUnitOfWork 方法获得新节点,然后再重复上面的过程。
  5. 当所有节点都 doWork 完成后,会触发 commitRoot 方法,React 进入 commit 阶段。
  6. 在 commit 阶段中,React 会根据前面为各个节点打的Tag,一次性更新整个 dom 元素。

了解 fiber 么?解决了什么问题?具体原理是;

react之前的更新是同步、不可中断的(使用了递归) ,为了解决这个问题,React提出Fiber架构,意在将更新流程变为异步、可中断的。 将diff过程从递归遍历变成了链表遍历算法。

有用过 hooks 么?怎么看待 hooks?它的原理是;

  1. 每个hook内部方法都有mountXxx和updateXxx, 会在首次加载和更新时调用, 如useEffect内部叫mountEffect和updateEffect, useRef有mountRef何updateRef,
  2. 主要是fiber.memoizedStatefiber.updateQueue这两个属性,上面说的mountXxx方法就是生成一个hook对象,挂载到memoizedState上,形成一个单向链表,而有处理副作用的方法useEffectuseLayoutEffect会额外生成一个effect对象,是一个单向循环链表,挂载到updateQueue上,

了解过 react 最新的一些动态么?time slice 、suspense、server component 能说说么;

Time Slicing 时间分片

判断一帧有空闲时间,则去执行某个任务,目的是为了解决当任务需要长时间占用主进程,导致更高优先级任务(如动画或事件任务),无法及时响应,而带来的页面丢帧(卡死)情况。

requestIdleCallback:

  • 实验 api,兼容情况一般。
  • 实验结论: requestIdleCallback FPS只有20ms,正常情况下渲染一帧时长控制在16.67ms (1s / 60 = 16.67ms)。该时间是高于页面流畅的诉求。

react选择自己实现:

  1. 当前帧结束时间: 我们知道requestAnimationFrame的回调被执行的时机是当前帧开始绘制之前。也就是说rafTime是当前帧开始时候的时间,如果按照每一帧执行的时间是16.66ms。那么我们就能算出当前帧结束的时间, frameDeadline = rafTime + 16.66。

  2. 当前帧剩余时间:当前帧剩余时间 = 当前帧结束时间(frameDeadline) - 当前帧花费的时间。关键是我们怎么知道'当前帧花费的时间',这个是怎么算的,这里就涉及到js事件循环的知识。react中是用MessageChannel实现的。

let frameDeadline; // 当前帧的结束时间
let channel = new MessageChannel();

// 我们可以理解requestAnimationFrame的回调执行是在当前的主线程中,
channel.port2.onmessage = function () {
  let timeRema = frameDeadline - performance.now();
  if (timeRema > 0) {
    // 有空闲时间
  }
};
// 计算当前帧的剩余时间
function timeRemaining() {
  // 当前帧结束时间 - 当前时间
  // 如果结果 > 0 说明当前帧还有剩余时间
  return;
}
window.requestIdleCallback = function (callback) {
  requestAnimationFrame((rafTime) => {
    // 算出当前帧的结束时间 这里按照16.66ms一帧来计算
    frameDeadline = rafTime + 16.66;

    // 这里发送消息,MessageChannel是一个宏任务,也就是说上面onmessage方法会在当前帧执行完成后才执行
    // 这样就可以计算出当前帧的剩余时间了
    channel.port1.postMessage("haha"); // 发送内容随便写了
  });
};

Suspense

延迟加载组件

// This component is loaded dynamically
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // Displays <Spinner> until OtherComponent loads
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}

server components

就是在服务器上渲染好React组件,前端请求后直接展示。之前我们是基于HTTP的API接口获取数据,现在直接拿到的就是组件+数据。

Server Component具备服务端的能力,比如直接查询DB、访问文件等,通过传送序列化的“指令”来更新客户端组件和视图

什么是 CSRF 攻击,怎么预防;

跨站请求伪造,诱导用户点击已登录的网站,执行危险操作

  1. SameSite 可以让Cookie不随着跨域请求发送

    • Strict: 只允许相同站点请求的 Cookie\
    • Lax: 允许部分第三方请求携带 Cookie\
    • None: 都允许
  2. Token验证

    cookie是发送时自动带上的,而不会主动带上Token,可以在每次发送时主动发送Token 简单token:uid(用户唯一的身份标识) + time(当前时间的时间戳) + sign(签名)。

  3. Referer验证, Referer也容易被修改\

  4. 验证码,效果很好,但是体验较差

为什么说用 css 实现动画比 js 动画性能好;

渲染线程分为main thread(主线程)和compositor thread(合成器线程)。 主线程中维护了一棵Layer树,在compositor thread,维护了同样一颗Layer树,这两棵树的内容是拷贝关系。因此可以彼此不干扰,当Javascript在main thread修改css时,compositor thread可以用自己维护的Layer树做渲染。当Javascript繁忙导致主线程卡住时,合成到屏幕的过程也是流畅的。

CSS的transform属性来实现动画效果,就可以避免重排和重绘,直接在非主线程上执行合成动画操作

什么是 合成层;

在 DOM 树中每个节点都会对应一个渲染对象(RenderObject),当它们的渲染对象处于相同的坐标空间(z 轴空间)时,就会形成一个 RenderLayers,也就是渲染层。渲染层将保证页面元素以正确的顺序堆叠,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示。

这个模型类似于 Photoshop 的图层模型,在 Photoshop 中,每个设计元素都是一个独立的图层,多个图层以恰当的顺序在 z 轴空间上叠加,最终构成一个完整的设计图。

对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

http2 与 http1.1 区别;

  • 二进制分帧: 1.1响应是文本格式,而2.0把响应划分成了两个帧,图中的HEADERS(首部)和DATA(消息负载)
  • 多路复用: 1.1有队头阻塞,必须等其它请求处理完,2.0,可以有任意流,但服务器的荷载是有限的,流控制会帮我们动态调整
  • 头部压缩: 1.1 直接传输文本,客户端和服务器共同维护一个对照表,比如1表示get请求,传输的消耗会大大减少, 像user-agent、cookie这种只有首部名称而没有值的首部,第一次传输需要传在静态字典中的索引以及他的值,值会压缩后传输。第二次可以直接传输索引值
  • 服务器推送: 浏览器请求index.html,服务器可以把index.htmlstyle.cssexample.png全部发送给浏览器。需要手动配置服务器推送那些内容

说一说 http 缓存;

强缓存

  • Expires: Expires= max-age + 请求时间, 修改本地时间可以让缓存失效
  • Cache-Control private public no-cache no-store 协商缓存
  • Last-Modified: 最后修改时间, 打开文件不修改也算,以秒计,多次短时间内修改无法感知
  • ETag: 基于内容的标识,需要服务器额外计算

http 状态码;

  • 100 继续 post header请求确认
  • 101 切换协议 websocket http2.0
  • 200 成功
  • 201 请求成功并且服务器创建了新的资源
  • 301 永久重定向
  • 302 临时重定向
  • 303 临时重定向,并使用GET 请求新的 URI
  • 304 未修改过 缓存请求返回
  • 400 客户端请求错误
  • 401 请求未授权
  • 403 禁止访问
  • 404 找不到
  • 500 服务器错误
  • 503 暂时无法处理请求,如维护或过载

斐波那契数列

// 递归 时间复杂度O(2^N)
function fibonacci(n) {
  if (n == 1 || n == 2) {
    return 1;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 缓存 时间复杂度O(n)
const fibonacci = (n) => {
  const arr = [1, 1];
  for (let i = 2; i < n; i++) {
    arr[n] = arr[n - 2] + arr[n - 1];
  }
  return arr[n];
};