写点什么

前端高频面试题(附答案)

  • 2022 年 8 月 29 日
    浙江
  • 本文字数:6218 字

    阅读完需:约 20 分钟

说一下 HTTP 3.0

HTTP/3 基于 UDP 协议实现了类似于 TCP 的多路复用数据流、传输可靠性等功能,这套功能被称为 QUIC 协议。


  1. 流量控制、传输可靠性功能:QUIC 在 UDP 的基础上增加了一层来保证数据传输可靠性,它提供了数据包重传、拥塞控制、以及其他一些 TCP 中的特性。

  2. 集成 TLS 加密功能:目前 QUIC 使用 TLS1.3,减少了握手所花费的 RTT 数。

  3. 多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了 TCP 的队头阻塞问题。

  4. 快速握手:由于基于 UDP,可以实现使用 0 ~ 1 个 RTT 来建立连接。

箭头函数与普通函数的区别

(1)箭头函数比普通函数更加简洁


  • 如果没有参数,就直接写一个空括号即可

  • 如果只有一个参数,可以省去参数的括号

  • 如果有多个参数,用逗号分割

  • 如果函数体的返回值只有一句,可以省略大括号

  • 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个 void 关键字。最常见的就是调用一个函数:


let fn = () => void doesNotReturn();复制代码
复制代码


(2)箭头函数没有自己的 this


箭头函数不会创建自己的 this, 所以它没有自己的 this,它只会在自己作用域的上一层继承 this。所以箭头函数中 this 的指向在它在定义时已经确定了,之后不会改变。


(3)箭头函数继承来的 this 指向永远不会改变


var id = 'GLOBAL';var obj = {  id: 'OBJ',  a: function(){    console.log(this.id);  },  b: () => {    console.log(this.id);  }};obj.a();    // 'OBJ'obj.b();    // 'GLOBAL'new obj.a()  // undefinednew obj.b()  // Uncaught TypeError: obj.b is not a constructor复制代码
复制代码


对象 obj 的方法 b 是使用箭头函数定义的,这个函数中的 this 就永远指向它定义时所处的全局执行环境中的 this,即便这个函数是作为对象 obj 的方法调用,this 依旧指向 Window 对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。


(4)call()、apply()、bind()等方法不能改变箭头函数中 this 的指向


var id = 'Global';let fun1 = () => {    console.log(this.id)};fun1();                     // 'Global'fun1.call({id: 'Obj'});     // 'Global'fun1.apply({id: 'Obj'});    // 'Global'fun1.bind({id: 'Obj'})();   // 'Global'复制代码
复制代码


(5)箭头函数不能作为构造函数使用


构造函数在 new 的步骤在上面已经说过了,实际上第二步就是将函数中的 this 指向该对象。 但是由于箭头函数时没有自己的 this 的,且 this 指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。


(6)箭头函数没有自己的 arguments


箭头函数没有自己的 arguments 对象。在箭头函数中访问 arguments 实际上获得的是它外层函数的 arguments 值。


(7)箭头函数没有 prototype


(8)箭头函数不能用作 Generator 函数,不能使用 yeild 关键字

Loader 和 Plugin 有什么区别

Loader:直译为"加载器"。Webpack 将一切文件视为模块,但是 webpack 原生是只能解析 js 文件,如果想将其他文件也打包的话,就会用到loader。 所以 Loader 的作用是让 webpack 拥有了加载和解析非 JavaScript 文件的能力。 Plugin:直译为"插件"。Plugin 可以扩展 webpack 的功能,让 webpack 具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

setTimeout、Promise、Async/Await 的区别

(1)setTimeout

console.log('script start')    //1. 打印 script startsetTimeout(function(){    console.log('settimeout')    // 4. 打印 settimeout})    // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数console.log('script end')    //3. 打印 script start// 输出顺序:script start->script end->settimeout复制代码
复制代码

(2)Promise

Promise 本身是同步的立即执行函数, 当在 executor 中执行 resolve 或者 reject 的时候, 此时是异步操作, 会先执行 then/catch 等,当主栈完成后,才会去调用 resolve/reject 中存放的方法执行,打印 p 的时候,是打印的返回结果,一个 Promise 实例。


console.log('script start')let promise1 = new Promise(function (resolve) {    console.log('promise1')    resolve()    console.log('promise1 end')}).then(function () {    console.log('promise2')})setTimeout(function(){    console.log('settimeout')})console.log('script end')// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout复制代码
复制代码


当 JS 主线程执行到 Promise 对象时:


  • promise1.then() 的回调就是一个 task

  • promise1 是 resolved 或 rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue

  • promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中

  • setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况

(3)async/await

async function async1(){   console.log('async1 start');    await async2();    console.log('async1 end')}async function async2(){    console.log('async2')}console.log('script start');async1();console.log('script end')// 输出顺序:script start->async1 start->async2->script end->async1 end复制代码
复制代码


async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。


例如:


async function func1() {    return 1}console.log(func1())复制代码
复制代码



func1 的运行结果其实就是一个 Promise 对象。因此也可以使用 then 来处理后续逻辑。


func1().then(res => {    console.log(res);  // 30})复制代码
复制代码


await 的含义为等待,也就是 async 函数需要等待 await 后的函数执行完成并且有了返回结果(Promise 对象)之后,才能继续执行下面的代码。await 通过返回一个 Promise 对象来实现同步的效果。

浏览器是如何对 HTML5 的离线储存资源进行管理和加载?

  • 在线的情况下,浏览器发现 html 头部有 manifest 属性,它会请求 manifest 文件,如果是第一次访问页面 ,那么浏览器就会根据 manifest 文件的内容下载相应的资源并且进行离线存储。如果已经访问过页面并且资源已经进行离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的 manifest 文件与旧的 manifest 文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,就会重新下载文件中的资源并进行离线存储。

  • 离线的情况下,浏览器会直接使用离线存储的资源。

vuex

vuex是一个专为vue.js应用程序开发的状态管理器,它采用集中式存储管理应用的所有组件的状态,并且以相应的规则保证状态以一种可以预测的方式发生变化。
state: vuex使用单一状态树,用一个对象就包含来全部的应用层级状态
mutation: 更改vuex中state的状态的唯一方法就是提交mutation
action: action提交的是mutation,而不是直接变更状态,action可以包含任意异步操作
getter: 相当于vue中的computed计算属性复制代码
复制代码

代码输出结果

const first = () => (new Promise((resolve, reject) => {    console.log(3);    let p = new Promise((resolve, reject) => {        console.log(7);        setTimeout(() => {            console.log(5);            resolve(6);            console.log(p)        }, 0)        resolve(1);    });    resolve(2);    p.then((arg) => {        console.log(arg);    });}));first().then((arg) => {    console.log(arg);});console.log(4);复制代码
复制代码


输出结果如下:


374125Promise{<resolved>: 1}复制代码
复制代码


代码的执行过程如下:


  1. 首先会进入 Promise,打印出 3,之后进入下面的 Promise,打印出 7;

  2. 遇到了定时器,将其加入宏任务队列;

  3. 执行 Promise  p 中的 resolve,状态变为 resolved,返回值为 1;

  4. 执行 Promise first 中的 resolve,状态变为 resolved,返回值为 2;

  5. 遇到 p.then,将其加入微任务队列,遇到 first().then,将其加入任务队列;

  6. 执行外面的代码,打印出 4;

  7. 这样第一轮宏任务就执行完了,开始执行微任务队列中的任务,先后打印出 1 和 2;

  8. 这样微任务就执行完了,开始执行下一轮宏任务,宏任务队列中有一个定时器,执行它,打印出 5,由于执行已经变为 resolved 状态,所以resolve(6)不会再执行;

  9. 最后console.log(p)打印出Promise{<resolved>: 1}

setTimeout 模拟 setInterval

描述:使用setTimeout模拟实现setInterval的功能。


实现


const mySetInterval(fn, time) {    let timer = null;    const interval = () => {        timer = setTimeout(() => {            fn();  // time 时间之后会执行真正的函数fn            interval();  // 同时再次调用interval本身        }, time)    }    interval();  // 开始执行    // 返回用于关闭定时器的函数    return () => clearTimeout(timer);}
// 测试const cancel = mySetInterval(() => console.log(1), 400);setTimeout(() => { cancel();}, 1000); // 打印两次1复制代码
复制代码

函数中的 arguments 是数组吗?类数组转数组的方法了解一下?

是类数组,是属于鸭子类型的范畴,长得像数组,


  • ... 运算符

  • Array.from

  • Array.prototype.slice.apply(arguments)

实现一个扇形

用 CSS 实现扇形的思路和三角形基本一致,就是多了一个圆角的样式,实现一个 90°的扇形:


div{    border: 100px solid transparent;    width: 0;    heigt: 0;    border-radius: 100px;    border-top-color: red;}复制代码
复制代码

如果一个构造函数,bind 了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?

不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象

什么是尾调用,使用尾调用有什么好处?

尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

CSS 预处理器/后处理器是什么?为什么要使用它们?

预处理器, 如:lesssassstylus,用来预编译sass或者less,增加了css代码的复用性。层级,mixin, 变量,循环, 函数等对编写以及开发 UI 组件都极为方便。


后处理器, 如: postCss,通常是在完成的样式表中根据css规范处理css,让其更加有效。目前最常做的是给css属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。


css预处理器为css增加一些编程特性,无需考虑浏览器的兼容问题,可以在CSS中使用变量,简单的逻辑程序,函数等在编程语言中的一些基本的性能,可以让css更加的简洁,增加适应性以及可读性,可维护性等。


其它css预处理器语言:Sass(Scss), Less, Stylus, Turbine, Swithch css, CSS Cacheer, DT Css


使用原因:


  • 结构清晰, 便于扩展

  • 可以很方便的屏蔽浏览器私有语法的差异

  • 可以轻松实现多重继承

  • 完美的兼容了CSS代码,可以应用到老项目中

new 一个构造函数,如果函数返回 return {}return nullreturn 1return true 会发生什么情况?

如果函数返回一个对象,那么 new 这个函数调用返回这个函数的返回对象,否则返回 new 创建的新对象

什么是 margin 重叠问题?如何解决?

问题描述: 两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种行为就是外边距折叠。需要注意的是,浮动的元素和绝对定位这种脱离文档流的元素的外边距不会折叠。重叠只会出现在垂直方向


计算原则: 折叠合并后外边距的计算原则如下:


  • 如果两者都是正数,那么就去最大者

  • 如果是一正一负,就会正值减去负值的绝对值

  • 两个都是负值时,用 0 减去两个中绝对值大的那个


解决办法: 对于折叠的情况,主要有两种:兄弟之间重叠父子之间重叠 (1)兄弟之间重叠


  • 底部元素变为行内盒子:display: inline-block

  • 底部元素设置浮动:float

  • 底部元素的 position 的值为absolute/fixed


(2)父子之间重叠


  • 父元素加入:overflow: hidden

  • 父元素添加透明边框:border:1px solid transparent

  • 子元素变为行内盒子:display: inline-block

  • 子元素加入浮动属性或定位

CSS 选择器及其优先级


对于选择器的优先级


  • 标签选择器、伪元素选择器:1

  • 类选择器、伪类选择器、属性选择器:10

  • id 选择器:100

  • 内联样式:1000


注意事项:


  • !important 声明的样式的优先级最高;

  • 如果优先级相同,则最后出现的样式生效;

  • 继承得到的样式的优先级最低;

  • 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;

  • 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。

Proxy 可以实现什么功能?

在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。


Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。


let p = new Proxy(target, handler)复制代码
复制代码


target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。


下面来通过 Proxy 来实现一个数据响应式:


let onWatch = (obj, setBind, getLogger) => {  let handler = {    get(target, property, receiver) {      getLogger(target, property)      return Reflect.get(target, property, receiver)    },    set(target, property, value, receiver) {      setBind(value, property)      return Reflect.set(target, property, value)    }  }  return new Proxy(obj, handler)}let obj = { a: 1 }let p = onWatch(  obj,  (v, property) => {    console.log(`监听到属性${property}改变为${v}`)  },  (target, property) => {    console.log(`'${property}' = ${target[property]}`)  })p.a = 2 // 监听到属性a改变p.a // 'a' = 2复制代码
复制代码


在上述代码中,通过自定义 setget 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。


当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。

说下对 JS 的了解吧

是基于原型的动态语言,主要独特特性有 this、原型和原型链。


JS 严格意义上来说分为:语言标准部分(ECMAScript)+ 宿主环境部分


语言标准部分


2015 年发布 ES6,引入诸多新特性使得能够编写大型项目变成可能,标准自 2015 之后以年号代号,每年一更


宿主环境部分


  • 在浏览器宿主环境包括 DOM + BOM 等

  • 在 Node,宿主环境包括一些文件、数据库、网络、与操作系统的交互等

用户头像

还未添加个人签名 2022.07.31 加入

还未添加个人简介

评论

发布
暂无评论
前端高频面试题(附答案)_JavaScript_helloworld1024fd_InfoQ写作社区