2023.04.17面试题整理--js

218 阅读12分钟

js

1.说下事件循环

事件循环是JavaScript的一种异步编程模型,用于处理异步操作和事件回调。在JavaScript中,事件循环是通过消息队列来实现的,每当有新的事件加入到消息队列中时,事件循环就会不断地从队列中取出事件并执行,直到队列为空为止。

事件循环的过程可以分为以下几个步骤:

执行同步代码:事件循环首先会执行当前线程的同步代码,直到遇到异步操作或者定时器等待。

处理异步操作:当遇到异步操作时,事件循环会将它放到消息队列中,然后继续执行同步代码。

等待定时器:当遇到定时器等待时,事件循环会将定时器放到定时器队列中,然后继续执行同步代码。

执行消息队列中的事件回调:当同步代码执行完毕,并且消息队列中有事件时,事件循环会将队列中的事件回调取出,并执行它们。

重复执行:事件循环会一直重复以上步骤,直到队列为空或者达到了某个条件(如超时)为止。

在事件循环中,JavaScript使用单线程来执行代码,这意味着同一时间只能执行一个任务。当代码执行到一个异步操作时,它会将操作放到消息队列中,并继续执行下一条语句。当所有的同步代码都执行完毕后,事件循环才会从消息队列中取出并执行异步操作对应的回调函数。

- 整体`script` 作为第一个宏任务进入主线程,遇到console.log,输出`Start`。
- 遇到`setTimeout`,其回调函数被分发到宏任务事件队列中。
- 遇到`Promise`new Promise直接执行,输出Promise。then被分发到微任务事件队列中。
- 遇到console.log,立即执行,输出`End`。
- 整体代码`script`作为第一个宏任务执行结束,看看有哪些微任务?我们发现了`then`在微任务Event Queue里面,执行
- 第一轮事件循环结束后,开始第二轮循环,从宏任务事件队列开始。发现了宏任务事件队列中`setTimeout`对应的回调函数,立即执行。
- 所有代码结束。

2.try catch有什么异常无法捕获不到的

在js中,try-catch语句可以用来捕获和处理异常。但是,有一些异常无法被try-catch语句捕获到,包括以下几种:

异步异常:异步异常是指在异步操作中发生的异常,例如异步回调函数中的异常。由于异步操作的执行顺序是不确定的,因此try-catch语句无法捕获到这些异常。

语法错误:语法错误是指在代码中存在语法错误,例如拼写错误、缺少括号等。由于语法错误会导致代码无法执行,因此try-catch语句也无法捕获到这些异常。

未定义变量:未定义变量是指在代码中使用了未定义的变量。由于未定义变量会导致代码无法执行,因此try-catch语句也无法捕获到这些异常。

跨域异常:跨域异常是指在JavaScript中访问跨域资源时发生的异常。由于浏览器的同源策略限制,JavaScript无法访问跨域资源,因此try-catch语句也无法捕获到这些异常。

总之,try-catch语句可以捕获大部分的异常,但是对于异步异常、语法错误、未定义变量和跨域异常等情况无能为力。因此,在编写JavaScript代码时,需要注意这些异常情况,并采取相应的措施来避免它们的发生。

3.js中for..of和for...in有什么区别

for...of
for...of语句用于遍历可迭代对象(包括数组、字符串、SetMap等),它会迭代对象的所有属性值,并且会自动跳过对象的原型链上的属性。for...of循环返回的是集合中的元素值,而不是元素的下标或键值。

const arr = [1, 2, 3];
for (const item of arr) {
  console.log(item); // 1, 2, 3
}

const str = "hello";
for (const char of str) {
  console.log(char); // h, e, l, l, o
}

for...in
for...in语句用于遍历对象的可枚举属性(包括自身属性和原型链上的属性),它会迭代对象的所有属性名称,并且返回的是属性名称,而不是属性值。

const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key); // a, b, c
  console.log(obj[key]); // 1, 2, 3
}

需要注意的是,for...in循环会遍历对象的所有可枚举属性,包括原型链上的属性,因此可能会遍历出一些意外的属性。为了避免这种情况,可以使用Object.hasOwnProperty()方法来过滤掉原型链上的属性。

总的来说,for...of适用于遍历集合中的元素,for...in适用于遍历对象的属性。如果不确定要遍历的集合类型,可以使用typeof运算符来判断集合的类型,然后选择相应的遍历方式。

4.js如果实现for...of迭代对象

要实现for...of迭代对象,需要将对象转换为可迭代对象(Iterable),即实现Symbol.iterator方法。Symbol.iterator方法是一个内置的、返回迭代器的方法,它定义了一个对象的默认迭代器。

具体来说,要实现for...of迭代对象,需要按照以下步骤:

定义对象,并实现Symbol.iterator方法,返回一个迭代器对象。
const obj = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

在上述代码中,我们定义了一个对象obj,它包含一个数组data和一个Symbol.iterator方法。Symbol.iterator方法返回一个包含next方法的迭代器对象。next方法用于返回迭代器的下一个值和迭代状态,当所有值都迭代完毕时,返回done: true。

使用for...of遍历对象。
for (const value of obj) {
  console.log(value);
}
在上述代码中,我们使用for...of遍历对象obj,它会依次输出数组data中的元素123。

需要注意的是,只有实现了Symbol.iterator方法的对象才能被for...of循环遍历。如果对象没有实现Symbol.iterator方法,就不能使用for...of遍历对象。

5.for...in和obj.keys有什么区别

for...inObject.keys()都可以用于遍历对象的属性名,但它们有以下几个区别:

1for...in循环会遍历对象的所有可枚举属性,包括自身属性和原型链上的属性,而Object.keys()方法只会返回对象自身的可枚举属性。

2for...in循环返回的是属性名,而Object.keys()方法返回的是属性名数组。

3for...in循环遍历对象属性的顺序是不确定的,而Object.keys()方法返回的属性名数组是按照属性插入的顺序排列的。

总的来说,for...in循环适用于遍历对象的所有属性,包括原型链上的属性,而Object.keys()方法适用于遍历对象自身的可枚举属性

6.箭头函数和普通函数有什么区别?通常用在什么地方?

箭头函数和普通函数有以下几个区别:

语法形式不同:箭头函数使用 => 符号来定义,而普通函数使用 function 关键字来定义。
箭头函数没有自己的 thisargumentssuper 对象:箭头函数会继承上下文中的 thisargumentssuper 对象,而不是像普通函数那样具有自己的 thisargumentssuper 对象。这意味着在箭头函数中使用 this 时,它将指向定义该函数的上下文中的 this,而不是函数本身的 this。

箭头函数不能用作构造函数:箭头函数没有自己的 prototype 对象,因此不能用作构造函数来创建对象。
箭头函数不能使用 yield 关键字:箭头函数不能作为生成器函数使用 yield 关键字。
通常情况下,箭头函数用于简化代码和解决上下文的问题。它们通常用于处理回调函数、映射和过滤数组等常见的函数式编程场景,以及在 React 中处理组件状态或属性的值。而普通函数则更加通用,适用于各种场景,如事件处理程序、对象方法、构造函数等等。

7.es6对象上有那些新增的方法?

ES6在对象上新增了一些方法,以下是它们的介绍:

Object.assign(target, ...sources)
Object.assign方法用于将源对象的所有可枚举属性复制到目标对象中。它接收一个目标对象和一个或多个源对象作为参数,返回目标对象。如果目标对象中已经存在同名属性,则后面的属性会覆盖前面的属性。
例如:
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 3, c: 4 }

Object.keys(obj)
Object.keys方法用于返回一个由目标对象的所有可枚举属性组成的数组。它接收一个目标对象作为参数,返回一个数组。
例如:
const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
console.log(keys); // ['a', 'b', 'c']

Object.values(obj)
Object.values方法用于返回一个由目标对象的所有可枚举属性的值组成的数组。它接收一个目标对象作为参数,返回一个数组。
例如:
const obj = { a: 1, b: 2, c: 3 };
const values = Object.values(obj);
console.log(values); // [1, 2, 3]

Object.entries(obj)
Object.entries方法用于返回一个由目标对象的所有可枚举属性的键值对组成的数组。它接收一个目标对象作为参数,返回一个数组。
例如:
const obj = { a: 1, b: 2, c: 3 };
const entries = Object.entries(obj);
console.log(entries); // [['a', 1], ['b', 2], ['c', 3]]

Object.getOwnPropertyDescriptors(obj)
Object.getOwnPropertyDescriptors方法用于返回一个目标对象的所有属性的描述符。它接收一个目标对象作为参数,返回一个对象。

8.js中class和extend原理

JavaScript中,class是一种创建对象的模板,它可以被看作是一组函数的集合,用于描述一个对象的属性和方法。class定义的对象通常被称为类(class),而类的实例化对象则称为对象(object)。

class的语法如下:

class ClassName {
  constructor() {
    // 构造函数
  }

  method1() {
    // 方法1
  }

  method2() {
    // 方法2
  }
}
class定义了一个名为ClassName的类,其中包含了一个构造函数和两个方法method1和method2。通过class定义的类,可以使用new操作符来创建实例对象,例如:

const obj = new ClassName();
extend是一个关键字,用于继承一个已有的类,并创建一个新的子类。使用extend可以使子类继承父类的属性和方法,并且可以增加自己的属性和方法。

extend的语法如下:

class ChildClass extends ParentClass {
  constructor() {
    super();
    // 子类的构造函数
  }

  childMethod() {
    // 子类的方法
  }
}
在上述语法中,ChildClass继承了ParentClass,子类的构造函数中使用了super关键字来调用父类的构造函数。子类可以增加自己的属性和方法,例如childMethod。

使用extend关键字可以实现面向对象编程中的继承,使得子类可以复用父类的代码,提高了代码的复用性和可维护性。
ES6中,class是一种新的语法糖,用于创建基于原型继承的对象。class语法糖本质上是基于原型和构造函数的继承模型实现的,它提供了更加简洁、直观的方式来定义类和继承关系。

class语法糖的基本用法如下:

class Animal {
  constructor(name) {
    this.name = name;
  }

  hello() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  bark() {
    console.log(`Woof, I'm a ${this.breed}`);
  }
}

const dog = new Dog('Buddy', 'Labrador');
dog.hello(); // Hello, I'm Buddy
dog.bark(); // Woof, I'm a Labrador
在上述代码中,AnimalDog都是classAnimal是父类,Dog是子类。Dog继承了Animal,并且在构造函数中通过super关键字调用了父类的构造函数。

class语法糖的实现原理是基于原型和构造函数的继承模型。在ES6之前,JavaScript中的继承是通过原型链实现的,这种方式比较复杂,容易出错。而class语法糖提供了更加简洁、直观的方式来定义类和继承关系,它的实现原理可以概括为以下几个步骤:

定义类
使用class关键字定义一个类,类名通常使用大写字母开头,类中包含构造函数和其他方法。

创建实例
使用new关键字和类名创建一个类的实例,通过实例可以调用类中的方法和属性。

定义继承关系
使用extends关键字定义一个类的继承关系,子类可以继承父类

9.js中Set、Map、WeakSet、WeakMap的区别

SetMapWeakSetWeakMap都是ES6中新增的数据结构,它们各自有不同的特点和用途。

Set
Set是一种无序、不可重复的集合,它的成员唯一。Set的主要用途是去重,它提供了一系列的方法用于添加、删除、遍历集合中的元素。

Map
Map是一种键值对的集合,它的键可以是任何类型,包括基本类型和引用类型。与Object不同的是,Map可以使用任意类型作为键值,并且提供了一系列的方法用于添加、删除、遍历集合中的元素。

WeakSet
WeakSet是一种特殊的Set集合,它的成员只能是对象,并且成员对象都是弱引用。WeakSet的主要用途是存储DOM节点,因为DOM节点在页面中删除后,相应的WeakSet成员也会自动被清除,避免了内存泄漏的问题。

WeakMap
WeakMap是一种特殊的Map集合,它的键只能是对象,并且键值对都是弱引用。WeakMap的主要用途是在对象上存储元数据,并且这些元数据不会随着对象的生命周期而被清除,而是在对象被垃圾回收时自动清除。

总的来说,SetMap是常规数据结构,常用于管理数据集合和键值对集合,而WeakSetWeakMap则是在特定场景下使用的数据结构,主要用于解决内存泄漏问题。

10.fetch是基于什么协议实现的?说说过程

Fetch API 是基于 HTTP 协议实现的,它是一种用于 Web 开发的 API,提供了一个简单的接口,用于实现基于网络的资源请求和响应。

Fetch API 是通过 XMLHttpRequest 对象实现的,但提供了更简单和更强大的接口,同时支持 Promise,可以方便地进行异步操作。

当使用 fetch 发送网络请求时,它会首先创建一个 Request 对象,然后使用该对象来发送请求。Request 对象包含有关请求的信息,如请求的 URL、请求方法、请求头等。Fetch API 还可以使用一个可选的 RequestInit 对象来指定其他的请求参数,例如请求体、身份验证、缓存等。

发送请求后,fetch 会返回一个 Promise 对象,该对象表示一个 Response,其中包含有关响应的信息,如响应的状态、响应头、响应体等。在收到响应后,可以使用一系列方法来读取响应体中的数据,如 text()、json()、arrayBuffer() 等。

需要注意的是,fetch API 是基于 Promise 实现的异步请求,因此需要使用 async/await 或 then/catch 进行处理。另外,fetch API 也支持跨域请求和 Cookies 的发送和接收,但需要在发送请求时进行相应的配置。