let likeArr = {0:1, length: 1};
let arr = [...likeArr]
上述代码会报错: Uncaught TypeError: likeArr is not iterable
js中一个对象有了Symbol(Symbol.iterator)这样一个属性,才可以通过...展开迭代
元编程指可以改变js本身功能的编程方式,例如
let obj = { // Symbol的用法
get [Symbol.toStringTag](){
return 'zf'
}
};
console.log(Object.prototype.toString.call(obj))
// 结果为[object zf]
如果调用[...likeArray],则会调用likeArray的迭代器,想要让普通对象likeArray可以有迭代功能就要作如下处理:
let likeArray = { // 数组中有Symbol(Symbol.iterator) 这个方法就是告诉浏览器如何迭代此对象
0:1,
1:2,
2:3,
3:4,
length:4,
[Symbol.iterator](){ // 此方法要返回一个迭代器
let i = 0;
// this
return {
next:()=>{
return {value:this[i],done:this.length === i++}
}
}
}
}
完成这个功能还可以使用生成器
let likeArray = { // 数组中有Symbol(Symbol.iterator) 这个方法就是告诉浏览器如何迭代此对象
0:1,
1:2,
2:3,
3:4,
length:4,
[Symbol.iterator]:function *(){ // 迭代数组的时候 会自动调用next
let i = 0;
let len = this.length;
while(len !== i){
yield this[i++]
}
}
}
generator和Promise配合使用案例:
const fs = require("fs/promises");
const path = require('path')
function* readFile() {
let data1 = yield fs.readFile(path.resolve(__dirname,"a.txt"), "utf8");
let data2 = yield fs.readFile(path.resolve(__dirname,data1), "utf8");
return data2; // 30
}
let it = readFile();
let {value,done} = it.next();
value.then(data1=>{
let {value,done} = it.next(data1)
value.then(data2=>{
let {value,done} = it.next(data2);
console.log(value)
})
})
上面的then嵌套可以采用co库来简化:
const fs = require("fs/promises");
const path = require('path')
function* readFile() {
let data1 = yield fs.readFile(path.resolve(__dirname,"a.txt"), "utf8");
let data2 = yield fs.readFile(path.resolve(__dirname,data1), "utf8");
return data2; // 30
}
co(readFile()).then(data=>{
console.log(data);
}).catch(e=>{
console.log(e);
})
co的实现:
function co(it){
return new Promise((resolve,reject)=>{
function step(data){
let {value,done} = it.next(data);
if(!done){
Promise.resolve(value).then(data=>{ // 第一步完成
step(data); // 下一步
}).catch(e=>{
reject(e);
})
}else{
resolve(value)
}
}
step();
})
}
generator编译后的效果:
function _regeneratorRuntime(){
function wrap(iteratorFn){
// 基本稍后执行 所需要的信息
const _context = {
next:0, // 上下文信息
done:false,
stop(){
_context.done = true;
},
sent:null
}
// wrap 函数执行后返回的是一个迭代器
return { // it
next(v){ // 每次调用next的时候 会给上一次的yield返回值赋值
_context.sent = v;
let value = iteratorFn(_context);
return {value,done:_context.done}
}
}
}
return {
wrap
}
}
function foo(x) {
var a, b;
return _regeneratorRuntime().wrap(function foo$(_context) {
// while (1) { // 表示一个状态机 状态的扭转, 表示这个逻辑会走多次 (根据状态执行对应的流程)
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return x + 1;
case 2:
a = _context.sent;
console.log(a);
_context.next = 6;
return x + 2;
case 6:
b = _context.sent;
console.log(b);
case 8:
case "end":
return _context.stop();
}
//}
});
}
let it = foo(1);
console.log(it.next())
console.log(it.next('ok'))
console.log(it.next('ok ~!'))
async和await会被编译成generator和cn
宏任务和微任务
浏览器是由多个进程组成的 (每个页签都是一个独立的进程) 浏览器中主要的进程有:主进程 网络进程、绘图的进程、 插件都是一个个的进程
一个页签都是一个进程 (渲染进程): 渲染的线程
渲染进程中 : 进程中包含的线程 ui线程 负责页面渲染、布局、绘制 js线程:js引擎线程, 主要负责执行js代码的 (ui线程 js线程互斥的,js也是单线程的) 为了保证渲的一致性要保证整个执行是单线程的 主线程:单线程的 (代码是从上大下执行) webworker(不能操作dom元素) 同步代码
定时器、请求、用户的事件操作 都是异步的 (每次开一个定时器 都会生成一个新的线程)
异步任务的划分 : 宏任务、微任务 微任务: Promise.then(ECMAScript里面提供的) mutationObserver(html5,这里的回调是一异步执行的) queueMircrotask 宏任务:默认的script脚本 ui渲染也是一个宏任务 setTimout, 请求,用户的事件、messageChannel, setImmediate
requestIDleCallback requestFrameAnimation (这个是放在渲染的) 只能算回调,你可以当成宏任务
宏任务队列(消息队列 底层是由多个队列组成 , 我们实际看的宏任务队列当成一个来理解) 时间到达后会将任务放到宏任务队列中 webApi 微任务队列 每次执行宏任务的时候 都会产生一个微任务队列 , 我们在看执行过程的时候当成只有一个来理解 微任务就是个回调 代码执行的过程中 宏任务和微任务 会将对应的结果放到不同的队列中 等待当前宏任务执行完毕后,会将微任务全部清空 (在微任务执行的过程中 如果在产生微任务 则会将当前产生的微任务放到队列尾部 ) 微任务都执行完毕后,会在宏任务队列中拿出来一个执行 (宏任务每次执行一个,微任务每次执行一堆)
js 是单线程的所以要有一个事件触发线程 来实现任务的调度 宏任务的执行顺序 是按照 调用的顺序 (时间一样的情况下), 如果时间不一样,则以放入的顺序为准 渲染是要在特定的时机才能渲染, 根据浏览器的刷新频率 16.6ms, 不是每一轮都要渲染的 (一定在微任务之后)
对于事件回调类宏任务,直接调用事件回调和通过用户触发效果不太一样,例如如下代码:
button.addEventListener('click',()=>{
console.log('listener1');
Promise.resolve().then(()=>console.log('micro task1'))
})
button.addEventListener('click',()=>{
console.log('listener2');
Promise.resolve().then(()=>console.log('micro task2'))
})
button.click();
控制台将输出:
listener1
listener2
micro task1
micro task2
从结果上来看,是把两个回调放到了同一个宏任务中执行 但如果点击按钮的话,控制台将输出:
listener1
micro task1
listener2
micro task2
从结果上来看,是分别执行了两个宏任务
循环引用
commonjs 的特点就是内部会维护一个属性 loaded 内部会用一个变量来表示这个模块是否加载完成 如果没有加载完成 只会运行到已经加载的部分
因此出现循环引用时,如果被循环引用的模块还没有执行module.exports = xxx,就会报错了,但依然可以正常执行 参考:/jiagouke07-3-node/5.module/c1.js
全局方法
process.cwd(), path.resolve()两种方式效果一致,而且这个路径是可以修改的 cwd: current working directory
process.chdir("../../")方法的执行会改变process.cwd(), path.resolve()的结果
事件循环
官方给的文档: nodejs.org/en/docs/gui…
对于微任务来说,nextTick比Promise.resolve()优先级要高,即使将Promise.resolve()放到process.nextTick前面,nextTick的回调也会先执行
Promise.resolve().then(()=>{
console.log('promise')
})
process.nextTick(()=>{ // 此方法用的比较少
console.log('nextTick')
})
EventEmit模块提供了一个固定的订阅名——newListener,如下面案例所示,girl对象通过once方法给"失恋"绑定了2个方法,每绑定一次,newListener就会触发一次
const EventEmitter = require("events"); //事件触发器 on emit off once
function Girl() {}
// class Girl extends EventEmitter{} ;// 会继承实例属性,也会继承原型属性
Object.setPrototypeOf(Girl.prototype, EventEmitter.prototype);
const girl = new Girl();
const sleep = function (type) {
console.log("睡觉", type);
};
const drink = function (type) {
console.log("喝", type);
};
let wating = false;
girl.on("newListener", function (type) {
// 每次绑定事件的时候 就会执行此方法
// newListener的回调的调用时机,实在绑定之前触发的
girl.emit(type);
if (!wating) {
process.nextTick(() => {
girl.emit(type);
wating = false;
});
wating = true;
}*/
});
girl.once("失恋", sleep); // on('wrapper')
girl.once("失恋", drink);
遍历删除文件夹下所有子文件和子文件夹
方法一:串行删除,即使树有很多个子节点,也是一个一个的删,和并行删除相比,效率低
function removeDir(filepath, callback) {
fs.stat(filepath, function (err, statObj) {
if (err) return callback(err);
if (statObj.isDirectory()) {
fs.readdir(filepath, function (err, dirs) {
if (err) return callback(err);
dirs = dirs.map((item) => path.join(filepath, item));
let idx = 0; // 异步遍历 就是 递归迭代
function next() {
if (idx === dirs.length) return fs.rmdir(filepath, callback);
let dir = dirs[idx++]; // 先删除第一个,第一个删除后,删除下一个
removeDir(dir, next);
}
next();
});
} else {
fs.unlink(filepath, callback); // 如果是文件直接删除即可
}
});
}
removeDir("a", function (err) {
if (err) return console.log(err);
console.log("删除成功");
});
方法二:并行删除,子树可以同时删除
function removeDir(filepath, callback) {
fs.stat(filepath, function (err, statObj) {
if (err) return callback(err);
if (statObj.isDirectory()) {
fs.readdir(filepath, function (err, dirs) {
if (err) return callback(err);
dirs = dirs.map((item) => path.join(filepath, item));
if (dirs.length === 0) {
// 如果没有孩子,则直接删除即可
return fs.rmdir(filepath, callback);
}
let times = 0;
function done() {
if (++times === dirs.length) {
return fs.rmdir(filepath, callback);
}
}
for (let i = 0; i < dirs.length; i++) {
// 同时删除两个节点
removeDir(dirs[i], done);
}
});
} else {
fs.unlink(filepath, callback); // 如果是文件直接删除即可
}
});
}
removeDir("a", function (err) {
if (err) return console.log(err);
console.log("删除成功");
});
用Promise.all优化并行删的逻辑
function removeDir(filepath) {
return new Promise((resolve, reject) => {
fs.stat(filepath, function (err, statObj) {
if (err) return reject(err);
if (statObj.isDirectory()) {
fs.readdir(filepath, function (err, dirs) {
if (err) return reject(err);
dirs = dirs.map((item) => removeDir(path.join(filepath, item)));
Promise.all(dirs).then(() => fs.rmdir(filepath, resolve));
});
} else {
fs.unlink(filepath, resolve); // 如果是文件直接删除即可
}
});
});
}
用async await fsPromise继续优化上述代码:
const fsPromise = require("fs/promises");
async function removeDir(filepath) {
let statObj = await fsPromise.stat(filepath);
if (statObj.isDirectory()) {
let dirs = await fsPromise.readdir(filepath);
dirs = dirs.map((item) => removeDir(path.join(filepath, item)));
await Promise.all(dirs);
await fsPromise.rmdir(filepath);
} else {
return fsPromise.unlink(filepath); // 如果是文件直接删除即可
}
}
方法三:层序遍历,即先广度优先遍历,从最底层的叶子结点开始逐级往上,将全路径存到一个stack中,再删除
function rmdirSync(filepath) {
let stack = [filepath]; // 默认认为第一个是文件夹
let idx = 0;
let current;
while ((current = stack[idx++])) {
let statObj = fs.statSync(current);
if (statObj.isDirectory()) {
let dirs = fs.readdirSync(current);
stack = [...stack, ...dirs.map((dir) => path.join(current, dir))];
}
}
// [ 'a', 'a/b', 'a/b/c', 'a/b/c/d' ]
for (let i = stack.length - 1; i >= 0; i--) {
let statObj = fs.statSync(stack[i]);
if (statObj.isDirectory()) {
fs.rmdirSync(stack[i]);
} else {
fs.unlinkSync(stack[i]);
}
}
}