0401面试题整理

71 阅读42分钟

1.react子组件往父组件传值

要在React中将子组件的值传递给父组件,可以通过以下步骤实现:

在父组件中定义一个状态变量,用于接收子组件传递的值,例如:
const [value, setValue] = useState("");
在父组件中创建一个处理子组件值的函数,将它作为props传递给子组件,例如:
const handleChildValue = (childValue) => {
  setValue(childValue);
};

return (
  <ChildComponent onChildValue={handleChildValue} />
);
在子组件中,通过调用props中传递的处理函数,将子组件的值传递给父组件,例如:
const handleChange = (event) => {
  props.onChildValue(event.target.value);
};

return (
  <input type="text" onChange={handleChange} />
);
这样,当子组件的值发生变化时,会触发handleChange函数,将子组件的值通过onChildValue函数传递给父组件,并更新父组件中的状态变量。

2.浅克隆的方法

JavaScript中,可以使用以下方法实现浅克隆:

使用Object.assign()方法
const original = { a: 1, b: 2 };
const clone = Object.assign({}, original);
console.log(clone); // { a: 1, b: 2 }

使用展开运算符(...)
const original = { a: 1, b: 2 };
const clone = { ...original };
console.log(clone); // { a: 1, b: 2 }

使用Array.slice()方法(仅适用于数组)
const original = [1, 2, 3];
const clone = original.slice();
console.log(clone); // [1, 2, 3]

这些方法都可以创建一个新的对象或数组,包含原始对象或数组的所有属性或元素。但是,如果原始对象或数组包含引用类型的属性或元素,那么克隆的对象或数组仍然会与原始对象或数组共享这些引用类型的属性或元素。这意味着,如果在克隆对象或数组后修改其中一个引用类型的属性或元素,原始对象或数组中的相应属性或元素也会被修改。

3.css模块化

常见的CSS模块化方案有以下几种:

CSS ModulesCSS Modules是一种基于WebpackCSS模块化方案,它将CSS样式表封装到模块中,并生成一个唯一的类名,从而避免样式冲突的问题。

CSS-in-JSCSS-in-JS是一种将CSS样式表写在JavaScript代码中的模块化方案,它允许在JavaScript中使用CSS语法,并将样式封装到组件中,从而实现组件级别的样式定义。

Styled ComponentsStyled Components是一种基于CSS-in-JS的库,它允许在JavaScript代码中使用CSS语法,同时提供了一些额外的功能,如动态样式、主题支持等。

总的来说,CSS模块化使得CSS样式表更易于维护和组织,并提高了代码的可重用性和可维护性。

4.实现数组去重并排序

可以使用以下代码实现数组去重并排序:

const arr = [3, 1, 2, 3, 5, 1, 4, 2];

// 数组去重
const uniqueArr = Array.from(new Set(arr));

// 数组排序
const sortedArr = uniqueArr.sort((a, b) => a - b);

console.log(sortedArr); // [1, 2, 3, 4, 5]
首先,我们使用ES6Set数据结构来去重数组。通过Array.from()将Set转换为数组,得到去重后的uniqueArr数组。

然后,我们使用sort()方法来对数组进行排序,sort()方法接收一个排序函数作为参数。我们可以使用箭头函数来定义排序函数,它将两个参数a和b进行比较,返回一个整数,表示a和b的大小关系。如果返回负数,表示a在b之前;如果返回正数,表示a在b之后;如果返回0,表示a和b相等。因此,我们可以使用a-b来升序排序,或者b-a来降序排序。

最后,输出排序后的数组sortedArr。

5.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则是在特定场景下使用的数据结构,主要用于解决内存泄漏问题。

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

JavaScript中,for...offor...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运算符来判断集合的类型,然后选择相应的遍历方式。

7.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遍历对象。

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

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

for...in循环会遍历对象的所有可枚举属性,包括自身属性和原型链上的属性,而Object.keys()方法只会返回对象自身的可枚举属性。
const obj = { a: 1, b: 2 };
Object.prototype.c = 3;

for (const key in obj) {
  console.log(key); // a, b, c
}

const keys = Object.keys(obj);
console.log(keys); // ['a', 'b']
在上述代码中,for...in循环会遍历对象obj的属性a、b以及原型链上的属性c,而Object.keys()方法只会返回对象自身的属性a、b,不包括原型链上的属性c。

for...in循环返回的是属性名,而Object.keys()方法返回的是属性名数组。
const obj = { a: 1, b: 2 };
const keys = Object.keys(obj);

for (const key of keys) {
  console.log(key); // a, b
}
在上述代码中,Object.keys()方法返回的是属性名数组,我们可以使用for...of循环遍历属性名数组,并输出属性名。

for...in循环遍历对象属性的顺序是不确定的,而Object.keys()方法返回的属性名数组是按照属性插入的顺序排列的。
const obj = { a: 1, c: 3, b: 2 };
const keys = Object.keys(obj);

console.log(keys); // ['a', 'c', 'b']
在上述代码中,虽然属性c在对象中放置的位置在属性b之后,但Object.keys()方法返回的属性名数组中,属性c排在了属性b之前。

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

9.微前端,沙箱怎么实现

微前端是一种将大型单体应用拆分成小型、独立的子应用的架构模式,每个子应用可以独立开发、部署、运行,并且可以在同一个页面中共存。在微前端架构中,子应用之间的通信是一个很重要的问题,而沙箱技术就是用来解决子应用之间通信问题的。

沙箱(Sandbox)是一种隔离机制,用于隔离不同的代码执行环境,使它们互不干扰。在微前端架构中,每个子应用都有自己的沙箱,用于隔离子应用之间的DOM、样式、脚本等资源。

沙箱的实现可以分为以下几个步骤:

  • 创建沙箱

沙箱可以使用iframe或Web Worker等技术实现,这里以iframe为例。创建沙箱时,需要为iframe设置src属性,指向子应用的入口文件。

const iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3001';
document.body.appendChild(iframe);
  • 沙箱通信

要在主应用和子应用之间进行通信,可以使用postMessage方法。postMessage方法可以在不同的窗口间发送消息,它有两个参数:消息内容和接收消息的窗口的origin。

在主应用中,可以使用iframe.contentWindow属性获取到子应用的window对象,然后使用postMessage方法向子应用发送消息。在子应用中,可以使用parent.postMessage方法向父应用发送消息。

// 主应用中向子应用发送消息
const iframe = document.getElementById('child-app');
iframe.contentWindow.postMessage('hello', 'http://localhost:3001');

// 子应用中向主应用发送消息
parent.postMessage('world', 'http://localhost:3000');

需要注意的是,在发送消息时需要指定接收消息的窗口

10.判断对象是数组还是非数组对象的方式

JavaScript中,可以使用以下几种方式来判断一个对象是数组还是非数组对象:

instanceof运算符
可以使用instanceof运算符判断一个对象是否为数组,因为数组是Array类型的实例。instanceof运算符返回一个布尔值,如果对象是指定类型的实例,则返回true,否则返回falseconst arr = [1, 2, 3];
console.log(arr instanceof Array); // true

const obj = { a: 1, b: 2 };
console.log(obj instanceof Array); // false
在上述代码中,arr是一个数组,因此arr instanceof Array返回true;而obj是一个普通对象,因此obj instanceof Array返回false。

需要注意的是,instanceof运算符只能判断对象是否为数组类型,不能判断对象是否为对象类型。

Array.isArray()方法
可以使用Array.isArray()方法判断一个对象是否为数组。Array.isArray()方法返回一个布尔值,如果对象是数组,则返回true,否则返回falseconst arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true

const obj = { a: 1, b: 2 };
console.log(Array.isArray(obj)); // false
在上述代码中,Array.isArray(arr)返回true,因为arr是一个数组;而Array.isArray(obj)返回false,因为obj是一个普通对象。

需要注意的是,Array.isArray()方法是ES5新增的方法,如果在较旧的浏览器中使用,可能会出现兼容性问题。

Object.prototype.toString.call()方法
可以使用Object.prototype.toString.call()方法判断一个对象的类型。Object.prototype.toString.call()方法返回一个表示对象类型的字符串,格式为"[object 类型]",其中类型可以是任何JavaScript数据类型,包括数组和对象。

const arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr)); // [object Array]

const obj = { a: 1, b: 2 };
console.log(Object.prototype.toString.call(obj)); // [object Object]

11.js中如何判断promise对象

JavaScript中,可以使用以下几种方式来判断一个对象是否为Promise对象:

instanceof运算符
可以使用instanceof运算符判断一个对象是否为Promise对象,因为Promise对象是Promise类型的实例。instanceof运算符返回一个布尔值,如果对象是指定类型的实例,则返回true,否则返回falseconst promise = new Promise((resolve, reject) => {
  // ...
});

console.log(promise instanceof Promise); // true
在上述代码中,promise是一个Promise对象,因此promise instanceof Promise返回true。

需要注意的是,instanceof运算符只能判断对象是否为Promise类型,不能判断对象是否为Promise实例。

Promise.resolve()方法
可以使用Promise.resolve()方法判断一个对象是否为Promise对象。Promise.resolve()方法会将一个对象转换为Promise对象,并返回一个Promise对象。

如果传入的对象已经是Promise对象,则直接返回该对象,否则会创建一个新的Promise对象,将传入的对象作为该Promise对象的值进行解析。

const promise = Promise.resolve('hello');
const obj = { a: 1, b: 2 };

console.log(Promise.resolve(promise) === promise); // true
console.log(Promise.resolve(obj) instanceof Promise); // true
在上述代码中,Promise.resolve(promise)返回promise本身,因为promise已经是Promise对象;而Promise.resolve(obj)返回一个新的Promise对象,因为obj不是Promise对象。

判断对象是否有then方法
可以使用对象是否有then方法来判断该对象是否为Promise对象。Promise对象必须具有then方法,用于指定Promise对象的状态变化时的回调函数。

const obj = { a: 1, b: 2 };
const promise = new Promise((resolve, reject) => {
  // ...
});

console.log(typeof obj.then === 'function'); // false
console.log(typeof promise.then === 'function'); // true
在上述代码中,obj不是Promise对象,因此obj.then返回undefined;而promise是Promise对象,因此promise.then返回一个函数。

12.git查看当前分支

要查看当前所在的分支,可以使用以下命令:

git branch
执行该命令后,Git会列出所有本地分支,并在当前所在的分支前面添加一个星号。

例如,如果当前所在的分支是master,则执行git branch命令会得到以下输出:

* master
  dev
  feature-1
在上述输出中,*号表示当前所在的分支是master,dev和feature-1是其他本地分支。

需要注意的是,git branch命令只能查看本地分支,如果想要查看远程分支,需要使用git branch -r命令。如果想要查看本地和远程分支,可以使用git branch -a命令。

13.em和rem有什么区别

em和rem都是CSS中用于设置字体大小的单位,它们的区别在于相对于谁进行计算。

em是相对于父元素的字体大小进行计算的,如果没有设置父元素的字体大小,则会继承父元素的字体大小。例如,如果父元素的字体大小为14px,子元素的字体大小为1.5em,则子元素的字体大小为21px(14px * 1.5)。

rem是相对于根元素(即html元素)的字体大小进行计算的。这意味着,如果根元素的字体大小为16px,那么1rem就等于16px。例如,如果要设置一个字体大小为24px的元素,可以使用1.5rem(24px / 16px = 1.5)。

使用rem单位可以使网站在不同设备上的显示效果更加一致,因为它是相对于根元素的字体大小进行计算的,不受父元素的影响。但是,rem单位在某些老版本的浏览器中不被支持,因此需要进行兼容性处理。

需要注意的是,em和rem单位都可以用于设置除字体大小之外的其他CSS属性,例如margin、padding等。但是,由于em单位是相对于父元素的字体大小进行计算的,因此在设置其他CSS属性时需要注意父元素的字体大小。

14.浏览器解析和渲染过程

浏览器解析和渲染过程一般包括以下几个步骤:

解析HTML
浏览器首先会解析HTML文档,生成DOM树。DOM树表示HTML文档的结构,包括HTML标签、属性和文本内容等。

解析CSS
浏览器会解析CSS样式,生成CSSOM树。CSSOM树表示CSS样式的结构,包括选择器、属性和值等。

合并DOM树和CSSOM树
浏览器将DOM树和CSSOM树合并成一个渲染树(Render Tree),渲染树包括所有需要显示的元素和属性,但不包括不需要显示的元素,例如head、meta和script等。

布局和绘制
浏览器根据渲染树进行布局和绘制。布局(Layout)是指确定每个元素在屏幕上的位置和大小,绘制(Paint)是指将元素绘制到屏幕上。

显示页面
浏览器将渲染好的页面显示在屏幕上。

需要注意的是,以上过程是一个逐步迭代的过程。浏览器通常会在解析HTMLCSS时就开始生成DOM树和CSSOM树,然后不断地进行合并、布局和绘制,直到最终渲染出完整的页面。在这个过程中,浏览器会对页面进行优化和缓存,以提高页面的加载速度和性能。

15.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关键字定义一个类的继承关系,子类可以继承父类

16.前端高阶组件

前端高阶组件是指一个函数或者一个组件,它接收一个组件作为参数,并返回一个新的组件。这个新的组件具有了原来的组件的特性,并且在原来的基础上增加了一些新的功能或者特性。这个过程通常被称为“增强”或者“装饰”。

高阶组件通常用于以下几个方面:

复用组件逻辑:如果多个组件中具有相同的逻辑或者功能,可以将这些逻辑或者功能提取出来,封装成一个高阶组件,从而可以复用这些逻辑或者功能。

增强组件特性:如果一个组件需要增加某些特性或者功能,可以使用高阶组件对这个组件进行增强或者装饰,从而实现这些特性或者功能。

控制组件渲染:如果需要对组件的渲染进行控制,可以使用高阶组件对组件进行包装,从而实现对组件渲染的控制。

例如,以下是一个简单的高阶组件示例,它可以在组件渲染之前打印一条日志:

function withLogger(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log(`Component ${WrappedComponent.name} is mounted.`);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}
在上述代码中,withLogger是一个高阶组件,它接收一个组件作为参数,并返回一个新的组件。这个新的组件在组件渲染之前会打印一条日志,然后渲染原来的组件。

使用高阶组件可以使组件的逻辑更加清晰和简洁,提高代码的复用性和可维护性.

17.react如何自定义hooks

React中,自定义Hooks是一种用于封装可重用逻辑的方式,它可以让我们把组件之间共享的逻辑抽离出来,封装成一个可复用的函数。自定义Hooks是一个函数,它的名称应该以use开头,这样React就可以正确地识别它是一个Hook。

自定义Hooks可以使用所有的React Hook,包括useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef和useImperativeHandle等。

以下是一个简单的自定义Hook示例:

import { useState, useEffect } from 'react';

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

function MyComponent() {
  useDocumentTitle('My Component');
  return <div>My Component</div>;
}
在上述代码中,useDocumentTitle是一个自定义Hook,它接收一个title参数,并在组件渲染时设置document的title。MyComponent组件使用useDocumentTitle Hook来设置组件的标题。

需要注意的是,自定义Hooks必须遵循以下两个规则:

只能在函数组件或者其他自定义Hooks中使用,不能在class组件中使用。

Hook的名称必须以use开头,这样React才能正确地识别它是一个Hook。

通过自定义Hooks,可以将组件之间共享的逻辑抽离出来,提高代码的复用性和可维护性。同时,自定义Hooks也可以使代码更加清晰和简洁,让组件的逻辑更加集中和易于维护。

18.react中diff的原理

在React中,Diff算法是一种用于优化组件渲染的算法,它通过比较新旧两个虚拟DOM树的差异,最小化DOM操作的次数,从而提高了组件的渲染性能。

React中的Diff算法基于以下几个原则:

两个不同类型的元素会产生不同的树形结构。

开发者可以通过key属性来暗示哪些子元素在不同的渲染下能保持稳定。

React会对同一层级的同类元素进行比较,并且会尽可能地复用相同的DOM节点。

在进行Diff算法时,React会首先比较两个虚拟DOM树的根节点,如果节点类型不同,则直接替换整个节点。如果节点类型相同,则比较节点的属性和子节点。

在比较子节点时,React会先比较新旧两个虚拟DOM树的子节点的key值,如果key值相同,则说明这是同一个节点,React会复用旧的节点并更新它的属性。如果key值不同,则说明这是不同的节点,React会删除旧的节点并创建新的节点。

如果新的虚拟DOM树比旧的虚拟DOM树多了一些节点,则React会将这些新节点插入到旧节点之后;如果新的虚拟DOM树比旧的虚拟DOM树少了一些节点,则React会将这些多余的旧节点删除。

需要注意的是,Diff算法并不是完美的,它可能会出现一些性能问题。例如,如果虚拟DOM树比较复杂,Diff算法的时间复杂度可能会很高,导致组件的渲染性能下降。

19.react中setState的原理

React中,setState是一种用于更新组件状态的方法。它是异步的,也就是说,调用setState并不会立即更新组件状态,而是放到一个队列中,等到合适的时机才会更新组件状态。

setState的原理可以概括为以下几个步骤:

合并状态
调用setState时,React会将新的状态对象合并到原来的状态对象中,从而得到一个新的状态对象。

触发更新
React会将新的状态对象放到一个更新队列中,并触发组件的重新渲染。在重新渲染时,React会比较新旧两个虚拟DOM树的差异,最小化DOM操作的次数,从而提高了组件的渲染性能。

执行回调
如果调用setState时传入了回调函数,则会在组件更新完成后执行回调函数。

需要注意的是,由于setState是异步的,因此在调用setState之后,不能立即获取更新后的状态值。如果需要获取更新后的状态值,可以在回调函数中获取,或者使用setState的第二个参数,它是一个可选的回调函数,将在setState完成并且组件已经重新渲染时被调用。

另外,React还提供了一种叫做“函数式更新”的方式,可以用于更新状态依赖于先前状态的情况。在函数式更新中,setState可以接收一个函数作为参数,这个函数会接收先前的状态作为参数,并返回一个新的状态值。例如:

this.setState((prevState) => {
  return {
    count: prevState.count + 1
  };
});
在上述代码中,setState接收一个函数作为参数,这个函数会接收先前的状态作为参数,并返回一个新的状态值。

20.react中子组件render后,父组件会不会跟着一起render

React中,当子组件的状态或者属性发生变化时,React会重新执行子组件的render方法,生成新的虚拟DOM树,并将它与旧的虚拟DOM树进行比较,从而决定是否需要更新DOM树。如果子组件的虚拟DOM树与旧的虚拟DOM树相同,则React不会更新DOM树;如果子组件的虚拟DOM树与旧的虚拟DOM树不同,则React会更新DOM树,并触发父组件的重新渲染。

当父组件重新渲染时,React会重新执行父组件的render方法,生成新的虚拟DOM树,并将它与旧的虚拟DOM树进行比较,从而决定是否需要更新DOM树。

需要注意的是,当父组件重新渲染时,如果子组件的虚拟DOM树与旧的虚拟DOM树相同,则React不会更新DOM树,这也就意味着子组件的render方法不会被重新执行。因此,子组件的render方法是否被重新执行,取决于子组件的虚拟DOM树是否发生了变化。

如果子组件的虚拟DOM树没有发生变化,但是需要执行一些副作用操作(例如发送网络请求、订阅事件等),可以在子组件中使用componentDidUpdate方法来处理。componentDidUpdate会在组件更新完成后被调用,它接收两个参数prevProps和prevState,可以通过它们来判断组件的状态或者属性是否发生变化。

总之,在React中,当子组件的状态或者属性发生变化时,React会重新执行子组件的render方法,并决定是否更新DOM树;当父组件重新渲染时,React会重新执行父组件的render方法,并决定是否更新DOM树。

21.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方法用于返回一个目标对象的所有属性的描述符。它接收一个目标对象作为参数,返回一个对象。

22.useEffect和useLayoutEffect有什么区别?

useEffect和useLayoutEffect都是ReactHook函数,都可以用来在函数组件中执行副作用操作,如访问DOM、发起网络请求、订阅事件等。它们的区别在于执行时间和优先级。

useEffect会在浏览器渲染完成后异步执行,不会阻塞浏览器的渲染工作,所以它的执行时间会稍晚于useLayoutEffect。useEffect的优先级较低,不会影响页面的布局和绘制。

useLayoutEffect则会在浏览器完成布局和绘制之后同步执行,它的优先级较高,可以在页面更新前同步读取DOM的尺寸和位置等信息,从而避免出现页面闪烁等问题。

总之,如果需要在渲染后执行一些异步操作,可以使用useEffect;如果需要在渲染前同步操作DOM,可以使用useLayoutEffect。

23.为什么filber是可中断的呢

Fiber之所以是可中断的,是因为它采用了一种时间分片的机制。在React早期的版本中,当组件开始渲染时,会一直执行直到渲染完成,期间无法中断,这样会导致页面卡顿,用户体验不佳。

而Fiber则将组件的渲染过程拆分成多个任务单元,每个任务单元的执行时间不超过16ms,如果超过了16ms,React就会中断当前任务单元的执行,让浏览器先处理其他任务单元,这样就能够避免页面卡顿。

同时,Fiber还提供了优先级调度功能,可以根据任务的优先级来决定任务的执行顺序,从而更加灵活地控制页面的渲染。这样,在用户交互时,React可以优先执行高优先级的任务,提高响应速度,提升用户体验。

因此,Fiber的可中断性是为了提高页面的流畅度和用户体验。

24.有了解过filber吗

FiberReact中一种用于调度和执行组件更新的新的机制。它的出现是为了解决React早期版本中组件渲染过程中可能出现的性能问题,如长时间的任务阻塞、页面卡顿等。

Fiber的核心思想是将组件的渲染过程拆分成多个任务单元,每个任务单元的执行时间不超过16ms,如果超过了16ms,React会中断当前任务单元的执行,让浏览器先处理其他任务单元,这样就能够避免页面卡顿。

同时,Fiber还提供了优先级调度功能,可以根据任务的优先级来决定任务的执行顺序,从而更加灵活地控制页面的渲染。这样,在用户交互时,React可以优先执行高优先级的任务,提高响应速度,提升用户体验。

总之,Fiber的出现是为了提高React的性能和用户体验,它采用了一系列新的机制来优化组件的渲染过程,从而使得React能够更加高效地执行组件更新。

25.为什么usecallback和usememo过多影响性能

useCallback和useMemo这两个ReactHook函数都是用来优化组件性能的,它们可以避免不必要的渲染,减少组件的计算量,提高应用的性能。但是,过多地使用这两个函数也可能会影响应用性能,原因如下:

过多的依赖项:使用useCallback和useMemo时,需要传入依赖项,当依赖项发生变化时,才会重新计算。如果依赖项过多或者不合理,会导致组件在不必要的情况下重新渲染,从而降低性能。

过度优化:过度地使用useCallback和useMemo可能会使代码变得复杂,增加维护成本,还可能会导致过度优化,从而产生逆向效果。

过度计算:useCallback和useMemo的实现需要消耗一定的计算资源,如果过度使用,会增加计算负担,从而降低性能。特别是在需要计算大量数据或者处理复杂逻辑时,过度使用这两个函数可能会导致性能问题。

因此,在使用useCallback和useMemo时,需要根据具体情况进行权衡和取舍,避免过度使用。在实际开发中,可以结合性能测试工具对应用进行性能优化,找到性能瓶颈并进行优化,从而提高应用的性能。

26.usememo和usecallback有什么区别?

useMemo和useCallback都是ReactHook函数,它们的作用都是优化函数组件的性能,避免组件的不必要渲染。它们之间的区别如下:

返回值不同:useMemo返回一个值,而useCallback返回一个函数。

使用场景不同:useMemo用于缓存计算结果,避免重复计算,通常用于处理耗时的计算和大量数据的计算。而useCallback用于缓存函数,避免函数的重复创建,通常用于传递给子组件的回调函数或effect中的依赖项。

传入参数不同:useMemo传入一个函数和一个依赖项数组,只有当依赖项数组中的值发生变化时才会重新计算。而useCallback传入一个函数和一个依赖项数组,只有当依赖项数组中的值发生变化时才会返回一个新的函数。

优化的方式不同:useMemo是通过缓存计算结果来优化性能的,而useCallback是通过缓存函数来优化性能的。

因此,在实际使用中,如果需要缓存计算结果,可以使用useMemo;如果需要缓存函数,可以使用useCallback。但是,需要注意传入的依赖项数组,避免不必要的重新计算和函数的重复创建,从而提高组件的性能。

27.函数组件和类组件区别

函数组件和类组件是React中两种常见的组件类型,它们的区别如下:

语法不同:函数组件是使用JavaScript函数定义的,而类组件是使用ES6类定义的。

内部状态管理方式不同:类组件可以使用内部状态(state)来管理组件的状态数据,而函数组件不支持内部状态。在React 16.8版本之后,函数组件可以使用Hooks来管理内部状态。

生命周期不同:类组件中有多个生命周期方法,如componentDidMount、componentDidUpdate、componentWillUnmount等,可以在这些方法中执行特定的逻辑。而函数组件中只有两个生命周期方法,分别是componentDidMount和componentWillUnmount,可以使用useEffect Hook来实现类似的功能。

对this的使用不同:在类组件中,this指向组件实例,可以使用this来访问组件的属性和方法。而在函数组件中,没有实例化对象,因此不能使用this。

性能不同:函数组件相比类组件更加轻量级,没有额外的实例化过程,通常会比类组件渲染更快,并且也更容易进行代码优化。

总之,函数组件和类组件各有优缺点。在实际开发中,可以根据具体需求选择合适的组件类型。如果需要管理内部状态和复杂的逻辑,可以选择类组件;如果只需要简单的渲染逻辑和数据展示,可以选择函数组件。

28.react里的key

React中的key是用来标识组件的唯一性的属性,它主要用于优化组件的渲染性能。Key可以帮助React更加准确地识别哪些组件需要更新,哪些组件需要重新渲染,从而避免不必要的渲染和提高应用性能。

具体来说,当一个列表中的组件需要重新排序或者删除、添加某些项时,React会根据组件的key属性来确定哪些组件需要重新渲染。如果不提供key属性,React会默认使用数组的下标作为key,但是这样可能会导致不必要的渲染和bug,因此在实际开发中,应该为每个组件提供唯一的key属性。

在使用key属性时,需要注意以下几点:

key应该是每个列表项唯一的标识,通常使用字符串或数字作为key。

key应该稳定不变,不应该随着渲染顺序的改变而改变。

key应该在同一列表中具有唯一性,不应该出现重复的key。

总之,key是React中一个重要的属性,它可以帮助我们优化组件的渲染性能,避免不必要的渲染和提高应用性能。在实际开发中,需要注意为每个组件提供唯一的key属性,并确保key的稳定性和唯一性。

29.diff算法

Diff算法是React中用于优化组件渲染性能的核心算法之一。其主要作用是比较前后两次组件的状态和属性,找出需要更新的部分,避免不必要的渲染,从而提高应用性能。

Diff算法的过程如下:

首先比较组件的根节点,如果根节点类型不同,则直接卸载旧节点,重新渲染新节点;如果根节点类型相同,则继续比较子节点。

然后比较子节点,如果子节点类型不同,则直接卸载旧节点,重新渲染新节点;如果子节点类型相同,则继续比较子节点的属性和状态。

最后比较子节点的属性和状态,如果有变化,则更新对应的DOM节点。

对于子节点的列表,React使用key属性来确定哪些节点需要更新,哪些节点需要添加或删除。

Diff算法的优化策略如下:

尽可能地复用已有的DOM节点,避免重复创建和销毁DOM节点。

将多个更新操作合并为一个操作,减少DOM操作次数。

对于列表中的子节点,使用唯一的key属性来标识节点,以便快速地识别需要更新的节点。

总之,Diff算法是React中用于优化组件渲染性能的核心算法。在实际开发中,需要注意使用合适的key属性、避免不必要的渲染和减少DOM操作次数,从而提高应用性能。

30.webpack热更新,它的原理是什么

Webpack热更新是指在应用程序运行时,修改代码后,Webpack可以自动将修改后的代码注入到应用程序中,而无需手动刷新页面,从而实现快速开发和调试的目的。Webpack热更新的原理如下:

Webpack使用Webpack Dev Server来启动本地服务器,监听应用程序的文件变化。

当文件发生变化时,Webpack会重新编译文件,并将编译后的代码通过Websocket协议发送到浏览器端。

浏览器收到新的代码后,使用HMRHot Module Replacement)技术将变化的代码注入到应用程序中,从而实现热更新。

在注入新代码之前,HMR会先将旧代码从应用程序中卸载,然后再将新代码注入到应用程序中。

在注入新代码之后,HMR会重新执行变化的模块,以便应用程序能够正确地处理新代码的变化。

总之,Webpack热更新是通过Webpack Dev ServerWebsocket协议和HMR技术实现的。它可以大大提高开发效率,减少手动刷新页面的次数,并且可以保持应用程序状态的稳定性。

31.怎么提升webpack的打包速度

Webpack是一个强大的打包工具,但是在处理大型项目时,Webpack的打包速度可能会变得很慢。为了提高Webpack的打包速度,可以采取以下几种方法:

使用缓存:Webpack可以使用缓存功能,将之前的编译结果保存到内存中,下次编译时可以直接使用,从而减少编译时间。可以使用babel-loader等插件来实现缓存功能。

减少文件搜索范围:Webpack在查找文件时,会搜索整个项目目录,这可能会导致很长的搜索时间。因此,可以通过配置resolve.modules来减少搜索范围,只搜索指定的目录。

按需编译:Webpack可以使用Tree ShakingCode Splitting等技术,只编译项目中实际使用的代码,减少编译时间。

多线程编译:Webpack可以使用HappyPack等插件,将编译任务分解成多个子任务,并行处理,从而提高编译速度。

使用DllPluginDllPlugin可以将一些不经常变化的第三方库单独打包成一个文件,减少每次打包时的编译时间。

优化LoaderLoaderWebpack中用于处理各种文件类型的工具,可以通过配置Loader选项,减少不必要的处理,从而提高打包速度。

使用Webpack 5Webpack 5相比Webpack 4在打包速度上有很大的提升,可以考虑升级到Webpack 5。

总之,在实际开发中,可以根据具体情况采用以上方法来提高Webpack的打包速度,从而提高应用的性能和开发效率。

32.webpack的构建流程

Webpack的构建流程主要分为以下几个步骤:

初始化:Webpack会读取配置文件,初始化Webpack的配置参数,如入口、输出路径、LoaderPlugin等。

编译:将源代码转换成抽象语法树(AST),并对代码进行静态分析、依赖分析、模块转换等操作,生成Module对象。

输出:将编译后的Module对象通过输出配置写入到指定的文件中,生成最终的打包文件。

在具体的构建流程中,Webpack会执行以下几个核心步骤:

解析模块:Webpack会从配置的入口模块开始,递归地解析模块的依赖关系,形成依赖图。在解析模块时,Webpack会使用配置的Loader对模块进行编译,将模块转换成Webpack可以理解的格式。

创建ChunkWebpack会根据依赖图,将模块组合成一个或多个Chunk,其中每个Chunk包含多个模块,通常是一个输出文件。Webpack会根据配置的EntryCode Splitting策略来生成Chunk。

加载模块:Webpack会根据模块的依赖关系,以深度优先的顺序逐个加载模块。在加载模块时,Webpack会从缓存中读取已经编译过的模块,如果没有缓存,则会调用配置的Loader对模块进行编译,最终将编译后的模块返回给Webpack。

将模块添加到Chunk:当所有依赖的模块都加载完成后,Webpack会将编译后的模块添加到Chunk中,最终生成完整的Chunk。

输出文件:Webpack会将生成的Chunk输出到指定的目录下,生成最终的打包文件。在输出文件时,Webpack会使用Plugin对输出结果进行优化和处理。

33.http1.0和http2.0的区别

HTTP/1.0HTTP/1.1是基于文本的协议,而HTTP/2.0是二进制协议。HTTP/2.0在传输数据时使用了新的二进制格式,这使得传输速度更快、效率更高。

以下是HTTP/1.0HTTP/2.0的一些主要区别:

多路复用:HTTP/2.0可以在同一连接上同时传输多个请求和响应,而HTTP/1.0HTTP/1.1每次只能处理一个请求和响应。这个特性可以提高网站的加载速度,减少延迟。

二进制协议:HTTP/2.0在传输数据时使用了新的二进制格式,这使得传输速度更快、效率更高。

头部压缩:HTTP/2.0使用HPACK算法对头部信息进行压缩,减少了传输的数据量,从而提高了性能。

服务器推送:HTTP/2.0支持服务器推送,可以在客户端请求之前将相关资源主动推送给客户端,提高了用户体验。

流量控制:HTTP/2.0支持流量控制,可以在同一连接上控制每个请求和响应的流量,防止网络拥塞。

总之,HTTP/2.0相比于HTTP/1.0HTTP/1.1,在传输速度、效率和性能方面都有了很大的提升,可以更好地满足现代Web应用的需求。

34.怎么去缓存协商缓存的配置

缓存协商是指浏览器和服务器之间的一种通信机制,用于确定资源是否需要缓存。在缓存协商中,浏览器会向服务器发送一个请求,询问资源是否有更新,如果有更新,则服务器返回新的资源;如果没有更新,则服务器返回一个304 Not Modified响应,告诉浏览器可以使用缓存的资源。

可以通过以下几种方式来配置缓存协商:

设置ExpiresCache-Control响应头:可以设置ExpiresCache-Control响应头,告诉浏览器在多长时间内可以缓存资源。这样可以减少不必要的请求,提高应用性能。

设置Last-ModifiedETag响应头:可以设置Last-ModifiedETag响应头,用于记录资源的最后修改时间和标识资源的唯一性。当浏览器请求资源时,会将这些信息发送给服务器,服务器可以根据这些信息判断资源是否有更新。

可以通过以下几种方式来禁用缓存协商:

设置Cache-Control:no-cache和Pragma:no-cache响应头:可以设置Cache-Control:no-cache和Pragma:no-cache响应头,告诉浏览器不要缓存资源。

在URL中添加随机参数:可以在URL中添加随机参数,使每次请求都是一个新的URL,从而禁用缓存协商。

总之,在实际开发中,可以根据具体需求来配置缓存协商。需要注意的是,缓存协商可以提高应用性能,但是如果配置不当,可能会导致缓存混淆、缓存降级等问题,因此需要仔细调试和测试。

35.这两种缓存的场景有哪些,什么样的场景下用的比较多?比如一个网页什么情况下适合做强缓存?什么情况下适合做协商缓存

强制缓存和协商缓存都是HTTP缓存机制的一部分,它们在不同的场景下可以提高应用程序的性能。一般来说,强制缓存适用于静态资源,如图片、样式等不易改变的资源,而协商缓存适用于动态资源,如HTMLAPI等经常变化的资源。

强制缓存的场景:

静态资源:如图片、样式、脚本等,这些资源通常不会经常变化。

需要快速响应:如首页、静态页面等,这些页面需要快速响应,减少加载时间。

高并发场景:如热门商品、热门文章等,这些资源需要快速加载,以提高用户体验。

协商缓存的场景:

动态资源:如HTMLAPI、数据等,这些资源经常变化。

需要更新资源:如新闻、文章等,这些资源需要频繁更新,以保持内容的新鲜度。

需要控制缓存策略:如需要控制缓存时间、缓存的范围等,这些情况下需要使用协商缓存。

对于网页,适合做强制缓存的情况包括:

首页:通常情况下,首页是不需要频繁更新的,可以使用强制缓存来提高加载速度。

静态资源:如图片、样式、脚本等,这些资源通常不会经常变化,可以使用强制缓存来提高加载速度。

对于网页,适合做协商缓存的情况包括:

动态资源:如HTMLAPI、数据等,这些资源经常变化,需要使用协商缓存。

36.强缓存和协商缓存

强制缓存和协商缓存都是HTTP缓存机制的一部分,它们都是用来减少网络请求,提高应用性能的方法。

强制缓存
强制缓存是通过在响应头中使用ExpiresCache-Control两个字段来实现的。当客户端第一次请求资源时,服务器会将资源的过期时间一起返回给客户端,而客户端在再次请求资源时,会先判断资源是否过期,如果没有过期,则直接使用本地缓存。

ExpiresHTTP/1.0提出的一种缓存机制,它通过设置资源的到期时间来控制缓存。但由于时间是由服务器提供的,客户端和服务器时间可能不同步,因此有可能会出现问题。

Cache-ControlHTTP/1.1提出的一种缓存机制,它通过设置max-age的值来控制缓存。max-age表示资源的有效期,单位为秒,例如max-age=3600表示资源在1个小时后过期。

协商缓存
协商缓存是通过在响应头中使用Last-ModifiedETag两个字段来实现的。当客户端第一次请求资源时,服务器会将资源的Last-ModifiedETag一起返回给客户端,而客户端在再次请求资源时,会先发送If-Modified-SinceIf-None-Match两个字段给服务器,告诉服务器自己上一次请求时的资源最后修改时间和ETag值,如果服务器判断资源没有被修改,则返回304 Not Modified响应,客户端直接使用本地缓存。

Last-Modified表示资源的最后修改时间,ETag表示资源的唯一标识符。当客户端再次请求资源时,如果Last-ModifiedETag与服务器上的值一致,则说明资源没有被修改过,可以直接使用缓存。

总之,在实际开发中,可以根据具体需求来选择

37.说下事件循环

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

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

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

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

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

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

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

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

总之,事件循环是JavaScript实现异步编程的核心机制,它通过消息队列来实现异步操作和事件回调的处理,使得JavaScript可以处理大量的并发请求,提高了应用程序的性能。

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

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

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

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

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

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

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