模拟实现AMD模块化规范
引子
本文最后的目的是模拟实现AMD模块化规范,而写下本文的原因是今天阅读到了《你不知道的JavaScript--上卷》中作用域闭包的章节,让我对闭包又有了更深入的理解。
对于闭包的相关知识我之前也根据自己所学到的进行了较详细的总结,大家可以先来看看这篇文章先,写的不好的地方多多见谅,更欢迎提出意见和建议
就将这篇文章作为对闭包相关知识的加深深入,废话不多说,全篇开始!
再谈什么是闭包(闭包的产生)?
我的这篇文章中对闭包的解释是:
- 函数互相嵌套
- 当内部函数引用外部函数中的变量时闭包就产生了,而这个内部函数就是闭包,它并不需调用就能成为闭包
那在我阅读书籍后,是否推翻了这套结论呢? 恰恰不是,这套结论总结的非常的好,它让我能够迅速判别当前的函数是否是闭包, 但作为学术上或者说真正比较官方的对闭包概念解释上,这样显得仍不够
所以以前的这套结论就来用作迅速判别闭包,而本文就来记录、总结闭包的完整理解, 记住: 并不是以前的结论不对,而是不够深入
先总结出闭包最终的完整理解,再不断的对其进行解释
所谓闭包,就是:
- 当函数不仅能够在定义时所在的词法作用域以外进行调用
- 调用时还能正常访问定义时的词法作用域
词法作用域看着很眼熟,不明白什么意思? 没关系
词法作用域
JavaScript中作用域就是指: 用来管理引擎是如何在当前作用域或者嵌套的父子作用域中根据标识符进行变量和变量值查找的一套规则
一提到作用域,我们大多数可以能就会想到: 哦! 全局作用域、函数作用域、变量查找与作用域链,甚至块作用域。但是我想提前说的是: 词法作用域这些概念息息相关,它属于是这些概念的集合。一个更加上层的概念
作用域有两种模型: 词法作用域和动态作用域
JavaScript中主要的是词法作用域,但是也有动态作用域的身影,由于本节主要介绍的是词法作用域,所以不会涉及动态作用域
之所以将作用域命名为词法作用域,是因为这个概念和JavaScript工作流程中的编译流程息息相关。虽然JavaScript作为一门动态语言不需要像java和C等语言那样需要在执行前手动编译,但不代表它没有这一套过程
编译阶段最重要的一个工作就是词法化(单词化), 它对源代码进行分析,然后赋予单词和代码块含义,作用域是在词法阶段定义的(在编写代码阶段),因此当编译器进行词法分析时就会保持作用域不变(至少大部分情况是这样的)
既然作用域与词法化关联这么深,所以就命名为词法作用域了
总结来说: 词法作用域是指作用域由代码书写时各种变量和函数的位置所决定,编译的词法阶段通过作用域大概预测出执行过程中如何进行查找
回到闭包
对词法作用域做了一大段枯燥的解释后,终于可以回到闭包了。闭包就是基于词法作用域书写代码时所产生的自然结果,它并不是一种新的语法或技术。
当函数能够记住并访问所在的词法作用域时,闭包就产生了,不管函数是否在当前词法作用域之外执行 这时候回到上面对闭包完整总结那细细品一品先吧
// 一个最典型的闭包
function foo() {
var a = 2
function bar() {
console.log(a)
}
return bar
}
var baz = foo()
baz() // 2
根据词法作用域的概念,更细来说是作用域。 bar函数拥有一个覆盖foo函数的作用域,在当前来说bar的作用域范围是最为之广阔的,bar能够访问foo的作用域甚至全局作用域,用图来表达一下我说的意思

foo执行后将返回值赋值给全局变量baz,调用baz实际上是根据引用传递去调用内部的这个bar而已。根据JS的垃圾回收机制在foo执行完毕后其内部所有的变量和函数都会被回收,但是由于回收机制不会对仍有引用的对象进行回收,所以bar作用域仍然存在。
在回收机制和词法作用域是静态确定的相互作用下,baz不仅能够正常调用,还能够正常访问baz引用着的那个函数(bar函数)在定义时的词法作用域 于是乎闭包就这样产生了,再回到前面一开始我给出的闭包的完整理解,大家应该就明白是怎么个意思了
我对闭包概念的再一次深入理解,对闭包概念的深入总结到这就结束了
那闭包有什么用呢? 书中作者的经验是,闭包最大的作用就是对当今JavaScript模块化规范的贡献。模块化正是利用闭包发挥出了强大的威力,对于JavaScript模块化可以看看我的这篇学习笔记
利用闭包编写模块
模块拥有两个必要条件:
- 外部必须是一个函数,且函数必须至少被调用一次(每次调用产生的闭包作为新的模块实例)
- 外部函数内部至少有一个内部函数, 内部函数用于修改和访问各种内部私有成员
利用闭包编写一个模块(例子)
function myModule (){
const moduleName = '我的自定义模块'
var name = 'Fitz'
// 在模块内定义方法(API)
function getName(){
console.log(name)
}
function modifyName(newName){
name = newName
}
// 模块暴露: 向外暴露API
return {
getName,
modifyName
}
}
// 测试
const md = myModule()
md.getName() // 'Fitz'
md.modifyName('LX')
md.getName() // 'LX'
// 模块实例之间互不影响
const md2 = myModule()
md2.sayHello = function () {
console.log('hello')
}
console.log(md) // {getName: ƒ, modifyName: ƒ}
当一个模块确定只需要一个模块实例的时候,我们就可以通过IIFE创建,这种方式我们成为单例模式
var singleSample = (function Module (){
const moduleName = '我的自定义模块'
var name = 'Fitz'
// 在模块内定义方法(API)
function getName(){
console.log(name)
}
function modifyName(newName){
name = newName
}
// 模块暴露: 向外暴露API
return {
getName,
modifyName
}
})()
console.log(singleSample) // {getName: ƒ, modifyName: ƒ}
实现AMD模块化规范
介绍了词法作用域、闭包这些概念夯实了基础, 通过使用闭包实现简单模块的创建懂的了原理的实际运用,是时候朝着本篇的目标进发了!
AMD规范的语法是大概这样的
暴露模块
// 暴露没有依赖的模块
define(function () {
// do something
return 模块
})
// 暴露有依赖的模块
define(
['依赖1','依赖2'],
function (m1, m2) {
// do something
return 模块
}
)
引入模块
requirejs(
['依赖'],
function (m1) {
// do something
}
)
我们模拟实现这些功能
// IIFE命名的原因是: 无论是否为匿名函数都应该为其取名, 达到见字知意
// ModuleManager是模块管理器, 它有用于定义模块和暴露模块的API, 其本身就是一个模块
const ModuleManager = (function Fake_AMD_Module_Standard() {
// 使用者所用定义的模块对象都会储存在这里
/*
最终结果会是
modules = {
moduleID: 使用者向外暴露的模块对象
}
*/
let modules = {}
// 用于定义模块的API
/*
@parms{
moduleID: String,
depends: Array,
implement: Function
}
分别是: 模块名字, 依赖对象组成的数组, 使用者定义的模块
*/
function define(moduleID, depends, implement) {
// 当模块有依赖对象时
if (depends && implement) {
// 需要将依赖数组内的所有模块名替换成实际的对应的模块
depends.forEach((moduleID, index)=>{
depends[index] = __getModule(moduleID)
/*
原: depends => ['foo', 'bar', 'baz']
替换后: depends => [ {say: f}, {test: f, talk: f}, {getName: f} ]
*/
})
}
// 当没有依赖对象时, 可以省略数组
// 有依赖的模块(implement)想要使用依赖内的各种API,必须通过apply将依赖注入到使用者当前定义的模块(implement)中
modules[moduleID] = implement? implement.apply(implement, depends) : depends()
/*
depends内的所有模块对象, 最终会分别被implement中定义的形参所接收
*/
}
// 该私有方法用于获得modules中的模块对象
function __getModule(moduleID) {
return modules[moduleID]
}
// 定义用于引入模块的API
/*
@parms{
depends: Array
moduleID: String
}
*/
function requireJS(requireModeles, implement) {
requireModeles.forEach((eachModule, index)=>{
requireModeles[index] = __getModule(eachModule)
})
implement.apply(implement, requireModeles)
}
let publicAPI = {
define,
requireJS,
}
/*
为了让一切更加自然(可以直接在全局调用, 而不需要经过模块管理器调用API)
模仿JQuery的方式向全局中也暴露模块管理器的API
*/
for (const api in publicAPI) {
if (Object.hasOwnProperty.call(publicAPI, api)) {
window[api] = publicAPI[api]
}
}
return publicAPI
})()
// =============================测试==========================
// 定义一个没有依赖的模块
ModuleManager.define('foo', function() {
function getParm(parm) {
console.log('我是foo模块')
return `得到实参 => ${parm}`
}
// 向外暴露一个对象, 包含所有需要暴露的API
return {
getParm
}
})
// 没有引入其他模块
ModuleManager.requireJS(['foo'], function(foo) {
console.log(foo)
foo.getParm()
})
// 定义一个有依赖的模块
define('sayUtil', ['foo'], function(fooDepend) {
function sayName(name) {
let result = fooDepend.getParm(name)
return `
<成功测试有依赖的模块>
${result}
`
/*
sayName('啊达')预计返回结果:
`
'我是foo模块'
<成功测试有依赖的模块>
得到实参 => 啊达
`
*/
}
// 向外暴露本模块的API
return {
sayName
}
})
requireJS(['sayUtil'], function(sayUtil) {
var result = sayUtil.sayName('啊达')
console.log(result)
})
// 模拟引入其他库, 再使用模块
requireJS(['foo', 'sayUtil'], function(foo, $) {
foo.getParm()
console.log($.sayName())
})
// =============================测试==========================
写在最后
文章到这就结束了, 模拟实现只能对最简单的功能进行模拟
文章看起来有点啰嗦(尤其是前面),写的不好的地方希望大家见谅
模拟实现AMD模块化规范的更多相关文章
- Javascript AMD模块化规范-备用
AMD是"Asynchronous Module Definition"的缩写,意思是"异步模块定义". 模块定义define(id?, dependencie ...
- Javascript模块化编程系列三: CommonJS & AMD 模块化规范描述
CommonJS Module 规范 CommonJS 的模块化规范描述在Modules/1.1.1 中 目前实现此规格的包有: Yabble,CouchDB,Narwhal (0.2), Wakan ...
- JavaScript AMD模块化规范
浏览器环境 有了服务器端模块以后,很自然地,大家就想要客户端模块.而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行. 但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器 ...
- CommonJs、AMD、CMD模块化规范
/** * CommonJS 模块化规范 * CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作 */ /*-------Node.js遵循Commonjs规范----- ...
- 【整理】 JavaScript模块化规范AMD 和 CMD 的区别有哪些?
根据玉伯等人在知乎上的回答整理.整理中... AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD CMD 规范在这里:https://githu ...
- 模块化规范Common.js,AMD,CMD
随着网站规模的不断扩大,嵌入网页中的javascript代码越来越大,开发过程中存在大量问题,如:协同开发,代码复用,大量文件引入,命名冲突,文件依赖. 模块化编程称为迫切的需求. 所谓的模块,就是实 ...
- Javascript模块化规范
Javascript模块化规范 一.前端js模块化由来与演变 CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践.09年下 ...
- javaScript模块化规范ADM与CMD
模块化:模块化是指在解决某一个复杂问题时,依照一种分类的思维把问题进行系统性的分解处理,可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在. 模块化系统所必须 ...
- js-模块化(三大模块化规范)
###1. JS模块化 * 模块化的理解 * 什么是模块? * 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 * 块的内部数据/实现是私有的, 只是向外部 ...
随机推荐
- PWA All In One
PWA All In One chrome://apps/ PWA Progressive Web App 可安装,添加到主屏 离线使用 轻量,快速 基于 Web 技术一套代码多端复用(移动端,桌面端 ...
- Web Components & HTML template & HTML slot
Web Components & HTML template & HTML slot https://github.com/xgqfrms/es-next/issues/2 live ...
- browser parse CSS style order
browser parse CSS style order 浏览器解析 CSS style 的顺序 从右到左 https://juejin.im/entry/5a123c55f265da432240c ...
- flutter & i18n & L10n & json
flutter & i18n & L10n & json https://marketplace.visualstudio.com/items?itemName=esskar. ...
- 口罩 & 防毒面具 N95 & P100
口罩 & 防毒面具 N95 & P100 N95 口罩 < 防毒面具 P100 https://www.techritual.com/2020/01/30/210599/
- NGK项目是一个怎样的项目?区块链里算是有前景的吗?
牛市时,项目被众星捧月,优点被无限放大,缺点无限被掩盖:而当市场开始下行时,之前的赞美则变成了贬低.所以了解项目不能盲目跟风,需要有独立的思考.对于近期引起社区讨论的NGK项目,以它为例,今天就来给大 ...
- 加州金融专访NGK,就NGK DeFi+展开讨论
近日,加利福尼亚金融日报联合数家知名媒体就DeFi+行业专访了NGK团队代表特德惠斯基. 首先,加利福尼亚金融日报专栏记者迈尔斯表示,目前区块链领域,去中心化金融(DeFi+)的概念是目前市场上面最火 ...
- [转]什么是 C 和 C ++ 标准库?
转载地址:https://www.cnblogs.com/findumars/p/9000371.html 简要介绍编写C/C ++应用程序的领域,标准库的作用以及它是如何在各种操作系统中实现的.我已 ...
- 生成类库项目时同时生成的pdb文件是什么东东?
英文全称:Program Database File Debug里的PDB是full,保存着调试和项目状态信息.有断言.堆栈检查等代码.可以对程序的调试配置进行增量链接.Release 里的PDB是p ...
- 数据库分表自增ID问题
.................................................................................................... ...