diff --git a/03.HTML && CSS.md b/03.HTML && CSS.md index 28acc16..8ba7831 100644 --- a/03.HTML && CSS.md +++ b/03.HTML && CSS.md @@ -212,7 +212,7 @@ CSS 盒模型本质上是一个盒子,它包括:边距,边框,填充和 (1)width和margin实现。`margin: 0 auto`; - (2)绝对定位和margin-left: -width/2, 前提是父元素position: relative + (2)绝对定位和margin-left: margin-left: (父width - 子width)/2, 前提是父元素position: relative - 对于宽度未知的块级元素 diff --git a/04.JavaScript.md b/04.JavaScript.md index e36acb4..53f1fc3 100644 --- a/04.JavaScript.md +++ b/04.JavaScript.md @@ -24,6 +24,7 @@ BigInt:由于在 Number 与 BigInt 之间进行转换会损失精度,因而 console.log(typeof 1); // number console.log(typeof true); // boolean console.log(typeof 'mc'); // string +console.log(typeof Symbol) // function console.log(typeof function(){}); // function console.log(typeof console.log()); // function console.log(typeof []); // object @@ -69,6 +70,18 @@ console.log(toString.call(null)); //[object Null] 缺点:写法繁琐不容易记,推荐进行封装后使用 + +#### instanceof 的作用 +用于判断一个引用类型是否属于某构造函数; + +还可以在继承关系中用来判断一个实例是否属于它的父类型。 + +#### instanceof 和 typeof 的区别: +typeof在对值类型number、string、boolean 、null 、 undefined、 以及引用类型的function的反应是精准的;但是,对于对象{ } 、数组[ ] 、null 都会返回object + +为了弥补这一点,instanceof 从原型的角度,来判断某引用属于哪个构造函数,从而判定它的数据类型。 + + ### var && let && const ES6之前创建变量用的是var,之后创建变量用的是let/const @@ -151,21 +164,58 @@ ES6之前创建变量用的是var,之后创建变量用的是let/const - **闭包的优点**:延长局部变量的生命周期 - **闭包缺点**:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏 -### JS 中 this 的五种情况 +### JS 中 this 的情况 + +1. 普通函数调用:通过函数名()直接调用:`this`指向`全局对象window`(注意let定义的变量不是window属性,只有window.xxx定义的才是。即let a =’aaa’; this.a是undefined) +2. 构造函数调用:函数作为构造函数,用new关键字调用时:`this`指向`新new出的对象` +3. 对象函数调用:通过对象.函数名()调用的:`this`指向`这个对象` +4. 箭头函数调用:箭头函数里面没有 this ,所以`永远是上层作用域this`(上下文) +5. apply和call调用:函数体内 this 的指向的是 call/apply 方法`第一个参数`,若为空默认是指向全局对象window。 +6. 函数作为数组的一个元素,通过数组下标调用的:this指向这个数组 +7. 函数作为window内置函数的回调函数调用:this指向window(如setInterval setTimeout 等) + + +### call/apply/bind 的区别 + +相同: + +1、都是用来改变函数的this对象的指向的。\ +2、第一个参数都是this要指向的对象。\ +3、都可以利用后续参数传参。 + +不同: + +apply和call传入的参数列表形式不同。apply 接收 arguments,call接收一串参数列表 +``` +fn.call(obj, 1, 2); +fn.apply(obj, [1, 2]); +``` +bind:语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8 +bind 主要就是将函数绑定到某个对象,bind()会创建一个函数,返回对应函数便于稍后调用;而apply、call则是立即调用。 + +总结:基于Function.prototype上的 ` apply 、 call 和 bind `调用模式,这三个方法都可以显示的指定调用函数的 this 指向。`apply`接收参数的是数组,`call`接受参数列表,`` bind`方法通过传入一个对象,返回一个` this ` 绑定了传入对象的新函数。这个函数的 `this`指向除了使用`new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。 + +参考:☞ [call、apply、bind三者的用法和区别](https://blog.csdn.net/hexinyu_1022/article/details/82795517) + +### 箭头函数的特性 + +1. `箭头函数没有自己的this`,会捕获其所在的上下文的this值,作为自己的this值 +1. `箭头函数没有constructor`,是匿名函数,不能作为构造函数,不能通过new 调用; +1. `没有new.target 属性`。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined +1. `箭头函数不绑定Arguments 对象`。取而代之用rest参数...解决。由于 箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。(这种现象对于bind方法同样成立) +1. 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。 +1. 箭头函数没有原型属性 Fn.prototype 值为 undefined +1. 箭头函数不能当做Generator函数,不能使用yield关键字 -1. 作为普通函数执行时,`this`指向`window`。 -1. 当函数作为对象的方法被调用时,`this`就会指向`该对象`。 -1. 构造器调用,`this`指向`返回的这个对象`。 -1. 箭头函数 箭头函数的`this`绑定看的是`this所在函数定义在哪个对象下`,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。 -1. 基于Function.prototype上的 ` apply 、 call 和 bind `调用模式,这三个方法都可以显示的指定调用函数的 this 指向。`apply`接收参数的是数组,`call`接受参数列表,`` bind`方法通过传入一个对象,返回一个` this ` 绑定了传入对象的新函数。这个函数的 `this`指向除了使用`new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。 +参考:[箭头函数与普通函数的区别](https://www.cnblogs.com/biubiuxixiya/p/8610594.html) ### 原型 && 原型链 **原型关系:** -- 每个 class都有显示原型 prototype -- 每个实例都有隐式原型 _ proto_ -- 实例的_ proto_指向对应 class 的 prototype +- 每个 class都有显示原型 prototype +- 每个实例都有隐式原型 `__proto__` +- 实例的 `__proto__` 指向对应 class 的 prototype ‌ **原型:**  在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个`函数对象`都有一个`prototype` 属性,这个属性指向函数的`原型对象`。 diff --git a/07.Vue.md b/07.Vue.md new file mode 100644 index 0000000..a67402c --- /dev/null +++ b/07.Vue.md @@ -0,0 +1,215 @@ + +## Vue 面试专题 + +### 简述MVVM + +**什么是MVVM?** + +`视图模型双向绑定`,是`Model-View-ViewModel`的缩写,也就是把`MVC`中的`Controller`演变成`ViewModel。Model`层代表数据模型,`View`代表UI组件,`ViewModel`是`View`和`Model`层的桥梁,数据会绑定到`viewModel`层并自动将数据渲染到页面中,视图变化的时候会通知`viewModel`层更新数据。以前是操作DOM结构更新视图,现在是`数据驱动视图`。 + +**MVVM的优点:** + +1.`低耦合`。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;\ +2.`可重用性`。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。\ +3.`独立开发`。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。\ +4.`可测试`。 + +### Vue底层实现原理 + +vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调\ +Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观 + +**Observer(数据监听器)** : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher + +**Watcher(订阅者)** : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是: + +1. 在自身实例化时往属性订阅器(dep)里面添加自己 +1. 自身必须有一个update()方法 +1. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调 + +**Compile(指令解析器)** : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图 + +### 谈谈对vue生命周期的理解? + +每个`Vue`实例在创建时都会经过一系列的初始化过程,`vue`的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件 + +- `create阶段`:vue实例被创建\ + `beforeCreate`: 最初调用触发,创建前,此时data和methods中的数据都还没有初始化,data和events都不能用\ + `created`: 创建完毕,data中有值,未挂载,data和events已经初始化好,data已经具有响应式;在这里可以发送请求 +- `mount阶段`: vue实例被挂载到真实DOM节点\ + `beforeMount`:在模版编译之后,渲染之前触发,可以发起服务端请求,去数据,ssr中不可用,基本用不上这个hook\ + `mounted`: 在渲染之后触发,此时可以操作DOM,并能访问组件中的DOM以及$ref,SSR中不可用 +- `update阶段`:当vue实例里面的data数据变化时,触发组件的重新渲染\ + `beforeUpdate` :更新前,在数据变化后,模版改变前触发,切勿使用它监听数据变化\ + `updated`:更新后,在数据改变后,模版改变后触发,常用于重渲染案后的打点,性能检测或触发vue组件中非vue组件的更新 +- `destroy阶段`:vue实例被销毁\ + `beforeDestroy`:实例被销毁前,组件卸载前触发,此时可以手动销毁一些方法,可以在此时清理事件、计时器或者取消订阅操作\ + `destroyed`:卸载完毕后触发,销毁后,可以做最后的打点或事件触发操作 + +#### 组件生命周期 + +生命周期(父子组件) 父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件 mounted --> 父组件mounted -->父组件beforeUpdate -->子组件beforeDestroy--> 子组件destroyed --> 父组件updated + +**加载渲染过程** 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted + +**挂载阶段** 父created->子created->子mounted->父mounted + +**父组件更新阶段** 父beforeUpdate->父updated + +**子组件更新阶段** 父beforeUpdate->子beforeUpdate->子updated->父updated + +**销毁阶段** 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed + +### `computed与watch` + +通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。 + +**watch 属性监听** 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用 + +**computed 计算属性** 属性的结果会被`缓存`,当`computed`中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 `computed`中的函数必须用`return`返回最终的结果 `computed`更高效,优先使用。`data 不改变,computed 不更新。` + +**使用场景** `computed`:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 `watch`:当一条数据影响多条数据的时候使用,例:搜索数据 + +#### 组件中的data为什么是一个函数? + +1.一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。 2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。 + +#### 为什么v-for和v-if不建议用在一起 + +1.当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费 +2.这种场景建议使用 computed,先对数据进行过滤 + + +注意:3.x 版本中 `v-if` 总是优先于 `v-for` 生效。由于语法上存在歧义,建议避免在同一元素上同时使用两者。比起在模板层面管理相关逻辑,更好的办法是通过创建计算属性筛选出列表,并以此创建可见元素。 + +解惑传送门 ☞ [# v-if 与 v-for 的优先级对比非兼容](https://v3.cn.vuejs.org/guide/migration/v-if-v-for.html#%E6%A6%82%E8%A7%88) + + +### React/Vue 项目中 key 的作用 + +- key的作用是为了在diff算法执行时更快的找到对应的节点,`提高diff速度,更高效的更新虚拟DOM`; + + vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。 + +- 为了在数据变化时强制更新组件,以避免`“就地复用”`带来的副作用。 + + 当 Vue.js 用 `v-for` 更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的key会造成渲染错误。 + +### vue组件的通信方式 + +- `props`/`$emit` 父子组件通信 + + 父->子`props`,子->父 `$on、$emit` 获取父子组件实例 `parent、children` ` Ref `获取实例的方式调用组件的属性或者方法 父->子孙 `Provide、inject` 官方不推荐使用,但是写组件库时很常用 + +- `$emit`/`$on` 自定义事件 兄弟组件通信 + + `Event Bus` 实现跨组件通信 `Vue.prototype.$bus = new Vue()` 自定义事件 + +- vuex 跨级组件通信 + + Vuex、`$attrs、$listeners` `Provide、inject` + +### nextTick的实现 + +1. `nextTick`是`Vue`提供的一个全局`API`,是在下次`DOM`更新循环结束之后执行延迟回调,在修改数据之后使用`$nextTick`,则可以在回调中获取更新后的`DOM`; +1. Vue在更新DOM时是异步执行的。只要侦听到数据变化,`Vue`将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个`watcher`被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和`DOM`操作是非常重要的。`nextTick`方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用; +1. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可; +1. 我也有简单了解`nextTick`实现,它会在`callbacks`里面加入我们传入的函数,然后用`timerFunc`异步方式调用它们,首选的异步方式会是`Promise`。这让我明白了为什么可以在`nextTick`中看到`dom`操作结果。 + +### nextTick的实现原理是什么? + +在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。 + +### 使用过插槽么?用的是具名插槽还是匿名插槽或作用域插槽 + +vue中的插槽是一个非常好用的东西slot说白了就是一个占位的 在vue当中插槽包含三种一种是默认插槽(匿名)一种是具名插槽还有一种就是作用域插槽 匿名插槽就是没有名字的只要默认的都填到这里具名插槽指的是具有名字的 + +### keep-alive的实现 + +keep-alive是Vue.js的一个内置组件。它能够不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。 + +作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染 + +场景:tabs标签页 后台导航,vue性能优化 + +原理:`Vue.js`内部将`DOM`节点抽象成了一个个的`VNode`节点,`keep-alive`组件的缓存也是基于`VNode`节点的而不是直接存储`DOM`结构。它将满足条件`(pruneCache与pruneCache)`的组件在`cache`对象中缓存起来,在需要重新渲染的时候再将`vnode`节点从`cache`对象中取出并渲染。 + +### keep-alive 的属性 + +它提供了include与exclude两个属性,允许组件有条件地进行缓存。 + +include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。 + +在动态组件中的应用 + +```js + + + +``` +在vue-router中的应用 + +```js + + + +``` + +vue 中完整示例 +``` + + + + + + +export default { + data () { + return { + test: true + } + }, + methods: { + handleClick () { + this.test = !this.test; + } + } +} +``` + +参考: +[keep-alive 官网](https://cn.vuejs.org/v2/api/#keep-alive) + +[keep-alive实现原理](https://www.jianshu.com/p/9523bb439950) + +[Vue keep-alive的实现原理](https://blog.csdn.net/weixin_38189842/article/details/103999989) + +### mixin + +mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin +多个组件有相同的逻辑,抽离出来 +mixin并不是完美的解决方案,会有一些问题 +vue3提出的Composition API旨在解决这些问题【追求完美是要消耗一定的成本的,如开发成本】 +场景:PC端新闻列表和详情页一样的右侧栏目,可以使用mixin进行混合 +劣势:1.变量来源不明确,不利于阅读 2.多mixin可能会造成命名冲突 3.mixin和组件可能出现多对多的关系,使得项目复杂度变高 + +### vue 如何实现模拟 v-model 指令 + +可以使用 vue 自定义指令 Vue.directive() 模拟 + +具体参考:[vue自定义指令模拟v-model指令](https://blog.csdn.net/qq_39157944/article/details/106262546) + +### Vuex的理解及使用场景 + +Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。 + +1. Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候, + +若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新 2. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation, 这样使得我们可以方便地跟踪每一个状态的变化 Vuex主要包括以下几个核心模块: + +1. State:定义了应用的状态数据 +1. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性), + +就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算 3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数 4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作 5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a7249773a1634f779c48f3f0ffabf968~tplv-k3u1fbpfcp-zoom-1.image) diff --git a/08.React.md b/08.React.md new file mode 100644 index 0000000..81dc9b5 --- /dev/null +++ b/08.React.md @@ -0,0 +1,360 @@ +## React 面试专题 + + +### hooks用过吗?聊聊react中class组件和函数组件的区别 + +类组件是使用ES6 的 class 来定义的组件。 函数组件是接收一个单一的 `props` 对象并返回一个React元素。 + +关于React的两套API(类(class)API 和基于函数的钩子(hooks) API)。官方推荐使用钩子(函数),而不是类。因为钩子更简洁,代码量少,用起来比较"轻",而类比较"重"。而且,钩子是函数,更符合 React 函数式的本质。 + +函数一般来说,只应该做一件事,就是返回一个值。 如果你有多个操作,每个操作应该写成一个单独的函数。而且,数据的状态应该与操作方法分离。根据函数这种理念,React 的函数组件只应该做一件事情:返回组件的 HTML 代码,而没有其他的功能。函数的返回结果只依赖于它的参数。不改变函数体外部数据、函数执行过程里面没有副作用。 + +类(class)是数据和逻辑的封装。 也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。 + +**类组件的缺点** : + +大型组件很难拆分和重构,也很难测试。\ +业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。\ +组件类引入了复杂的编程模式,比如 render props 和高阶组件。\ +难以理解的 class,理解 JavaScript 中 `this` 的工作方式。 + +**区别**: + +函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。 + +1.状态的有无\ +hooks出现之前,函数组件`没有实例`,`没有生命周期`,`没有state`,`没有this`,所以我们称函数组件为无状态组件。 hooks出现之前,react中的函数组件通常只考虑负责UI的渲染,没有自身的状态没有业务逻辑代码,是一个纯函数。它的输出只由参数props决定,不受其他任何因素影响。 + +2.调用方式的不同\ +函数组件重新渲染,将重新调用组件方法返回新的react元素。类组件重新渲染将new一个新的组件实例,然后调用render类方法返回react元素,这也说明为什么类组件中this是可变的。 + +3.因为调用方式不同,在函数组件使用中会出现问题\ +在操作中改变状态值,类组件可以获取最新的状态值,而函数组件则会按照顺序返回状态值 + +**React Hooks(钩子的作用)** + +*Hook* 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 + +React Hooks的几个常用钩子: + +1. `useState()` //状态钩子 +1. `useContext()` //共享状态钩子 +1. `useReducer()` //action 钩子 +1. `useEffect()` //副作用钩子 + +还有几个不常见的大概的说下,后续会专门写篇文章描述下 + +- 1.useCallback 记忆函数 一般把**函数式组件理解为class组件render函数的语法糖**,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。而有了 useCallback 就不一样了,你可以通过 useCallback 获得一个记忆后的函数。 + + ```js + function App() { + const memoizedHandleClick = useCallback(() => { + console.log('Click happened') + }, []); // 空数组代表无论什么情况下该函数都不会发生改变 + return Click Me; + } + ``` + + 第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback 就会重新返回一个新的记忆函数提供给后面进行渲染。 + +- 2.useMemo 记忆组件 useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。 唯一的区别是:**useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你**。\ + 所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。 + +- 3.useRef 保存引用值 + + useRef 跟 createRef 类似,都可以用来生成对 DOM 对象的引用。useRef 返回的值传递给组件或者 DOM 的 ref 属性,就可以通过 ref.current 值**访问组件或真实的 DOM 节点,重点是组件也是可以访问到的**,从而可以对 DOM 进行一些操作,比如监听事件等等。 + +- 4.useImperativeHandle 穿透 Ref + + 通过 useImperativeHandle 用于让父组件获取子组件内的索引 + +- 5.useLayoutEffect 同步执行副作用 + + 大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。 + + **useEffect和useLayoutEffect有什么区别**:简单来说就是调用时机不同,useLayoutEffect和原来componentDidMount&componentDidUpdate一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。而useEffect是会在整个页面渲染完才会调用的代码。`官方建议优先使用useEffect` + + +### React 组件通信方式 + +react组件间通信常见的几种情况: + +- 1. 父组件向子组件通信 +- 2. 子组件向父组件通信 +- 3. 跨级组件通信 +- 4. 非嵌套关系的组件通信 + +#### 1)父组件向子组件通信 + +父组件通过 props 向子组件传递需要的信息。父传子是在父组件中直接绑定一个正常的属性,这个属性就是指具体的值,在子组件中,用props就可以获取到这个值 + +```js +// 子组件: Child +const Child = props =>{ + return

{props.name}

+} + +// 父组件 Parent +const Parent = ()=>{ + return +} +``` + +#### 2)子组件向父组件通信 + +props+回调的方式,使用公共组件进行状态提升。子传父是先在父组件上绑定属性设置为一个函数,当子组件需要给父组件传值的时候,则通过props调用该函数将参数传入到该函数当中,此时就可以在父组件中的函数中接收到该参数了,这个参数则为子组件传过来的值 + +```js +// 子组件: Child +const Child = props =>{ + const cb = msg =>{ + return ()=>{ + props.callback(msg) + } + } + return ( + + ) +} + +// 父组件 Parent +class Parent extends Component { + callback(msg){ + console.log(msg) + } + render(){ + return + } +} +``` + +#### 3)跨级组件通信 + +即父组件向子组件的子组件通信,向更深层子组件通信。 + +- 使用props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递props,增加了复杂度,并且这些props并不是中间组件自己需要的。 +- 使用context,context相当于一个大容器,我们可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用context实现。 + +```js +// context方式实现跨级组件通信 +// Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据 + +const BatteryContext = createContext(); + +// 子组件的子组件 +class GrandChild extends Component { + render(){ + return ( + + { + color =>

我是红色的:{color}

+ } +
+ ) + } +} + +// 子组件 +const Child = () =>{ + return ( + + ) +} +// 父组件 +class Parent extends Component { + state = { + color:"red" + } + render(){ + const {color} = this.state + return ( + + + + ) + } +} +``` + +#### 4)非嵌套关系的组件通信 + +即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。 + +- 1. 可以使用自定义事件通信(发布订阅模式),使用pubsub-js +- 2. 可以通过redux等进行全局状态管理 +- 3. 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。 +- 4. 也可以new一个 Vue 的 EventBus,进行事件监听,一边执行监听,一边执行新增 VUE的eventBus 就是发布订阅模式,是可以在React中使用的; + +### setState 既存在异步情况也存在同步情况 + +1.异步情况 在`React事件当中是异步操作` + +2.同步情况 如果是在`setTimeout事件或者自定义的dom事件`中,都是同步的 + +```js +//setTimeout事件 +import React,{ Component } from "react"; +class Count extends Component{ + constructor(props){ + super(props); + this.state = { + count:0 + } + } + + render(){ + return ( + <> +

count:{this.state.count}

+ + + ) + } + + btnAction = ()=>{ + //不能直接修改state,需要通过setState进行修改 + //同步 + setTimeout(()=>{ + this.setState({ + count: this.state.count + 1 + }); + console.log(this.state.count); + }) + } +} + +export default Count; +``` + +```js +//自定义dom事件 +import React,{ Component } from "react"; +class Count extends Component{ + constructor(props){ + super(props); + this.state = { + count:0 + } + } + + render(){ + return ( + <> +

count:{this.state.count}

+ + + ) + } + + componentDidMount(){ + //自定义dom事件,也是同步修改 + document.querySelector('#btn').addEventListener('click',()=>{ + this.setState({ + count: this.state.count + 1 + }); + console.log(this.state.count); + }); + } +} + +export default Count; +``` + +### 生命周期 + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8bae01e6eb804d849e5bb889f787707d~tplv-k3u1fbpfcp-zoom-1.image) + +```js +安装 +当组件的实例被创建并插入到 DOM 中时,这些方法按以下顺序调用: + +constructor() +static getDerivedStateFromProps() +render() +componentDidMount() + +更新中 +更新可能由道具或状态的更改引起。当重新渲染组件时,这些方法按以下顺序调用: + +static getDerivedStateFromProps() +shouldComponentUpdate() +render() +getSnapshotBeforeUpdate() +componentDidUpdate() + +卸载 +当组件从 DOM 中移除时调用此方法: + +componentWillUnmount() +``` + +### 说一下 react-fiber + +#### 1)背景 + +react-fiber 产生的根本原因,是`大量的同步计算任务阻塞了浏览器的 UI 渲染`。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用`setState`更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。 + +#### 2)实现原理 + +- react内部运转分三层: + + - Virtual DOM 层,描述页面长什么样。 + - Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。 + - Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。 + +`Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示`: + +```js +const fiber = { + stateNode, // 节点实例 + child, // 子节点 + sibling, // 兄弟节点 + return, // 父节点 +} +``` + +- 为了实现不卡顿,就需要有一个调度器 (Scheduler) 来进行任务分配。优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。任务的优先级有六种: + + - synchronous,与之前的Stack Reconciler操作一样,同步执行 + - task,在next tick之前执行 + - animation,下一帧之前执行 + - high,在不久的将来立即执行 + - low,稍微延迟执行也没关系 + - offscreen,下一次render时或scroll时才执行 + +- Fiber Reconciler(react )执行过程分为2个阶段: + + - 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。阶段一可被打断的特性,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率。 + - 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。 + +- Fiber树:React 在 render 第一次渲染时,会通过 React.createElement 创建一颗 Element 树,可以称之为 Virtual DOM Tree,由于要记录上下文信息,加入了 Fiber,每一个 Element 会对应一个 Fiber Node,将 Fiber Node 链接起来的结构成为 Fiber Tree。Fiber Tree 一个重要的特点是链表结构,将递归遍历编程循环遍历,然后配合 requestIdleCallback API, 实现任务拆分、中断与恢复。 + +从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情 + +传送门 ☞[# 深入了解 Fiber](https://juejin.cn/post/7002250258826657799) + +### Portals + +Portals 提供了一种一流的方式来将子组件渲染到存在于父组件的 DOM 层次结构之外的 DOM 节点中。结构不受外界的控制的情况下就可以使用portals进行创建 + +### 何时要使用异步组件?如和使用异步组件 + +- 加载大组件的时候 +- 路由异步加载的时候 + +react 中要配合 Suspense 使用 + +```js +// 异步懒加载 +const Box = lazy(()=>import('./components/Box')); +// 使用组件的时候要用suspense进行包裹 +loading...}> + {show && } + +``` +### React 事件绑定原理 + +React并不是将click事件绑在该div的真实DOM上,而是`在document处监听所有支持的事件`,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。\ +另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 `event.preventDefault`。 + +![react事件绑定原理](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2089718f74b342869de15f01588f033f~tplv-k3u1fbpfcp-zoom-1.image)