与缓存相关的HTTP请求头有哪些

强缓存:

  • Expires
  • Cache-Control

协商缓存:

  • Etag、If-None-Match
  • Last-Modified、If-Modified-Since

----问题知识点分割线----

display的属性值及其作用

属性值 作用
none 元素不显示,并且会从文档流中移除。
block 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
inline 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
inline-block 默认宽度为内容宽度,可以设置宽高,同行显示。
list-item 像块类型元素一样显示,并添加样式列表标记。
table 此元素会作为块级表格来显示。
inherit 规定应该从父元素继承display属性的值。

前端进阶面试题详细解答
----问题知识点分割线----

如何提取高度嵌套的对象里的指定属性?

有时会遇到一些嵌套程度非常深的对象:

const school = {classes: {stu: {name: 'Bob',age: 24,}}
}

像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:

const { name } = school

显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:

const { classes } = school
const { stu } = classes
const { name } = stu
name // 'Bob'

但是还有一种更标准的做法,可以用一行代码来解决这个问题:

const { classes: { stu: { name } }} = schoolconsole.log(name)  // 'Bob'

可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。

----问题知识点分割线----

为什么0.1+0.2 ! == 0.3,如何让其相等

在开发过程中遇到类似这样的问题:

let n1 = 0.1, n2 = 0.2
console.log(n1 + n2)  // 0.30000000000000004

这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:

(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入

toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?

计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100...(1100循环),0.2的二进制是:0.00110011001100...(1100循环),这两个数的二进制都是无限循环的数。那JavaScript是如何处理无限循环的二进制小数呢?

一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。

根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004

下面看一下双精度数是如何保存的:

  • 第一部分(蓝色):用来存储符号位(sign),用来区分正负数,0表示正数,占用1位
  • 第二部分(绿色):用来存储指数(exponent),占用11位
  • 第三部分(红色):用来存储小数(fraction),占用52位

对于0.1,它的二进制为:

0.00011001100110011001100110011001100110011001100110011001 10011...

转为科学计数法(科学计数法的结果就是浮点数):

1.1001100110011001100110011001100110011001100110011001*2^-4

可以看出0.1的符号位为0,指数位为-4,小数位为:

1001100110011001100110011001100110011001100110011001

那么问题又来了,指数位是负数,该如何保存呢?

IEEE标准规定了一个偏移量,对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也就是正数了。由于JavaScript的数字是双精度数,这里就以双精度数为例,它的指数部分为11位,能表示的范围就是0~2047,IEEE固定双精度数的偏移量为1023

  • 当指数位不全是0也不全是1时(规格化的数值),IEEE规定,阶码计算公式为 e-Bias。 此时e最小值是1,则1-1023= -1022,e最大值是2046,则2046-1023=1023,可以看到,这种情况下取值范围是-1022~1013
  • 当指数位全部是0的时候(非规格化的数值),IEEE规定,阶码的计算公式为1-Bias,即1-1023= -1022。
  • 当指数位全部是1的时候(特殊值),IEEE规定这个浮点数可用来表示3个特殊值,分别是正无穷,负无穷,NaN。 具体的,小数位不为0的时候表示NaN;小数位为0时,当符号位s=0时表示正无穷,s=1时候表示负无穷。

对于上面的0.1的指数位为-4,-4+1023 = 1019 转化为二进制就是:1111111011.

所以,0.1表示为:

0 1111111011 1001100110011001100110011001100110011001100110011001

说了这么多,是时候该最开始的问题了,如何实现0.1+0.2=0.3呢?

对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3

function numberepsilon(arg1,arg2){                   return Math.abs(arg1 - arg2) < Number.EPSILON;
}        console.log(numberepsilon(0.1 + 0.2, 0.3)); // true

----问题知识点分割线----

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"));});
}

----问题知识点分割线----

对类数组对象的理解,如何转化为数组

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,函数参数也可以被看作是类数组对象,因为它含有 length属性值,代表可接收的参数个数。

常见的类数组转换为数组的方法有这样几种:

  • 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 方法来实现转换
Array.from(arrayLike);

----问题知识点分割线----

let、const、var的区别

(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。

(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

区别 var let const
是否有块级作用域 × ✔️ ✔️
是否存在变量提升 ✔️ × ×
是否添加全局属性 ✔️ × ×
能否重复声明变量 ✔️ × ×
是否存在暂时性死区 × ✔️ ✔️
是否必须设置初始值 × × ✔️
能否改变指针指向 ✔️ ✔️ ×

----问题知识点分割线----

new 操作符

题目描述:手写 new 操作符实现

实现代码如下:

function myNew(fn, ...args) {let obj = Object.create(fn.prototype);let res = fn.call(obj, ...args);if (res && (typeof res === "object" || typeof res === "function")) {return res;}return obj;
}
用法如下:
// // function Person(name, age) {// //   this.name = name;
// //   this.age = age;
// // }
// // Person.prototype.say = function() {// //   console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();

----问题知识点分割线----

await 到底在等啥?

await 在等待什么呢? 一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行:

function getSomething() {return "something";
}
async function testAsync() {return Promise.resolve("hello async");
}
async function test() {const v1 = await getSomething();const v2 = await testAsync();console.log(v1, v2);
}
test();

await 表达式的运算结果取决于它等的是什么。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

来看一个例子:

function testAsy(x){return new Promise(resolve=>{setTimeout(() => {resolve(x);}, 3000)})
}
async function testAwt(){    let result =  await testAsy('hello world');console.log(result);    // 3秒钟之后出现hello worldconsole.log('cuger')   // 3秒钟之后出现cug
}
testAwt();
console.log('cug')  //立即输出cug

这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。await暂停当前async的执行,所以’cug’'最先输出,hello world’和‘cuger’是3秒钟后同时出现的。

----问题知识点分割线----

函数节流

触发高频事件,且 N 秒内只执行一次。

简单版:使用时间戳来实现,立即执行一次,然后每 N 秒执行一次。

function throttle(func, wait) {var context, args;var previous = 0;return function() {var now = +new Date();context = this;args = arguments;if (now - previous > wait) {func.apply(context, args);previous = now;}}
}

最终版:支持取消节流;另外通过传入第三个参数,options.leading 来表示是否可以立即执行一次,opitons.trailing 表示结束调用的时候是否还要执行一次,默认都是 true。
注意设置的时候不能同时将 leading 或 trailing 设置为 false。

function throttle(func, wait, options) {var timeout, context, args, result;var previous = 0;if (!options) options = {};var later = function() {previous = options.leading === false ? 0 : new Date().getTime();timeout = null;func.apply(context, args);if (!timeout) context = args = null;};var throttled = function() {var now = new Date().getTime();if (!previous && options.leading === false) previous = now;var remaining = wait - (now - previous);context = this;args = arguments;if (remaining <= 0 || remaining > wait) {if (timeout) {clearTimeout(timeout);timeout = null;}previous = now;func.apply(context, args);if (!timeout) context = args = null;} else if (!timeout && options.trailing !== false) {timeout = setTimeout(later, remaining);}};throttled.cancel = function() {clearTimeout(timeout);previous = 0;timeout = null;}return throttled;
}

节流的使用就不拿代码举例了,参考防抖的写就行。

----问题知识点分割线----

为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?

arguments是一个对象,它的属性是从 0 开始依次递增的数字,还有calleelength等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach, reduce等,所以叫它们类数组。

要遍历类数组,有三个方法:

(1)将数组的方法应用到类数组上,这时候就可以使用callapply方法,如:

function foo(){ Array.prototype.forEach.call(arguments, a => console.log(a))
}

(2)使用Array.from方法将类数组转化成数组:‌

function foo(){ const arrArgs = Array.from(arguments) arrArgs.forEach(a => console.log(a))
}

(3)使用展开运算符将类数组转化成数组

function foo(){ const arrArgs = [...arguments] arrArgs.forEach(a => console.log(a))
}

----问题知识点分割线----

AJAX

题目描述:利用 XMLHttpRequest 手写 AJAX 实现

实现代码如下:

const getJSON = function (url) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.open("GET", url, false);xhr.setRequestHeader("Content-Type", "application/json");xhr.onreadystatechange = function () {if (xhr.readyState !== 4) return;if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);} else {reject(new Error(xhr.responseText));}};xhr.send();});
};

----问题知识点分割线----

如何解决 1px 问题?

1px 问题指的是:在一些 Retina屏幕 的机型上,移动端页面的 1px 会变得很粗,呈现出不止 1px 的效果。原因很简单——CSS 中的 1px 并不能和移动设备上的 1px 划等号。它们之间的比例关系有一个专门的属性来描述:

window.devicePixelRatio = 设备的物理像素 / CSS像素。

打开 Chrome 浏览器,启动移动端调试模式,在控制台去输出这个 devicePixelRatio 的值。这里选中 iPhone6/7/8 这系列的机型,输出的结果就是2: 这就意味着设置的 1px CSS 像素,在这个设备上实际会用 2 个物理像素单元来进行渲染,所以实际看到的一定会比 1px 粗一些。 解决1px 问题的三种思路:

思路一:直接写 0.5px

如果之前 1px 的样式这样写:

border:1px solid #333

可以先在 JS 中拿到 window.devicePixelRatio 的值,然后把这个值通过 JSX 或者模板语法给到 CSS 的 data 里,达到这样的效果(这里用 JSX 语法做示范):

<div id="container" data-device={{window.devicePixelRatio}}></div>

然后就可以在 CSS 中用属性选择器来命中 devicePixelRatio 为某一值的情况,比如说这里尝试命中 devicePixelRatio 为2的情况:

#container[data-device="2"] {border:0.5px solid #333
}

直接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简单的一种方法。这种方法的缺陷在于兼容性不行,IOS 系统需要8及以上的版本,安卓系统则直接不兼容。

思路二:伪元素先放大后缩小

这个方法的可行性会更高,兼容性也更好。唯一的缺点是代码会变多。

思路是先放大、后缩小:在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的宽和高都设置为目标元素的两倍,border值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一,间接地实现了 0.5px 的效果。

代码如下:

#container[data-device="2"] {position: relative;
}
#container[data-device="2"]::after{position:absolute;top: 0;left: 0;width: 200%;height: 200%;content:"";transform: scale(0.5);transform-origin: left top;box-sizing: border-box;border: 1px solid #333;}
}

思路三:viewport 缩放来解决

这个思路就是对 meta 标签里几个关键属性下手:

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

这里针对像素比为2的页面,把整个页面缩放为了原来的1/2大小。这样,本来占用2个物理像素的 1px 样式,现在占用的就是标准的一个物理像素。根据像素比的不同,这个缩放比例可以被计算为不同的值,用 js 代码实现如下:

const scale = 1 / window.devicePixelRatio;
// 这里 metaEl 指的是 meta 标签对应的 Dom
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);

这样解决了,但这样做的副作用也很大,整个页面被缩放了。这时 1px 已经被处理成物理像素大小,这样的大小在手机上显示边框很合适。但是,一些原本不需要被缩小的内容,比如文字、图片等,也被无差别缩小掉了。

----问题知识点分割线----

为什么需要清除浮动?清除浮动的方式

浮动的定义: 非IE浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。 此时,内容会溢出到容器外面而影响布局。这种现象被称为浮动(溢出)。

浮动的工作原理:

  • 浮动元素脱离文档流,不占据空间(引起“高度塌陷”现象)
  • 浮动元素碰到包含它的边框或者其他浮动元素的边框停留

浮动元素可以左右移动,直到遇到另一个浮动元素或者遇到它外边缘的包含框。浮动框不属于文档流中的普通流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的普通流就会表现得该浮动框不存在一样的布局模式。当包含框的高度小于浮动框的时候,此时就会出现“高度塌陷”。

浮动元素引起的问题?

  • 父元素的高度无法被撑开,影响与父元素同级的元素
  • 与浮动元素同级的非浮动元素会跟随其后
  • 若浮动的元素不是第一个元素,则该元素之前的元素也要浮动,否则会影响页面的显示结构

清除浮动的方式如下:

  • 给父级div定义height属性
  • 最后一个浮动元素之后添加一个空的div标签,并添加clear:both样式
  • 包含浮动元素的父级标签添加overflow:hidden或者overflow:auto
  • 使用 :after 伪元素。由于IE6-7不支持 :after,使用 zoom:1 触发 hasLayout**
.clearfix:after{content: "\200B";display: table; height: 0;clear: both;}.clearfix{*zoom: 1;}

----问题知识点分割线----

display:inline-block 什么时候会显示间隙?

  • 有空格时会有间隙,可以删除空格解决;
  • margin正值时,可以让margin使用负值解决;
  • 使用font-size时,可通过设置font-size:0letter-spacingword-spacing解决;

----问题知识点分割线----

如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

----问题知识点分割线----

如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。

----问题知识点分割线----

JavaScript 类数组对象的定义?

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。

常见的类数组转换为数组的方法有这样几种:

(1)通过 call 调用数组的 slice 方法来实现转换

Array.prototype.slice.call(arrayLike);

(2)通过 call 调用数组的 splice 方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

(3)通过 apply 调用数组的 concat 方法来实现转换

Array.prototype.concat.apply([], arrayLike);

(4)通过 Array.from 方法来实现转换

Array.from(arrayLike);

字节前端二面高频面试题相关推荐

  1. 京东前端二面高频面试题

    寄生组合继承 题目描述:实现一个你认为不错的 js 继承方式 实现代码如下: function Parent(name) {this.name = name;this.say = () => { ...

  2. 校招前端二面高频面试题合集

    DOCTYPE(⽂档类型) 的作⽤ DOCTYPE是HTML5中一种标准通用标记语言的文档类型声明,它的目的是告诉浏览器(解析器)应该以什么样(html或xhtml)的文档类型定义来解析文档,不同的渲 ...

  3. 2023前端二面高频vue面试题集锦

    vuex是什么?怎么使用?哪种功能场景使用它? Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.vuex 就是一个仓库,仓库里放了很多对象.其中 state 就是数据源存放地,对应于一 ...

  4. 字节跳动《Python高频面试题》火了,完整版 PDF 开放下载!

    最近很多小伙伴找我要一些Python基础/框架资料及面试题,于是我翻箱倒柜,把这份字节跳动大牛总结的Python面经总结找出来,免费共享给大家! 据说有小伙伴靠这份笔记顺利进入TMD华为等大厂,所以一 ...

  5. 社招前端二面react面试题整理

    解释 React 中 render() 的目的. 每个React组件强制要求必须有一个 render().它返回一个 React 元素,是原生 DOM 组件的表示.如果需要渲染多个 HTML 元素,则 ...

  6. 前端二面必会面试题(附答案)

    typeof NaN 的结果是什么? NaN 指"不是一个数字"(not a number),NaN 是一个"警戒值"(sentinel value,有特殊用途 ...

  7. 2023前端二面react面试题(边面边更)

    何为 JSX JSX 是 JavaScript 语法的一种语法扩展,并拥有 JavaScript 的全部功能.JSX 生产 React "元素",你可以将任何的 JavaScrip ...

  8. 美团前端二面必会面试题(附答案)

    判断数组的方式有哪些 通过Object.prototype.toString.call()做判断 Object.prototype.toString.call(obj).slice(8,-1) === ...

  9. 前端社招高频面试题(一)(附答案)

    手写发布订阅 class EventListener {listeners = {};on(name, fn) {(this.listeners[name] || (this.listeners[na ...

最新文章

  1. Ubuntu下安装mysql与mysql workbench
  2. openstack swift middleware开发
  3. 自己帮别人写的网站可以公开源码吗_雷军靠写代码赚第一个 100 万,马化腾亲自写腾讯网,码农出身的大佬...
  4. 前端进阶之路: 前端架构设计(2)-流程核心
  5. 前瞻科技,引领未来!Microsoft Connect(); 2018即将重磅来袭!
  6. 【英语学习】【WOTD】hoopla 释义/词源/示例
  7. 怎么让背景铺满整个页面_必能用到,一页PPT中想放超多图片怎么办?
  8. 离线版Google Chrome Frame下载
  9. java发送http请求 utf8_Java 发送http请求(get、post)的示例
  10. ESP32-WiFi功能的理解
  11. 计算机组装要哪些硬件,DIY组装电脑要买哪些硬件
  12. MySQL ERROR 1698 (28000) 错误
  13. Linux meld安装
  14. 不得不看的Python入门知识
  15. 常见数学中的特殊符号
  16. java程序设计案例教程答案,2年以上经验必看
  17. 软件项目方案模板~!
  18. MySQL insert 插入优化技巧,MySQL 优化学习第8天
  19. sql 高级查询语句总结
  20. java事务是基于数据库的么_详解在Spring Boot中使用数据库事务

热门文章

  1. Python 定义一个完整的 vector 类
  2. 互联网产品分析的3大基础模型
  3. 计算机里的游戏怎么输入,我的世界电脑版怎么输入指令
  4. 如何从零开始自学前端
  5. 【哲学】康德的空间和时间理论—罗素 读书笔记
  6. 微课堂V2v4.6.6 知识付费
  7. 从离不开手机到开始维护自己的笔记
  8. fastmock模拟post、get请求
  9. 毕设 - 网络商城之环境搭建(SSM)
  10. nvm切换node失败,原因:网速慢文件下载不全,使用淘宝镜像加速下载