# 没有难度但应该熟记的知识

# 从浏览器地址栏输入 url 到页面加载的过程是怎么样的?

  • DNS 解析:将域名解析成 IP 地址
  • TCP 连接:TCP 三次握手
  • 发送 HTTP 请求
  • 服务器处理请求并返回 HTTP 报文
  • 关闭 TCP 连接,通过四次挥手释放 TCP 连接
  • 浏览器解析渲染页面
    • 渲染进程解析解析 HTML 文本,构建 DOM 树
    • 解析 CSS,生成 CSS 规则树
    • 合并 DOM 树和 CSS 规则,生成 render 树后绘制
    • 加载外部脚本
    • 解析并执行部分脚本代码
    • DOM 树构建完成(PS 可以使用 DOMContentLoaded 事件插入在此之后)
    • 加载图片等外部文件
    • 页面执行完毕后再执行 window.onload

# 重绘和重排

  • 重排(reflow):当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流
  • 重绘(repaint):当页面中元素样式的改变并不影响它在文档流中的位置时,浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘
  • 导致回流的操作:
    • 页面首次渲染
    • 浏览器窗口大小发生改变
    • 元素尺寸或位置发生改变
    • 元素内容变化(文字数量或图片大小等等)
    • 元素字体大小变化
    • 添加或者删除可见的 DOM 元素
    • 激活 CSS 伪类(例如::hover)
    • 查询某些属性或调用某些方法
  • 使用 diff 算法的好处之一就是减少频繁操作节点导致过多的重绘和重排带来的性能损耗

# 防抖和节流

  • 防抖是将多次执行变为最后一次执行
  • 节流是将多次执行变成每隔一段时间执行

# TCP 三次握手

  • 客户端发送一个带 SYN=1,Seq=X 的数据包到服务器端口
  • 服务器发回一个带 SYN=1, ACK=X+1, Seq=Y 的响应包以示传达确认信息
  • 客户端再回传一个带 ACK=Y+1, Seq=Z 的数据包,代表“握手结束”

# get 和 post 的区别(get 和 post 虽然本质都是 tcp/ip)

  • GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包
    • get 请求时,浏览器会把 headers 和 data 一起发送出去,服务器响应 200
    • post 请求时,浏览器先发送 headers,服务器响应 100 continue,浏览器再发送 data,服务器响应 200(返回数据)
  • GET 在浏览器回退时是无害的,而 POST 会再次提交请求
  • GET 参数通过 URL 传递,POST 放在 Request body 中,所以 GET 请求只能进行 url 编码,而 POST 支持多种编码方式
  • GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息
  • GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有

# instanceof 操作符的实现原理

其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype 原理就是:取右表达式的 prototype 值和左边表达式的__proto__值比较,除了 null 为 false

//Object instanceof Object、Function instanceof Function 和 Function instanceof Object
//Object 的 prototype 属性是 Object.prototype, 而由于 Object 本身是一个函数,由 Function 所创建,所以 Object.__proto__ 的值是 Function.prototype,而 Function.prototype 的 __proto__ 属性是 Object.prototype,所以我们可以判断出,Object instanceof Object 的结果是 true
leftValue = Object.__proto__ = Function.prototype;
rightValue = Object.prototype;
// 第一次判断
leftValue != rightValue;
leftValue = Function.prototype.__proto__ = Object.prototype;
// 第二次判断
leftValue === rightValue;
// 返回 true
//Foo instanceof Foo
//Foo 函数的 prototype 属性是 Foo.prototype,而 Foo 的 __proto__ 属性是 Function.prototype,由图可知,Foo 的原型链上并没有 Foo.prototype ,因此 Foo instanceof Foo 也就返回 false 。
(leftValue = Foo), (rightValue = Foo);
leftValue = Foo.__proto = Function.prototype;
rightValue = Foo.prototype;
// 第一次判断
leftValue != rightValue;
leftValue = Function.prototype.__proto__ = Object.prototype;
// 第二次判断
leftValue != rightValue;
leftValue = Object.prototype = null;
// 第三次判断
leftValue === null;
// 返回 false
//Foo instanceof Object
(leftValue = Foo), (rightValue = Object);
leftValue = Foo.__proto__ = Function.prototype;
rightValue = Object.prototype;
// 第一次判断
leftValue != rightValue;
leftValue = Function.prototype.__proto__ = Object.prototype;
// 第二次判断
leftValue === rightValue;
// 返回 true
//Foo instanceof Function
(leftValue = Foo), (rightValue = Function);
leftValue = Foo.__proto__ = Function.prototype;
rightValue = Function.prototype;
// 第一次判断
leftValue === rightValue;
// 返回 true

# typeof

typeof 可以判断 number, string, object, boolean, function, undefined,symbol 这七种类型,但是 typeof 在判断一个 object 的数据的时候无法告诉我们这个数据 object 是哪一种 object 原理是:js 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息

  • 000:对象
  • 010:浮点数
  • 100:字符串
  • 110:布尔
  • 1:整数
  • null:所有机器码均为 0
  • undefined:用 −2^30 整数来表示 typeof 在判断 null 的时候由于 null 的所有机器码均为 0,因此直接被当做了对象来看待
let s = new String("abc");
typeof s === "object"; // true
s instanceof String; // true
typeof null === "object"; // true

# 深拷贝时如何解决循环引用

//解决这一循环引用的问题其实很简单,我们只需要在每次对复杂数据类型进行深拷贝前保存其值,如果下次又出现了该值,就不再进行拷贝,直接截止
//这里我们选用 ES6 中的 WeakMap 或者 Map 数据结构来存储每一次的复杂类型的值,我们也要对原来的 deepClone 函数内部的逻辑封装到内部的另外一个函数内,目的是为了在内部函数外部我们定义映射,形成闭包
const deepClone = (obj) => {
  // 定义一个映射,初始化的时候将 obj 本身加入映射中
  const map = new WeakMap();
  map.set(obj, true);
  // 封装原来的递归逻辑
  const copy = (obj) => {
    if (!obj || typeof obj !== "object") {
      return {};
    }
    const newObj = Array.isArray(obj) ? [] : {};
    for (const key in obj) {
      const value = obj[key];
      // 如果拷贝的是简单类型的值直接进行赋值
      if (typeof value !== "object") {
        newObj[key] = value;
      } else {
        // 如果拷贝的是复杂数据类型第一次拷贝后存入 map
        // 第二次再次遇到该值时直接赋值为 null,结束递归
        if (map.has(value)) {
          newObj[key] = null;
        } else {
          map.set(value, true);
          newObj[key] = copy(value);
        }
      }
    }
    return newObj;
  };
  return copy(obj);
};

// test
const seven = {
  name: "seven",
};
const juejin = {
  name: "juejin",
  relative: seven,
};
seven.relative = juejin;
const newObj = deepClone(seven);
console.log(newObj);
// { name: 'seven', relative: { name: 'juejin', relative: null } }

# 函数记忆化

  • 函数记忆化是指使用缓存来保存函数的结果,从而避免重复计算。这种技巧可以用于提高函数的性能。
// 函数记忆化示例:使用缓存来保存函数的结果
function memoize(fn) {
  const cache = {};
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}
function add(a, b) {
  console.log("Calculating sum...");
  return a + b;
}
const memoizedAdd = memoize(add);
console.log(memoizedAdd(2, 3)); // Calculating sum... 5
console.log(memoizedAdd(2, 3)); // 5 (from cache)

# v8垃圾回收机制?

  • V8 引擎的垃圾回收机制基于代际假说和分代回收的原理。它将内存分为新生代和老生代两个代。新生代用于存放新创建的对象,老生代用于存放经过一定时间仍然存活的对象。
  • 下面是V8 引擎中的垃圾回收机制的一般流程:
  1. 新生代垃圾回收:V8 将新生代内存空间分为两个部分:From 空间和To 空间。新创建的对象首先被分配到From 空间,当From 空间满时,会触发垃圾回收过程。回收过程中,V8 首先进行标记操作,标记活跃的对象,然后将这些对象复制到To 空间,同时进行压缩等操作。最后,From 空间和To 空间的角色互换,完成垃圾回收。
  2. 老生代垃圾回收:老生代中的对象由于存活时间较长,垃圾回收的成本较高。V8 使用标记-清除(mark-sweep)和标记-压缩(mark-compact)两种算法进行老生代的垃圾回收。标记-清除算法首先进行标记操作,标记出活跃的对象,然后清除未标记的对象。标记-压缩算法在清除未标记的对象后,将存活的对象压缩到内存的一端,从而减少内存碎片化。
  3. 增量标记:为了降低垃圾回收对程序执行的影响,V8 引擎使用增量标记算法。增量标记允许垃圾回收过程与程序执行交替进行,每次执行一小部分的标记操作,减少了垃圾回收对程序的中断时间。

# 手写reduce

  • 接收一个回调函数作为参数,该回调函数可以有四个参数:累计值(初始值或上一次回调的返回值)、当前元素、当前索引和原数组。 reduce函数会依次遍历数组的每个元素,将回调函数的返回值作为下一次调用的累计值,最终返回一个累计值。
    Array.prototype.myReduce = function(callback, initialValue) {
      let accumulator = initialValue === undefined ? undefined : initialValue;
      for (let i = 0; i < this.length; i++) {
        if (accumulator === undefined) {
          accumulator = this[i];
        } else {
          accumulator = callback(accumulator, this[i], i, this);
        }
      }
      return accumulator;
    };
    

# 白屏的原因是什么?

  • 网络加载问题:网页可能因为网络问题导致资源无法正常加载而呈现空白。这可以包括服务器故障、网络连接问题或资源路径错误等。
  • 脚本错误:网页中的JavaScript脚本错误可能导致页面无法正常渲染,从而呈现空白。这可能是由于语法错误、逻辑错误或依赖项加载失败等问题引起的。
  • 样式问题:CSS样式文件加载或解析错误可能导致页面无法正确显示内容,呈现为空白。这可能是由于CSS文件路径错误、语法错误或样式冲突等问题引起的
  • 渲染问题:浏览器的渲染引擎可能遇到问题,导致页面无法正确渲染并呈现空白。这可能是由于浏览器版本问题、渲染引擎错误或不受支持的特性使用等原因引起的。
  • 编码问题:网页的字符编码可能与浏览器解析不一致,导致页面无法正常显示内容而呈现空白。
  • 其他问题:其他因素如服务器配置错误、缓存问题、安全策略限制等也可能导致白屏问题的发生。

# react 的diff 和vue的diff的区别?

  • React的diff算法采用的是基于双指针的算法,称为"React Reconciliation"。它通过比较两个虚拟DOM树的差异来确定需要更新的部分,并最小化对实际DOM的操作。
  • Vue的diff算法则是基于双端队列的算法,称为"Vue Diff"。它通过对新旧虚拟DOM树进行同层比较,找到最小的差异集合,并在实际DOM中进行相应的更新。
Last Updated: 8/27/2023, 11:07:46 AM