鱼龙混杂之前端面试题

595 阅读20分钟

下面的答案,基本在掘金和百度等都可以找得到,我这里只是做一下记录。慢慢更新吧这个文章,有遇到新的个人觉得有必要就会更新。 我按照自己习惯分了不同模板,有的模块可能还没有题目,有的题目没有答案是答案还在寻找中(因为我自己在摸鱼的时候就会去看看有那些还没有答案),不要急,慢慢等,总会有的,哈哈哈!!!

HTTP

http与https的区别

http
  1. 无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作
  2. 无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。
  3. 基于请求和响应:基本的特性,由客户端发起请求,服务端响应
  4. 简单快速、灵活
  5. 通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性
https

基于HTTP协议,通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护

  1. 内容加密:采用混合加密技术,中间者无法直接查看明文内容
  2. 验证身份:通过证书认证客户端访问的是自己的服务器
  3. 保护数据完整性:防止传输的内容被中间人冒充或者篡改

http1、http1.1、http2的区别

http1和http1.1
版本HTTP1.0HTTP1.1
连接方面使用 非持久连接,即在非持久连接下,一个tcp连接只传输一个web对象。每次请求和响应都需要建立一个单独的连接,每次连接只是传输一个对象,严重影响客户机和服务器的性能默认使用持久连接(然而,HTTP/1.1协议的客户机和服务器可以配置成使用非持久连接)在持久连接下,不必为每个Web对象的传送建立一个新的连接,一个连接中可以传输多个对象。在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。HTTP 1.1的持续连接,也需要增加新的请求头来帮助实现,例如,Connection请求头的值为Keep-Alive时,客户端通知服务器返回本次请求结果后保持连接;Connection请求头的值为close时,客户端通知服务器返回本次请求结果后关闭连接。HTTP 1.1还提供了与身份认证、状态管理和Cache缓存等机制相关的请求头和响应头。并且不允许同时存在两个并行的响应。
缓存方面主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略带宽优化及网络连接的使用
状态码无状态新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除
宽带优化存在一些浪费带宽的现象,例如客户端只是需要某个对象一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端如果接收到100,才开始把请求body发送到服务器。这样当服务器返回401的时候,客户端就可以不用发送请求body了,节约了带宽。
Host头HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
http1.1和http2
版本HTTP1.1HTTP2.0
多路复用在HTTP/1.1协议中,浏览器客户端在同一时间针对同一域名的请求有一定数据限制。超过限制数目的请求会被阻塞HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。而这个强大的功能则是基于“二进制分帧”的特性。
首部压缩不支持header数据的压缩使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快
服务器推送当我们对支持HTTP2.0的web server请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源

get、post的区别

  • GET在浏览器回退时是无害的,而POST会再次提交请求。

  • GET产生的URL地址可以被Bookmark,而POST不可以。

  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。

  • GET请求只能进行url编码,而POST支持多种编码方式。

  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

  • GET请求在URL中传送的参数是有长度限制的,而POST么有。

  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

  • GET参数通过URL传递,POST放在Request body中。

  • GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

    而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

跨域处理

fillder

前端通过fiddler正则匹配解决跨域请求

webpack配置代理

webpack解决本地开发跨域问题

http状态码

HTTP状态码

http https流程

三次握手?四次挥手?

JavaScript

原型链继承

小议JS原型链、继承

闭包

深入贯彻闭包思想,全面理解JS闭包形成过程

Promise

【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)

promise为什么可以链式调用

因为返回了一个新的实例?

从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节

es6

  • 解构赋值
  • 箭头函数
  • ......

数组几个方法的不同

  • pop() 方法从数组中删除最后一个元素并返回该元素
  • push() 方法(在数组结尾处)向数组添加一个新的元素并返回新数组的长度
  • shift() 方法会删除首个数组元素并返回该元素
  • unshift() 方法(在开头)向数组添加新元素返回新数组的长度
  • forEach() 方法为每个数组元素调用一次函数(回调函数)返回undefined
  • map() 方法通过对每个数组元素执行函数来创建新数组。不会对没有值的数组元素执行函数。不会更改原始数组。
  • filter() 方法创建一个包含通过测试的数组元素的新数组。
  • every() 方法检查所有数组值是否通过测试。所有元素都满足条件
  • some() 方法检查某些数组值是否通过了测试。不需要所有元素都满足

js判断对象是否为空

js判断对象是否为空对象的几种方法

深拷贝 -怎么处理循环引用以及爆栈问题

一个递归爆栈引起的对深拷贝的理解

instance of 指向

JS原型链与instanceof底层原理

this是在词法阶段还是语法阶段确定的

JavaScript 引擎(V8)是如何工作的

Parser Parser 是 V8 的解析器,负责根据生成的 Tokens进行语法分析。Parser的主要工作包括:

  • 分析语法错误:遇到错误的语法会抛出异常;
  • 输出 AST:将词法分析输出的词法单元流(数组)转换为一个由元素逐级嵌套所组成的代表了程序语法结构的树——抽象语法树(Abstract Syntax Tree, AST);
  • 确定词法作用域;
  • 生成执行上下文;

weakMap弱引用

WeakMap 弱引用与 Map 强引用的区别

TypeScript

ts比js多了哪些数据类型

any unknown void never tuple

Css

flex布局

Flex 布局教程:语法篇

圣杯、双飞翼布局

css经典布局系列三——三列布局(圣杯布局、双飞翼布局)

垂直居中

[css实现元素水平垂直居中]

margin外边距塌陷

原因:CSS中的BFC详解

解决办法:

  • 在给子元素margin-top的基础上,给父元素加overflow:hidden;

  • 在给子元素margin-top的基础上,给父元素加padding-top

  • 在给子元素margin-top的基础上,给父元素加边框 border: 1px solid transparent;(颜色是透明的)

  • 给子元素的margin-top值改为给父元素的padding-top

Html

cookie、localstroage 、sessionstorage三者的区别

特性CookielocalStoragesessionStorage
数据的生命期一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效除非被清除,否则永久保存仅在当前会话下有效,关闭页面或浏览器后被清除
存放数据大小4K左右一般为5MB一般为5MB
与服务器端通信每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题仅在客户端(即浏览器)中保存,不参与和服务器的通信仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性需要程序员自己封装,源生的Cookie接口不友好源生接口可以接受,亦可再次封装来对Object和Array有更好的支持源生接口可以接受,亦可再次封装来对Object和Array有更好的支持

Vue

生命周期

生命周期钩子

data为什么是function并会返回一个新对象

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象

computed与watch的区别

computed
  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
  3. computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的
  4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
  5. 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
watch
  1. 不支持缓存,数据变,直接会触发相应的操作;
  2. watch支持异步;
  3. 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  4. 当一个属性发生变化时,需要执行对应的操作,一对多;
  5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,immediate:组件加载立即触发回调函数执行,deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

数据双向绑定原理

Vue2.0

Object.defineProperty(),具体实现--- vue双向绑定原理分析

Vue3.0

es6的Proxy,具体实现 --vue 2.0 以及3.0的双向绑定原理的实现

Vue路由 hash和history的区别

Vue-router 中hash模式和history模式的区别

vue-router的实现原理

Vue组件通信方法

Vue组件间通信6种方式

computed和methods有什么区别?

一次渲染当中只执行一次computed函数,后续的访问不会再执行,直到下一次更新之后才会再更新

React

react的我学习的也不多,哈哈哈。下面是我自己的学习笔记

从Vue到React/Redux的学习,小黑的我一步一步过来

设计模式

js实现23种设计模式

微前端

微前端到底是什么?

我自己写的demo 微前端从理论到实践

手写方法

获取url中的参数

var url = 'https://juejin.im/editor/drafts/6867127999888621575?name=cyb&age=18&sex=1'
//普通
function getUrlParamBySplit(url) {
    let paramStr = url.slice(url.indexOf('?') + 1)//name=cyb&age=18&sex=1
    let paramArr = paramStr.split('&')
    let paramObj = {}
    paramArr.forEach(item => {
        paramObj[item.split('=')[0]] = item.split('=')[1]
    })
    return paramObj;
}
// console.log(getUrlParamBySplit(url))

//正则
function getUrlParamByRegExp(url) {
    let paramStr = url.slice(url.indexOf('?') + 1)
    let paramReg = /&?([0-9a-zA-Z]*)=([0-9a-zA-Z]*)/g
    let paramObj = {}
    while ((res = paramReg.exec(paramStr)) !== null) {
        paramObj[res[1]] = res[2]
    }
    return paramObj;
    //两种写法,随便选一种,第二种比较装逼 哈哈哈
    // let paramStr = url.slice(url.indexOf('?') + 1)
    // let paramReg = /&?(?<key>[0-9a-zA-Z]*)=(?<value>[0-9a-zA-Z]*)/g
    // let paramObj = {}
    // while ((res = paramReg.exec(paramStr)) !== null) {
    //     let {key ,value} = res.groups
    //     paramObj[key] = value
    // }
    // return paramObj;
}

console.log(getUrlParamByRegExp(url))

XMLHttpRequest

function ajax(option) {
    let { method = 'GET', params, data, url, async = false, headers } = option;
    return new Promise((resolve, reject) => {
        let xhr;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else {
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
        xhr.open(method, url, async);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr.responseText)
            } else {
                reject(xhr)
            }
        }
        if (headers) {
            Object.keys(Headers).forEach(key => xhr.setRequestHeader(key, headers[key]))
        }
        method === 'GET' ? xhr.send() : xhr.send(data);
    })
}

防抖节流

//防抖 immediate是否立即执行
const debounce = (fn, delay, immediate = false) => {
    let timer = null;
    return function () {
        const args = [...arguments];
        if (timer) clearTimeout(timer);
        if (immediate) {
            const callNow = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, delay)
            if (callNow) fn.apply(this, args)
        } else {
            timer = setTimeout(() => {
                fn.apply(this, args)
            }, delay)
        }
    }
}

//节流
constthrottle = (fn, delay) => {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                fn.apply(context, args)
            }, delay)
        }
    }
}

深拷贝(这里不考虑循环引用以及栈溢出问题)

function deepCopy(obj) {
    if (obj === null) return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object") return obj;
    let cloneObj = new obj.constructor();
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepCopy(obj[key]);
        }
    }
    return cloneObj;
}

深拷贝(考虑循环引用、栈溢出、函数等问题)

『面试的底气』—— 实现一个深拷贝

call、apply、bind

Function.prototype.myApply = function (content = window) {
    content.fn = this;
    let result;
    if (arguments[1]) {
        result = content.fn(...arguments[1])
    } else {
        result = content.fn()
    }
    delete content.fn;
    return result;
}
Function.prototype.myCall = function (content = window) {
    content.fn = this;
    let args = [...arguments].slice(1)
    let result = content.fn(...args)
    delete content.fn;
    return result;
}
Function.prototype.myBind = function(content) {
    if(typeof this != 'function') {
       throw Error('not a function');
    }
    let _this = this;
    let args = [...arguments].slice(1);
    return function F() {
       if(this instanceof F) {
          return _this.apply(this, args.concat([...arguments]))
       }
       return _this.apply(content, args.concat([...arguments]))
    }
 }
 

排序

检测是否为数组和交换元素(下面会用到)

function checkArray(arr) {
    if ((!(arr instanceof Array)) || arr.length <= 2) return;
}

function swap(arr, oldIndex, newIndex) {
    [arr[oldIndex], arr[newIndex]] = [arr[newIndex], arr[oldIndex]];
}
冒泡
// [2, 1, 5, 4, 3];
function bubble(arr) {
    checkArray(arr);
    for (let i = arr.length; i > 0; i--) {
        for (let j = 0; j < i; j++) {
            if (arr[j] > arr[j + 1]) swap(arr, j, j + 1)
        }
    }
    return arr;
}
插入
function insertion(arr) {
    checkArray(arr);
    for (let i = 1; i < arr.length; i++) {
        for (let j = i - 1; j >= 0; j--) {
            if (arr[j] > arr[j + 1]) swap(arr, j, j + 1);
        }
    }
    return arr;
}
选择
function selection(arr) {
    checkArray(arr);
    for (let i = 0; i < arr.length - 1; i++) {
        let minIndex = i;
        for (let j = i + 1; j < arr.length; j++) {
            minIndex = arr[j] < arr[minIndex] ? j : minIndex;
        }
        swap(arr, i, minIndex);
    }
    return arr;
}
快速
function quickSort(arr) {
    checkArray(arr);
    if(arr.length <= 1) return arr 
    let center = arr.length / 2;
    let centerVal = arr.splice(center, 1)[0];
    let leftArr = [];
    let rightArr = [];
    arr.forEach(val => {
        val > centerVal ? rightArr.push(val) : leftArr.push(val);
    });
    return [...quickSort(leftArr), centerVal, ...quickSort(rightArr)];
}

宏任务

微任务

项目优化

其他不知道叫什么好

浏览器从输入url到页面返回做了什么

浏览器从输入url到页面返回做了什么

图片懒加载

图片懒加载

浏览器渲染一帧的流程

浏览器渲染机制

垃圾回收机制

「硬核JS」你真的了解垃圾回收机制吗

一个html引用了100个js 100个css 100个img 如何做缓存 第二次最快 文件改变也可以重新得到文件

协商缓存?强缓存?

如何控制dom渲染的优先级

promise加载?

虚拟列表

「前端进阶」高性能渲染十万条数据(虚拟列表)

es6 class extend继承原理

详解JS的继承(三)-- 图解Es6的Extend

DNS解析出ip地址后怎么找到对方

字节面试被虐后,是时候搞懂 DNS 了

iframe如何判断是否被嵌套?

window.self === window.top
  • 如果返回false –> 说明页面被嵌套在iframe中了
  • 如果返回true –> 说明页面并没有被嵌套在iframe中

上面的代码是判断当前的窗口是不是顶层窗口(判断当前的窗口有没有被嵌套在别的窗口中 ) 如果window.top = window.self 表示没嵌套 当前窗口就是顶层窗口

webpack css中的路径是如何解析的?

透过现象看webpack处理css文件中图片路径转换的具体过程

css-loader和file-loader如何一起工作的?

Webpack 只懂 JavaScript 。

file-loader 让 Webpack 可以理解一些非 JavaScript 的资源,自动生成(emit)文件到目标文件夹(outputPath),然后返回项目运行时用的地址(publicPath)。(也可以不生成文件,只为获得地址,文件再自行处理)。目的是为了借用 Webpack 来一并处理文件依赖。

url-loader 功能跟 file-loader 一样,只是可以对小的资源进行 base64 编码 URL 处理而不 emit 文件。

css-loader 是为了让 Webpack 理解 CSS,只是把 url() 变成 import/require()。还需要上面两个 loader 来处理资源。

说说module、chunk、bundle、asset的区别?

webpack中的bundle、module、chunk分别是什么

webpack hash、chunkhash、contenthash分别是什么

webpack中的hash、chunkhash、contenthash分别是什么

单页和多页应用各自怎么通讯?

  • 单页

  • 多页

    • 1.window.addEventListener('storage',function(event){}) window绑定storage事件,当localstorage中的只发生改变的时候触发
    • 2.通过cookie+setInterval实现
    • 3.websocket实现
    • 4.sharedWorker实现js多线程

const const a=1;词法分析能通过吗?是到语法分析才报错吗?

语法分析阶段

Parser 是 V8 的解析器,负责根据生成的 Tokens 进行语法分析

const const a=1
// VM229:1 Uncaught SyntaxError: Unexpected token 'const'  报错

语法分析

requestAnimationFrame的作用,并实现获取每秒的帧数

2022年了,真的懂requestAnimationFrame么?

jpg png优劣

  • jpg优点:尺寸较小,节省空间;打开速度快
  • jpg缺点:有损格式,在修图时不断保存会导致图片质量不断降低;不支持透明
  • png优点:无损格式,不论保存多少次,理论上图片质量都不会受任何影响;支持透明
  • png缺点:尺寸过大;打开速度与保存速度和jpg没法比

用fetch动态加载一个组件

这个? 你可能不知道的动态组件玩法

dns缓存

浏览器缓存、DNS缓存、CDN缓存

写一个谷歌插件屏蔽广告 思路

使用Chrome插件拦截广告的原理

vite、rollup、webpack之间的区别

总结:rollup更适合打包库,webpack更适合打包项目,vite基于rollup实现了热更新也适合打包项目

  • rollup基于esm打包,打包生成的文件更小。(识别commonJs需要插件)
  • rollup原生支持tree-shaking,webpack2开始支持且消除效果不好。(去除未使用代码)
  • webpack支持代码切割。(分包)
  • webpack支持HMR。(热更新)
  • vite在生产环境通过rollup进行打包(打包体积小),生成esm模块包。(快)
  • vite在开发环境时,基于浏览器支持esm,让浏览器解析模块,然后服务器按需编译返回。同时基于esbuild(go)进行预构建打包不常变动的第三包,并用进行缓存。(缓存+快)
  • vite热更新,实现按需编译,按模块更新。webpack需要全部重新编译并更新。(快)

ESM与Vite

javascript中的esm

说一下 SSR,使用 PM2 写了什么?如果进程挂掉的话如何处理

pm2 实践指南

master挂了的话pm2怎么处理 使用pm2方便开启node集群模式

PM2怎么处理分支挂掉 / 多进程协作

函数柯里化

function add() {
  let args = [...arguments]
  function _add() {
      args.push(...arguments)
      return _add
  }
  _add.toString = function () {
      return args.reduce((pre, cur) => {
          return pre + cur
      })
  }
  return _add
}
console.log(add(1, 2)(3, 4)(5)(6)()().toString())

http1.1的keep-alive和HTTP2多路复用的区别

  • HTTP/1.x 是基于文本的,只能整体去传;HTTP/2 是基于二进制流的,可以分解为独立的帧,交错发送
  • HTTP/1.x keep-alive 必须按照请求发送的顺序返回响应;HTTP/2 多路复用不按序响应
  • HTTP/1.x keep-alive 为了解决队头阻塞,将同一个页面的资源分散到不同域名下,开启了多个 TCP 连接;HTTP/2 同域名下所有通信都在单个连接上完成
  • HTTP/1.x keep-alive 单个 TCP 连接在同一时刻只能处理一个请求(两个请求的生命周期不能重叠);HTTP/2 单个 TCP 同一时刻可以发送多个请求和响应

昨天被问HTTP/1.1的 keep-alive 与HTTP/2多路复用的区别,我懵了......

https如何防止冒充中间人

HTTPS怎么避免中间人攻击

滑动窗口

前端搞算法再也不难,如何套路解题:滑动窗口类

什么是曝光埋点?怎么做?

前端埋点之曝光实现

try{}catch(e){}可以捕获到promise吗?

try..catch 结构,它只能是同步的,无法用于异步代码模式,但是修改成await/async后就可以进行捕获

function f2() {
  try {
    Promise.reject('出错了')
  } catch (e) {
    console.log('f2', e)
  }
}
f2()//报错,无法捕获
async function f() {
  try {
    await Promise.reject('出错了')
  } catch (e) {
    console.log('f', e)
  }
}
f()//f 出错了  ---可以捕获

try/catch无法捕获promise.reject的问题 be2e9e70b9e1574ee6b809060682628.png

5057bf7c677f42ed0c45557e71db28a.png

webpack4/5的tree shaking

聊聊 Webpack4 的 Tree Shaking

定义一个class A然后执行 A instanceof Function的返回值?为什么?

js灵魂整理(六):function 与 class

视图层、逻辑层跟native的交互

微信小程序笔记

部分高级API(可以用做性能优化)

这几个高级前端常用的API,你用到了吗?

如果后台有模块1、模块2、模块3,如果使用webpack打包出单独的模块或者模块1和2、模块2和3这样?

webpack把我们的业务模块分开打包的方法

CDN的优缺点

  • 优点
    • 更快地传递内容
    • 更多同步用户
    • 持续可用性
    • 可靠的内容传递
    • 控制资产交付
    • 防止流量高峰
  • 缺点
    • 成本
    • 服务地点
    • 限制
    • 支持可用性
    • 失去控制

CDN对企业的作用,CDN的优缺点

h5页面中如何调用手机相机

核心API navigator.mediaDevices.getUserMedia

github.com/dragonir/h5…

如何使用http2.0的服务器推送

HTTP/2 服务器推送(Server Push)教程

能看到这里的人肯定都是很有耐心的人,哈哈哈哈哈,那点个👍吧