1.模块化的历史
概括来讲,以下是演变史
最先是将变量,方法定义在全局,我想用啥就定义啥。
其次是将变量,方法封装在一个函数里面,然后对外暴露一个接口的方式提供服务。(此时已经有模块化的雏形了)
最终比如common.js的模块化,你有一个变量,把它挂在module上面,然后把它export出去,其实和函数,对外抛出一个return,其实是相似的一个东西。
其实目前模块化处在的交错的时代,比如引入有import,require,目前还没有一个统一的规范化标准。
2.各模块化的特点
AMD特点:依赖前置
//定义
define(id?,depencies?,factory);
define('foo',['utils','bar'],function(utils,bar)) {
utils.add(1,2);
return {
name:'foo'
}
}
//全局配置
rj.config({ paths : {
'jquery': 'url'
}});
define('a',function(){
console.log('a load');
return {
run: function(){console.log('a run')}
}
})
define('b',function(){
console.log('b load');
return {
run: function(){console.log('b run')}
}
})
require(['a','b'],function(a,b){
console.log('a,b引入了');
a.run();
b.run();
})
CMD特点:就近依赖/按需引入
define('a',function(require,exports,module){
console.log('a load');
exports.run = () => {
console.log('a run');
}
})
define('b',function(require,exports,module){
console.log('b load');
exports.run = () => {
console.log('b run');
}
})
define('main',function(require,exports,module){
console.log('main run');
const a = require('a');
a.run();
const b = require('b');
b.run();
})
seajs.use('main')
commonJs特点:值拷贝,缓存机制
//缓存机制
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'
//值拷贝
//a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18
3.自写一个requreJs的实例
参照了某位大佬的实现后,自己参照着使用例子又重新写了一遍实现
const defineMap = new Map();
//定义模块,存到一个defineMap里面
window.define = (id,deps,factory) => {
//简单判断一下几个参数 --此处逻辑待优化
if(factory){
defineMap.set(id, {id , deps , factory});
}else{
const emptyList = [];
defineMap.set(id, {id , emptyList, deps});
}
}
const rjOptions = {paths: {}};
//全局引用依赖方式
const rj = window.rj = {};
rj.config = (options) => {
for(let key in options){
rjOptions.paths[key] = options[key];
}
}
window.require = (deps,callback) => {
return new Promise((resolve,reject)=>{
Promise.all(deps.map(dep=>{
//走 CDN
if (rjOptions.paths[dep]) return cdnImport(rjOptions.paths[dep]);
//走 本地
//各种各样情况,检查是否已获取js文件
const promise = !isExistUrl(getUrl(dep)) ? loadUrl(getUrl(dep)) : Promise.resolve();
return promise.then(()=>{
const {deps , factory} = defineMap.get(dep);
if(deps.length > 0){
return require(deps,factory);
}else{
return factory(null);
}
})
})).then(resolve,reject);
}).then(factorys => callback(...factorys));
}
//加载文件到html页面
const loadUrl = (url) => {
return new Promise((resolve,reject)=>{
const script = document.createElement('script');
const head = document.getElementsByTagName('head')[0];
script.url = url;
script.type = 'javascript';
script.async = true;
script.onload = resolve;
script.onerror = reject;
head.append(script);
})
}
//获取依赖的相对路径
const getUrl = (dep) => {
const p = location.pathname;
return p.slice(0, p.lastIndexOf('/')) + '/' + dep + '.js';
}
// From CDN 加载资源
const cdnImport = (url) => {
return new Promise((resove, reject) => {
System.import(url).then(resove, reject)
})
}
// 判断js文件是否已引入
const isExistUrl = (url) => {
const srciptNodes = Array.prototype.slice.call(document.getElementsByTagName('script'));
const list = srciptNodes.map(scriptNode=>{
return scriptNode.baseURI
})
return list.indexOf(url) > 0 ? true : false;
}
1.定义一个全局define和一个defineMap用来存放依赖(很简单,不细说了)
const defineMap = new Map();
//定义模块,存到一个defineMap里面
window.define = (id,deps,factory) => {
//简单判断一下几个参数 --此处逻辑待优化
if(factory){
defineMap.set(id, {id , deps , factory});
}else{
const emptyList = [];
defineMap.set(id, {id , emptyList, deps});
}
}
2.定义一个全局rj和一个rjOptions用来存放全局依赖(很简单,不细说了)
const rjOptions = {paths: {}};
//全局引用依赖方式
const rj = window.rj = {};
rj.config = (options) => {
for(let key in options){
rjOptions.paths[key] = options[key];
}
}
3.定义一个全局require用来引入依赖
window.require = (deps,callback) => {
return new Promise((resolve,reject)=>{
Promise.all(deps.map(dep=>{
//走 CDN
if (rjOptions.paths[dep]) return cdnImport(rjOptions.paths[dep]);
//走 本地
//各种各样情况,检查是否已获取js文件
const promise = !isExistUrl(getUrl(dep)) ? loadUrl(getUrl(dep)) : Promise.resolve();
return promise.then(()=>{
const {deps , factory} = defineMap.get(dep);
if(deps.length > 0){
return require(deps,factory);
}else{
return factory(null);
}
})
})).then(resolve,reject);
}).then(factorys => callback(...factorys));
}
4.其中第一个参数deps是一个依赖数组,第二个参数callback是回调函数
5.第一步new一个Promise,需要等promise状态完成后,才执行回调函数
return new Promise((resolve,reject)=>{
6.因为依赖是异步加载的,所以需要用一个Promise.all包裹起来
Promise.all(deps.map(dep=>{
7.判断依赖是属于外部引入,还是本地自定义,分情况走CDN和本地
//走 CDN
if (rjOptions.paths[dep]) return cdnImport(rjOptions.paths[dep]);
//走 本地
//各种各样情况,检查是否已获取js文件
const promise = !isExistUrl(getUrl(dep)) ? loadUrl(getUrl(dep)) : Promise.resolve();
return promise.then(()=>{
const {deps , factory} = defineMap.get(dep);
if(deps.length > 0){
return require(deps,factory);
}else{
return factory(null);
}
})
8.本地依赖需要判断js文件是否引入
isExistUrl(getUrl(dep))
9.继续执行promise任务,如果还有依赖,怎么重复进行require,否则返回factory的结果
const {deps , factory} = defineMap.get(dep);
if(deps.length > 0){
return require(deps,factory);
}else{
return factory(null);
}
10.Promise.all将factory返回结果集收集起来,作为参数,执行callback回调
.then(factorys => callback(...factorys))