关于前端:高级前端二面面试题

8次阅读

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

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

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

TCP 和 UDP 的应用场景

  • TCP 利用场景: 效率要求绝对低,但对准确性要求绝对高的场景。因为传输中须要对数据确认、重发、排序等操作,相比之下效率没有 UDP 高。例如:文件传输(精确高要求高、然而速度能够绝对慢)、承受邮件、近程登录。
  • UDP 利用场景: 效率要求绝对高,对准确性要求绝对低的场景。例如:QQ 聊天、在线视频、网络语音电话(即时通讯,速度要求高,然而呈现偶然断续不是太大问题,并且此处齐全不能够应用重发机制)、播送通信(播送、多播)。

POST 和 PUT 申请的区别

  • PUT 申请是向服务器端发送数据,从而批改数据的内容,然而不会减少数据的品种等,也就是说无论进行多少次 PUT 操作,其后果并没有不同。(能够了解为时 更新数据
  • POST 申请是向服务器端发送数据,该申请会扭转数据的品种等资源,它会创立新的内容。(能够了解为是 创立数据

闭包的利用场景

  • 柯里化 bind
  • 模块

New 操作符做了什么事件?

1、首先创立了一个新对象
2、设置原型,将对象的原型设置为函数的 prototype 对象
3、让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)4、判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象

代码输入后果

const async1 = async () => {console.log('async1');
  setTimeout(() => {console.log('timer1')
  }, 2000)
  await new Promise(resolve => {console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)

输入后果如下:

script start
async1
promise1
script end
1
timer2
timer1

代码的执行过程如下:

  1. 首先执行同步带吗,打印出 script start;
  2. 遇到定时器 timer1 将其退出宏工作队列;
  3. 之后是执行 Promise,打印出 promise1,因为 Promise 没有返回值,所以前面的代码不会执行;
  4. 而后执行同步代码,打印出 script end;
  5. 继续执行上面的 Promise,.then 和.catch 冀望参数是一个函数,这里传入的是一个数字,因而就会产生值浸透,将 resolve(1)的值传到最初一个 then,间接打印出 1;
  6. 遇到第二个定时器,将其退出到微工作队列,执行微工作队列,按程序顺次执行两个定时器,然而因为定时器工夫的起因,会在两秒后先打印出 timer2,在四秒后打印出 timer1。

对 WebSocket 的了解

WebSocket 是 HTML5 提供的一种浏览器与服务器进行 全双工通信 的网络技术,属于应用层协定。它基于 TCP 传输协定,并复用 HTTP 的握手通道。浏览器和服务器只须要实现一次握手,两者之间就间接能够创立持久性的连贯,并进行双向数据传输。

WebSocket 的呈现就解决了半双工通信的弊病。它最大的特点是:服务器能够向客户端被动推动音讯,客户端也能够被动向服务器推送音讯。

WebSocket 原理:客户端向 WebSocket 服务器告诉(notify)一个带有所有接收者 ID(recipients IDs)的事件(event),服务器接管后立刻告诉所有沉闷的(active)客户端,只有 ID 在接收者 ID 序列中的客户端才会解决这个事件。

WebSocket 特点的如下:

  • 反对双向通信,实时性更强
  • 能够发送文本,也能够发送二进制数据‘’
  • 建设在 TCP 协定之上,服务端的实现比拟容易
  • 数据格式比拟轻量,性能开销小,通信高效
  • 没有同源限度,客户端能够与任意服务器通信
  • 协定标识符是 ws(如果加密,则为 wss),服务器网址就是 URL
  • 与 HTTP 协定有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采纳 HTTP 协定,因而握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

Websocket 的应用办法如下:

在客户端中:

// 在 index.html 中间接写 WebSocket,设置服务端的端口号为 9999
let ws = new WebSocket('ws://localhost:9999');
// 在客户端与服务端建设连贯后触发
ws.onopen = function() {console.log("Connection open."); 
    ws.send('hello');
};
// 在服务端给客户端发来音讯的时候触发
ws.onmessage = function(res) {console.log(res);       // 打印的是 MessageEvent 对象
    console.log(res.data);  // 打印的是收到的音讯
};
// 在客户端与服务端建设敞开后触发
ws.onclose = function(evt) {console.log("Connection closed.");
}; 

实现模板字符串解析

形容 :实现函数使得将 template 字符串中的{{}} 内的变量替换。

外围:应用字符串替换办法 str.replace(regexp|substr, newSubStr|function),应用正则匹配代换字符串。

实现

function render(template, data) {// 模板字符串正则 /\{\{(\w+)\}\}/, 加 g 为全局匹配模式, 每次匹配都会调用前面的函数
    let computed = template.replace(/\{\{(\w+)\}\}/g, function(match, key) {
        // match: 匹配的子串;  key:括号匹配的字符串
        return data[key];
    });
    return computed;
}

// 测试
let template = "我是{{name}},年龄{{age}},性别{{sex}}";
let data = {
  name: "张三",
  age: 18
}
console.log(render(template, data)); // 我是张三,年龄 18,性别 undefined

常见的 CSS 布局单位

罕用的布局单位包含像素(px),百分比(%),emremvw/vh

(1)像素px)是页面布局的根底,一个像素示意终端(电脑、手机、平板等)屏幕所能显示的最小的区域,像素分为两种类型:CSS 像素和物理像素:

  • CSS 像素:为 web 开发者提供,在 CSS 中应用的一个形象单位;
  • 物理像素:只与设施的硬件密度无关,任何设施的物理像素都是固定的。

(2)百分比%),当浏览器的宽度或者高度发生变化时,通过百分比单位能够使得浏览器中的组件的宽和高随着浏览器的变动而变动,从而实现响应式的成果。个别认为子元素的百分比绝对于间接父元素。

(3)em 和 rem绝对于 px 更具灵活性,它们都是绝对长度单位,它们之间的区别:em 绝对于父元素,rem 绝对于根元素。

  • em: 文本绝对长度单位。绝对于以后对象内文本的字体尺寸。如果以后行内文本的字体尺寸未被人为设置,则绝对于浏览器的默认字体尺寸(默认 16px)。(绝对父元素的字体大小倍数)。
  • rem: rem 是 CSS3 新增的一个绝对单位,绝对于根元素(html 元素)的 font-size 的倍数。作用:利用 rem 能够实现简略的响应式布局,能够利用 html 元素中字体的大小与屏幕间的比值来设置 font-size 的值,以此实现当屏幕分辨率变动时让元素也随之变动。

(4)vw/vh是与视图窗口无关的单位,vw 示意绝对于视图窗口的宽度,vh 示意绝对于视图窗口高度,除了 vw 和 vh 外,还有 vmin 和 vmax 两个相干的单位。

  • vw:绝对于视窗的宽度,视窗宽度是 100vw;
  • vh:绝对于视窗的高度,视窗高度是 100vh;
  • vmin:vw 和 vh 中的较小值;
  • vmax:vw 和 vh 中的较大值;

vw/vh 和百分比很相似,两者的区别:

  • 百分比(%):大部分绝对于先人元素,也有绝对于本身的状况比方(border-radius、translate 等)
  • vw/vm:绝对于视窗的尺寸

说一下 for…in 和 for…of 的区别?

for...of 遍历获取的是对象的键值, for...in 获取的是对象的键名;
for...in 会遍历对象的整个原型链, 性能十分差不举荐应用, 而 for...of 只遍历以后对象不会遍历原型链;
对于数组的遍历,for...in 会返回数组中所有可枚举的属性(包含原型链上可枚举的属性),for...of 只返回数组的下标对应的属性值;
总结:for...in 循环次要是为了遍历对象而生, 不实用遍历数组; for....of 循环能够用来遍历数组、类数组对象、字符串、Set、Map 以及 Generator 对象

页面有多张图片,HTTP 是怎么的加载体现?

  • HTTP 1 下,浏览器对一个域名下最大 TCP 连接数为 6,所以会申请屡次。能够用 多域名部署 解决。这样能够进步同时申请的数目,放慢页面图片的获取速度。
  • HTTP 2 下,能够一瞬间加载进去很多资源,因为,HTTP2 反对多路复用,能够在一个 TCP 连贯中发送多个 HTTP 申请。

new 操作符的实现原理

new 操作符的执行过程:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

具体实现:

function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  // 判断参数是否是一个函数
  if (typeof constructor !== "function") {console.error("type error");
    return;
  }
  // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  newObject = Object.create(constructor.prototype);
  // 将 this 指向新建对象,并执行函数
  result = constructor.apply(newObject, arguments);
  // 判断返回对象
  let flag = result && (typeof result === "object" || typeof result === "function");
  // 判断返回后果
  return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);

数组扁平化

数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。应用 Array.prototype.flat 能够间接将多层数组拍平成一层:

[1, [2, [3]]].flat(2)  // [1, 2, 3]

当初就是要实现 flat 这种成果。

ES5 实现:递归。

function flatten(arr) {var result = [];
    for (var i = 0, len = arr.length; i < len; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]))
        } else {result.push(arr[i])
        }
    }
    return result;
}

ES6 实现:

function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);
    }
    return arr;
}

事件循环机制(Event Loop)

事件循环机制从整体上通知了咱们 JavaScript 代码的执行程序 Event Loop即事件循环,是指浏览器或 Node 的一种解决 javaScript 单线程运行时不会阻塞的一种机制,也就是咱们常常应用 异步 的原理。

先执行 Script 脚本,而后清空微工作队列,而后开始下一轮事件循环,持续先执行宏工作,再清空微工作队列,如此往返。

  • 宏工作:Script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
  • 微工作:process.nextTick()/Promise

上诉的 setTimeout 和 setInterval 等都是工作源,真正进入工作队列的是他们散发的工作。

优先级

  • setTimeout = setInterval 一个队列
  • setTimeout > setImmediate
  • process.nextTick > Promise
for (const macroTask of macroTaskQueue) {handleMacroTask();    
  for (const microTask of microTaskQueue) {handleMicroTask(microTask);  
  }
}

懒加载的概念

懒加载也叫做提早加载、按需加载,指的是在长网页中提早加载图片数据,是一种较好的网页性能优化的形式。在比拟长的网页或利用中,如果图片很多,所有的图片都被加载进去,而用户只能看到可视窗口的那一部分图片数据,这样就节约了性能。

如果应用图片的懒加载就能够解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,缩小了服务器的负载。懒加载实用于图片较多,页面列表较长(长列表)的场景中。

冒泡排序 – 工夫复杂度 n^2

题目形容: 实现一个冒泡排序

实现代码如下:

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于管制从头到尾的比拟 + 替换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于实现每一轮遍历过程中的反复比拟 + 替换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素后面的数比前面的大
      if (arr[j] > arr[j + 1]) {
        // 替换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

为什么须要浏览器缓存?

对于浏览器的缓存,次要针对的是前端的动态资源,最好的成果就是,在发动申请之后,拉取相应的动态资源,并保留在本地。如果服务器的动态资源没有更新,那么在下次申请的时候,就间接从本地读取即可,如果服务器的动态资源曾经更新,那么咱们再次申请的时候,就到服务器拉取新的资源,并保留在本地。这样就大大的缩小了申请的次数,进步了网站的性能。这就要用到浏览器的缓存策略了。

所谓的 浏览器缓存 指的是浏览器将用户申请过的动态资源,存储到电脑本地磁盘中,当浏览器再次拜访时,就能够间接从本地加载,不须要再去服务端申请了。

应用浏览器缓存,有以下长处:

  • 缩小了服务器的累赘,进步了网站的性能
  • 放慢了客户端网页的加载速度
  • 缩小了多余网络数据传输

Promise.all 和 Promise.race 的区别的应用场景

(1)Promise.all Promise.all能够将多个 Promise 实例包装成一个新的 Promise 实例。同时,胜利和失败的返回值是不同的,胜利的时候返回的是 一个后果数组 ,而失败的时候则返回 最先被 reject 失败状态的值

Promise.all 中传入的是数组,返回的也是是数组,并且会将进行映射,传入的 promise 对象返回的值是依照程序在数组中排列的,然而留神的是他们执行的程序并不是依照程序的,除非可迭代对象为空。

须要留神,Promise.all 取得的胜利后果的数组外面的数据程序和 Promise.all 接管到的数组程序是统一的,这样当遇到发送多个申请并依据申请程序获取和应用数据的场景,就能够应用 Promise.all 来解决。

(2)Promise.race

顾名思义,Promse.race 就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])外面哪个后果取得的快,就返回那个后果,不论后果自身是胜利状态还是失败状态。当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

列表转成树形构造

题目形容:

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]

实现代码如下:

function listToTree(data) {let temp = {};
  let treeData = [];
  for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
  }
  for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
      temp[temp[i].parentId].children.push(temp[i]);
    } else {treeData.push(temp[i]);
    }
  }
  return treeData;
}

Promise.allSettled

形容 :等到所有promise 都返回后果,就返回一个 promise 实例。

实现

Promise.allSettled = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = {
                            status: 'fulfilled',
                            value: value
                        };
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => {
                        count++;
                        result[index] = {
                            status: 'rejected'.
                            reason: reason
                        };
                        if(count === promises.length) resolve(result);
                    }
                );
            });
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

正文完
 0