• 三阶段课程——Day05(模块系统:自定义模块、内置模块、第三方模块;npm与包:包管路工具、常用命令、package.json、包的分类、包加载机制、全局包)


    一、模块系统

      1、模块化介绍

      传统开发的问题

      随着项目的扩大,然后代码就越来越庞大 ,如果没有很好的规划,后期维护非常复杂(甚至就维护不了)。

      比如:前端html中有很多特效会依赖文件:a.js、b.js、c.js、....

    <script src="c.js">
    <script src="b.js">
    <script src="a.js">

      

      传统的解决方式

      我们可以用全局命名、也可以用闭包、也可以面向对象封装。这就造成了程序员随心所欲的封装,没有规矩(规范)

      node中提出了模块的概念。把公共的函数(代码)进行模块化封装(那么一定是有要求的【规范】)。

      解决问题(好处)

        1、有规范

        2、代码复用高

        3、易维护

      2、Commonjs规范

      这个commonjs规范是node中特有的,就是约束node中的模块的。

      Commonjs组成:

      (1)如何定义数据和功能函数(即如何定义公共代码)

      (2)外部如何使用定义的数据和功能函数

      定义规范的好处

      既然是规范,那么就应该是大家默认都应该遵守的,这样就降低了沟通的成本,极大方便了各个模块之间的相互调用,利于团队协作开发。

      3、模块的种类

    • 自定义模块:(开发者自己定义的模块,每一个js文件都可以称为一个模块)
      • 开发者,可以使用commonjs规范自己写的js文件,都称为自定义模块
    • 内置模块:(由Node.js官方提供,如:fs、path、querystring等)
    • 第三方模块:(由第三方开源出来的模块,使用前需要npm工具从npm社区下载)
      • 第三方(可以是个人、也可以是一个小团队、也可以是公司)

      4、自定义模块

      介绍:开发者,遵守commonjs规范自己写的js文件,都称为自定义模块

      使用步骤:

      module:模块 exports:出口 require:要求

        1、创建模块文件

        2、在模块文件定义公共数据

        3、把数据暴露出去

          用moudule.exports或exports进行暴露

        4、在某些文件中要使用这引用数据(引入:require)

          自定义模块引入必须以.或..开头,这是commonjs规定

      module.exports暴露

      module/a.js  定义数据

    // 定义数据
    let name = 'zs';
    let sex = '男';
    let age = 3;
    
    let obj = {
        fn() {
            console.log('前端开发');
        }
    }
    
    // 暴露出去
    module.exports = {
        name,
        sex,
        age,
        obj
    }

      1 自定义模块.js使用

    let o = require('./module1/a.js'); // 引入自定义模块,必须以.或..开头
    
    // 使用
    console.log(o);
    console.log(o.name);
    console.log(o.age);
    o.obj.fn();

      原生模拟module.exports和exports的区别

    // 直接给exports赋值,没有切断和module.exports的引用关系
    function fn1() {
        let module = {};
        module.exports = {};
        let exports = module.exports;
    
        exports.a = 10;
        exports.b = 20;
    
        return module.exports;
    }
    
    console.log(fn1()); // {a:10, b:20}
    
    
    // ------------------------------
    // 直接给module.exports赋对象,切断了和exports的引用关系
    function fn2() {
        let module = {};
        module.exports = {};
        let exports = module.exports;
    
        module.exports = { // 用一个新的对象赋值,则切断了和原对象的引用关系
            c: 3,
            d: 4
        }
    
        return module.exports;
    }
    
    console.log(fn2()); // {c:3, d:4}
    
    // ----------------------------------
    // module.exports和exports混用,因为给module.exports赋了对象,因此,exports赋的属性外面就没有
    function fn3() {
        let module = {};
        module.exports = {};
        let exports = module.exports;
    
        module.exports = {
            c: 3
        }
        exports.ab = 55;
    
        return module.exports;
    }
    console.log(fn3()); // {c:3}
    
    // ----------------------------
    // 混用直接改属性,没有赋值,所以它们的引用关系存在
    function fn4() {
        let module = {};
        module.exports = {};
        let exports = module.exports;
    
        module.exports.c = 3;
        exports.ab = 55;
    
        return module.exports;
    }
    console.log(fn4()); // {c:3, ab:55}

      module.exports和exports的区别

      module.exports和exports它们俩指向同一个对象,但是默认返回的是module.exports,所以可以对module.exports直接赋一个对象,但是如果直接给exports赋一个对象,则不可以。

      总结:

    • module.exports可以赋对象,也可以改属性。但是一旦赋了对象,则和exports的引用关系就没有了。加给exports的属性也没有了。
    • exports只能改属性,不能赋对象,因为一赋对象,就切断了和module.exports的引用关系。

      注意:这两个不要混用

      js模块私有化

      node的commonjs规范,把我们js模块都进行私有化了(不会污染全局变量):每一个js中的代码都套了一层匿名函数

    a.js引用b.js文件,其实b就是一个模块,则b文件的外面,套了一个匿名的函数
    通过在b中打印console.log(arguments.callee.toString())可以看出来
    function (exports, require, module, __filename, __dirname) {
    console.log(arguments.callee.toString());
    }

      面试中关于node底层的问题可能会问

      练习

    定义一个名为trim.js文件
    暴露一个Trim方法。
    Trim()  //去除左右空格
    Trim.left()  //去除左空格
    Trim.right()  //去除右空格

    trim.js

    function Trim(str) {
        let re = /^s+|s+$/g;
        return str.replace(re, '');
    }
    
    Trim.left = function (str) {
        let re = /^s+/;
        return str.replace(re, '');
    }
    
    Trim.right = function (str) {
        let re = /s+$/;
        return str.replace(re, '');
    }
    
    
    // 以下三种暴露方式均可
    module.exports.Trim = Trim;
    // exports.Trim = Trim;
    // module.exports = {
    //     Trim
    // }
    let t = require('./trim.js');
    
    // console.log(t);
    
    let str = '    小王吃饭了    ';
    
    console.log('(' + t.Trim(str) + ')');
    console.log('(' + t.Trim.left(str) + ')');
    console.log('(' + t.Trim.right(str) + ')');

      5、内置(核心)模块

      fs / url / querystring / path

      URL(了解)

      简单的说就是网址。这个模块比较特殊,不需要引入(但要new实例化),类似于全局变量的用法 。

    const myurl = 'http://www.ujiuye.com:8080/a/b/c?name=zs&age=3'
    
    let u = new URL( myurl )
    
    console.log( u );
    console.log( u.searchParams.get('name') )
    console.log( u.searchParams.get('age') )
    console.log( u.pathname )

      querystring(了解)

      先引入模块,如果内置模块需要引入,则只写字符串名称即可。

      类似于JSON.parse()和JSON.stringify()

    const qs = require('querystring')
    
    // qs.parse() // 把 "name=swk&age=20" 字符串 转成对象
    // qs.stringify() // 把xxx对象 转成字符串
    
    let d = {
        username:'swk2',
        age:30
    }

      path(掌握)

      parse解析, basename文件名, extname后缀名, join拼接

    const filepath = 'a/b/c/d/a.html';
    
    // 解析路径,包含文件名,后缀名等等
    // let o = path.parse(filepath);
    // console.log(o);
    
    // ---------------------------
    // 文件名和后缀名
    // console.log(path.basename(filepath)); // 完整的文件名
    // console.log(path.extname(filepath)); // 后缀名
    
    // ------------------------------
    
    // 路径拼接
    console.log(__dirname); // D:524day051代码demo4
    let url = path.join(__dirname, './demo/a.txt'); // 路径拼接
    console.log(url); // 'D:524day051代码demo4demoa.txt'

      6、第三方模块

      第三方模块又可以称为包

        包的方法

        假如你的业务中需要处理字符串,字符串需要去除空格(trim)或处理时间(time-stamp)

        其实就是没有必要去造轮子,而很多现成的公共代码已经封装好了(使用commonjs规范)

        1、建立你自己的业务代码

        2、在你业务代码下打开cmd,下载第三方模块(包) npm i 包名

    npm install trim

        下载完成之后,你的项目根目录下会有一个node_modules文件夹和package-lock.json文件

        3、在自己的业务文件中引入第三方模块(包)

    let t = require( 第三方模块名称 )

        4、根据业务写代码

      

    二、npm与包

      1、包的概念

      包:Node.js中的第三方模块又叫做包。就像电脑和计算机指的是同一个事物,第三方模块和包指的是同一个概念,只不过叫法不同。

      npm:主要内容分为两块:(1)包管理工具 (2)npm社区

     

      包来源:

      包是由第三方个人或团队开发出来的,免费供给所有开发者使用。

      npm社区:https://www.npmjs.com/

     

      包的特点:

    • 在Node.js中,如果只用内置模块或开发者自己定义模块开发,效率会很低。所以就有了第三方包

    • 包是基于内置模块( 按照commonjs规范 ) 封装出来的,提供了更高级、更方便的API,极大的提高了开发效率

    • 包和模块之间的关系,类似于Jquery和原生js之间的关系

    • 要想称为一个包除了遵守模块化规范以外,还要遵守包的一些规范,如:说明文档,协议说明等。

      2、包管理工具npm

      包管理工具指的是安装node环境后,自动安装了npm工具。全称(Node Package Manager),简称 npm 包管理工具。

      查看安装的版本 npm -v

     

      第一次安装包的说明

    • 初次装包完成后,在项目文件夹下多一个node_modules的文件夹和package-lock.json的配置文件

    • node_modules文件夹用来存放所有已安装到项目中的第三方包require()导入第三方包时,就是从这个目录中查找并加载

    • package-lock.json配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等

    • 开发者不要手动修改node_modules或package-lock.json文件中的任何代码,npm包管理工具会自动维护它们

      3、常用命令

      init:项目初始化

    npm init 
    npm init [-y]

      install/i:安装包

    npm install/i 包名
        默认下载最新的版本
        
    npm install/i 包名@版本号
        下载特定的版本包
        npm install trim@1.0.0
        注意:只能保留一个版本
        
    npm install/i 包名 -D/--save-dev    开发依赖
    npm install/i 包名 -S/--save          项目依赖
    
    npm install 包名1 包名2 ...  一次安装多个包

      uninstall/r:卸载包

    npm uninstall 包名
    npm r 包名

      4、查看手册

      以trim为例:https://www.npmjs.com/package/trim

      5、package.json

      工作中给别人项目时,是不给node_modules的,应该使用git,通过.gitignore把node_modules给忽略掉

      需要包含name,version,main等信息,如下表

    属性名说明
    name 包(项目)的名称
    version 包(项目)的版本号
    description 包(项目)的描述
    main 包(项目)入口文件
    scripts(到项目中可以讲到) 定义快捷脚本命令
    keywords 项目关键词
    author 作者
    license 协议
    dependencies 包(项目)依赖的模块
    devDependencies( webpack再说 ) 包(项目)开发依赖的模块

      

      重要属性

      dependencies   项目依赖

    会自动的记录到dependencies属性中
    npm i 包名
    npm i 包名 -S
    npm i 包名 --save

      devDenpendencies  开发依赖

    会自动的记录到devDependencies属性中
    npm i 包名 -D
    npm i 包名 --save-dev

      使用命令创建package.json文件

    npm init [-y] : 默认配置直接生成package.json文件。        
    注意事项:文件夹不要有中文,不要使用第三方模块名称和内置模块的名称定义项目文件夹名

    npm init : 一问一答的形式(不推荐)

      6、包的分类

      项目包

      被安装到项目的node_modules目录中的包,都是项目包

      项目包又分为两类

      1)开发依赖包:被记录到devDenpendencies节点中的包,只在开发期间会用到(只是在写代码的时候用)

      2)核心依赖包:被记录到dependencies节点中的包,在开发期间和项目上线之后都会用到。

      总结:我们的包会很多很多,只记录你自己下载的包即可(但是也不用刻意记,因为常用的就那么几个,而且package.json帮我们记录了)

      全局包

      markdown工具包

      npm地址:https://www.npmjs.com/package/markdown

    npm i markdown -g

      命令行:

    md2html 笔记06.md > abc.html

      7、包加载机制(总结)

      内置模块的加载机制

      内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高。例如,require('fs') 始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs,也会引入核心的内置fs模块。

      所以第三方模块(npm上搜索不到内置模块)和自定义模块不要起官网已有的模块名称。

      官方给的建议:自己定义模块的时候,不要起官方的内置模块 。

      注意:在引入内置模块的时候不要 加 .和..( fs、path、querystring、url )

    // let fs = require('fs'); // 引入内置的
    // console.log(fs);
    
    let fs = require('./node_modules/fs/index'); // 引入自己的,不要这样做(自定义模块不要放在node_modules中)
    console.log(fs);

      自定义模块加载机制

      1)使用require()加载自定义模块时,必须指定以./或../开头的路径标识符。

      2)如果没有指定./或../这样的路径标识符,则Node.js会把它当作内置模块第三方模块进行加载。

      3)自定义模块不要放在node_modules中。

      4)在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

        (1)文件名.js扩展名进行加载

        (2)文件名.json扩展名进行加载

          json文件中必须是以 [] 或 {} 包起来的数据,键必须是双引号、如果你对应的值是字符串则必须带双引号

        (3)加载失败,终端报错Error:Cannot find module 'xxx'

    //1. 完整的写法
    // let t = require('./module/t1.js')//
    //2. 后缀名可以省略,按照 .js 和 .json的顺序进行加载。
        //如果没有则报错  xxx not find
        // let t = require('./module/t1')
    
        //let t = require('./module/t2')
        //let t = require('./module/t2') 
        let t = require('./module/t3')  // cantnot find 't3.js'
    
    console.log(t ); 

      第三方模块加载机制

      1)如果require()的模块标识符不是内置模块,即没有以‘./’或‘../’开头,则Node.js会从当前模块的父目录开始,尝试从当前文件夹的/node_modules文件夹中加载第三方模块。

      2)如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到当前项目文件的盘符根目录。

      总结:一定是把第三方模块下载到 根项目下的 node_modules ,且项目中只有根项目下有 node_modules

      package.json中的main属性

      main属性,可以指定模块加载的主入口文件,当引入模块时用文件夹名结尾时生效。有三种加载方式:

      1)根据目录下的package.json的文件,寻找main属性指定的文件名,作为require()加载的入口。

      2)如果目录里没有package.json文件,或者main入口不存在,则Node.js将会加载目录下的index.js文件(所以目录中的默认入口为index.js)。

      3)如果以上两步都加载失败,则Node.js会在终端打印错误消息,报告模块缺失:Error:Cannot find module 'xxx'

     

      8、全局包

      项目包:之前学所的是项目包,是在我们js文件中需要 require 引入的。

      全局包:又称 “工具”,不是写代码。

      需求:把.md文件 转换成 html。

      比如:markdown的全局的使用规则 。

      1、下载 g == global

      只需要下载一次,无论哪个目录都可以执行此命令

    npm i 包名 -g
    npm i markdown -g  
    虽然包名叫markdown,但是实际下载下来的文件名叫 md2html /  md to html

      2、下载到此目录

      C:Users你的用户名AppDataRoaming pm

      3、而是当作命令来使用的

    md2html 笔记06.md > 06.html
  • 相关阅读:
    C语言 数组排序 – 冒泡法排序
    类加载过程
    布隆过滤器的原理及应用
    回答两个被频繁问到的代码写法问题
    关于分布式事务的理解
    根据使用者反馈,对开源项目 go-gin-api 新增两个功能
    图书速度 | 《如何高效学习》
    报错kernel:NMI watchdog: BUG: soft lockup
    容器Jenkins中运行docker
    容器Jenkins中运行docker
  • 原文地址:https://www.cnblogs.com/miaochaofan/p/14850245.html
Copyright © 2020-2023  润新知