关于commonjs:CommonJS的两种导出方式

晓得的敌人应该晓得,CommonJS有两种导出形式,如下所示: // module1.jsconst name1 = 'Ben';const name2 = 'Lisa'// 导出形式1module.exports = { name1, name2,};// 导出形式2exports.name1 = name1;exports.name2 = name2;差别那么下面这两种导出形式有什么差别呢?先说论断,从导出的后果来看,没有差别。 import module1 from './module1.js'// 无论module1.js中应用的哪种形式导出,这里失去的module1都是一样的构造。console.log(module1)// { name1, name2 }为什么没有差别至于为什么两者没有差别,起因也很简略。module.exports和exports是相等的,因为exports就是援用自module.exports。 const name = 'Ben';// 这两个写法是等价的exports.name = namemodule.exports = { name }看下图,给exports增加一个name属性,也就是相当于给module.exports增加了一个属性。 留神防止意外切断援用关系不要应用上面的导出形式,还记得exports是援用自module.exports吗,上面这种写法,相当于切断了exports和module.exports的援用关系,所以也就无奈导出了。 const name = 'Ben';exports = { name}防止同时应用两种形式const name1 = 'Ben';const name2 = 'Lisa'// 导出形式1module.exports = { name1,};// 导出形式2exports.name2 = name2;下面同时存在两种导出形式的状况,会导致导出形式2会被疏忽。 还记得他们的援用关系吗?导出形式1将援用关系切断了,所以就会导致导出形式2失败。

February 20, 2024 · 1 min · jiezi

关于commonjs:CommonJS模块分类及加载流程及模块加载模拟实现

模块分类内置模块:Node源码编译时写入到二进制文件中文件模块:代码运行时,动静加载加载流程路径分析:根据标识符确定模块地位(门路标识符、非门路标识符)文件定位:确定指标模块中具体的文件及文件类型(存在'm1'模块,导入时应用require('m1')语法,应用m1.js->m1.json->m1.node的程序,如果都没找到,会被当做一个目录,查找package.json文件,应用JOSN.parse()解析。接下来会找main.js->main.json->main.node。将index作为指标模块中的具体文件名称)编译执行:采纳对应的形式实现文件的编译执行(将某个具体类型的文件依照相应的形式进行编译和执行,创立新对象,按门路载入,实现编译执行,返回可用exports对象)缓存优先准则 进步模块加载速度以后模块不存在,则经验一次残缺加载流程模块加载实现后,应用门路作为索引进行缓存模块加载模仿实现外围逻辑 路径分析缓存优化文件定位编译执行依据下面的加载流程特色模仿实现 const fs = require("fs");const path = require("path");const vm = require("vm");function Module(id) { this.id = id; this.exports = {};}// 静态方法Module._resolveFilename = function (filename) { // 利用path将filename转为绝对路径 let absPath = path.resolve(__dirname, filename); console.log(absPath); // 判断以后门路对应的内容是否存在 if (fs.existsSync(absPath)) { // 条件成立阐明absPath对应的内容是存在的 return absPath; } else { // 依据文件定位的程序顺次去找 let suffix = Object.keys(Module.__extensions); for (var i = 0; i < suffix.length; i++) { // 拼接 let newPath = absPath + suffix[i]; if (fs.existsSync(newPath)) { return newPath; } } } throw new Error(`${filename} is not exists`);};Module.__extensions = { ".js"(module) { // 读取 let content = fs.readFileSync(module.id, "utf-8"); // 包装 content = Module.wrapper[0] + content + Module.wrapper[1]; // 应用vm执行 let compileFn = vm.runInThisContext(content); // 转换成一个可执行匿名函数 // 筹备参数的值 let exports = module.exports; let dirname = path.dirname(module.id); let filename = module.id; // 调用 compileFn.call(exports, exports, myRequire, module, filename, dirname); }, ".json"(module) { // 读取而后格式化 let content = JSON.parse(fs.readFileSync(module.id, "utf-8")); module.exports = content; },};Module.wrapper = [ "(function(exports,require,module,__filename,__dirname){", "})",];Module._cache = {};Module.prototype.load = function () { let extname = path.extname(this.id); console.log("extname", extname); Module.__extensions[extname](this);};function myRequire(filename) { // 1,获取绝对路径 let modulePath = Module._resolveFilename(filename); // 2,实现缓存优先 let cacheModule = Module._cache[modulePath]; if (cacheModule) { return cacheModule.exports; } // 3创立空对象加载指标模块 let module = new Module(modulePath); // 4,缓存曾经加载的模块 Module._cache[modulePath] = module; // 5,执行加载(编译执行) module.load(); // 6 ,返回数据 return module.exports;}let obj = myRequire("./m");console.log(obj);

May 1, 2022 · 1 min · jiezi

关于commonjs:分析如何将excel表格电话号码导入手机通讯录

当你的手里有一个excel表格,外面有铭单,几百个、几千个、甚至几万个,你想把他们疾速批量存入手机通讯录,不论你是安卓手机还是苹果手机,跟着上面的操作方法,这里通过借助于网络上常见的软件,金芝号码提取导入助手,来做演示如何一键批量疾速导入号码,我这里仅仅几步和几分钟即可搞定。 (1)把你的excel表格在电脑上关上,就像我下图那样,铭字和号码筹备好,没有铭字也没有关系,照样能够导入,间接多复制一遍就能够。申明:我下图的资料是虚构的,并非实在,仅作为解说导入过程应用。 (2)把铭字和号码,别离复制好,各自粘贴到软件,金芝号码提取导入助手,第一个框和第二个框,放好了当前,点下方的“转换通信禄”,即可进去一个文件,把它保留到电脑桌面,同时起个好记的文件名比方abc,这样待会好认好找。 (3)把上一步转换好的放在电脑桌面的文件abc,发送给你的手机,能够借助电脑某信或者电脑球球,发送给你的手机某信或者手机球球,这是两种常见的传送文件的形式,个别的人都懂。 (4)在你的手机上,点开方才接管到的文件,抉择“其余利用形式关上”,顺着提醒一步步操作即可实现。安卓手机,选“分割仁”或者“电括本”或者“拨号与分割仁”等常见的选项,确定导入。苹果手机,下方有个“通信禄”,就选它,存储,导入确定。 下面就是通过借助网络上常见的便捷软件,金芝号码提取导入助手,来做残缺具体的四步解说,操作的过程其实很快,花不了几分钟就能够将excel表格电话号码导入手机通讯录,导入的过程也是高度自动化的,很容易就能搞定的。

March 5, 2022 · 1 min · jiezi

import和require的区别

关于 import 和 require 的不同,其实可以理解成 CommonJs 和 ES Module 的区别。这两者都是前端模块化的规范。 我们在 node 里使用的是 CommonJs,在前端页面的时候,用的是 ES Module,这两者的区别,还是很容易混淆的,所以整理一下 CommonJs 和 ES Moudule 的相关知识点,把这里好好的整理一下。 一、CommonJs 1.1 概述 Nodejs 是 CommonJS 规范的主要实践者,在 CommonJs 里每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。CommonJs 提供了四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。CommonJS 规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。 var x = 5;var addX = function (value) { return value + x;};module.exports.x = x;module.exports.addX = addX;而使用 require 方法来引入并加载模块 var example = require('./example.js');console.log(example.x); // 5console.log(example.addX(1)); // 61.2 CommonJS模块的特点 ...

July 3, 2020 · 2 min · jiezi

前端面试每日-31-第183天

今天的知识点 (2019.10.16) —— 第183天[html] HTML5的output是非常棒的一个标签,你对它有了解吗?[css] 怎样去除图片自带的边距?[js] 在js中函数返回多个值有哪些方法?[软技能] 说说你对AMD、CMD和CommonJS的理解《论语》,曾子曰:“吾日三省吾身”(我每天多次反省自己)。 前端面试每日3+1题,以面试题来驱动学习,每天进步一点! 让努力成为一种习惯,让奋斗成为一种享受!相信 坚持 的力量!!!欢迎在 Issues 和朋友们一同讨论学习! 项目地址:前端面试每日3+1 【推荐】欢迎跟 jsliang 一起折腾前端,系统整理前端知识,目前正在折腾 LeetCode,打算打通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个Star, 同时欢迎微信扫码关注 前端剑解 公众号,并加入 “前端学习每日3+1” 微信群相互交流(点击公众号的菜单:进群交流)。 学习不打烊,充电加油只为遇到更好的自己,365天无节假日,每天早上5点纯手工发布面试题(死磕自己,愉悦大家)。希望大家在这浮夸的前端圈里,保持冷静,坚持每天花20分钟来学习与思考。在这千变万化,类库层出不穷的前端,建议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢迎大家到Issues交流,鼓励PR,感谢Star,大家有啥好的建议可以加我微信一起交流讨论!希望大家每日去学习与思考,这才达到来这里的目的!!!(不要为了谁而来,要为自己而来!)交流讨论欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个[Star] https://github.com/haizlin/fe...

October 16, 2019 · 1 min · jiezi

前端模块化的前世

随着前端项目的越来越庞大,组件化的前端框架,前端路由等技术的发展,模块化已经成为现代前端工程师的一项必备技能。无论是什么语言一旦发展到一定地步,其工程化能力和可维护性势必得到相应的发展。 模块化这件事,无论在哪个编程领域都是相当常见的事情,模块化存在的意义就是为了增加可复用性,以尽可能少的代码是实现个性化的需求。同为前端三剑客之一的 CSS 早在 2.1 的版本就提出了 @import 来实现模块化,但是 JavaScript 直到 ES6 才出现官方的模块化方案: ES Module (import、export)。尽管早期 JavaScript 语言规范上不支持模块化,但这并没有阻止 JavaScript 的发展,官方没有模块化标准开发者们就开始自己创建规范,自己实现规范。 CommonJS 的出现十年前的前端没有像现在这么火热,模块化也只是使用闭包简单的实现一个命名空间。2009 年对 JavaScript 无疑是重要的一年,新的 JavaScript 引擎 (v8) ,并且有成熟的库 (jQuery、YUI、Dojo),ES5 也在提案中,然而 JavaScript 依然只能出现在浏览器当中。早在2007年,AppJet 就提供了一项服务,创建和托管服务端的 JavaScript 应用。后来 Aptana 也提供了一个能够在服务端运行 Javascript 的环境,叫做 Jaxer。网上还能搜到关于 AppJet、Jaxer 的博客,甚至 Jaxer 项目还在github上。 但是这些东西都没有发展起来,Javascript 并不能替代传统的服务端脚本语言 (PHP、Python、Ruby) 。尽管它有很多的缺点,但是不妨碍有很多人使用它。后来就有人开始思考 JavaScript 要在服务端运行还需要些什么?于是在 2009 年 1 月,Mozilla 的工程师 Kevin Dangoor 发起了 CommonJS 的提案,呼吁 JavaScript 爱好者联合起来,编写 JavaScript 运行在服务端的相关规范,一周之后,就有了 224 个参与者。 ...

October 9, 2019 · 5 min · jiezi

commonjs-ES-module-babel转码-webpack转码

js模块发展历程-javaScript模块七日谈前端模块化开发那点历史 #588现代ES模块也需要各种转码工具才可以在浏览器里正常运行,下面是转码现代ES模块需要了解到的知识点 commonjs & ES module & babel转码 & webpack转码 CommonJS简述CommonJS 模块输出的是一个值的 拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值了如果输出的是对象,改变其属性的话,外部引用的地方是会发生变化的如果直接改变输出的引用,那外界引用的地方是不会变化的(取缓存里面的结果)CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成commonjs 一个模块就是一个文件,require 命令第一次执行加载该脚本就会执行整个脚本,然后在内存中生成一个对象{ id: '...', // 模块名 exports: {}, // 真实的模块 loaded: true // 是否加载完毕}以后再次 require 该模块时,就会去缓存里取该对象的 exports 的属性无论 require 多少次,模块都只会运行一次,后续加载都是从缓存里面取module.exports 与 exports 的关系commonjs 规范仅仅定义了 exportsmodule.exports 是 nodejs 对 commonjs 规范的实现我们把这种实现称为 commonjs2https://github.com/webpack/webpack/issues/1114#issuecomment-105509929exports 只是在初始化对 module.exports 的引用初始化指向同一片内存空间模块导出的是 module.exports 如果对 module.exports 重新赋值,exports 上,挂的方法/属性将会失效require 引入的是 module.exports 导出的东西为避免混乱/错误,一般导出模块只建议用 module.exports 一般第三方包都用这种方式导出 modules.exports = exports = {}循环引用问题 (某个模块出现循环加载,就只输出已经执行的部分,还未执行的部分不会输出)// 代码如下// a.jsexports.A = '我是a模块';var b = require('./b.js');console.log('在 a.js 之中, 输出的 b模块==> ', b.B);exports.A = '我是后期修改过的a模块';console.log('a.js 执行完毕');// b.jsexports.B = '我是b模块';var a = require('./a.js');console.log('在 b.js 之中,输出a模块 ==>', a.A);exports.B = '我是修改后的b模块';console.log('b.js 执行完毕');// main.jsvar a = require('./a.js');var b = require('./b.js');console.log('在 main.js 之中,输出的 a模块=%j, b模块=%j', a.A, b.B);// 输出结果如下:➜ webpack-plugin git:(master) ✗ node src/babel/index 在 b.js 之中,输出a模块 ==> 我是a模块b.js 执行完毕在 a.js 之中, 输出的 b模块==> 我是修改后的b模块a.js 执行完毕在 main.js 之中,输出的 a模块="我是后期修改过的a模块", b模块="我是修改后的b模块"// 执行过程如下:执行 a.js 遇到 require b.js,暂停 a.js 执行,去执行 b.jsb.js 执行到第二行,遇到 require a.js ,从缓存中拿出刚刚 a.js 导出的模块,在 b.js 里面使用继续执行 b.js 后面的代码待 b.js 执行完毕后,控制权交还 a.js,继续执行拿到 b.js 导出的模块,在 a.js 继续使用 ... 直到结束 循环引用注意点:由于 commonjs 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是全部代码之后的值,两者可能会有差异,所以输入变量的时候必须非常小心,使用 var a = require('a') 而不是 var a = require('a').fooES6 Module基本使用export default A // 用户不需要知道导出模块的变量名import a from 'a.js'// 可以导出多个export var a = 1 // 这种方式可以直接导出一个表达式或var a = 1export {a} // 必须用花括号包起来import {a} from 'a.js'// as 关键字重命名模块export { a as A }// 导入导出合并export { default as Comps } from '../xxx'相当于import Comps from './xx'export { Comps }// 执行 loadsh 模块,但并不输出任何值import 'lodash';// 整体加载所有模块,访问时用 circle.xxx 访问import * as circle from './circle';简述: ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入,它的接口只是一种静态定义,在代码静态解析阶段就会生成。// ES6模块import { stat, exists, readFile } from 'fs';上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。由于 ES6 模块是编译时加载,使得静态分析成为可能import命令具有提升效果,会提升到整个模块的头部,首先执行import命令是编译阶段执行的,在代码运行之前。由于 import 是静态执行,所以不能使用表达式和变量(这类只有在运行时才能得到结果的语法结构)静态加载模块的好处:1. 不再需要UMD模块2. 浏览器API可以用模块格式提供,不必再做成全局变量,不再需要全局对象如:Math (可以像Python一样用模块导入)动态 import动态import() 是非常有用的。而静态型的 import 是初始化加载依赖项的最优选择,使用静态 import 更容易从代码静态分析工具和 tree shaking 中受益import(模块路径) 返回 promise,从 then 的结果里拿到加载的模块webpack 2.x 之后,有一个魔力注释的功能,会把加载的模块重命名为你注释里的文字ES6模块的浏览器加载传统方法加载js脚本script type="application/javascript"异步加载: async defer脚本异步加载,不会阻塞dom结构的解析async:加载完立即执行,渲染引擎中断,待之脚本执行完继续渲染defer:加载完会等待页面渲染完毕及页面其他脚本执行完毕才会执行多个 async 执行没有顺序保证,多个 defer 有顺序保证 es6 模块加载script type="module"浏览器对 type="module" 的处理和 defer 标志一致es6 模块的循环加载ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。es6 模块会在使用使用时才去加载对应的模块如果是循环应用,可以将对应的输出改写成函数形式,利用函数的变量提升功能CommonJS 与 ES Module 的对比// 此处是对比CommonJS 模块时运行时加载 -- 值得拷贝ES6模块时 编译时 输出接口 -- 值得引用commonjs 模块只会加载一次,以后在 碰到 require 同样的东西就从缓存里面加载如果把原模块导出的东西改变,引入模块不会跟着改变,还是从缓存里面取原来的值ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。commonjs: module.exports = {} exports 运行阶段才加载模块,可以使用逻辑语句 模块就是对象加载的就是该对象 加载的是整个模块即将所有的接口都加载进来 输出的是值得拷贝,原模块发生变化不会影响已经加载的 this 指向当前的模块es6 模块 export 可以输出多个 {} export default 解析阶段确定对外的接口,解析阶段输出接口,不可以使用逻辑语句 加载的模块不是对象 可以单独加载其中的几个模块 静态分析,动态引用输出的是值得引用,原模块变化会影响已加载的模块 this 指向 underfinedBabel 转换 ES6 的模块化语法Babel 对 ES6 模块转码就是转换成 CommonJS 规范模块输出语法转换Babel 对于模块输出的转换,就是把所有输出都赋值到 exports 对象的属性上,并加上 ESModule: true 的标识表示这个模块是由 ESModule 转换来的 CommonJS 输出对于解构赋值输入import {a} from './a.js'转义为var _c = require('./a.js')然后取 _c.a 对于 defaultimport a from './a'import {default as a} from './a'babel转义时的处理,引入了一个 函数function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {'default': obj}}var _a = _interopRequireDefault(require("./a.js"));console.log(_a["default"]);// 意思就是如果不是 esmodule 就为其手动添加个 default 属性,取值时统一取 default有个疑问:babel 为什么 会把 export export.default 导出的模块转换为 exports.xxx 和 exports.default 呢?而不是 module.exports ???我没有找到解释,如果您知道,麻烦给我留言下webpack 对 es6 模块和commonjs 的处理webpack本身维护了一套模块系统,这套系统兼容所有历史进程下的前端规范写一个简单的webpack配置module.exports = { entry: "./index.js", output: { path: path.resolve(__dirname, "dist"), filename: "[name].[contenthash:8].js" }, mode: "development"};执行打包命令 webpack --config webpack.config.js --env=dev 输出 main.[hash].js// 打包后代码简化如下// 首先是一个 webpack 模块运行时代码(function(modules) { // webpackBootstrap // 缓存模块 var installedModules = {}; // 函数 __webpack_require__ 参数 模块 id,用于加载和缓存模块 function __webpack_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded module.l = true; // Return the exports of the module return module.exports; } /*** 所有加载的模块都存在于 installedModules 内,其结构为: id: { id, loaded: Boolean // 是否加载过 exports // 模块的导出 } */ // 省略... 定义各种工具函数和变量 // Load entry module and return exports // 加载 entry 模块,并返回其导出,我们写的模块才会被真正执行 return __webpack_require__(__webpack_require__.s = "./index.js");})({ "./index.js": (function(module, __webpack_exports__, __webpack_require__) { // ... }, "./src/a.js": (function(module, __webpack_exports__, __webpack_require__) { // ... }, // ...})这个自调用的函数的参数 modules,就是包含所有待加载模块的一个对象{ [id: string]: Function}异步加载: import ==> webpack.requireEnsure ==> webpackJsonphttps://www.njleonzhang.com/2018/12/30/webpack-bundle-1.html其他常见问题1. babel 与 webpack 的关系webpack:将 ES6、CommonJS、amd、cmd 等模块化通过自己内部的机制统一成 webpack 的模块化。babel:转码 es6 语法,配合一些列 babel 工具链可以将新的 es2015+ 代码转换成所有浏览器支持的 es代码,babel 也可以将 es6 模块转换成 commonjs 模块2. es module 与 commonjs 为何可以混用因为 babel 会把 es module 转换成 commonjs 规范的代码babel 转码 es module 时如果遇到 export default 这种导出模块的书写方式后,会将其转换成 exports.default ,这时如果用 require 引入时,需要对其加上 .default 如 require('./a.js').a 这样才能获取 a 模块 export default 导出的 aimport 动态加载的模块也需要 .default 才能获取真实模块导出的值,如 import('./a.js').then(res => res.dafault)3. antd、element-ui 等ui框架的按需加载组件的实现需要 babel-plugin-component ...

August 18, 2019 · 4 min · jiezi

译JS-模块化历史简介

对于 JavaScript 来说,模块化是一个相对现代的概念,这篇文章会带你在 JavaScript 的世界里快速浏览模块化的历史进程~ Script 标签和闭包在早些年间,JavaScript 就是直接写在 HTML 的 <script> 标签里的,最多也就是放在独立的文件里面,而它们也都共享一个全局作用域。 任何 JS 文件里面声明的变量都会被附加在全局的 window 对象上,并且还有可能意外覆盖掉第三方库中的变量。 随着 web 应用越来越复杂,共享全局作用域这种方式的弊端开始显现,于是 IIFE(立即调用函数表达式)就被发明了出来,并且广为使用。IIFE 就是将一整段代码包裹在一个函数中,然后立即执行这个函数。在 JavaScript 中,每个函数都有一个作用域,所以在函数中声明的变量就只在这个函数中可见。即使有变量提升,变量也不会污染到全局作用域中。 下面让我们看几个 IIFE 的写法,每个 IIFE 的作用域都是独立的,其中第一种写法比较常见: (function() { console.log('IIFE using parenthesis')})()~function() { console.log('IIFE using a bitwise operator')}()void function() { console.log('IIFE using the void operator')}()使用 IIFE 这种方式,某个库如果想要暴露全局变量,可以在 window 上绑定一个对象作为命名空间,这样就避免了污染全局作用域。看下面的代码,假如我们要建立一个 mathlib 工具,它有一个 sum 方法。假如这个工具有多个模块,也可以建立多个文件,每个文件里都是一个 IIFE,然后向 window.mathlib 对象中添加方法就可以了: (function() { window.mathlib = window.mathlib || {} window.mathlib.sum = sum function sum(...values) { return values.reduce((a, b) => a + b, 0) }})()mathlib.sum(1, 2, 3)// <- 6IIFE 这种方式可以说是模块化的先河,它让开发者可以将模块放在单独的文件中,并且不污染全局作用域。 ...

June 28, 2019 · 2 min · jiezi

关于js-module系统的一些笔记

起因最近写完一个基于sao的模板再把之前写的一些代码弄到github上,在项目中使用时遇到需要兼容IE的情况才发现webpack在加载文件时存在一定策略,下面来简单说一下。 例子先看一下这个例子的文件和pkg的设置 dist/ vpin.esm.js #ES2015格式 vpin.min.js #UMD格式 vpin.js #Commonjs格式package.js文件设置如下 { "main": "vpin.js", "jsnext:main": "vpin.esm.js", "module": "vpin.esm.js", "browser": "vpin.min.js"}在项目中用webpack加载时这个例子时,会优先加载browser设置的文件,然后才是module或者jsnext:main,最后才是main。 不知道为何用rollup生成的UMD在webpack引入后无法命中exports关键字,导致引入空内容。由于上面问题导致我困惑很久,后来根据webpack加载策略,把browser设置去掉后加载esm方式的文件就把问题解决了。 如果你也有把项目发布至npm,那么请留意一下package.json相关设置,能避免不必要的麻烦。 参考资料JavaScript Module Systems Showdown: CommonJS vs AMD vs ES2015 分析三种JS Module的设计因由Webpack 4 不完全迁移指北 提及webpack模块类型相关内容webpack 4 compatibility issue

April 24, 2019 · 1 min · jiezi

【Node】CommonJS 包规范与 NPM 包管理

NPM 实践了 CommonJS 包规范规范,帮助我们安装和管理依赖包,使得 Node 项目的第三方模块更加规范便捷,可以在 NPM 平台上找到所有共享的插件。一、CommonJS 包规范CommonJS 包规范的定义分为两部分:用于组织文件目录的包结构和用于描述包信息的包描述文件 package.json。1.1 包结构一个包由相当于一个存档文件,可压缩为.zip 或tar.gz,安装时解压还原。完全符合 CommonJS 包规范的目录包含以下文件:|– .bin // 存放可执行的二进制文件|– lib // 存放 Javascript 文件|– doc // 存放文档|– test // 存放单元测试用例|– package.json // 包描述文件1.2 包描述文件包描述文件 package.json 是一个 JSON 文件,在包的根目录下。NPM 的所有行为都与 package.json 中的字段有关,Node 程序的依赖项也体现在这些字段上。CommonJS 包规范定义了 package.json 中的字段,NPM 实现时对 CommonJS 包规范中的字段也进行取舍和新增,常用字段有:name: 包名称;description: 包描述;keyword: 关键字数组,用于 NPM 分类搜索;repository: 代码托管位置列表;homepage: 当前包的网址;bugs: 反馈 bug 的邮箱或网址;dependencies: 使用当前包所需要的依赖包列表,NPM 根据这个属性自动加载依赖;devDependencies: 后续开发时需要安装的依赖包列表;main: 模块入口,使用require()引入时优先检查该字段。如果 main 字段不存在,Node 按照模块文件定位的规则依次查找包目录下的 index.js、index.node、index.json;scripts: 包管理器用来安装、编译、测试包的命令对象。bin: 配置包的 bin 字段后,可以通过npm install package_name -g将包添加到执行路径中,之后可以“全局使用”。二、npm 管理NPM 帮助 Node 完成第三方模块的发布、安装和依赖。可以直接执行$ npm 查看所有命令。使用$ npm init 可以快速生成一个 package.json 文件。2.1 npm install 原理使用 npm install安装依赖包是 NPM 最常用的功能,例如执行npm install express后,npm 向 registry 查询模块压缩包的网址,下载压缩包后 NPM 会在当前的 node_module 目录下创建 express 目录,将包解压还原在此。registry 是 NPM 模块仓库提供了一个查询服务,例如 npmjs.org 的查询服务网址 https://registry.npmjs.org/ ,加模块名 https://registry.npmjs.org/vue 就得到包含 Vue 模块所有版本的信息 JSON 对象,也可以使用$ npm view vue查询。Node 项目使用require(’express’)引入 express 模块时,require()方法在路径分析时按照模块路径查找策略,沿当前路径向上逐级查找node_module目录,最终定位到 express 目录。包的安装和模块引入是相辅相成的, 可以进一步理解 Node 模块加载原理2.2 npm install 使用npm install默认将包和 package.json 的依赖关系保存在dependencies,但在可以通过一些额外的标志来控制它们的保存位置和方式:-P or –save or –save-prod: 依赖在dependencies,默认值.-D or –save-dev: 依赖在 devDependencies.-O or –save-optional: 依赖在 optionalDependencies.–no-save: 防止包依赖保存在 dependencies.例如 npm install express -D 就会将 express 依赖关系保存在devDependencies。在npm install一个模块时经常纠结要安装在devDependencies还是dependencies,从字面意思看前者用于生产环境,后者用于开发环境。在官方的定义中,如果环境变量 NODE_ENV 设置为 production,执行 npm install –production 时 npm 会默认安装dependencies里面的依赖项,不会去安装devDependencies里的。并且推荐dependencies里配置正式运行时必须依赖的插件,devDependencies通常用来放我们开发或测试的工具,比如 Webpack,Gulp,babel,eslint等。在实际开发过程中,Node 包的安装是依据 require/import 模块机制,无论是-P还是-D指令都会把依赖下载到 node_modules 文件夹,-P还是-D只是修改了dependencies对象,在安装这个库进行开发调试的时候,可以通过npm install一键安装这两个目录下所有的依赖。2.3 全局安装使用 -g或 –global可以将包安装为“全局可用”,但需要注意的是,全局安装并不意味将模块包安装为一个全局包,也不是可以在任何地方都可以require()引入。实际上-g命令是将模块包安装在“全局”的node_module中,即 Node 可执行文件相同的路径下,并通过配置 bin 字段链接。例如使用命令行查看 Node 可执行文件的位置:$ which node/usr/local/bin/node那么全局安装模块的实际位置就是/usr/local/lib/node_modules(在 Finder 中用 command+shift+G 快捷键访问隐藏目录 )进一步了解 NPM 的使用可以看 NPM DOCS,NPM更多命令 NPM CLI继续加油哦永远十八岁的少女~ ...

April 16, 2019 · 1 min · jiezi

js导入导出总结与实践

在上一篇文章中JavaScript中AMD和ES6模块的导入导出对比,偏向于理论层面,还有一些同学在微信群里或是私下里针对一些问题进行了沟通,所以有了这一篇文章,对js的导入导出进行总结和实践当直接给 module.exports时,exports会失效这个问题其实已经和导入导出没什么关系了,我们看一个知乎上的问题(详细地址阅读原文可以查看)我们以此为突破点js 数组赋值问题 :值传递还是引用?var a = [1,2,3];var b = a;a = [4,5,6];console.log(b); //=>[1,2,3]继续看var a = [1,2,3];var b = a;a.pop();console.log(b); //=>[1,2]为什么会出现这种情况?数组和对象的赋值操作都是引用传递看下这个(留意注释部分)var a = [1,2,3];// a指向了数组 [1,2,3];var b = a;//b 指向 a 所指向的数组[1,2,3];a = [4,5,6];//a 指向了新的数组 [4,5,6],(a的指向发生了变化,改变的是a引用本身,没有改变数组对象,所以b没有变)console.log(b); //b没有改变,还是指向数组 [1,2,3];再看下这个(留意注释部分)var a = [1,2,3];// a指向了数组 [1,2,3];var b = a;//b 指向 a 所指向的数组[1,2,3];a.pop();// a 指向的数组实例发生了 pop 操作console.log(b); //=>a和b都是指向同一个数组,a变量,所以b也变量,最后输出=>[1,2]看一张图片,很形象的描述数组如此,对象也是大同小异看一个群友@ZSing提供的demovar test = { “name”: “zhangshuo”}var demo = test;demo.name = “want you”//你认为test是什么?console.log(test)//=>{ name: ‘want you’ }下面通过注释解释一下(如出一辙)var test = { “name”: “zhangshuo”}//test指向了一个对象 { “name”: “zhangshuo”}var demo = test;//demo 指向 test 所指向的对象 { “name”: “zhangshuo”}demo.name = “want you”//对象的属性发生了改变 { “name”: “want you”}//你认为test是什么?console.log(test)//=>{ name: ‘want you’ }test和demo指向了同一个对象,一个变了,就都变了同样的,我们对上面的demo做一下改造var test = { “name”: “zhangshuo”}var demo = test; test={ “name”: “更改了这个name” }demo.name = “want you”//你认为test是什么?console.log(test)//=>{ name: ‘更改了这个name’ }还需要对此进行赘述吗?还是通过注释对此进行解释说明var test = { “name”: “zhangshuo”}//test指向了一个对象 { “name”: “zhangshuo”}var demo = test;//demo 指向 test 所指向的对象 { “name”: “zhangshuo”} test={ “name”: “更改了这个name” }//test的指向发生了变化,指向了一个新对象{ “name”: “更改了这个name” }demo.name = “want you”//demo的指向没有变,改变了原对象的属性 { “name”: “want you”}//你认为test是什么?console.log(test)//=>{ name: ‘更改了这个name’ }我相信,上面的两个栗子你已经看懂了,即将进入正题先来一个过渡再看一个栗子,用来模拟exports和 module.exports的关联关系 let md = {exps:{}}//md指向一个对象 {exps:{}} let exps = md.exps//exps指向了md.exps所指向的对象 ,这个空对象{} md.exps = {a: 1, b: 2}//md.exps指向了一个新对象 {a: 1, b: 2} exps.c=3//exps,属性赋值 {c: 3} console.log(md.exps); //新对象{ a: 1, b: 2 }上面栗子中的md就是module,md.exps就是module.exports,exps就是exports在每一个模块的头部都有一行这样的命令var exports = module.exports;当直接给module.exports赋值时(module.exports={…..}),module.exports就指向了一个新对象,exports会失效直接给exports赋值会切断exports和 module.exports的关联关系还是这样的一个前提var exports = module.exports;exports是来自于module,exports指向 module.exports所指向的对象当直接给exports赋值,即 exports = {a:1}exports指向了一个新对象,不再是 module.exports所指向的对象,所以不要给 exports 直接赋值( exports =。。。)实践=>导出exportsexports的output.jsexports.str=‘string字符串’//导出字符串exports.bool=true//导出布尔exports.num=123//导出numberexports.foo=(r)=>{//导出函数 console.log(导出函数为:${r});}exports.arr=[1,2,3]//导出数组exports.obj={ a:1, b:2}//导出对象input.js const iptObj= require(’./output.js’) console.log(iptObj.str);//=>string字符串 console.log(iptObj.bool);//=>true console.log(iptObj.num);//=>123 console.log(iptObj.arr);//=>[ 1, 2, 3 ] console.log(iptObj.obj);//=>{ a: 1, b: 2 } iptObj.foo(‘参数’)//=>导出函数为:参数module.exportsmodule.exports的output.jsmodule.exports={ str:‘string字符串’, bool:true, num:123, foo:(r)=>{ console.log(导出函数为:${r}); }, arr:[1,2,3], obj:{ a:1, b:2}}input.js const iptObj= require(’./output.js’) console.log(iptObj.str);//=>string字符串 console.log(iptObj.bool);//=>true console.log(iptObj.num);//=>123 console.log(iptObj.arr);//=>[ 1, 2, 3 ] console.log(iptObj.obj);//=>{ a: 1, b: 2 } iptObj.foo(‘参数’)//=>导出函数为:参数module.exports的output.js同时支持如下写法module.exports.str=‘string字符串’module.exports.bool=truemodule.exports.num=123module.exports.foo=(r)=>{ console.log(导出函数为:${r});}module.exports.arr=[1,2,3]module.exports.obj={ a:1, b:2}input.js不变exportexport的output.jsexport const srt = ‘string字符串’export const bool = trueexport const num = 123export const arr = [1, 2, 3]export const obj = { a: 1, b: 2}export function foo(r) { console.log(导出函数为:${r});}input.jsimport {str,arr,obj,bool,num,foo} from ‘./output’console.log(str)console.log(arr)console.log(obj)console.log(bool)console.log(num)foo(‘参数’)export的output.js同时支持如下写法const str = ‘string字符串’ const bool = trueconst num = 123const arr = [1, 2, 3]const obj = { a: 1, b: 2}function foo(r) { console.log(导出函数为:${r});}export { str,bool,num,arr,obj,foo}input.js 导入支持重命名import {str as STR,arr,obj,bool,num,foo as FOO} from ‘./output’console.log(STR)console.log(arr)console.log(obj)console.log(bool)console.log(num)FOO(‘参数’)继续重命名import * as newName from ‘./output’console.log(newName.str)console.log(newName.arr)console.log(newName.obj)console.log(newName.bool)console.log(newName.num)newName.foo(‘参数’)export defaultexport default的output.jsexport default { str: ‘string字符串’, bool: true, num: 123, foo: (r) => { console.log(导出函数为:${r}); }, arr: [1, 2, 3], obj: { a: 1, b: 2 }}input.jsimport defaultObj from ‘./output’console.log(defaultObj.str)console.log(defaultObj.arr)console.log(defaultObj.bool)console.log(defaultObj.num)console.log(defaultObj.obj)defaultObj.foo(’ef’)//=>导出函数为:efexport default的output.js同时支持如下写法const str = ‘string字符串’const bool = trueconst num = 123const arr = [1, 2, 3]const obj = {a: 1, b: 2}function foo(r) { console.log(导出函数为:${r});}export default { str, bool, num, arr, obj, foo}input.js不变总结这篇文章是对上一篇文章的总结和实践当直接给 module.exports时,exports会失效直接给exports赋值会切断exports和 module.exports的关联关系export,export default,exports,module.exports具体的使用方法实例更多前端资源请关注微信公众号“前端陌上寒”原文链接参考链接js 数组赋值问题 :值传递还是引用? ...

March 31, 2019 · 2 min · jiezi

npm包的发布和管理

npm包管理npm其实是Node.js的包管理工具(node package manager)。为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包,每次都根据名称搜索一下官方网站,下载代码,解压,再使用,非常繁琐。于是一个集中管理的工具应运而生:大家都把自己开发的模块打包后放到npm官网上,如果要使用,直接通过npm安装就可以直接用,不用管代码存在哪,应该从哪下载。更重要的是,如果我们要使用模块A,而模块A又依赖于模块B,模块B又依赖于模块C和模块D,npm可以根据依赖关系,把所有依赖的包都下载下来并管理起来。否则,靠我们自己手动管理,肯定既麻烦又容易出错。npm的基础使用npm的指令其实常用的并不多官方文档;列出来如下面:access Set access level on published packagesadduserAdd a registry user accountauditRun a security auditbinDisplay npm bin folderbugs Bugs for a package in a web browser maybebuildBuild a packagebundleREMOVED 已删除cacheManipulates packages cacheciInstall a project with a clean slatecompletion Tab Completion for npmconfigManage the npm configuration filesdedupeReduce duplicationdeprecateDeprecate a version of a packagedist-tagModify package distribution tagsdocsDocs for a package in a web browser maybedoctorCheck your environmentseditEdit an installed packageexploreBrowse an installed packagehelp-searchSearch npm help documentationhelpGet help on npmhookManage registry hooksinitcreate a package.json fileinstall-ci-testInstall a project with a clean slate and run testsinstall-testInstall package(s) and run testsinstallInstall a packagelinkSymlink a package folderlogout Log out of the registrylsList installed packagesnpm javascript package manageroutdatedCheck for outdated packagesowner Manage package ownerspackCreate a tarball from a packagepingPing npm registryprefixDisplay prefixprofileChange settings on your registry profilepruneRemove extraneous packagespublish Publish a packagerebuildRebuild a packagerepo Open package repository page in the browserrestartRestart a packageroot Display npm rootrun-scriptRun arbitrary package scriptssearch Search for packagesshrinkwrapLock down dependency versions for publicationstarMark your favorite packagesstarsView packages marked as favoritesstartStart a packagestopStop a packageteamManage organization teams and team membershipstestTest a packagetokenManage your authentication tokensuninstallRemove a packageunpublishRemove a package from the registryupdateUpdate a packageversion Bump a package versionviewView registry infowhoamiDisplay npm usernameinit初始化创建package.jsonnpm init [–force|-f|–yes|-y|–scope]npm init <@scope> (same as npx <@scope>/create)npm init [<@scope>/]<name> (same as npx [<@scope>/]create-<name>)search搜索查看远程npm相关资源包信息npm search [-l|–long] [–json] [–parseable] [–no-description] [search terms …]aliases: s, se, findinstall可以是说是install是最为常见的命令官方介绍,npm install (with no args, in package dir)npm install [<@scope>/]<name>npm install [<@scope>/]<name>@<tag>npm install [<@scope>/]<name>@<version>npm install [<@scope>/]<name>@<version range>npm install <git-host>:<git-user>/<repo-name>npm install <git repo url>npm install <tarball file>npm install <tarball url>npm install <folder> alias: npm icommon options: [-P|–save-prod|-D|–save-dev|-O|–save-optional] [-E|–save-exact] [-B|–save-bundle] [–no-save] [–dry-run] In global mode (ie, with -g or –global appended to the command), it installs the current package context (ie, the current working directory) as a global package. The -g or –global argument will cause npm to install the package globally rather than locally. The -f or –force argument will force npm to fetch remote resources even if a local copy exists on disk.上面的还介绍已经很详细了,所以这里只是讲一下npm install packageName [|–save |–save-prod|–save-dev]的区别;npm install babel npm5以前,会把X包安装到node_modules目录中,不会修改package.json的dependencies字段,之后运行npm install命令时,不会自动安装Xnpm install babelnpm5以后,会把X包安装到node_modules目录中,会修改package.json的dependencies字段,之后运行npm install命令时,会自动安装X, 线上环境时会被安装npm install babel -P -P, –save-prod: Package will appear in your dependencies. This is the default unless -D or -O are present. Package will appear in your dependencies, With the –production flag (or when the NODE_ENV environment variable is set to production), npm will install modules listed in dependencies.npm install babel -DPackage will appear in your devDependencies,With the –production flag (or when the NODE_ENV environment variable is set to production), npm will not install modules listed in devDependencies. 会把X包安装到node_modules目录中,会在package.json的devDependencies属性下添加X,之后运行npm install命令时,会自动安装X到node_modules目录中,之后运行npm install –production或者注明NODE_ENV变量值为production时,不会自动安装X到node_modules目录中update升级某个资源包或者全部资源包到某一个版本或者匹配的最新版本。npm update [-g] [<pkg>…]aliases: up, upgradeuninstall移除某个资源包npm uninstall [<@scope>/]<pkg>[@<version>]… [-S|–save|-D|–save-dev|-O|–save-optional|–no-save]aliases: remove, rm, r, un, unlinknpm包创建、编写、测试、维护Node出现之前,JavaScript是缺少包结构的。CommonJS致力于改变这种现状,于是定义了包的结构规范。而NPM的出现则是为了在CommonJS规范的基础上,实现解决包的安装卸载,依赖管理,版本管理等问题。require的查找机制明了之后,我们来看一下包的细节。一个符合CommonJS规范的包应该是如下这种结构:一个package.json文件应该存在于包顶级目录下二进制文件应该包含在bin目录下(可选)JavaScript代码入库是index.js,其他包含在lib目录下文档应该在doc目录下(可选)单元测试应该在test目录下(可选)初始化包创建包的根目录mkdir testpackage初始化npm init // 需要进行一些基本配置编写创建入口文件touch index.js编写代码const updateQueryString = function(url, key, value) { let urlParts = url.split(’#’), hash = ‘’, uri = urlParts.shift(), re = new RegExp(([?&amp;])${key}=.*?(&amp;|$), ‘i’), separator = uri.indexOf(’?’) !== -1 ? ‘&’ : ‘?’, encodeKey = encodeURIComponent(key), encodeValue = encodeURIComponent(value); urlParts.length > 0 && (hash = #${urlParts.join('#')}); if (uri.match(re)) { return uri.replace(re, $1${encodeKey}=${encodeValue}$2) + hash; } else { return ${uri}${separator}${encodeKey}=${encodeValue}${hash}; }};// 最后的导出部分module.exports = { updateQueryString};测试创建包的根目录npm i mocha -D // 安装测试库npm i chai -D // 安装断言库mkdir testcd testtouch index.test.js编写测试代码const utils = require(’./../index.js’);const expect = require(‘chai’).expect;let { updateQueryString} = utils;describe(‘updateQueryString函数的测试’, function() { it(‘https://test.com/path?test=11 修改test参数为22 应该等于 https://test.com/path?test=22', function() { expect(updateQueryString(‘https://test.com/path?test=11', ’test’, 22)).to.be.equal(‘https://test.com/path?test=22'); });});运行测试cd …/node_modules/mocha/bin/mocha npm包的发布注册账号npm官网终端执行 npm login,输入用户名和密码 、邮箱npm publish 发布Organization包我们经常可以看到@angular、@ionic他们的包, 都可以以@开头,那么我们的可不可以,原来angular、ionic都属于一个组织(Organization)只有新创建一个Organization组织之后,才能创建@testorg/testpackname这样的包!!!那么我们就可以去官网上创建我们的Organization,命名之后,官方步骤,初始化npm init –scope=<your_org_name>npm init foo -> npx create-foonpm init @usr/foo -> npx @usr/create-foonpm init @usr -> npx @usr/create修改package.json里面的name字段为@your_org_name/<pkg_name>发布npm publish –access public // 公开包发布npm包支持esmodule ...

November 22, 2018 · 3 min · jiezi