├─ js # js文件夹
│ ├─ main.js # 入口
│ ├─ config.js # 项目配置
│ └─ utils.js # 工具
└─ index.html # 页面html
// config.js
var api = 'hahaha';
var config = {
api: api,
}
// utils.js
var utils = {
request() {
console.log(window.config.api);
}
}
// main.js
window.utils.request();
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xxx</title>
</head>
<body>
<!-- 所有 script 标签必须保证顺序正确,否则会依赖报错 -->
<script src="./js/config.js"></script>
<script src="./js/utils.js"></script>
<script src="./js/main.js"></script>
</body>
</html>
;(function () {
...
}());
用IIFE重构 config.js:
;(function (root) {
var api = 'xxx';
var config = {
api: api,
};
root.config = config;
}(window));
IIFE的出现,使全局变量的声明数量得到了有效的控制。
function namespace(tpl, value) {
return tpl.split('.').reduce((pre, curr, i) => {
return (pre[curr] = i === tpl.split('.').length - 1
? (value || pre[curr])
: (pre[curr] || {}))
}, window);
}
用namespace设置window.app.a.b的值:
namespace('app.a.b', 3); // window.app.a.b 值为 3
用namespace获取window.app.a.b的值:
var b = namespace('app.a.b'); // b 的值为 3
var d = namespace('app.a.c.d'); // d 的值为 undefined
app.a.c值为undefined,但因为使用了namespace, 所以app.a.c.d不会报错,变量d的值为undefined。
异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。本规范只定义了一个函数define,它是全局变量。
/**
* @param {string} id 模块名称
* @param {string[]} dependencies 模块所依赖模块的数组
* @param {function} factory 模块初始化要执行的函数或对象
* @return {any} 模块导出的接口
*/
function define(id?, dependencies?, factory): any
├─ js # js文件夹
│ ├─ ...
│ └─ require.js # RequireJS 的 JS 库
└─ ...
// config.js
define(function() {
var api = 'xxx';
var config = {
api: api,
};
return config;
});
// utils.js
define(['./config'], function(config) {
var utils = {
request() {
console.log(config.api);
}
};
return utils;
});
// main.js
require(['./utils'], function(utils) {
utils.request();
});
<!-- index.html -->
<!-- ...省略其他 -->
<html>
<body>
<script data-main="./js/main" src="./js/require.js"></script>
</body>
</html>
可以看到,使用 RequireJS 后,每个文件都可以作为一个模块来管理,通信方式也是以模块的形式,这样既可以清晰的管理模块依赖,又可以避免声明全局变量。
特别说明:
先有 RequireJS,后有 AMD 规范,随着 RequireJS 的推广和普及,AMD 规范才被创建出来。
// AMD
// 依赖必须一开始就写好
define(['./utils'], function(utils) {
utils.request();
});
// CMD
define(function(require) {
// 依赖可以就近书写
var utils = require('./utils');
utils.request();
});
AMD 也支持依赖就近,但 RequireJS 作者和官方文档都是优先推荐依赖前置写法。
考虑到目前主流项目中对 AMD 和 CMD 的使用越来越少,大家对 AMD 和 CMD 有大致的认识就好,此处不再过多赘述。
随着 ES6 模块规范的出现,AMD/CMD 终将成为过去,但毋庸置疑的是,AMD/CMD 的出现,是前端模块化进程中重要的一步。
CommonJS
前面说了, AMD、CMD 主要用于浏览器端,随着 node 诞生,服务器端的模块规范 CommonJS 被创建出来。
还是以上面介绍到的 config.js、utils.js、main.js 为例,看看 CommonJS 的写法:
// config.js
var api = 'xxx';
var config = {
api: api,
};
module.exports = config;
// utils.js
var config = require('./config');
var utils = {
request() {
console.log(config.api);
}
};
module.exports = utils;
// main.js
var utils = require('./utils');
utils.request();
console.log(global.api)
执行node main.js,"xxx"被打印了出来。
在 main.js 中打印global.api,打印结果是undefined。node 用global管理全局变量,与浏览器的window类似。与浏览器不同的是,浏览器中顶层作用域是全局作用域,在顶层作用域中声明的变量都是全局变量,而 node 中顶层作用域不是全局作用域,所以在顶层作用域中声明的变量非全局变量。
// a.js
var s = 'hhh'
module.exports = s;
console.log(module);
执行node a.js,看看打印的module对象:
{
exports: 'hhh',
id: '.', // 模块id
filename: '/Users/apple/Desktop/a.js', // 文件路径名称
loaded: false, // 模块是否加载完成
parent: null, // 父级模块
children: [], // 子级模块
paths: [ /* ... */ ], // 执行 node a.js 后 node 搜索模块的路径
}
其他模块导入该模块时:
// b.js
var a = require('./a.js'); // a -->hhh
当在 a.js 里这样写时:
// a.js
var s = 'hhh'
exports = s;
a.js 模块的module.exports是一个空对象。
// b.js
var a = require('./a.js'); // a --> {}
把module.exports和exports放到“明面”上来写,可能就更清楚了:
var module = {
exports: {}
}
var exports = module.exports;
console.log(module.exports === exports); // true
var s = 'hhh'
exports = s; // module.exports 不受影响
console.log(module.exports === exports); // false
模块初始化时,exports和module.exports指向同一块内存,exports被重新赋值后,就切断了跟原内存地址的关系。
所以,exports要这样使用:
// a.js
exports.s = 'hhh';
// b.js
var a = require('./a.js');
console.log(a.s); // hhh
CommonJS 和 CommonJS2 经常被混淆概念,一般大家经常提到的 CommonJS 其实是指 CommonJS2,本文也是如此,不过不管怎样,大家知晓它们的区别和如何应用就好。
var a = require('./a.js');时,在 a.js 文件加载完成后,才执行后面的代码。AMD 加载模块是异步的,所有依赖加载完成后以回调函数的形式执行代码。
[如下代码]fs和chalk都是模块,不同的是,fs是 node 内置模块,chalk是一个 npm 包。这两种情况在 CommonJS 中才有,AMD 不支持。
var fs = require('fs');
var chalk = require('chalk');
!function (root, factory) {
if (typeof exports === 'object' && typeof module === 'object') {
// CommonJS2
module.exports = factory()
// define.amd 用来判断项目是否应用 require.js。
// 更多 define.amd 介绍,请[查看文档](https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property-)
} else if (typeof define === 'function' && define.amd) {
// AMD
define([], factory)
} else if (typeof exports === 'object') {
// CommonJS
exports.myLibName = factory()
} else {
// 全局变量
root.myLibName = factory()
}
}(window, function () {
// 模块初始化要执行的代码
});
UMD 解决了 JS 模块跨模块规范、跨平台使用的问题,它是非常好的解决方案。
export const prefix = 'https://github.com';
export const api = `${prefix}/hhhh`;
const prefix = 'https://github.com';
const api = `${prefix}/hhh`;
export {
prefix,
api,
}
方式1和方式2只是写法不同,结果是一样的,都是把prefix和api分别导出。
方式3(默认导出)
// foo.js
export default function foo() {}
// 等同于:
function foo() {}
export {
foo as default
}
export default用来导出模块默认的接口,它等同于导出一个名为default的接口。配合export使用的as关键字用来在导出接口时为接口重命名。
方式4(先导入再导出简写)
export { api } from './config.js';
// 等同于:
import { api } from './config.js';
export {
api
}
如果需要在一个模块中先导入一个接口,再导出,可以使用export ... from 'module'这样的简便写法。
// config.js
const prefix = 'https://github.com';
const api = `${prefix}/hhh`;
export {
prefix,
api,
}
接口已经导出,如何导入呢:
import { api } from './config.js';
// or
// 配合`import`使用的`as`关键字用来为导入的接口重命名。
import { api as myApi } from './config.js';
import * as config from './config.js';
const api = config.api;
将 config.js 模块导出的所有接口都挂载在config对象上。
// foo.js
export const conut = 0;
export default function myFoo() {}
// index.js
// 默认导入的接口此处刻意命名为cusFoo,旨在说明该命名可完全自定义。
import cusFoo, { count } from './foo.js';
// 等同于:
import { default as cusFoo, count } from './foo.js';
export default导出的接口,可以使用import name from 'module'导入。这种方式,使导入默认接口很便捷。
import './config.js';
这样会加载整个 config.js 模块,但未导入该模块的任何接口。
// 报错
if (/* ... */) {
import { api } from './config.js';
}
// 报错
function foo() {
import { api } from './config.js';
}
// 报错
const modulePath = './utils' + '/api.js';
import modulePath;
使用import()实现按需加载:
function foo() {
import('./config.js')
.then(({ api }) => {
});
}
const modulePath = './utils' + '/api.js';
import(modulePath);
特别说明:
该功能的提议目前处于 TC39 流程的第4阶段。更多说明,请查看TC39/proposal-dynamic-import。
// o.js
let num = 0;
function getNum() {
return num;
}
function setNum(n) {
num = n;
}
console.log('o init');
module.exports = {
num,
getNum,
setNum,
}
// a.js
const o = require('./o.js');
o.setNum(1);
// b.js
const o = require('./o.js');
// 注意:此处只是演示,项目里不要这样修改模块
o.num = 2;
// main.js
const o = require('./o.js');
require('./a.js');
console.log('a o.num:', o.num);
require('./b.js');
console.log('b o.num:', o.num);
console.log('b o.getNum:', o.getNum());
命令行执行node main.js,打印结果如下:
1. `o init`
_模块即使被其他多个模块导入,也只会加载一次,并且在代码运行完成后将接口赋值到`module.exports`属性上。_
2. `a o.num: 0`
_模块在加载完成后,模块内部的变量变化不会反应到模块的`module.exports`。_
3. `b o.num: 2`
_对导入模块的直接修改会反应到该模块的`module.exports`。_
4. `b o.getNum: 1`
_模块在加载完成后即形成一个闭包。_
// o.js
let num = 0;
function getNum() {
return num;
}
function setNum(n) {
num = n;
}
console.log('o init');
export {
num,
getNum,
setNum,
}
// main.js
import { num, getNum, setNum } from './o.js';
console.log('o.num:', num);
setNum(1);
console.log('o.num:', num);
console.log('o.getNum:', getNum());
我们增加一个 index.js 用于在 node 端支持 ES6 module:
// index.js
require("@babel/register")({
presets: ["@babel/preset-env"]
});
module.exports = require('./main.js')
命令行执行npm install @babel/core @babel/register @babel/preset-env -D安装 ES6 相关 npm 包。
命令行执行node index.js,打印结果如下:
1. `o init`
_模块即使被其他多个模块导入,也只会加载一次。_
2. `o.num: 0`
3. `o.num: 1`
_编译时确定模块依赖的 ES6 module,通过`import`导入的接口只是值的引用,所以`num`才会有两次不同打印结果。_
4. `o.getNum: 1`
对于打印结果3,知晓其结果,在项目中注意这一点就好。这块会涉及到“Module Records(模块记录)”、“module instance(模快实例)” “linking(链接)”等诸多概念和原理,大家可查看ES modules: A cartoon deep-dive进行深入的研究,本文不再展开。
ES6 module 是编译时加载(或叫做“静态加载”),利用这一点,可以对代码做很多之前无法完成的优化:
在开发阶段就可以做导入和导出模块相关的代码检查。
结合 Webpack、Babel 等工具可以在打包阶段移除上下文中未引用的代码(dead-code),这种技术被称作“tree shaking”,可以极大的减小代码体积、缩短程序运行时间、提升程序性能。
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co
我有一个Controller,我想为这个Controller创建一个助手,我可以在不包含它的情况下使用它。我尝试像这样创建一个与Controller同名的助手classCars::EnginesController我创建的助手是moduleCars::EnginesHelperdefcheck_fuellogger.debug("chekingfuel")endend我得到的错误是undefinedlocalvariableormethod`check_fuel'for#有没有我遗漏的约定? 最佳答案 如果你真的想在Controll
我有一个模块stat存在于目录结构中:lib/stat_creator/stat/在lib/stat_creator/stat.rb中,我在lib/stat_creator/stat/目录中有我需要的文件,以及:moduleStatCreatormoduleStatendend当我使用该模块时,我将这些类称为StatCreator::Stat::Foo.new现在我想要一个存在于应用程序中的根Stat类。我在app/models中制作了我的Stat类,并在routes.rb中进行了设置。但是,如果我转到Rails控制台并尝试在应用程序/模型中使用Stat类,例如:Stat.by_use