大四学生的前端知识点面试学习记录


前端面试学习

开始于2021年3月17日。最后编辑于2022年2月10日

JavaScript基础

数据类型

基本类型:null,undefined,boolean,number,string,symbol,bigInt 、引用类型:object

  • 当对字符串进行parseInt转换等其他转换时,返回的NaN(NaN属于number)跟另一个字符串转换获得的NaN进行比较时会返回false。因'A'不是一个数字 ,'B'也不是一个数字,无法证明两者一样的!所以要用isNaN()方法来判断。NaN互不相等

  • 栈去存放变量和其值。基本类型是它真实的值,引用类型保存的是对象在堆中的地址值。所以对基本类型比较时是比较实际的值。对引用类型(Object)比较的时候则是比较地址值,赋值也是赋值在堆区中的地址值。js是值传递

    let a={}, b=a, c={};
    console.log(`${a===b},${a==c}`); // true,false
    
  • js跟java一样也有包装类型,如boolean->Boolean,number->Numbe类比java里的int->Integer,boolean->Boolean。当对基本类型调用方法或者获取属性时,会隐式的创建一个包装类,用完就销毁。自动包装和拆箱

    var str = 1;
    str.pro = 2;
    console.log(str.pro + 10); // NaN
    
  • 明白对象赋值是传递引用。对象深浅拷贝的问题就解决了,浅拷贝只拷贝对象里的第一层。

  • 使用typeof可以查看其属于的类型是什么,数组[]也是object哦,函数会返回functiontypeof null返回object←历史遗留问题,因为js引擎对二进位前三位都为0就判为object,恰好null全0。(可以改,但没必要)

  • 对于Boolean的转换,除了 undefinednullfalseNaN''0-0[],其他所有值都转为 true,包括所有对象。

    • symbol最主要是用来定义一个独一无二的属性名,来避免重名

      ES6引入新的基础类型,类似唯一标识ID,通过Symbol()来创建实例

      Symbol可以作为对象的键,且在序列化,Object.keys()或者for...in来枚举都不会被包含进去

      所以还可以使用Symbol来定义类的私有属性/方法。总的来说,见的很少。

  • 判断方法:typeof/instanceof/construtor/isPrototypeOf/Object.getPrototypeOf()/Object.prototype.toString.call()

原型和原型链

img??有名的js原型图

img精简版

  • js通过原型和原型链来实现类似继承的机制。就像OOP中的继承、Java中的父类,类方法等。

在Java中可以使用Cat cat=new Cat();cat.eat()调用属于类的方法也可以使用Cat.eat()直接调用。由于js的动态语言特性,可以在'类'中增加方法和属性。让其'实例对象'都可以调用到这个方法。同时继承也让'实例对象'知道它爹是谁。

  • 使用关键关键字function声明函数有prototype属性,使用const fun1=()=>{}创建没有prototype属性

    const fun4=(){ console.log('is func4') }
    fun4(); // is func4
    console.log(fun4.prototype); // undefined
    
  • 使用function声明函数实际上是内部调用new Function(...),并被添加__proto__属性来链接到其构造函数原型上。

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它

  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它

  • Function.prototypeObject.prototype 是两个特殊的对象,他们由引擎来创建

  • 除了以上两个特殊对象,其他对象都是通过构造器 new 出来的

  • 函数的 prototype 是一个对象,也就是原型

  • 对象的 __proto__ 指向原型, __proto__ 将对象和原型连接起来组成了原型链

  • 所有引用类型的_proto_属性指向它构造函数的prototype

  • Function.[[Prototype]] === Function.prototype; // true

  • Object.prototype是浏览器底层根据 ECMAScript 规范创造的一个对象。

  • 以上来自 https://github.com/KieSun/Dream/issues/2

关键字new

  • 使用new FunctionName来创建对象发生四件事:新生成了一个对象、链接到原型、绑定this、返回新对象

  • 创建对象最好还是用字面量的方式let a = { b: 1 }方法来创建,可读性高,性能好。

  • new内部默认返回this,如果手动返回一个引用类型则不会返回this,返回基本类型不生效

    function _new(constructor,...args) {
      let obj = new Object()// 创建空对象
     
      obj.__proto__ = constructor.prototype  // 链接到原型
     
      let result = constructor.call(obj, ...args)  // 绑定this执行构造函数
     
      return typeof result === 'object' ? result : obj  // 确保 new 出来的是个对象
    }
    

关键字instanceof

  • 判断对象的类型,通过判断其原型链上是否能找到类型的prototype,大概就是一直通过obj.__proto__跟目标的``prototype比较 let obj={}; console.log(obj instanceof Object) // true`

    function instanceof(left, right) {
        // 获得类型的原型
        let prototype = right.prototype
        // 获得对象的原型
        left = left.__proto__
        // 判断对象的类型是否等于类型的原型
        while (true) {
        	if (left === null)
        		return false
        	if (prototype === left)
        		return true
        	left = left.__proto__
        }
    }
    

关键字this

官方中文文档指路

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

  • this是一个执行上下文(context)的属性,它会在执行的过程中用到。它的指向取决于在哪里被调用,并就近查找
  • 可以用call,apply,bind可以改变this为第一个参数
  • 箭头函数()=>{}没有this,所以this是向外一层层查找的。并且this绑定了上下文就不会被更改
  • 在严格模式下,函数被直接调用(不作为对象的属性/方法)时会出现undefined
  • 使用new关键字new一个对象,这个对象并被绑定为this,并隐式的返回这个this
  • addEventListener的事件函数this指向当前DOM元素,可以使用箭头函数或者bind改变this指向
var obj = {
  bar() {
    return  (() => this);
  }
};
----es5↓----
var obj = {
  bar: function bar() {
    var _this = this;
    return function () {
      return _this;
    };
  }
};

在这里插入图片描述

关键字varletconst

  • let跟const差不多,都是块级作用域,不能再被声明前使用,不能重复定义,存在暂时性死区。const不能更改值且必须定义时就赋初值
  • 在变量提升的过程中,相同名的函数会覆盖上一个,且函数优先于变量提升
function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world'; // 会导致内层变量会覆盖外层变量(变量提升的原因)
  }
}
?
f(); // undefined
// ==循环陷阱问题==
for (var i = 0; i < 3; i++){}

关键字class

  • 函数声明类声明之间的一个重要区别在于, 函数声明会提升,类声明不会。

题目:下面这个 class 的四个属性分别属于这个 class 的什么,fn 和 f 有什么区别 (babel在线编一下就知道了)

class A {
  static a = 1;
  b = 2;
  fn() {console.log(this)}
  f = () => {console.log(this)};
}

相等(==)

在两个操作数是不同类型会尝试转换成相同类型

  1. 数字和字符串比,尝试字符串转成数字 | 0 == '' // true
  2. 有一个是Bool,则将Bool换成数字 | false == 0 // true
  3. 有一个是对象,则尝试使用对象的valueOf()toString()

Number和Number的比较:+0和-0视为相同值,NaN和NaN返回false

[] == false ?

  1. 应用规则2=> [] == 0;
  2. 对象类型会转换为其原始值=>[].valueOf().toString() = ""
  3. "" == 0""被转换为数字Number("") === 0

作用域

当创建函数时,该函数的作用域便已经声明,为所处的上下文,

函数中查找变量的过程中形成的链条就叫作用域链(当前作用域没查到就会向上一级作用域查找)

箭头函数

不能用new 、不绑定argumentsnew.target 、 不绑定this、没有prototype属性

函数对象是一个支持[[Call]][[Construct]]内部方法的对象,不过箭头函数没有[[Construct]],所有不能new

闭包

有句话叫做:闭包是懒人的对象,对象是天然的闭包。闭包让无状态的函数有了状态,可以记录函数用到的变量。

  • 闭包的产生条件:函数嵌套、嵌套的内部函数必须引用外部函数定义的变量、内部函数被执行

  • 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来

  • 不过闭包对于变量的引用,让引用类型一直被引用,无法被GC,若使用不当会导致内存泄漏的问题

  • 变量的存储:除了局部变量,其他的全都(全局变量和被捕获变量)存在堆中 跟下面对比着看。。

    实现词法绑定的一种技术。

    闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用)。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。

  • 基本类型存放: https://www.zhihu.com/question/482433315/answer/2083349992

    var a = 1;
    function foo() {
      // foo函数在创建时也会创建一个内部的VO(变量对象):{b : 2; innerFoo : <函数地址引用>}
      // 同时建立作用域链,对外层的VO链接起来,就能够读取到变量 a
      var b = 2;
      return function innerFoo() {
        console.log(a,b);
      }
    }
    const foo1=foo();
    foo1(); //1,2   因为闭包,函数innerFoo记录了他的上下文。b被引用了,就不被释放。(当前作用域产生对父作用域的引用)
    
  • 使用场景:能访问函数定义时所在的词法作用域,让变量私有化,模拟块级作用域、函数懒执行(比如实现柯里化和反柯里化)

参考:https://juejin.cn/post/6844903997615128583

模块化

  • ES Modules 就ES6才有的。好用、静态导入、动态引用、导入导出的值都指向同一个内存地址
  • CommonJs 是 Node 独有的规范,浏览器不能直接用,支持动态导入
  • [javascript模块化演进及原理浅析] https://juejin.cn/post/6940163713345257486

CommonJs提供了exports(导出) 和 require(导入) 两个对象。并在在运行时才确定引入然后再执行这个模块(相当于函数调用)。输出是值的浅拷贝。this指向当前模块

ES6 Module是静态的。是在代码正式运行前执行(编译阶段,只能写在头部)。导出的是值的内存的地址的引用(不能更改,相当于用const定义),this指向undefined,利于打包工具进行tree shaking

Es Module 还可以 import() 懒加载方式实现代码分割。

包装函数的本质

function wrapper (script) {
    return '(function (exports, require, module, __filename, __dirname) {' 
       + script +
    '\n})'
}

Promise

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。

  • Promise让异步函数原来的回调函数处理变成链式处理.then().then() ...,上一个处理完交给下一个then()

  • 一个promise可以接收多个then,每个then都返回一个新的Promise对象

  • Promise.catch(()=>{}) 等于 Promise.then(undefined,()=>{})

  • Promise.rejected有传递机制,会不断向下传递直到遇见onRejected 处理函数

    当一个没有绑定处理函数的 Promsie 被调用 reject 则会创建一个 微任务来再次检测这个 Promise 是否存在处理函数,如果此时还不存在则输出报错

  • 内部有3个状态,未完成(收集依赖),成功和失败(将then/catch中的回调函数加入任务队列)

  • then的onFulfilled 可以返回一个thenable对象(包含then方法的任意对象)

    let p2 = Promise.resolve().then(() => {
        console.log(0);
        let p3 = Promise.resolve(4)
        return p3;
    })
    

    p2要达到Fulfilled,会先函数一个微任务并推入队列,然后当该任务被执行再调用p3.then()

    microtask(() => {
      p3.then((value) => { ReslovePromise(p2, value) }) // 再次加入微任务队列
    })
    // https://juejin.cn/post/7055202073511460895#heading-30
    
  • asyncawait是es7有的。await用来等待一个Promise对象的结果。代替.then()的写法。async用来定义异步函数,返回一个Promse对象。

  • asyncawait是Generator 函数的语法糖 ,awaityield命令作用一样。await必须在async函数的上下文中

  • 手写 Promise类之内部重要组成部分

    1. 当前Promise实例的状态 'Pending' 'Fulfilled' 'Rejected' 三种之一 (转变后不能再转变)

    2. 当前Promise接受构造器执行完的结果 result,(作为依赖的参数值)

    3. then方法传入的回调函数的队列 resolveQueue [] & rejectQueue []

    4. 改变Promise实例的状态的函数 _resolve & _reject,(调用收集的依赖,放入微任务队列)

  • Promise静态方法四兄弟

    • Promise.all接收多个Promise实例,都成功时,返回结果数组(被then接收),当有一个失败时,发生短路返回第一个失败的实例(被catch接收)
    • Promise.race接收多个实例,最先做完的被返回,是成功就被then接收,是失败就被catch接收
    • Promise.allSettled都做完再执行成功回调
    • Promise.any跟all相反,都失败了reject,一个成功就fulfiled
    • all和race都具有短路特性,all有一个失败就reject,race参数中某个promise解决或拒绝,返回的 promise就会解决或拒绝。

Generator

  • Generator 是 ES6 中新增的语法,和 Promise 一样,都可以用来异步编程

    // 使用 * 表示这是一个 Generator 函数
    // 内部可以通过 yield 暂停代码,并 声明内部状态的值
    // 通过调用 next 恢复执行
    function* test() {
      let a = 1 + 2;
      yield 2;
      yield 3;
    }
    let b = test();
    console.log(b.next()); // >  { value: 2, done: false }
    console.log(b.next()); // >  { value: 3, done: false }
    console.log(b.next()); // >  { value: undefined, done: true }
    
  • 从以上代码可以发现,加上 * 的函数执行后拥有了 next 函数,也就是说函数执行后返回了一个对象。每次调用 next 函数可以继续执行被暂停的代码。

  • Generator函数内部遇到yield命令,那么就不往下执行了,就把执行权交出来给别的函数。别的函数做完,返还执行权后才继续执行。

Proxy

用来代理一个对象,通过Proxy可以轻松监视到对象的读写过程

Reflect

Reflect 可以用于获取目标对象的行为,它与 Object 类似,它的方法与 Proxy 是对应的。

Map&Set

map和Object的区别 事件订阅用Map不错

来源:https://juejin.cn/post/6940945178899251230#heading-37

Map Object
意外的键 Map默认情况不包含任何键,只包含显式插入的键。 Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突。
键的类型 任意 any! Object 的键必须是 String 或是Symbol。
键的顺序 Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。 Object 的键是无序的
Size Map 的键值对个数可以轻易地通过size 属性获取 Object 的键值对个数只能手动计算
迭代 Map 是 iterable 的,所以可以直接被迭代。 迭代Object需要以某种方式获取它的键然后才能迭代。
性能 在频繁增删键值对的场景下表现更好。 在频繁添加和删除键值对的场景下未作出优化。
  • 如果需要遍历键值对,并且考虑顺序,优先考虑 Map
  • Map是纯Hash结构,对频繁增删键值对的场景表现更好

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

XMLHttpRequest

const xhr = new XMLHttpRequest();
xhr.open("post", "http://localhost:8080/api/test");
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(JSON.stringify({a: 1, b: 2 }));
xhr.onreadystatechange = function(){
if(xhr.status==200 && xhr.readyState==4){
    let result=xhr.responseText;//获取到结果
    alert(result);
  }
}

ajax!=XHR ajax是一种概念,XHR是基于浏览器是一种实现

fetch和XHR的区别

  1. fetch更加语义化和简洁、基于Promise实现、更加底层
  2. fetch不能检测请求进度,不能中断请求(abort)

for in & for of

for...of是ES6新增的,用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。获取的是对象的

// 普通对象使用for...of遍历 来自: https://juejin.cn/post/6940945178899251230#heading-77
var obj = {
    a:1,
    b:2,
    c:3
};
//方法一:
obj[Symbol.iterator] = function(){
	var keys = Object.keys(this);
	var count = 0;
	return {
		next(){
			if(count

for...in主要是为了遍历为对象,获取的是对象的键。

let obj={a:1,b:2}
for (const key in obj) {
  console.log(key); 
}

JSON.stringify

列举一些注意事项

  • 对于不能被序列化的转换值,可以添加toJSON()方法(Date对象自带),如RegExp,Error和function,且toJSON方法会覆盖对象默认的序列化行为

  • 对象的属性值为undefined和Symbol的属性则会被忽略(在数组中变成null)

  • NaN 和 Infinity 格式的数值及 null 都会被当做 null

  • 无法解决包含循环引用的对象,会抛出错误

  • 只遍历可枚举属性,对于Set、Map家族,默认返回{}

事件循环

https://juejin.cn/post/7049385716765163534

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop,异步线程与主线程通讯靠的就是Event Loop(比如网络请求,定时器)

设计 Loop 机制和 Task 队列是为了支持异步,解决逻辑执行阻塞主线程的问题,设计 MicroTask 队列的插队机制是为了解决高优任务尽早执行的问题。

微任务:Promise.then/catch/finally、MutationObserver、process.nextTick(Node)

宏任务:script、setTimeout()、setInterval()、requestAnimationFrame()、I/O、Ajax

Timers等是先放到放到Event Table中,等满足触发条件才添加到宏任务队列中的(定时器线程处理)

简易:每次执行一个宏任务,然后执行所有微任务!

首先事件循环从宏任务队列开始,有就取一个任务放入主进程(一个调用栈)中执行,然后检查微任务队列,并执行清空所有微任务队列。然后就检查是否需要视图更新(也有个更新队列),这样就算一个tick

btw,在视图更新前会执行‘请求动画帧’的回调函数。浏览器只保证请求动画帧的回调在重绘前执行,没有确定时间。何时重绘有浏览器决定,且频率不高于主显示器的刷新率

Nodejs的事件循环与浏览器事件循环的区别

Node中的宏任务之间也有优先级,比如定时器 Timer 的逻辑就比 IO 的逻辑优先级高。

Timers Callback: 涉及到时间,肯定越早执行越准确,所以这个优先级最高很容易理解。

Pending Callback:处理网络、IO 等异常时的回调,有的 lniux 系统会等待发生错误的上报,所以得处理下。

Poll Callback:处理 IO 的 data、网络的 connection,服务器主要处理的就是这个。

Check Callback:执行 setImmediate 的回调,特点是刚执行完 IO 之后就能回调这个。

Close Callback:关闭资源的回调,晚点执行影响也不到,优先级最低。

链接:https://juejin.cn/post/7049385716765163534#heading-1

process.nextTick方法总是发生在所有异步任务之前。(总是在当前"执行栈"的尾部触发,高优先级的微任务)

事件模型

“事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。”

浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。

事件的操作和触发都定义在EventTarget接口,分别具有addEventListenerremoveEventListenerdispatchEvent方法

element1.addEventListener('click', hello); 	// 注册事件,调用 hello方法,默认冒泡阶段触发
element1.removeEventListener('click',hello);// 移除事件,第二 、第三个参数都要一样才能移除
element1.dispatchEvent(new Event('click'));	// 触发事件,传入一个事件对象(必须指定事件类型)

事件流

事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。

捕获阶段是从window对象传导到目标节点

冒泡阶段是从目标节点导回到window对象

父元素 子元素

父级先捕获→子级捕获→子级冒泡→父级冒泡

事件委托/代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

利用事件冒泡(里面往外面冒泡li>ul>body),只指定一个事件处理(ul)程序,就可以管理某一类型的所有事件。(公司前台MM帮忙取快递例子)
比如ul>li*6,不必为每个li绑定事件,ul绑定事件即可, 利用e.target即可知道触发的是哪个li了。
这样节省了绑定事件的数量,进而节省内存,提高性能。同时后面新来的元素也不用麻烦的添加事件函数,也能处理事件了

鼠标事件坐标

属性 说明 兼容性
offsetX/Y 以当前的目标元素左上角为原点,定位x/y轴坐标
clientX/Y 以浏览器可视窗口左上角为原点,定位x/y轴坐标 all
pageX/Y 鼠标指针相对于整个文档(document)的X/y坐标;
screenX/Y 鼠标指针相对于全局(屏幕)的X/y坐标 all

元素视图尺寸

属性 说明
offsetLeft/Top 获取当前元素到定位父节点的left/top方向的距离
offsetWidth/Height 返回一个元素的布局宽度/高度(包含到border和padding)
clientWidth/Height 表示元素的内部宽度/高度(包含padding,不包含border)
scrollWidth/Height 包含clientWidth/Height以及溢出的内容(如果有)
scrollLeft/Top 可以读取或设置元素滚动条到元素左/上边的距离
Window.innerWidth/Height 浏览器窗口可视区宽度/高度(不包含菜单栏、工具栏等)

script标签中的async和defer

async:开启另外一个线程下载js文件,下载完成,立马执行。(此时才发生阻塞)

defer:开启另一个线程下载js文件,直到页面加载完成时才执行。(根本不阻塞)

在开发中,defer常用于需要整个DOM的脚步(依赖于执行顺序),async用于独立脚本如计数器或广告

defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。

ES6(ES2015)

从以下方面数据类型、关键字、机制、对象、便利性

symbol、const/let、Class与extend、ES Module 、解构、字符串模板、箭头函数、新的自带对象orApi( Set、Map、Promise、Promise、Genrator、Proxy、Refect) 、迭代器和for...of、函数默认值、对象属性名简写、以及自带对象再增强

ES2016-ES2020

  • ES7: includes()方法(更好的语义化和NaN比较) 、求幂运算符 **
  • ES8: async/await 关键字 、Object.values, Object.entries
  • ES9: for await of(异步迭代)、改进正则表达式(命名捕获组、dotAll等) 、剩余运算符和对象扩展运算符
  • ES10: flat/flatMap、新类型BigInt、catch变量可选、function.toString()等
  • ES11: 可选的链接操作 ?.?? 运算符、Promise.allSettledglobal this、动态引入import()BigIntString.prototype.matchAll()

CSS基础

文章推荐

  • 一个比较全的总结:1.5 万字 CSS 基础拾遗(核心知识、常见需求): https://juejin.cn/post/6941206439624966152
  • css篇--100道近两万字帮你巩固css知识点: https://juejin.cn/post/6844904185847087111
  • 你未必知道的49个CSS知识点: https://juejin.cn/post/6844903902123393032

psition 定位

  • static:默认定位
  • relative:相对其正常位置定位,不脱离文档流
  • absolute:相对于最近定位为非static的父级元素进行定位,脱离文档流
  • fixed:生成固定定位的元素,相对于浏览器窗口进行定位!
  • sticky:集合了fixed和relative,但受控于父元素们。需要设置top属性,当具体元素距离上边距{{top}}时,由relative变成类似的fixed效果,但父元素滚动出去了它也要跟着出去,用于实现跟随窗口的效果
  • inherit:继承父级元素的position属性值

overflow 溢出

来自:https://juejin.cn/post/6844904199772176392#heading-3

属性值 描述
visible 不剪切内容也不添加滚动条
hidden 不显示超过对象尺寸的内容,超出的部分隐藏掉
scroll 不管超出内容否,总是显示滚动条
auto 超出自动显示滚动条,不超出不显示滚动条

文本溢出处理:

/*1. 先强制一行内显示文本*/
white-space: nowrap;
/*2. 超出的部分隐藏*/
overflow: hidden;
/*3. 文字用省略号替代超出的部分*/
text-overflow: ellipsis;

BFC 块级格式化上下文

BFC 就是页面上的一个隔离的独立容器(断绝空间内外元素间相互的作用),容器里面的子元素不会影响到外面的元素。反之也如此。计算BFC的高度会包含所有子元素进行计算。浮动盒区域不叠加到BFC上。

  • 哪些条件可以触发

    1. 浮动元素:float 除 none 以外的值

    2. 绝对定位元素:position (absolute、fixed)

    3. display 为 inline-block、table-cells、flex、grid

    4. overflow 除了 visible 以外的值 (hidden、auto、scroll)【最常用】

  • BFC的应用

    1. 解决margin重叠:当父元素和子元素发生 margin 重叠时,解决办法:给子元素或父元素创建BFC
    2. BFC区域不与float区域重叠(清除浮动原理:计算BFC的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算;)
    3. 分栏布局:一边float,另一边BCF占满剩余空间 (浮动盒区域不叠加到BFC上;)

来自:https://juejin.cn/post/6844904071497777165#heading-5

box-sizing 盒模型

  • box-sizing:content-box; 默认值,只计算内容的宽度,border和padding不计算入width之内
  • box-sizing:border-box; border和padding计算入width之内
  • box-sizingpadding-box; padding计算入width之内

CSS优先级

  1. !important 会覆盖页面内任何位置的元素样式

  2. 内联样式,如 style="color: green",权值为 1000

  3. ID 选择器,如#app,权值为 0100

  4. 类、伪类、属性选择器,权值为 0010

  5. 标签、伪元素选择器,如 div::first-line,权值为 0001

  6. 通配符、子类选择器、兄弟选择器,如*, >, +,权值为 0000

  7. 继承的样式没有权值

CSS变量

// 定义在这个伪类确保所有选择器可以访问
:root{
    --red: #ff0e0e;  // 变量名必须 --开头 区分大小写
    --tiny-shadow: 4px 4px 2px 0 rgba(0, 0, 0, 0.8);
}
// 使用
li {
  color:var(--red,red); // 第二个可选参数是当第一个参数不生效时使用(备用选项)
  box-shadow: var(--tiny-shadow);
}

CSS变量在管理颜色,主题切换,减少重复代码,增加易读性方面很有作用

在其他CSS变量中、calc()函数中也可以使用

z-index

z-index只应用在定位的元素,默认z-index:auto;

在层叠上下文中 ,子级层叠上下文的z-index值只在父级容器中才有效(即在同一层叠上下文中)。其值只决定在同一父级容器中,同级子元素的堆叠顺序。

Example of stacking rules modified using z-index

image

常用单位

  • px:像素单位
  • %:百分比
  • em: 相对于元素的字体大小(font-size)
  • rem:作用于非根元素时,是相对于根元素字体大小 (root em)
  • vh/vw:v是view,视窗的意思 100vh就是100%的视窗高度

margin padding的百分比计算

padding-top,padding-bottom,margin-top,margin-bottom取值为百分比的时候,参照的是父元素的宽度

top, left, right, bottom 取值百分比时相对于父级定位元素宽高

CSS元素分类

块级元素(block):能设置宽高,独占一行。

内联元素(inline):不能设置宽和高,不影响换行。

替换元素和非替换元素:非替换元素的表现由内容决定。替换元素相反可随意设置宽高,其展现效果不是由CSS来控制的。简单来说,它们的内容不受当前文档的样式的影响。 CSS 可以影响可替换元素的位置,但不会影响到可替换元素自身的内容。

inline-level 元素分类 具体元素 默认特征
可替换元素 img、input、iframe、video、canvas、 宽高可任意设定
非替换元素 a、strong、code、label、span 宽高由内容决定

伪类和伪元素

https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements

伪元素 (它表现像加入全新的HTML元素一样)

选择器 描述
::after 匹配出现在原有元素的实际内容之后的一个可样式化元素。
::before 匹配出现在原有元素的实际内容之前的一个可样式化元素。
::first-letter 匹配元素的第一个字母
::first-line 匹配包含此伪元素的元素的第一行
::selection 匹配文档中被选择的那部分。

伪类(它用于选择处于特定状态的元素)

选择器 描述
:active 在用户激活(例如点击)元素的时候匹配
:checked 匹配处于选中状态的单选或者复选框。
:focus 当一个元素有焦点的时候匹配。
:hover 当用户悬浮到一个元素之上的时候匹配。
:disabled 匹配处于关闭/禁用状态的用户界面元素
:nth-... :nth-child:nth-of-type 等等
:visited 匹配已访问链接。

ele:nth-of-type(n) 指父元素下第n个ele元素 (计算n时只纳入ele元素)

ele:nth-child(n) 指父元素下第n个元素且这个元素是ele元素才匹配

选择器优先级

按照权重排列 !important 拥有最高优先级

  1. 内联样式(style=“ ”) 1000
  2. id选择器(#myid) 100
  3. 类选择器(.myclass) 10
  4. 属性选择器(a[rel="external"]) 10
  5. 伪类选择器( :active , :hover) 10
  6. 元素选择器(div, h1,p,after) 1
  7. 关系选择器、通配符 0
  8. 继承样式
  9. 默认样式

inline inline-block block的区别

  • block:
  1. block元素会独占一行,多个block元素会各自新起一行。默认情况下,block元素宽度自动填满其父元素宽度。
  2. block元素可以设置width,height属性。块级元素即使设置了宽度,仍然是独占一行。
  3. block元素可以设置margin和padding属性。
  • inline-block:

    简单来说就是将对象呈现为inline对象,但是对象的内容作为block对象呈现。之后的内联对象会被排列在同一行内。比如我们可以给一个link(a元素)inline-block属性值,使其既具有block的宽度高度特性又具有inline的同行特性。

  • inline:

  1. inline元素不会独占一行,多个相邻的行内元素会排列在同一行里,直到一行排列不下,才会新换一行,其宽度随元素的内容而变化。
  2. inline元素设置width,height属性无效。
  3. inline元素的margin和padding属性,水平方向的padding-left, padding-right, margin-left, margin-right都产生边距效果;但竖直方向的padding-top, padding-bottom, margin-top, margin-bottom不会产生边距效果。

注: 一些inline元素同时又是可替换元素,比如img\input这些,本身带有width height属性,所以可以设置宽高

作者:homyeeking
链接:https://juejin.cn/post/6844904197435949064

transition和animation

transition

? 语法:transition: CSS属性,花费时间,效果曲线(默认ease),延迟时间(默认0)

? 作用:为元素的变化添加过度效果

animation

? 语法:animation:动画名称,一个周期花费时间,运动曲线(默认ease),动画延迟(默认0),播放次数(默认1),是否反向播放动画(默认normal),是否暂停动画(默认running)

需要 @keyframes 动画名称{ }

display:none和visibility:hidden

前者不会保留元素(不被渲染),位置会被之后正常的文档流覆盖。更改值后会触发reflow(回流)

后者仍会保留元素,只是看不见。更改值后会触发repaint(重绘)

dom树:display:none和visibility:hidden

渲染树:visibility:hidden

此外opacity:0和visibility:hidden一样也只是隐身,但opacity:0可以触发点击等事件

水平居中

  • 已知宽度的元素设置文本内容水平居中:text-align:center
  • 设置有宽度的块级元素水平居中:margin:0 auto
  • display:flex+ justify-content:center设置子元素水平居中

垂直居中

  • 已知高度的元素设置文本内容垂直居中:line-height:高度或者给父元素设置display:table-cell;vertical-align:middle
  • display:flex+ aligin-items:center设置子元素垂直居中

水平+垂直居中

  • 绝对定位居中除了top/left:50%之外需要使用margin-top/left:高度/宽度的一半或者transform: translate(-50%, -50%)来让中心点而非左上角居中。
  • .box1 {display: grid;place-items: center;} 最便捷实现水平+垂直居中
  • .box1 {display: flex; justify-content: center; align-items: center;}` 第二便捷实现水平+垂直居中
  • 以上两种若有多个元素要居中,使用grid布局会在一列上放,flex布局会在一行上放(默认主轴方向为行)

等高居中

使用Flexbox或者Grid布局,子元素默认等高(flex:align-items的默认值为stretch)。

三列布局

float(两边各自浮动到两边)、flex(flex属性)、grid(grid-template-columns)、table(display:table-cell)、position(都用绝对定位,中间使用left,right扩展开)

清除浮动

不清除子元素的浮动,可能会导致没东西撑起父元素,从而造成高度塌陷

最好的方案就使用伪元素

.clearfix {
  zoom: 1;  /* 为了兼容IE低版本 */
  &::after {
    display: block;
    content: ' ';
    clear:both
  }
}

其他不常用方法(面试官可能会问还有其他方式吗)

  1. 父级元素添加overflow属性 触发BFC
  2. 添加额外标签(再最后一个浮动标签后再添加一个新标签 给其设置clear:both)

扩大可点击区域

利用伪元素代替主元素响应鼠标交互

button {
  position:relative;
  /* ... */
}
button:before {
   content:'';
   position:absolute;
  top:-10px; 
   right:-10px;
  bottom:-10px; 
   left:-10px;
}

BEM命名规范

BEM的命名规矩很容易记:block-name__element-name--modifier-name,也就是模块名 + 元素名 + 修饰器名/状态。

一般来说,根据组件目录名来作为组件名字:

比如分页组件:/app/components/page-btn/

那么该组件模块就名为page-btn,组件内部的元素命名都必须加上模块名,比如:

.header__logo{ border-radius: 50%; } .header__title{ font-size:16px; }

.page-btn__prev--loading{ background:gray; } .page-btn__prev--disabled{ cursor: not-allowed; }

在Sass中使用

.header {
    &__title {
        wdith: 100px;
        padding: 20px;
    }

    &__button {
        &--primary {
            background: blue;
        }
        &--default {
            background: white;
        }
    }
} // from https://juejin.cn/post/6969524904400011301#heading-6

CSS面试题

  • css的匹配顺序: 从右向左(更高效)

  • 两栏布局方式 三列布局 BFC 要么FLEX 顶宽可以用margin

  • 移动端适配

    1. 响应式布局(媒体查询)
    2. 使用postcss px-rem加上flexible
    3. 使用postcss px - vw

Webpack基础

webpack编译项目从解析webpack.config.js开始,每完成特定的一步会调用响应的钩子。

  1. 首先是要解析入口文件,并将文件转换成AST(@babel/parser)。
  2. 找出入口文件所有的依赖模块(@babel/traverse
  3. 将文件转换成可执行的代码,并按照第二步这样递归下去重复执行1、2、3
  4. 重写require函数,并按照步骤4生成递归关系图,输出到bundle中

涉及到多个区块的代码打包,通过import()实现code spliting,webpack会利用jsonp加载 chunk 的运行时代码。


先从配置文件和shell语句读取参数并初始化Compiler对象,加载所有配置插件,执行run()开始编译。webpack使用nodejs的fs模块,读取定义的入口entry文件,将文件转换成AST树,并递归地获取所依赖的模块文件,调用对用的loader处理匹配后缀的文件,将遇到的模块文件都放入_webpack_modules_这个对象中,并用文件的相对src路径作为对象key,key对应的是一个函数,该函数的参数是模块、导出、导入三个对象/方法,方法内部利用eval()执行原本文件的js内容(被webpack用babel转成AST,进行遍历,再生成浏览器可执行的代码,里面的用到的导入导出功能,就是由参数传进来的[源代码被包裹函数进行包裹])。_webpack_modules_处于的代码中,还有对模块的缓存、重写后的__webpack_require__方法等。

function __webpack_require__(moduleId) {
    if (__webpack_module_cache__[moduleId]) {
        return __webpack_module_cache__[moduleId].exports;
    }
    var module = (__webpack_module_cache__[moduleId] = { exports: {}, });

    // 原文件的require、module被重写了并作为参数传入
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    return module.exports;
}
var __webpack_modules__ = {
  "./src/add.js": ( __unused_webpack_module, __webpack_exports__,__webpack_require__) => {
     eval(`__webpack_require__.r(__webpack_exports__);
            __webpack_require__.d(__webpack_exports__, {
              "default": () => __WEBPACK_DEFAULT_EXPORT__
            });
            var add = function add(a, b) { return a + b; };
            const __WEBPACK_DEFAULT_EXPORT__ = (add);`);
  },
    
  "./src/index.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {
      eval(
          '__webpack_require__.r(__webpack_exports__);\n  var _cute_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/cute.js");\n  var _add_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./src/add.js");\n\n\n var num1 = (0,_add_js__WEBPACK_IMPORTED_MODULE_1__.default)(1, 2);\nvar num2 = (0,_cute_js__WEBPACK_IMPORTED_MODULE_0__.cute)(100, 22);\nconsole.log(num1, num2);'
      );
  },
}
//.d是定义属性 .o是hasOwnProperty调用 .r 不懂`Object.defineProperty(exports, "__esModule", { value: true });`

image.png

loader

loader是文件加载器(本质是一个函数,接受源代码输出处理后的结果),能对文件进行编译压缩等处理(webpack默认支持js),并将它们转换为有效模块,最后打包进去,loader的执行顺序和配置中相反。

在处理文件前,首先会经历patch阶段,pitich loader属性返回非undefined会产生熔断效果。

详解:https://juejin.cn/post/7036379350710616078

plugin

plugin是对webpack在运行的生命周期中广播出的事件进行处理,可通过Webpack的API改变结果。(基于Tapable )

Q: plugin1可以派发事件让plugin2监听吗?

A: webpack的事件机制是基于观察者模式的。plugin不仅能够监听事件,也能够广播事件和其他的插件进行通信。

// 广播事件
compiler.apply('event-name', params)
// 监听同名的事件,当这个事件触发的时候,回调函数就会被执行
compiler.plugin('event-name', params => {
。。。
})
// https://zhuanlan.zhihu.com/p/40930680

Babel

核心就是利用一系列plugin来管理编译的案例,对不同es版本的js、甚至jsx。来把它们编译成所需要的js,来让更多浏览器支持该js的运行

解析、遍历、生成

babel.config.js 和.babelrc 有什么区别

全局配置 (babel.config.js) :即针对第三方代码也针对自己的代码

局部配置 (.babelrc):按目录加载 ,只影响版项目

Q: modulechunkbundleasset的区别?

asset:就是图片字体等资源文件

chunk:webpack处理时根据文件引用关系组成的chunk文件

moudule:每个文件都可以视为一个模块

bundle:处理chunk文件后,生成可在浏览器中运行的代码

https://blog.51cto.com/u_15283585/2957111

打包体积优化

主要思路是分离&提取、按需加载、提取通用模块、压缩&混淆代码、Tree Shaking、图片压缩、

热更新原理

当启动一个服务之后(用的webpack-dev-server),浏览器和服务端是通过websocket进行长连接,webpack内部实现的watch(基于chokidar库)就会去监听文件修改。只要文件有修改,webpack就会重新打包编译到内存中,然后webpack-dev-server依赖中间件webpack-dev-middleware和webpack之间进行交互,每次热更新都会请求一个携带hash值的json文件和一个js,websocke传递的也是hash值,内部机制通过hash值检查进行热更新。

  • Hash值代表每一次编译的标识
  • 编译完成后通过websocket向客户端推送当前编译的hash戳
  • 客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比
    • 一致则走缓存,不去请求
    • 不一致则通过ajaxjsonp向服务端获取最新资源,并替换删除缓存
  • 使用内存文件系统(memfs)去替换有修改的内容实现局部刷新
  • https://blog.csdn.net/chern1992/article/details/106893227/
  • https://segmentfault.com/a/1190000020310371
  • https://mp.weixin.qq.com/s/gG_FwVGHiJGjQOvt5rZheA (未看)

与Rollup的对比

Webpack强调的是前端模块化方案,侧重模块打包。Rollup简洁且打包出体积更小的文件(tree shaking)。

开发库的时候用Rollup多,比如Vue和React等。

Vite基础

vite主要特性:bundless,基于浏览器原生支持的ES module,相当于按需引入模块。生产环境下使用rollup打包编译。开发和生产环境下共享同一套 Rollup 插件机制。

模块解析

预构建是用来提升页面重载速度,它将 CommonJS、UMD 等转换为 ESM 格式。预构建这一步由 esbuild 执行,这使得 Vite 的冷启动时间比任何基于 JavaScript 的打包程序都要快得多。

热更新HMR

跟webpack-dev-server类似,服务端监听文件改动(通过给.vue文件注册Watcher)和编译资源,通过websocket想客户端发送消息,客户端进行逻辑判断(文件指纹)是否要更新。

基于ESM的devServer插件在启动时会先初始化服务器和加载对应插件。插件包括拦截请求,将其转换成浏览器可识别的ESM语法、对.ts.vue的即使编译以及 sassless 的预编译、与浏览器建立socket 连接,用于实现HMR

启动一开始通过插件向向index.html注入代码,劫持/vite/hmr的请求,然后返回client.js 文件,该文件主要用于跟服务器(koa)建立websocket连接。

参考:https://juejin.cn/post/6854573209329598477

Nodejs基础

NodeJS 是基于Chrome V8引擎的 JavaScript 运行环境。NodeJS使用事件驱动非阻塞型I/O的模型,使其轻量又高效。且有一堆优化的API类库调用(如c++的libuv)。

Nodejs异步编程最直接的体现就是回调。使得代码执行时没有阻塞或者等待文件I/O的操作,在读文件的同时执行接下来的代码,提高了程序性能。

事件循环

Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。

Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。

Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。

Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.

作者:前端开发小马哥
链接:https://juejin.cn/post/6901093313756332040

包管理器

依赖管理

yarn最先时候loack文件锁定版本,后npm也才增加上

都采用扁平化管理依赖,避免嵌套深、大量包重复安装的问题

安装速速

yarn采用并行安装依赖方式,比串行的npm快些

浏览器原理

最推荐:https://zhuanlan.zhihu.com/p/102149546 (太长就看了一部分)

浏览器原理

  • 页面加载过程(仅为HTTP)

    1. 构建请求行(GET / HTTP/1.1),然后检查强缓存,命中则直接使用不发起请求

    2. 进行DNS域名解析

    3. 通过三次握手建立TCP连接(第三次是判断客户端的接收能力)

    4. 浏览器构造HTTP请求报文并发起请求(请求资源前会判断是否有缓存)

      img

    5. 服务器处理请求并返回(响应行HTTP/1.1 200 OK)处理结果给浏览器

    6. 当不需要连接时,任意一方可以发起关闭请求,通过四次挥手来关闭(四次是为了确保数据传输完毕)

    7. 解析HTML并生成DOM树,CSS下载完后解析生成CSSOM

    8. 当DOM和CSSOM树构建完毕后,确定元素位置,生成渲染树

      ? JavaScript/CSS > 样式计算 > 布局 > 绘制 > 渲染层合并 > 光栅化

    9. 构建完渲染树之后,还会对特定节点进行分层,构建图层树,用于图层绘制并展现。

      1. 某些特殊的渲染层会被认为是合成层(Compositing Layers),合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层共用一个绘图层(节点的图层默认属于父节点图层) (Composite

      2. 对于拥有层叠上下文的节点属于显示合成,会提升为单独的一个合成层

      3. 显示合成对比的是隐示合成:在一个单独图层上还有层叠等级更高的节点,都会被提升为一个单独的图层,可能就会造成层爆炸,但浏览器也会尽力优化。

      4. -- 分割线 ++

      5. 图层树构建后,就是图层绘制,渲染引擎会将图层的绘制拆成绘制指令,并组成绘制列表

      6. 准备完绘制列表,主线程会把绘制列表commit给合成线程(compositor thread)

      7. 合成线程会将图层分块(以便按需绘制),然后便是将视图附近的图块转换成位图(点阵图像)

        1. 栅格化线程池完成转换工作,做的事就叫栅格化(过程中会使用GPU来加速生成)
      8. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程

      9. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上

    10. -- ++ == -- ++ == --++== --++==

浏览器渲染

零散的知识点记录。。。

https://fed.taobao.org/blog/taofed/do71ct/performance-composite/

https://csstriggers.com/

合成,就是把图层(GraphicsLayer)合并!

绘制阶段,并不是真绘制,而是生成绘制指令列表!有了绘制列表在进行光栅化生成图片(位图)。

每一个图层都对应一张图片,合成线程有了这些图片之后,会将这些图片合成为“一张”图片,并最终将生成的图片发送到后缓冲区。合成操作是在合成线程上完成的。这也就意味着在执行合成操作时,是不会影响到主线程执行的。

能直接在合成线程中实现的是整个图层的几何变换,透明度变换,阴影等,这些变换都不会影响到图层的内容。比如滚动页面的时候,整个页面内容没有变化,这时候做的其实是对图层做上下移动,这种操作直接在合成线程里面就可以完成了。

合成层拥有独立的绘图层(GraphicsLayer),而其他不是合成层的渲染层(RenderLayer),则和其第一个拥有绘图层的父层共用一个绘图层。

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快得多;

  • 渲染层决定渲染的层级顺序

  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层;

  • 元素提升为合成层后,transform 和 opacity 才不会触发 repaint,如果不是合成层,则其依然会触发 repaint。

    img

整个渲染流程

  1. 帧开始:浏览器发送垂直同步信号(Vsync), 表明新一帧的开始。
  2. 处理输入事件:输入事件(例如 touchmovescrollclick)在每一帧只会被触发一次。

优化:使用createDocumentFragment进行批量的 DOM 操作、对resize、scroll防抖 、为动画元素创建合成层、使用请求动画帧(rAF)

层爆炸:一个非显示合成层被渲染层的元素覆盖产生交叠(overlap),会导致覆盖元素也被提升到合成层(隐式合成),就可能产生此问题。但浏览器也会有对应处理,将隐式合成的多个渲染层压缩到同一个绘图层中进行渲染,但也是有极限的。

详细博客(★):

重排与重绘

  1. 只要元素位置大小发生改变,就是重排(css3-transform除外)

  2. 元素节点内部渲染如颜色阴影字体家族等变化才是重绘

  3. 触发创建单独图层的,浏览器将其放在合成层,不影响默认复合图层,所以不影响周末DOM结构,属性的改变也交给GPU处理。(各个复合图层都是单独绘制,所以互不影响)

    故transform和opacity改变的仅是图层的结合不会触发回流和重绘:

    opactity是GPU在绘画时简单的降低了之前已经画好的纹理的alpha值来达到效果,故不会触发回流和重绘

  4. 降低重排的方式:要么减少DOM节点属性的读取,要么减少修改次数,要么降低影响范围,创建新的复合图层

  5. 重绘&回流与事件循环的关系 (渲染流程)

    1. 当一次事件循环结束后,会判断文档是否需要更新,这个间隔为16ms(60Hz为例)
    2. 浏览器发送垂直同步信号(Vsync), 表明新一帧的开始(Frame Start)
    3. 先会判断是否有reizescrolltouchmove事件(每帧只触发一次)
    4. 然后会判断是否触发媒体查询、并更新动画和发送事件、判断是否全屏操作
    5. 执行requestAnimationFrame 回调,该函数告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
    6. 执行 IntersectionObserver 回调,该接口提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法(元素是否可见)
    7. 更新界面,如果还有空闲时间会执行requestIdleCallback 回调
      1. 样式计算、布局、绘制、合成、光栅化、将合成帧发送给GPU(Frame End)

浏览器多进程

好处:避免单个线程崩溃导致进程卡死,更好利用多核优势,方便使用沙箱模型,提高稳定性

  1. 每个浏览器有一个浏览器Browser进程负责协调和主控,以及插件进程,GPU进程

  2. 然后每个页面(Tab)都有一个进程,负责页面渲染和脚本执行、事件处理(★)

    1. 主线程★(main thread)负责HTML/CSS解析、对象树的构建和JS解析执行
    2. 合成线程★(compositor thread)负责页面的各个部分分层、单独光栅化,最后变成一个位图
    3. 事件触发线程:将setTimeout,AJAX,鼠标点击等对应任务加入事件线程中。当事件符合触发条件时,该线程会将事件添加到任务队列
    4. 定时器触发线程:setIntervalsetTimeout所在线程,用来计时并触发,然后加入任务队列
    5. 异步HTTP请求线程,一个XHR开一个线程请求
    6. Worker threads 、Raster thread
  3. Browser进程收到用户请求,获取页面,然后交给Tab的渲染进程。渲染的过程中可能会有Browser进程获取资源和需要GPU进程帮助渲染。渲染Render进程会讲结果传递给Browser进程。再由Browser进程接收并绘制。

  4. GPU 进程(GPU Process)不是在 GPU 中执行的,而是负责将渲染进程中绘制好的 tile 位图作为纹理上传至 GPU,最终绘制至屏幕上。

img

垃圾回收

常见标记清除算法(JS引擎常用)和引用计数算法。标记清除算法就是打标记(0 | 1),实现简单,当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。但容易让内存碎片化,以及分配内存时要遍历一次(O(n)的操作)。

V8引擎对GC的优化:

V8 的垃圾回收策略主要基于分代式垃圾回收机制(JVM也是),分成新老两代用不同策略来管理。新生代还有2个区域,一个使用区,一个空闲区,标记阶段会将活动对象复制到空闲区,在清理阶段将非活动对象(也就是原先的使用区)清掉。交换两个区的角色。多次不被清掉的、大的对象会被移入老生代。

其次就是并行回收,减少暂停时间。

内存泄漏

全局(window)属性、闭包、遗忘的定时器 总之就是可能存在引用,导致未被释放

vue中可能发生内存泄漏场景

例如使用v-if或者vue router跳转时从VNode中移除元素时,该元素由第三方库创建的,可能就会导致泄漏,要做好即使的清理工作

这些内存泄漏往往会发生在使用 Vue 之外的其它进行 DOM 操作的三方库时,没有正确调用销毁函数。

Service Worker

Service Worker实际上是浏览器和服务器之间的代理服务器,它最大的特点是在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。
Service Worker的目的在于离线缓存,转发请求和网络代理。它有自己的生命周期。

网络基础

TCP 和 UDP 的差别

tcp 可靠,有序,面向连接要握手挥手,需要消耗更多的资源,速度较慢,适合少量数据,只能一对一,能全双工

upd 不可靠,无序,面向非链接,资源消耗更少,速度更快(实时性好),结构简单,可能丢包,适合大量数据,提供单播,多播,广播的功能。

TCP协议为什么需要三次握手?

  1. 表因:三次挥手是在信道不可靠的基础上,避免已失效的连接请求报文段让server端建立无用的连接(会白白浪费资源,一直等client端消息)。 三次通信是理论上的最小值
  2. 本质:因为通讯的双方维护一个序列号seq,用于标识发送出去的数据包中, 哪些是已经被对方收到的。三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
  3. 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认(没被 确认就建立连接岂不是不行!)

TCP连接,标记位为大写字母

TCP协议为什么需要四次挥手?2MSL又是什么?

A:主动方 B:被动方

第二次挥手后(B→A),A不会再想B发送数据,但B还可以继续发(可用于发送没发完的数据)

第三次挥手后(B→A):③,B便进入LAST-ACK状态

第四次挥手(A→B):④,是在A接到第三次挥手后,立即发送确认应答,A会进入TIME-WAIT状态。该状态会持续2MSL时间。在这期间没B没请求,才会进入CLOSED状态。B在收到确认应答也便进入CLOSED状态。

第四次挥手原因也是要确保B能收到A的确认应答!如果A的确认应答(第四次挥手)丢失,会导致B无法正常关闭。因为B没有收到确实应答④的话,就不能确认A受否收到③,B就自动会重传③。不论是重传③或者收到④,A都需要等待,2MSL就是去MSL+来MSL的时间。B不重传就证明B收到了嘛。并会让B重传的FIN在网络中消逝。

摘要

  1. 来MSL+去MSL=2MSL,MSL:报文生存时间
  2. 不论是否要收到B的重传,A都得等。收到就重新应答,没收到就默认B收到,B不进行重传
  3. 确认B能收到A的确认应答,进而让B正常关闭
  4. 让B重传的FIN在网络中消逝

OSI和TCPIP模型

层级 OSI七层模型 TCP/IP四层 常用协议
7 应用 应用 HTTPS、HTTP、SSH、DNS、FTP、SMTP(Email)
6 表示层 应用
5 会话层 应用
4 传输层 传输层 TCP、UDP
3 网络层 网络层 IP、ICMP、ARP
2 数据链路层 网络接口层 物理线路、光纤等处理连接网络的硬件部分
1 物理层 网络接口层

WebSocket

不受同源限制,是一个新的协议。一般需要HTTP协议来帮忙握手建立连接。后面就一直保持着全双工通信方式。

HTTP缓存

浏览器缓存通常分为两种:强缓存和协商缓存。

强缓存 在缓存期间不发起新请求,返回200

  • Expires,http1.0,是绝对时间的GMT格式字符串,在此时间前都有效
  • Cache-Control,http1.1,max-age=xxx秒,代表缓存xxx秒,优先级高
  • 二者都是响应中携带的字段,用来表示资源缓存时间,Expires使用绝对时间,当服务器与客户端时间偏差大可能就会导致缓存混乱。
  • 强制刷新时,请求中会携带Cache-Control:no-cache和Pragma:no-cache

Cache-Control的常用指令:

  • no-cache:不使用本地缓存,需要使用协商缓存(校验新鲜度)
  • no-store:禁用缓存,没次都从原始服务器获取
  • public :响应可以被任何对象(浏览器、代理)缓存
  • private: 只能被浏览器缓存,代理服务器不得缓存

协商缓存 如果缓存过期了,就要用协商缓存,需要一次请求,如果缓存有效,返回304

  • Last-Modify(in 响应头)和If-Modify-Since(in 请求头)是一对的,值是资源最后修改时间(GMT格式字符串)
  • Etag(每次都携带)和If-None-Match也是一对的,是文件的校验码,用来判断是否命中缓存
  • 服务器会优先验证ETag,If-None-Match字段和Etag一致则返回304
  • ETag对比Last-Modified优势:
    • 一些文件可能内容不变,但修改时间便了,但这并不算是修改
    • Last-Modified精度是秒,要是文件修改频繁也可能认不出
    • 可能服务器不能精确获取文件的最后修改时间

clipboard.png

HTTP Header

请求字段(Request)

字段 描述 实例
Accept 可接受的内容类型(MIME) Accept: text/plain
Accept-Charset 可接受的字符集 Accept-Charset: utf-8
Accept-Encoding 可接受的编码 Accept-Encoding:gzip, deflate
Accept-Language 可接受的语言 Accept-Language: en,zh
Accept-Ranges 定义范围请求的单位 Accept-Ranges: bytes
Range 以字节为单位,传输0到499字节范围的内容 Range: bytes=0-499
Authorization 用于超文本传输协议的认证的认证信息 Authorization: token字符串
Cache-Control 强缓存相关 Cache-Control: no-cache
Cookie 由服务端返回的Cookie Cookie: $Version=1; Skin=new;
Content-Length 请求内容长度 Content-Length: 348
Content-Type 请求体的MIME类型 Content-Type: application/x-www-form-urlencoded
Referer 请求来路 Referer: http://www.baidu.com
Upgrade 协议转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 (Websocket也是)
User-Agent 浏览器的浏览器身份标识字符串 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36

响应字段

字段 描述 实例
Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600;
Content-Encoding 返回内容的压缩编码类型 Content-Encoding: gzip
Content-Length 返回内容的长度 Content-Length: 9951
Date 原始服务器消息发出的时间 Date: Thu, 10 Feb 2022 14:06:04 GMT
Location 重定向到新位置 Location: http://www.baidu.com
Keep-Alive 便于连接复用 Keep-Alive: 300
Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD

内容对应系列

Accpet-* 代表可接受的类型

Content-* 代表要返回的类型

缓存系列

请求头的host,origin,refer的区别是什么

TODO

HTTP状态码

  • 1xx:表示还在协议处理的中间状态,例如要建立websocket链接
  • 2xx:表示成功,204与200相同,但响应头后没有body数据,206表示部分内容,用于HTTP分块下载和断点续传,搭配响应头字段Content-Range
  • 301:永久重定向, 302:临时重定向,304:协商缓存命中
  • 400:错误请求, 401:未授权, 403:禁止访问,405:请求方法不允许,408:超时
  • 500:服务器错误, 503 服务器忙不可用

HTTP流式传输

判断数据流结束的方法

  1. Content-Length 对于已知大小的数据,可以在请求头中添加。

  2. Transfer-Encoding:chunk 分块发送,由一个标明长度为0的chunk标示结束。

    每个Chunk由头+正文组成(CRFL分割),头部内容指定正文的字符总数(16进制),正文就是实际内容

HTTP断点续传

HTTP1.1开始支持,通过Header的两个参数实现。客户端发送请求时对应Range,服务端响应Content-Range,同时响应头变成HTTP/1.1 206 Partial Content

Range: bytes=0-499  //以字节为单位,传输0到499字节范围的内容
??请求----响应??
Content-Range: bytes 0-499/22400 //0-499 是指当前发送的数据的范围,22400则是文件的总大小
Accept-Ranges: bytes // 服务器支持按字节下载

搭配If-Range可判断实体是否发生改变(判断Etag或者Last-Modified返回值)

补充:

前端使用localStorage记录已上传切片的Hash值。

字节跳动面试官:请你实现一个大文件上传和断点续传

HTTPS

HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。(TLS是更为安全的的升级版SSL)

Http+加密+认证+完整性保护=Https

HTTPS页面中发送HTTP请求

HTTP请求的默认会被浏览器阻止(Mixed Content错误),而不是跨域错误。

混合内容又分为主动混合内容被动混合内容

  • 被动混合内容是指不与页面其余部分进行交互的内容,包括图像、视频和音频内容 ,以及无法与页面其余部分进行交互的其他资源。
  • 主动混合内容指的是能与页面交互的内容,包括浏览器可下载和执行的脚本、样式表、iframe、flash、Ajax请求等

借助被动混合内容可以突破限制

const img = new Image();
img.src = 'http的请求地址'

HTTP2.0

增强核心便是二进制分帧层。

帧是最小通信单位。不同数据流(Stream)的帧可以交错发送(并发!),然后接收端根据帧头重新组装。之前HTTP/1x中,一个连接每次只能交互一个响应(串行)。

2.0的分帧突破了这个限制,这让级联文件,雪碧图,域名分片不是成为必要的优化。就这就是多路复用

二进制帧结构:https://juejin.cn/post/6844904100035821575#heading-97

其次是头部压缩:建立索引查表,让请求头字段得到极大程度的精简和服用,还利用霍夫曼编码对整数和字符串处理,压缩头部,减少大小。

最后就是服务端推送,比如在返回HTML的基础上,还把HTML引用的其他资源一起返回给客户端,减少客户端等待时间。

每个cookie:name、value、Domain、Path、Expires/Max-Age、size、HttpOnly、Secure、SameSite、SameParty、Priority

Set-Cookie: session=abc123; SameSite=None

字段详解:

字段 解释
Domain 指定域(对子域生效),在指定域下的请求会携带此Cookie,需要.开始
Path 指定路径(对子路径生效),且同Domain,需要/结尾
Expires/Max-Age 有效期,一个是几点到期(GMT格式),另一个是能活多久(单位秒)
HttpOnly true 或 false。true时不允许Javascript操作此cookie
Secure true 或 false。true时在HTTPS下才传输此cookie
SameSite Strict、Lax或None。用来限制第三方 Cookie(可以参考阮一峰的文章)

Cookie 隔离

请求资源的时候不要让它带cookie怎么做,以此降低请求头大小

使用非主要域名

框架对比

共同点

两者都是数据驱动视图(声明式编程),组件化,虚拟DOM+同层diff,

不同点

Vue核心是数据跟视图绑定,数据收集组件的render函数,在变化时以最小代价生成vnode并diff,且内置功能多,自带编译优化

react是函数式思想,局部重新刷新,推崇纯组件,数据不可变,通过js操作一切

两者DIFF的不同点:
1.React 首位是除删除外是固定不动的,然后依次遍历对比;
2.Vue 的compile 阶段的optimize标记了static 点,可以减少 differ 次数,而且是采用双向遍历方法;

React的更新粒度

在不优化的情况,所有层次都会重新render,生成VDOM并通过diff算法决定要更新的视图部分。不过利用Fiber提供异步渲染,进行弥补,利用memo和shouldComponentUpdate进行优化。

Vue的更新粒度

通过依赖收集对应组件进行精确更新。

当父组件更新时,会重新计算子组件的props,保证只有更变数据所对应的Watcher被调用。

框架特性、生态、开发体验、社区评价、性能、源码等多个角度

其他

低代码

TODO

微前端

前端体系

esbuild

TODO

测试驱动开发TDD

Canvas 和 SVG 区别

Canvas依赖分辨率,文本渲染能力弱,颜色丰富,适合图像密集型,方便保存图像

SVG(Scalable Vector Graphics)使用XML定义,基于矢量,易于编辑,有事件机制

Element 和 Node 区别

Node是基类,node是相对tree这种数据结构而言的。tree就是由node组成!

Element就是Node的子类,Text节点,document 也是Node的子类。Element扩展了更多的方法

HTMLCollection 和 NodeList

都是实时变动的(live)的伪数组,document上的更改会反映到相关对象上(例外:document.querySelectorAll返回的NodeList不是实时的)

函数式编程

  • 可抽象出细粒度的函数,可以组合为更强大的函数
  • 函数式编程讲究就是一个纯,不能有副作用,无状态和数据不可变
  • 函数式编程是运算过程的抽象
  • 复用性好,方便测试和优化,方便理解

圈复杂度CC

圈复杂度(Cyclomatic complexity,CC)也称为条件复杂度,是一种衡量代码复杂度的标准,其符号为V(G)。

节点判定法V (G) = P + 1,常见P(判定点):

  • if 语句
  • while 语句
  • for 语句
  • case 语句
  • catch 语句
  • and 和 or 布尔操作
  • ? : 三元运算符

降低圈复杂度的方法

  • 简化、合并条件表达式
  • 将条件判定提炼出独立函数
  • 将大函数拆成小函数
  • 以明确函数取代参数
  • 替换算法

TS中Type 跟 Interface 的区别?

都是描述类型,差距不大。interface更注重于描述数据结构 ,type侧重于描述类型

interface Person{
  name:string;
  age: number;
}
type Sex = 'MALE'|'FEMALE'

type还有专属的联合类型

interface Dog {
    name:string
}
interface Cat {
  name:string
}
type Pet = Dog | Cat
let a:Dog={name:'1'} , b:Cat={name:'2'}

let c:Pet = a;

协变和逆变

  • 协变: 允许子类型转换为父类型

    let dog:Dog=new Dog();
    let animal:Animal=dog;
    dog=animal; // Error,Aniaml不满足子类Dog(比如没有狗叫方法)
    
  • 逆变: 允许父类型转换为子类型

    interface Animal{}
    interface Dog  extends Animal{
        bark:()=>void
    }
    let db:(d:Dog)=>void=function(d:Dog){ d.bark() }
    let ab:(a:Animal)=>void=function(a:Animal){}
    
    db = ab
    ab = db // TS Error  animal没有bark方法
    db({bark(){}}); // 调用原ab的函数,Dog发散为Aniamal,安全
    ab({})  // 调用原db的函数,但{}并没有bark方法
    
    
  • 协变表示类型收敛,即类型范围缩小或不变。逆变反之(发散)

  • 除了函数参数类型是逆变,都是协变

iframe安全

iframe内容获取: iframe.contentWindowiframe.contentDocumentwindow.frames['ifr1']

iframe获取父级内容: window.top(最顶级)window.parent

嵌套检测:window.self === window.top | top.location.host === self.location.host(限定域名)

禁止被作为iframe: CSP、X-Frame-Options、framekiller

  1. 设置HTTP响应头:Content-Security-Policy:(frame-ancestors 'none | self | xx.com' )
  2. 设置HTTP响应头: X-Frame-Options:(deny、sameorigin、allow-from xxx.com)
  3. 写脚本进行嵌套检测

同源和跨域

同源:同源就是"协议+域名+端口"三者都相同

CORS:主要利用HTTP头的Access-Control-Allow-Origin 来指示请求的资源能共享给哪些域。

JSONP:只支持get,通过创建script并添加全局回调

proxy:反向代理,例如NGINX

server {
  listen  80;
  server_name  client.com;
  location /api {
    proxy_pass server.com;
  }
}

postMessage:H5 API 类似消息订阅机制,可在不同域的页面发送跨域消息

document.domain: 在同个基础域名的前提下,设置该属性为基础域名,实现不同页面间跨域操作

? 在chrome101版本中将要变成可读属性,添加Origin-Agent-Cluster

window.name: 可以直接操作该属性,因此可以向不同域的页面发送消息

软链接和硬链接

  • 硬链接: 与普通文件没什么不同,inode (指针)都指向同一个文件在硬盘中的区块。由文件系统维护一个引用计数。
  • 软链接: 保存了其代表的文件的绝对路径,是另外一种文件,在硬盘上有独立的区块,访问时替换自身路径。 软链接是另外一种类型的文件

SourceMap理解

构建处理前以及处理后的代码之间的一座桥梁,方便定位bug出现的位置

开启source-map后文件末尾会保存map文件的url,map文件中,mappings属性保存这源码对应信息

  第一层是行对应,以分号(;)表示,每个分号对应转换后源码的一行。所以,第一个分号前的内容,就对应源码的第一行,以此类推。

  第二层是位置对应,以逗号(,)表示,每个逗号对应转换后源码的一个位置。所以,第一个逗号前的内容,就对应该行源码的第一个位置,以此类推。

  第三层是位置转换,以VLQ编码表示,代表该位置对应的转换前的源码位置。

"mappings": "AAAA;AACA,c"为例子,分号;分隔的内容是行对应,逗号,分隔前的转换后的位置对应,后面是转换前的位置,所有可以得出转换后的源码分成两行,第一行有一个位置,第二行有两个位置。字母由VLQ编码而成。

Webpack 中的 Source Map主要分为内联外部两种。开发dev环境推荐eval-source-map内联速度也快,信息也完整些。

代码压缩混淆

TODO

路由库原理Hash&History

Hash

  1. 原理是用hashchange监听hash变化
  2. URL中的hash部分不会被浏览器发出去

History

  1. 原理是用popState监听URL的变化
  2. 利用H5提供的pushState 和 repalceState 两个 API 来操作实现 URL 的变化
  3. 对于原生a标签,可以拦截a标签的点击事件以支持URL变化监听
  4. 服务端要进行相应配置try file,应对资源不存在时,返回默认index页面

调用history.pushState()history.replaceState()不会触发popstate事件

vue-router是监听当前url的变化(this.$router.data.current= to),让router-view动态渲染,对应组件

XSS和CSRF

xxs:攻击者在网站上注入的恶意代码,通常是存储型:例如评论中的script标签,可以检查输入输出和httpOnly进行防范。

  • 对HTML标签转义
  • 对于链接属性href|src和事件方法,禁用恶意代码

csrf:利用(冒)用户的cookie恶意发起请求,进行非用户预期请求的攻击,比如修改删除等。可用验证码,token严重,referer检查进行预防

User->黑网站->黑网站要求访问正常网站->正常网站不知道请求是用户自己主动发出的->接受了恶意请求

预渲染

https://zhuanlan.zhihu.com/p/395828896

https://juejin.cn/post/7046898330000949285

指在服务端完成页面的html拼接处理,然后发送浏览器。为了更好的SEO支持,以及更快的首屏渲染。

源码在经过Webpack build时,会分成两份,Server Bundle&Client Bundle

客户端阶段
同步服务端的一些状态数据,避免造成两端组件状态不一致,在挂载vue时,判断mount的dom是否含有data-server-rendered属性,如果有就跳过渲染阶段,执行组件生命周期的钩子。

前端错误监控

页面性能提升

  1. 选择合适的缓存策略
    1. 对于代码文件,文件名添加hash,文件名变化立即更新
    2. 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用
    3. 对于无需缓存的资源 ,可以使用 Cache-control: no-store表示资源无需缓存
  2. 升级HTTP协议到HTTP/2
  3. 懒执行 & 懒加载 &预执行 & 预取
  4. 图片配合CDN,根据屏幕宽度选择适合的图片资源(大小,格式等)
  5. 其他静态资源也使用CDN加载,避免占用单域名的并发请求
  6. webpack方面使用ES6开启tree shaking、优化图片、代码/路由分割、文件名加hash、代码压缩等
  7. 使用requestAnimationFrame 优化动画效果
  8. 使用Intersection Observer API 代替Element.getBoundingClientReact检测元素是否出现
  9. 将长时间运行的 JavaScript 从主线程移到 Web Worker。
  10. 使用transformopacitywill-change建立独立图层,减少paint涉及的范围 或者 减短渲染流水线
  11. 减少不合理的访问元素的布局属性或计算属性,避免触发Force Layout

参考:https://blog.towavephone.com/front-end-performance-optimization-2021/

图片懒加载

性能指标

性能分析工具 Performance 和 Lighthouse

FP,FCP,白屏时间,首屏加载时间

性能监控-埋点

白屏检测

白屏时间是指浏览器从响应用户输入网址地址,到浏览器开始显示内容的时间。白屏时间是首屏时间的一个子集。

白屏时间:performance.timing.responseStart - performance.timing.navigationStart

首屏时间
DOMContentLoad

扫码登陆

简易原理

  1. 用户请求登陆的二维码图片,并生成一个uuid,作为该页面唯一标识,存入redis中
  2. 浏览器拿到二维码和uuid后就不断轮询查看该uuid是否已经登陆成功,是就跳转
  3. 手机端扫码后会将uuid和token提交给服务端,并把uuid 和userid 存储redis
  4. 轮询的具体就是看uuid和userid这个键值对是否存在,在就返回用户信息和token等

安全

二维码设置过期时间、限制二维码使用次数、二维码长度足够长,防止穷举、使用HTTPS

单点登陆

1、用户访问A系统,系统A发现用户未登录,跳转至sso认证中心,并把自己的地址作为参数。

2、sso认证中心发现用户未登录,则引导用户到登录页面。

3、用户输入用户名和密码提交登录。

4、sso认证中心验证用户信息,创建用户->sso之间的会话(全局会话),同时创建授权令牌。

5、sso认证中心带着令牌跳转到A系统

6、系统A拿到令牌,去sso认证中心校验令牌是否有效。

7、sso认证中心校验令牌,返回有效,注册系统A。

8、系统A使用该令牌创建与用户的会话(局部会话),返回请求资源。

9、用户访问系统B。

10、系统B发现用户未登录,跳转至sso认证中心,也将自己的地址作为参数。

11、sso认证中心发现用户已登录,跳转到系统B,并附上令牌。

12、系统B拿到令牌,去sso认证中心校验令牌是否有效。

13、sso认证中心校验令牌,返回有效,注册系统B。

14、系统B使用该令牌创建与用户的局部会话,返回请求资源。

// 参考 https://juejin.cn/post/6911565511226556430#heading-28

OAuth2

OAuth 2.0 是一个授权协议,它允许软件应用代表(而不是充当)资源拥有者去访问资源拥有者的资源。应用向资源拥有者请求授权,然后取得令牌(token),并用它来访问资源,并且资源拥有者不用向应用提供用户名和密码等敏感数据。

参考

JS - 前端面试之道 - http://caibaojian.com/interview-map/frontend/

js常见面试题总结 - 大厂面试题每日一题

金九银十,你准备好面试了吗?(太多了,暂时没看) - https://juejin.cn/post/6996841019094335519

浏览器灵魂之问,请问你能接得住几个? - https://juejin.cn/post/6844904021308735502

浏览器渲染流程 - https://zhuanlan.zhihu.com/p/162722524

迟到的大厂前端面试记录(面试题+部分答案)- https://juejin.cn/post/7017655711291146253