面经2

99 阅读10分钟

1、怎么实现一个宽高自适应的正方形?

  • 利用vw来实现:
1.square {
2  width: 10%;
3  height: 10vw;
4  background: tomato;
5}
  • 利用元素的margin/padding百分比是相对父元素width的性质来实现:
1.square {
2  width: 20%;
3  height: 0;
4  padding-top: 20%;
5  background: orange;
6}
  • 利用子元素的margin-top的值来实现:
复制
1.square {
2  width: 30%;
3  overflow: hidden;
4  background: yellow;
5}
6.square::after {
7  content: '';
8  display: block;
9  margin-top: 100%;
10}

2、简单描述从输入网址到页面显示的过程

fe.ecool.fun/topic/5def1…

3、null 和 undefined 有什么区别?

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。

undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。

当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

复制
1typeof null; // "object" (not "null" for legacy reasons)
2typeof undefined; // "undefined"
3null === undefined; // false
4null == undefined; // true
5null === null; // true
6null == null; // true
7!null; // true
8Number.isNaN(1 + null); // false
9Number.isNaN(1 + undefined); // true

4、ES6中数组新增了哪些扩展?

fe.ecool.fun/topic/4a0df…

5、说说你对 Iterator, Generator 和 Async/Await 的理解

fe.ecool.fun/topic/82b53…

6、es5 中的类和es6中的class有什么区别?

fe.ecool.fun/topic/10364…

6.1、ES5怎么实现继承

fe.ecool.fun/topic/9b198…

7、谈谈对 this 对象的理解

fe.ecool.fun/topic/fe97f…

8、为什么要区分宏任务和微任务?它们的执行优先级是什么?

宏任务(macrotask)和微任务(microtask)的区分主要是为了解决 JavaScript 引擎中不同任务之间的执行优先级问题。

宏任务通常包括以下几种:

  • setTimeout 和 setInterval 定时器
  • DOM 事件处理程序
  • AJAX 请求的回调函数
  • script 标签的加载和执行

对于宏任务,JavaScript 引擎会将其添加到任务队列(task queue)中,在当前任务执行完毕后按顺序依次执行。

而微任务通常包括以下几种:

  • Promise 的 then 方法和 catch 方法
  • async/await 中的 await 表达式
  • MutationObserver 监听器

对于微任务,JavaScript 引擎也会将其添加到任务队列中,但是微任务的执行在当前宏任务执行结束后立即进行,也就是说微任务具有更高的执行优先级,可以优先于下一个宏任务执行。

通过区分宏任务和微任务,我们可以更好地控制任务的执行顺序,提高应用程序的性能和响应速度。例如,在处理一些异步操作时,可以使用 Promise 来代替普通的回调函数,并通过 then 方法和 catch 方法来实现更灵活、更高效的任务处理方式。同时,在编写代码时需要注意,尽量避免在宏任务中进行耗时操作,以免影响其他任务的执行。

总之,宏任务和微任务的区分是为了更好地协调任务的执行优先级,提高 JavaScript 的运行效率和代码的可读性。

7.1、object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别是什么?

fe.ecool.fun/topic/8244f…

8、如何实现浏览器内多个标签页之间的通信?

Broadcast Channel

顾名思义,“广播频道”,官方文档里的解释为“用于同源不同页面之间完成通信的功能”,在其中某个页面发送的消息会被其他页面监听到。

注意“同源”二字,该方法无法完成跨域的数据传输。

localStorage

localStorage是浏览器多个标签共用的存储空间,所以可以用来实现多标签之间的通信(ps:session是会话级的存储空间,每个标签页都是单独的)。

SharedWorker

SharedWorker可以被多个window共同使用,但必须保证这些标签页都是同源的(相同的协议,主机和端口号)

WebSocket通讯

全双工(full-duplex)通信自然可以实现多个标签之间的通信

定时器setInterval+cookie

  • 在页面A设置一个使用setInterval定时器不断刷新,检查Cookies的值是否发生变化,如果变化就进行刷新的操作。
  • 由于Cookies是在同域可读的,所以在页面B审核的时候改变Cookies的值,页面A自然是可以拿到的。

这样做确实可以实现我想要的功能,但是这样的方法相当浪费资源。虽然在这个性能过盛的时代,浪费不浪费也感觉不出来,但是这种实现方案,确实不够优雅。

postMessage

两个需要交互的tab页面具有依赖关系。

如 A页面中通过JavaScript的window.open打开B页面,或者B页面通过iframe嵌入至A页面,此种情形最简单,可以通过HTML5的 window.postMessage API完成通信,由于postMessage函数是绑定在 window 全局对象下,因此通信的页面中必须有一个页面(如A页面)可以获取另一个页面(如B页面)的window对象,这样才可以完成单向通信;B页面无需获取A页面的window对象,如果需要B页面对A页面的通信,只需要在B页面侦听message事件,获取事件中传递的source对象,该对象即为A页面window对象的引用:

1//B页面
2window.addEventListner('message',(e)=>{
3    let {data,source,origin} = e;
4    source.postMessage('message echo','/');
5});

postMessage的第一个参数为消息实体,它是一个结构化对象,即可以通过“JSON.stringify和JSON.parse”函数还原的对象;第二个参数为消息发送范围选择器,设置为“/”意味着只发送消息给同源的页面,设置为“*”则发送全部页面。

9、如何顺序执行10个异步任务?

解法1:for 循环 + await

简单的 for 循环是依次进行循环的,不像 Array.forEach,Array.map 方法是并发执行的,利用这一特点加 async / await 很容易写出下面这样的代码:

1(async () => {
2  const sleep = delay => {
3    return new Promise((resolve, reject) => {
4      setTimeout(_ => resolve(), delay)
5    })
6  }
7  
8  const task = (i) => {
9    return new Promise(async (resolve, reject) => {
10      await sleep(500)
11      console.log(`now is ${i}`)
12      ++i
13      resolve(i)
14    })
15  }
16  
17  let param = 0
18  for (let i = 0; i < 4; i++) {
19    param = await task(param)
20  }  
21})()

输出:

now is 0
now is 1
now is 2
now is 3

解法2:Array.prototype.reduce

关于 Array.prototype.reduce 方法相信大部分小伙伴初见时都是用来数组求和。

reduce有初始值积累值,以及当前值的概念。其中 积累值可以看作是前一个值,通过返回积累值又可以看作是 下一个值。使用reduce来解决问题的代码为:

1const sleep = delay => {
2  return new Promise((resolve, reject) => {
3    setTimeout(_ => resolve(), delay)
4  })
5}
6
7const task = (i) => {
8  return new Promise(async (resolve, reject) => {
9    await sleep(500)
10    console.log(`now is ${i}`)
11    ++i
12    resolve(i)
13  })
14}
15
16[task, task, task, task].reduce(async (prev, task) => {
17  const res = await prev
18  return task(res)
19}, 0)

输出:

now is 0
now is 1
now is 2
now is 3

可以这样理解 prev 和 task

  • prev:前一个 异步任务(promise)
  • task:当前的异步任务

当前的异步任务需要上一个异步任务的结果作参数,故很显然要 await prev。

10、怎么理解ES6中 Generator的?使用场景有哪些?

fe.ecool.fun/topic/6b4f3…

11、连续 bind()多次,输出的值是什么?

var bar = function(){
2    console.log(this.x);
3}
4var foo = {
5    x:3
6}
7var sed = {
8    x:4
9}
10var func = bar.bind(foo).bind(sed);
11func(); //?
12 
13var fiv = {
14    x:5
15}
16var func = bar.bind(foo).bind(sed).bind(fiv);
17func(); //?

参考答案:

两次都输出 3

在Javascript中,多次 bind() 是无效的。

更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

11.1、什么是 Samesite Cookie 属性?

fe.ecool.fun/topic/aef19…

11.2、Cookie 的 SameSite 属性有什么作用?

fe.ecool.fun/topic/d86b1…

12、TypeScript中的 Declare 关键字有什么用?

JavaScript库或框架没有TypeScript声明文件。 但是,如果要在TypeScript文件中使用它们而没有任何编译错误,则必须使用declare关键字。 declare关键字用于环境声明和您要定义可能在其他位置存在的变量的方法。

如果要在我们的TypeScript代码中使用该库,则可以使用以下代码:

1declare var myLibrary;

TypeScript运行时会将myLibrary变量分配为 any。

13、ts中any和unknown有什么区别?

unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。

举例说明:

1let foo: any = 123;
2console.log(foo.msg); // 符合TS的语法
3let a_value1: unknown = foo;   // OK
4let a_value2: any = foo;      // OK
5let a_value3: string = foo;   // OK
6
7let bar: unknown = 222; // OK 
8console.log(bar.msg); // Error
9let k_value1: unknown = bar;   // OK
10let K_value2: any = bar;      // OK
11let K_value3: string = bar;   // Error

因为bar是一个未知类型(任何类型的数据都可以赋给 unknown 类型),所以不能确定是否有msg属性。不能通过TS语法检测;而 unknown 类型的值也不能将值赋给 any 和 unknown 之外的类型变量

总结

any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。

14、什么是Typescript的方法重载?

在TypeScript中,方法重载(Method Overloading)是一种允许函数在不同参数数量或参数类型下具有不同的返回类型或行为的特性。这允许您以一种更灵活的方式定义函数,并根据传入的参数类型或数量来选择适当的行为或返回类型。

方法重载通常用于提供更加严格的类型检查和更好的类型推断,以及在代码中提供更清晰的接口。它使得函数可以根据不同的参数签名,提供不同的实现方式,而无需使用额外的运行时检查。

要定义方法重载,您需要按照以下步骤进行:

  1. 首先,定义一个函数的多个签名(overload signatures)。每个签名包含一个参数列表和一个返回类型。
  2. 然后,定义一个实际的函数体,这个函数体实现了多个签名所涵盖的不同情况。

这里有一个简单的例子,演示了如何在TypeScript中使用方法重载:

1function greet(name: string): string;
2function greet(age: number): string;
3function greet(value: string | number): string {
4  if (typeof value === "string") {
5    return `Hello, ${value}!`;
6  } else {
7    return `You are ${value} years old!`;
8  }
9}
10
11console.log(greet("Lydia")); // Output: "Hello, Lydia!"
12console.log(greet(30)); // Output: "You are 30 years old!"

上面定义了greet函数的两个不同的签名:一个接受string类型参数,另一个接受number类型参数。然后,我们实现了一个函数体,根据传入的参数类型进行相应的处理。

使用方法重载,TypeScript能够更好地检查函数调用,以确保传递的参数类型与预期的类型相符,并提供适当的类型推断,从而增加代码的类型安全性和可读性。

15、Typescript中泛型是什么?

fe.ecool.fun/topic/bc2dd…

16、Typescript中 interface 和 type 的差别是什么?

fe.ecool.fun/topic/cb091…

17、body-parser 这个中间件是做什么用的?

fe.ecool.fun/topic/4adc7…

18、说说对 Node 中的 fs模块的理解? 有哪些常用方法

fe.ecool.fun/topic/b415a…

19、pm2守护进程的原理是什么?

fe.ecool.fun/topic/6a538…