精华内容
下载资源
问答
  • js的事件循环机制比较简单 先执行主线程代码,执行完毕后,清空微任务队列,然后取出一个宏任务,然后清空微任务队列,如此循环 Node的事件循环比较复杂  Node的事件循环分为六个阶段 (1)timers计时器 执行...

    宏任务:setTimeout,setInterval,

    微任务:promise的回调

    js的事件循环机制比较简单

    先执行主线程代码,执行完毕后,清空微任务队列,然后取出一个宏任务,然后清空微任务队列,如此循环

    Node的事件循环比较复杂 

    Node的事件循环分为六个阶段

    (1)timers计时器  执行setTimeoutsetInterval的回调函数

    (2)I/0 callbacks  执行I/O callback被延迟到下一阶段执行;

    (3)idle, prepare   队列的移动,仅内部使用

    (4)poll 轮询阶段 这个阶段是用来执行和 IO 操作有关的回调的,Node会向操作系统询问是否有新的 IO 事件已经触发,然后会执行响应的事件回调。几乎所有除了 定时器事件、 setImmediate() 和 close callbacks 之外操作都会在这个阶段执行。

    (5)check 这个阶段会执行 setImmediate() 设置的任务

    (6)close  执行close事件的callback,例如socket.on("close",func) 如果一个 socket 或 handle(句柄) 突然被关闭了,例如通过 socket.destroy() 关闭了,close事件将会在这个阶段发出。

    参考:https://juejin.im/post/5b5f365e6fb9a04fa8673f97

    https://juejin.im/post/5b50af02e51d45190f4ab64b

    https://cnodejs.org/topic/5a9108d78d6e16e56bb80882

    展开全文
  • Android消息循环机制

    万次阅读 2016-06-19 01:56:37
    转载请注明出处:http://blog.csdn.net/crazy1235/article/details/50771703Android的消息循环机制主要先关的类有: Handler Looper Message MessageQueue ActivityThread 实际上应用程序启动的时候,会创建一个UI...

    转载请注明出处:http://blog.csdn.net/crazy1235/article/details/51707527


    Android的消息循环机制主要先关的类有:

    • Handler
    • Looper
    • Message
    • MessageQueue
    • ActivityThread

    实际上

    应用程序启动的时候,会创建一个UI线程,然后该线程关联一个消息队列,相关操作封装一个个消息放入队列中,主线程会不断循环从队列中取出消息进行分发处理。


    为什么用Handler

    大家都知道,Android规定【访问UI只能在主线程中进行】,如果在子线程中访问UI,程序会出现异常。

        throw new CalledFromWrongThreadException("only the original thread that created a view hierarchy can touch its views.");

    所以只能在主线程中访问UI,但是Android又不建议在主线程中做耗时操作,比如IO操作、网络请求等操作,否则容易引起程序无法响应(ANR)。所以想这些耗时操作,都会放到其他的线程中进行处理,但是非UI线程又无法操作UI,所以 Handler 就派上用场了。

    Handler的作用就是将一个任务切换到另外一个线程中执行。而我们主要用它来 更新UI


    Handler的基本使用

    先来看看Handler的基本使用。
    它的使用方法分为两套: “post”方法和“send”方法。

        private Handler handler2 = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
        });
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case 1:
                        testview.setText("处理完毕");
                        break;
                    default:
                        break;
                }
            }
        };
        new Thread(){
                @Override
                public void run() {
                    // 一些耗时的操作
    
                    handler.sendEmptyMessage(1);
                }
            }.start();
        handler.post(new Runnable() {
                @Override
                public void run() {
                    testview.setText("post");
                }
            });
    
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    testview.setText("postDelayed");
                }
            }, 5000);

    send 系列有 7 个方法:

    这里写图片描述


        /**
         * 1. 发送一个消息 
         */
        public final boolean sendMessage(Message msg){
            return sendMessageDelayed(msg, 0);
        }
        /**
         * 2.  发送一个空消息
         */
        public final boolean sendEmptyMessage(int what){
            return sendEmptyMessageDelayed(what, 0);
        }
        /**
         * 3. 定时发送一个消息
         */
        public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
            Message msg = Message.obtain();
            msg.what = what;
            return sendMessageAtTime(msg, uptimeMillis);
        }
        /**
         * 4.  延迟发送一个空的消息
         *     内部调用了 sendMessageDelayed() 方法 
         */
        public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
            Message msg = Message.obtain();
            msg.what = what;
            return sendMessageDelayed(msg, delayMillis);
        }
        /**
         * 5. 发送一个消息到消息队列的头部
         */
        public final boolean sendMessageAtFrontOfQueue(Message msg) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, 0);
        }
        /**
         * 6. 定时发送一个消息
         */
        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
        /**
         * 7. 延迟delayMillis时间发送一个消息
         */
        public final boolean sendMessageDelayed(Message msg, long delayMillis){
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }


    通过以上代码可以发现,重载方法相互调用,最终都是调用了enqueueMessage()方法。


    post 系列有 5 个方法:

    这里写图片描述

        /**
         * 1. 
         */
        public final boolean post(Runnable r){
           return sendMessageDelayed(getPostMessage(r), 0);
        }
        /**
         * 2. 
         */
        public final boolean postDelayed(Runnable r, long delayMillis){
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
        /**
         * 3. 
         */
        public final boolean postAtTime(Runnable r, long uptimeMillis){
            return sendMessageAtTime(getPostMessage(r), uptimeMillis);
        }
        /**
         * 4.  
         */
        public final boolean postAtFrontOfQueue(Runnable r){
            return sendMessageAtFrontOfQueue(getPostMessage(r));
        }
        /**
         * 5.  
         */
        public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
            return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
        }


    从以上代码中可以看出,post的这几个方法都是调用的send的先关方法。只不过 通过getPostMessage()的几个重载方法,将Runnable封装成了Message

    来看看getPostMessage的重载方法:

        /**
         * 设置消息对象的callback, 返回Message对象
         * 当调用dispatchMessage()方法的时候,判断Message的callback是否为空,不为空时,调用callback的run()方法。
         */
        private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    
        /**
         * 将Runnable和Object封装成一个Message对象返回。
         */
        private static Message getPostMessage(Runnable r, Object token) {
            Message m = Message.obtain();
            m.obj = token;
            m.callback = r;
            return m;
        }

    一共有两个,都是通过 Message.obtain() 方法获取一个消息对象,然后重新对内部变量赋值,然后返回该Message对象。

    我们先记下这个方法,稍后进行探索。


    综上,不管是post方法,还是send方法,最后都牵扯到 enqueueMessage 这样一个方法。

        /**
         * 将message添加到消息队列
         */
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this; // 这里将message的target对象赋为handler
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    该方法内部,将message的target赋值为当前Handler对象,可见 target是Handler类型的对象

    最后调用了queue.enqueueMessage()对象。

    queue是MessageQueue类型的变量 ,表示一个消息队列。我们先不管该变量什么时候初始化的,先看看这个方法。

        Message mMessages;
    
    
        /**
         * 消息入队操作
         */
        boolean enqueueMessage(Message msg, long when) {
            // ...
    
            synchronized (this) {
                // ...
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
    
                //当mMessage为空 或者 when是0 后者when小于对头的when值时,将当前msg作为对头。
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // 当msg添加到队列中间。
    
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // ...
            }
            return true;
        }

    从上面代码可以看出,消息队列实际上是通过 单链表 的结构来实现的。其内部逻辑也好懂,就是通过对when和当前对头指针的一些判断逻辑,进而将参数中的message添加到单单链表中。


    说到这里,进行一个小总结:
    当我们使用Handler的时候,通过post或者send的一些列方法时,实际上是把一个Message(消息)添加到MessageQueue(消息队列)中去。


    Looper

    Looper可以称之为“消息循环”。我们将消息放到消息队列之后,还需要通过Looper从队列中取出消息进行处理。

    主线程也就是ActivityThread,它被创建的时候,会调用 ActivityThread.main() 方法。

    ActivityThread.java 的部分源码:

        static Handler sMainThreadHandler;  // set once in main()
    
        public static void main(String[] args) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
            //...
    
            Process.setArgV0("<pre-initialized>");
    
            //初始化当前线程为looper
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
    
            // End of event ActivityThreadMain.
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    
            //开始运行线程中的 消息队列 -- message queue
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

    主要代码是这两句:

    • Looper.prepareMainLooper();

    • Looper.loop();

    这又牵涉到了Looper.java类。

    接着往下看。

    先把Looper类中的先关代码贴出来:

    // sThreadLocal.get() will return null unless you've called prepare().
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        private static Looper sMainLooper;  // guarded by Looper.class
        final MessageQueue mQueue;
        final Thread mThread;
        //系统调用此方法,初始化当前线程为looper,作为一个应用程序的主looper。
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {//一个线程只允许存在一个looper对象。
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
        //Looper构造函数 -- 私有化
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    
        //返回绑定到当前线程上的Looper对象
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }

    通过以上代码,我们可以看出:

    • Looper类中维护一个消息队列和一个ThreadLocal类型的对象。

    • Looper的构造函数是private类型,私有化的。 所以只能通过 prepare(boolean) 方法来进行初始化。

    • 在Looper的构造函数中对MessageQueue进行了初始化。

    • 一个线程只允许存在一个looper对象。否则会出现运行时异常。


    当ActivityThread.main方法准备好Looper之后,此时队列就和线程关联了起来。,然后调用了Looper的loop()方法。

        final MessageQueue mQueue;
        final Looper mLooper;
        final Callback mCallback;
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {//如果me是null,则表示没有调用prepare()方法。
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;// 1. 获取消息队列
    
            // ...
    
            for (;;) { // 2. 死循环--消息循环
                Message msg = queue.next(); // 3. 获取消息 (从消息队列中阻塞式的取出Message)
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // ...
    
                // Message对象内部有一个Handler对象target。故,实际上调用的handler的dispatchMessage
                // 故,实际上调用的handler的dispatchMessage(msg)方法进行分发。
                msg.target.dispatchMessage(msg); // 4. 分发处理消息
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // ...
    
                msg.recycleUnchecked();// 5. 回收消息
            }

    loop方法实际上建立了一个死循环,一直从消息队列中读取消息,然后调用Message的target对象,实际上就是Handler对象的 dispatchMessage(msg) 方法进行处理。

    (上面提到了,target实际上是Handler类型的对象)


    接着来看Handler.dispatchMessage(msg)

        /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {// 当message对象中Runnable类型的callback不为空时
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg); // 调用重写的方法
            }
        }

    当我们使用Handler.postxx()之类的方法时,会传入一个Runnable对象,这种情况下,“创建”的Message的callback变量不是null,dispatchMessage方法里面调用 handleCallback(msg) ,然后就调用了run()方法;

        // 调用runnable类型的run()方法
        private static void handleCallback(Message message) {
            message.callback.run();
        }

    如果不是采用Handler.postxxx()之类的方法,也即callback变量为null,dispatchMessage方法里面调用handlerMessage(msg);

        // 实现该接口时,重写该方法。
        public interface Callback {
            public boolean handleMessage(Message msg);
        }
    
        /**
         * 使用Handler的时候,会重写该方法。
         */
        public void handleMessage(Message msg) {
        }



    综上:

    通过dispatchMessage(msg)方法的分发处理,就可以将事件在主线程中处理。
    

    Handler的构造函数

    上面提到,handler的7+5个方法都是调用的 MessageQueue.enqueueMessage() 方法把消息添加到消息队列中去。

    实际上消息队列就是在Handler的构造函数中 获取 的。

    看看它的构造方法:

        /**
         * 无参构造函数
         */
        public Handler() {
            this(null, false);
        }
    
        /**
         * Callback就是那个接口
         */
        public Handler(Callback callback) {
            this(callback, false);
        }
    
        /**
         * 通过looper对象构造
         */
        public Handler(Looper looper) {
            this(looper, null, false);
        }
    
        public Handler(Looper looper, Callback callback) {
            this(looper, callback, false);
        }
    
        public Handler(boolean async) {
            this(null, async);
        }
    
        /**
         * 进行一些初始化操作
         * 可以看出当使用无参的Handler的时候,mCallback对象是null。
         */
        public Handler(Callback callback, boolean async) {
            // ...
    
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;// 获取Looper中保存的消息队列
            mCallback = callback;
            mAsynchronous = async;
        }
    
        /**
         * 指定Looper对象的构造函数很简单
         */
        public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }

    我们最常用的就是无参的构造。
    无参构造又调用了 public Handler(Callback callback, boolean async) 这个方法。
    该方法内部调用了Looper类的 myLooper() 方法。
    然后又从looper中取出messagequeue为Handler里的mQueue 赋值。


    综上,当调用Handler的send或者post相关方法时,把消息添加进的消息队列是从Looper对象中获取的。而Looper的消息队列是new出来的,是在ActivityThread.main()中调用Looper.prepareMainLooper(),然后调用Looper.prepare(boolean)方法,在这个方法里面new了一个Looper对象,而Looper的私有构造函数中正好创建了一个消息队列。


    此时,【消息产生->添加到消息队列->处理消息】 这个流程就走通了。

    图片名称


    Message.obtain()

    回过头来看getPostMessage()的两个重载方法,内部调用了 Message.obtain() 方法。

    该方法用来获取一个Message对象,而不是直接new一个Message。

        // Message采用链式存储结构,内部存储指向下一个message的“指针”
        Message next;
    
        private static final Object sPoolSync = new Object(); //
        private static Message sPool; //
        private static int sPoolSize = 0; //消息池大小
    
        /**
         * 直接从message池中返回一个对象,在多数情况下,都不需要重新new一个对象,
         * 从而节省了开销。
         */
        public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }


    看到这段代码,就发现一个“池”的概念。原来android使用了类似于“线程缓冲池”之类的“消息池”,用来保存一些处理完毕不用的Message对象,以便于下次使用时可以直接从池中获取,而不是直接创建一个新的message,从而节省了内存开销。当池中没有时,才回去new一个新的Message返回。

    那既然有从池中获取的方法,当然也要有将对象放入池中的方法。

    在Message类中有 recycler() recyclerUnchecked() 两个方法。

        /**
         * 将message对象回收
         */
        public void recycle() {
            if (isInUse()) { //是否还在使用
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            recycleUnchecked();
        }
    
        /**
         * 重置状态,将消息放到消息池中
         */
        void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }


    Message类内部维护一个链表来存储被回收的Message对象,recycler()方法会把不使用的Message对象添加此链表中。当调用handler的post系列方法时,会去构建一个message,这个message会优先从消息池中获取,如果有就复用,没有就重新创建。


    总结

    • 程序启动的时候,主线程会创建一个Looper对象。Looper对象内部维护一个MessageQueue,然后调用loop()方法循环去读取消息。

    • 初始化Handler的时候,在Handler的构造函数内部,会获取当前线程的Looper对象,进而获取MessageQueue对象。由此可见,想要操作UI的Handler必须在主线程中创建。否则会提示你:【”Can’t create handler inside thread that has not called Looper.prepare()”】

    • 调用Handler的先关方法时,会获取Message对象,将消息对象的target指向当前handler对象,然后放到消息队列中。

    • loop()工作中,会从消息队列中获取一个个的消息,调用handle的dispatchMessage(msg)分发处理。

    • Message内部维护一个消息池,用来回收缓存message对象。

    • Looper相当于一个发动机,MessageQueue相当于流水线,Message相当于一个个的物品,而Handler就相当于工人。


    展开全文
  • JS的事件循环机制

    千次阅读 2019-06-28 14:40:10
    JS的异步操作来源于浏览器提供的异步API,以及事件循环机制,回调函数队列(或者叫task queue,任务队列)。事件循环机制循环往复地监测JS调用栈和任务队列,一旦调用栈为空,且任务队列中有回调函数,就会将回调...

    事件循环的意义

    将耗时的任务分解为几部分来执行,这几个分解的任务所执行的时机由事件循环机制来确保。这样做是为了避免耗时操作阻塞浏览器UI界面的重绘。

    事件循环与异步操作

    JS的异步操作实质上是将一个任务分为几部分,以两部分为例,第二部分作为回调函数(这个函数根据API的写法来确定是异步调用还是同步调用,如果是异步调用才真正应该叫回调函数,同步的操作无所谓回调),等第一部分运行结束有了结果后,再通过调用回调函数(即第二部分)来继续运行任务。第一部分运行往往要么用时不长,要么是浏览器的其他线程执行操作(如远程请求等),回调函数在等待被调用期间,浏览器可以执行其他代码。因此,JS的异步是通过事件循环机制,将回调函数放入任务队列来进行的。

    解释异步操作流程

    在浏览器中,JS解释器用于执行JS代码,即JS函数调用栈。浏览器提供webAPI诸如setTimeout,Ajax等等异步操作,以及任务队列(task queue)。如下图
    浏览器内部执行原理图
    回调函数的写法是浏览器API提供的,那样的写法意味着回调函数会在将来被加入到任务队列里。比如一个Ajax请求$.ajax('url', foo),调用栈开始运行时,函数foo会一直等待ajax的运行结果。一旦运行有结果,foo会被加入到任务队列task queue中。此时,event loop机制开始发挥作用。事件循环机制就是一个监视器,一直监视着调用栈是否为空。一旦为空,就会把任务队列中的函数加入到调用栈内执行。之所以叫事件循环,就是这个监视器会一遍一遍循环监测任务队列中是否有任务。有任务的时候,就准备将任务加入调用栈(只有调用栈空了才加入)。
    此外,还有一个叫job queue的队列,一般称之为微任务队列,而task queue则称为宏任务队列。事件循环监测一圈时,先加入宏任务,宏任务执行完后,再加入微任务。等微任务执行完后,再继续循环。所以,循环一圈,最后加入的一定是微任务,除非没有微任务。不过关于微任务,node环境跟浏览器环境的执行不一样。

    事件循环每一轮的标志

    事件循环的每一轮都以调用栈清空为标志,每次清空完调用栈,就代表着一轮事件循环的结束。

    事件循环与浏览器重绘

    JS的调用栈不为空的时候,浏览器是没法执行重绘的,这样设置也很正常,因为JS代码可能会更改DOM结构,因此只有调用栈为空时,浏览器才会执行重绘。为了避免出现UI界面阻塞,调用栈中的函数代码不应该是耗时操作,如果有任务是耗时的,那应该将任务分割成几部分,采用异步操作来执行任务,从而得到一个漂亮顺畅的UI界面。

    展开全文
  • JavaScript事件循环机制

    千次阅读 2018-02-14 17:57:15
    众所周知JS是一门单线程执行环境的语言,对于同步任务而言,同一时刻只能执行一个任务,后续的任务都要在当前执行的任务后面...而JS的事件循环机制就是负责对这些同步任务和异步任务的执行顺序进行调度的。 Ja...

    众所周知JS是一门单线程执行环境的语言,对于同步任务而言,同一时刻只能执行一个任务,后续的任务都要在当前执行的任务后面排队。这种模式在遇到一些执行时间较长的任务的时候就会出问题,会导致页面失去响应。所以这些时间较长的任务我们在编写的时候一般会把他们用异步的方式去调用,并指定任务完成时对结果进行后续处理的回调函数。而JS的事件循环机制就是负责对这些同步任务和异步任务的执行顺序进行调度的。

    JavaScript的函数堆栈以及任务队列

    JavaScript引擎中存在着一个主线程,所有的同步任务都会在这个主线程上执行,每当一个同步任务要执行了,主线程就会把这个同步任务推入函数堆栈中,待执行完成后,主线程读取下一个同步任务到堆栈中,继续执行;而在这个过程中若存在被注册的异步任务回调函数,这些异步任务会交给引擎中的其他模块进行处理,并在异步任务完成或是合适的时机(比如setTimeout指定的时间间隔达到了)将回调函数放到任务队列当中,一般来说不同的异步任务会有不同的任务队列,而不是所有回调都放在同一个任务队列当中。当主线程中的函数堆栈中不再有更多的同步任务时,主线程就会开始读取任务队列中的回调函数,将其一一执行。
    可以用一张图来表示这个过程:
    一张图
    如图所示,当主线程遇到了像是Ajax,setTimeout和DOM操作这一类异步任务的时候,它会把这些任务交给特定的其他模块去处理,然后它自个继续执行接下去的同步任务,而那些异步任务执行完毕之后,则会将注册的回调函数放入任务队列当中,待主线程将同步任务执行完后,就会去读取这些回调函数。

    异步任务的类型以及事件循环的过程

    我在学习事件循环的时候,在别的博客了解到了JavaScript的任务分成两种不同的类型这一概念:一种称为macro task(宏任务),包括script(主代码),setTimeout,setInterval,setImmediate,I/O, UI rendering。另一种称为micro task(微任务),包括process.nextTick, Promise, Object.observe,MutationObserver这些任务都有他们自己独立的任务队列,也就是他们的回调函数会被放到不同的队列中等待主线程读取执行。而在这个过程中事件循环就开始起作用了,具体来说,事件循环的过程如下:
    首先要明确,每一次事件循环,只会执行一个macrotask,在执行完这个macrotask后,主线程会开始去读取microtask的队列,将所有microtask在这一次事件循环中执行完毕,也就是会将microtask队列清空;这也就说明,如果在执行microtask的回调函数的过程中,如果又注册了新的microtask,那么这些microtask也都会在这一轮的事件循环中执行。microtask队列清空以后,主线程从macrotask的队列中读取下一个macrotask,进行执行。
    (1)事件循环首先从整体代码片开始(要注意整体代码其实也是一个macrotask),读取代码片中的同步任务,将其推入函数堆栈中执行。
    (2)在处理整体代码片中,若遇到了宏任务和微任务的调用,像是setTimeout或是new Promise的话,就把他们交给他们对应的模块去处理,那些模块会负责把回调函数推入他们对应的任务队列中。
    (3)当函数堆栈中不再有正在执行的同步任务后,开始读取任务队列中的回调函数,事件循环会先查看微任务中的回调函数,优先执行微任务的回调函数。
    (4)当微任务的回调函数执行完后,事件循环拿出一个宏任务的回调函数,并执行,执行完后,事件循环会回到第三步,再次查看有没有新的微任务的回调函数,有的话则取出来执行。

    接下来用几个例子来更直观地描述事件循环的过程,现在存在如下代码:

    console.log('golb1');
    setImmediate(function() {
        console.log('immediate1');
        process.nextTick(function() {
            console.log('immediate1_nextTick');
        })
        new Promise(function(resolve) {
            console.log('immediate1_promise');
            resolve();
        }).then(function() {
            console.log('immediate1_then')
        })
    })
    setTimeout(function() {
        console.log('timeout1');
        process.nextTick(function() {
            console.log('timeout1_nextTick');
        })
        new Promise(function(resolve) {
            console.log('timeout1_promise');
            resolve();
        }).then(function() {
            console.log('timeout1_then')
        })
        setTimeout(function() {
            console.log('timeout1_timeout1');
        process.nextTick(function() {
            console.log('timeout1_timeout1_nextTick');
        }, 0);
        setImmediate(function() {
            console.log('timeout1_setImmediate1');
        })
        });
    }, 0);
    new Promise(function(resolve) {
        console.log('glob1_promise');
        resolve();
    }).then(function() {
        console.log('glob1_then')
    })
    process.nextTick(function() {
        console.log('glob1_nextTick');
    })

    开始执行后,首先主代码入栈,执行console.log(‘golb1’),这没什么好说的,接着遇到了第一个宏任务setImmediate, 将他交给对应的执行模块;同理下面遇到的setTimeout,Promise以及process.nextTick都被交给特定的模块处理,接着他们的回调函数被放入他们对应的任务队列中。注意Promise的构造函数的参数是一个在主代码片中执行的同步任务,所以会输出glob1_promise。
    接下来,主代码片执行完成,事件循环机制开始读取微任务的任务队列,首先会执行process.nextTick的回调函数,输出glob1_nextTick;接着执行Promise.then中指定的回调函数,输出glob1_then。
    现在微任务的任务队列清空了,事件循环机制开始读取宏任务的任务队列。首先是setTimeout的回调函数,在其中又指定了两个微任务的调用和两个宏任务的调用。这个回调函数执行完后,事件循环机制查看微任务的任务队列,发现多了两个微任务的回调函数,于是把他们按顺序读取执行。
    微任务再次清空,事件循环读取一开始在主代码片中执行的setImmediate的回调函数,输入immediate1,同理,这里又注册了两个微任务,所以在执行完后会去读取他们并执行。
    最后异步任务只剩在setTimeout的回调函数中执行的setTimeout:timeout1_timeout1,将这个回调函数读取入栈并执行,事件循环就结束了。整体输入结果如下:

        golb1
        globl_promise
        glob1_nextTick
        glbl1_then
        timeout1
        timeout1_promise
        timeout1_nextTick
        timeout1_then
        immediate1
        immediate1_promise
        immediate1_nextTick
        immediate1_then
        timeout1_timeout1
        timeout1_timeout1_nextTick
        timeout1_setImmediate1

    再用一个我在一篇文章中看到的很有说服力的例子来描述事件循环:
    假设现在用如下代码:

    <div class="outer">
        <div class="inner"></div>
    </div>
    
    var outer = document.querySelector('.outer');
    var inner = document.querySelector('.inner');
    
    // Let's listen for attribute changes on the
    // outer element
    new MutationObserver(function() {
      console.log('mutate');
    }).observe(outer, {
      attributes: true
    });
    
    // Here's a click listener…
    function onClick() {
      console.log('click');
    
      setTimeout(function() {
        console.log('timeout');
      }, 0);
    
      Promise.resolve().then(function() {
        console.log('promise');
      });
    
      outer.setAttribute('data-random', Math.random());
    }
    
    inner.addEventListener('click', onClick);
    outer.addEventListener('click', onClick);

    按照图中的代码描述,现在如果我们点击了子div,也就是class为inner的div,那么控制台的输出会是什么呢?

    在这里揭晓答案:

    click
    promise
    mutation
    click
    promise
    mutation
    timeout
    timeout

    为何会输出这样的结果呢?我们一步步讲解:
    在点击了inner以后,触发了一个点击事件,我们叫这个点击事件的task为Dispatch Click。
    此时它是macrotask中唯一一个task,取出来执行。然后就会在函数堆栈中推入点击事件的处理函数onClick,先输出click;接着在onClick中一共注册了三个异步回调:setTimeout,Promise.then,还有MutationObserver观测到outer的属性变化而注册的回调,其中按照前文所述,setTimeout被推入macrotask队列,而Promise和MutationObserver按顺序被推入microtask的队列中;到这,onClick函数执行完毕,被推出堆栈中,此时函数堆栈为空,所以事件循环会开始去读取microtask队列,也就是刚刚注册的Promise和MutationObserver的回调函数,输出promise和mutation;接着,由于点击事件会冒泡,又触发了outer上的点击事件,同样是onClick函数,被压入函数堆栈。接着上述过程就重复了一次。最后,macrotask队列中只剩下两个setTimeout的回调函数,依次执行,输出timeout。

    不过,如果将上述点击事件的触发方式修改一下,却会出现不同的结果,我们在上述代码的最后添加一行:

    //....省略上述代码
    inner.click();

    我们将点击inner触发点击事件改为用JS代码显式调用点击事件方法,去触发onClick,在这种情况下,上述代码在控制台的输出结果会变为:

    click
    click
    promise
    mutation
    promise
    timeout
    timeout

    为什么会变成这样呢?我们再按刚刚的方式,一步步分析任务队列和函数堆栈的情况:
    由于现在是通过inner.click()这种通过JS代码显式调用的方法调用click事件,那么函数堆栈中首先会存在一个我们称为script的顶层执行上下文,也就是前文所述的整体代码片;调用click方法后,onClick函数被执行,压入函数堆栈中,注意此时函数堆栈中有两个执行环境,一个是script,一个是onClick。此时当然还是会先输出click,以及注册回调函数,和前面一样;然后onClick执行完毕后,情况就有所不同了,onClick被推出堆栈,但是script还存在在堆栈中。记得前面所说的吗?当函数堆栈为空时才会去读取microtask任务队列,所以此时,microtask中的Promise和MutationObserver都不会被读到函数堆栈中去执行。接着,点击事件冒泡,再次执行onClick,重复前述步骤,不过这里有一处不同,当执行到给outer设置属性这一步代码时,由于此时microtask队列中已经存在同一个MutationObserver注册的回调函数了,所以不会再推出一个MutationObserver的回调。也就是说此时microtask的队列是这样的:Promise -> Mutation -> Promise。接着,onClick事件调用完毕,推出堆栈,此时整体代码片也已经运行完了,script也推出堆栈,函数堆栈为空,事件循环开始读取microtask。接着的执行过程就和前面的例子一样了,只是少了一个mutation的输出。
    关于这个例子,如果想要看到更详细的讲解的话可以参考下面这篇文章,我也是看了这篇文章之后对事件循环有了更深刻的理解:
    Tasks, microtasks, queues and schedules

    参考文章:
    深入浅出Javascript事件循环机制(上)
    深入浅出Javascript事件循环机制(下)

    展开全文
  • nodejs的事件循环机制

    2019-06-01 21:44:28
    一直以来,我写的的大部分JS代码都是在浏览器环境下运行,因此也了解过浏览器的事件循环机制,知道有macrotask和microtask的区别。但最近写node时,发现node的事件循环机制和浏览器端有很大的不同,特此深入地学习了...
  • 浏览器的事件循环机制

    千次阅读 2019-03-14 20:20:57
    浏览器事件循环机制(even loop) js语言执行的环境是‘单线程’,所谓的单线程是同一时间只能完成一件任务,其他任务必须排在前一个事件之后执行,很多时候CPU空闲的,因为IO设备很慢,不得不等结果出来再往下执行。...
  • JavaScript的堆、栈、队列、事件循环机制
  • Js事件循环机制(上)

    千次阅读 2017-11-12 12:54:24
    深入浅出Javascript事件循环机制(上)柳兮7 个月前最近琢磨了好久的Javascript的事件循环机制,看了很多国内的博客总觉得写的还是不够深,很多都只说了Javascript的事件分为同步任务和异步任务,遇到同步任务就放在...
  • 深入了解nodejs的事件循环机制

    千次阅读 2018-09-29 17:36:59
    一直以来,我写的的大部分JS代码都是在浏览器环境下运行,因此也了解过浏览器的事件循环机制,知道有macrotask和microtask的区别。但最近写node时,发现node的事件循环机制和浏览器端有很大的不同,特此深入地学习了...
  • chromium消息循环机制

    千次阅读 2012-09-28 17:18:31
    Chromium的线程模型和消息循环机制一直是其很有亮点也非常值得学习的一部分,关于这部分的工作原理网上有很多好文章,比如这篇、这篇还有这篇。但是这几篇文章基本是站在一定的高度上来概括其思想,而深入到代码里...
  • js事件循环机制(一)

    千次阅读 2018-02-04 10:56:15
    在看Callbacks、Deferred的过程中常常遇到异步编程的概念,而异步编程又和事件循环机制息息相关,之前对事件循环和异步编程也是一知半解,所以先花点时间整理一下事件循环和异步编程相关的知识。 一、Heap、Stack...
  • js事件循环机制和ui渲染 事件循环 任务队列 所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax...
  • js事件循环机制

    万次阅读 2018-08-14 17:39:13
     从上一篇说明vue nextTick的文章中,多次出现“事件循环”这个名词,简单说明了事件循环的步骤,以便理解nextTick的运行时机,这篇文章将更为详细的分析下事件循环。在此之前需要了解JS单线程,及由此产生的同步...
  • 常问面试题:js事件循环机制:

    千次阅读 2019-02-19 02:20:02
    参考文档: juejin.im/post/59e85e… blog.csdn.net/qq_31628337… ...1.深入浅出js事件循环机制(上)zhuanlan.zhihu.com/p/26229293 2.深入浅出js事件循环机制(下)zhuanlan.zhihu.com/p/26238030 ...
  • JS:事件循环机制、调用栈以及任务队列

    万次阅读 多人点赞 2017-05-01 15:40:07
    写在前面js里的事件循环机制十分有趣。从很多面试题也可以看出来,考察简单的setTimeout也就是考察这个机制的。 在之前,我只是简单地认为由于函数执行很快,setTimeout执行时间即便为0也不会马上输出,而是等待...
  • 面试题:说说事件循环机制(满分答案来了)

    千次阅读 多人点赞 2020-03-09 08:45:00
    答题大纲先说基本知识点,宏任务、微任务有哪些说事件循环机制过程,边说边画图出来说async/await执行顺序注意,可以把 chrome 的优化,做法其实是违反了规范的,V8 团队的PR...
  • 浏览器环境下JS的事件循环机制: function foo() { setTimeout(foo, 0); // 是否存在堆栈溢出错误? }; 浏览器的主要组件包括调用堆栈,事件循环,任务队列和Web API。像setTimeout,setInterval和Promise这样的...
  • Windows消息循环机制

    千次阅读 2015-07-13 17:25:52
    理解消息循环和整个消息传送机制对Windows编程来说非常重要。如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方。 什么是消息(Message) 每个消息是一个整型数值,如果查看头文件(查看...
  • Qt的事件循环机制

    万次阅读 多人点赞 2018-09-26 15:49:58
    QCoreApplication::exec()开启了这种循环,一直到QCoreApplication::exit()被调用才终止,所以说事件循环是伴随着Qt程序的整个运行周期! 另外一种同步处理情形是通过sendEvent()将事件发送出去,直接进入事件的...
  • 现在,慢慢的,我知道我不知道的,比如事件循环机制。然后花时间去了解,就变成我知道我知道的。说实话,这个我也就朦朦胧胧知道了一点。正文从这里开始~~~说起JavaScript特性,单线程运行当然是其中很重要的一点...
  • setTimeout(function() { console.log(1); setTimeout(function() { console.log(2); }, 0); process.nextTick(function() { console.log(5); process.nextTick(function() { console.log(6);...
  • 消息循环机制及其原理

    千次阅读 2018-07-21 18:00:19
    在Android线程内,可以通过消息循环机制以队列的方式实现消息的发送,处理等工作 作用:线程间通信 涉及到的核心类: Message:消息的实体的封装 Handler:消息的发送和处理 Looper:消息循环的核心,管理消息...
  • 从event loop到async await来了解事件循环机制 作者:船长_ 链接:https://juejin.im/post/5c148ec8e51d4576e83fd836 JS为什么是单线程的? 最初设计JS是用来在浏览器验证表单操控DOM元素的是一门脚本语言,如果js...
  • Js中的事件循环机制EventLoop 在JavaScript代码自上而下执行过程中,分为同步任务和异步任务,异步任务又分为异步微任务和异步宏任务,同步任务也是宏任务。 【常见的异步微任务】 promise async/await ...
  • 详谈Windows消息循环机制

    千次阅读 2017-11-16 19:10:15
    一直对windows消息循环不太清楚,今天做个详细的总结,如果有说错的地方,请务必指出。 用VS2017新建一个win32 Application的默认代码如下: 程序入口 //int WINAPI WinMain 定义窗口类 //typedef struct ...
  • 这篇文章主要介绍了提升Python效率之使用循环机制代替递归函数的相关知识,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下 斐波那契数列 当年,典型的递归题目,斐波那契数列还记得吗? ...
  • 深入剖析WTL—WTL消息循环机制详解

    千次阅读 2013-10-23 00:15:55
    WTL消息循环机制实现了消息过滤和空闲处理机制。 消息过滤 首先看一下CMessageLoop的核心逻辑CMessageLoop.Run()的代码: int CMessageLoop.Run() { BOOL bDoIdle = TRUE; int nIdleCount = 0; BOOL bRet; ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 562,715
精华内容 225,086
关键字:

循环的机制