一杯茶的时间,上手 Node,web前端面试问题及答案

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

你也许会好奇,通过名称导入 Node 模块的时候(例如 express),是从哪里找到这个模块的?实际上每个模块都有个路径搜索列表 module.paths,在后面讲解 module 对象的时候就会一清二楚了。

exports

我们已经学会了用 require 导入其他模块中的内容,那么怎么写一个 Node 模块,并导出其中内容呢?答案就是用 exports 对象。

例如我们写一个 Node 模块 myModule.js:

// myModule.js

function add(a, b) {

return a + b;

}

// 导出函数 add

exports.add = add;

通过将 add 函数添加到 exports 对象中,外面的模块就可以通过以下代码使用这个函数。在 myModule.js 旁边创建一个 main.js,代码如下:

// main.js

const myModule = require(‘./myModule’);

// 调用 myModule.js 中的 add 函数

myModule.add(1, 2);

提示

如果你熟悉 ECMAScript 6 中的解构赋值[7],那么可以用更优雅的方式获取 add 函数:

const { add } = require(‘./myModule’);

module

通过 require 和 exports,我们已经知道了如何导入、导出 Node 模块中的内容,但是你可能还是觉得 Node 模块机制有一丝丝神秘的感觉。接下来,我们将掀开这神秘的面纱,了解一下背后的主角——module 模块对象。

我们可以在刚才的 myModule.js 文件的最后加上这一行代码:

console.log(‘module myModule:’, module);

在 main.js 最后加上:

console.log(‘module main:’, module);

运行后会打印出来这样的内容(左边是 myModule,右边是 module):

可以看到 module 对象有以下字段:

id:模块的唯一标识符,如果是被运行的主程序(例如 main.js)则为 .,如果是被导入的模块(例如 myModule.js)则等同于此文件名(即下面的 filename 字段)•path 和 filename:模块所在路径和文件名,没啥好说的•exports:模块所导出的内容,实际上之前的 exports 对象是指向 module.exports 的引用。例如对于 myModule.js,刚才我们导出了 add 函数,因此出现在了这个 exports 字段里面;而 main.js 没有导出任何内容,因此 exports 字段为空•parent 和 children:用于记录模块之间的导入关系,例如 main.js 中 require 了 myModule.js,那么 main 就是 myModule 的 parent,myModule 就是 main 的 childrenloaded:模块是否被加载,从上图中可以看出只有 children 中列出的模块才会被加载•paths:这个就是 Node 搜索文件模块的路径列表,Node 会从第一个路径到最后一个路径依次搜索指定的 Node 模块,找到了则导入,找不到就会报错

提示

如果你仔细观察,会发现 Node 文件模块查找路径(module.paths)的方式其实是这样的:先找当前目录下的 node_modules,没有的话再找上一级目录的 node_modules,还没找到的话就一直向上找,直到根目录下的 node_modules。

深入理解 module.exports

之前我们提到,exports 对象本质上是 module.exports 的引用。也就是说,下面两行代码是等价的:

// 导出 add 函数

exports.add = add;

// 和上面一行代码是一样的

module.exports.add = add;

实际上还有第二种导出方式,直接把 add 函数赋给 module.exports 对象:

module.exports = add;

这样写和第一种导出方式有什么区别呢?第一种方式,在 exports 对象上添加一个属性名为 add,该属性的值为 add 函数;第二种方式,直接令 exports 对象为 add 函数。可能有点绕,但是请一定要理解这两者的重大区别!

在 require 时,两者的区别就很明显了:

// 第一种导出方式,需要访问 add 属性获取到 add 函数

const myModule = require(‘myModule’);

myModule.add(1, 2);

// 第二种导出方式,可以直接使用 add 函数

const add = require(‘myModule’);

add(1, 2);

警告

直接写 exports = add; 无法导出 add 函数,因为 exports 本质上是指向 module 的 exports 属性的引用,直接对 exports 赋值只会改变 exports,对 module.exports 没有影响。如果你觉得难以理解,那我们用 apple 和 price 类比 module 和 exports

apple = { price: 1 }; // 想象 apple 就是 module

price = apple.price; // 想象 price 就是 exports

apple.price = 3; // 改变了 apple.price

price = 3; // 只改变了 price,没有改变 apple.price

我们只能通过 apple.price = 1 设置 price 属性,而直接对 price 赋值并不能修改 apple.price

重构 timer 脚本

在聊了这么多关于 Node 模块机制的内容后,是时候回到我们之前的定时器脚本 timer.js 了。我们首先创建一个新的 Node 模块 info.js,用于打印系统信息,代码如下:

const os = require(‘os’);

function printProgramInfo() {

console.log(‘当前用户’, os.userInfo().username);

console.log(‘当前进程 ID’, process.pid);

console.log(‘当前脚本路径’, __filename);

}

module.exports = printProgramInfo;

这里我们导入了 Node 内置模块 os,并通过 os.userInfo() 查询到了系统用户名,接着通过 module.exports 导出了 printProgramInfo 函数。

然后创建第二个 Node 模块 datetime.js,用于返回当前的时间,代码如下:

function getCurrentTime() {

const time = new Date();

return time.toLocaleString();

}

exports.getCurrentTime = getCurrentTime;

上面的模块中,我们选择了通过 exports 导出 getCurrentTime 函数。

最后,我们在 timer.js 中通过 require 导入刚才两个模块,并分别调用模块中的函数 printProgramInfo 和 getCurrentTime,代码如下:

const printProgramInfo = require(‘./info’);

const datetime = require(‘./datetime’);

setTimeout(() => {

console.log(‘Hello World!’);

}, 3000);

printProgramInfo();

console.log(‘当前时间’, datetime.getCurrentTime());

再运行一下 timer.js,输出内容应该与之前完全一致。

读到这里,我想先恭喜你渡过了 Node.js 入门最难的一关!如果你已经真正地理解了 Node 模块机制,那么我相信接下来的学习会无比轻松哦。

命令行开发:接受输入参数


Node.js 作为可以在操作系统中直接运行 JavaScript 代码的平台,为前端开发者开启了无限可能,其中就包括一系列用于实现前端自动化工作流的命令行工具,例如 Grunt[8]、Gulp[9] 还有大名鼎鼎的 Webpack[10]。

从这一步开始,我们将把 timer.js 改造成一个命令行应用。具体地,我们希望 timer.js 可以通过命令行参数指定等待的时间(time 选项)和最终输出的信息(message 选项):

$ node timer.js --time 5 --message “Hello Tuture”

通过 process.argv 读取命令行参数

之前在讲全局对象 process 时提到一个 argv 属性,能够获取命令行参数的数组。创建一个 args.js 文件,代码如下:

console.log(process.argv);

然后运行以下命令:

$ node args.js --time 5 --message “Hello Tuture”

输出一个数组:

[

‘/Users/mRc/.nvm/versions/node/v12.10.0/bin/node’,

‘/Users/mRc/Tutorials/nodejs-quickstart/args.js’,

‘–time’,

‘5’,

‘–message’,

‘Hello Tuture’

]

可以看到,process.argv 数组的第 0 个元素是 node 的实际路径,第 1 个元素是 args.js 的路径,后面则是输入的所有参数。

实现命令行应用

根据刚才的分析,我们可以非常简单粗暴地获取 process.argv 的第 3 个和第 5 个元素,分别可以得到 time 和 message 参数。于是修改 timer.js 的代码如下:

const printProgramInfo = require(‘./info’);

const datetime = require(‘./datetime’);

const waitTime = Number(process.argv[3]);

const message = process.argv[5];

setTimeout(() => {

console.log(message);

}, waitTime * 1000);

printProgramInfo();

console.log(‘当前时间’, datetime.getCurrentTime());

提醒一下,setTimeout 中时间的单位是毫秒,而我们指定的时间参数单位是秒,因此要乘 1000。

运行 timer.js,加上刚才说的所有参数:

$ node timer.js --time 5 --message “Hello Tuture”

等待 5 秒钟后,你就看到了 Hello Tuture 的提示文本!

不过很显然,目前这个版本有很大的问题:输入参数的格式是固定的,很不灵活,比如说调换 time 和 message 的输入顺序就会出错,也不能检查用户是否输入了指定的参数,格式是否正确等等。如果要亲自实现上面所说的功能,那可得花很大的力气,说不定还会有不少 Bug。有没有更好的方案呢?

npm:洪荒之力,都赐予你


从这一节开始,你将不再是一个人写代码。你的背后将拥有百万名 JavaScript 开发者的支持,而这一切仅需要 npm 就可以实现。npm 包括:

•npm 命令行工具(安装 node 时也会附带安装)•npm 集中式依赖仓库(registry),存放了其他 JavaScript 开发者分享的 npm 包•npm 网站[11],可以搜索需要的 npm 包、管理 npm 帐户等

npm 初探

我们首先打开终端(命令行),检查一下 npm 命令是否可用:

$ npm -v

6.10.3

然后在当前目录(也就是刚才编辑的 timer.js 所在的文件夹)运行以下命令,把当前项目初始化为 npm 项目:

$ npm init

这时候 npm 会提一系列问题,你可以一路回车下去,也可以仔细回答,最终会创建一个 package.json 文件。package.json 文件是一个 npm 项目的核心,记录了这个项目所有的关键信息,内容如下:

{

“name”: “timer”,

“version”: “1.0.0”,

“description”: “A cool timer”,

“main”: “timer.js”,

“scripts”: {

“test”: “echo “Error: no test specified” && exit 1”

},

“repository”: {

“type”: “git”,

“url”: “git+https://github.com/mRcfps/nodejs-quickstart.git”

},

“author”: “mRcfps”,

“license”: “ISC”,

“bugs”: {

“url”: “https://github.com/mRcfps/nodejs-quickstart/issues”

},

“homepage”: “https://github.com/mRcfps/nodejs-quickstart#readme”

}

其中大部分字段的含义都很明确,例如 name 项目名称、 version 版本号、description 描述、author 作者等等。不过这个 scripts 字段你可能会比较困惑,我们会在下一节中详细介绍。

安装 npm 包

接下来我们将讲解 npm 最最最常用的命令—— install。没错,毫不夸张地说,一个 JavaScript 程序员用的最多的 npm 命令就是 npm install

在安装我们需要的 npm 包之前,我们需要去探索一下有哪些包可以为我们所用。通常,我们可以在 npm 官方网站[12] 上进行关键词搜索(记得用英文哦),比如说我们搜 command line:

出来的第一个结果 commander 就很符合我们的需要,点进去就是安装的说明和使用文档[13]。我们还想要一个“加载中”的动画效果,提高用户的使用体验,试着搜一下 loading 关键词:

第二个结果 ora 也符合我们的需要。那我们现在就安装这两个 npm 包:

$ npm install commander ora

少许等待后,可以看到 package.json 多了一个非常重要的 dependencies 字段:

“dependencies”: {

“commander”: “^4.0.1”,

“ora”: “^4.0.3”

}

这个字段中就记录了我们这个项目的直接依赖。与直接依赖相对的就是间接依赖,例如 commander 和 ora 的依赖,我们通常不用关心。所有的 npm 包(直接依赖和间接依赖)全部都存放在项目的 node_modules 目录中。

提示

node_modules 通常有很多的文件,因此不会加入到 Git 版本控制系统中,你从网上下载的 npm 项目一般也只会有 package.json,这时候只需运行 npm install(后面不跟任何内容),就可以下载并安装所有依赖了。

整个 package.json 代码如下所示:

{

“name”: “timer”,

“version”: “1.0.0”,

“description”: “A cool timer”,

“main”: “timer.js”,

“scripts”: {

“test”: “echo “Error: no test specified” && exit 1”

},

“repository”: {

“type”: “git”,

“url”: “git+https://github.com/mRcfps/nodejs-quickstart.git”

},

“author”: “mRcfps”,

“license”: “ISC”,

“bugs”: {

“url”: “https://github.com/mRcfps/nodejs-quickstart/issues”

},

“homepage”: “https://github.com/mRcfps/nodejs-quickstart#readme”,

“dependencies”: {

“commander”: “^4.0.1”,

“ora”: “^4.0.3”

}

}

关于版本号

在软件开发中,版本号是一个非常重要的概念,不同版本的软件存在或大或小的差异。npm 采用了语义版本号(Semantic Versioning,简称 semver[14]),具体规定如下:

•版本格式为:主版本号.次版本号.修订号•主版本号的改变意味着不兼容的 API 修改•次版本号的改变意味着做了向下兼容的功能性新增•修订号的改变意味着做了向下兼容的问题修正

提示

向下兼容的简单理解就是功能只增不减

因此在 package.json 的 dependencies 字段中,可以通过以下方式指定版本:

精确版本:例如 1.0.0,一定只会安装版本为 1.0.0 的依赖•锁定主版本和次版本:可以写成 1.01.0.x 或 ~1.0.0( npm install 默认采用的形式),那么可能会安装例如 1.0.8 的依赖•仅锁定主版本:可以写成 11.x 或 1^1.0.0,那么可能会安装例如 1.1.0 的依赖•最新版本:可以写成 * 或 x,那么直接安装最新版本(不推荐)

你也许注意到了 npm 还创建了一个 package-lock.json,这个文件就是用来锁定全部直接依赖和间接依赖的精确版本号,或者说提供了关于 node_modules 目录的精确描述,从而确保在这个项目中开发的所有人都能有完全一致的 npm 依赖。

站在巨人的肩膀上

我们在大致读了一下 commander 和 ora 的文档之后,就可以开始用起来了,修改 timer.js 代码如下:

const program = require(‘commander’);

const ora = require(‘ora’);

const printProgramInfo = require(‘./info’);

const datetime = require(‘./datetime’);

program

.option(‘-t, --time ’, ‘等待时间 (秒)’, 3)

.option(‘-m, --message ’, ‘要输出的信息’, ‘Hello World’)

.parse(process.argv);

setTimeout(() => {

spinner.stop();

console.log(program.message);

}, program.time * 1000);

printProgramInfo();

console.log(‘当前时间’, datetime.getCurrentTime());

const spinner = ora(‘正在加载中,请稍后 …’).start();

这次,我们再次运行 timer.js:

$ node timer.js --message “洪荒之力!” --time 5

转起来了!

尝鲜 npm scripts


在本教程的最后一节中,我们将简单地介绍一下 npm scripts,也就是 npm 脚本。之前在 package.json 中提到,有个字段叫 scripts,这个字段就定义了全部的 npm scripts。我们发现在用 npm init 时创建的 package.json 文件默认就添加了一个 test 脚本:

“test”: “echo “Error: no test specified” && exit 1”

那一串命令就是 test 脚本将要执行的内容,我们可以通过 npm test 命令执行该脚本:

$ npm test

timer@1.0.0 test /Users/mRc/Tutorials/nodejs-quickstart

echo “Error: no test specified” && exit 1

Error: no test specified

npm ERR! Test failed. See above for more details.

在初步体验了 npm scripts 之后,我们有必要了解一下 npm scripts 分为两大类:

预定义脚本:例如 teststartinstallpublish 等等,直接通过 npm <scriptName> 运行,例如 npm test,所有预定义的脚本可查看文档[15]•自定义脚本:除了以上自带脚本的其他脚本,需要通过 npm run <scriptName> 运行,例如 npm run custom

现在就让我们开始为 timer 项目添加两个 npm scripts,分别是 start 和 lint。第一个是预定义的,用于启动我们的 timer.js;第二个是静态代码检查,用于在开发时检查我们的代码。首先安装 ESLint[16] npm 包:

$ npm install eslint --save-dev

$ # 或者

$ npm install eslint -D

注意到我们加了一个 -D 或 --save-dev 选项,代表 eslint 是一个开发依赖,在实际项目发布或部署时不需要用到。npm 会把所有开发依赖添加到 devDependencies 字段中。然后分别添加 start 和 lint 脚本,代码如下:

{

“name”: “timer”,

“version”: “1.0.0”,

“description”: “A cool timer”,

“main”: “timer.js”,

“scripts”: {

“lint”: “eslint **/*.js”,

“start”: “node timer.js -m ‘上手了’ -t 3”,

“test”: “echo “Error: no test specified” && exit 1”

},

“repository”: {

“type”: “git”,

“url”: “git+https://github.com/mRcfps/nodejs-quickstart.git”

},

“author”: “mRcfps”,

“license”: “ISC”,

“bugs”: {

“url”: “https://github.com/mRcfps/nodejs-quickstart/issues”

},

“homepage”: “https://github.com/mRcfps/nodejs-quickstart#readme”,

“dependencies”: {

“commander”: “^4.0.1”,

“ora”: “^4.0.3”

},

“devDependencies”: {

“eslint”: “^6.7.2”

}

}

ESLint 的使用需要一个配置文件,创建 .eslintrc.js 文件(注意最前面有一个点),代码如下:

module.exports = {

“env”: {

“es6”: true,

“node”: true,

},

“extends”: “eslint:recommended”,

};

运行 npm start,可以看到成功地运行了我们的 timer.js 脚本;而运行 npm run lint,没有输出任何结果(代表静态检查通过)。

npm scripts 看上去平淡无奇,但是却能为项目开发提供非常便利的工作流。例如,之前构建一个项目需要非常复杂的命令,但是如果你实现了一个 build npm 脚本,那么当你的同事拿到这份代码时,只需简单地执行 npm run build 就可以开始构建,而无需关心背后的技术细节。在后续的 Node.js 或是前端学习中,我们会在实际项目中使用各种 npm scripts 来定义我们的工作流,大家慢慢就会领会到它的强大了。

下次再见:监听 exit 事件


在这篇教程的最后一节中,我们将让你简单地感受 Node 的事件机制。Node 的事件机制是比较复杂的,足够讲半本书,但这篇教程希望能通过一个非常简单的实例,让你对 Node 事件有个初步的了解。

提示

如果你有过在网页(或其他用户界面)开发中编写事件处理(例如鼠标点击)的经验,那么你一定会觉得 Node 中处理事件的方式似曾相识而又符合直觉。

我们在前面简单地提了一下回调函数。实际上,回调函数和事件机制共同组成了 Node 的异步世界。具体而言,Node 中的事件都是通过 events 核心模块中的 EventEmitter 这个类实现的。EventEmitter 包括两个最关键的方法:

on:用来监听事件的发生•emit:用来触发新的事件

请看下面这个代码片段:

const EventEmitter = require(‘events’).EventEmitter;

const emitter = new EventEmitter();

最后

最后写上我自己一直喜欢的一句名言:世界上只有一种真正的英雄主义就是在认清生活真相之后仍然热爱它

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
慢慢就会领会到它的强大了。

下次再见:监听 exit 事件


在这篇教程的最后一节中,我们将让你简单地感受 Node 的事件机制。Node 的事件机制是比较复杂的,足够讲半本书,但这篇教程希望能通过一个非常简单的实例,让你对 Node 事件有个初步的了解。

提示

如果你有过在网页(或其他用户界面)开发中编写事件处理(例如鼠标点击)的经验,那么你一定会觉得 Node 中处理事件的方式似曾相识而又符合直觉。

我们在前面简单地提了一下回调函数。实际上,回调函数和事件机制共同组成了 Node 的异步世界。具体而言,Node 中的事件都是通过 events 核心模块中的 EventEmitter 这个类实现的。EventEmitter 包括两个最关键的方法:

on:用来监听事件的发生•emit:用来触发新的事件

请看下面这个代码片段:

const EventEmitter = require(‘events’).EventEmitter;

const emitter = new EventEmitter();

最后

最后写上我自己一直喜欢的一句名言:世界上只有一种真正的英雄主义就是在认清生活真相之后仍然热爱它

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-l3Ujdp8L-1713442464572)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些常见的 Vue 前端面试题及其答案: 1. Vue 是什么? Vue 是一种用于构建用户界面的渐进式框架。它被设计为易于上手,并且可以与其他库或现有项目进行集成。 2. Vue 的特点是什么? Vue 的特点包括: - 渐进式:可以逐步采用和集成到现有项目中; - 双向数据绑定:实现了数据和视图之间的自动同步; - 组件化:将页面拆分为多个组件,方便维护和复用; - 轻量级:文件大小小,性能高效; - 生态丰富:有大量的插件和工具可供选择。 3. Vue 的生命周期有哪些? Vue 的生命周期包括:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy 和 destroyed。 4. Vue 中的 computed 和 watch 的区别是什么? computed 和 watch 都是用于监听 Vue 实例中数据变化的方式。 computed 是计算属性,只有当计算属性中用到的数据发生变化时才会重新计算,可以缓存计算结果,提高性能。 watch 是侦听器,可以监控数据的变化并执行相应的操作,适用于需要在数据变化时执行异步或开销较大的操作。 5. Vue 中的 v-if 和 v-show 的区别是什么? v-if 和 v-show 都是用于控制元素的显示和隐藏。 v-if 在条件表达式为 true 时才会渲染元素,否则不会渲染,可以在切换时销毁或重建元素,适用于在条件较少变化时使用。 v-show 则是通过修改元素的 display 样式属性来控制元素的显示和隐藏,不会销毁或重建元素,适用于在频繁切换时使用。 6. Vue 中的路由是什么? Vue 中的路由是指通过 URL 来访问不同的页面或组件。Vue-Router 是 Vue 官方的路由管理器,可以实现单页面应用(SPA)的路由控制。 7. Vue 中的组件通信有哪些方式? Vue 中的组件通信有以下方式: - 父子组件通信:父组件通过 props 将数据传递给子组件,子组件通过 $emit 触发事件将数据传递回父组件; - 兄弟组件通信:可以通过共同的父组件来实现兄弟组件之间的通信; - 跨级组件通信:可以通过 provide/inject 来实现祖先组件向后代组件的数据传递; - 非父子组件通信:可以通过 Vuex 或事件总线(event bus)来实现任意组件之间的通信。 以上是一些常见的 Vue 前端面试题及其答案,希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值