前端面试题(更新中…)

1.说说对闭包的理解,优缺点,及应用场景

什么是闭包

闭包是指一个函数内部定义的函数,并且该函数可以访问外部函数的变量,简单来说就是一个函数及其相关的引用环境的组合体

闭包的优缺点

优点:
1.数据封装:闭包可以将函数内的变量与函数绑定起来,形成一个封闭的环境,外部无法直接访问这些变量,只能通过闭包提供的接口来访问和修改
2.变量保持:闭包内的变量可以在函数调用之间保持状态,即使函数退出后闭包仍然保持可以访问和修改这些变量,可以用于实现类似于全局变量的效果
3.高阶函数:闭包是函数式编程中的重要概念,可以作为参数传递给其他函数,实现函数的咳里化,延迟计算等高级特性
缺点:
1.内存占用:闭包会导致函数内的变量无法被垃圾回收机制回收,可能会占用较多的空间
2.性能影响:由于闭包涉及变量的引用和作用域链的查找,会导致函数的执行效率

闭包的应用场景

1.封装:使用闭包可以将一些私有变量封装起来,只暴露必要的接口,实现信息隐藏和安全性
2.记忆化:通过闭包可以实现函数的记忆化,提供函数的执行效率,避免重复计算
3.回调函数:闭包可以作为回调函数传递给其它函数,实现回调机制
4.柯里化:通过闭包可以实现函数的柯里化,将多个参数的函数转化为接收单个参数的函数序列,方便函数组合和复用
5.防抖节流

2.事件循环的理解,及应用理解

事件循环的理解

事件循环就是一个执行机制,JavaScript是一个单线程的运行环境,不满足我们的需求,所以事件循环机制就会把等待执行的事件放入到一个队列里循环执行

为什么js要使用单线程

js是单线程,如果是多线程的话会带来复杂的同步问题,比如:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变

应用理解

1.同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
2.异步任务:异步执行的任务 比如:ajax网络请求,setTimeout等
3.宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的对一些高实时性的需求就不太符合
常见的宏任务有:serTimeout,UI事件,postmessage
4.微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后,当前宏任务结束之前
常见的微任务有:promise.then,node.js
·扩展:
宏任务和微任务的执行机制:执行一个宏任务,如果遇到微任务就将他放到微任务的事件队列中,当前宏任务执行完成之后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完
5.Dom事件

宏任务和微任务的代码案例

console.log('script start');
setTimeout(function() {
  console.log('timeout1');
}, 10);
new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})
console.log('script end');

3.js类型检验的方法

1.typeof:用于判断基本数据类型
使用方式:typeof(表达式)和typeof变量名,第一种是对表达式做运算,第二种是对变量做运算
typeof运算符的返回类型为字符串包括一下几种:
a.undefined:“未定义的变量或值”
b.boolen:“布尔类型的的变量或值”
c.string:“字符串类型的的变量或值”
d.number:“数字类型的的变量或值”
e.object:“对象类型的的变量或值”
f. function:“函数类型的的变量或值”
总结:
typeof运算符用于判断对象的类型,但是对于一些创建的对象,它们都会返回’object’,有时我们需要判断该实例是否为某个对象的实例,那么这个时候需要用到instanceof运算符。
2.instanceof:用于引用数据类型的判断。所有引用数据类型的值都是Object的实例。目的是判断一个对象在其原型链上是否存在构造函数的prototype属性
总结:
instanceof不仅能检测构造对象的构造器,还检测原型链。instanceof要求前面是个对象,后面是一个构造函数。而且返回的是布尔型的,不是true就是false。
3.array.isarray():可以用于判断数组类型
总结:
isArray是一个静态方法,使用Array对象(类)调用,而不是数组对象实例。其中Array.prototype 也是一个数组,Array.isArray 优于 instanceof。
4.Object.prototype.toString.call():
判断某个对象值属于哪种内置类型, 最靠谱的做法就是通过Object.prototype.toString方法。object.prototype.toString()输出的格式就是[object 对象数据类型]。
5.constructor:返回实例对象的构造函数
6.symbol.stringTag:自定义类型

typeof的代码案例

console.log(typeof a);    //'undefined'    
console.log(typeof(true));  //'boolean'    
console.log(typeof '123');  //'string'    
console.log(typeof 123);   //'number'    
console.log(typeof NaN);   //'number'    
console.log(typeof null);  //'object'       
var obj = new String();    console.log(typeof(obj));    //'object'    
var  fn = function(){};    console.log(typeof(fn));  //'function'    
console.log(typeof(class c{}));  //'function'

4.面向对象编程的方式的理解

面向对象编程的理解就是:以事物(对象)为中心的编程思想,即考虑目标实现所涉及的对象,通过对象间的相互作用来实现目标,以JAVA为代表;
优点:

易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护。面向对象技术具有程序结构清晰,自动生成程序框架,实现简单,可有效地减少程序的维护工作量,代码重用率高,软件开发效率高等优点。

缺点:

因为类调用时需要实例化,开销比较大,比较消耗资源,性能比面向过程低。

面向过程编程的理解:以过程为中心的编程思想,即考虑目标实现的过程,按照步骤进行编程,一步一步实现目标,以为C语言为代表;

面向过程优点:

性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点:

没有面向对象易维护、易复用、易扩展。

面向对象编程的三大基本特性:封装,继承,多态
封装:

封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

继承:

继承,指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过 “继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用父类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。

多态:

是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

五大基本原则:SPR, OCP, LSP, DIP, ISP

单一职责原则SRP(Single Responsibility Principle)

1.一个程序只做好一件事
2.如果功能过于复杂就拆分开,每个部分保持独立

开放封闭原则OCP(Open-Close Principle)

1.对扩展开放,对修改封闭
2.增加需求时,扩展新代码,而非修改已有代码
3.这是软件设计的终极目标

里式替换原则LSP(the Liskov Substitution Principle LSP)

1.子类能够覆盖父类
2.父类能出现的地方子类就能出现
3.JS中较少使用(弱类型&继承使用较少)

依赖倒置原则DIP(the Dependency Inversion Principle DIP)

1.面向接口编程,依赖于抽象而不依赖于具体
2.使用方只关注接口而不关注具体类的实现
3.JS中使用较少(没有接口&弱类型)

接口分离原则ISP(the Interface Segregation Principle ISP)

1.保持接口的单一独立,避免出现“胖接口”
2.JS中没有接口,使用较少
3.类似于单一接口,这里更关注接口

5.封装一个使用递归方式的深拷贝方法deepClone?

// 递归的方式实现深拷贝

// 创建一个函数,传入参数是需要深拷贝的对象
function deepclone(obj) {
// 判断参数是对象还是数组
let isArr = Array.isArray(obj);
// 根据传入的参数声明克隆的类型
let objClone = isArr ? [] : {}
// 遍历需要拷贝的对象的属性
for (const key in obj) {
// console.log(obj[key]);
// 判断属性的值是否为对象
if (typeof obj[key] === ‘object’) {
// 是对象递归复制
objClone[key] = deepclone(obj[key])
} else {
// 不是对象直接复制
objClone[key] = obj[key]
}
}
return objClone
}

console.log(deepclone(obj));

6.对自定义hook的理解,简易版的useState?

什么是hook?

hook是react的新增特性,他通常与函数式组件同时使用,可以使函数式组件在不编写class的情况下,可以拥有class组件的状态,生命周期,引用等功能

常用的有哪些hook?

useState 状态管理
useEffect 生命周期
useContext 跨组件数据传递
useRef 组件引用

什么是自定义hook?

自定义hook其实就是自定义函数,与我们写函数组件非常类似,自定义的hook组件的命名与系统的hooks一样,需要以use开头,函数内部可以调用其他的hook

简易版useState代码

let _state; // 存储 state 的变量
 
function useState(initialValue) {
  _state = _state || initialValue; // 如果 _state 未定义,则使用初始值
 
  function setState(newState) {
    _state = newState;
    renderComponent(); // 更新 state 后重新渲染组件
  }
 
  return [_state, setState];
}
 
function renderComponent() {
  const [count, setCount] = useState(0);
  console.log(`render: ${count}`);
  setCount(count + 1);
}
 
renderComponent();

7.redux的实现原理

什么redux?

redux是一个js库,一个状态容器
作用:集中式管理react应用中多个组件共享的状态

(1)它可以和 react,vue,angular 等一起使用,但一般都和 react一起使用 。
(2)Redux 可以 解耦 React(View层)于数据管理和对数据的操作,保持View层的纯净,
让每一个组件的职责划分更加清楚,同时又降低了React数据传递的难度和不可控性
(3)Redux 采用了中间件机制,既保证了代码的简洁,又增加了可扩展性。

redux的三大原则?

1.单一数据源

整个应用的state都被存储到一个状态树里面,并且这个状态数,只存在于唯一的store中

2.state是只读的

唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象

3.使用纯函数来执行修改

为了描述action如何改变state tree,需要编写reducers

redex的核心原理

1.将应用的状态统一放到state中,由store来管理state。
2.reducer的作用是返回一个新的state去更新store中对应的state。
3.按redux的原则,UI层每一次状态的改变都应通过action去触发,action传入对应的reducer 中,reducer返回一个新的state更新store中存放的state,这样就完成了一次状态的更新
4.subscribe是为store订阅监听函数,这些订阅后的监听函数是在每一次dipatch发起后依次执行
5.可以添加中间件对提交的dispatch进行重写

redex的关键功能分析

1.Store:集中状态管理中心

(1)store就是保存数据的地方,当有数据改变时,store会调用reducer进行处理,完事后通知View层调用getState更新
(2)Redux提供createStore这个函数,用来生成Store,内部接收reducer

import {createStore} from 'redux'
const store=createStore(reducer);

2.View层:由组件构成视图层

(1)接收数据时,调用getState( )接收Store的newState,并重新渲染更新视图
(2)用户操作时,通过事件触发制造action并派发 来通知Store来更新state

// 接收 State
const state=store.getState()

3.ActionCreator:一个工厂函数,负责产生action对象

(1)与View层的事件进行关联,一旦有通知就制造action对象
(2)action = {
type: action类型,
data: 事件携带的数据(可有可无)
}

const action={
  type:'ADD_TODO',
  payload:'redux原理'
}

4.dispatch:派发action,方法使用了中间件机制
(1)store.dispatch()是view发出Action的唯一办法
(2)store.dispatch接收一个action作为参数,将它发送给store来通知 store调用reducer改变state。
(3)dispatch 时,还可以在中间插入副作用操作(网络请求等),其内部会调用中间件进行处理

const action={
  type:'ADD_TODO',
  payload:'redux原理'
}
store.dispatch(action)
// 或者直接
store.dispatch({
  type:'ADD_TODO',
  payload:'redux原理'
})

5…Reducer:处理action,再把处理完成后的newState返回给Store

(1)接收两个参数(preState, action),第一个参数是之前的旧数据或初始数据,第二个参数是action
(2)处理完的State的引用地址必须为新的内存地址,否则不会触发更新**(重要)
(3)当reducer过多时,可以使用combineReducers进行整合处理
注意:Reducer必须是一个纯函数,只要传入参数相同,返回计算得到的下一个 state 就一定相同。
没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

const reducer = (preState = initialState, action)=>{
  switch(action.type){
    case ADD_TODO:
        return newState;
    default return state
  }
}

工作原理:
前端面试题(更新中…)_第1张图片

8.扩展运算符都有哪些作用,详细介绍

1.数组的拼接和复制使用扩展运算符可以将两个数组拼接成一个新数组,例如:

const arrl =[1,2,3]: const arr2 =[4,5,6] const newArr = [...arrl, ...arr2]; console.log(newArr); // [1,2,3,4,5,6]

还可以使用扩展运算符将一个数组复制到另一个数组中

const arrl =[1,2,3]: const arr2 = [...arrl]; console.log(arr2); //[1,2,3]

2.函数的参数传递使用扩展运算符可以方便地将一个数组作为函数的多个参数传递

function sum(a, b, c) {return a +b +c; const arr =[l,2,3];console.log(sum(...arr)); // 6

3.对象的合并使用扩展运算符可以方便地将两个对象合并成一个新对象。

const objl ={a: 1,b: 2}; const obj2 = { c: 3,d: 4}; const newObj= { ...obj1, ...obj2 }; console.log(newObj); // { a: 1, b: 2,c: 3,d: 4};

同时,如果合并的两个对象存在同名属性,后面的对象会覆盖前面的对象。

const obj1 = {a: 1,b: 2}; const obj2 = { b: 3,c: 4}; const newObj = { ...obj1, ...obj2 }; consolc.log(newObj); // { a: 1,b: 3,c: 4}

4.函数的返回值

使用扩展运算符可以方便地将一个数组或对象作为另一个数组或对象的部分返回值。

function getUser(){ const user = {na'Tom',age:18,address:'beijing',phone:'123456'}

9.React性能优化的手段有哪些?

1.使用纯组件

如果 React 组件为相同的状态和 props 渲染相同的输出,则可以将其视为纯组件。
对于像 this 的类组件来说,React 提供了 PureComponent 基类。扩展 React.PureComponent 类的类组件被视为纯组件。
它与普通组件是一样的,只是 PureComponents 负责 shouldComponentUpdate——它对状态和 props 数据进行浅层比较(shallow comparison)
如果先前的状态和 props 数据与下一个 props 或状态相同,则组件不会重新渲染。

2.使用react.memo进行组件记忆

React.memo 是一个高阶组件。
它很像 PureComponent,但 PureComponent 属于 Component 的类实现,而“memo”则用于创建函数组件。
这里与纯组件类似,如果输入 props 相同则跳过组件渲染,从而提升组件性能。
它会记忆上次某个输入 prop 的执行输出并提升应用性能。即使在这些组件中比较也是浅层的。
你还可以为这个组件传递自定义比较逻辑。
用户可以用自定义逻辑深度对比(deep comparison)对象。如果比较函数返回 false 则重新渲染组件,否则就不会重新渲染。

3.使用 shouldComponentUpdate 生命周期事件

这是在重新渲染组件之前触发的其中一个生命周期事件。
可以利用此事件来决定何时需要重新渲染组件。如果组件 props 更改或调用 setState,则此函数返回一个 Boolean 值。
在这两种情况下组件都会重新渲染。我们可以在这个生命周期事件中放置一个自定义逻辑,以决定是否调用组件的 render 函数。
这个函数将 nextState 和 nextProps 作为输入,并可将其与当前 props 和状态做对比,以决定是否需要重新渲染。

4.懒加载组件

导入多个文件合并到一个文件中的过程叫打包,使应用不必导入大量外部文件。
所有主要组件和外部依赖项都合并为一个文件,通过网络传送出去以启动并运行 Web 应用。
这样可以节省大量网络调用,但这个文件会变得很大,消耗大量网络带宽。
应用需要等待这个文件的加载和执行,所以传输延迟会带来严重的影响。
为了解决这个问题,我们引入代码拆分的概念。
像 webpack 这样的打包器支持就支持代码拆分,它可以为应用创建多个包,并在运行时动态加载,减少初始包的大小。
为此我们使用 Suspense 和 lazy

5.不要使用内联函数定义

6.箭头函数与构造函数中的绑定

7.避免使用内联样式

8.优化react中的条件渲染

9.组件的不可变数据结构

10.事件节流和防抖

11.不要在render方法中导出数据

12.列表渲染的时候加key

13.在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有改变的话,不重复执行

14.如果是类组件,事件函数在Constructor中绑定bind改变this指向;

10.找出数组[1,2,3,4,5,3,2,2,4,2,2,3,1,3,5] 中出现次数最多的数,并统计出现多少次,编写个函数?

前端面试题(更新中…)_第2张图片

10.说说对react的理解,有哪些特性

理解

React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟 DOM 来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流
组件可以是一个函数或者是一个类,接受数据输入,处理它并返回在 UI 中呈现的 React 元素
组件:帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
react 类组件使用一个名为 render() 的方法或者函数组件return,接收输入的数据并返回需要展示的内容

特性

1.JSX 语法
2.单向数据绑定
3.虚拟 DOM
4.声明式编程
声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做。它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件
5.Component
Component在 React 中,一切皆为组件。通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件
6.生态系统丰富
7.虚拟dom:使用虚拟dom技术进行页面渲染,可以有效的减少页面重绘操作,提高性能
8.组件化开发:提高复用率

11.说说Real DOM和Virtual DOM的区别?优缺点?

区别:

真实的dom:在页面渲染出的每个节点都是一个真实的DOM结构
虚拟dom:本质是以js对象形式存在的,对DOM的描述,
Virtual DOM 是一个轻量级的js对象,它最初只是real DOM的副本,也是一个节点树,它将元素、它们的属性和内容作为该对象及其属性。
Virtual DOM(虚拟dom工作过程有3个步骤):
每当底层数据发生变化时,整个UI都将在虚拟dom中重新渲染;
计算之前虚拟dom与新的虚拟dom之间的差异;
完成计算后,将只用实际更新的内容更新真实的DOM.

1.虚拟dom不会进行重绘和回流,而真实dom会频繁重排与重绘
2.虚拟dom的总损耗是”虚拟dom的增删改+真实dom的差异增删改+重排“;真实dom的消耗是”真实dom全部增删改+重排“

优缺点

真实dom
优点:
​ 1. 直接操作HTML,易用
​ 缺点:
​ 1. 解析速度慢,效率低,内存占用量高
​ 2. 性能差:频繁操作真实DOM,导致重绘、回流

2.虚拟dom
​ 优点:
​ 1. 减少真实dom的频繁更新,减少重绘回流、占用内存少
​ 2. 跨平台:一套react代码可以多端运行
​ 缺点:
​ 1. 页面首次渲染时,由于多一层虚拟dom的计算,速度比正常慢些

12.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?

React 生命周期主要包含 3 个阶段,分别是初始化阶段、运行中阶段、销毁阶段,也就是创建阶段、更新阶段、卸载阶段。在 React 不同的生命周期里会依次触发不同的钩子函数

1.初始化

在组件初始化阶段会执行
constructor (用来定义状态,或者是用来放一些this的方法的)
static getDerivedStateFromProps() ----将来会使用(定义一个状态,这个状态将来会使用)
componentWillMount()----componentWillMount()会在17版本后启用,使用static
getDerivedStateFromProps()
render()
componentDidMount()(组件挂载结束)

2.更新阶段

props或state的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
componentWillReceiveProps()(属性发生改变)
static getDerivedStateFromProps()(属性发生改变触发)
.shouldComponentUpdate() // react性能优化第二方案 (控制重新渲染的声明周期)
componentWillUpdate() //将要更新
render()渲染
getSnapshotBeforeUpdate() (快照)
componentDidUpdate()(组件更新结束)

3.卸载阶段

componentWillUnmount()将要卸载

13.说说React中setState执行机制?

setState是异步的
1 .调用setState是不会立即更新的
2 .所有组件使用的是同一套更新机制,当所有组件didmount之后,父组件didmount,然后统一执行更新
3 .更新时会把每个组件的更新合并,每个组件只会触发一次更新后的生命周期

异步函数和原生事件中的setState
1 .用setTimeout模拟异步和回调接口执行情况
2 .在这种情况下,setState会同步更新的

执行state流程
1 .将setState传入的partialState参数存储在当前组件实例的state暂存队列中
2 .判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列中去
3 .如果未处于批量更新状态,将批量更新状态标识未true,用事务再次调用前一步的方法,保证当前组件加入了待更新的组件队列中去
4 .调用事务的waper方法,遍历待更新组件队列依次执行更新
5 .执行生命周期componentWillReceiveProps
6 .将组件的state和暂存队列中的state进行合并,得到最新的state对象,并将队列置为空
7 .执行生命周期componentShouldUpdate,根据返回值判断是否要更新。那这里不更新的时候,值还会倒退回去吗?
8 .执行生命周期componentWillUpdate
9 .执行真正的更新 render
10 .执行生命周期comonentDidUpdate
11 .当上一次更新机制执行完毕,以生命周期为例,所有组件,最顶层组件的didmount之后将isBranchUpdate设置为false,执行之前累积的setState
总结:
一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state
当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用,可以通过点击按钮触发onclick事件,执行this.setState方法更新state状态,然后重新执行render函数,从而导致页面的视图更新

14.React组件之间如何通信?

  1. 父组件向子组件通讯:
    父组件可以向子组件传入props的方式,向子组件进行通讯。
  2. 子组件向父组件通讯:
    props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中。
  3. 兄弟组件通信:
    兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递
    例:组件A – 传值 --> 父组件 – 传值 --> 组件B
  4. 跨层级通讯:
    Context 设计⽬的是为了共享那些对于⼀个
    组件树⽽⾔是“全局”的数据,
    使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据
    例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过。
  5. 发布订阅者模式:
    发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event模块进⾏通信。
  6. 全局状态管理工具:
    借助Redux或者Mobx等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态。

15.说说你对受控组件和非受控组件的理解?应用场景?

非受控组件(添加)指的是表单数据由dom本身处理,即不受setState的控制,与传统的html表单输入相似,input输入值及显示最新值
在非受控组件中可以使用ref从dom获取数据

受控组件(修改):由react控制的输入表单元素而改变其值的方法

实现受控组件的双向绑定:
	需要给表单元素添加value属性与onChange事件,value属性绑定的是state的状态,当表单元素发生变化的时候可以讲表单元素的值给value绑定state属性
	将表单的数据放到state中
	将子组件中使用props接收父组件的state
	在子组件中的表单上把props接收的参数绑定到state上
	绑定onChange事件,当用户输入时更新父组件的state

16.说说你对fiber架构的理解?解决了什么问题?

解决的问题:
JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待;如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿
理解:
React Fiber 是对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现
主要做了:

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行

17.说说react diff的原理是什么?

  1. Diff算法是虚拟DOM的一个必然结果,它是通过新旧DOM的对比,将在不更新页面的情况下,将需要内容局部更新
  2. Diff算法遵循深度优先,同层比较的原则
  3. 可以使用key值,可以更加准确的找到DOM节点
    reactdiff算法主要遵循三个层级的策略:
    tree层级: tree层不会做任何修改,如果有不一样,直接删除创建
    conponent 层级: component层从父级往子集查找,如果发现不一致,直接删除创建
    element 层级:element层有key值做比较,如果发现key值可以复用的话,就会将位置进行移动,如果没有,则执行删除创建

18.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?

中间件的理解

Redux中中间件是介于应用系统和软件系统之间的一类软件,他使用系统软件所提供的基础服务,中间件就是放在dispatch过程中,在分发action进行拦截的处理本质上的一个函数,对store.dispatch方法进行改造在发出action和执行reducer这两个之间
如果需要支持异步操作,支持错误处理和日志监控这个过程就可以用上中间件

常用的中间件:

1.redux-thunk:用于异步操作
2.redux-logger:用于日志记录

上述的中间件都需要通过applyMiddlewares进行注册,
作用是将所有的中间件组成一个数组,依次执行
然后作为第二个参数传入到createStore中

实现原理

所有的中间件都被放到一个数组chain,然后进行嵌套执行,最后执行store.dispatch,可以看到中间件内部拿到getState和dispatch两个方法,会将dispatch进行判断,然后执行对应操作

19.如何使用css实现一个三角形,

方法一:使用border

不设置宽高,用边框大小控制三角型大小


方法二:设置透明

留下想要指向方向相反的边框设定,其他方向的边框设为transparent透明


方法三:使用clip-path

裁剪多边型的方式,创建元素的可显示区域。区域内的部分显示,区域外的隐藏。


20.什么是强缓存和协商缓存?

缓存:
缓存是指浏览器(客户端)在本地磁盘中队访问过的资源保存的副本文件。

强缓存:
强缓存是根据返回头中的 Expires 或者 Cache-Control 两个字段来控制的,都是表示资源的缓存有效时间。

协商缓存:
协商缓存是由服务器来确定缓存资源是否可用。 主要涉及到两对属性字段,都是成对出现的,即第一次请求的响应头带上某个字, Last-Modified 或者 Etag,则后续请求则会带上对应的请求字段 If-Modified-Since或者 If-None-Match,若响应头没有 Last-Modified 或者 Etag 字段,则请求头也不会有对应的字段。

强缓存和协商缓存的区别

如果浏览器命中强缓存,则不需要给服务器发请求;而协商缓存最终由服务器来决定是否使用缓存,即客户端与服务器之间存在一次通信。
在 chrome 中强缓存(虽然没有发出真实的 http 请求)的请求状态码返回是 200 (fromcache);而协商缓存如果命中走缓存的话,请求的状态码是 304 (not modified)。 不同浏览器的策略不同,在
FireFox中,from cache 状态码是 304.

21.说说React jsx转换成真实DOM的过程?

在jsx进行转换的时候,首先看字母是否大小写,如果大写就会渲染成对应的组件,小写的话就会转换成html中同名的标签,渲染完成以后会先构成一个虚拟dom数,虚拟dom树和真实dom树进行比较,然后在渲染到真实的dom树上,转换成真实dom

22.说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?

React-redux是官方react ui绑定层,允许react组件从redux存储中读取数据,将操作分派到存储已更新状态,@reduxjs/toolkit是对redux的第二次封装,可以随时用到一个高效的redux开发工具集,使创建store,更新store更方便
提供createClice函数用于生成reducer和action creator,帮助开发者减少了编写频繁的reducer和action creator的工作量,用起来更简洁
相对于redux,@reduxjs/toolkit简化了开发是使用流程,减少了代码样板的编写,提供了更高层的抽象和易用性

23.React render方法的原理,在什么时候会触发

Render在页面中渲染其中的数据,如果其中有子组件的时候,子组件的声明周期就会包含在父组件的生命周期中
Render在创建的时候就会触发这个方法,在使用setState的时候,会触发到这个方法,再删除的时候会触发这方法,如果我们想要修改的数据即使展示出来的时候需要用到render方法

24.如何通过原生js实现一个节流函数和防抖函数,写出核心代码,

节流

function throttle(fn, delay) {
  let timer = null;
  return function() {
    let context = this;
    let args = arguments;
    if(!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, delay);
    }
  }
}

工作原理

定义一个timer变量来存放setTimeout返回的定时器id
判断timer是否为null,如果为null表示上一次调用后delay时间还没结束,直接return
如果timer不为null,表示上一次调用后delay时间已经结束,执行fn函数并清除timer
每次执行函数都会重置timer,这样就实现了间隔delay时间只调用一次函数

防抖

function debounce(fn, delay) {
  let timer = null;
  return function() {
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  }
}

工作原理

也是定义一个timer变量保存setTimeout返回的id
每次调用函数都会清除定时器timer
设置新的定时器,延迟delay毫秒后执行原函数
这样实现了函数连续调用,但是最后一次调用延迟delay毫秒后才执行

25.说说webpack中代码分割如何实现?

Webpack实现代码分割:使用动态导入语法
动态导入语法:
	Webpack2提供了import()函数来实现动态导入模块。这个函数会返回一个promise对象,可以用于异步加载模块。在webpack中,可以将动态导入语法实现代码分割。

26.说说如何借助webpack来优化前端性能?

借助webpack的5个组件来进行一个修改和优化,如果不使用webpack来进行优化的时候,浏览器就会根据你的项目一个一个的进行渲染,当其中一个页面的功能不被识别发生错误的时候就会导致项目缺少功能
Webpack可以帮助我们的项目在不能识别的情况下,优化成可以识别的代码

27.说说javascript内存泄漏的几种情况?

					1. 闭包中未释放的变量
                    2. 没有被清除的定时器

                    3. 意外的全局变量 : 例如在一个函数中给window对象上挂载一个变量 而window对象指向全局                        

                    4. 给DOM对象添加的属性是一个对象的引用 例如 

                            let obj = {...}  let  document.querySelector('.idname').property = obj  如果DOM不消除 则 这个obj会一直存在 造成内存泄漏  ( 这里的DOM元素的property属性是DOM元素自带的属性  html自带的dom属性会自动转换成property)

                    5.  DOM对象和js对象互相引用

                    function testObject(element) { 

this.elementReference = element; // 为testObject(js)对象的属性绑定element(DOM)对象
element.property = this; // 为element(DOM)对象的属性绑定testObject(js)对象
}
new testObject(document.getElementById(‘idname’));

                4和5的    解决方案 :在window.onunload事件中写入下边的代码 document.getElementById('idname').property = null; 将DOM元素的property属性设置为空

你可能感兴趣的:(前端,javascript,typescript)