模拟实现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模块化 * 模块化的理解 * 什么是模块? * 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 * 块的内部数据/实现是私有的, 只是向外部 ...
随机推荐
- 二、mycat基础知识、基本配置
官网 http://www.mycat.io/ Mycat 概要介绍 https://github.com/MyCATApache/Mycat-Server 入门指南 https://github.c ...
- 多线程(一)java并发编程基础知识
线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...
- 从.NET看微软的焦虑
节日没事,就像聊聊微软的NET. 1.孩子静悄悄,必定在作妖 截止目前,微软的市值达到1.85万亿美元,按说,这样一个宙斯级的巨无霸应该过的非常舒坦, 但是,和微软市值成鲜明的反差,我们从.NET的发 ...
- Apple & 人体工程学
Apple & 人体工程学 https://support.apple.com/zh-cn/HT205655 MBP 2018 https://help.apple.com/macbookpr ...
- 使用 js 实现一个中文自动转换成拼音的工具库
使用 js 实现一个中文自动转换成拼音的工具库 中文 => zhong-wen 应用场景 SEO 友好, URL 自动转换 blogs 发布文章,自动化部署,自动生成 url 的 path (时 ...
- how to tell a function arguments length in js
how to tell a function arguments length in js JavaScript函数不对参数值(参数)执行任何检查 https://www.w3schools.com/ ...
- js & array remove one item ways
js & array remove one item ways // array remove one item ways let keys = [1,2,3,4,5,6,7]; let ke ...
- 万链互联时代,NGK DeFi项目如何在牛市中崭露头角!
众所周知,中心化交易所存在技术风险.道德风险与法律风险.去中心化交易所像是NGK以其匿名性.安全性.私钥独立掌控的特点,弥补了中心化交易所的不足,我们看到Uniswap日成交量均超过1亿美元,甚至接近 ...
- NGK项目好不好?
在谈NGK项目之前,我们不得不提到NGK背后的研发团队,硅谷顶尖技术团队灵石团队.硅谷作为全世界最顶尖的高新技术和科技创新产业区,NGK.IO区块链系统正是在此处诞生. 灵石部门核心成员曾负责过多个P ...
- redis和mysql结合数据一致性方案
缓存读: 缓存由于高并发高性能,已经被广泛的应用.在读取缓存方面做法一致.流程如下: 写缓存: 1.先更新数据库,再更新缓存 2.先更新数据库,再删除缓存. (1).先更新数据库,再更新缓存 这套方案 ...