关于javascript:前端面试十万字书籍总结

8次阅读

共计 18365 个字符,预计需要花费 46 分钟才能阅读完成。

JavaScript 专题

防抖

你是否在⽇常开发中遇到⼀个问题,在滚动事件中须要做个简单计算或者实现⼀个按钮的防⼆次点击操作。

这些需要都能够通过函数防抖动来实现。尤其是第⼀个需要,如果在频繁的事件回调中做简单计算,很有可能导致⻚⾯卡顿,不如将屡次计算合并为⼀次计算,只在⼀个准确点做操作。

PS:防抖和节流的作⽤都是防⽌函数屡次调⽤。区别在于,假如⼀个⽤户⼀直触发这个函数,且每次触发函数的距离⼩于 wait,防抖的状况下只会调⽤⼀次,⽽节流的 状况会每隔⼀定工夫(参数 wait)调⽤函数。

咱们先来看⼀个袖珍版的防抖了解⼀下防抖的实现:

// func 是⽤户传⼊须要防抖的函数
// wait 是等待时间
const debounce = (func, wait = 50) => {
    // 缓存⼀个定时器 id
    let timer = 0
    // 这⾥返回的函数是每次⽤户理论调⽤的防抖函数
    // 如果曾经设定过定时器了就清空上⼀次的定时器
    // 开始⼀个新的定时器,提早执⾏⽤户传⼊的⽅法
    return function (...args) {if (timer) clearTimeout(timer)
        timer = setTimeout(() => {func.apply(this, args)
        }, wait)
    }
}
// 不难看出如果⽤户调⽤该函数的距离⼩于 wait 的状况下,上⼀次的工夫还未到就被革除了,并不会执⾏函数

这是⼀个简略版的防抖,然而有缺点,这个防抖只能在最初调⽤。⼀般的防抖会有 immediate 选项,示意是否⽴即调⽤。这两者的区别,举个栗⼦来说:

  • 例如在搜索引擎搜寻问题的时候,咱们当然是心愿⽤户输⼊完最初⼀个字才调⽤查问接⼝,这个时候适⽤ 提早执⾏ 的防抖函数,它总是在⼀连串(距离⼩于 wait 的)函数触发之后调⽤。
  • 例如⽤户给 interviewMap 点 star 的时候,咱们心愿⽤户点第⼀下的时候就去调⽤接⼝,并且胜利之后扭转 star 按钮的样⼦,⽤户就能够⽴⻢失去反馈是否 star 胜利了,这个状况适⽤ ⽴即执⾏ 的防抖函数,它总是在第⼀次调⽤,并且下⼀次调⽤必须与前⼀次调⽤的工夫距离⼤于 wait 才会触发。

下⾯咱们来实现⼀个带有⽴即执⾏选项的防抖函数

// 这个是⽤来获取以后工夫戳的
function now() {return new Date()
}

/**
 * 防抖函数,返回函数间断调⽤时,闲暇工夫必须⼤于或等于 wait,func 才会执⾏
 *
 * @param {function} func 回调函数
 * @param {number} wait 示意工夫窗⼝的距离
 * @param {boolean} immediate 设置为 ture 时,是否⽴即调⽤函数
 * @return {function} 返回客户调⽤函数
 */
function debounce(func, wait = 50, immediate = true) {
    let timer, context, args

    // 提早执⾏函数
    const later = () => setTimeout(() => {
        // 提早函数执⾏结束,清空缓存的定时器序号
        timer = null
        // 提早执⾏的状况下,函数会在提早函数中执⾏
        // 使⽤到之前缓存的参数和高低⽂
        if (!immediate) {func.apply(context, args)
            context = args = null
        }
    }, wait)
    // 这⾥返回的函数是每次理论调⽤的函数
    return function (...params) {
        // 如果没有创立提早执⾏函数(later),就创立⼀个
        if (!timer) {timer = later()
            // 如果是⽴即执⾏,调⽤函数
            // 否则缓存参数和调⽤高低⽂
            if (immediate) {func.apply(this, params)
            } else {
                context = this
                args = params
            }
            // 如果已有提早执⾏函数(later),调⽤的时候革除原来的并从新设定⼀
            // 这样做提早函数会从新计时
        } else {clearTimeout(timer)
            timer = later()}
    }
}

整体函数实现的不难,总结⼀下。

  • 对于按钮防点击来说的实现:如果函数是⽴即执⾏的,就⽴即调⽤,如果函数是提早执⾏的,就缓存高低⽂和参数,放到提早函数中去执⾏。⼀旦我开始⼀个定时器,只有我定时器还在,你每次点击我都从新计时。⼀旦你点累了,定时器工夫到,定时器重置为 null,就能够再次点击了。
  • 对于延时执⾏函数来说的实现:革除定时器 ID,如果是提早调⽤就调⽤函数

节流

防抖动和节流实质是不⼀样的。防抖动是将屡次执⾏变为最初⼀次执⾏,节流是将屡次执⾏ 变成每隔⼀段时间执⾏。

/*
   * 节流 思路:* 先开启一个定时工作执行,定时工作实现后则清空,当再调用时,如果定时工作仍存在则不执行任何操作
   * */
function throttle(fn, space) {
    let task = null;
    return function () {if (!task) {task = setTimeout(function (...arg) {
                task = null;
                fn.apply(this, arg);
            }, space);
        }
    }
}

let throttleShowLog = throttle(()=>{}, 3000);

模仿实现 call 和 apply

能够从以下⼏点来思考如何实现

  • 不传⼊第⼀个参数,那么默认为 window
  • 扭转了 this 指向,让新的对象能够执⾏该函数。那么思路是否能够变成给新的对象增加
    ⼀个函数,而后在执⾏完当前删除?
Function.prototype.myCall = function (context) {
    context = context || window
    // 给 context 增加⼀个属性
    // getValue.call(a, 'yck', '24') => a.fn = getValue
    context.fn = this // 外围
    
    // 将 context 后⾯的参数取出来
    const args = [...arguments].slice(1)
    // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
    const result = context.fn(...args)
    // 删除 fn
    delete context.fn
    return result
}

以上就是 call 的思路,apply 的实现也相似

Function.prototype.myApply = function (context) {
    context = context || window
    context.fn = this
    let result
    // 须要判断是否存储第⼆个参数
    // 如果存在,就将第⼆个参数开展
    if (arguments[1]) {result = context.fn(...arguments[1])
    } else {result = context.fn()
    }
    delete context.fn
    return result
}

bind 和其余两个⽅法作⽤也是⼀致的,只是该⽅法会返回⼀个函数。并且咱们能够通过 bind 实现柯⾥化。

同样的,也来模仿实现下 bind

Function.prototype.myBind = function (context) {if (typeof this !== 'function') {throw new TypeError('Error')
    }
    const _this = this
    const args = [...arguments].slice(1)
    // 返回⼀个函数
    return function F() {// 因为返回了⼀个函数,咱们能够 new F(),所以须要判断
        if (this instanceof F) {return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
    }
}

Promise 实现

Promise 是 ES6 新增的语法,解决了回调天堂的问题。

能够把 Promise 看成⼀个状态机。初始是 pending 状态,能够通过函数 resolve 和 reject,将状态转变为 resolved 或者 rejected 状态,状态⼀旦扭转就不能再次变动。

Generator 实现

Generator 是 ES6 中新增的语法,和 Promise ⼀样,都能够⽤来异步编程

从以上代码能够发现,加上 * 的函数执⾏后领有了 next 函数,也就是说函数执⾏后返回 了⼀个对象。每次调⽤ next 函数能够持续执⾏被暂停的代码。以下是 Generator 函数的简略实现。

Map、FlatMap 和 Reduce

Map 作⽤是⽣成⼀个新数组,遍历原数组,将每个元素拿进去做⼀些变换而后 append 到新 的数组中。

[1, 2, 3].map((v) => v + 1)
// -> [2, 3, 4]

Map 有三个参数,别离是以后索引元素,索引,原数组

['1','2','3'].map(parseInt)
// parseInt('1', 0) -> 1
// parseInt('2', 1) -> NaN
// parseInt('3', 2) -> NaN

FlatMap 和 map 的作⽤⼏乎是雷同的,然而对于多维数组来说,会将原数组降维。能够将 FlatMap 看成是 map + flatten,⽬前该函数在浏览器中还不⽀持。

而且仅反对一维

const res = [1, [2], 3].flatMap((v) => v + 1)
// -> [2, '21', 4]

如果想将⼀个 多维数组彻底的降维,能够这样实现

const flattenDeep = (arr) => Array.isArray(arr)
 ? arr.reduce((a, b) => [...a, ...flattenDeep(b)] , [])
 : [arr]
flattenDeep([1, [[2], [3, [4]], 5]])

Reduce 作⽤是数组中的值组合起来,最终失去⼀个值

function a() {console.log(1);
}
function b() {console.log(2);
}
[a, b].reduce((a, b) => a(b()))
// -> 2 1

async 和 await ⼀个函数如果加上 async,那么该函数就会返回⼀个 Promise

async function test() {return "1";}
console.log(test()); // -> Promise {<resolved>: "1"}

能够把 async 看成将函数返回值使⽤ Promise.resolve() 包裹了下。

await 只能在 async 函数中使⽤。

function sleep() {
    return new Promise(resolve => {setTimeout(() => {console.log('finish')
            resolve("sleep");
        }, 2000);
    });
}

async function test() {let value = await sleep();
    console.log("object");
}

test()

上⾯代码会先打印 finish 而后再打印 object。因为 await 会期待 sleep 函数 resolve,所以即便后⾯是同步代码,也不会先去执⾏同步代码再来执⾏异步代码。

async 和 await 相⽐间接使⽤ Promise 来说,劣势在于解决 then 的调⽤链,可能更清晰精确的写出代码。毛病在于滥⽤ await 可能会导致性能问题,因为 await 会阻塞代码,兴许之后的异步代码并不依赖于前者,但依然须要期待前者实现,导致代码失去了并发性。

下⾯来看⼀个使⽤ await 的代码。

var a = 0
var b = async () => {
    a = a + await 10
    console.log('2', a) // -> '2' 10
    a = (await 10) + a
    console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1

对于以上代码你可能会有纳闷,这⾥阐明下原理

  • ⾸先函数 b 先执⾏,在执⾏到 await 10 之前变量 a 还是 0,因为在 await 外部实现了 generators,generators 会保留堆栈中东⻄,所以这时候 a = 0 被保留了下来
  • 因为 await 是异步操作,遇到 await 就会⽴即返回⼀个 pending 状态的 Promise 对象,临时返回执⾏代码的控制权,使得函数外的代码得以持续执⾏,所以会先执⾏ console.log(‘1’, a)
  • 这时候同步代码执⾏结束,开始执⾏异步代码,将保留下来的值拿进去使⽤,这时候 a = 10
  • 而后后⾯就是惯例执⾏代码了

Proxy

Proxy 是 ES6 中新增的性能,能够⽤来⾃定义对象中的操作

图层

⼀般来说,能够把一般⽂档流看成⼀个图层。特定的属性能够⽣成⼀个新的图层。不同的图 层渲染互不影响,所以对于某些频繁须要渲染的倡议独自⽣成⼀个新图层,提⾼性能。但也 不能⽣成过多的图层,会引起反作⽤。

通过以下⼏个常⽤属性能够⽣成新图层

  • 3D 变换:translate3d、translateZ
  • will-change
  • video、iframe 标签
  • 通过动画实现的 opacity 动画转换
  • position: fixed

重绘(Repaint)和回流(Reflow)重绘和回流是渲染步骤中的⼀⼩节,然而这两个步骤对于性能影响很⼤。

  • 重绘是当节点须要更改外观⽽不会影响布局的,⽐如扭转 color 就叫称为重绘
  • 回流是布局或者⼏何属性须要扭转就称为回流

回流必定会发⽣重绘,重绘不⼀定会引发回流。回流所需的老本⽐重绘⾼的多,扭转深层次 的节点很可能导致⽗节点的⼀系列回流。

所以以下⼏个动作可能会导致性能问题:

  • 扭转 window ⼤⼩
  • 扭转字体
  • 增加或删除款式
  • ⽂字扭转
  • 定位或者浮动
  • 盒模型

很多⼈不晓得的是,重绘和回流其实和 Event loop 无关。

  1. 当 Event loop 执⾏完 Microtasks 后,会判断 document 是否须要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新⼀次。
  2. 执⾏ requestAnimationFrame 回调

缩小重绘和回流

  • 使⽤ translate 代替 top
  • 使⽤ visibility 替换 display: none,因为前者只会引起重绘,后者会引发回流(扭转了布局)
  • 把 DOM 离线后批改,⽐如:先把 DOM 给 display:none (有⼀次 Reflow),而后你修 改 100 次,而后再把它显示进去
  • 不要把 DOM 结点的属性值放在⼀个循环⾥当成循环⾥的变量

    for(let i = 0; i < 1000; i++) {
     // 获取 offsetTop 会导致回流,因为须要去获取正确的值
         console.log(document.querySelector('.test').style.offsetTop)
    }
  • 不要使⽤ table 布局,可能很⼩的⼀个⼩改变会造成整个 table 的从新布局
  • 动画实现的速度的抉择,动画速度越快,回流次数越多,也能够抉择使⽤
    requestAnimationFrame
  • CSS 选择符从右往左匹配查找,防止 DOM 深度过深
  • 将频繁运⾏的动画变为图层,图层可能阻⽌该节点回流影响别的元素。⽐如对于 video 标签,浏览器会⾃动将该节点变为图层。

性能章节

DNS 预解析

DNS 解析也是须要工夫的,能够通过预解析的⽅式来事后取得域名所对应的 IP

缓存

缓存对于前端性能优化来说是个很重要的点,良好的缓存策略能够升高资源的反复加载提⾼ ⽹⻚的整体加载速度。

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

强缓存

实现强缓存能够通过两种响应头实现:Expires 和 Cache-Control。强缓存示意在缓存期 间不须要申请,state code 为 200

Expires: Wed, 22 Oct 2018 08:41:00 GMT

Expires 是 HTTP / 1.0 的产物,示意资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过 期,须要再次申请。并且 Expires 受限于本地工夫,如果批改了本地工夫,可能会造成缓存 生效。

Cache-control: max-age=30

Cache-Control 呈现于 HTTP / 1.1,优先级⾼于 Expires。该属性示意资源会在 30 秒后 过期,须要再次申请。

协商缓存

如果缓存过期了,咱们就能够使⽤协商缓存来解决问题。协商缓存须要申请,如果缓存无效 会返回 304。

协商缓存须要客户端和服务端独特实现,和强缓存⼀样,也有两种实现⽅式。

Last-Modified 和 If-Modified-Since

Last-Modified 示意本地⽂件最初批改⽇期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该⽇期后资源是否有更新,有更新的话就会将新的资源发 送回来。

然而如果在 本地关上缓存⽂件,就会造成 Last-Modified 被批改,所以在 HTTP / 1.1 呈现 了 ETag。

ETag 和 If-None-Match

ETag 相似于⽂件指纹,If-None-Match 会将以后 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级⽐ Last-Modified ⾼。

抉择适合的缓存策略

对于⼤局部的场景都能够使⽤强缓存配合协商缓存解决,然而在⼀些非凡的地⽅可能须要选 择非凡的缓存策略

  • 对于某些不须要缓存的资源,能够使⽤ Cache-control: no-store,示意该资源不需 要缓存
  • 对于频繁变动的资源,能够使⽤ Cache-Control: no-cache 并配合 ETag 使⽤,示意 该资源已被缓存,然而每次都会发送申请询问资源是否更新。
  • 对于代码⽂件来说,通常使⽤ Cache-Control: max-age=31536000(超长工夫) 并配合策略缓存使⽤,而后对⽂件进⾏指纹解决,⼀旦⽂件名变动就会⽴刻下载新的⽂件

使⽤ HTTP / 2.0

因为浏览器会有并发申请限度,在 HTTP / 1.1 时代,每个申请都须要建⽴和断开,耗费了好⼏个 RTT 工夫,并且因为 TCP 慢启动的起因,加载体积⼤的⽂件会须要更多的工夫。

在 HTTP / 2.0 中引⼊了多路复⽤,可能让多个申请使⽤同⼀个 TCP 链接,极⼤的放慢了⽹
⻚的加载速度。并且还⽀持 Header 压缩,进⼀步的缩小了申请的数据⼤⼩。

预加载

在开发中,可能会遇到这样的状况。有些资源不须要⻢上⽤到,然而心愿尽早获取,这时候 就能够使⽤预加载。

预加载其实是 申明式的 fetch,强制浏览器申请资源,并且不会阻塞 onload 事件,能够 使⽤以下代码开启预加载

<link rel="preload" href="http://example.com">

预加载能够⼀定水平上升高⾸屏的加载工夫,因为能够将⼀些不影响⾸屏但重要的⽂件延后 加载,唯⼀毛病就是兼容性不好。

预渲染

能够通过预渲染将下载的⽂件事后在后盾渲染,能够使⽤以下代码开启预渲染

<link rel="prerender" href="http://example.com">

预渲染尽管能够提⾼⻚⾯的加载速度,然而要确保该⻚⾯百分百会被⽤户在之后关上,否则 就⽩⽩浪费资源去渲染

优化渲染过程

懒执⾏

懒执⾏就是将某些逻辑提早到使⽤时再计算。该技术能够⽤于⾸屏优化,对于某些耗时逻辑 并 不须要在⾸屏就使⽤的 ,就能够使⽤懒执⾏。 懒执⾏须要唤醒,⼀般能够通过定时器或者 事件的调⽤来唤醒。

懒加载

懒加载就是将不要害的资源延后加载。

懒加载的原理就是只加载⾃定义区域(通常是可视区域,但也能够是行将进⼊可视区域)内 须要加载的东⻄。对于图⽚来说,先设置图⽚标签的 src 属性为⼀张占位图,将实在的图⽚ 资源放⼊⼀个⾃定义属性中,当进⼊⾃定义区域时,就将⾃定义属性替换为 src 属性,这样 图⽚就会去下载资源,实现了图⽚懒加载

懒加载不仅能够⽤于图⽚,也能够使⽤在别的资源上。⽐如进⼊可视区域才开始播放视频等 等。

⽂件优化

图⽚优化

图⽚加载优化

  1. 不⽤图⽚。很多时候会使⽤到很多润饰类图⽚,其实这类润饰图⽚齐全能够⽤ CSS 去代
    替。
  2. 对于挪动端来说,屏幕宽度就那么点,齐全没有必要去加载原图节约带宽。⼀般图⽚都⽤
    CDN 加载,能够计算出适配屏幕的宽度,而后去申请相应裁剪好的图⽚。
  3. ⼩图使⽤ base64 格局
  4. 将多个图标⽂件整合到⼀张图⽚中(雪碧图)
  5. 抉择正确的图⽚格局:
  6. 对于可能显示 WebP 格局的浏览器尽量使⽤ WebP 格局。因为 WebP 格局具备更好的图像数据压缩算法,能带来更⼩的图⽚体积,⽽且领有⾁眼辨认⽆差别的图像品质,毛病就是兼容性并不好
  7. ⼩图使⽤ PNG,其实对于⼤局部图标这类图⽚,齐全能够使⽤ SVG 代替
  8. 照⽚使⽤ JPEG

其余⽂件优化

  • CSS ⽂件放在 head 中
  • 服务端开启⽂件压缩性能
  • 将 script 标签放在 body 底部,因为 JS ⽂件执⾏会阻塞渲染。当然也能够把 script 标签放在任意地位而后加上 defer,示意该⽂件会 并⾏下载 ,然而会放到 HTML 解析实现后程序执⾏。对于没有任何依赖的 JS ⽂件能够加上 async,示意 加载和渲染后续⽂档元素的过程将和 JS ⽂件的加载与执⾏并⾏⽆序进⾏
  • 执⾏ JS 代码过⻓会卡住渲染,对于须要很多工夫计算的代码能够思考使⽤
    Webworker。Webworker 能够让咱们另开⼀个线程执⾏脚本⽽不影响渲染。

CDN

动态资源尽量使⽤ CDN 加载,因为浏览器对于单个域名有并发申请下限,能够思考使⽤多个 CDN 域名。对于 CDN 加载动态资源须要留神 CDN 域名要与主站不同,否则每次申请都会带 上主站的 Cookie。

其余

使⽤ Webpack 优化项⽬

  • 对于 Webpack4,打包项⽬使⽤ production 模式,这样会⾃动开启代码压缩
  • 使⽤ ES6 模块来开启 tree shaking(树摇),这个技术能够移除没有使⽤的代码
  • 优化图⽚,对于⼩图能够使⽤ base64 的⽅式写⼊⽂件中
  • 依照路由拆分代码,实现按需加载
  • 给打包进去的⽂件名增加哈希,实现浏览器缓存⽂

如何渲染⼏万条数据并不卡住界⾯

这道题考查了如何在不卡住⻚⾯的状况下渲染数据,也就是说不能⼀次性将⼏万条都渲染进去,⽽应该⼀次渲染局部 DOM,那么就能够通过 requestAnimationFrame 来每 16 ms 刷 新⼀次

框架基本原理篇

MVVM 由以下三个内容组成

View:界⾯

Model:数据模型

ViewModel:作为桥梁负责沟通 View 和 Model

在 JQuery 期间,如果须要刷新 UI 时,须要先取到对应的 DOM 再更新 UI,这样数据和业务 的逻辑就和⻚⾯有强耦合。

在 MVVM 中,UI 是通过数据驱动的,数据⼀旦扭转就会相应的刷新对应的 UI,UI 如果改 变,也会扭转对应的数据。这种⽅式就能够在业务解决中只关⼼数据的流转,⽽⽆需间接和 ⻚⾯打交道。ViewModel 只关⼼数据和业务的解决,不关⼼ View 如何解决数据,在这种状况 下,View 和 Model 都能够独⽴进去,任何⼀⽅扭转了也不⼀定须要扭转另⼀⽅,并且能够将 ⼀些可复⽤的逻辑放在⼀个 ViewModel 中,让多个 View 复⽤这个 ViewModel。

在 MVVM 中,最核⼼的也就是 数据双向绑定,例如 Angluar 的脏数据检测,Vue 中的数据劫持。

数据劫持

Vue2 外部使⽤了 Object.defineProperty() 来实现双向绑定,通过这个函数能够监听到 set 和 get 的事件。

以上代码简略的实现了如何监听数据的 set 和 get 的事件,然而仅仅如此是不够的,还需 要在适当的时候给属性增加公布订阅

<div>
 {{name}}
</div>

在解析如上模板代码时,遇到 {{name}} 就会给属性 name 增加公布订阅。

Proxy 与 Object.defineProperty 对⽐

Object.defineProperty 尽管曾经可能实现双向绑定了,然而他还是有缺点的。

  1. 只能对属性进⾏数据劫持,所以须要 深度遍历整个对象
  2. 对于数组不能监听到数据的变动(Vue2 重写数组办法)

路由原理

前端路由实现起来其实很简略,实质就是监听 URL 的变动,而后匹配路由规定,显示相应的 ⻚⾯,并且⽆须刷新。⽬前单⻚⾯使⽤的路由就只有两种实现⽅式

  • hash 模式
  • history 模式

www.test.com/#/ 就是 Hash URL,当 # 后⾯的哈希值发⽣变动时,不会向服务器申请数 据,能够通过 hashchange 事件 来监听到 URL 的变动,从⽽进⾏跳转⻚⾯。

History 模式是 HTML5 新推出的性能,⽐之 Hash URL 更加好看

Virtual Dom

为什么须要 Virtual Dom

家喻户晓,操作 DOM 是很消耗性能的⼀件事件,既然如此,咱们能够思考通过 JS 对象来模 拟 DOM 对象,毕竟操作 JS 对象⽐操作 DOM 省时的多。

当然在实际操作中,咱们还须要给每个节点⼀个标识,作为判断是同⼀个节点的根据。所以 这也是 Vue 和 React 中官⽅举荐列表⾥的节点使⽤唯⼀的 key 来保障性能

那么既然 DOM 对象能够通过 JS 对象来模仿,反之也能够通过 JS 对象来渲染出对应的 DOM

Virtual Dom 算法简述
DOM 是多叉树的构造, 如果须要残缺的对⽐两颗树的差别,那么须要的工夫复杂度会是 O(n^ 3),这个复杂度必定是不能承受的。于是 React 团队优化了算法,实现了 O(n) 的复杂度来对⽐差别。

实现 O(n) 复杂度的要害就是 <u> 只对⽐同层的节点,⽽不是跨层对⽐ </u>,这也是思考到在理论业 务中很少会去跨层的挪动 DOM 元素。

所以判断差别的算法就分为了 两步

  • ⾸先从上⾄下,从左往右遍历对象,也就是树的 深度遍历 这⼀步中会给每个节点增加索引,便于最初渲染差别
  • ⼀旦节点有⼦元素,就去判断⼦元素是否有不同

Vue 章节

NextTick 原理剖析

nextTick 能够让咱们在 下次 DOM 更新循环完结之后 执⾏提早回调,⽤于取得更新后的 DOM。

⽣命周期剖析

⽣命周期函数就是组件在初始化或者数据更新时会触发的钩⼦函数。

  • created
  • mounted
  • upadted
  • destoryed

平安章节

XSS

跨⽹站指令码(英语:Cross-site scripting,通常简称为:XSS)是⼀种⽹站应⽤程 式的安全漏洞攻打,是代码注⼊的⼀种。它容许歹意使⽤者将程式码注⼊到⽹⻚上,其余使⽤者在观看⽹⻚时就会受到影响。这类攻打通常蕴含了 HTML 以及使⽤者端 脚本语⾔。

XSS 分为三种:反射型,存储型和 DOM-based

  • 例如通过 URL 获取某些参数
  • ⽐如写了⼀篇蕴含攻打代码 的⽂章

如何进攻

最广泛的做法是本义输⼊输入的内容,对于 引号,尖括号,斜杠 进⾏本义

对于显示富⽂原本说,不能通过上⾯的方法来本义所有字符,因为这样会把须要的格局也过滤掉 。这种状况通常采⽤ ⽩名单过滤 的方法,当然也能够通过⿊名单过滤,然而思考到须要 过滤的标签和标签属性切实太多,更加举荐使⽤⽩名单的⽅式。

CSP

内容安全策略 (CSP) 是⼀个额定的平安层,⽤于检测并减弱某些特定类型的攻打,包含跨站脚本 (XSS) 和数据注⼊攻打等。⽆论是数据盗取、⽹站内容净化还是散发恶 意软件,这些攻打都是次要的⼿段。

CSRF

跨站申请伪造(英语:Cross-site request forgery),也被称为 one-click attack 或 者 session riding,通常缩写为 CSRF 或者 XSRF,是⼀种挟制⽤户在以后已登录 的 Web 应⽤程序上执⾏⾮本意的操作的攻打⽅法。跟跨網站指令碼(XSS)相 ⽐,XSS 利⽤的是⽤户对指定⽹站的信赖,CSRF 利⽤的是⽹站对⽤户⽹⻚浏览器 的信赖。

假如⽹站中有⼀个通过 Get 申请提交⽤户评论的接⼝,那么攻击者就能够在钓⻥⽹站中加⼊ ⼀个图⽚,图⽚的地址就是评论接⼝

<img src="http://www.domain.com/xxx?comment='attack'"/>

如果接⼝是 Post 提交的,就绝对麻烦点,须要⽤表单来提交接⼝

<form action="http://www.domain.com/xxx" id="CSRF" method="post">
 <input name="comment" value="attack" type="hidden">
</form>

如何进攻

防备 CSRF 能够遵循以下⼏种规定:

  1. Get 申请不对数据进⾏批改
  2. 不让第三⽅⽹站拜访到⽤户 Cookie
  3. 阻⽌第三⽅⽹站申请接⼝
  4. 申请时附带验证信息,⽐如验证码或者 token

SameSite

能够对 Cookie 设置 SameSite 属性。该属性设置 Cookie 不随着跨域申请发送,该属性能够很⼤水平缩小 CSRF 的攻打,然而该属性⽬前并不是所有浏览器都兼容。

验证 Referer

对于须要防备 CSRF 的申请,咱们能够通过验证 Referer 来判断该申请是否为第三⽅⽹站发动的。(用 referer 来判断上一页面是不是本人网站)

Token

服务器下发⼀个随机 Token(算法不能简单),每次发动申请时将 Token 携带上,服务器验证 Token 是否无效。

明码平安

明码平安尽管⼤多是后端的事件,然而作为⼀名优良的前端程序员也须要相熟这⽅⾯的常识。

加盐

对于明码存储来说,必然是不能明⽂存储在数据库中的,否则⼀旦数据库泄露,会对⽤户造 成很⼤的损失。并且不倡议只对明码单纯通过加密算法加密,因为存在彩虹表的关系。

⽹络章节

UDP

⾯向报⽂

UDP 是⼀个⾯向报⽂(报⽂能够了解为⼀段段的数据)的协定。意思就是 UDP 只是报⽂的搬运⼯,不会对报⽂进⾏任何拆分和拼接操作。

具体来说

  • 在发送端,应⽤层将数据传递给传输层的 UDP 协定,UDP 只会给数据减少⼀个 UDP 头标识下是 UDP 协定,而后就传递给⽹络层了
  • 在接收端,⽹络层将数据传递给传输层,UDP 只去除 IP 报⽂头就传递给应⽤层,不会任何拼接操作

不可靠性

  1. UDP 是⽆连贯的,也就是说通信不须要建⽴和断开连接。
  2. UDP 也是不牢靠的。协定收到什么数据就传递什么数据,并且也不会备份数据,对⽅能不能收到是不关⼼的
  3. UDP 没有拥塞管制,⼀直会以恒定的速度发送数据。即便⽹络条件不好,也不会对发送速率进⾏调整。这样实现的弊病就是在⽹络条件不好的状况下可能会导致丢包,然而长处也很显著,在某些实时性要求⾼的场景(⽐如电话会议)就须要使⽤ UDP ⽽不是 TCP。

⾼效

因为 UDP 没有 TCP 那么简单,须要保证数据不失落且有序达到。所以 UDP 的头部开销⼩,只有⼋字节,相⽐ TCP 的⾄少⼆⼗字节要少得多,在传输数据报⽂时是很⾼效的。

头部蕴含了以下⼏个数据

  • 两个⼗六位的端⼝号,别离为源端⼝(可选字段)和⽬标端⼝
  • 整个数据报⽂的⻓度
  • 整个数据报⽂的测验和(IPv4 可选 字段),该字段⽤于发现头部信息和数据中的谬误

传输⽅式

UDP 不⽌⽀持⼀对⼀的传输⽅式,同样⽀持⼀对多,多对多,多对⼀的⽅式,也就是说 UDP

提供了单播,多播,⼴播的性能

TCP

头部

TCP 头部⽐ UDP 头部简单的多

对于 TCP 头部来说,以下⼏个字段是很重要的

  • Sequence number,这个序号保障了 TCP 传输的报⽂都是有序的,对端能够通过序号顺 序的拼接报⽂
  • Acknowledgement Number,这个序号示意数据接收端冀望接管的下⼀个字节的编号是 多少,同时也示意上⼀个序号的数据曾经收到
  • Window Size,窗⼝⼤⼩,示意还能接管多少字节的数据,⽤于流量管制
  • 标识符

建⽴连贯三次握⼿

你是否有纳闷明明两次握⼿就能够建⽴起连贯,为什么还须要第三次应答?

因为这是为了防⽌生效的连贯申请报⽂段被服务端接管(防止丢包的状况),从⽽产⽣谬误。

能够设想如下场景。客户端发送了⼀个连贯申请 A,然而因为⽹络起因造成了超时,这时 TCP 会启动超时重传的机制再次发送⼀个连贯申请 B。此时申请顺利达到服务端,服务端应答完就建⽴了申请。如果连贯申请 A 在两端敞开后终于到达了服务端,那么这时服务端会认为客户端⼜须要建⽴ TCP 连贯,从⽽应答了该申请并进⼊ ESTABLISHED 状态。此时客户端其实是 CLOSED 状态,那么就会导致服务端⼀直期待,造成资源的节约。

PS:在建⽴连贯中,任意⼀端掉线,TCP 都会重发 SYN 包,⼀般会重试五次,在建⽴连贯 中可能会遇到 SYN FLOOD 攻打。遇到这种状况你能够抉择调低重试次数或者⼲脆在不能处 理的状况下拒绝请求。

断开链接四次握⼿

为什么 A 要进⼊ TIME-WAIT 状态,期待 2MSL 工夫后才进⼊ CLOSED 状态?

为了保障 B 能收到 A 的确认应答。若 A 发完确认应答后间接进⼊ CLOSED 状态,如果确认 应答因为⽹络问题⼀直没有达到,那么会造成 B 不能失常敞开。

ARQ 协定

ARQ 协定也就是超时重传机制。通过确认和超时机制保障了数据的正确送达,ARQ 协定蕴含
停⽌期待 ARQ 和间断 ARQ。

滑动窗⼝

在上⾯⼩节中讲到了发送窗⼝。在 TCP 中,两端都保护着窗⼝:别离为 发送端窗⼝ 接收端窗⼝

发送端窗⼝蕴含已 发送但未收到应答的数据和能够发送然而未发送的数据

发送端窗⼝是由接管窗⼝残余⼤⼩决定的。接管⽅会把以后接管窗⼝的残余⼤⼩写⼊应答报 ⽂,发送端收到应答后依据该值和以后⽹络拥塞状况设置发送窗⼝的⼤⼩,所以发送窗⼝的 ⼤⼩是一直变动的。

当发送端接管到应答报⽂后,会随之将窗⼝进⾏滑动

滑动窗⼝实现了流量管制。接管⽅通过报⽂告知发送⽅还能够发送多少数据,从⽽保障接管 ⽅可能来得及接收数据。

Zero 窗⼝

在发送报⽂的过程中,可能会遇到对端呈现零窗⼝的状况。在该状况下,发送端会停⽌发送 数据,并启动 persistent timer。该定时器会定时发送申请给对端,让对端告知窗⼝⼤⼩。在 重试次数超过⼀定次数后,可能会中断 TCP 链接。

拥塞解决

拥塞解决和流量管制不同,后者是作⽤于接管⽅,保障接管⽅来得及承受数据。⽽前者是作 ⽤于⽹络,防⽌过多的数据拥塞⽹络,避免出现⽹络负载过⼤的状况。

拥塞解决包含了四个算法,别离为:慢开始,拥塞防止,疾速重传,疾速复原

慢开始算法

慢开始算法,顾名思义,就是在传输开始时将发送窗⼝缓缓指数级扩⼤,从⽽防止⼀开始就 传输⼤量数据导致⽹络拥塞。

慢开始算法步骤具体如下

  1. 连贯初始设置 拥塞窗⼝(Congestion Window) 为 1 MSS(⼀个分段的最⼤数据量)
  2. 每过⼀个 RTT 就将窗⼝⼤⼩乘⼆
  3. 指数级增⻓必定不能没有限度的,所以有⼀个阈值限度,当窗⼝⼤⼩⼤于阈值时就会启动
    拥塞防止算法。

拥塞防止算法

拥塞防止算法相⽐简略点,每过⼀个 RTT (来回通信提早 Round-trip delay)窗⼝⼤⼩只加⼀,这样可能防止指数级增⻓导致⽹ 络拥塞,缓缓将⼤⼩调整到最佳值。

在传输过程中可能 定时器超时的状况,这时候 TCP 会认为⽹络拥塞了,会⻢上进⾏以下步骤:

  • 将阈值设为以后拥塞窗⼝的⼀半
  • 将拥塞窗⼝设为 1 MSS
  • 启动拥塞防止算法

疾速重传

疾速重传⼀般和快复原⼀起呈现。⼀旦接收端收到的报⽂呈现失序的状况,接收端只会回复 最初⼀个程序正确的报⽂序号(没有 Sack 的状况下)。如果收到三个反复的 ACK,⽆需等 待定时器超时再重发⽽是启动疾速重传。具体算法分为两种:

HTTP

HTTP 协定是个⽆状态协定,不会保留状态。

Post 和 Get 的区别

先引⼊ 副作⽤ 和幂等的概念。

副作⽤指对服务器上的资源做扭转,搜寻是⽆副作⽤的,注册是副作⽤的。

幂等指发送 M 和 N 次申请(两者不雷同且都⼤于 1),服务器上资源的状态⼀致,⽐如注册 10 个和 11 个帐号是不幂等的,对⽂章进⾏更改 10 次和 11 次是幂等的。

在标准的应⽤场景上说,Get 多⽤于⽆副作⽤,幂等的场景,例如搜寻关键字。Post 多⽤于 副作⽤,不幂等的场景,例如注册。

在技术上说:

  • Get 申请能缓存,Post 不能
  • Post 绝对 Get 平安⼀点点,因为 Get 申请都蕴含在 URL ⾥,且会被浏览器保留历史纪录,Post 不会,然而在抓包的状况下都是⼀样的。
  • Post 能够通过 request body 来传输⽐ Get 更多的数据,Get 没有这个技术
  • URL 有⻓度限度,会影响 Get 申请,然而这个⻓度限度是浏览器规定的,不是 RFC(Request for Comments,申请意见稿的相干规范)规定的
  • Post ⽀持更多的编码类型且不对数据类型限度

常⻅状态码

2XX 胜利

  • 200 OK,示意从客户端发来的申请在服务器端被正确处理
  • 204 No content,示意申请胜利,但响应报⽂不含实体的主体局部
  • 205 Reset Content,示意申请胜利,但响应报⽂不含实体的主体局部,然而与 204 响应
    不同在于要求申请⽅重置内容
  • 206 Partial Content,进⾏范畴申请

3XX 重定向

  • 301 moved permanently,永久性重定向,示意资源已被调配了新的 URL
  • 302 found,临时性重定向,示意资源长期被调配了新的 URL
  • 303 see other,示意资源存在着另⼀个 URL,应使⽤ GET ⽅法获取资源
  • 304 not modified,示意服务器容许拜访资源,但因发⽣申请未满⾜条件的状况
  • 307 temporary redirect,长期重定向,和 302 含意相似,然而冀望客户端放弃申请⽅法不
    变向新的地址发出请求

4XX 客户端谬误

  • 400 bad request,申请报⽂存在语法错误
  • 401 unauthorized,示意发送的申请须要有通过 HTTP 认证的认证信息
  • 403 forbidden,示意对申请资源的拜访被服务器回绝
  • 404 not found,示意在服务器上没有找到申请的资源

5XX 服务器谬误

  • 500 internal sever error,示意服务器端在执⾏申请时发⽣了谬误
  • 501 Not Implemented,示意服务器不⽀持以后申请所须要的某个性能
  • 502 Bad Gateway,个别来自上游服务器有效响应,多来自 nginx 配置谬误
  • 503 service unavailable,表明服务器临时处于超负载或正在停机保护,⽆法解决申请

HTTP ⾸部

HTTPS

HTTPS 还是通过了 HTTP 来传输信息,然而信息通过 TLS 协定进⾏了加密。

TLS

TLS 协定位于传输层之上,应⽤层之下。⾸次进⾏ TLS 协定传输须要两个 RTT,接下来能够 通过 Session Resumption 缩小到⼀个 RTT。

在 TLS 中使⽤了两种加密技术,别离为:对称加密和⾮对称加密。

对称加密

对称加密就是两边领有雷同的秘钥,两边都晓得如何将密⽂加密解密。

⾮对称加密:

有公钥私钥之分,公钥所有⼈都能够晓得,能够将数据⽤公钥加密,然而将数据解密必须使 ⽤私钥解密,私钥只有散发公钥的⼀⽅才晓得。

TLS 握⼿过程如下图:

<img src=”https://mc-web-1259409954.cos.ap-guangzhou.myqcloud.com/MyImages/20210130224551735.png” alt=”HTTPS(二) — SSL/TLS 工作原理和具体握手过程_- 登程 - 的博客 -CSDN 博客_ssl 具体握手过程 ” style=”zoom: 80%;” />

  1. 客户端发送⼀个随机值,须要的协定和加密⽅式
  2. 服务端收到客户端的随机值,⾃⼰也产⽣⼀个随机值,并依据客户端需要的协定和加密⽅式来使⽤对应的⽅式,发送⾃⼰的证书(如果须要验证客户端证书须要阐明)
  3. 客户端收到服务端的证书并验证是否无效,验证通过会再⽣成⼀个随机值,通过服务端证书的公钥去加密这个随机值并发送给服务端,如果服务端须要验证客户端证书的话会附带证书
  4. 服务端收到加密过的随机值并使⽤私钥解密取得第三个随机值,这时候两端都领有了三个随机值,能够通过这三个随机值依照之前约定的加密⽅式⽣成密钥,接下来的通信就能够通过该密钥来加密解密

通过以上步骤可知,在 TLS 握⼿阶段,两端使⽤⾮对称加密的⽅式来通信,然而因为⾮对称 加密损耗的性能⽐对称加密⼤,所以在正式传输数据时,两端使⽤对称加密的⽅式通信。

PS:以上阐明的都是 TLS 1.2 协定的握⼿状况,在 1.3 协定中,⾸次建⽴连贯只须要⼀个 RTT,后⾯复原连贯不须要 RTT 了。

### HTTP 2.0

HTTP 2.0 相⽐于 HTTP 1.X,能够说是⼤幅度提⾼了 web 的性能。

在 HTTP 1.X 中,为了性能思考,咱们会引⼊雪碧图、将⼩图内联、使⽤多个域名等等的⽅式。这⼀切都是因为浏览器限度了同⼀个域名下的申请数量,当⻚⾯中须要申请很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最⼤申请数量时,残余的资源须要期待其余资源申请实现后能力发动申请。

在 HTTP 1.X 中,因为 队头阻塞 的起因,你会发现申请是这样的

队头阻塞(head-of-line blocking)产生在一个 TCP 分节失落,导致其后续分节不按序达到接管的时候。 该后续分节将被接收端始终放弃,直到失落的第一个分节被发送端重传并达到接管为止。该后续分节的提早递送 确保接管利用过程可能依照发送端的发送程序接收数据

  1. ⼆进制传输

    HTTP 2.0 中所有增强性能的核⼼点在于此。在之前的 HTTP 版本中,咱们是通过 ⽂本的⽅式 传数据 。在 HTTP 2.0 中引⼊了新的编码机制, 所有传输的数据都会被宰割,并采⽤⼆进制 格局码

  2. 多路复⽤

    在 HTTP 2.0 中,有两个⾮常重要的概念,别离是 帧(frame)和流(stream)

    帧代表着最⼩的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流

    多路复⽤,就是在⼀个 TCP 连贯中能够存在多条流。换句话说,也就是能够发送多个申请,对端能够通过帧中的标识晓得属于哪个申请。通过这个技术,能够 防止 HTTP 旧版本中的队头阻塞问题,极⼤的提⾼传输性能。

  3. Header 压缩

    在 HTTP 1.X 中,咱们使⽤⽂本的模式传输 header,在 header 携带 cookie 的状况下,可能 每次都须要反复传输⼏百到⼏千的字节。

    在 HTTP 2.0 中,使⽤了 HPACK 压缩格局对传输的 header 进⾏编码 ,缩小了 header 的⼤ ⼩。 并在两端保护了索引表,⽤于记录呈现过的 header后⾯在传输过程中就能够传输曾经 记录过的 header 的键名,对端收到数据后就能够通过键名找到对应的值。

  4. 服务端 Push

    在 HTTP 2.0 中,服务端能够在客户端某个申请后,被动推送其余资源。

    能够设想以下状况,某些资源客户端是⼀定会申请的,这时就能够采取服务端 push 的技术,提前给客户端推送必要的资源,这样就能够绝对缩小⼀点延迟时间。当然在浏览器兼容的情 况下你也能够使⽤ prefetch。

QUIC

这是⼀个⾕歌出品的基于 UDP 实现的同为传输层的协定,⽬标很远⼤,心愿代替 TCP 协定。

  • 该协定⽀持多路复⽤,尽管 HTTP 2.0 也⽀持多路复⽤,然而上层仍是 TCP,因为 TCP 的重传机制,只有⼀个包失落就得判断失落包并且重传,导致发⽣队头阻塞的问题,然而 UDP 没有这个机制
  • 实现了⾃⼰的加密协议,通过相似 TCP 的 TFO 机制能够实现 0-RTT,当然 TLS 1.3 曾经实现了 0-RTT 了
  • ⽀持重传和纠错机制(向前复原),在只失落⼀个包的状况下不须要重传,使⽤纠错机制复原失落的包

    • 纠错机制:通过异或的⽅式,算登程进来的数据的异或值并独自收回⼀个包,服务端在发现有⼀个包失落的状况下,通过其余数据包和异或值包算出失落包
    • 在失落两个包或以上的状况就使⽤重传机制,因为算不进去了

DNS

DNS 的作⽤就是通过域名查问到具体的 IP。

因为 IP 存在数字和英⽂的组合(IPv6),很不利于⼈类记忆,所以就呈现了域名。你能够把 域名看成是某个 IP 的别名,DNS 就是去查问这个别名的真正名称是什么。

在 TCP 握⼿之前就曾经进⾏了 DNS 查问,这个查问是操作系统⾃⼰做的。当你在浏览器中 想拜访 www.google.com 时,会进⾏⼀下操作:

  1. 操作系统会⾸先在本地缓存中查问
  2. 没有的话会去系统配置的 DNS 服务器中查问
  3. 如果这时候还没得话,会间接去 DNS 根服务器查问,这⼀步查问会找出负责 com 这个⼀级域名的服务器
  4. 而后去该服务器查问 google 这个⼆级域名
  5. 接下来三级域名的查问其实是咱们配置的,你能够给 www 这个域名配置⼀个 IP,而后还能够给别的三级域名配置⼀个 IP

以上介绍的是 DNS 迭代查问,还有种是递归查问,区别就是前者是由客户端去做申请,后者是系统配置的 DNS 服务器做申请,失去后果后将数据返回给客户端。

PS:DNS 是基于 UDP 做的查问。

从输⼊ URL 到⻚⾯加载实现的过程

这是⼀个很经典的⾯试题,在这题中能够将本⽂讲得内容都串联起来。

  1. ⾸先做 DNS 查问,如果这⼀步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来。
  2. 接下来是 TCP 握⼿,应⽤层会下发数据给传输层,这⾥ TCP 协定会指明两端的端⼝号,而后下发给⽹络层。⽹络层中的 IP 协定会确定 IP 地址,并且批示了数据传输中如何跳转路由器。而后包会再被封装到数据链路层的数据帧构造中,最初就是物理层⾯的传输了。
  3. TCP 握⼿完结后会进⾏ TLS 握⼿,而后就开始正式的传输数据。
  4. 数据在进⼊服务端之前,可能还会先通过负责负载平衡的服务器,它的作⽤就是将申请正当的散发到多台服务器上,这时假如服务端会响应⼀个 HTML ⽂件
  5. ⾸先浏览器会判断状态码是什么,如果是 200 那就持续解析,如果 400 或 500 的话就会报错,如果 300 的话会进⾏重定向,这⾥会有个重定向计数器,防止过屡次的重定向,超过次数也会报错。
  6. 浏览器开始解析⽂件,如果是 gzip 格局的话会先解压⼀下,而后通过⽂件的编码格局晓得该如何去解码⽂件。
  7. ⽂件解码胜利后会正式开始渲染流程,先会依据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer,前者会并⾏进⾏下载并执⾏ JS,后者会先下载⽂件,而后期待 HTML 解析实现后程序执⾏,如果以上都没有,就会阻塞住渲染流程直到 JS 执⾏结束。遇到⽂件下载的会去下 载⽂件,这⾥如果使⽤ HTTP 2.0 协定的话会极⼤的提⾼多图的下载效率。
  8. 初始的 HTML 被齐全加载和解析后会触发 DOMContentLoaded 事件
  9. CSSOM 树和 DOM 树构建实现后会开始⽣成 Render 树,这⼀步就是确定⻚⾯元素的布局、款式等等诸多⽅⾯的东⻄
  10. 在⽣成 Render 树的过程中,浏览器就开始调⽤ GPU 绘制,合成图层,将内容显示在屏幕上了

数据结构章节

科班就略了哈 ….

正文完
 0