阅读更多
引用
来源:GitBook
作者:大师兄

作为一名前端,一直以来以精通Javascript为目标。其实说实话精通真的挺难,不是你记住全部的API就算是精通。

JavaScript的知识非常零散而庞杂,很多情况下上周学习的知识下周或是下个月就会忘记,给人的感觉就是好难学,怎么一直没进步呢?我们不能仅限于语言本身,我们要透过语法看到深层次的运行机制。掌握了Javascript运行机制,就好比学武术,大神级别都讲究“无招胜有招”。懂得了机制就可以举一反三,灵活运用。

事件循环机制(Event Loop),则是理解运行机制最关键的一个点。我们先抛出一个面试题:
setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

先思考,这段代码会输出什么?

单线程的JavaScript

JavaScript是单线程的,我想大家都不会怀疑吧。那什么是单线程?

当初我对“js是单线程”的理解仅限于js的代码是一行一行执行的,不会出现同时执行两行的代码的情况,可是这个理解是太浅显,遇到异步请求就懵逼了,怎么不按我的想法走呢?还用说想法有问题呗。

所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。JS运行在浏览器中,是单线程的,每个window一个JS线程。

第一个问题,为啥要是单线程,多线程不好吗?减轻cpu的压力。现在如果有两个线程,一个线程修改页面某一个dom元素,正巧另一个线程将这个元素给删除了。这不是混乱了么。所以单线程是有原因的。

那你又有疑问了,既然是单线程的,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。那不行啊,我们总不能一直等着啊,前端需要调用后端接口取数据,这个过程是需要响应时间的,那执行这个代码的时候浏览器也等着?答案是否定的。

其实还有其他很多类线程(应该叫做任务队列),比如进行ajax请求、监控用户事件、定时器、读写文件的线程(例如在NodeJS中)等等。这些我们称之为异步事件,当异步事件发生时,将他们放入执行队列,等待当前代码执行完成。就不会长时间阻塞主线程。等主线程的代码执行完毕,然后再读取任务队列,返回主线程继续处理。如此循环这就是事件循环机制。

总结一下:

  • 我们可以认为某个同域浏览器上下文中 JavaScript 只有一个主线程、函数调用栈以及多个任务队列。
  • 主线程会依次执行代码,当遇到函数时,会先将函数入栈,函数运行完毕后再将该函数出栈,直到所有代码执行完毕。
  • 当函数调用栈为空时,即会根据事件循环(Event Loop)机制来从任务队列中提取出待执行的回调并执行,执行的过程同样会利用函数栈。
  • 所有同属一个的窗体都共享一个事件循环,所以它们可以同步交流。不同窗体之间相互独立,互不干扰。
你如果想彻底研究清楚事件模型,那还需要了解如下知识:
  • Javascript的队列数据结构
  • Javascript的执行上下文
  • 函数调用栈(call stack)
我们会分为两节来学习,队列数据结构为一节,执行上下文和函数调用栈合在一起为一节。

Javascript的内存空间

队列数据结构

我们知道Javascript中有两种基本的种数据结构堆(heap)和栈(stack),还有一个队列(queue),并不是严格意义上的数据结构。

栈数据结构

在我们平时的工作过程中我们写javascript代码并不关心数据结构,但是它确实彻底理解某些运行机制的必不可少的部分。

JavaScript中并没有严格的去区分栈内存与堆内存。我们平时基本都认为JavaScript的所有数据(变量、函数)都保存在堆内存中。但是在某些场景,我们仍然需要基于堆栈数据结构的思维(看好这里是思维)来看待,比如JavaScript的执行上下文。

要简单理解栈的存取方式,我们可以通过类比乒乓球盒子来分析。如下图左侧。

我们用栈存取数据的方式类比成乒乓球的存放方式,处于盒子中最顶层的乒乓球5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球1,就必须将上面的4个乒乓球取出来,让乒乓球1处于盒子顶层。这就是栈空间先进后出,后进先出的特点。

堆数据结构

堆数据的存取数据的方式和与书架与书非常相似。

书架上放满了不同的书,我们只要知道书的名字我们就可以很方便的取出,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。在JSON格式的数据中,我们存储的key-value是可以无序的,我们并不关心顺序,我只要通过key取出value即可。

队列

在JavaScript中,理解队列数据结构的目的主要是为了理解事件循环(Event Loop)的机制。在后续的章节中我会详细分析事件循环机制。

队列是一种先进先出(FIFO)的数据结构。正如排队过安检一样,排在队伍前面的人一定是最先过检的人。用以下的图示可以清楚的理解队列的原理。

执行上下文and函数调用栈

这节我们稍微研究一下JavaScript中最基本的部分——执行上下文(Execution Context), 读完后,你应该清楚了解释器做了什么,为什么函数和变量能在声明前使用以及他们的值是如何决定的。

每当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境一般有两种:
  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
其实这里还应该有一个eval环境,不推荐用eval,今天也就不谈。

因此在一个JavaScript程序中,必定会产生多个执行上下文,JavaScript引擎会以栈的方式来处理它们,我们称其为函数调用栈(call stack)。

栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

遇到以上两种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。

为了更加清晰的理解这个过程,根据下面的例子,结合图示给大家展示。

执行上下文可以理解为函数执行的环境,每一个函数执行时,都会给对应的函数创建这样一个执行环境。

modify.js
var name= 'dsx';
function modifyName() {
    var secondName= 'gwj';
    function swapName() {
        var temp = secondName;
        secondName= name;
        name= temp ;
    }
     swapName();
}
changeColor();

我们用ECStack来表示处理执行上下文组的堆栈。我们很容易知道,第一步,首先是全局上下文入栈。

全局入栈后,开始执行可执行代码,直到遇到了modifyName(),这一句激活函数modifyName创建它自己的执行上下文,因此第二步就是modifyName的执行上下文入栈。

modifyName入栈之后,继续执行函数内部可执行代码,遇到swapName()之后又激活了swapName执行上下文。因此第三步是swapName的执行上下文入栈。

在swapName的内部再没有遇到其他能生成执行上下文的代码,因此这段代码顺利执行完毕,swapName的上下文从栈中弹出。

swapName的执行上下文出栈后,继续执行modifyName其他可执行代码,也没有再遇到其他执行上下文,顺利执行完毕之后出栈。这样,ECStack中就只身下全局上下文了。

全局上下文在浏览器窗口关闭后出栈。
注意: 第一、函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。 第二、不要把执行上下文和作用域链混为一谈

如上我们演示了整个modif.js的执行过程。总结一下:
  • 单线程,依次自顶而下的执行,遇到函数就会创建函数执行上下文,并入栈
  • 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
事件循环

现在我们知道JavaScript的单线程,以及这个线程中拥有唯一的一个事件循环机制。那什么事件循环机制是什么?且看下文分析。

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。

根据上面的分析,任务队列的特点是先进先出。

一个js文件里事件循环只有一个,但是任务队列可以有多个。任务队列又可以分为macro-task(task)与micro-task(job)。

macro-task(task)包括:
  • setTimeout/setInterval
  • setImmediate
  • I/O操作
  • UI rendering
micro-task(job)包括:
  • process.nextTick
  • Promise
  • Object.observe(已废弃)
  • MutationObserver(html5新特性)
浏览器中新标准中的事件循环机制与nodejs类似,其中会介绍到几个nodejs有,但是浏览器中没有的API,大家只需要了解就好。比如process.nextTick,setImmediate

我们称他们为事件源, 事件源作为任务分发器,他们的回调函数才是被分发到任务队列,而本身会立即执行。

例如,setTimeout第一个参数被分发到任务队列,Promise的then方法的回调函数被分发到任务队列(catch方法同理)。

不同源的事件被分发到不同的任务队列,其中setTimeout和setInterval属于同源

整体代码开始第一次循环。全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的job。当所有可执行的job执行完毕之后。循环再次从task开始,找到其中一个任务队列执行完毕,然后再执行所有的job,这样一直循环下去。

无论是task还是job,都是通过函数调用栈来完成。

这个时候我们是不是有一个大发现,除了首次整体代码的执行,其他的都有规律,先执行task任务队列,再执行所有的job并清空job队列。再执行task--job--task--job......,往复循环直到没有可执行代码。

那我们可不可以这么理解,第一次script代码的执行也算是一个task任务呢,如果这么理解那整个事件循环就很容易理解了。

来走一个栗子:
console.log(1);

new  Promise(function(resolve){
    console.log(2);
    resolve();
}).then(function(){
    console.log(3)
})

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

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

一下子写了这么多,是不是感觉有点复杂啊,不过没关系,我们一步一步来分析。

第一步,开始执行代码,global入栈,执行到第一个console.log(1),直接输出1。

第二步、执行遇到了Peomise,Promise构造函数的回调函数是同步执行,直接输出2。它的then方法才是任务源,将会分发一个job任务。
 new  Promise(function(resolve){
        console.log(2);
        resolve();
    }).then(function(){
        console.log(3)
    })



第三步、执行到setTimeout,作为task任务分发源,分发一个任务出去。
setTimeout(function(){
        console.log(4);
        process.nextTick(function(){
            console.log(5);
         })
        new  Promise(function(resolve){
            console.log(6);
            resolve()
        }).then(function(){
            console.log(7)
        })
    })


第四步、执行遇到process.nextTick, 一个job任务分发器,分发一个job任务。
process.nextTick(function(){
      console.log(8)
})


第五步、执行遇到setImmediate, 一个task任务分发器,分发一个task任务到任务队列。并且会在setTimeout的任务队列之后执行。
setImmediate(function(){
       console.log(9);
       new  Promise(function(resolve){
               console.log(10);
               resolve()
           }).then(function(){
               console.log(11)
           })
          process.nextTick(function(){
              console.log(12);
           })
 })


这样script代码的第一轮执行完毕,在执行的过程中会遇到不同的任务分发器,分发到对应的任务队列。接下来将会执行所有的job队列的任务。
注意:nextTick任务队列会比Promise的队列先执行(别问为什么,我也不知道)

这阶段会依次输出 8 3。

执行完所有的job任务后,就会循环下一次,从task开始,根据图示,会先执行Time_out1队列。

task任务的执行也是要借助函数栈来完成,也就是说回到主线程。

会先依次输出4和6,然后依次分发nextTick2和promise_then2两个job任务。

第一个task任务执行完不会立即执行其他task任务,会执行刚才被分发的job任务
在这个过程中会依次输出5和7

现在就剩下一个task任务setIm1,按照同样的方式进行再次循环 。示意图如下:

以上阶段会依次输出9和10。
最后执行job任务,依次输出12和11。

完事,这个事件循环就完成了,我想是很清楚了。最终的输出结果是:

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

最后我们用node执行一下我们写的这个例子。结果如下:

Vue中的nextTick()实现原理
new Vue({
  el: '#app',
  data: {
    list: []
  },
  mounted: function () {
    this.get()
  },
  methods: {
    get: function () {
      this.$http.get('/api/article').then(function (res) {
        this.list = res.data.data.list
        // this.$refs.list引用了ul元素,我想把第一个li颜色变为红色
        this.$refs.list.getElementsByTagName('li')[0].style.color = 'red'
      })
    },
  }
})

我在获取到数据后赋值给data对象的list属性,然后我想引用ul元素找到第一个li把它的颜色变为红色,但是事实上,这个要报错的。我们知道,在执行这句话时,ul下面并没有li,也就是说刚刚进行的赋值操作,当前并没有引起视图层的更新。

因为Vue的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

因此,在这样的情况下,vue给我们提供了$nextTick方法,如果我们想对未来更新后的视图进行操作,我们只需要把要执行的函数传递给this.$nextTick方法,vue在更新完视图后就会执行我们的函数帮我们做事情。

$nextTick()原理:

Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

看过上一个chat(Vue.2x 源码分析之响应式原理)的同学应该有了解,vue里面有一个watcher,用于观察数据的变化,数据有变化就会更新dom。

但是vue并不是每次数据改变都会立即触发更新dom,而是将这些操作都缓存在一个队列,如果同一个 watcher 被多次触发,只会一次推入到队列中。这样可以避免不必要的重复计算和 DOM 操作,提升性能。

那什么时候更新DOM呢?在下一个事件循环“tick”中,Vue 刷新队列并执行,统一执行(已去重的)dom的更新操作。

在前面我们花了大量篇幅来介绍javascript的事件循环机制,应该知道事件循环中有两种任务队列, macro-task(task) 和 micro-task(job)。

引擎在每个 task 执行完毕,从队列中取下一个 task 来执行之前,会先执行完所有 micro-task(job)队列中的 job。

setTimeout 任务源会分配回调到一个新的 task 中执行,而 Promise 的 then、MutationObserver 的回调都会被安排到一个新的 job 中执行,会比 setTimeout 产生的 task 先执行。

想要要创建一个新的 job,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。实在不行,只能用 setTimeout 创建 task 了。

为啥要用 job?根据HTML Standard,在每个 task 运行完以后,UI 都会重渲染,那么在 job 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

总结$nextTick触发的时机:

同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发

$nextTick源码:
//首先,这个函数是采用了一个单利模式还是什么创建的一个闭包函数
export  const  nextTick = (function(){
    // 缓存函数的数组
    var callbacks = [];
    // 是否正在执行   
    var pending = false;  
    // 保存着要执行的函数
    var timerFunc;  
})()

首先定义一些变量,供后面调用。接下来是一个函数:
//执行并且清空所有的回调列表
  function nextTickHandler() {
    pending = false;
    //拷贝出函数数组副本
    const copies = callbacks.slice(0);
    //把函数数组清空
    callbacks.length = 0;
    //依次执行函数
    for (let i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }

这个函数就是$nextTick内实际调用的函数。

接下来,是vue分了三种情况来延迟调用以上这个函数,因为$nextTick目的就是把传进来的函数延迟到dom更新后再使用,所以这里依次优雅降序的使用js的方法来做到这一点。

利用promise.then延迟调用
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve();
  var logError = function (err) { console.error(err); };
  timerFunc = function () {
    p.then(nextTickHandler).catch(logError);

    // 在部分 iOS 系统下的 UIWebViews 中,Promise.then 可能并不会被清空,因此我们需要添加额外操作以触发
    if (isIOS) { setTimeout(noop); }
  };

如果浏览器支持Promise,那么就用Promise.then的方式来延迟函数调用,Promise.then方法可以将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数。从而做到延迟。

MutationObserver 监听变化

else if (typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  / 当 Promise 不可用时候使用 MutationObserver
  var counter = 1;
  var observer = new MutationObserver(nextTickHandler);
  var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true
  });
  timerFunc = function () {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
}

MutationObserver是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数。

具体有一下几点变动的监听:
  • childList:子元素的变动
  • attributes:属性的变动
  • characterData:节点内容或节点文本的变动
  • subtree:所有下属节点(包括子节点和子节点的子节点)的变动
可以看出,先创建了一个文本节点,来改变文本节点的内容来触发的变动,因为我们在数据模型更新后,将会引起dom节点重新渲染,所以,我们加了这样一个变动监听,用一个文本节点的变动触发监听,等所有dom渲染完后,执行函数,达到我们延迟的效果。

setTimeout延迟器

else {
    timerFunc = function () {
      setTimeout(nextTickHandler, 0);
    };
  }

这个很简单哈。利用setTimeout的延迟原理,setTimeout(func, 0)会将func函数延迟到下一次函数调用栈的开始,也就是当前函数执行完毕后再执行该函数,因此完成了延迟功能。

但是我们看到的$nextTick是一个函数啊,这里就是一个自执行函数,并不是一个函数啊,没错我们还需要返回一个闭包函数才可以。往下看:

闭包函数

return function queueNextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) { cb.call(ctx); }
      if (_resolve) { _resolve(ctx); }
    });
    // 如果没有函数队列在执行才执行
    if (!pending) {
      pending = true;
      timerFunc();
    }
    // promise化
    // 如果没有传入回调,则表示以异步方式调用
    if (!cb && typeof Promise !== 'undefined') {
      console.log('进来了')
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }

这个return的函数就是我们实际使用的闭包函数,每一次调用$nextTick函数,都会向callbacks这个函数数组入栈。然后监听当前是否正在执行,如果没有,执行函数。下面一个if是promise化,如果没有传入回调,则表示以异步方式调用。

后记

现在 Vue()的 nextTick 实现移除了 MutationObserver 的方式(兼容性原因),取而代之的是使用 MessageChannel。

并且加入了setImmediate。

有兴趣的同学可以继续去学习,原理我们已经说的很明白。源码传送门
  • 大小: 12.6 KB
  • 大小: 13.8 KB
  • 大小: 6.7 KB
  • 大小: 8.5 KB
  • 大小: 10.3 KB
  • 大小: 8.5 KB
  • 大小: 6.7 KB
  • 大小: 13.8 KB
  • 大小: 9.8 KB
  • 大小: 11.9 KB
  • 大小: 14.3 KB
  • 大小: 17.2 KB
  • 大小: 19.5 KB
  • 大小: 15.8 KB
  • 大小: 17.2 KB
  • 大小: 12.7 KB
  • 大小: 15.8 KB
  • 大小: 10 KB
  • 大小: 36.1 KB
来自: gitbook
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 利用储存导入链接服务器的所有用户表

    --本例链接到FOXPRO数据库, SQL Server本地实例PSMS--一、   创建四个储存过程--1、建立链接服务器CREATE     PROCEDURE P_CreateSrv      @server nvarchar(30)=PSMS,     @DBPath nvarchar(30)=C:/PSMS_DB1ASDECLARE @SourceDB nvarcha

  • 理解event Loop与vue中的nextTick(浏览器)

    event Loop、nextTick

  • Vue.nextTick核心原理

    相信大家在写vue项目的时候,一定会发现一个神奇的api,。为什么说它神奇呢,那是因为在你做某些操作不生效时,将操作写在内,就神奇的生效了。那这是什么原因呢?让我们一起来研究一下。

  • 从JS事件循环(Event Loop)机制到vue.nextTick的实现

    前言 众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言。 ... 因为如果在DOM操作中,有两个线程一个添加节点,一个...长期来看,JS将一直是单线程。 为何非阻塞?因为单线程意味着任.

  • Vue中$nextTick原理

    1.$nextTick作用 如下图例子,文本改变后,响应式数据处理后,在mouted中获取到box高度都是0 &lt;div id='app'&gt; &lt;div class='box'&gt; {{msg}} &lt;/div&gt; &lt;/div&gt; &lt;script&gt; let app = ...

  • Vue.nextTick实现及原理探究

    Vue.nextTick实现及原理探究 一. nextTick() 使用场景/语法/分类 使用场景 当更新状态(数据)后,需要对新DOM做一些操作,但这时我们获取不到更新后的DOM,因为还没有重新渲染。nextTick接收一个回调函数作为参数...

  • 结合Event Loop谈谈对Vue中nextTick的理解

    本文结构- 带着问题看这篇文章-event loop中任务的执行顺序-微任务 &amp; 宏任务- Vue中nextTick的实现-对nex...

  • 【VUE2源码学习】nextTick 实现原理

    什么是nextTick? 定义: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM 看完是不是有一堆问号?我们从中找出来产生问号的关键词 ...这涉及到了 js 的执行机制。 JS

  • vue中nextTick()原理

    JS运行机制(Event Loop) JS执行是单线程的,它是基于事件循环的 所有同步任务都在主线程上执行,形成一个执行栈。 主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件。 当执行...

  • 【VUE3源码学习】nextTick 实现原理

    什么是nextTick? 定义: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

  • 从 Event Loop 角度解读 Vue NextTick 源码

    解读背景在学习vue源码,nextTick方法借助了浏览器的event loop事件循环做到了异步更新。在公司面试的时候,笔试题最喜欢出关于JavaScript运行机制,Pr...

  • 说说Vue.nextTick 的原理和用途

    Vue.nextTick在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

  • 全面解析Vue.nextTick实现原理

    vue中有一个较为特殊的API,nextTick。根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调,用法如下: // 修改数据 vm.msg='Hello' // DOM 还没有更新 Vue.nextTick(function(){ // DOM 更新...

  • Vue中 $nextTick 原理及作用

    Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。 我们可以理解成,Vue在更新DOM时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再...

  • Vue 中 nextTick 原理和应用场景

    在 Vue 官方文档中,nextTick 是这么说明的: 在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。 原理 1.异步执行 Vue实现响应式并不是数据发生变化之后 DOM 就立即...

  • vue2和vue3中nextTick的底层原理详解

    vue2和vue3中nextTick的底层原理详解

  • 技术文章 | JavaScript Event Loop机制详解与Vue.js中nextTick的实践应用

    本文依次介绍了函数调用栈、MacroTask 与 MicroTask 执行顺序、浅析 Vue.js 中 nextTick 实现等内容;本文中引用的参考资料统一声明在 JavaScript 学习与实践资料索引。

  • SQLServer2005 身份证函数,含验证和15位转18位

    Author:水如烟SQLServer2005 身份证函数,含验证和15位转18位USE [LzmTWWorks]GO/****** 对象:  UserDefinedFunction [Helper].[IDCard]    脚本日期: 12/07/2007 23:21:37 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCRE

Global site tag (gtag.js) - Google Analytics