«
前端通关手册:JavaScript

时间:2022-6   


前端通关手册:JavaScript

来自力扣《前端通关手册:JavaScript》

View / MVVM 框架

对比 React 、Angular 和 Vue

该问题 Vue 官方在 《对比其他框架》 中作过说明,整理如下:

React 和 Vue 不同点

渲染优化

HTML 和 CSS

CSS 作用域

规模

向上扩展:

向下拓展

原生渲染

MobX

Preact 和其它类 React 库

Vue 和 Angular 相同点

Vue 和 Angular 不同点

体积

如何实现一个组件,前端组件的设计原则是什么?

Angular

如何理解 Angular 的依赖注入?

依赖注入(DI)是一种重要应用设计模式

分层注入体系

ModuleInjector

ElementInjector

注入器继承机制

当组件声明依赖时

解析修饰符

VUE

computed 与 watch 的区别?

computed

watch

Vue.nextTick 原理和作用?

Vue3.x 的新特性

API 风格

生命周期

数据

监听

slot

v-model

新功能

性能

React

React 生命周期

React 16 前的生命周期

React 16.3 生命周期

React 16.4 + 生命周期

React 中使用 useEffect 如何清除副作用?

在 useEffect 中返回一个清除函数,名称随意,可以是匿名函数或箭头函数,在清除函数中添加 处理副作用的逻辑,如移除订阅等

function component(props) {
    function handleStatusChange(status) { console.log(status.isOnine) }
    useEffect(() => {
        API.subscribe(props.id, handleStatusChange))
    }
    return function cleanup() {
        API.unsubscribe(props.id, handleStatusChange)
    }
}

对比 React 和 Vue 的 diff 算法?

(1)相同点

(2)不同点

React 中有哪几种类型的组件,如何选择?

CSS

如何实现单行居中,多行居左?

父级元素:

text-align: center;

自身元素:

text-align: left;
display: inline-block;

如何实现 1px 边框

前置知识:
现代 webkit 内核浏览器提供 私有属性

当前设备的物理像素分辨率与CSS像素分辨率比值的最小值

当前设备的物理像素分辨率与 CSS 像素分辨率比值的最大值

分别可以用标准属性 min-resolution 和 max-resolution 替代

方法一:border-width

.border {
    border: 1px solid;
}
@media screen and {min-resolution: 2dppx} {
    .border {
        border: 0.5px solid;
    }
}
@media screen and (min-resolution: 3dppx) {
    .border {
        border: 0.333333px solid;
    }
}

方法二:伪类 + transform

div::after {
    content: '';
    display: block;
    border-bottom: 1px solid;
}
@media only screen and (min-resolution: 2dppx) {
    div::after {
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5);
    }
}
@media only screen and (min-resolution: 3dppx) {
    div::after {
        -webkit-transform: scaleY(.33);
        transform: sacleY(.33);
    }
}

多方法实现高度 100% 容器

(1)百分比

<style>
html, body {
    height: 100%;
}
div {
    height: 100%;
    background-color: azure;
}
</style>
<div></div>

(2)相对单位

<style>
div {
    height: 100vh;
    background-color: azure;
}
</style>
<div></div>

(3)calc

<style>
html, body{
    height: 100%;
}
div {
    height: calc(100%)
}
</style>
<div></div>

多方法实现圆角边框

多方法实现文字描边

JavaScirpt

ES3

a = [],a.push(...[1, 2, 3]) ,a = ?

a = [1, 2, 3],考核点如下:

arr.push(element1, ..., elementN)

综上,题目等同于

a.push(1, 2, 3) // [1, 2, 3]

a = ?, a==1 && a==2 && a==3 成立

== 会触发隐式转换,=== 不会

对象转字符串

对象转数字

对象转布尔值

代码

const a = {
    count: 0,
    valueOf() {
        return ++this.count
    }
}

数组

隐式转换会调用数组的 join 方法,改写此方法

const a = [1, 2, 3]
a.join = a.shift

null == undefined 结果

常见的类型转换

类型 to Boolean to Number to String
Boolean true true 1 "true"
Boolean false false 0 "false"
Number 123 true 123 "123"
Number Infinity true Infinity "Infinity"
Number 0 false 0 "0"
Number NaN false NaN "NaN"
String "" false 0 ""
String "123" true 123 "123"
String "123abc" true NaN "123abc"
String "abc" true NaN "abc"
Null null false 0 "null"
Undefined undefined false NaN "undefined"
Function function() {} true NaN "function(){}"
Object {} true NaN "[object Object]"
Array [] true 0 ""
Array ["abc"] true NaN "abc"
Array ["123"] true 123 "123"
Array ["123", "abc"] true NaN "123, abc"

对比 get 和 Object.defineProperty

相同点

都可以定义属性被查询时的函数

不同点

在 classes 内部使用

对比 escape encodeURI 和 encodeURIComponent

escape

对字符串编码
ASCII 字母、数字 @ * / + - _ . 之外的字符都被编码

encodeURI

对 URL 编码
ASCII 字母、数字 @ * / + 和 ~ ! # $ & () =, ; ?- _ . '之外的字符都被编码

encodeURIComponent

对 URL 编码
ASCII 字母、数字 ~ ! * ( ) - _ . ' 之外的字符都被编码

事件

事件传播的过程

事件冒泡

事件捕获

事件流

阻止事件冒泡

Event Loop 的执行顺序?

宏任务

微任务

执行顺序

为什么 Vue.$nextTick 通常比 setTimeout 优先级高,渲染更快生效?

ES6 新特性

列举 ES6、ES7、ES8、ES9、ES10 新特性

ES6

ES7

ES8

ES9

const match = /(?<year>\d{4})/.exec('2022')
console.log(match.groups.year) // 2022
const match = /(?<=\D)\d+/.exec('a123')
console.log(match[0]) // 123
const match = /(?<!\d)\d+/.exec('a123')
console.log(match[0]) // 123
/\p{...}/u.test('π')
/\P{...}/u.test('π')

ES10

const sym = Symbol('description')
sym.description // description

ES11+

逻辑运算符赋值表达式

a ||= b // 若 a 不存在,则 a = b
a &&= b // 若 a 存在,则 a = b
a ??= b // 若 a 为 null 或 undefined,则 a = b
const a = {}
a?.b?.c // undefined,不报错
123_1569_9128 // 12315699128

变量

列举类数组对象

(1)定义

(2)举例

(3)类数组对象转数组

Array.prototype.push.apply(null, ArrayLike)

闭包

什么是闭包?

闭包是由函数以及声明该函数的词法环境组合而成

什么是词法?

词法,英文 lexical ,词法作用域根据源代码声明变量的位置来确定变量在何处可用

嵌套函数可访问声明于它们外部作用域的变量

什么是函数柯里化?

函数调用:一个参数集合应用到函数

部分应用:只传递部分参数,而非全部参数

柯里化(curry):使函数理解并处理部分应用的过程

bind / apply / call / new

手写 bind

Function.prototype.myBind = function(_this, ...args) {
    const fn = this
    return function F(...args2) {
        return this instanceof F ? new fn(...args, ...args2)
        : fn.apply(_this, args.concat(args2))
    }
}
//使用
function Sum (a, b) {
    this.v= (this.v || 0)+ a + b
    returnthis
}
const NewSum = Sum.myBind({v: 1}, 2)
NewSum(3) // 调用:{v: 6}
new NewSum(3) // 构造函数:{v: 5} 忽略 myBind 绑定this

手写 call

Function.prototype.myCall = function(_this, ...args) {
    if (!_this) _this = Object.create(null)
    _this.fn = this
    const res = _this.fn(...args)
    delete _this.fn
    return res
}
// 使用
function sum (a, b) {
    return this.v + a + b
}
sum.myCall({v: 1}, 2, 3) // 6

手写 apply

Function.prototype.myApply = function(_this, args = []) {
    if (!_this) _this = Object.create(null)
    _this.fn =this
    const res = _this.fn(...args)
    delete _this.fn
    return res
}
// 使用
function sum (a, b) {
    return this.v + a + b
}
sum.myApply({v: 1}, [2, 3]) // 6

手写 new

function myNew(...args) {
    const Constructor = args[0]
    const o = Object.create(Constructor.prototype)
    const res = Constructor.apply(o, args.slice(1))
    return res instanceof Object ? res : o
}
// 使用
function P(v) {
    this.v = v
}
const p = myNew(P, 1) // P {v: 1}

手写防抖

function debounce(fn, delay) {
    let timer = null
    return function (...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            timer = null
            fn.apply(this, args)
        }, (delay + '') | 0 || 1000 / 60)
    }
}

手写节流

function throttle(fn, interval) {
    let timer = null
    return function (...args) {
        if (timer) return
        timer = setTimeout(() => {
            timer = null
            fn.apply(this, args)
        }, (interval +'')| 0 || 1000 / 60)
    }
}

原型链

对比各种继承?

(1)原型链继承

子类原型指向父类实例

function Parent() {}
function Child() {}
Child.prototype = new Parent()
const child = new Child()

好处:

坏处:

(2)构造函数

function Parent() {}
function Child(...args) {
    Parent.call(this, ...args) // Parent.apply(this, args)
}
const child = new Child(1)

好处:

坏处:

(3)组合继承

function Parent() {}
function Child(...args) {
    Parent.call(this, ...args)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
const child = new Child(1)

好处:

(4)对象继承

const Parent = { property: 'value', method() {} }
const Child = Object.create(Parent)
const Parent = { property: 'value', method() {} }
function create(obj) {
    function F() {}
    F.prototype = obj
    return new F()
}
const child  = create(Parent)

好处:

坏处:

(5)寄生组合继承

function Parent() {}
function Child(...args) {
    Parent.call(this, args)
}
function create(obj) {
    function F() {}
    F.prototype =  obj
    return F()
}
function extend(Child, Parent) {
    const clone = create(Parent.prototype)
    clone.constructor = Child
    Child.prototype = clone
}
extend(Child, Parent)
const child = new Child(1)

(6)ES6继承

class Parent {
    constructor(property) {
        this.property = property
    }
    method() {}
}
class Child extends Parent {
    constructor(property) {
        super(property)
    }
}

模块化

webpack 中 loader 和 plugin 的区别?

loader

plugin

如何自定义一个 webpack 插件?

示例:

实现一个 MyPlugin,获取指定图片,新增静态资源到本地

class MyPlugin { // 声明类
    apply(compiler) { // 新增 apply 方法
        // 声明生命周期函数
        compiler.hooks.additionalAssets.tapAsync('MyPlugin', (compilation, callback) => {
            download('https://img.shields.io/npm/v/webpack.svg', res => {
                if (res.status === 200) {
                    // 调用 API
                    compilation.assets['webpack-version.svg'] = toAsset(res)
                    // 异步处理后,调用回调函数
                    callback()
                } else {
                    callback(new Error('[webpack-example-plugin] Unable to download the image'))
                }
            })
        })
    }
}

对比 import、import() 和 requrie

- import import() require
规范 ES6Module ES6Module CommonJS
执行阶段 静态 编译阶段 动态 执行阶段 动态 执行阶段
顺序 置顶最先 异步 同步
缓存
默认导出 default default 直接赋值
导入赋值 解构赋值,传递引用 在then方法中解构赋值,属性值是仅可读,不可修改 基础类型 赋值,引用类型 浅拷贝

如何实现一个深拷贝,要点是什么?

JSON

function deepCopy(o) {
    return JSON.parse(JSON.stringify(o))
}

递归

function deepCopy(target) {
    if (typeof target === 'object') {
        const newTarget = Array.isArray(target) ? [] : Object.create(null)
        for (const key in target) {
            newTarget[key] = deepCopy(target[key])
        }
        return newTarget
    } else {
        return target
    }
}

递归

function deepCopy(target, h = new Map) {
    if (typeof target === 'object') {
        if (h.has(target)) return h.get(target)
        const newTarget = Array.isArray(target) ? [] : Object.create(null)
        for (const key in target) {
            newTarget[key] = deepCopy(target[key], h)
        }
        h.set(target, newTarget)
        return newTarget
    } else {
        return target
    }
}

递归

function deepCopy(target, h = new WeakMap) {
    if (typeof target === 'object') {
      if (h.has(target)) return h.get(target)
      const newTarget = Array.isArray(target) ? [] : Object.create(null)
      for (const key in target) {
        newTarget[key] = deepCopy(target[key], h)
      }
      h.set(target, newTarget)
      return newTarget
    } else {
      return target
    }
}

可以继续完善点

0.1 + 0.2 !== 0.3 的原因,如何解决?

0.1 * 2 = 0.2 0
0.2 * 2 = 0.4 0
0.4 * 2 = 0.8 0
0.8 * 2 = 1.6 1
0.6 * 2 = 1.2 1
0.2 * 2 = 0.4 0
...

用科学计数法表示:2 ^ -4 * 1.10011(0011)

0.2 * 2 = 0.4 0
0.4 * 2 = 0.8 0
0.8 * 2 = 1.6 1
0.6 * 2 = 1.2 1
0.2 * 2 = 0.4 0
...

用科学计数法表示:2 ^ -3 * 1.10011(0011)

0.1100110011001100110011001100110011001100110011001101
+  1.1001100110011001100110011001100110011001100110011010
—————————————————————————————————————————————————————————
= 10.0110011001100110011001100110011001100110011001100111
let q = -2, sum = 2 ** -2
while ((q -= 3) >= -50) sum += 2 ** q + 2 ** --q
sum += 2 ** -52 // 0.30000000000000004
const equal = (a, b, c) => {
    return Math.abs(c - a - b) < Number.EPSILON
}
const equal = (a, b, c) => {// 产生 0.1 + 0.2 === 0.333 结果
return (a + b).toFixed(1) === c.toFixed(1)
}
const sum = (a, b) => {
    const len = Math.max((a + '').split('.')[1].length, (b + '').split('.')[1].length)
    return (a * len + b * len) / len
}

使用bignumber.js、bigdecimal.js 等 JS 库

正则表达式

实现 trim ,去除首尾空格

方法一:ES5

''.trim()

方法二:正则

''.repalce(/^\s+|\s+$/g, '')

匹配查询字符串

/**
 * 获取指定的 querystring 中指定 name 的 value
 * @param {String} name 查询名
 * @param {String} querystring 查询到的值
 * @return {String|undefined} 查询到返回String,没有返回undefined
 */
function query (name, querystring) {
  if (name && name.match(/(?:http[s]?:|^)\/{2}.*?(?:\.)/)) name = encodeURIComponent(name) // 仅编码Url
  const r = (querystring + '').match(new RegExp('(?:^|&)\\s*?' + name + '\\s*=\\s*(.*?)\\s*(?:&|$)')) 
  return r === null ? undefined : decodeURIComponent(r[1])
}

异步编程

如何终止Promise

Promises / A+ 标准

Promise.race 竞速

抛出错误

async/await 的错误捕获

function async get(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => data ? resolve('res') : reject('error'), 1000)
  })
}2. 
function done (data) {
  return get(data).then(res => [null, res]).catch(err => [err, null])
}
[error, res] = await done()

手写 Promise

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function Promise(callback) {
  this.state = PENDING
  this.value = null
  this.resolvedCallbacks = []
  this.rejectedCallbacks = []

  callback(value => {
    if (this.state === PENDING) {
      this.state = RESOLVED
      this.value = value
      this.resolvedCallbacks.map(callback => callback(value))
    }
  }, value => {
    if (this.state === PENDING) {
      this.state = REJECTED
      this.value = value
      this.rejectedCallbacks.map(callback => callback(value))
    }
  })
}
Promise.prototype.then = function (onFulfilled = () => {}, onRejected = () => {}) {
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onFulfilled)
    this.rejectedCallbacks.push(onRejected)
  }
  if (this.state === RESOLVED) {
    onFulfilled(this.value)
  }
  if (this.state === REJECTED) {
    onRejected(this.value)
  }
}

实现异步请求的方式

AJAX -Asynchronous JavaScript and XML

const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://leetcode-cn.com', true)
xhr.responeseType = 'json'
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.response)
  }
}
xhr.ontimeout = function() {
  console.log('超时')
}
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send({ requestBody: 1 })

$.ajax

$.ajax({
  url: 'https://leetcode-cn.com',
  data: {
    requestBody: 1
  },
  suceess(data) {
    console.log(data)
  }
}).done(data) { // jQuery 1.5+ 支持
  console.log(data)
}

Axios

axios({
  url: 'https://leetcode-cn.com',
  data: {
    requestBody: 1
  }
}).then(data => console.log(data))

Fetch

fetch('https://leetcode-cn.com', {
  requestBody: 1
}).then(res => res.json()).then(res => res)

性能

JavaScript 垃圾回收机制

两种回收检测模式

引用计数:

清除没有任何引用指向的数据。无法处理循环引用。IE6使用

标记清除:

标记可达数据,清除不可达数据。可处理循环引用。现代浏览器广泛使用

标记清除的优化:

标记清除存在 内存不连续,回收效率低,偶尔卡顿 的缺点

详解标记整理算法

前端常见的内存溢出途径,如何避免?

占用内存且无法被垃圾回收机制回收对象,容易导致内存溢出(泄漏),让应用程序占用本应回收或不需要占用的内存

意外的全局变量:

全局变量是标记清除中的「根」,除非定义为空 或 重新分配,不可回收

避免:尽量不使用全局变量,在 JavaScript 头部使用严格模式

未清除的计时器

避免:使用 requestAnimationFrame / setTimeout +递归 代替 setInterval,并设置边界

删除不完全的对象

避免:

闭包中未使用函数引用了可从根访问的父级变量

var global = 0
function closure () {
    let fromGlobal = global // 引用全局变量 global
    function unused () { // 闭包内不使用的函数
        if (fromGlobal) return // 引用父级变量 fromGlobal,导致 unused 占用内存
    }
    global = {} // 每次调用闭包 global 都会重新赋值
    /** 避免 **/
    fromGlobal = null
    closure()
}

算法

位运算

什么是原码、反码、补码?

原码、反码和补码,均有 符号位 和 数值位 两部分

符号位:用 0 表示正,用 1 表示负

在计算机系统中

正数

负数

0

位运算求绝对值?

数在计算机中,用补码表示

负数的补码 = 负数的原码 → 符号位不变,其余位取反,+1

-2
原码:1000 0010
反码:1111 1101
补码:1111 1110(反码加一,计算机实际存储的值)
取反:0000 0001
加一:0000 0010 = 2

解法一:根据符号位,正数返回本身,负数返回 取反 + 1

const abs = x => {
const y = x >>> 31 // 看符号位
return y === 0 ? x : ~x + 1
}

解法二:任何数与 -1 异或 ^ 运算,相当于取反。任何数与 0 异或 ^ 运算,还是本身

-1
原码:1000 0001
反码:1111 1110
补码:1111 1111

无符号右移

const abs = x => {
const y = x >>> 31
return (x ^ -y) + y
}

有符号右移

const abs = x => {
const y = x >> 31
return (x ^ y) - y
}

数组

去除数组中的指定元素

输入:a = ['1', '2', '3', '4', '5', '6'] target = '4'

输出:a = ['1', '2', '3', '5', '6']

方法一

const removeByValue = (a, target) => {
    for (let i = 0; i < a.length; i++) {
        if (target === a[i]) {
            a.splice(i, 1)
        }
    }
    return a
}

方法二

const removeByValue = (a, target) => a.splice(a.findIndex(v => v === target), 1)

方法三

const removeByValue = (a, target) => {
    let j = 0, i = -1
    while (++i < a.length) {
        if (a[i] === target) i++
            a[j++] = a[i]
        }
    a.length--
    return a
}

数组去重方法

function unique (arr) {
    return Array.from(new Set(arr))
}
function unique (arr) {
    return [...new Set(arr)]
}
function unique (arr) {
    const h = Object.create(null)
    arr.forEach(v => h[v] = 1)
    return Object.keys(h).map(v => v | 0)
}
function unique (arr) {
    const h = new Map
    arr.forEach(v => h.set(v, 1))
    return Array.from(h.keys())
}
function unique (arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[i] === arr[j]) {
                arr.splice(j, 1)
                j--
            }
        }
    }
    return arr
}
function unique (arr) {
    arr.sort()
    for (let i = 0; i < arr.length - 1; i++) {
        if (arr[i] === arr[i + 1]) {
            arr.splice(i, 1)
            i--
        }
    }
    return arr
}
function unique (arr) {
    return arr.filter((v, index, ar) => ar.indexOf(v) === index)
}
function unique (arr) {
    const h = {} // 注意只有 {} 才有 hasOwnProperty
    return arr.filter(v => !h.hasOwnProperty(v) && (h[v] = 1))
}
function unique (arr) {
    const r = []
    arr.forEach(v => r.indexOf(v) === -1 && r.push(v)) 
    return r
}
function unique (arr) {
    const r = []
    arr.forEach(v => !r.includes(v) && r.push(v))
    return r
}function unique (arr) {
    const r = []
    arr.forEach(v => !r.includes(v) && r.push(v))
    return r
}

判断一个对象是不是数组 Array

 function isArray(o) {
    return Array.prototype.isPrototypeOf(o)
}
function isArray(o) {
    return o instanceof Array
}
function isArray(o) {
    return Array.isArray(o)
}    * 
function isArray(o) {
  return Object.prototype.toString.call(o) === '[object Array]'
}

移动零

问题

给定一个数组 nums,编写一个函数所有 0 移动到数组的末尾,同时保持非零元素的相对顺序

示例

输入:[0, 1, 0, 3, 12]
输出:[1, 3, 12, 0, 0]

说明
(1)必须在原数组上操作,不能拷贝额外的数组

(2)尽量减少操作次数

解析

(1) 辅助数组

const moveZeros = a => {
    const tmp = new Uint32Array(a.length)
    for (let i = 0, j = 0; i < a.length; i++) if (a[i]) tmp[j++] = a[i]
    return tmp
}

(2) 双指针交换

const moveZeros = a => {
    let i = j = -1
    while (++i < a.length) if (a[i]) a[++j] = a[i]
    while (++j < a.length) a[j] = 0
    return a
}

(3) Sort 排序

const moveZeros = a => a.sort((a, b) => -(b === 0 ^ 0))

空间复杂度为 O(1) 的中序遍历(莫里斯)遍历

const isValidBST = (root, l = -Infinity) {
  while(root) {
    if (root.left) {
      const p = root.left
      while (p.right && p.right !== root) p = p.right
      if (p.right === null) p.right = root, root = root.left
      else {
        root = root.right
      p.right= null
}
    } else {
      root= root.right
    }
  }
}

递归

求最大公约数

辗转相除法 (又称 欧几里得算法)

function gcd(a, b) {
    const t = a % b
    if (t === 0) return b
    return gcd(b, t)
}
function gcd(a, b) {
    let t
    while (t = a % b) {
        a = b
        b = t
    }
    return b
}

更相减损法(又称 九章算术)

function gcd(a, b) {
    if (a === b) return b
    a > b ? a -= b : b -= a
    return gcd(a, b)
}

排序

插入排序

const sort = a => {
    for (let i = 1; i < a.length; i++)
    for (let j = i; j-- && a[j + 1] < a[j];)
    [a[j + 1], a[j]] = [a[j], a[j + 1]]
    return a
}

快速排序

const sort = (a, s = 0, e = a.length - 1) => {
    if (s >= e) return
    let l = s, r = e
    while (l < r) {
        while (l < r && a[r] >= a[s]) r--
        while (l < r && a[l] <= a[s]) l++
        [a[l], a[r]] = [a[r], a[l]]
    }
    [a[l], a[s]] = [a[s], a[l]]
    sort(a, s, l - 1)
    sort(a, l + 1, e)
    return a
}

归并排序

const sort = (a, l = 0, r = a.length - 1) => {
    if (l === r) return 
    const m = l + r >>> 1, t = []
    sort(a, l, m)
    sort(a, m + 1, r)
    let p1 = l, p2 = m + 1, i = 0
    while (p1 <= m || p2 <= r) t[i++] = p2 > r || p1 <= m && a[p1] < a[p2] ? a[p1++] : a[p2++]
    for (i = 0; i < t.length; i++) a[l + i] = t[i]
    return a
}

冒泡排序

const sort = (a) => {
    for(let i = 0; i < a.length - 1; i++)
    for (let j = 0; j < a.length - 1 - i; j++)
    if (a[j] > a[j + 1])[a[j], a[j + 1]] = [a[j + 1], a[j]]
    return a
}

全栈

Node.js

express 中 next 的作用?

next 是 中间件函数的第三参数

对比 express 和 koa?

计算机网络

对比持续通信的方法?

轮询

长连接

长轮询

Flash Socket

WebSocket

socket.io

// 服务端
const io = require("socket.io")(3000)
io.on('connection', socket => {
  socket.on('update item', (arg1, arg2, callback) => {
    console.log(arg1, arg2)
    callback({ status: 'fulfilled' })
  })
})
// 客户端
const socket = io()
socket.emit('update item', "1", { name: 'updated' }, res => {
  console.log(res.status) // ok
})

网络结构按五层和七层分别是?

TCP / IP 体系结构

五层

七层:Open System Inerconnect Reference Model 开放式系统互联通信参考模型

什么是 TCP 三次握手,为什么要三次握手?

(1)什么是 TCP 三次握手?

(2)为什么要三次握手?

需要三次握手,客户端发送确认报文后再建立连接,避免重复建立连接

浏览器有哪些请求方法?

安全:请求会影响服务器资源

幂等:多次执行重复操作,结果相同

方法 描述 请求体 响应体 支持表单 安全 幂等 可缓存
GET 请求资源
HEAD 请求资源头部
POST 发送数据 数据类型由Content-Type 指定 响应头包含 expires 和 max-age
PUT 发送负载 创建或替换目标资源
DELETE 删除资源 不限 不限
CONNECT 创建点到点沟通隧道
OPTIONS 检测服务器支持方法
TRACE 消息环路测试 多用于路由检测
PATCH 部分修改资源

提交表单的内容类型有哪些?

Content-Type:application/x-www-form-urlencoded
...
key1=value1&key2=value2
Content-Type:multipart/form-data; boundary=------数据边界值
...
------数据边界值
Content-Disposition: form-data; name="key1"
value1
------数据边界值
Content-Disposition: form-data; name="key2"
value2
Content-Type:text/plain
...
key1=value1
key2=value2

docker 与虚拟机区别

启动速度

需要资源

轻量

安全性

可管理型

高可用和可恢复性

快速创建、删除

交付、部署

对比 Intel 的 VT-x,VT-d,VT-c

VT-x

VT-d

VT-c

Virtual Machine Direct Connect

Virtual Machine Direct Queues

对比 Cookie、LocalStorage、SessionStorage、Session、Toke

Cookie

LocalStorage

SessionStorage

Seesion

Token

JWT - JSON Web Token

特点

组成

Header(头部)

Payload(负载)

Signature(签名)

算出签名

什么是 Service Worker?

性能

如何测量并优化前端性能?

如何测量并优化前端性能?

(1)前端页面加载阶段
网络

请求响应

解析渲染

(2)衡量性能的时间点

**(3)测量性能的工具

Chrome 浏览器 ,开发者工具

NodeJS

npm i -g sitespeed.io && sitespeed.io https://leetcode-cn.com

线上测试工具

(4)性能优化方法

DNS 预读取:

<link rel="dns-prefetch" href="//leetcode-cn.com" />

预加载

懒加载

渲染

缓存

图片

HTTP

SEO

前端中有哪些 SEO 优化手段?

文字

图片

适配

独立 H5 网站

代码适配

自适应

SSR:Server Side Render 服务端渲染

安全

什么是帆布指纹?

目标:追踪用户

背景:移动互联网 IP 经常变化,用户禁用 cookie

解决:

Canvas Fingerprinting

手写代码

const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const txt = 'canvasfingerprinting'
ctx.fillText(txt, 0, 0)
const b64 = canvas.toDataURL().replace('data:image/png;base64,', '') // 返回一个包含图片的 data URI,并替换 Mine-type,返回 base64 编码
const bin = atob(b64) // 函数能够解码 base64 编码的字符串数据
const canvasFingerprinting = bin2hex(bin.slice(-16, -12)) // 将 Unicode 编码转为十六进制

fingerprintJS2 / fingerprintJS

除 canvas 外,加入其它影响因素:

对比各种分布式 ID 的生成方式?

UUID

数据库自增ID

数据库自增ID段

CREATE TABLE ids_increment (
  type int(8) NOT NULL COMMENT '业务类型',
  max_id int(8) NOT NULL COMMENT '当前业务的最大ID',
  step int(8) NOT NULL COMMENT '当前业务的ID段步长',
  version ini(8) NOT NULL COMMENT '当前业务的版本号,每次分配 +1,乐观锁'
)

Redis

雪花算法

列举各种跨域方法

(1)跨域

协议、域名、端口不同,需要跨域

(2)跨域解决方案

jsonp:JSON with Padding