面试准备

113 阅读31分钟

HTML CSS

1. BFC

BFC块级格式化上下文,,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。

触发条件

  • overflow: hidden
  • display: inline-block/table
  • position: absolute/fixed
  • float

2. 盒子模型

所谓盒子模型就是把HTML页面中的元素看作是一个矩形的盒子,也就是一个盛装内容的容器。每个矩形都由元素的内容、内边距、边框和外边距组成。 页面渲染时,dom 元素所采用的 布局模型。可通过box-sizing进行设置。根据计算宽高的区域可分为:

  • content-box (W3C 标准盒模型)
  • border-box (IE 盒模型)

3.层叠上下文

元素提升为一个比较特殊的图层,在三维空间中 (z轴) 高出普通元素一等。

  • 触发条件

    • 根层叠上下文(html)

    • position

    • css3属性

      • flex
      • transform
      • opacity
      • filter
      • will-change
      • -webkit-overflow-scrolling
  • 层叠等级:层叠上下文在z轴上的排序

    • 在同一层叠上下文中,层叠等级才有意义
    • z-index的优先级最高

4. 居中布局

.father {
    border: 1px solid red;
    width: 300px;
    height: 300px;
}

.son {
    width: 100px;
    height: 100px;
    background: green;    
}
/* 公共代码 */

4.1 absolute + 负margin(宽高)

.father {
    position: relative;
}
.son {
    position: absolute;;
    top: 50%;
    left: 50%;
    margin-left: -50px;
    margin-top: -50px;
}

4.2 absolute + margin auto(宽高)

.father {
    position: relative;
}
.son {
    position: absolute;;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
}

4.3 absolute + calc(宽高)

.father {
    position: relative;
}
.son {
    position: absolute;;
    top: calc(50% - 50px);
    left: calc(50% - 50px);
}

4.4 absolute + transform

.father {
    position: relative;
}
.son {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

4.5 lineheight(高)

.father {
    line-height: 300px;
    text-align: center;
    font-size: 0px;
}
.son {
    font-size: 16px;
    display: inline-block;
    vertical-align: middle;
    line-height: initial;
    text-align: left; /* 修正文字 */
}

4.6 writing-mode

html
 体验AI代码助手
 代码解读
复制代码
<div class="div1">水平方向</div>
<div class="div2">垂直方向</div>
css
 体验AI代码助手
 代码解读
复制代码
.div2 {
    writing-mode: vertical-lr;
}

显示效果如下:

 体验AI代码助手
 代码解读
复制代码
水平方向
垂
直
方
向

更神奇的是所有水平方向上的css属性,都会变为垂直方向上的属性,比如text-align,通过writing-modetext-align就可以做到水平和垂直方向的居中了,只不过要稍微麻烦一点

html
 体验AI代码助手
 代码解读
复制代码
<div class="wp">
    <div class="wp-inner">
        <div class="box">123123</div>
    </div>
</div>
css
 体验AI代码助手
 代码解读
复制代码
/* 此处引用上面的公共代码 */
/* 此处引用上面的公共代码 */

/* 定位代码 */
.wp {
    writing-mode: vertical-lr;
    text-align: center;
}
.wp-inner {
    writing-mode: horizontal-tb;
    display: inline-block;
    text-align: center;
    width: 100%;
}
.box {
    display: inline-block;
    margin: auto;
    text-align: left;
}

4.7css-table

css新增的table属性,可以让我们把普通元素,变为table元素的现实效果,通过这个特性也可以实现水平垂直居中,下面通过css属性,可以让div显示的和table一样

.father {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
.son {
    display: inline-block;
}

4.8flex

css
 体验AI代码助手
 代码解读
复制代码
.father {
    display: flex;
    justify-content: center;
    align-items: center;
}

4.9 grid

.father {
    display: grid;
}
.son {
    align-self: center;
    justify-self: center;
}

5. 选择器优先级

  • !important > 行内样式 > #id > .class > tag > * > 继承 > 默认
  • 选择器 从右往左 解析

6.去除浮动影响,防止父级高度塌陷

  • 通过增加尾元素清除浮动

    • :after / <br> : clear: both
  • 创建父级 BFC

  • 父级设置高度

7.link 与 @import 的区别

  • link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css
  • 当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载
  • @import需要 IE5 以上才能使用
  • link可以使用 js 动态引入,@import不行

8. CSS预处理器(Sass/Less/Postcss)

CSS预处理器的原理: 是将类 CSS 语言通过 Webpack 编译 转成浏览器可读的真正 CSS。在这层编译之上,便可以赋予 CSS 更多更强大的功能,常用功能:

  • 嵌套
  • 变量
  • 循环语句
  • 条件语句
  • 自动前缀
  • 单位转换
  • mixin复用

9.CSS动画

  • transition: 过渡动画

    • transition-property: 属性
    • transition-duration: 间隔
    • transition-timing-function: 曲线
    • transition-delay: 延迟
    • 常用钩子: transitionend
  • animation / keyframes

    • animation-name: 动画名称,对应@keyframes

    • animation-duration: 间隔

    • animation-timing-function: 曲线

    • animation-delay: 延迟

    • animation-iteration-count: 次数

      • infinite: 循环动画
    • animation-direction: 方向

      • alternate: 反向播放
    • animation-fill-mode: 静止模式

      • forwards: 停止时,保留最后一帧
      • backwards: 停止时,回到第一帧
      • both: 同时运用 forwards / backwards
    • 常用钩子: animationend

  • 动画属性: 尽量使用动画属性进行动画,能拥有较好的性能表现

    • translate
    • scale
    • rotate
    • skew
    • opacity
    • color

JavaScript

1. 原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。
  • 构造函数: 可以通过new新建一个对象 的函数。
  • 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数
const instance = new Object()

此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:

// 原型
const prototype = Object.prototype

这里我们可以来看出三者的关系:

实例.__proto__ === 原型

原型.constructor === 构造函数

构造函数.prototype === 原型

// 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线
// 例如: 
// const o = new Object()
// o.constructor === Object   --> true
// o.__proto__ = null;
// o.constructor === Object   --> false
// 注意: 其实实例上并不是真正有 constructor 这个指针,它其实是从原型链上获取的
//      instance.hasOwnProperty('constructor') === false   (感谢 刘博海 Brian 童鞋🥳)
实例.constructor === 构造函数

2.原型链:

原型链是由原型对象组成,每个对象都有 __proto__ 属性,指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。

  • 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出 undefined
  • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。

3. 执行上下文(EC)

执行上下文可以简单理解为一个对象:

  • 它包含三个部分:

    • 变量对象(VO)
    • 作用域链(词法作用域)
    • this指向
  • 它的类型:

    • 全局执行上下文
    • 函数执行上下文
    • eval执行上下文
  • 代码执行过程:

    • 创建 全局上下文 (global EC)
    • 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层
    • 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
    • 函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行

4.作用域链

我们知道,我们可以在执行上下文中访问到父级甚至全局的变量,这便是作用域链的功劳。作用域链可以理解为一组对象列表,包含 父级和自身的变量对象,因此我们便能通过作用域链访问到父级里声明的变量或者函数。

  • 由两部分组成:

    • [[scope]]属性: 指向父级变量对象和作用域链,也就是包含了父级的[[scope]]AO
    • AO: 自身活动对象

如此 [[scopr]]包含[[scope]],便自上而下形成一条 链式作用域

5. 闭包

闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。

  • 闭包会产生一个很经典的问题:

    • 多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
  • 解决:

    • 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
    • 使用setTimeout包裹,通过第三个参数传入
    • 使用 块级作用域,让变量成为自己上下文的属性,避免共享
  • 闭包的优点

  1. 保护变量:闭包可以将变量封装在函数内部,避免全局污染,保护变量不被外部访问和修改。
  2. 延长变量生命周期:闭包使得函数内部的变量在函数执行完后仍然存在,可以在函数外部继续使用。
  3. 实现模块化:闭包可以创建私有变量和私有方法,实现模块化的封装和隐藏,提高代码的可维护性和安全性。
  4. 保持状态:闭包可以捕获外部函数的变量,并在函数执行时保持其状态。这使得闭包在事件处理、回调函数等场景中非常有用。
  • 闭包的缺点
  1. 内存占用:闭包会导致外部函数的变量无法被垃圾回收,从而增加内存占用。如果滥用闭包,会导致内存泄漏问题。
  2. 性能损耗:闭包涉及到作用域链的查找过程,会带来一定的性能损耗。在性能要求高的场景下,需要注意闭包的使用。
  • 使用场景
  1. 自执行函数
  2. 防抖节流
  3. 函数柯里化
  4. 链式调用
  5. 迭代器
  6. 发布订阅者模式
  • 以下是使用闭包时解决内存泄漏的示例
js

function createClosure() {
  let value = 'Hello';

  // 闭包函数
  var closure = function() {
    console.log(value);
  };

  // 解绑定闭包函数,并释放资源
  var releaseClosure = function() {
    value = null; // 解除外部变量的引用
    closure = null; // 解除闭包函数的引用
    releaseClosure = null; // 解除解绑函数的引用
  };

  // 返回闭包函数和解绑函数
  return {
    closure,
    releaseClosure
  };
}

// 创建闭包
var closureObj = createClosure();

// 调用闭包函数
closureObj.closure(); // 输出:Hello

// 解绑闭包并释放资源
closureObj.releaseClosure();

// 尝试调用闭包函数,此时已解绑,不再引用外部变量
closureObj.closure(); // 输出:null

6. script 引入方式:

  • html 静态<script>引入
  • js 动态插入<script>
  • <script defer>: 延迟加载,元素解析完成后执行
  • <script async>: 异步加载,但执行时会阻塞元素渲染

7. 对象的拷贝

  • 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响

    • Object.assign
    • 展开运算符(...)
  • 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响

    • JSON.parse(JSON.stringify(obj)): 性能最快

      • 具有循环引用的对象时,报错
      • 当值为函数、undefined、或symbol时,无法拷贝
    • 递归进行逐一赋值

    • lodash _cloneDeep()

8. new运算符的执行过程

  • 新生成一个对象
  • 链接到原型: obj.__proto__ = Con.prototype
  • 绑定this: apply
  • 返回新对象(如果构造函数有自己 retrun 时,则返回该值)

9. instanceof原理

能在实例的 原型对象链 中找到该构造函数的prototype属性所指向的 原型对象,就返回true。即:

 体验AI代码助手
 代码解读
复制代码
// __proto__: 代表原型对象链
instance.[__proto__...] === instance.constructor.prototype

// return true

10. 代码的复用

当你发现任何代码开始写第二遍时,就要开始考虑如何复用。一般有以下的方式:

  • 函数封装
  • 继承
  • 复制extend
  • 混入mixin
  • 借用apply/call

11. 继承

在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。

  • 最优化: 圣杯模式
 体验AI代码助手
 代码解读
复制代码
var inherit = (function(c,p){
	var F = function(){};
	return function(c,p){
		F.prototype = p.prototype;
		c.prototype = new F();
		c.uber = p.prototype;
		c.prototype.constructor = c;
	}
})();
  • 使用 ES6 的语法糖 class / extends

12. 类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

  • -、*、/、% :一律转换成数值后计算

  • +:

    • 数字 + 字符串 = 字符串, 运算顺序是从左到右
    • 数字 + 对象, 优先调用对象的valueOf -> toString
    • 数字 + boolean/null -> 数字
    • 数字 + undefined -> NaN
  • [1].toString() === '1'

  • {}.toString() === '[object object]'

  • NaN !== NaN+undefined 为 NaN

13. 类型判断

  • typeof / instanceof / object.prototype.toString.call(obj)
  • 基本类型(null): 使用 String(null)
  • 基本类型(string / number / boolean / undefined) + function: 直接使用 typeof即可
  • 其余引用类型(Array / Date / RegExp Error): 调用toString后根据[object XXX]进行判断

很稳的判断封装:

 体验AI代码助手
 代码解读
复制代码
let class2type = {}
'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase()) 

function type(obj) {
    if (obj == null) return String(obj)
    return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
}

14. 模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。

  • 分类:

    • es6: import / export
    • commonjs: require / module.exports / exports
    • amd: require / defined
  • requireimport的区别

    • require支持 动态导入import不支持,正在提案 (babel 下可支持)
    • require同步 导入,import属于 异步 导入
    • require值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化

15. 防抖与节流

防抖函数的实现是,传入一个函数以及一个时间,返回一个防抖化的函数。利用闭包保存定时器,进而实现这个防抖化的效果。

  • 输入框
  • 频繁点击按钮触发事件
  • 缩放浏览器时触发resize

防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。

  • 防抖 (debounce) : 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
js
function debounce(fn,time){
    //利用闭包,定义定时器,返回的函数内部可以获取到这个变量
    let timer 
    return function(...args){  //返回一个可以接收参数的函数    
        //每次触发函数之前,重置定时器
        clearTimeout(timer)
        
        timer = setTimeout(()=>{
            fn.apply(this,args) //执行函数
        },time)
    }
}

//如果我们希望,第一次触发防抖函数的时候,函数立即响应,后续触发函数的时候才进行防抖,可以用下面这种方法实现。
function debounce(fn, time, immediate = true) {
  let timer;
  //标记是否立即调用过
  let isInvoke = false
  return function (...args) {
    //如果没有立即调用过,并且需要立即调用的话
    if(!isInvoke && immediate){
        fn.apply(this, args); 
        isInvoke = true
    }else{
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(this, args); 
          isInvoke = false  //不要忘了在防抖函数结束的时候重置isInvoke哦~
        }, time);
    }
  };
}
  • 节流(throttle) : 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。
js
function throttle (fn,time){
    //保存上次执行的时间戳
    let lastTime = 0
    return function(...args){
        //获取当前的时间戳
        let nowTime = new Date().getTime()
        //如果两次触发的时间间隔大于了time,就执行
        if(nowTime - lastTime >= time){
            fn.apply(this,...args)
            lastTime = nowTime
        }
    }
}
//如果我们希望能控制第一次是否立即执行的话,可以这么实现。
//leading为false表示不想开头立即触发
function throttle(fn, time, options = { leading: false }) {
    const { leading } = options
    let lastTime = 0
    let timer
    return function () {
        const nowTime = new Date().getTime()
        //如果不希望第一个触发的是立即执行的话,就设置lastTime为nowTime
        //这样的话时间间隔就会从0开始计算,直到时间间隔大于给定的time
        
        //lastTime === 0 其实可以用于表示是不是一段连续触发事件的第一个事件
        if(lastTime === 0 && leading === false) lastTime = nowTime 
        const remainTime = time - (nowTime - lastTime)
        if (remainTime <= 0) {
            fn.apply(this)
            lastTime = nowTime
        } 
    }
}

16. 函数执行改变this

由于 JS 的设计原理: 在函数中,可以引用运行环境中的变量。因此就需要一个机制来让我们可以在函数体内部获取当前的运行环境,这便是this

因此要明白 this 指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如:

  • obj.fn(),便是 obj 调用了函数,既函数中的 this === obj
  • fn(),这里可以看成 window.fn(),因此 this === window

但这种机制并不完全能满足我们的业务需求,因此提供了三种方式可以手动修改 this 的指向:

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)

17. ES6/ES7

由于 Babel 的强大和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。

  • 声明

    • let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明
    • const: 声明常量,无法修改
  • 解构赋值

  • class / extend: 类声明与继承

  • Set / Map: 新的数据结构

  • 异步解决方案:

    • Promise的使用与实现

    • generator:

      • yield: 暂停代码
      • next(): 继续执行代码
     体验AI代码助手
     代码解读
    复制代码
    function* helloWorld() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    const generator = helloWorld();
    
    generator.next()  // { value: 'hello', done: false }
    
    generator.next()  // { value: 'world', done: false }
    
    generator.next()  // { value: 'ending', done: true }
    
    generator.next()  // { value: undefined, done: true }
    
    • await / async: 是generator的语法糖, babel中是基于promise实现。
     体验AI代码助手
     代码解读
    复制代码
    async function getUserByAsync(){
       let user = await fetchUser();
       return user;
    }
    
    const user = await getUserByAsync()
    console.log(user)
    

18. AST

抽象语法树 (Abstract Syntax Tree) ,是将代码逐字母解析成 树状对象 的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。例如:


function square(n){
	return n * n
}

通过解析转化成的AST如下图:

19. babel编译原理

  • babylon 将 ES6/ES7 代码解析成 AST
  • babel-traverse 对 AST 进行遍历转译,得到新的 AST
  • 新 AST 通过 babel-generator 转换成 ES5

20. 函数柯里化

在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。

 体验AI代码助手
 代码解读
复制代码
const add = function add(x) {
	return function (y) {
		return x + y
	}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

21. 数组(array)

  • map: 遍历数组,返回回调返回值组成的新数组

  • forEach: 无法break,可以用try/catchthrow new Error来停止

  • filter: 过滤

  • some: 有一项返回true,则整体为true

  • every: 有一项返回false,则整体为false

  • join: 通过指定连接符生成字符串

  • push / pop: 末尾推入和弹出,改变原数组, push 返回数组长度, pop 返回原数组最后一项;

  • unshift / shift: 头部推入和弹出,改变原数组,unshift 返回数组长度,shift 返回原数组第一项 ;

  • sort(fn) / reverse: 排序与反转,改变原数组

  • concat: 连接数组,不影响原数组, 浅拷贝

  • slice(start, end): 返回截断后的新数组,不改变原数组

  • splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组

  • indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标

  • reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值

    • 当传入 defaultPrev 时,从第一项开始;
    • 当未传入时,则为第二项
  • 数组乱序:

 体验AI代码助手
 代码解读
复制代码
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});
  • 数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
 体验AI代码助手
 代码解读
复制代码
Array.prototype.flat = function() {
    return this.toString().split(',').map(item => +item )
}

TS

1. TS 中 interface 和 type 究竟有什么区别?

typescript
interface Person {
    name: string
    age: number
}

type Person = {
    name: string
    age: number
}

const person: Person = {
    name: 'xin',
    age: 23
}

interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

type (类型别名),顾名思义,类型别名只是给类型起一个新名字。它并不是一个类型,只是一个别名而已

就像我们项目中配置 alias,不用写相对路径就能很方便地引入文件

javascript

import componentA from '../../../../components/componentA/index.vue'
变成
import componentA from '@/components/componentA/index.vue

有了 type,我们书写 TS 的时候可以更加方便简洁。

两者相同点

都可以定义一个对象或函数

都允许继承(extends)

两者不同点

type 可以,interface 不行

类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。 -- TS 文档

声明基本类型、联合类型、交叉类型、元组
typescript
 体验AI代码助手
 代码解读
复制代码
type Name = string                              // 基本类型

type arrItem = number | string                  // 联合类型

const arr: arrItem[] = [1,'2', 3]

type Person = { 
  name: Name 
}

type Student = Person & { grade: number  }       // 交叉类型

type Teacher = Person & { major: string  } 

type StudentAndTeacherList = [Student, Teacher]  // 元组类型

const list:StudentAndTeacherList = [
  { name: 'lin', grade: 100 }, 
  { name: 'liu', major: 'Chinese' }
]

interface可以,type 不行

合并重复声明
interface Person {
    name: string
}

interface Person {         // 重复声明 interface,就合并了
    age: number
}

const person: Person = {
    name: 'lin',
    age: 18
}

重复声明 type ,就报错了

type Person = {
    name: string
}

type Person = {     // Duplicate identifier 'Person'
    age: number
}

const person: Person = {
    name: 'lin',
    age: 18
}

小结

interface 是接口,用于描述一个对象。

type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。

平时开发中,一般使用组合或者交叉类型的时候,用 type。

一般要用类的 extendsimplements 时,用 interface。

其他情况,比如定义一个对象或者函数,就看你心情了。

浏览器

1. 跨标签页通讯

不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:

  • 通过父页面window.open()和子页面postMessage

    • 异步下,通过 window.open('about: blank')tab.location.href = '*'
  • 设置同域下共享的localStorage与监听window.onstorage

    • 重复写入相同的值无法触发
    • 会受到浏览器隐身模式等的限制
  • 设置共享cookie与不断轮询脏检查(setInterval)

  • 借助服务端或者中间层实现

2. 浏览器架构

  • 用户界面

  • 主进程

  • 内核

    • 渲染引擎

    • JS 引擎

      • 执行栈
    • 事件触发线程

      • 消息队列

        • 微任务
        • 宏任务
    • 网络异步线程

    • 定时器线程

3. 浏览器下事件循环(Event Loop)

事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表

  • 微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)
  • 宏任务 macrotask(task): setTimout / script / IO / UI Rendering

4. 从输入 url 到展示的过程

  • DNS 解析

  • TCP 三次握手

  • 发送请求,分析 url,设置请求报文(头,主体)

  • 服务器返回请求的文件 (html)

  • 浏览器渲染

    • HTML parser --> DOM Tree

      • 标记化算法,进行元素状态的标记
      • dom 树构建
    • CSS parser --> Style Tree

      • 解析 css 代码,生成样式树
    • attachment --> Render Tree

      • 结合 dom树 与 style树,生成渲染树
    • layout: 布局

    • GPU painting: 像素绘制页面

5. 重绘与回流

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

  • 重绘(repaint) : 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少

  • 回流(reflow) : 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:

    • 页面初次渲染

    • 浏览器窗口大小改变

    • 元素尺寸、位置、内容发生改变

    • 元素字体大小变化

    • 添加或者删除可见的 dom 元素

    • 激活 CSS 伪类(例如::hover)

    • 查询某些属性或调用某些方法

      • clientWidth、clientHeight、clientTop、clientLeft
      • offsetWidth、offsetHeight、offsetTop、offsetLeft
      • scrollWidth、scrollHeight、scrollTop、scrollLeft
      • getComputedStyle()
      • getBoundingClientRect()
      • scrollTo()

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

最佳实践:
  • css

    • 避免使用table布局
    • 将动画效果应用到position属性为absolutefixed的元素上
  • javascript

    • 避免频繁操作样式,可汇总后统一 一次修改
    • 尽量使用class进行样式修改
    • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
    • 极限优化时,修改样式可将其display: none后修改
    • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

6. 存储

我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。

  • 短暂性的时候,我们只需要将数据存在内存中,只在运行时可用

  • 持久性存储,可以分为 浏览器端 与 服务器端

    • 浏览器:

      • cookie: 通常用于存储用户身份,登录状态等

        • http 中自动携带, 体积上限为 4K, 可自行设置过期时间
      • localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为 4~5M

      • indexDB

    • 服务器:

      • 分布式缓存 redis
      • 数据库

7. Web Worker

现代浏览器为JavaScript创造的 多线程环境。可以新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制 相互通信。

基本用法:

 体验AI代码助手
 代码解读
复制代码
// 创建 worker
const worker = new Worker('work.js');

// 向 worker 线程推送消息
worker.postMessage('Hello World');

// 监听 worker 线程发送过来的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}

限制:

  • 同源限制
  • 无法使用 document / window / alert / confirm
  • 无法加载本地资源

8. V8垃圾回收机制

垃圾回收: 将内存中不再使用的数据进行清理,释放出内存空间。V8 将内存分成 新生代空间老生代空间

  • 新生代空间: 用于存活较短的对象

    • 又分成两个空间: from 空间 与 to 空间

    • Scavenge GC算法: 当 from 空间被占满时,启动 GC 算法

      • 存活的对象从 from space 转移到 to space
      • 清空 from space
      • from space 与 to space 互换
      • 完成一次新生代GC
  • 老生代空间: 用于存活时间较长的对象

    • 从 新生代空间 转移到 老生代空间 的条件

      • 经历过一次以上 Scavenge GC 的对象
      • 当 to space 体积超过25%
    • 标记清除算法: 标记存活的对象,未被标记的则被释放

      • 增量标记: 小模块标记,在代码执行间隙执,GC 会影响性能
      • 并发标记(最新技术): 不阻塞 js 执行
    • 压缩算法: 将内存中清除后导致的碎片化对象往内存堆的一端移动,解决 内存的碎片化

9. 内存泄露

  • 意外的全局变量: 无法被回收
  • 定时器: 未被正确关闭,导致所引用的外部变量无法被释放
  • 事件监听: 没有正确销毁 (低版本浏览器可能出现)
  • 闭包: 会导致父级中的变量无法被释放
  • dom 引用: dom 元素被删除时,内存中的引用未被正确清空

可用 chrome 中的 timeline 进行内存标记,可视化查看内存的变化情况,找出异常点。

服务端与网络

1. http/https 协议

  • 1.0 协议缺陷:

    • 无法复用链接,完成即断开,重新慢启动和 TCP 3次握手
    • head of line blocking: 线头阻塞,导致请求之间互相影响
  • 1.1 改进:

    • 长连接(默认 keep-alive),复用

    • host 字段指定对应的虚拟站点

    • 新增功能:

      • 断点续传

      • 身份认证

      • 状态管理

      • cache 缓存

        • Cache-Control
        • Expires
        • Last-Modified
        • Etag
  • 2.0:

    • 多路复用
    • 二进制分帧层: 应用层和传输层之间
    • 首部压缩
    • 服务端推送
  • https: 较为安全的网络传输协议

    • 证书(公钥)
    • SSL 加密
    • 端口 443
  • TCP:

    • 三次握手

    • 四次挥手

    • 滑动窗口: 流量控制

    • 拥塞处理

      • 慢开始
      • 拥塞避免
      • 快速重传
      • 快速恢复
  • 缓存策略: 可分为 强缓存协商缓存

    • Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires

    • 当缓存已经过期时,使用协商缓存

      • 唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,

      • 最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)

        • 如果一致,则直接返回 304 通知浏览器使用缓存
        • 如不一致,则服务端返回新的资源
    • Last-Modified 缺点:

      • 周期性修改,但内容未变时,会导致缓存失效
      • 最小粒度只到 s, s 以内的改动无法检测到
    • Etag 的优先级高于 Last-Modified

2. 常见状态码

  • 1xx: 接受,继续处理
  • 200: 成功,并返回数据
  • 201: 已创建
  • 202: 已接受
  • 203: 成为,但未授权
  • 204: 成功,无内容
  • 205: 成功,重置内容
  • 206: 成功,部分内容
  • 301: 永久移动,重定向
  • 302: 临时移动,可使用原有URI
  • 304: 资源未修改,可使用缓存
  • 305: 需代理访问
  • 400: 请求语法错误
  • 401: 要求身份认证
  • 403: 拒绝请求
  • 404: 资源不存在
  • 500: 服务器错误

3. get / post

  • get: 缓存、请求长度受限、会被历史保存记录

    • 无副作用(不修改资源),幂等(请求次数与资源无关)的场景
  • post: 安全、大数据、更多编码类型

两者详细对比如下图:

4. Websocket

Websocket 是一个 持久化的协议, 基于 http , 服务端可以 主动 push

  • 兼容:

    • FLASH Socket
    • 长轮询: 定时发送 ajax
    • long poll: 发送 --> 有消息时再 response
  • new WebSocket(url)

  • ws.onerror = fn

  • ws.onclose = fn

  • ws.onopen = fn

  • ws.onmessage = fn

  • ws.send()

5. TCP三次握手

建立连接前,客户端和服务端需要通过握手来确认对方:

  • 客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认
  • 服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态
  • 客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态

6. TCP四次挥手

  • 客户端 -- FIN --> 服务端, FIN—WAIT
  • 服务端 -- ACK --> 客户端, CLOSE-WAIT
  • 服务端 -- ACK,FIN --> 客户端, LAST-ACK
  • 客户端 -- ACK --> 服务端,CLOSED

7. Node 的 Event Loop: 6个阶段

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调

  • I/O 阶段: 执行上轮循环残流的callback

  • idle, prepare

  • poll: 等待回调

      1. 执行回调
      1. 执行定时器
      • 如有到期的setTimeout / setInterval, 则返回 timer 阶段
      • 如有setImmediate,则前往 check 阶段
  • check

    • 执行setImmediate
  • close callbacks

跨域

  • JSONP: 利用<script>标签不受跨域限制的特点,缺点是只能支持 get 请求
 体验AI代码助手
 代码解读
复制代码
function jsonp(url, jsonpCallback, success) {
  const script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
  • 设置 CORS: Access-Control-Allow-Origin:*
  • postMessage

安全

  • XSS攻击: 注入恶意代码

    • cookie 设置 httpOnly
    • 转义页面上的输入内容和输出内容
  • CSRF: 跨站请求伪造,防护:

    • get 不修改数据
    • 不被第三方网站访问到用户的 cookie
    • 设置白名单,不被第三方网站请求
    • 请求校验

算法

其实算法方面在前端的实际项目中涉及得并不多,但还是需要精通一些基础性的算法,一些公司还是会有这方面的需求和考核,建议大家还是需要稍微准备下,这属于加分题。

1. 五大算法

  • 贪心算法: 局部最优解法
  • 分治算法: 分成多个小模块,与原问题性质相同
  • 动态规划: 每个状态都是过去历史的一个总结
  • 回溯法: 发现原先选择不优时,退回重新选择
  • 分支限界法

2. 基础排序算法

  • 冒泡排序: 两两比较
 体验AI代码助手
 代码解读
复制代码
	function bubleSort(arr) {
	    var len = arr.length;
	    for (let outer = len ; outer >= 2; outer--) {
	        for(let inner = 0; inner <=outer - 1; inner++) {
	            if(arr[inner] > arr[inner + 1]) {
	                [arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]]
	            }
	        }
	    }
	    return arr;
	}
  • 选择排序: 遍历自身以后的元素,最小的元素跟自己调换位置
 体验AI代码助手
 代码解读
复制代码
function selectSort(arr) {
    var len = arr.length;
    for(let i = 0 ;i < len - 1; i++) {
        for(let j = i ; j<len; j++) {
            if(arr[j] < arr[i]) {
                [arr[i],arr[j]] = [arr[j],arr[i]];
            }
        }
    }
    return arr
}
  • 插入排序: 即将元素插入到已排序好的数组中
 体验AI代码助手
 代码解读
复制代码
function insertSort(arr) {
    for(let i = 1; i < arr.length; i++) {  //外循环从1开始,默认arr[0]是有序段
        for(let j = i; j > 0; j--) {  //j = i,将arr[j]依次插入有序段中
            if(arr[j] < arr[j-1]) {
                [arr[j],arr[j-1]] = [arr[j-1],arr[j]];
            } else {
                break;
            }
        }
    }
    return arr;
}

3. 高级排序算法

  • 快速排序

    • 选择基准值(base),原数组长度减一(基准值),使用 splice
    • 循环原数组,小的放左边(left数组),大的放右边(right数组);
    • concat(left, base, right)
    • 递归继续排序 left 与 right
 体验AI代码助手
 代码解读
复制代码
function quickSort(arr) {
    if(arr.length <= 1) {
        return arr;  //递归出口
    }
    var left = [],
        right = [],
        current = arr.splice(0,1); 
    for(let i = 0; i < arr.length; i++) {
        if(arr[i] < current) {
            left.push(arr[i])  //放在左边
        } else {
            right.push(arr[i]) //放在右边
        }
    }
    return quickSort(left).concat(current,quickSort(right));
}
  • 希尔排序:不定步数的插入排序,插入排序
  • 口诀: 插冒归基稳定,快选堆希不稳定

稳定性: 同大小情况下是否可能会被交换位置, 虚拟dom的diff,不稳定性会导致重新渲染;

4. 递归运用(斐波那契数列): 爬楼梯问题

初始在第一级,到第一级有1种方法(s(1) = 1),到第二级也只有一种方法(s(2) = 1), 第三级(s(3) = s(1) + s(2))

 体验AI代码助手
 代码解读
复制代码
function cStairs(n) {
    if(n === 1 || n === 2) {
        return 1;
    } else {
        return cStairs(n-1) + cStairs(n-2)
    }
}

5. 数据树

  • 二叉树: 最多只有两个子节点

    • 完全二叉树

    • 满二叉树

      • 深度为 h, 有 n 个节点,且满足 n = 2^h - 1
  • 二叉查找树: 是一种特殊的二叉树,能有效地提高查找效率

    • 小值在左,大值在右
    • 节点 n 的所有左子树值小于 n,所有右子树值大于 n

  • 遍历节点

    • 前序遍历

        1. 根节点
        1. 访问左子节点,回到 1
        1. 访问右子节点,回到 1
    • 中序遍历

        1. 先访问到最左的子节点
        1. 访问该节点的父节点
        1. 访问该父节点的右子节点, 回到 1
    • 后序遍历

        1. 先访问到最左的子节点
        1. 访问相邻的右节点
        1. 访问父节点, 回到 1
  • 插入与删除节点

6. 天平找次品

有n个硬币,其中1个为假币,假币重量较轻,你有一把天平,请问,至少需要称多少次能保证一定找到假币?

  • 三等分算法:

      1. 将硬币分成3组,随便取其中两组天平称量
      • 平衡,假币在未上称的一组,取其回到 1 继续循环
      • 不平衡,假币在天平上较轻的一组, 取其回到 1 继续循环