Node.js常见面试题
1、NodeJS中的this为什么是一个空对象?
因为所有的NodeJS文件在执行的时候都会被包裹到一个函数中,this都被修改为module.exports。(详细请看之前的Node模块原理分析【详细】那篇文章)。
2、NodeJS中为什么可以直接使用exports、require、module、__filename、__dirname?
因为所有的NodeJS文件在执行的时候都会被包裹到一个函数中,这些属性都被通过参数的形式传递过来了。
// var args = [this.exports, require, module, filename, dirname];
// var result = compiledWrapper.call(this.exports, args);
都会被包裹到下面函数中:
(function (exports, require, module, __filename, __dirname) {
exports.名 = 值;
});
3、NodeJS中为什么不能直接exports赋值,而可以给module.exports赋值?
<!--
(function (exports, require, module, __filename, __dirname) {
exports = "lnj";
});
jsScript.call(module.exports, module.exports);
return module.exports;
相当于
let exports = module.exports;
exports = "lnj";
return module.exports;
exports是形参,module.exports传递给它,两者指向同一个对象。如果直接给exports赋值(exports=‘aaa’)则相当于修改了它的指向,但最后却返回module.exports。
4、通过require导入包的时候应该使用var/let还是const?
导入包的目的是使用包而不是修改包,所以导入包时使用const接受。
5、require和import的区别
import和require都是被模块化所使用。在ES6当中,用export导出接口,用import引入模块。但是在node模块中,使用module.exports/exports导出接口,使用require引入模块,
- 区别一:出现的时间不同。
- require表示的是运行时加载/调用,所以理论上可以运用在代码的任何地方。
- import表示的是编译时加载(效率更高),由于是编译时加载,所以import命令会提升到整个模块的头部。
- import输入的变量是只读的,引用类型可以,其他模块也可以读到改后的值,但不建议修改。
- import是静态执行,不能使用表达式和变量。
// 报错 import { 'f' + 'oo' } from 'my_module'; // 报错 let module = 'my_module'; import { foo } from module;
- import是Singleton模式。
import { foo } from 'my_module'; import { bar } from 'my_module'; // 等同于 import { foo, bar } from 'my_module'; // 虽然foo和bar在两个语句加载,但是他们对应的是同一个my_module实例
- Singleton模式。
即单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前的进程中只有一个实例(根据需要,也有可能一个线程中属于单列模式,如:仅线程上下文内使用同一个实例)。
定义::保证一个类仅有一个实例,并提供一个访问它的全局访问点(类似node的global)。
- 遵循的模块化规范不同。
- 本质
- require是赋值过程。module.exports后面的内容是什么,require的结果就是什么,比如对象、数字、字符串、函数等,然后再把require的结果赋值给某个变量,它相当于module.exports的传送门。
- import是结构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require。
- import虽然是es6中的语法,但就目前来说,所有的引擎都还没有实现import。
- import语法实际上会被转码为require。这也是为什么在模块导出时使用module.exports,在引入模板时使用import仍然起效,因为本质上,import会被转码为require去执行。
6、浏览器的事件循环
6.1、JS是单线程的
6.2、执行顺序
- 程序运行会从上至下依次执行所有的同步代码。
- 在执行的过程中如果遇到异步代码会将异步代码放到事件循环中。
- 当所有同步代码都执行完毕后,JS会不断检测事件循环中的异步代码是否满足条件。
- 一旦满足条件就执行满足条件的异步代码。
6.3、宏任务和微任务
在JS的异步代码中又区分“宏任务(MacroTask)”和“微任务(MicroTask)”。
宏任务:宏/大的意思,可以理解为比较费时比较慢的任务。
微任务:微/小的意思,可以理解为相对没那么费时没那么慢的任务。
6.4、常见的宏任务和微任务
宏任务:setTimeout、setInterval、setImmediate(IE独有)…
微任务:Promise、MutationObserver、process.nextTick(node独有)…
注意点:所有的宏任务和微任务都会放到自己的执行队列中,也就是有一个宏任务队列和一个微任务队列。所有放到队列中的任务都采用“先进先出原则”,也就是多个任务同时满足条件,那么会先执行先放进去的。
6.5、完整执行顺序
每次执行完一个宏任务都会立刻检查微任务队列有没有被清空,如果没有就立刻清空。
附:
1、setImmediate(IE浏览器独有)
2、MutationObserver是专门监听节点的变化
// html
<body>
<div></div>
<button class="add">添加节点</button>
<button class="del">删除节点</button>
</body>
// js
let oDiv = document.querySelector("div");
let oAddBtn = document.querySelector(".add");
let oDelBtn = document.querySelector(".del");
oAddBtn.onclick = function () {
let op = document.createElement("p");
op.innerText = "我是段落";
oDiv.appendChild(op);
}
oDelBtn.onclick = function () {
let op = document.querySelector("p");
oDiv.removeChild(op);
}
let mb = new MutationObserver(function () {
console.log("执行了");
});
mb.observe(oDiv, {
"childList": true
});
console.log("同步代码Start");
console.log("同步代码End");
7、NodeJS的事件循环(Event Loop)
7.1、概述
和浏览器中一样NodeJS中也有事件循环(Event Loop),但是由于代码执行的宿主环境和应用场景不同,所以两者的事件循环也有所不同。
扩展阅读:
在NodeJS中使用libuv实现了Event Loop。
源码地址:https://github.com/libuv/libuv
7.2、NodeJS事件循环和浏览器事件循环区别
7.3、NodeJS中的任务队列
图一:
- 注意点:
和浏览器不同的是没有宏任务队列和微任务队列的概念。
宏任务被放到了不同的队列中,但是没有队列是存放微任务的队列。
微任务会在执行完同步代码和队列切换的时候执行。
什么时候切换队列?
当队列为空(已经执行完毕或者没有满足条件回倒)或者执行的回调函数数量达到系统设定的阈值时任务队列就会切换。
图二:NodeJS完整执行顺序。
注意点:
执行完poll,会查看check队列是否有内容,有就切换到check。如果check队列没有内容,就会查看timers是否有内容,有就切换到timers。如果check队列和timers队列都没有内容,为了避免资源浪费就会阻塞在poll。
7.4、NodeJS-EventLoop面试题
/*
注意点: 如下代码输出的结果是随机的
在NodeJS中指定的延迟时间是有一定的误差的, 所以导致了输出结果随机的问题
* */
/*
setTimeout(function () {
console.log("setTimeout");
}, 0);
setImmediate(function () {
console.log("setImmediate");
});
*/
// 但是在下面的代码中输出结果都是固定的,即无论setTimeout、setImmediate顺序怎样,都会先执行setImmediate代码。
const path = require("path");
const fs = require("fs");
fs.readFile(path.join(__dirname, "04.js"), function () {
setTimeout(function () {
console.log("setTimeout");
}, 0);
setImmediate(function () {
console.log("setImmediate");
});
});
原因:
8、自定义本地包和全局包
8.1、包的规范(了解)
8.2、package.json字段分析(了解)
- name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
- description:包的简要说明。
- version:符合语义化版本识别规范的版本字符串。
- keywords:关键字数组,通常用于搜索。
- maintainers:维护者数组,每个元素要包含name、email(可选)、web(可选)字段。
- contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一个元素。
- bugs:提交bug的地址,可以是网站或者电子邮件地址。
- licenses:许可证数组,每个元素要包含type(许可证名称)和URL(连接到许可证文本的地址)字段。
- repositories:仓库托管地址数组,每个元素要包含type(仓库类型,如git)、url(仓库地址)和path(相对于仓库的路径,可选)字段。
- dependencies:生产环境包的依赖,一个关键数组,由包的名称和版本号组成。
- devDependencies:开发环境包的依赖,一个关联数组,由包的名称和版本号组成。
- main:指定包入口文件,如果没有配置main,默认会将index.js作为入口,如果包中没有index.js,那么就必须配置main。
- scripts:1、保存一些常用的指令,可以通过npm run key方式运行。2、应用场景:每次执行某个js文件都需要传递参数,并且每次传递的参数都是一样的,那么就可以通过将指令保存到script中来简化输入指令的操作。(通过指令执行某个文件的时候可以传递一个参数,并且该文件可以通过process.argv拿到这个参数。scripts可以简化执行命令)
执行npm run start就相当于执行node lgg.js name=lgg age=3
8.3、自定义包实现步骤
- 创建一个包文件夹。
- 初始化一个package.json文件。
- 初始化一个包入口js文件。
注意点:如果没有配置main,默认会将index.js作为入口,如果包中没有index.js,那么就必须配置main。 - 根据包信息配置package.json文件。
注意点:通过scripts可以帮助我们记住指令,然后通过npm run xxx方式,就可以执行该指令。如果指令的名称叫做start或者test,那么执行的时候可以不加run。
全局包:一般全局包都是些工具包比如nrm、yarn、cnpm等,工具包的特点需要自定义指令。
- 给package.json添加bin属性,告诉系统执行全局命令式时需要执行哪一。个JS文件。
- 在全局命令执行的JS文件中添加#! /usr/bin/envnode。 (node环境执行)
- 通过npm link 将本地包放到全局方便我们调试。
8.4、将自定义包发布到官网
注意:在第三步前,要把源切换为官网。
nrm ls;
nrm use npm;
原文地址:https://blog.csdn.net/weixin_44767973/article/details/127705276
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_33694.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!