seajs1.3.0源码解析之module依赖有序加载


Posted in Javascript onNovember 07, 2012

这里是seajs loader的核心部分,有些IE兼容的部分还不是很明白,主要是理解各个模块如何依赖有序加载,以及CMD规范。

代码有点长,需要耐心看:

/** 
* The core of loader 
*/ 
;(function(seajs, util, config) { 
// 模块缓存 
var cachedModules = {} 
// 接口修改缓存 
var cachedModifiers = {} 
// 编译队列 
var compileStack = [] 
// 模块状态 
var STATUS = { 
'FETCHING': 1, // The module file is fetching now. 模块正在下载中 
'FETCHED': 2, // The module file has been fetched. 模块已下载 
'SAVED': 3, // The module info has been saved. 模块信息已保存 
'READY': 4, // All dependencies and self are ready to compile. 模块的依赖项都已下载,等待编译 
'COMPILING': 5, // The module is in compiling now. 模块正在编译中 
'COMPILED': 6 // The module is compiled and module.exports is available. 模块已编译 
} 
function Module(uri, status) { 
this.uri = uri 
this.status = status || 0 
// this.id is set when saving 
// this.dependencies is set when saving 
// this.factory is set when saving 
// this.exports is set when compiling 
// this.parent is set when compiling 
// this.require is set when compiling 
} 

Module.prototype._use = function(ids, callback) { 
//转换为数组,统一操作 
util.isString(ids) && (ids = [ids]) 
// 使用模块系统内部的路径解析机制来解析并返回模块路径 
var uris = resolve(ids, this.uri) 
this._load(uris, function() { 
// Loads preload files introduced in modules before compiling. 
// 在编译之前,再次调用preload预加载模块 
// 因为在代码执行期间,随时可以调用seajs.config配置预加载模块 
preload(function() { 
// 编译每个模块,并将各个模块的exports作为参数传递给回调函数 
var args = util.map(uris, function(uri) { 
return uri ? cachedModules[uri]._compile() : null 
}) 
if (callback) { 
// null使回调函数中this指针为window 
callback.apply(null, args) 
} 
}) 
}) 
} 
// 主模块加载依赖模块(称之为子模块),并执行回调函数 
Module.prototype._load = function(uris, callback) { 
// 过滤uris数组 
// 情况一:缓存中不存在该模块,返回其uri 
// 情况二:缓存中存在该模块,但是其status < STATUS.READY(即还没准备好编译) 
var unLoadedUris = util.filter(uris, function(uri) { 
return uri && (!cachedModules[uri] || 
cachedModules[uri].status < STATUS.READY) 
}) 
var length = unLoadedUris.length 
// 如果length为0,表示依赖项为0或者都已下载完成,那么执行回调编译操作 
if (length === 0) { 
callback() 
return 
} 
var remain = length 
for (var i = 0; i < length; i++) { 
// 闭包,为onFetched函数提供上下文环境 
(function(uri) { 
// 创建模块对象 
var module = cachedModules[uri] || 
(cachedModules[uri] = new Module(uri, STATUS.FETCHING)) 
//如果模块已下载,那么执行onFetched,否则执行fetch操作(请求模块) 
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched) 
function onFetched() { 
// cachedModules[uri] is changed in un-correspondence case 
module = cachedModules[uri] 
// 如果模块状态为SAVED,表示模块的依赖项已经确定,那么下载依赖模块 
if (module.status >= STATUS.SAVED) { 
// 从模块信息中获取依赖模块列表,并作循环依赖的处理 
var deps = getPureDependencies(module) 
// 如果存在依赖项,继续下载 
if (deps.length) { 
Module.prototype._load(deps, function() { 
cb(module) 
}) 
} 
// 否则直接执行cb 
else { 
cb(module) 
} 
} 
// Maybe failed to fetch successfully, such as 404 or non-module. 
// In these cases, just call cb function directly. 
// 如果下载模块不成功,比如404或者模块不规范(代码出错),导致此时模块状态可能为fetching,或者fetched 
// 此时直接执行回调函数,在编译模块时,该模块就只会返回null 
else { 
cb() 
} 
} 
})(unLoadedUris[i]) 
} 
function cb(module) { 
// 更改模块状态为READY,当remain为0时表示模块依赖都已经下完,那么执行callback 
(module || {}).status < STATUS.READY && (module.status = STATUS.READY) 
--remain === 0 && callback() 
} 
} 

Module.prototype._compile = function() { 
var module = this 
// 如果该模块已经编译过,则直接返回module.exports 
if (module.status === STATUS.COMPILED) { 
return module.exports 
} 
// Just return null when: 
// 1. the module file is 404. 
// 2. the module file is not written with valid module format. 
// 3. other error cases. 
// 这里是处理一些异常情况,此时直接返回null 
if (module.status < STATUS.SAVED && !hasModifiers(module)) { 
return null 
} 
// 更改模块状态为COMPILING,表示模块正在编译 
module.status = STATUS.COMPILING 
// 模块内部使用,是一个方法,用来获取其他模块提供(称之为子模块)的接口,同步操作 
function require(id) { 
// 根据id解析模块的路径 
var uri = resolve(id, module.uri) 
// 从模块缓存中获取模块(注意,其实这里子模块作为主模块的依赖项是已经被下载下来的) 
var child = cachedModules[uri] 
// Just return null when uri is invalid. 
// 如果child为空,只能表示参数填写出错导致uri不正确,那么直接返回null 
if (!child) { 
return null 
} 
// Avoids circular calls. 
// 如果子模块的状态为STATUS.COMPILING,直接返回child.exports,避免因为循环依赖反复编译模块 
if (child.status === STATUS.COMPILING) { 
return child.exports 
} 
// 指向初始化时调用当前模块的模块。根据该属性,可以得到模块初始化时的Call Stack. 
child.parent = module 
// 返回编译过的child的module.exports 
return child._compile() 
} 
// 模块内部使用,用来异步加载模块,并在加载完成后执行指定回调。 
require.async = function(ids, callback) { 
module._use(ids, callback) 
} 
// 使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。 
require.resolve = function(id) { 
return resolve(id, module.uri) 
} 
// 通过该属性,可以查看到模块系统加载过的所有模块。 
// 在某些情况下,如果需要重新加载某个模块,可以得到该模块的 uri, 然后通过 delete require.cache[uri] 来将其信息删除掉。这样下次使用时,就会重新获取。 
require.cache = cachedModules 
// require是一个方法,用来获取其他模块提供的接口。 
module.require = require 
// exports是一个对象,用来向外提供模块接口。 
module.exports = {} 
var factory = module.factory 
// factory 为函数时,表示模块的构造方法。执行该方法,可以得到模块向外提供的接口。 
if (util.isFunction(factory)) { 
compileStack.push(module) 
runInModuleContext(factory, module) 
compileStack.pop() 
} 
// factory 为对象、字符串等非函数类型时,表示模块的接口就是该对象、字符串等值。 
// 如:define({ "foo": "bar" }); 
// 如:define('I am a template. My name is {{name}}.'); 
else if (factory !== undefined) { 
module.exports = factory 
} 
// 更改模块状态为COMPILED,表示模块已编译 
module.status = STATUS.COMPILED 
// 执行模块接口修改,通过seajs.modify() 
execModifiers(module) 
return module.exports 
} 

Module._define = function(id, deps, factory) { 
var argsLength = arguments.length 
// 根据传入的参数个数,进行参数匹配 
// define(factory) 
// 一个参数的情况: 
// id : undefined 
// deps : undefined(后面会根据正则取出依赖模块列表) 
// factory : function 
if (argsLength === 1) { 
factory = id 
id = undefined 
} 
// define(id || deps, factory) 
// 两个参数的情况: 
else if (argsLength === 2) { 
// 默认情况下 :define(id, factory) 
// id : '...' 
// deps : undefined 
// factory : function 
factory = deps 
deps = undefined 
// define(deps, factory) 
// 如果第一个参数为数组 :define(deps, factory) 
// id : undefined 
// deps : [...] 
// factory : function 
if (util.isArray(id)) { 
deps = id 
id = undefined 
} 
} 
// Parses dependencies. 
// 如果deps不是数组(即deps未指定值),那么通过正则表达式解析依赖 
if (!util.isArray(deps) && util.isFunction(factory)) { 
deps = util.parseDependencies(factory.toString()) 
} 
// 元信息,之后会将信息传递给对应的module对象中 
var meta = { id: id, dependencies: deps, factory: factory } 
var derivedUri 
// Try to derive uri in IE6-9 for anonymous modules. 
// 对于IE6-9,尝试通过interactive script获取模块的uri 
if (document.attachEvent) { 
// Try to get the current script. 
// 获取当前的script 
var script = util.getCurrentScript() 
if (script) { 
// 将当前script的url进行unpareseMap操作,与模块缓存中key保持一致 
derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script)) 
} 
if (!derivedUri) { 
util.log('Failed to derive URI from interactive script for:', 
factory.toString(), 'warn') 
// NOTE: If the id-deriving methods above is failed, then falls back 
// to use onload event to get the uri. 
} 
} 
// Gets uri directly for specific module. 
// 如果给定id,那么根据id解析路径 
// 显然如果没指定id: 
// 对于非IE浏览器而言,则返回undefined(derivedUri为空) 
// 对于IE浏览器则返回CurrentScript的src 
// 如果指定id: 
// 则均返回有seajs解析(resolve)过的路径url 
var resolvedUri = id ? resolve(id) : derivedUri 
// uri存在的情况,进行模块信息存储 
if (resolvedUri) { 
// For IE: 
// If the first module in a package is not the cachedModules[derivedUri] 
// self, it should assign to the correct module when found. 
if (resolvedUri === derivedUri) { 
var refModule = cachedModules[derivedUri] 
if (refModule && refModule.realUri && 
refModule.status === STATUS.SAVED) { 
cachedModules[derivedUri] = null 
} 
} 
// 存储模块信息 
var module = save(resolvedUri, meta) 
// For IE: 
// Assigns the first module in package to cachedModules[derivedUrl] 
if (derivedUri) { 
// cachedModules[derivedUri] may be undefined in combo case. 
if ((cachedModules[derivedUri] || {}).status === STATUS.FETCHING) { 
cachedModules[derivedUri] = module 
module.realUri = derivedUri 
} 
} 
else { 
// 将第一个模块存储到firstModuleInPackage 
firstModuleInPackage || (firstModuleInPackage = module) 
} 
} 
// uri不存在的情况,在onload回调中进行模块信息存储,那里有个闭包 
else { 
// Saves information for "memoizing" work in the onload event. 
// 因为此时的uri不知道,所以将元信息暂时存储在anonymousModuleMeta中,在onload回调中进行模块save操作 
anonymousModuleMeta = meta 
} 
} 
// 获取正在编译的模块 
Module._getCompilingModule = function() { 
return compileStack[compileStack.length - 1] 
} 
// 从seajs.cache中快速查看和获取已加载的模块接口,返回值是module.exports数组 
// selector 支持字符串和正则表达式 
Module._find = function(selector) { 
var matches = [] 
util.forEach(util.keys(cachedModules), function(uri) { 
if (util.isString(selector) && uri.indexOf(selector) > -1 || 
util.isRegExp(selector) && selector.test(uri)) { 
var module = cachedModules[uri] 
module.exports && matches.push(module.exports) 
} 
}) 
return matches 
} 
// 修改模块接口 
Module._modify = function(id, modifier) { 
var uri = resolve(id) 
var module = cachedModules[uri] 
// 如果模块存在,并且处于COMPILED状态,那么执行修改接口操作 
if (module && module.status === STATUS.COMPILED) { 
runInModuleContext(modifier, module) 
} 
// 否则放入修改接口缓存中 
else { 
cachedModifiers[uri] || (cachedModifiers[uri] = []) 
cachedModifiers[uri].push(modifier) 
} 
return seajs 
} 

// For plugin developers 
Module.STATUS = STATUS 
Module._resolve = util.id2Uri 
Module._fetch = util.fetch 
Module.cache = cachedModules 

// Helpers 
// ------- 
// 正在下载的模块列表 
var fetchingList = {} 
// 已下载的模块列表 
var fetchedList = {} 
// 回调函数列表 
var callbackList = {} 
// 匿名模块元信息 
var anonymousModuleMeta = null 
var firstModuleInPackage = null 
// 循环依赖栈 
var circularCheckStack = [] 
// 批量解析模块的路径 
function resolve(ids, refUri) { 
if (util.isString(ids)) { 
return Module._resolve(ids, refUri) 
} 
return util.map(ids, function(id) { 
return resolve(id, refUri) 
}) 
} 
function fetch(uri, callback) { 
// fetch时,首先将uri按map规则转换 
var requestUri = util.parseMap(uri) 
// 在fethedList(已下载的模块列表)中查找,有的话,直接返回,并执行回调函数 
// TODO : 为什么这一步,fetchedList可能会存在该模? 
if (fetchedList[requestUri]) { 
// See test/issues/debug-using-map 
cachedModules[uri] = cachedModules[requestUri] 
callback() 
return 
} 
// 在fetchingList(正在在下载的模块列表)中查找,有的话,只需添加回调函数到列表中去,然后直接返回 
if (fetchingList[requestUri]) { 
callbackList[requestUri].push(callback) 
return 
} 
// 如果走到这一步,表示该模块是第一次被请求, 
// 那么在fetchingList插入该模块的信息,表示该模块已经处于下载列表中,并初始化该模块对应的回调函数列表 
fetchingList[requestUri] = true 
callbackList[requestUri] = [callback] 
// Fetches it 
// 获取该模块,即发起请求 
Module._fetch( 
requestUri, 
function() { 
// 在fetchedList插入该模块的信息,表示该模块已经下载完成 
fetchedList[requestUri] = true 
// Updates module status 
var module = cachedModules[uri] 
// 此时status可能为STATUS.SAVED,之前在_define中已经说过 
if (module.status === STATUS.FETCHING) { 
module.status = STATUS.FETCHED 
} 
// Saves anonymous module meta data 
// 因为是匿名模块(此时通过闭包获取到uri,在这里存储模块信息) 
// 并将anonymousModuleMeta置为空 
if (anonymousModuleMeta) { 
save(uri, anonymousModuleMeta) 
anonymousModuleMeta = null 
} 
// Assigns the first module in package to cachedModules[uri] 
// See: test/issues/un-correspondence 
if (firstModuleInPackage && module.status === STATUS.FETCHED) { 
cachedModules[uri] = firstModuleInPackage 
firstModuleInPackage.realUri = uri 
} 
firstModuleInPackage = null 
// Clears 
// 在fetchingList清除模块信息,因为已经该模块fetched并save 
if (fetchingList[requestUri]) { 
delete fetchingList[requestUri] 
} 
// Calls callbackList 
// 依次调用回调函数,并清除回调函数列表 
if (callbackList[requestUri]) { 
util.forEach(callbackList[requestUri], function(fn) { 
fn() 
}) 
delete callbackList[requestUri] 
} 
}, 
config.charset 
) 
} 
function save(uri, meta) { 
var module = cachedModules[uri] || (cachedModules[uri] = new Module(uri)) 
// Don't override already saved module 
// 此时status可能有两个状态: 
// STATUS.FETCHING,在define里面调用(指定了id),存储模块信息 
// STATUS.FETCHED,在onload的回调函数里调用,存储模块信息 
if (module.status < STATUS.SAVED) { 
// Lets anonymous module id equal to its uri 
// 匿名模块(即没有指定id),用它的uri作为id 
module.id = meta.id || uri 
// 将依赖项(数组)解析成的绝对路径,存储到模块信息中 
module.dependencies = resolve( 
util.filter(meta.dependencies || [], function(dep) { 
return !!dep 
}), uri) 
// 存储factory(要执行的模块代码,也可能是对象或者字符串等) 
module.factory = meta.factory 
// Updates module status 
// 更新模块状态为SAVED,(注意此时它只是拥有了依赖项,还未全部下载下来(即还未READY)) 
module.status = STATUS.SAVED 
} 
return module 
} 
// 根据模块上下文执行模块代码 
function runInModuleContext(fn, module) { 
// 传入与模块相关的两个参数以及模块自身 
// exports用来暴露接口 
// require用来获取依赖模块(同步)(编译) 
var ret = fn(module.require, module.exports, module) 
// 支持返回值暴露接口形式,如: 
// return { 
// fn1 : xx 
// ,fn2 : xx 
// ... 
// } 
if (ret !== undefined) { 
module.exports = ret 
} 
} 
// 判断模块是否存在接口修改 
function hasModifiers(module) { 
return !!cachedModifiers[module.realUri || module.uri] 
} 
// 修改模块接口 
function execModifiers(module) { 
var uri = module.realUri || module.uri 
var modifiers = cachedModifiers[uri] 
// 内部变量 cachedModifiers 就是用来存储用户通过 seajs.modify 方法定义的修改点 
// 查看该uri是否又被modify更改过 
if (modifiers) { 
// 对修改点统一执行factory,返回修改后的module.exports 
util.forEach(modifiers, function(modifier) { 
runInModuleContext(modifier, module) 
}) 
// 删除 modify 方法定义的修改点 ,避免再次执行 
delete cachedModifiers[uri] 
} 
} 
//获取纯粹的依赖关系,得到不存在循环依赖关系的依赖数组 
function getPureDependencies(module) { 
var uri = module.uri 
// 对每个依赖项进行过滤,对于有可能形成循环依赖的进行剔除,并打印出警告日志 
return util.filter(module.dependencies, function(dep) { 
// 首先将被检查模块的uri放到循环依赖检查栈中,之后的检查会用到 
circularCheckStack = [uri] 
//接下来检查模块uri是否和其依赖的模块存在循环依赖 
var isCircular = isCircularWaiting(cachedModules[dep]) 
if (isCircular) { 
// 如果循环,则将uri放到循环依赖检查栈中 
circularCheckStack.push(uri) 
// 打印出循环警告日志 
printCircularLog(circularCheckStack) 
} 
return !isCircular 
}) 
} 
function isCircularWaiting(module) { 
// 如果依赖模块不存在,那么返回false,因为此时也无法获得依赖模块的依赖项,所以这里无法做判断 
// 或者如果模块的状态值等于saved,也返回false,因为模块状态为saved的时候代表该模块的信息已经有了, 
// 所以尽管形成了循环依赖,但是require主模块时,同样可以正常编译,返回主模块接口(好像nodejs会返回undefined) 
if (!module || module.status !== STATUS.SAVED) { 
return false 
} 
// 如果不是以上的情况,那么将依赖模块的uri放到循环依赖检查栈中,之后的检查会用到 
circularCheckStack.push(module.uri) 
// 再次取依赖模块的依赖模块 
var deps = module.dependencies 
if (deps.length) { 
// 通过循环依赖检查栈,检查是否存在循环依赖(这里是第一层依赖模块检查,与主模块循环依赖的情况) 
if (isOverlap(deps, circularCheckStack)) { 
return true 
} 
// 如果不存在上述情形,那么进一步查看,依赖模块的依赖模块,查看他们是否存在对循环依赖检查栈中的uri的模块存在循环依赖 
// 这样的话,就递归了,循环依赖检查栈就像形成的一条链,当前模块依次对主模块,主模块的主模块...直到最顶上的主模块,依次进行判断是否存在依赖 
for (var i = 0; i < deps.length; i++) { 
if (isCircularWaiting(cachedModules[deps[i]])) { 
return true 
} 
} 
} 
// 如果不存在循环依赖,那么pop出之前已经push进的模块uri,并返回false 
circularCheckStack.pop() 
return false 
} 
// 打印出循环警告日志 
function printCircularLog(stack, type) { 
util.log('Found circular dependencies:', stack.join(' --> '), type) 
} 
//判断两个数组是否有重复的值 
function isOverlap(arrA, arrB) { 
var arrC = arrA.concat(arrB) 
return arrC.length > util.unique(arrC).length 
} 
// 从配置文件读取是否有需要提前加载的模块 
// 如果有预先加载模块,首先设置预加载模块为空(保证下次不必重复加载),并加载预加载模块并执行回调,如果没有则顺序执行 
function preload(callback) { 
var preloadMods = config.preload.slice() 
config.preload = [] 
preloadMods.length ? globalModule._use(preloadMods, callback) : callback() 
} 

// Public API 
// 对外暴露的API 
// ---------- 
// 全局模块,可以认为是页面模块,页面中的js,css文件都是通过它来载入的 
// 模块初始状态就是COMPILED,uri就是页面的uri 
var globalModule = new Module(util.pageUri, STATUS.COMPILED) 
// 页面js,css文件加载器 
seajs.use = function(ids, callback) { 
// Loads preload modules before all other modules. 
// 预加载模块 
preload(function() { 
globalModule._use(ids, callback) 
}) 
// Chain 
return seajs 
} 

// For normal users 
// 供普通用户调用 
seajs.define = Module._define 
seajs.cache = Module.cache 
seajs.find = Module._find 
seajs.modify = Module._modify 

// For plugin developers 
// 供开发者使用 
seajs.pluginSDK = { 
Module: Module, 
util: util, 
config: config 
} 
})(seajs, seajs._util, seajs._config)
Javascript 相关文章推荐
用Javascript数组处理多个字符串的连接问题
Aug 20 Javascript
js 遍历对象的属性的代码
Dec 29 Javascript
javascript中的nextSibling使用陷(da)阱(keng)
May 05 Javascript
省市区三级联动下拉框菜单javascript版
Aug 11 Javascript
xmlplus组件设计系列之文本框(TextBox)(3)
May 03 Javascript
jQuery UI Draggable + Sortable 结合使用(实例讲解)
Sep 07 jQuery
vue2.0 常用的 UI 库实例讲解
Dec 12 Javascript
用JS实现根据当前时间随机生成流水号或者订单号
May 31 Javascript
JS实现前端页面的搜索功能
Jun 12 Javascript
vue 实现数字滚动增加效果的实例代码
Jul 06 Javascript
详解Vue CLI3配置解析之css.extract
Sep 14 Javascript
记一次用ts+vuecli4重构项目的实现
May 21 Javascript
Javascript引用指针使用介绍
Nov 07 #Javascript
JavaScript在多浏览器下for循环的使用方法
Nov 07 #Javascript
Javascript的数组与字典用法与遍历对象的属性技巧
Nov 07 #Javascript
JS正则中的RegExp对象对象
Nov 07 #Javascript
js模拟点击事件实现代码
Nov 06 #Javascript
js判断变量是否未定义的代码
Mar 28 #Javascript
jquery判断浏览器类型的代码
Nov 05 #Javascript
You might like
上海无线电三厂简史修改版
2021/03/01 无线电
php随机显示指定文件夹下图片的方法
2015/07/13 PHP
php官方微信接口大全(微信支付、微信红包、微信摇一摇、微信小店)
2015/12/21 PHP
PHP实现清除MySQL死连接的方法
2016/07/23 PHP
PHP机器学习库php-ml的简单测试和使用方法
2017/07/14 PHP
Ajax+PHP实现的分类列表框功能示例
2019/02/11 PHP
JavaScript与Div对层定位和移动获得坐标的实现代码
2010/09/08 Javascript
js jq 单击和双击区分示例介绍
2013/11/05 Javascript
JavaScript开发者必备的10个Sublime Text插件
2016/02/27 Javascript
百度地图给map添加右键菜单(判断是否为marker)
2016/03/04 Javascript
JS简单实现禁止访问某个页面的方法
2016/09/13 Javascript
Bootstrap整体框架之JavaScript插件架构
2016/12/15 Javascript
京东优选小程序的实现代码示例
2020/02/25 Javascript
js实现盒子移动动画效果
2020/08/09 Javascript
详解JavaScript中的数据类型,以及检测数据类型的方法
2020/09/17 Javascript
[05:48]DOTA2英雄梦之声vol21 屠夫
2014/06/20 DOTA
[02:30]联想杯DOTA2完美世界全国高校联赛—北京站现场
2015/11/16 DOTA
[46:28]EG vs Liquid 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.23
2019/09/05 DOTA
python的id()函数解密过程
2012/12/25 Python
python snownlp情感分析简易demo(分享)
2017/06/04 Python
详解Python odoo中嵌入html简单的分页功能
2019/05/29 Python
wxpython+pymysql实现用户登陆功能
2019/11/19 Python
python对execl 处理操作代码
2020/06/22 Python
Python爬虫模拟登陆哔哩哔哩(bilibili)并突破点选验证码功能
2020/12/21 Python
HTML+CSS3 模仿Windows7 桌面效果
2010/06/17 HTML / CSS
英语国培研修感言
2014/02/13 职场文书
求职简历自我评价范例
2014/03/12 职场文书
《每逢佳节倍思亲》教后反思
2014/04/19 职场文书
运动会宣传口号
2014/06/09 职场文书
世界读书日的活动方案
2014/08/20 职场文书
合伙经营协议书范本(通用版)
2014/12/03 职场文书
优秀共产党员事迹材料
2014/12/18 职场文书
高一地理教学工作总结
2015/08/12 职场文书
九年级化学教学反思
2016/02/22 职场文书
2016年感恩节活动总结大全
2016/04/01 职场文书
详解python中[-1]、[:-1]、[::-1]、[n::-1]使用方法
2021/04/25 Python