关于前端:字节前端二面高频面试题

6次阅读

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

与缓存相干的 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} }} = school

console.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 world
  console.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);
正文完
 0