node.js 文件操作 + 模块化 + 模块作用域 (module、require、exports)

1. 文件操作

1.1 文件读取

需求:使用 Node 中 提供的 文件操作API,读取 files 目录下的 1.txt 文档中文本内容:const fs = require('fs')

fs 核心模块中,提供了一个 fs.readFile 方法,来读取指定目录下的文件 fs.readFile()

fs.readFile 有三个参数:

  • 参数1: 表示要读取的文件的路径
  • 参数2: 表示 要以什么样的编码格式,来读取指定的文件 默认 编码格式为 null
  • 参数3: 表示 当文件读取完成,调用这个 callback 回调函数来处理读取的结果:
    • 第一个 参数,是 Error 对象,
    • 第二个参数,才是读取成功的结果
/**
 * 如果{}只有一行代码,可以去掉{}
 * 去掉function
 */
// 1.引入fs模块
const fs = require('fs');
// 2.调用fs.readFilse函数
fs.readFile('./files/1.txt', 'utf-8', (err, data) => {
    if (err) return console.log('读取文件失败:' + err.message)
    console.log('读取文件成功,内容是' + data)
})

1.2 文件写入

// 需求:调用 fs.writeFile 方法,向 files 目录中,写入一个 2.txt 文档   222

// 1. 导入 fs 文件操作模块
// const fs = require('fs')

// 2. 调用 fs.writeFile 写入文件
// 参数1: 路径字符串,表示要把文件内容,写入到哪个文件中
// 参数2: 要写入的数据,可以给定一个 字符串
// 参数3: 可选参数,表示 要以什么格式写入文件内容  默认以 utf8 格式写入文件【一般默认不传递第三个参数】
// 参数4: 文件写入完成之后的 callback 回调函数
//           在回调函数中,只有一个形参,err 错误对象

/**
 * 实例1:
 * 如果在使用 fs.writeFile 的时候,要写入的文件不存在,则直接写入;
 * 如果要写入的文件路径已经存在,则会覆盖之前的文件
 */

/**
 * 实例1:变形
 * 匿名函数改为箭头函数
 */

// 1. 导入 fs 文件操作模块
const fs = require('fs');
// 2.调用 fs.writeFile 写入文件
fs.writeFile('./files/2.txt', '222', (err) => {
    // 如果文件写入失败,则报错
    if (err) return console.log('写入文件失败!' + err.message)
    console.log('文件写入成功!')
})

1.3 文件追加

// 1. 导入 fs 文件操作模块
// const fs = require('fs')

// 2. 调用 fs.appendFile 追加文件内容
// 参数1: 表示要向哪个文件中追加内容,指定一个文件的路径
// 参数2: 表示要追加的具体的内容,可以传递字符串内容
// 参数3: 可选参数;表示 追加文本内容时候的编码格式,如果省略此参数,默认以 utf8 的格式追加内容
// 参数4: 表示 追加完成之后的回调
//         有形参 err ,追加失败之后的错误结果

/**
 * 实例1:
 * 如果要追加的文件路径不存在,则会先尝试创建这个文件,然后再向创建的文件中,追加具体的内容
 */

// 1.导入 fs 文件操作模块
const fs = require('fs');
// 2.调用 fs.appendFile 追加文件内容
fs.appendFile('./files/3.txt', '\n332', (err) => {
    if (err) return console.log('追加文件失败!' + err.message)
    console.log('追加文件成功!')
})

1.4 fs模块中路径操作问题【难点】

  • 使用 fs 模块操作文件的时候,如果提供的操作路径是 相对路径, 则会根据当前执行node命令时的磁盘目录,去拼接提供的文件的相对路径,从而容易出现问题;例如:

    /**
     *  演示,调用 fs 中方法的时候,如果提供的是相对路径,容易出现问题
     *  */
    
    // const fs = require('fs')
    // fs.readFile('./files/1.txt', 'utf-8', (err, data) => {
    //     if (err) return console.log('读取文件失败:' + err.message)
    //     console.log(data)
    // })
    
    // PS C:\Users\liulongbin\Desktop\node-day1>
    // 读取文件失败:ENOENT: no such file or directory, open 'C:\Users\liulongbin\Desktop\node-day1\files\1.txt'
    
    // PS C:\Users\liulongbin\Desktop>
    // 读取文件失败:ENOENT: no such file or directory, open 'C:\Users\liulongbin\Desktop\files\1.txt'
    
    // PS C:\Users\liulongbin>
    // 读取文件失败:ENOENT: no such file or directory, open 'C:\Users\liulongbin\files\1.txt'
    
  • 推荐使用 node 中提供的 __dirname 来解决 fs 模块操作文件时候的路径问题

    
    // 注意:在node中 __dirname 表示当前这个文件,所处的磁盘目录
    // console.log(__dirname)
    // console.log(__dirname + '/files/1.txt')
    
    
    /**
     * 结论:今后,只要在使用 fs 模块操作文件的时候,而且这个 方法需要提供一个文件的操作路径,
     * 这时候,大家一定要使用 __dirname 去拼接路径
     *  */
    
    const fs = require('fs');
    fs.readFile(__dirname + '/files/1.txt', 'utf8', (err, data) => {
        if (err) return console.log(err.message)
        console.log(data)
    })
    
    
    /**
     *     __dirname 和 __filename 之间的区别
     *  __dirname 表示 当前这个文件执行的时候,所处的 根目录,只是代表一层目录而已;
     *  __filename 表示 当前这个 文件的 完整路径,路径中包含了具体的文件名
     */
    
    // console.log(__dirname);
    // console.log(__filename)
    

1.5 读取文件信息

/**
 * 实例1:查看文件信息
 * stats.size 文件的大小 单位是 字节   Byte
 * stats.birthtime 文件的创建时间
 * stats.isFile()  通过 isFile() 方法判断是否为 文件
 * stats.isDirectory()  isDirectory() 判断是否为目录
 */

const fs = require('fs')

fs.stat(__dirname + '/files/1.txt', (err, stats) => {
    if (err) return console.log(err.message);
    console.log(stats.size)
    console.log(stats.birthtime)
    console.log(stats.isFile())
    console.log(stats.isDirectory())
})

/**
 * 实例2:查看文件夹信息
 */

const fs = require('fs')

fs.stat(__dirname, (err, stats) => {
    if (err) return console.log(err.message)
    console.log(stats.size)
    console.log(stats.birthtime)
    console.log(stats.isFile())
    console.log(stats.isDirectory())
})

1.7 复制文件

// ............................注意:拷贝文件有可能会报错,原因:是管理权限问题

/**
 * 拷贝文件:方法1
 */

const fs = require('fs');
fs.copyFile(__dirname + '/files/3.txt', __dirname + '/files/1-copy.txt', (err) => {
    if (err) return console.log('拷贝失败:' + err.message)
    console.log('拷贝成功!')
})

/**
 * 拷贝文件:方法2
 */

const fs = require('fs');
const path = require('path')
fs.copyFile(path.join(__dirname, '/files/1.txt'), path.join(__dirname, '/files/1-copy.txt'), (err) => {
    if (err) return console.log('拷贝失败:' + err.message)
    console.log('拷贝成功!')
})

1.8练习

  1. 整理成绩.txt文件中的数据到成绩 - ok.txt文件中,整理好的文件中,格式类似于:
小红:99
小白:100
小黄:70
小黑:66
小绿:88
// 1. 导入 fs 文件操作模块
// 2. 分析需求的处理步骤:
// 2.1 使用 fs.readFile 读取 成绩文本
// 2.2 把读取到的文本,处理成标准的格式
// 2.3 把处理的结果,输入到新的文件中

/** 1.split()方法用于切分字符串,它可以将字符串切分为数组。在切分完毕之后,返回的是一个新数组
 *    字符串.split("分割字符")
 *  本题:使用字符串的 split 方法分割字符串,以空格分隔 split(' ') ;注意:引号中间一定记得敲空格!!!
 *  2.数组.forEach(function(数组当前项的值,数组当前项的索引,数组对象本身){ })
 *  3.字符串.replace(被替换的字符串, 要替换为的字符串);
 *  4.数组.push()
 *  5.数组.join()
 */

/** join(参数) 方法用于把数组中的所有元素放入一个字符串
 *  元素是通过指定的分隔符进行分隔的
 *  指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符
 */

// 数组中包含很多字符串,字符串之间用逗号分隔
// 2. 循环 arr 数组,判断 当前循环的这一项长度是否大于 0 , 如果大于 0 才有操作的必要
// arr.forEach(function (item, i) {  })
// 定义 空数组,用来存储处理完毕之后的每个成绩信息
// 字符串.replace(被替换的字符串, 要替换为的字符串);

const fs = require('fs')
fs.readFile(__dirname + '/成绩.txt', 'utf8', (err, dataStr) => {
    if (err) return console.log('读取文件失败:' + err.message)
    let arr = dataStr.split(' ');
    let newArr = []
    arr.forEach(item => {
        if (item.length > 0) {
            let newScore = item.replace('=', ':')
            newArr.push(newScore)
        }
    })

    fs.writeFile(__dirname + '/成绩 - ok.txt', newArr.join('\n'), (err) => {
        if (err) return console.log('写入文件失败:' + err.message)
        console.log('处理成绩成功!')
    })
})

2. 路径操作

const path = require('path')
const fs = require('fs')

const result = path.join('c:/', 'a', './b', '/c', './d/e', 'f', '../g')
//  使用path.join拼接路径,会把 ./ 或者 ../ 中的.默认去除  c:\a\b\c\d\e\g
console.log(result)

/**
 * 方法1:
 */

const fs = require('fs')
fs.readFile(__dirname + '/files/1.txt', 'utf8', (err, dataStr) => {
    if (err) return console.log(err.message)
    console.log(dataStr)
})


/**
 *  最佳实践:以后只要设计到路径拼接,一定要使用 path.join(参数1,参数2,...) 
 *  path.join(__dirname, './files/1.txt')
 *  使用path.join拼接路径,会把 ./ 或者 ../  中的.默认去除
 *  */

const path = require('path')
const fs = require('fs')
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', (err, dataStr) => {
  if (err) return console.log(err.message)
  console.log(dataStr)
})

// ..........................了解内容部分

/** 
 * path模块中的其他属性或者方法
 * path.sep            路径分隔符
 * path.basename(路径)  获取文件名称的
 * path.dirname(路径)   获取文件所在的路径的
 * path.extname(路径)   获取文件的扩展名
 */

const path = require('path')
const str = 'c:/a/b/c/1.txt'

console.log(path.sep); // \
console.log(path.basename(str)); // 1.txt
console.log(path.dirname(str)); // c:/a/b/c
console.log(path.extname(str)); // .txt

3. Javascript 的单线程和异步

对于JS解析引擎来说,它做的工作是什么?

  1. 执行栈中的任务,如果任务是耗时操作,交给浏览器开启子线程执行;
    如果任务不耗时,由js解析引擎执行

  2. js解析引擎完成栈中的所有任务,通过事件循环查找队列,把队列中现有的任务取出来,放到自己的栈中 执行,执行完后,再次取任务执行任务…

浏览器做了什么事情?

浏览器自动开启子线程,处理异步耗时任务。

当耗时任务完成后,子线程并不会调用它的回调函数

而是把回调函数放入任务队列中,将来交给js解析引擎执行

一:Javascript 的解析和执行一直是单线程的,但是宿主环境(浏览器或node)是多线程的;

  • Javascript 的解析和执行一直是单线程的

    解读=>js是一门编程语言,它编写出来的代码只是普通的字符串而已;
    如果字符串要正常的解析和执行,必须依赖js解析引擎。
    js解析引擎在解析和执行代码的时候,是单线程的方式

    单线程:在js的解析引擎中永远只有一个人在干活,这个人在同一时间,只能完成一份工作

    总结:js解析引擎在运行的时候,是以单线程的模式解析和执行js代码的

  • 但是宿主环境(浏览器或node平台)是多线程的;

    解读=>宿主环境:js解析引擎必须放到特定的环境中才可以正常工作,如浏览器中包含了js的解析引擎;
    Node服务器端运行环境中也包含了js解析引擎。

    解读=>宿主环境是多线程的:在浏览器中我们同一时间有多个线程在执行工作,浏览器中有好多个并行任务
    但是在js的解析引擎中永远只有一个人在干活,这个人在同一时间,只能完成一份工作

二:异步任务是由宿主环境开启子线程完成,并通过事件驱动、回调函数、队列,把完成的任务, 交给主线程执行;

  • 异步任务是由宿主环境开启子线程完成

    解读=>异步任务:如定时器、ajax网络请求,这些任务都比较耗时,这些耗时的任务都是以异步的方式来执行的;
    【所有的异步任务都是由浏览器发起和执行的】
    并通过事件驱动、回调函数、队列,把完成的任务, 交给主线程执行

三:Javascript解析引擎,一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行

如果js解析引擎把自己栈中的任务执行完毕后,会立即查找任务队列把待执行的任务取出来,放到自己的栈中去执行;
但是待栈中的任务执行完毕后,通过事件循环机制,再次查找任务队列,如果队列中有任务,把待执行的任务取出来,放到自己的栈中去执行…

Node中的API为什么几乎都是异步操作

  1. 什么样的操作需要使用异步处理:要把 耗时的操作,放到异步中去执行;
  2. 异步执行任务的好处:能够提高 耗时的任务它的执行效率,提高 JS 解析引擎的工作效率;

4. 认识模块化

模块化就是一种约定,一定规范;

  1. 为什么要有模块化:为了解决文件之间的依赖关系

    假如如果没有模块化规范:
    买了华硕的鼠标,在联想电脑无法使用
    如果生产厂商按照USB接口规范生产USB接口,就不会出现这个问题

  2. 模块化是一种开发思想;具体开发中需要定制符合实际需求的模块化规范

  3. 大家可以把模块化规范,认为是一种明文的约定,大家都按照相同的约定写代码,减少了不必要的沟通成本,方便了各个模块之间的调用

CommonJS 规范

含义:就是一个js模块化规范而已,它里面规定了三个成员:require、exports、module

  • require是一个方法,用来导入成员
  • exports是一个对象,用来向外暴露成员
  • module是一个对象,表示当前这个模块

作用:是一套 Javascript 的模块化规范,规定了 模块的特性各模块之间如何相互依赖

用途:Node.js 中使用了 CommonJS 规范;

因为:在服务器端所有的模块都是在自己电脑本机上运行的,不存在走网络请求加载模块的情况

特点:同步加载模块;不适合在浏览器端使用;

 浏览器端适合使用:AMD 或者 CMD 规范

AMD/CMD可以理解为是commonjs在浏览器端的解决方案,AMD/CMD下,模块都是异步加载的;

1.AMD模块化规范代表:RequireJS
    *主要特性1:对于依赖的模块,AMD 是提前执行;
    *主要特性2:推崇依赖前置;

2.CMD模块化规范代表:SeaJS
    *主要特性1:对于依赖的模块,CMD 是延迟执行;CMD 推崇 as lazy as possible.
    *主要特性2:推崇依赖就近;

3.ES6的模块化(大趋势):es6是在语言标准层面上,实现了模块功能,而且实现得相当简单,
  完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案;


在CommonJS模块化规范中:它就具体规定了一个模块如何把自己内部的成员暴露给外部使用;
                       同时它也定义了外部在使用模块时应该遵循什么样的语法规范来进行使用

Node.js遵循了CommonJS规范:所以我们使用NodeJS编写代码的时候,无形中已经在使用了CommonJS规范

模块作用域 和 全局作用域

在Node.js中有两个作用域,分别是 全局作用域 和 模块作用域;

  1. 全局作用域使用 global 来访问,类似于浏览器中的window

  2. 每个 Javascript 文件,都是一个单独模块,每个模块都有自己独立的作用域

    因此:模块中的成员,默认无法被其它模块访问。

使用 global 全局作用域在模块之间共享成员

  1. 如果在某个模块内部,想为 全局的 global 作用域挂载一些属性,需要显示的调用global来挂载;
  2. 注意:使用global全局作用域来共享成员,会存在全局变量污染问题;

模块作用域

  1. module(模块标识)

    module 属性是 Common JS 规范中定义的,它是一个对象,表示当前这个具体的 js 模块;

  2. require(引用模块)

    每一个实现了 CommonJS 规范的模块,必须定义一个 require() 函数,使用这个 require 函数,就能够 很方便的导入其它 模块中的成员,供自己使用;

  3. exports(暴露模块成员)

    每一个模块中,如果想要把自己的一些私有成员,暴露给别人使用,那么,必须实现一个 exports 对象,通过exports对象,可以方便的把模块内私有的成员,暴露给外界使用;

module.exports 和 exports 的关系

  1. module.exportsexports 默认引用了同一个空对象;
  2. module.exportsexports 作用一致,都可以向外暴露成员;
  3. 一个模块作用域中,向外暴露私有成员时,永远以 module.exports 为准;

总结:为了防止一些不必要的问题,今后在开发中,推荐大家直接使用 module.epxorts 向外暴露成员

var a = 10
var b = 20

function show() {
    console.log('黎明');
}
module.exports = {
    a,
    b,
    show,
    say() {
        console.log('利玛窦');
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落花流雨

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值