字节前端必会面试题

2022-09-07 12:56:52 浏览数 (2)

常见的水平垂直方式有几种?

代码语言:html复制
//利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 translate 来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。
.parent {
    position: relative;
}

.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
//利用绝对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
//利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 margin 负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 自身 height 的一半 */
    margin-left: -50px;    /* 自身 width 的一半 */
}
//使用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要**考虑兼容的问题**,该方法在移动端用的较多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
//另外,如果父元素设置了flex布局,只需要给子元素加上`margin:auto;`就可以实现垂直居中布局
.parent{
    display:flex;
}
.child{
    margin: auto;
}

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

代码输出结果

代码语言:javascript复制
const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

输出结果如下:

代码语言:javascript复制
1
2
4
timerStart
timerEnd
success

代码执行过程如下:

  • 首先遇到Promise构造函数,会先执行里面的内容,打印1
  • 遇到定时器steTimeout,它是一个宏任务,放入宏任务队列;
  • 继续向下执行,打印出2;
  • 由于Promise的状态此时还是pending,所以promise.then先不执行;
  • 继续执行下面的同步任务,打印出4;
  • 此时微任务队列没有任务,继续执行下一轮宏任务,执行steTimeout
  • 首先执行timerStart,然后遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列,再执行timerEnd
  • 执行完这个宏任务,就去执行微任务promise.then,打印出resolve的结果。

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

函数柯里化

柯里化(currying) 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数。

对于已经柯里化后的函数来说,当接收的参数数量与原函数的形参数量相同时,执行原函数; 当接收的参数数量小于原函数的形参数量时,返回一个函数用于接收剩余的参数,直至接收的参数数量与形参数量一致,执行原函数。

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

对浏览器的缓存机制的理解

浏览器缓存的全过程:

  • 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用;
  • 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;
  • 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;
  • 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
  • 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200;

很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时获得网站的最新更新。

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

如何避免ajax数据请求重新获取

一般而言,ajax请求的数据都放在redux中存取。

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

Promise.resolve

代码语言:javascript复制
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));
}

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

数组去重

使用 indexOf/includes 实现
代码语言:javascript复制
function unique(arr) {
    var res = [];
    for(var i = 0; i < arr.length; i  ) {
        if(res.indexOf(arr[i]) === -1) res.push(arr[i]);
        // if(!res.includes(arr[i])) res.push(arr[i]);
    }
    return res;
}
使用 filter(forEach) indexOf/includes 实现
代码语言:javascript复制
// filter
function unique(arr) {
    var res = arr.filter((value, index) => {
        // 只存第一个出现的元素
        return arr.indexOf(value) === index;
    });
    return res;
}
// forEach
function unique(arr) {
    var res = [];
    arr.forEach((value) => {
        if(!res.includes(value)) res.push(value);
    });
    return res;
}
非 API 版本(原生)实现
代码语言:javascript复制
function unique(arr) {
    var res = [];
    for(var i = 0; i < arr.length; i  ) {
        var flag = false;
        for(var j = 0; j < res.length; j  ) {
            if(arr[i] === res[j]) {
                flag = true;
                break;
            }
        }
        if(flag === false) res.push(arr[i]);
    }
    return res;
}
ES6 使用 Set 扩展运算符(...)/Array.from() 实现
代码语言:javascript复制
function unique(arr) {
    // return [...new Set(arr)];
    return Array.from(new Set(arr));
}

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

documentFragment 是什么?用它跟直接操作 DOM 的区别是什么?

MDN中对documentFragment的解释:

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的DOM操作时,我们就可以将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作DOM相比,将DocumentFragment 节点插入DOM树时,不会触发页面的重绘,这样就大大提高了页面的性能。

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

写版本号排序的方法

题目描述:有一组版本号如下'0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'。现在需要对其进行排序,排序的结果为 '4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1'

实现代码如下:

代码语言:javascript复制
arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {
    const s1 = arr1[i];
    const s2 = arr2[i];
    i  ;
    if (s1 === undefined || s2 === undefined) {
      return arr2.length - arr1.length;
    }

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);

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

谈一谈队头阻塞问题

什么是队头阻塞?

对于每一个HTTP请求而言,这些任务是会被放入一个任务队列中串行执行的,一旦队首任务请求太慢时,就会阻塞后面的请求处理,这就是HTTP队头阻塞问题。

有什么解决办法吗

0 人点赞