资讯详情

最近遇到的面试题

1.promise的打印

2.setTimeout设置为0 为什么不能马上执行?

3.git 只merge一个commit的方法

4.for in 和 for of 、foreach 的区别

5.Promise原型方法then、catch、finally

1 Promise原型方法

2 Promise.prototype.then()

3 Promise.prototype.catch()

4 Promise.prototype.finally()

6.浏览器垃圾回收机制

7.vue3 watch和watchEffect使用和区别是什么?

1.watch侦听器

2.watchEffect

8.react中useEffect和useLayoutEffect的区别

9.Vue中模渲染原理


1.promise的打印

console.log(1) new Promise(resolve => {     console.log(2)     resolve(3) }).then(console.log).then(() => {     console.log(4) }) Promise.resolve('5').then(res => {     console.log(res) })

正确答案是什么? 1 2 3 5 4

看下面的打印

console.log(1);  function a() {      console.log('bbb');     return function () {          console.log('cccccc')     }  } new Promise(resolve => {     console.log(2)     resolve(3) }).then(console.log).then(() => {     console.log(4) }) setTimeout(() => {     console.log(6)     Promise.resolve(7).then(res => {         console.log(res)     }) }); setTimeout(a(),0) Promise.resolve('5').then(res => {     console.log(res) })

答案是 1 2 bbb 3 5 4 6 7 cccccc

在这里,顺便说一下面试中遇到的另一个问题,如何让settimeout立即执行函数,我的答案是写a函数;

2.setTimeout设置为0 为什么不能马上执行?

setTimeout(function(){}, timer) 指延迟执行。第一个参数是回调函数,第二个参数是回调函数延迟多久。

setTimeout(function(){console.log(1);}, 0); console.log(2);  //输入是 2 ,1

setTimeout(fn, 0)的意思是指定一个任务在主线程的最早空闲时间执行,也就是说,在当前代码执行(执行栈清空)后,尽可能早地执行。它在任务队列的末尾添加了一个事件,所以它将在同步任务和任务队列现有事件完成之前执行。

HTML5标准规定了setTimeout()第二个参数的最小值不得小于4毫秒,如果低于4毫秒。在此之前。旧版本的浏览器将最短时间设置为10毫秒。另外,对于那些DOM通常间隔16页面重新渲染的部分)通常间隔16毫秒执行。这时使用requestAnimationFrame()效果好于setTimeout();

注意:setTimeout()只将事件插入任务队列,必须等待当前代码(执行栈)执行,主线程才能执行其指定的回调函数。如果当前代码消耗时间长,可能要等很久,所以不能保证回调函数会在setTimeout()执行指定时间。所以,setTimeout()第二个参数表示最少时间,而不是确切时间。

3.git 只merge一个commit的方法

eg:说实话,这个问题真的让我的大脑停止了思考,真的是无卷啊

gil log 来查看commit的记录

git cherry-pick e43a6

4.for in 和 for of 、foreach 的区别

  1. for...in 循环:只能得到对象的键名,不能得到键值,for...of 循环:允许通历获得键值
  2. 对于普通对象,没有本地部署 iterator 直接使用接口for...of 会报错
  3. for...in 循环不仅是遍历数字键名,还有手动添加的其他键,甚至是原型链上的键。for...of 则不会这样
  4. forEach 循环无法中途跳出,break 命令或 return 命令无效

  5. for...of 循环可以与break、continue 和 return 配合使用,跳出循环,for...in可以使用breack和continue不能使用return,否则会报错

  6. 无论是 for...in 还是 for...of 不能遍历出 Symbol 类型值,遍历 Symbol 需要使用类型值 Object.getOwnPropertySymbols() 方法

总之,for...in 循环主要是为遍历对象而生,不适用于遍历数组

for...of 循环可用于数组数组、类数组对象、字符串、Set、Map 以及 Generator 对象

5.

     原文链接: https://www.jianshu.com/p/79327bd1511f

1 Promise原型方法

Promise 构造函数的原型上实现了then, catch, finally方法,也就意味着每个 Promise 实例都拥有这样 3 个函数。

另外提一下 Promise 构造函数上实现了all, race, reject, resolve, allSettled, any 方法,==这些方法只属于 Promise 构造函数自己,是不能在实例中被调用的==(也就是只会以这种形式出现:如 Promise.all()

2 Promise.prototype.then()

then() 方法可以处理 Promise 被接受或拒绝的情况。

参数

then() 最多可以接受两个参数 (回调函数):

onFulfilled

Promise 变成接受状态 (fulfilled) 时调用的函数。该函数有一个参数,即接受的最终结果 (the fulfillment value)。

// then()方法的第一个参数,用来处理接受状态的Promise
Promise.resolve(1)
.then((res) => {
    console.log(res) //1
})

如果该参数 (指的是onFulfilled) 不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果 (后面会讨论 then 方法的返回值)。

let param;

Promise.resolve(1).then(param)
//与下面的代码是等价的:
Promise.resolve(1).then((x) => x)

001.jpg

 

onRejected

1)当 Promise 变成拒绝状态 (rejected) 时调用的函数。该函数有一个参数,即拒绝的原因 (rejection reason)。

// then()方法的第二个参数,用来处理拒绝状态的Promise
Promise.reject(2)
.then(()=>{},
(reason)=>{
    console.log(reason) //2
})

2)如果该参数 (指的是 onRejected) 不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument),也就是会报错。

let param

Promise.reject(2)
.then(()=>{},param)

002.jpg

 返回值

Promise 完成 (fulfilled) 或者失败 (rejected) 时,会异步调用返回函数,==返回一个 Promise 对象==,不过返回值与 then() 方法中的回调函数是有关系的,如果then() 的回调函数:

。那么 then 返回一个接受状态的 Promise,并且该接受状态回调函数的参数值为 undefined

也就是说在处理then() 返回的 Promise 的回调函数中参数是 undefined,不理解就看下面的例子:

Promise.resolve(1)
.then(res=>{})
// 这里 then() 中的回调函数没有返回任何值,看看结果吧:

003.jpg

 

下面的例子可能对你理解第二句话有帮助 (后面的几种情况都与此类似,都举例说明了):

Promise.resolve(1)
.then(res=>{})
.then(param=>{
    console.log('参数',param)
})
//这里试着打印下参数,看下结果吧:

004.jpg

 

。那么 then 返回一个接受状态的 Promise ,并且将返回的值作为接受状态的回调函数的参数值。

与 ① 情况类似,只是返回的 undefined 变成值

Promise.resolve(1)
.then(res=>{return 6})

Promise.resolve(1)
.then(res=>{return 6})
.then(param=>{
    console.log('参数',param)
})

005.jpg

 

。那么 then 返回一个拒绝状态的 Promise,并且将抛出的错误作为拒绝状态的回调函数的参数值

Promise.resolve(1).then(()=>{
    throw new Error('Oops,Error')
})

//因为是拒绝,这次要用then()的第二个参数捕捉原因
Promise.resolve(1).then(()=>{
    throw new Error('Oops,Error')
}).then(null,reason=>{
    console.log(reason)
})

06.jpg

 

后面三种是分别返回不同状态的 Promise

。那么 then 也会返回一个接受状态的 Promise,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的 Promise 的接受状态回调函数的参数值

Promise.resolve(1).then(()=>{
    return Promise.resolve(3)
})

Promise.resolve(1).then(()=>{
    return Promise.resolve(3)
}).then(param=>{
    console.log('参数',param)
})

007.jpg

 

。那么 then 也会返回一个拒绝状态的 Promise,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值

Promise.resolve(1).then(()=>{
    return Promise.reject(5)
})

//因为是拒绝状态,要用then()的第二个参数捕捉原因
Promise.resolve(1).then(()=>{
    return Promise.reject(5)
}).then(null,param=>{
    console.log('参数',param)
})

008.jpg

 

。那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的

function func(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve('过了两秒')
        },2000)
    })
}

func()

示例中延时返回以得到 pending 状态的 Promise,立即查看就得到还未有返回值的 pending 状态,2秒钟之后就能看到有返回值的终态

009.png

 

也可以试着用 then() 方法捕捉一下参数:

func().
then(param=>{
    console.log('参数',param)
})

10.png

 

3 Promise.prototype.catch()

catch() 方法用来处理 Promise 被拒绝的情况,相当于 then(undefined, onRejected)

参数

catch() 接受一个参数 (回调函数):

onRejected

1)当 Promiserejected 时调用的函数。 该函数拥有一个参数 reason (rejection 的原因)

2)如果这个参数 (指的是 onRejected) 不是函数时,也是会报错的。这与 then() 方法中第二个参数不是函数的情况吻合。

返回值

如果 catch() 中的回调函数抛出一个错误或返回一个本身失败的 Promise, 则返回一个 rejected 状态的 Promise;其他情况返回 resolved 状态的 Promise

rejected的两种状态

 

catch()链式调用参考

4 Promise.prototype.finally()

要点理解

  1. finally 方法用来指定在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行的回调函数。这样可以避免同样的语句需要在 then()catch() 中都要写一次的情况

  2. finally 方法的回调函数不接受任何参数,这意味着没有办法知道前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 仅用于无论最终结果如何都要执行的情况,而不能依赖 Promise 执行结果

  3. finally 方法本身无异常抛出的情况下,总是会返回原来的 Promise 对象值;若抛出异常,则返回异常的 Promise 对象。

另一篇类似文章:图解 Promise 实现原理(三)—— Promise 原型方法实现

6.浏览器垃圾回收机制

原文链接:https://blog.csdn.net/a8725585/article/details/106836648

栈 定义 栈用来在函数执行时存储保存执行上下文环境,我们一般也称调用栈,如基本类型的变量,引用类型的引用地址等都保存在栈中。执行到当前函数时进行入栈,执行完毕进行出栈。 回收方式 有一个记录当前执行状态的指针(称为 ESP)指向活动栈,函数执行完毕,esp下移到后一节点,销毁当前函数执行上下文。新的函数执行上下文入栈直接覆盖掉销毁的空间即可 .

function test() {
  const a = { name: 'a' };
  function showName() {
    const b = { name: 'b' };
  }
  showName();
}
test();

 

堆 定义 对象的值等大数据保存在堆中。

回收方式 使用 JavaScript 中的垃圾回收器进行回收,通过标记的方式进行回收。 采用 可访问性(reachablility)算法来判断堆中的对象是否为活动对象。这个算法其实就将一些GC Root 作为初始存活对象的集合,从 GC Root 对象触发,遍历 GC Root中的所有对象

能够通过 GC Root(全局 wimdow 、dom树、栈上的变量、回收非活动对象占据的内存)遍历到的对象会被认为是可访问的,我们将其标记为活动对象,必须保留 如果一个对象无法通过 GC Root 遍历到,那么就认为这个对象是不可访问的,可能需要被回收,并标记为非活动对象。 堆的垃圾回收 在垃圾回收领域有一个代际假说的概念,大致意思就是数据分为生命周期短的和长久的。 根据代际假说,v8对堆的数据保存分为新生代区域,老生代区域两大块。这两区域的垃圾回收是不同的,分别为副垃圾回收器和主垃圾回收器。

新生代区域 存储空间小,不能存放大对象

老生代区域 空间大,存活时间长。新生代经过2次GC仍存活的的变量

副垃圾回收器 通过将新生代区域平分为2块区域:对象区、空闲区。当活动区中内存满时启动垃圾回收,遍历对象进行垃圾标记,将活动的对象复制到空闲区,移动到空闲区的对象是按顺序来的,因此不用去整理。处理完后,将对象区与空闲区翻转,完成垃圾回收。复制操作需要时间成本,因此为了效率,空间一般设置的很小。空间小,容易填满,因此又采用了对象晋升策略,经过两次垃圾回收依然存活的对象就会被移动到老生区

主垃圾回收器 采用标记-清除(Mark-Sweep)的算法进行垃圾回收。解决标记-清除空间不连续引入标记-整理(Mask-Compact),标记完后,先将所有存活的对象向一端移动,然后直接清理掉这一端以外的内存。

优化效率 并发标记: 在主程序运行阶段,并发的对变量进行标记处理,防止中断js主线程。笔辩主线程、辅助线程同时修改同一对象,需实现读写等功能。

并行整理:进行整理时占用主线程,在此基础上,启动多个辅助线程一起处理,加快速度。

增量整理:由于在整理时是占用的主线程,会阻塞js运行,因此通过增量的方式,将一次处理分成多个小任务处理,减小对用户的影响。js运行与标记相互交叉,防止变量引用在a,b之间变换,造成标记失败,使用黑白灰三色标记法进行处理。

黑白灰三色标记

黑:节点以及子节点已处理完 灰:正在处理的节点。黑节点的子节点不能为白节点,直接置灰(写屏障) 白: 非活动对象,等待回收  

类似文章:垃圾回收机制

7.vue3 watch和watchEffect的使用以及有哪些区别

1.watch侦听器

引入watch

1

import { ref, reactive, watch, toRefs } from 'vue'

对基本数据类型进行监听----- watch特性:

1.具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行

2.参数可以拿到当前值和原始值

3.可以侦听多个数据的变化,用一个侦听起承载

1

2

3

4

5

6

7

setup() {

    const name = ref('leilei')

    watch(name, (curVal, prevVal) => {

    console.log(curVal, prevVal)

 })

}

template: `Name: <input v-model="name" />`

对引用类型进行监听-----

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

setup() {

    const nameObj = reactive({name: 'leilei', englishName: 'bob'})

 监听一个数据

    watch(() => nameObj.name, (curVal, prevVal) => {

    console.log(curVal, prevVal)

 })

 监听多个数据

    watch([() => nameObj.name, () => nameObj.name], ([curName, curEng], [prevName, curEng]) => {

    console.log(curName, curEng, '----', prevName, curEng)

  setTimeout(() => {

   stop1()

  }, 5000)

 })

 const { name, englishName } = toRefs(nameObj)

}

template: `Name: <input v-model="name" /> englishName: <input v-model="englishName" />`

2.watchEffect

没有过多的参数 只有一个回调函数

1.立即执行,没有惰性,页面的首次加载就会执行。

2.自动检测内部代码,代码中有依赖 便会执行

3.不需要传递要侦听的内容 会自动感知代码依赖,不需要传递很多参数,只要传递一个回调函数

4.不能获取之前数据的值 只能获取当前值

5.一些=异步的操作放在这里会更加合适

1

2

3

watchEffect(() => {

    console.log(nameObj.name)

})

侦听器的取消 watch 取消侦听器用法相同

1

2

3

4

5

6

7

8

9

10

11

12

13

const stop = watchEffect(() => {

    console.log(nameObj.name)

 setTimeout(() => {

    stop()

 }, 5000)

})

const stop1 = watch([() => nameObj.name, () => nameObj.name], ([curName, curEng], [prevName, curEng]) => {

    console.log(curName, curEng, '----', prevName, curEng)

  setTimeout(() => {

   stop1()

  }, 5000)

 })

watch也可以变为非惰性的 立即执行的 添加第三个参数 immediate: true

1

2

3

4

5

6

7

8

watch([() => nameObj.name, () => nameObj.name], ([curName, curEng], [prevName, curEng]) => {

   console.log(curName, curEng, '----', prevName, curEng)

 setTimeout(() => {

  stop1()

 }, 5000)

}, {

   immediate: true

})

8.react中useEffect和useLayoutEffect的区别

原文链接:https://blog.csdn.net/qq_38164763/article/details/113532855

使用方式 这两个函数的使用方式其实非常简单,他们都接受一个函数一个数组,只有在数组里面的值改变的情况下才会再次执行 effect。所以对于使用方式我就不过多介绍了,不清楚的可以先参考官网 。

差异 useEffect 是异步执行的,而useLayoutEffect是同步执行的。 useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。 具体表现 我们用一个很简单的例子

import React, { useEffect, useLayoutEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  const [state, setState] = useState("hello world")

  useEffect(() => {
    let i = 0;
    while(i <= 100000000) {
      i++;
    };
    setState("world hello");
  }, []);

  // useLayoutEffect(() => {
  //   let i = 0;
  //   while(i <= 100000000) {
  //     i++;
  //   };
  //   setState("world hello");
  // }, []);

  return (
    <>
      <div>{state}</div>
    </>
  );
}

export default App;

这是它的效果

而换成 useLayoutEffect 之后闪烁现象就消失了

看到这里我相信你应该能理解他们的区别了,因为 useEffect 是渲染完之后异步执行的,所以会导致 hello world 先被渲染到了屏幕上,再变成 world hello,就会出现闪烁现象。而 useLayoutEffect 是渲染之前同步执行的,所以会等它执行完再渲染上去,就避免了闪烁现象。也就是说我们最好把操作 dom 的相关操作放到 useLayouteEffect 中去,避免导致闪烁。

ssr 也正是因为 useLayoutEffect 可能会导致渲染结果不一样的关系,如果你在 ssr 的时候使用这个函数会有一个 warning。

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.

1 这是因为 useLayoutEffect 是不会在服务端执行的,所以就有可能导致 ssr 渲染出来的内容和实际的首屏内容并不一致。而解决这个问题也很简单:

放弃使用 useLayoutEffect,使用 useEffect 代替 如果你明确知道 useLayouteffect 对于首屏渲染并没有影响,但是后续会需要,你可以这样写

import { useEffect, useLayoutEffect } from 'react';
export const useCustomLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;

当你使用 useLayoutEffect 的时候就用 useCustomLayoutEffect 代替。这样在服务端就会用 useEffect ,这样就不会报 warning 了。

源码剖析 那么 useEffect 和 useLayoutEffect 到底是在什么时候被调用的呢?我们去源码中一探究竟。

useEffect 首先找到 useEffect 调用的入口

function updateEffect(create, deps) {
  {
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
    }
  }

  return updateEffectImpl(Update | Passive, Passive$1, create, deps);
}

调用 updateEffectImpl 时传入的 hookEffectTag 为 Passive$1 , 所以我们找一下:Passive$1。

然后我们找到是在这里传入了 Passive$1 类型来调用 useEffect 。

function commitPassiveHookEffects(finishedWork) {
  if ((finishedWork.effectTag & Passive) !== NoEffect) {
    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent:
      case Block:
        {
          // TODO (#17945) We should call all passive destroy functions (for all fibers)
          // before calling any create functions. The current approach only serializes
          // these for a single fiber.
          commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork);
          commitHookEffectListMount(Passive$1 | HasEffect, finishedWork);
          break;
        }
    }
  }
}

那我们继续顺藤摸瓜找 commitPassiveHookEffects

function flushPassiveEffectsImpl() {
    ...省略
    while (_effect2 !== null) {
      {
        setCurrentFiber(_effect2);
        invokeGuardedCallback(null, commitPassiveHookEffects, null, _effect2);
      }
   }
    ...省略
}

老样子,找flushPassiveEffectsImpl

function flushPassiveEffects() {
  if (pendingPassiveEffectsRenderPriority !== NoPriority) {
    var priorityLevel = pendingPassiveEffectsRenderPriority > NormalPriority ? NormalPriority : pendingPassiveEffectsRenderPriority;
    pendingPassiveEffectsRenderPriority = NoPriority;
    return runWithPriority$1(priorityLevel, flushPassiveEffectsImpl);
  }
}

再往上一层是commitBeforeMutationEffects,这里面调用flushPassiveEffects的方法是scheduleCallback,这是一个调度操作,是异步执行的。

function commitBeforeMutationEffects{
    ...省略
    if ((effectTag & Passive) !== NoEffect) {
      // If there are passive effects, schedule a callback to flush at
      // the earliest opportunity.
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalPriority, function () {
          flushPassiveEffects();
          return null;
        });
      }
    }
    ...省略
}

继续顺着 commitBeforeMutationEffects方法往上找的话,我们可以找到最终调用 useEffect 的地方是 commitRootImpl ,这是我们 commit 阶段会调用的一个函数,所以就是在这里面对 useEffect 进行了调度,在完成渲染工作以后去异步执行了 useEffect 。

useLayoutEffect 老样子,从入口找起

function updateLayoutEffect(create, deps) {
  return updateEffectImpl(Update, Layout, create, deps);
}

这里传进去的 hookEffectTag 是Layout,那么我们找一下Layout。

function commitLifeCycles(finishedRoot, current, finishedWork, committedExpirationTime) {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block:
      {
        // At this point layout effects have already been destroyed (during mutation phase).
        // This is done to prevent sibling component effects from interfering with each other,
        // e.g. a destroy function in one component should never override a ref set
        // by a create function in another component during the same commit.
        commitHookEffectListMount(Layout | HasEffect, finishedWork);

        return;
      }

    case ClassComponent:
      {
        var instance = finishedWork.stateNode;

        if (finishedWork.effectTag & Update) {
          if (current === null) {
            startPhaseTimer(finishedWork, 'componentDidMount'); // We could update instance props and state here,
            // but instead we rely on them being set during last render.
            // TODO: revisit this when we implement resuming.

            {
              if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
                if (instance.props !== finishedWork.memoizedProps) {
                  error('Expected %s props to match memoized props before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }

                if (instance.state !== finishedWork.memoizedState) {
                  error('Expected %s state to match memoized state before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }
              }
            }

            instance.componentDidMount();
            stopPhaseTimer();
          } else {
            var prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps : resolveDefaultProps(finishedWork.type, current.memoizedProps);
            var prevState = current.memoizedState;
            startPhaseTimer(finishedWork, 'componentDidUpdate'); // We could update instance props and state here,
            // but instead we rely on them being set during last render.
            // TODO: revisit this when we implement resuming.

            {
              if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
                if (instance.props !== finishedWork.memoizedProps) {
                  error('Expected %s props to match memoized props before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }

                if (instance.state !== finishedWork.memoizedState) {
                  error('Expected %s state to match memoized state before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }
              }
            }

            instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
            stopPhaseTimer();
          }
        }
      ...省略
}

而在这里我们可以看到,class 组件的 componentDidMount生命周期也是在这里被调用的,所以其实useLayoutEffect是和componentDidMount等价的。

而一直往上找最后还是会找到 commitRootImpl方法中去,同时在这个过程中并没有找到什么调度的方法,所以 useLayoutEffect会同步执行。

总结 优先使用 useEffect,因为它是异步执行的,不会阻塞渲染 会影响到渲染的操作尽量放到 useLayoutEffect中去,避免出现闪烁问题 useLayoutEffect和componentDidMount是等价的,会同步调用,阻塞渲染 useLayoutEffect在服务端渲染的时候使用会有一个 warning,因为它可能导致首屏实际内容和服务端渲染出来的内容不一致。

9.Vue中模板渲染原理

原文链接:https://blog.csdn.net/weixin_39503495/article/details/100827445

1、概念 模板:本质字符串。 与字符串的区别: 有逻辑(vue中有v-if、v-for) 嵌入js变量({ {变量}}) 需要转化为html,页面才会识别并正确解析。 2、实现过程 过程:模板(字符串)解析成render函数---->render函数执行返回JS的vnode的结构,是如何实现嵌入变量和执行逻辑。转化为html则由updateComponent函数调用patch方法实现。 例子1(只有js变量、无涉及逻辑):  

<div id="app">
    <p>{
    
      {price}}</p>
  </div>

经过vue模板生成的结果(实现了模板转化为JS的vnode并填充data中的变量): // 此处vm为VUE的实例 //在控制台可以打印vm._c创建元素的,vm_v创建文本节点,vm_s是用来转化为字符串的

vm._c(
        'div',
        {
          attrs: {'id': 'app'}
        },
        [
          vm._c('p', [vm._v(vm._s(vm.price))])
        ]
      )

用法备注:

vm._c是基于snabbdom中的h(‘节点标签’,{属性、特性、事件},[子节点]/文本)实现的,是用来创建vnode的。vue中的render函数,实现了传入Vue实例后返回vnode的功能。 vm._v是用来创建文本节点或实现换行的。 vm._s是用来转化字符串的。 render内部实际是使用with(vm),这样在内部就可以直接使用_c、_v、_s和data中的属性 例子2(以todolist为例子,涉及v-for逻辑):  

<div id="app">
    <div>
      <input v-model="inputValue" type="text" />
      <button v-on:click="handleClick">submit</button>
    </div>
    <div>
      <ul>
        <li v-for='item of list'>
          {
    
      {item}}
        </li>
      </ul>
    </div>
  </div>

v-for逻辑实现是通过v._l()方法,生成对应的li结构后返回数组,将此数组作为ul节点的子节点。以下是返回的vnode:  

with(vm) {
      _c(
        // 第一层(根元素)div
        'div',
        {
          attrs:{"id":"app"}
        },
        // 包含所有子节点的数组
        [
          _c(
            // 第二层第一个div(包含input、button以及其attrs、绑定事件还有值)
           'div',
           [
             _c(
                 'input',
                 {
                  //  v-model数据的双向绑定
                   directives:[
                     {
                       name:"model",
                       rawName:"v-model",
                       value:(inputValue),
                       expression:"inputValue"
                      }
                    ],
                   attrs:{
                     "type":"text"
                    },
                   domProps:{
                     "value":(inputValue)
                    },
                   on:{
                    // 监听input内容,数据改变时,让Model层的数据更新
                     "input":function($event){
                       if($event.target.composing)return;
                       inputValue=$event.target.value
                      }
                    }
                  }
                ),
               _v(" "),
               _c(
                   'button',
                   {
                    // 渲染绑定事件
                     on:{
                       "click":handleClick
                      }
                    },
                   [_v("submit")]
                  )
            ]),
          _v(" "),//创建空文本节点,用于换行
          _c(
            // 第二层第二个div(包含ul、li,其中li是通过for循环生成的)
            'div',
              [
                _c(
                  // ul标签
                  'ul',
                  // _l返回的是包含子元素的数组
                  _l((list),function(item){
                    // v-for循环,实际是返回所有创建li标签的数组
                    return _c(
                      'li',
                      [
                        _v("\n          "+_s(item)+"\n        ")
                      ]
                     )
                  }),0)//数字表示list有多少项,要返回多少个li
              ]
            )
        ]
        )}
 }

3、总结: 个人理解(有误的话,欢迎指正): 1、 模板解析成render函数---->返回JS模拟的虚拟DOM结构:模板是一段字符串,模板解析生成render函数,执行render函数返回为vnode,vnode表明了各个节点的层级关系、特性、样式、绑定的事件。 2、 vnode---->html:通过 updateComponent函数调用vm._update()传入vnode,利用基于snabbdom的patch()方法改造的生成真实DOM节点并渲染页面。 注:vm.__patch__:初次渲染时,调用vm.__patch__(containe, vnode),生成真实的DOM结构渲染到容器中。re-render时,调用vm.__patch__(vnode, newVnode)利用diff算法对比新旧vnode之间的差异,生成需要更新的真实DOM,渲染到容器的对应位置。

标签: ygm219微风速变送器精轧螺纹钢连接器ygm28mm

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台