写点什么

前端面试比较好的回答

作者:loveX001
  • 2022-12-19
    浙江
  • 本文字数:6717 字

    阅读完需:约 22 分钟

介绍一下 Connection:keep-alive

什么是 keep-alive


我们知道 HTTP 协议采用“请求-应答”模式,当使用普通模式,即非 KeepAlive 模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP 协议为无连接的协议);


当使用 Keep-Alive 模式(又称持久连接、连接重用)时,Keep-Alive 功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。


为什么要使用 keep-alive


keep-alive 技术的创建目的,能在多次 HTTP 之前重用同一个 TCP 连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等),参考如下示意图



客户端如何开启


在 HTTP/1.0 协议中,默认是关闭的,需要在 http 头加入"Connection: Keep-Alive”,才能启用 Keep-Alive;


Connection: keep-alive
复制代码


http 1.1 中默认启用 Keep-Alive,如果加入"Connection: close “,才关闭。


Connection: close
复制代码


目前大部分浏览器都是用 http1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep- Alive 连接就看服务器设置情况。

Promise.any

描述:只要 promises 中有一个fulfilled,就返回第一个fulfilledPromise实例的返回值。


实现


Promise.any = function(promises) {    return new Promise((resolve, reject) => {        if(Array.isArray(promises)) {            if(promises.length === 0) return reject(new AggregateError("All promises were rejected"));            let count = 0;            promises.forEach((item, index) => {                Promise.resolve(item).then(                    value => resolve(value),                    reason => {                        count++;                        if(count === promises.length) {                            reject(new AggregateError("All promises were rejected"));                        };                    }                );            })        }        else return reject(new TypeError("Argument is not iterable"));    });}
复制代码

代码输出结果

async function async1() {  console.log("async1 start");  await async2();  console.log("async1 end");}async function async2() {  console.log("async2");}async1();console.log('start')
复制代码


输出结果如下:


async1 startasync2startasync1 end
复制代码


代码的执行过程如下:


  1. 首先执行函数中的同步代码async1 start,之后遇到了await,它会阻塞async1后面代码的执行,因此会先去执行async2中的同步代码async2,然后跳出async1

  2. 跳出async1函数后,执行同步代码start

  3. 在一轮宏任务全部执行完之后,再来执行await后面的内容async1 end


这里可以理解为 await 后面的语句相当于放到了 new Promise 中,下一行及之后的语句相当于放在 Promise.then 中。

闭包产生的本质

当前环境中存在指向父级作用域的引用

代码输出结果

 var a=3; function c(){    alert(a); } (function(){  var a=4;  c(); })();
复制代码


js 中变量的作用域链与定义时的环境有关,与执行时无关。执行环境只会改变 this、传递的参数、全局变量等

代码输出结果

Promise.resolve('1')  .then(res => {    console.log(res)  })  .finally(() => {    console.log('finally')  })Promise.resolve('2')  .finally(() => {    console.log('finally2')      return '我是finally2返回的值'  })  .then(res => {    console.log('finally2后面的then函数', res)  })
复制代码


输出结果如下:


1finally2finallyfinally2后面的then函数 2
复制代码


.finally()一般用的很少,只要记住以下几点就可以了:


  • .finally()方法不管 Promise 对象最后的状态如何都会执行

  • .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是无法知道 Promise 最终的状态是resolved还是rejected

  • 它最终返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异常则返回异常的 Promise 对象。

  • finally 本质上是 then 方法的特例


.finally()的错误捕获:


Promise.resolve('1')  .finally(() => {    console.log('finally1')    throw new Error('我是finally中抛出的异常')  })  .then(res => {    console.log('finally后面的then函数', res)  })  .catch(err => {    console.log('捕获错误', err)  })
复制代码


输出结果为:


'finally1''捕获错误' Error: 我是finally中抛出的异常
复制代码


参考 前端进阶面试题详细解答

Promise.resolve

Promise.resolve = function(value) {    // 1.如果 value 参数是一个 Promise 对象,则原封不动返回该对象    if(value instanceof Promise) return value;    // 2.如果 value 参数是一个具有 then 方法的对象,则将这个对象转为 Promise 对象,并立即执行它的then方法    if(typeof value === "object" && 'then' in value) {        return new Promise((resolve, reject) => {           value.then(resolve, reject);        });    }    // 3.否则返回一个新的 Promise 对象,状态为 fulfilled    return new Promise(resolve => resolve(value));}
复制代码

JS 整数是怎么表示的?

  • 通过 Number 类型来表示,遵循 IEEE754 标准,通过 64 位来表示一个数字,(1 + 11 + 52),最大安全数字是 Math.pow(2, 53) - 1,对于 16 位十进制。(符号位 + 指数位 + 小数部分有效位)

vuex

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

代码输出结果

var a, b(function () {   console.log(a);   console.log(b);   var a = (b = 3);   console.log(a);   console.log(b);   })()console.log(a);console.log(b);
复制代码


输出结果:


undefined undefined 3 3 undefined 3
复制代码


这个题目和上面题目考察的知识点类似,b 赋值为 3,b 此时是一个全局变量,而将 3 赋值给 a,a 是一个局部变量,所以最后打印的时候,a 仍旧是 undefined。

JS 隐式转换,显示转换

一般非基础类型进行转换时会先调用 valueOf,如果 valueOf 无法返回基本类型值,就会调用 toString


字符串和数字


  • "+" 操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接

  • "-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换


[] + {} 和 {} + []
复制代码


布尔值到数字


  • 1 + true = 2

  • 1 + false = 1


转换为布尔值


  • for 中第二个

  • while

  • if

  • 三元表达式

  • || (逻辑或) && (逻辑与)左边的操作数


符号


  • 不能被转换为数字

  • 能被转换为布尔值(都是 true)

  • 可以被转换成字符串 "Symbol(cool)"


宽松相等和严格相等


宽松相等允许进行强制类型转换,而严格相等不允许


字符串与数字


转换为数字然后比较


其他类型与布尔类型


  • 先把布尔类型转换为数字,然后继续进行比较


对象与非对象


  • 执行对象的 ToPrimitive(对象)然后继续进行比较


假值列表


  • undefined

  • null

  • false

  • +0, -0, NaN

  • ""

setInterval 模拟 setTimeout

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


思路setTimeout的特性是在指定的时间内只执行一次,我们只要在setInterval内部执行 callback 之后,把定时器关掉即可。


实现


const mySetTimeout = (fn, time) => {    let timer = null;    timer = setInterval(() => {        // 关闭定时器,保证只执行一次fn,也就达到了setTimeout的效果了        clearInterval(timer);        fn();    }, time);    // 返回用于关闭定时器的方法    return () => clearInterval(timer);}
// 测试const cancel = mySetTimeout(() => { console.log(1);}, 1000); // 一秒后打印 1
复制代码

js 脚本加载问题,async、defer 问题

  • 如果依赖其他脚本和 DOM 结果,使用 defer

  • 如果与 DOM 和其他脚本依赖不强时,使用 async

什么是作用域?

ES5 中只存在两种作用域:全局作用域和函数作用域。在 JavaScript 中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量(变量名或者函数名)查找

原型

JavaScript中的对象都有一个特殊的 prototype 内置属性,其实就是对其他对象的引用几乎所有的对象在创建时 prototype 属性都会被赋予一个非空的值,我们可以把这个属性当作一个备用的仓库当试图引用对象的属性时会出发get操作,第一步时检查对象本身是否有这个属性,如果有就使用它,没有就去原型中查找。一层层向上直到Object.prototype顶层
基于原型扩展描述一下原型链,什么是原型链,原型的继承,ES5和ES6继承与不同点。
复制代码

说一下 slice splice split 的区别?

// slice(start,[end])// slice(start,[end])方法:该方法是对数组进行部分截取,该方法返回一个新数组// 参数start是截取的开始数组索引,end参数等于你要取的最后一个字符的位置值加上1(可选)。// 包含了源函数从start到 end 所指定的元素,但是不包括end元素,比如a.slice(0,3);// 如果出现负数就把负数与长度相加后再划分。// slice中的负数的绝对值若大于数组长度就会显示所有数组// 若参数只有一个,并且参数大于length,则为空。// 如果结束位置小于起始位置,则返回空数组// 返回的个数是end-start的个数// 不会改变原数组var arr = [1,2,3,4,5,6]/*console.log(arr.slice(3))//[4,5,6] 从下标为0的到3,截取3之后的数console.log(arr.slice(0,3))//[1,2,3] 从下标为0的地方截取到下标为3之前的数console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/// 个人总结:slice的参数如果是正数就从左往右数,如果是负数的话就从右往左边数,// 截取的数组与数的方向一致,如果是2个参数则截取的是数的交集,没有交集则返回空数组 // ps:slice也可以切割字符串,用法和数组一样,但要注意空格也算字符
// splice(start,deletecount,item)// start:起始位置// deletecount:删除位数// item:替换的item// 返回值为被删除的字符串// 如果有额外的参数,那么item会插入到被移除元素的位置上。// splice:移除,splice方法从array中移除一个或多个数组,并用新的item替换它们。//举一个简单的例子 var a=['a','b','c']; var b=a.splice(1,1,'e','f'); console.log(a) //['a', 'e', 'f', 'c'] console.log(b) //['b']
var a = [1, 2, 3, 4, 5, 6];//console.log("被删除的为:",a.splice(1, 1, 8, 9)); //被删除的为:2// console.log("a数组元素:",a); //1,8,9,3,4,5,6
// console.log("被删除的为:", a.splice(0, 2)); //被删除的为:1,2// console.log("a数组元素:", a) //3,4,5,6console.log("被删除的为:", a.splice(1, 0, 2, 2)) //插入 第二个数为0,表示删除0个 console.log("a数组元素:", a) //1,2,2,2,3,4,5,6
// split(字符串)// string.split(separator,limit):split方法把这个string分割成片段来创建一个字符串数组。// 可选参数limit可以限制被分割的片段数量。// separator参数可以是一个字符串或一个正则表达式。// 如果separator是一个空字符,会返回一个单字符的数组,不会改变原数组。var a="0123456"; var b=a.split("",3); console.log(b);//b=["0","1","2"]// 注意:String.split() 执行的操作与 Array.join 执行的操作是相反的。
复制代码

代码输出结果

console.log('1');
setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') })})process.nextTick(function() { console.log('6');})new Promise(function(resolve) { console.log('7'); resolve();}).then(function() { console.log('8')})
setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') })})
复制代码


输出结果如下:


176824359111012
复制代码


(1)第一轮事件循环流程分析如下:


  • 整体 script 作为第一个宏任务进入主线程,遇到console.log,输出 1。

  • 遇到setTimeout,其回调函数被分发到宏任务 Event Queue 中。暂且记为setTimeout1

  • 遇到process.nextTick(),其回调函数被分发到微任务 Event Queue 中。记为process1

  • 遇到Promisenew Promise直接执行,输出 7。then被分发到微任务 Event Queue 中。记为then1

  • 又遇到了setTimeout,其回调函数被分发到宏任务 Event Queue 中,记为setTimeout2



上表是第一轮事件循环宏任务结束时各 Event Queue 的情况,此时已经输出了 1 和 7。发现了process1then1两个微任务:


  • 执行process1,输出 6。

  • 执行then1,输出 8。


第一轮事件循环正式结束,这一轮的结果是输出 1,7,6,8。


(2)第二轮时间循环从**setTimeout1**宏任务开始:


  • 首先输出 2。接下来遇到了process.nextTick(),同样将其分发到微任务 Event Queue 中,记为process2

  • new Promise立即执行输出 4,then也分发到微任务 Event Queue 中,记为then2



第二轮事件循环宏任务结束,发现有process2then2两个微任务可以执行:


  • 输出 3。

  • 输出 5。


第二轮事件循环结束,第二轮输出 2,4,3,5。


(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。


  • 直接输出 9。

  • process.nextTick()分发到微任务 Event Queue 中。记为process3

  • 直接执行new Promise,输出 11。

  • then分发到微任务 Event Queue 中,记为then3



第三轮事件循环宏任务执行结束,执行两个微任务process3then3


  • 输出 10。

  • 输出 12。


第三轮事件循环结束,第三轮输出 9,11,10,12。


整段代码,共进行了三次事件循环,完整的输出为 1,7,6,8,2,4,3,5,9,11,10,12。

代码输出结果

function Foo(){    Foo.a = function(){        console.log(1);    }    this.a = function(){        console.log(2)    }}
Foo.prototype.a = function(){ console.log(3);}
Foo.a = function(){ console.log(4);}
Foo.a();let obj = new Foo();obj.a();Foo.a();
复制代码


输出结果:4 2 1


解析:


  1. Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo 此时没有被调用,所以此时输出 Foo 的静态方法 a 的结果:4

  2. let obj = new Foo(); 使用了 new 方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。

  3. obj.a() ; 调用 obj 实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2

  4. Foo.a() ; 根据第 2 步可知 Foo 函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1

说一下 HTTP 和 HTTPS 协议的区别?

1、HTTPS协议需要CA证书,费用较高;而HTTP协议不需要2、HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议;3、使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443;4、HTTP协议连接很简单,是无状态的;HTTPS协议是具有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全
复制代码

script 标签中 defer 和 async 的区别

如果没有 defer 或 async 属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。


defer 和 async 属性都是去异步加载外部的 JS 脚本文件,它们都不会阻塞页面的解析,其区别如下:


  • 执行顺序: 多个带 async 属性的标签,不能保证加载的顺序;多个带 defer 属性的标签,按照加载顺序执行;

  • 脚本是否并行执行:async 属性,表示后续文档的加载和执行与 js 脚本的加载和执行是并行进行的,即异步执行;defer 属性,加载后续文档的过程和 js 脚本的加载(此时仅加载不执行)是并行进行的(异步),js 脚本需要等到文档所有元素解析完成之后才执行,DOMContentLoaded 事件触发执行之前。


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
前端面试比较好的回答_JavaScript_loveX001_InfoQ写作社区