前端面试03

react - 生命周期

一个React组件的生命周期分为三个部分:初始化(实例化)、存在期和销毁时
参考: https://www.cnblogs.com/MuYunyun/p/6629096.html
深入了解:https://zhuanlan.zhihu.com/p/30757059
https://zhuanlan.zhihu.com/p/30971608

实例化
当组件在客户端被实例化,第一次被创建时,以下方法依次被调用:
1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render
5、componentDidMount
当组件在服务端被实例化,首次被创建时,以下方法依次被调用:
1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render
getDefaultProps - 设置默认的 props,对于每个组件实例来讲,这个方法只会调用一次,该组件类的所有后续应用,getDefaultPops 将不会再被调用。
getInitialState - 对于组件的每个实例来说,这个方法的调用有且只有一次,用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。每一个React组件都有自己的 state,其与 props 的区别在于 state只存在组件的内部,props 在所有实例中共享。
注释: getDefaultPops 是对于组件类来说只调用一次,后续该类的应用都不会被调用,而 getInitialState 是对于每个组件实例来讲都会调用,并且只调一次。

componentWillMount - 该方法在首次渲染之前调用,也是再 render 方法调用之前修改 state 的最后一次机会。

render - 该方法会创建一个虚拟DOM,用来表示组件的输出。对于一个组件来讲,render方法是唯一一个必需的方法。render方法需要满足下面几点:

只能通过 this.props 和 this.state 访问数据(不能修改)
可以返回 null,false 或者任何React组件
只能出现一个顶级组件,不能返回一组元素
不能改变组件的状态
不能修改DOM的输出

render方法返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOMtree的结构的对象。react之所以效率高,就是这个原因。
componentDidMount —该方法不会在服务端被渲染的过程中调用。该方法被调用时,已经渲染出真实的 DOM,可以再该方法中通过 this.getDOMNode()访问到真实的 DOM(推荐使用 ReactDOM.findDOMNode())。

由于组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。有时需要从组件获取真实 DOM 的节点,这时就要用到 ref 属性:需要注意的是,由于 this.refs.[refName] 属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。

存在期
此时组件已经渲染好并且用户可以与它进行交互,比如鼠标点击,手指点按,或者其它的一些事件,导致应用状态的改变,你将会看到下面的方法依次被调用
1、componentWillReceiveProps
2、shouldComponentUpdate
3、componentWillUpdate
4、render
5、componentDidUpdate

componentWillReceiveProps - 组件的 props 属性可以通过父组件来更改,这时,componentWillReceiveProps 将来被调用。可以在这个方法里更新 state,以触发 render 方法重新渲染组件。

shouldComponentUpdate - 如果你确定组件的 props 或者 state 的改变不需要重新渲染,可以通过在这个方法里通过返回 false 来阻止组件的重新渲染,返回 `false 则不会执行 render 以及后面的 componentWillUpdate,componentDidUpdate 方法。
该方法是非必须的,并且大多数情况下没有在开发中使用。

componentWillUpdate ------ 在组件接收到了新的 props 或者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用,注意不要在此方面里再去更新 props 或者 state

componentDidUpdate - 在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。

销毁时
componentWillUnmount - 每当React使用完一个组件,这个组件必须从 DOM 中卸载后被销毁,此时 componentWillUnmout 会被执行,完成所有的清理和销毁工作,在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。

当再次装载组件时,以下方法会被依次调用:
1、getInitialState
2、componentWillMount
3、render
4、componentDidMount
当组件需要从DOM中移除时,即会触发这个钩子函数。这里没有太多需要注意的地方,在这个函数中通常会做一些“清洁”相关的工作
将已经发送的网络请求都取消掉
移除组件上DOM的Event Listener

反模式: 在 getInitialState 方法中,尝试通过 this.props 来创建 state 的做法是一种反模式。

//反模式
getDefaultProps: function(){
   return {
       data: new Date()
   }
},
getInitialState: function(){
   return {
       day: this.props.date - new Date()
   }
},
render: function(){
   return <div>Day:{this.state.day}</div>
} 
// 经过计算后的值不应该赋给 state,正确的模式应该是在渲染时计算这些值。这样保证了计算后的值永远不会与派生出它的 props 值不同步。
//正确模式
getDefaultProps: function(){
   return {
       data: new Date()
   }
},
render: function(){
   var day = this.props.date - new Date();
   return <div>Day:{day}</div>
}

react -diff算法【组件】

参考: https://que01.top/2019/06/25/react-diff/
React 的核心思想
React 最为核心的就是 Virtual DOM 和 Diff 算法。React 在内存中维护一颗虚拟 DOM 树,当数据发生改变时(state & props),会自动的更新虚拟 DOM,获得一个新的虚拟 DOM 树,然后通过 Diff 算法,比较新旧虚拟 DOM 树,找出最小的有变化的部分,将这个变化的部分(Patch)加入队列,最终批量的更新这些 Patch 到实际的 DOM 中。

diff算法分为Tree Diff、Component Diff、Element Diff三个层面。
通过分层对比策略,对 tree diff 进行算法优化

通过相同类生成相似树形结构,不同类生成不同树形结构以及shouldComponentUpdate策略,对 component diff 进行算法优化

通过设置唯一 key 策略,对 element diff 进行算法优化,element diff 就是通过唯一 key 来进行 diff 优化,通过复用已有的节点,减少节点的删除和创建操作。

综上,tree diff 和 component diff 是从顶层设计上降低了算法复杂度,而 element diff 则在在更加细节上做了进一步优化。

React 中的事件委托

事件委托有以下优点。
减少事件注册,节省内存,能够提升整体性能。
简化了dom节点更新时,相应事件的更新(用过 jquery 的都知道,动态加入的元素,事件需要重新绑定)。
参考文档: https://zhuanlan.zhihu.com/p/165089379
https://zhuanlan.zhihu.com/p/164844013

React 事件委托

React 借鉴事件委托的方式将大部分事件委托给了 Document 对象。
React 中的事件分为 3 类。分别是 DiscreteEvent(离散事件),UserBlockingEvent(用户阻塞事件),ContinuousEvent(连续事件)。不同类型的事件代表了不同的优先级。
事件委托需要区分捕获和冒泡,有些事件由于没有冒泡过程,只能在捕获阶段进行事件委托。
没有进行委托的事件是 Form 事件和 Media 事件,原因是这些事件针对特性类型元素,委托意义不大,React 将其直接注册到了目标对象。

React 中的事件分为 3 类。分别是
DiscreteEvent(离散事件),UserBlockingEvent(用户阻塞事件),ContinuousEvent(连续事件)。不同类型的事件代表了不同的优先级。

DiscreteEvent:click,blur,focus,submit,tuchStart 等,优先级是 0。
UserBlockingEvent:touchMove,mouseMove,scroll,drag,dragOver 等,这些事件会阻塞用户的交互,优先级是 1。
ContinuousEvent:load,error,loadStart,abort,animationend 等,优先级是 2,这个优先级最高,不会被打断。

无法冒泡:

scroll - scroll 事件不会冒泡,这个带来的影响就是,当我们去做事件委托的时候,其它的大部分事件可以在冒泡阶段的时候完成委托,而 scroll 事件必须在捕获阶段完成委托。
scroll 事件无法取消( 没有冒泡的基本都没法取消 ),scroll 回调中的 preventDefault 和 stopPropagation 都是无效的。

focus 和 blue 事件也是无法冒泡,无法取消的,事件委托的时候需要注意,在捕获阶段进行监听。否则会导致事件失效。想到和它们很像的两个事件,那就是 focusout/focusin。它们和前者的主要区别就是 focusout/focusin 事件会冒泡。如果同时存在的话,focus 先于 focusin。blur 先于 focusout。

Media 事件由媒介(比如视频、图像和音频)触发的事件,都不冒泡
onpause 当媒介被用户或程序暂停时运行的脚本
onplay 当媒介已就绪可以开始播放时运行的脚本
onplaying 当媒介已开始播放时运行的脚本
onsuspend 在媒介数据完全加载之前不论何种原因终止取回媒介数据时运行

我们需要监听音频开始播放的事件。由于所有的由媒体触发的事件都不冒泡,所以我们只能在捕获阶段进行事件委托。
mouseleave & mouseenter事件同样不会冒泡,与 mouseleave/mouseenter 事件非常相似的事件是 mouseout/mouseover,它们的区别就是 mouseout/mouseover 会触发冒泡,

for in 和 Object.keys区别

for in
常常用来枚举对象的属性。某些情况下,可能按照随机顺序便利数组元素
遍历对象及其原型链上的可枚举的属性
如果用于遍历数组,除了遍历其元素外,还会遍历开发者对数组对象自定义的可枚举属性及其原型链上的可枚举属性
遍历对象返回属性名和遍历数组返回的索引都是string类型
某些情况下,可能按随机顺序遍历数组元素 - 不推荐在数组中使用for in 遍历

Object.keys
返回对象自身可枚举属性组成的数组
不会遍历对象原型链上的属性以及Symbol属性
对数组的遍历顺序和for in 一致

for of
es6 中添加的循环遍历语法
支持遍历数组,类数组对象(DOM NodeList),字符串,Map对象,set对象;
不支持遍历普通对象
遍历后输出的结果为数组元素的值
可搭配实例方法entries(),同时输出数组的内容和索引;

Object.entries
Object.entries(obj) :如果参数的数据结构具有键和值,则返回一个二元数组,数组的每个元素为参数的[key,value]数组;

// Symbol 属性会被忽略
Object.entries({[Symbol()]:1,name:'jj',age:5})
//  [["name","jj"],["age",5]]

JS垃圾回收机制

js中的垃圾有哪些
https://cloud.tencent.com/developer/article/1852932
全局变量:全局变量存在被使用的可能性,所以不能当做垃圾
局部变量:函数执行完了变量当做垃圾清除
单引用,双引用,环引用,这三种情况下,只有将所有指向该变量的对象清除才能够将该变量作为垃圾处理

垃圾回收算法

标记扫除算法
这是最基本的垃圾扫除算法,首先我们从global也就是window出发,开始去找它每一个箭头,然后找到所有能根据箭头找到的变量,我们给这样的变量一个:√。其他找不到的标记为:×。最后清除所有为×的变量

缺点:
标记的效率太慢了,我们的垃圾回收需要时时刻刻去遍历检查每一个引用,如果你的js代码中对象太多,会导致非常严重的效率问题
浏览器在标记的过程中js是不能执行的,我们不能让垃圾回收拖慢我们js运行的时间

这里针对标记扫除有三个优化点:
分代垃圾回收:我们可以将对象分为新生代和老生代,两者我们采用不同的回收策略。比如说window对象,就像个老祖宗一样,你这些后代是不是要时常像老祖宗请教?所以我们对于老一代可以回收的不那么勤快,隔几秒钟去遍历一次就行啦。对于新生代,我们需要马上标记马上回收。
增量执行:不会一次就全部遍历完,可能一次遍历个十分之一,提高性能
空余时间执行,比如我可以在你js执行完了之后再执行,不会影响到js的执行效率

引用计数
引用计数,我们使用计数器在每个对象被新建,引用,删除引用的时候更新计数,如果计数器的值为0则直接删除,这种方法的优点很明显,暂停时间短

优点:
可即时回收垃圾
最大暂停时间短
不需要去沿指针去一遍遍的找

缺点:
计数器的增减处理任务繁重
计数器需要占位
实现起来很繁琐,没个赋值操作都得替换为引用更新操作
循环引用的无法回收

管理内存 只将需要的数据,存入变量。一旦这个数据不再使用了,我们需要手动的给变量赋值 null 来释放数据的引用。(-------解除引用) 大多需要我们进行手动的解除引用的都是一些全局的变量,因为局部的变量,在离开环境的时候就会被自动清除了。

前端的垃圾回收特殊性,不仅有js进程还有DOM进程,垃圾回收需要同时清除dom和js

React 你是如何理解单向数据流的

什么是数据流?
所谓数据的流动就是数据在组件间的传递,

为什么是自顶向下的?
自顶向下又怎么解释呢?更简单了,就是数据只会影响到下一个层级的节点,不会影响上一个层级的节点

单向数据流是什么意思?
单向数据流:规范数据的流向,数据由外层组件向内层组件进行传递和更新。

为什么是单向的?不能是双向的数据流嘛?
父组件的数据通过props传递给子组件,而子组件里更新了props,导致父组件和其他关联组件的数据更新,UI 渲染也会随数据而更新,毫无疑问,这是会导致严重的数据紊乱和不可控。 React 在这方面的处理,就是直接规定了 Props 为只读的,而不是可更改的。这也就是我们前面看到的数据更新不能直接通过 this.state 操作,想要更新,就需要通过 React 提供的专门的 this.setState() 方法来做

单向数据流有什么作用呢?
单向数据流这样的通信约束,使得我们react项目中的数据传递结构稳定且不易耦合

setState 是异步还是同步的?

this.setState() 确实是异步调用执行的代码
fun= async ()=>{
await this.setState({
name:‘xiling’
})
console.log(this.state.name)
}

setState 的执行逻辑
在使用 this.setState() 进行状态更改时,需要进行逻辑处理应该怎么做呢?其实 this.setState() 的第一个参数是可以接收一个函数处理的,需要注意的是,函数的运行必须返回一个 state 对象,

this.setState() 如果是函数,那么函数会依次从上往下执行,而如果是一个对象, React 会将多次 this.setState() 的调用合并为一次执行,如果修改的了相同的值,则会将前面的修改替换成最后一次的修改数据。

Context

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

webpack跨域问题:解决webpack跨域的三种方式
1.使用代理- 反向代理解决跨域的问题

devServer:{
    proxy:{  // 重写的方式,把请求代理到express服务器上
        '/api':{
            target:'http://localhost:3000',
            pathRewrite:{'/api':''} // 把/api 替换为空
        }
    }
},

2.模拟数据

devServer:{
    // 模拟数据
    before(app){
        app.get('/user',(req,res)=>{
            res.json({name:'小白'})
        })
    }
},

3.前端启动到服务端上

// server.js
let express = require('express');
let app = express();
let webpack = require('webpack');

let middle = require('webpack-dev-middleware'); // 引入这个
let config = require('./webpack.config.js');    // 配置文件
let compiler = webpack(config);

app.use(middle(compiler))

app.get('/user',(req,res)=>{
    res.json({name:'小白2'})
})
app.listen(3001)


js事件循环 机制

js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

同步和异步分别进入到不同的执行场所,同步进入到主线程,异步的进入到event table 并注册函数
当指定事件完成之后,event table 会将函数移入到event queue
当主线程的任务执行完毕之后,会把event queue里面读取对应的函数进入主线程执行
上述过程会不断循环,形成event loop (事件循环)

macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})


// 1,7,6,8,2,4,3,5,9,11,10,12

第一次:
主任务 - 1, 7,宏任务-setTimeout1, setTimmeout2; 微任务 - process1, then1,
1, 7;
微任务 - process1, then1 — 6, 8;
宏任务内部:
setTimeout1:主 - 2,4; 微任务-process, then -------3,5;
setTimeout: 主-9,11; 微任务-process, then -----10,12
总上:1,7,6,8,2,4,3,5,9,11,10,12

MVC MVVM

MVC
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式
Model(模型)表示应用程序核心(如数据库)。是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
View(视图)显示效果(HTML页面)。是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
Controller(控制器)处理输入(业务逻辑)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVVM
MVVM是Model-View-ViewModel的简写,MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。
M - 内容的数据访问层(以数据为中心)。
V - 结构、布局和外观(UI)
VM-视图模型是暴露公共属性和命令的视图的抽象。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
绑定器: 声明性数据和命令绑定隐含在MVVM模式中

mvc 和 mvvm 其实区别并不大。都是一种设计思想,主要区别如下:
1.mvc 中 Controller演变成 mvvm 中的 viewModel
2.mvvm 通过数据来驱动视图层的显示而不是节点操作。
3.mvc中Model和View是可以直接打交道的,造成Model层和View层之间的耦合度高。而mvvm中Model和View不直接交互,而是通过中间桥梁ViewModel来同步
4.mvvm主要解决了:mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值