1. 进程和线程 #

1.1 进程 #

1.2. 线程 #

2. chrome浏览器进程 #

3. 渲染进程 #

4. 事件环 #

4.1. 单线程顺序执行 #

4.1.1 main.js #

(function mainThread() {
    let task1 = 1 + 2;
    let task2 = 2 + 3;
    let task3 = 3 + 4;
    console.log(task1, task2, task3);
})()

4.2.事件循环 #

4.2.1 main.js #

let readline = require('readline-sync');
(function mainThread() {
    while (true) {
        var num1 = readline.question('num1: ');
        var num2 = readline.question('num2: ');
        let ret = eval(num1 + "+" + num2);
        console.log(ret);
    }
})();

4.3. 消息队列+IO线程 #

4.3.1 messageQueue.js #

class MessageQueue {
    constructor() {
        this.messages = [];
    }
    put(message) {
        this.messages.push(message);
    }
    get() {
        return this.messages.pop();
    }
}
module.exports = new MessageQueue();

4.3.2 main.js #

const messageQueue = require('./messageQueue');
(function mainThread() {
    setInterval(() => {
        let task = messageQueue.get();
        if (task) task();
    }, 1000);
})();

(function IOThread() {
    let counter = 1;
    setInterval(() => {
        messageQueue.put(()=>console.log(`task` + counter++));
    }, 1000);
})();

4.4.跨进程通信 #

4.4.1 render.js #

render.js

const { fork } = require('child_process');
const messageQueue = require('./messageQueue');
(function mainThread() {
    setInterval(() => {
        let task = messageQueue.get();
        if (task) task();
    }, 1000);
})();

(function IOThread() {
    let browser = fork('./browser.js');
    browser.on('message', function ({ data }) {
        messageQueue.put(() => {
            console.log(data);
        });
    });
    browser.send({ type: 'click', data: 'clicked' });
})();

4.4.2 browser.js #

4.5. 微任务 #

4.5.1 render.js #

render.js

const { fork } = require('child_process');
+const { macroTaskQueue, microTaskQueue } = require('./messageQueue');
(function mainThread() {
    setInterval(() => {
+       let macroTask = macroTaskQueue.get();
+       if (macroTask) macroTask();
+       let microTask;
+       while (microTask = microTaskQueue.get()) {
+           microTask();
+       }
    }, 1000);
})();

(function IOThread() {
    let browser = fork('./browser.js');
    browser.on('message', function ({ data }) {
        macroTaskQueue.put(() => {
            console.log(data);
+           microTaskQueue.put(() => {
+               console.log('microTask1');
+               microTaskQueue.put(() => {
+                   console.log('microTask2');
+               });
+           });
        });
    });
    browser.send({ type: 'click', data: 'clicked' });
})();

4.5.2 browser.js #

browser.js

//浏览器主进程
process.on('message', function ({ data }) {
    setTimeout(() => {
        process.send({ data });
    }, 100);
});

4.5.3 messageQueue.js #

messageQueue.js

class MessageQueue {
    constructor() {
        this.messages = [];
    }
    put(message) {
        this.messages.push(message);
    }
    get() {
        return this.messages.pop();
    }
}
+exports.macroTaskQueue = new MessageQueue();
+exports.microTaskQueue = new MessageQueue();

4.6. setTimeout #

4.6.1 DelayTask.js #

DelayTask.js

let timerCounter = 1;
class DelayTask {
    constructor(callback, delayTime) {
        this.id = timerCounter++;
        this.startTime = Date.now();
        this.callback = callback;
        this.delayTime = delayTime;
    }

}
module.exports = DelayTask;

4.6.2 render.js #

render.js

const { fork } = require('child_process');
const { macroTaskQueue, microTaskQueue } = require('./messageQueue');
+const DelayTask = require('./DelayTask');
+let delayTaskQueue = [];
(function mainThread() {
    setInterval(() => {
        let macroTask = macroTaskQueue.get();
        if (macroTask) macroTask();
+       processDelayTask();
        let microTask;
        while (microTask = microTaskQueue.get()) {
            microTask();
        }
    }, 16);
})();
+function setTimeout(callback, delayTime) {
+    delayTaskQueue.push(new DelayTask(callback, delayTime));
+}
+function clearTimeout(timeId) {
+    delayTaskQueue = delayTaskQueue.filter(delayTask => {
+        return delayTask.id !== timeId;
+    });
+}
+function processDelayTask() {
+    delayTaskQueue = delayTaskQueue.filter(delayTask => {
+        const { callback, startTime, delayTime } = delayTask;
+        if (Date.now() > startTime + delayTime) {
+            macroTaskQueue.put(callback);
+            return false;
+        }
+        return true;
+    });
+}

(function IOThread() {
    let browser = fork('./browser.js');
+   console.time('cost');
    browser.on('message', function ({ data }) {
        console.log(data);
+        setTimeout(() => {
+            console.timeEnd('cost');
+        }, 1000);
    });
    browser.send({ type: 'click', data: 'clicked' });
})();

4.7.XMLHttpRequest #

4.7.1 render.js #

render.js

const { fork } = require('child_process');
const { macroTaskQueue, microTaskQueue } = require('./messageQueue');
(function mainThread() {
    setInterval(() => {
        let macroTask = macroTaskQueue.get();
        if (macroTask) macroTask();
        let microTask;
        while (microTask = microTaskQueue.get()) {
            microTask();
        }
    }, 16);
})();


(function IOThread() {
    let browser = fork('./browser.js');
    console.time('cost');
    browser.on('message', function () {
+       let xhr = new XMLHttpRequest();
+       xhr.open('GET', 'http://localhost:3000/data');
+       xhr.onload = function () {
+           console.log(xhr.response);
+       }
+       xhr.send();
    });
    browser.send({ type: 'click', data: 'clicked' });
})();

+class XMLHttpRequest {
+    constructor() {
+        this.options = {};
+    }
+    open(method, url) {
+        this.options.method = method;
+        this.options.url = url;
+    }
+    send() {
+        let child = fork('./XMLHttpRequest.js');
+        child.on('message', (message) => {
+            if (message.type === 'response') {
+                this.response = message.data;
+                macroTaskQueue.put(this.onload);
+            }
+        });
+        child.send({ type: 'send', options: this.options });
+    }
+}

4.7.2 XMLHttpRequest.js #

XMLHttpRequest.js

let url = require('url');
let http = require('http');
process.on('message', function (message) {
    let { type, options } = message;
    if (type == 'send') {
        let urlObj = url.parse(options.url);
        const config = {
            hostname: urlObj.hostname,
            port: urlObj.port,
            path: urlObj.path,
            method: options.method
        };
        var req = http.request(config, (res) => {
            let chunks = [];
            res.on('data', (chunk) => {
                chunks.push(chunk);
            });
            res.on('end', () => {
                process.send({
                    type: 'response',
                    data: JSON.parse(Buffer.concat(chunks).toString())
                });
                process.exit();
            });
        });
        req.on('error', (err) => {
            console.error(err);
        });
        req.end();
    }
});

4.7.3 server.js #

server.js

var http = require("http");
var server = http.createServer();
server.on("request", function (request, response) {
    response.end(JSON.stringify({ message: 'hello' }));
})
server.listen(3000, function () {
    console.log("服务已经在3000端口启动!")
});

5.任务分类 #

5.1 宏任务 #

5.2 微任务 #

5.3 MutationObserver #

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="tree"></div>
    <button onclick="start()">开始监听</button>
    <button onclick="changeAttribute()">修改属性</button>
    <button onclick="addChild()">添加子节点,3秒后删除</button>
    <script>
        var targetNode = document.getElementById('tree');
        var config = { attributes: true, childList: true, subtree: true };
        var callback = function (mutationsList) {
            for (var mutation of mutationsList) {
                console.log(mutation);
                if (mutation.type == 'childList') {
                    console.log('添加或删除子节点');
                    console.log(mutation.addedNodes);
                    console.log(mutation.removedNodes);
                } else if (mutation.type == 'attributes') {
                    console.log('属性 ' + mutation.attributeName + ' 被改变了');
                }
            }
        };

        var observer = new MutationObserver(callback);
        function start() {
            observer.observe(targetNode, config);
        }
        function changeAttribute() {
            targetNode.setAttribute('data-name', '树');
        }
        function addChild() {
            let child = document.createElement('div');
            child.innerHTML = '子节点';
            targetNode.appendChild(child);
            setTimeout(() => {
                targetNode.removeChild(child);
            }, 2000);
        }
    </script>
</body>

</html>

6. Node中的EventLoop #

6.1 libuv #

6.2 setImmediate #

setTimeout(function  () {
  console.log('timeout');
},0);
setImmediate(function  () {
  console.log('immediate');
});
const fs = require('fs')
fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0)
    setImmediate(() => {
        console.log('immediate')
    })
})

6.3 process.nextTick #

setTimeout(() => {
    console.log('setTimeout1')
    Promise.resolve().then(function () {
        console.log('promise1')
    })
}, 0)
setTimeout(() => {
    console.log('setTimeout2')
    Promise.resolve().then(function () {
        console.log('promise2')
    })
}, 0)
setImmediate(() => {
    console.log('setImmediate1')
    Promise.resolve().then(function () {
        console.log('promise3')
    })
}, 0)

process.nextTick(() => {
    console.log('nextTick1');
    Promise.resolve().then(() => console.log('promise4'));
    process.nextTick(() => {
        console.log('nextTick2');
        Promise.resolve().then(() => console.log('promise5'));
        process.nextTick(() => {
            console.log('nextTick3')
            process.nextTick(() => {
                console.log('nextTick4')
            })
        })
    })
})
// nextTick1 nextTick2 nextTick3 nextTick4
//promise4 promise5 setTimeout1  promise1 setTimeout2 promise2  setImmediate1 promise3 

6.4 Node.js事件环 #

let fs = require('fs');
setTimeout(() => {
    console.log('1');
    let rs1 = fs.createReadStream(__filename);
    rs1.on('data', () => {
        rs1.destroy();
        setImmediate(() => console.log('setImmediate_a'));
        setTimeout(() => {
            console.log('setTimeout_a')
        });
        console.log('a');
    });
    rs1.on('close', () => console.log('end_a'));
    console.log('2');
    setImmediate(function () {
        console.log('setImmediate1');
        process.nextTick(() => console.log('nextTick1'));
    });
    setImmediate(function () {
        console.log('setImmediate2');
        process.nextTick(() => console.log('nextTick2'));
    });
    console.log('3');
    setTimeout(() => {
        console.log('setTimeout1');
        process.nextTick(() => {
            console.log('nextTick3')
            process.nextTick(() => console.log('nextTick4'));
        });
    });
    setTimeout(() => {
        console.log('setTimeout2');
    });
    console.log('4');
}, 1000);

7. 面试题 #

7.1 原题 #

const Promise = require('./Promise');
Promise.resolve().then(() => {
    console.log(0);
    return new Promise((resolve)=>{
        resolve('a');
    })
}).then(res => {
    console.log(res)
})
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(4);
}).then(() => {
    console.log(5);
})
// 0 1 2 3 a 4 5

7.2 改造 #

const Promise = require('./Promise');
let promise1 = Promise.resolve();
let promise2 = promise1.then(() => {
    console.log(0);
    let promise10 = Promise.resolve('a');
    return promise10;
})
let promise3 = promise2.then(res => {
    console.log(res)
})
let promise4 = Promise.resolve();
let promise5 = promise4.then(() => {
    console.log(1);
});
let promise6 = promise5.then(() => {
    console.log(2);
});
let promise7 = promise6.then(() => {
    console.log(3);
});
let promise8 = promise7.then(() => {
    console.log(4);
});
let promise9 = promise8.then(() => {
    console.log(5);
})
// 0 1 2 3 a 4 5

7.3 Promise.js #

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
function resolvePromise(promise, x, resolve) {
    if (x && typeof x.then === 'function') {
        queueMicrotask(() => {
            console.log('resolvePromise: microtask', x.id);
            x.then(y => resolvePromise(promise, y, resolve));
        });
    } else {
        resolve(x)
    }
}
class Promise {
    static counter = 1;
    constructor(executor) {
        this.id = Promise.counter++;
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        const resolve = (value) => {
            if (value instanceof Promise) {
                return queueMicrotask(() => {
                    console.log('resolve: microtask', value.id);
                    value.then(resolve)
                });
            }
            if (this.status == PENDING) {
                this.value = value;
                this.status = FULFILLED
                this.onResolvedCallbacks.forEach(cb => cb(this.value))
            }
        }
        executor(resolve);
    }
    then(onFulfilled) {
        let newPromise = new Promise((resolve) => {
            if (this.status === FULFILLED) {
                queueMicrotask(() => {
                    console.log('FULFILLED then: microtask', this.id);
                    let x = onFulfilled(this.value);
                    resolvePromise(newPromise, x, resolve)
                })
            }
            if (this.status == PENDING) {
                this.onResolvedCallbacks.push(() => {
                    queueMicrotask(() => {
                        console.log('PENDING then: microtask', this.id);
                        let x = onFulfilled(this.value);
                        resolvePromise(newPromise, x, resolve)
                    })
                });
            }
        });
        return newPromise;
    }
    static resolve(value) {
        return new Promise((resolve) => {
            resolve(value)
        })
    }
}
module.exports = Promise;

7.4 讲解 #

7.5 打印 #

FULFILLED then: microtask 1
0
FULFILLED then: microtask 4
1
resolvePromise: microtask 10
PENDING then: microtask 5
2
FULFILLED then: microtask 10
PENDING then: microtask 6
3
PENDING then: microtask 2
a
PENDING then: microtask 7
4
PENDING then: microtask 8
5

7.思考 #

const Promise = require('./Promise');
Promise.resolve().then(() => {
    console.log(0);
    return new Promise((resolve) => {
        resolve(new Promise((resolve) => {
            resolve('a');
        }));
    })
}).then(res => {
    console.log(res)
})
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(4);
}).then(() => {
    console.log(5);
})
// 0 1 2 3  4 a 5