侧边栏
requirejs源码分析
发布于 | 分类于 源码分析
RequireJS 是一个实现了 AMD (Asynchronous Module Definition) 规范的 JavaScript 模块加载器,由于目前参与的项目使用了类似于requirejs的运行时微前端架构,需要了解一下整体的原理。
本文将从原理、架构和代码实现三个层面深入剖析 RequireJS 2.3.7 版本的源码。
核心原理
AMD 规范
RequireJS 实现了 AMD 规范,其核心思想是:
- 异步加载:模块以异步方式加载,不阻塞页面渲染
- 依赖前置:在模块定义时声明依赖,加载器负责解析和加载
- 模块化封装:每个模块有独立的作用域,通过
define定义,通过require使用
模块加载流程
mermaid
sequenceDiagram
participant User as 用户代码
participant Req as require()
participant Ctx as Context
participant Mod as Module
participant Loader as Script Loader
participant Script as 脚本文件
User->>Req: require(['moduleA'], callback)
Req->>Ctx: context.require(deps, callback)
Ctx->>Mod: getModule(moduleMap)
Mod->>Mod: init(deps, factory)
Mod->>Mod: enable()
loop 每个依赖
Mod->>Ctx: enable(depMap)
Ctx->>Mod: 创建依赖模块
Mod->>Mod: fetch()
Mod->>Loader: load(id, url)
Loader->>Script: 创建 script 标签
Script-->>Loader: onload 事件
Loader->>Script: define(name, deps, factory)
Script->>Ctx: defQueue.push([name, deps, factory])
end
Ctx->>Ctx: intakeDefines()
Ctx->>Mod: check()
alt 依赖全部加载
Mod->>Mod: execCb(factory, depExports)
Mod->>Ctx: defined[id] = exports
Mod->>Mod: emit('defined', exports)
Mod->>User: callback(exports)
else 依赖未完成
Mod->>Mod: 等待依赖
end循环依赖处理
RequireJS 通过 exports 提前暴露机制解决循环依赖:
- 模块 A 依赖模块 B,模块 B 依赖模块 A
- 当 A 开始加载时,创建
exports对象 - B 加载时可以获取到 A 的
exports引用(虽然此时可能未完全初始化) - 通过
breakCycle函数检测并打破循环
循环依赖处理流程
mermaid
graph TD
A[模块 A 开始加载] --> B[创建 A 的 exports 对象]
B --> C[A 依赖模块 B]
C --> D[开始加载模块 B]
D --> E[B 依赖模块 A]
E --> F{A 是否在 traced 中?}
F -->|是| G[检测到循环依赖]
F -->|否| H[继续递归检查]
G --> I[使用 A 的 exports]
I --> J[B 完成定义]
J --> K[A 获取 B 的 exports]
K --> L[A 完成定义]
style G fill:#f96
style I fill:#9f6架构设计
整体架构
RequireJS 采用闭包模式,整个库包裹在一个 IIFE 中:
javascript
(function (global, setTimeout) {
var requirejs, require, define;
// 核心实现
}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));架构层次图
mermaid
graph TB
subgraph "全局 API"
A[requirejs/require]
B[define]
C[requirejs.config]
end
subgraph "Context 层"
D[Context Manager]
E[Default Context]
F[Custom Context]
end
subgraph "模块管理层"
G[Module Registry]
H[Enabled Registry]
I[Defined Modules]
J[DefQueue]
end
subgraph "模块实例层"
K[Module Instance]
L[ModuleMap]
M[Dependencies]
end
subgraph "加载层"
N[Script Loader]
O[Plugin System]
P[Event System]
end
A --> D
B --> D
C --> D
D --> E
D --> F
E --> G
E --> H
E --> I
E --> J
G --> K
K --> L
K --> M
K --> N
K --> O
K --> P核心组件
Context(上下文)
每个 Context 是一个独立的模块加载环境,包含:
- config: 配置对象(baseUrl, paths, shim 等)
- registry: 所有模块的注册表
- defined: 已定义完成的模块
- enabledRegistry: 已启用的模块
- defQueue: 定义队列
多个 Context 可以共存,通过 contexts 对象管理:
javascript
contexts = {
'_': defaultContext, // 默认上下文
'custom': customContext
}Context 结构图
mermaid
classDiagram
class Context {
+Object config
+Object registry
+Object defined
+Object enabledRegistry
+Array defQueue
+Object defQueueMap
+configure(cfg)
+require(deps, callback, errback)
+makeRequire(relMap, options)
+enable(depMap, mod)
+completeLoad(moduleName)
+nameToUrl(moduleName, ext)
+load(id, url)
}
class Module {
+Object map
+Array depMaps
+Array depExports
+Function factory
+Boolean enabled
+Boolean defined
+init(depMaps, factory, errback)
+enable()
+fetch()
+check()
+callPlugin()
}
class ModuleMap {
+String prefix
+String name
+String id
+String url
+Boolean isDefine
+Object parentMap
}
Context "1" --> "*" Module : manages
Module "1" --> "1" ModuleMap : has
Module "1" --> "*" Module : depends onModule(模块)
Module 是模块的抽象表示,核心属性和方法:
javascript
Module.prototype = {
init: function(depMaps, factory, errback, options) {},
enable: function() {},
fetch: function() {},
check: function() {},
callPlugin: function() {}
};ModuleMap(模块映射)
存储模块的元信息,包括插件前缀、模块名、URL 等。
配置系统
javascript
config = {
baseUrl: './', // 基础路径
paths: {}, // 路径映射
bundles: {}, // 打包配置
shim: {}, // 非 AMD 模块配置
map: {}, // 模块映射
config: {}, // 模块特定配置
waitSeconds: 7, // 超时时间
packages: [] // 包配置
}配置处理流程
mermaid
flowchart TD
A[requirejs.config] --> B{配置类型}
B -->|baseUrl| C[规范化 baseUrl<br/>确保以 / 结尾]
B -->|paths| D[路径映射配置<br/>支持数组 fallback]
B -->|shim| E[非 AMD 模块配置<br/>deps + exports]
B -->|map| F[模块映射配置<br/>不同模块使用不同版本]
B -->|bundles| G[打包配置<br/>多模块合并]
B -->|packages| H[包配置<br/>main 入口]
C --> I[合并到 context.config]
D --> I
E --> I
F --> I
G --> I
H --> I
I --> J[更新已注册模块的 map]
J --> K[配置生效]核心代码实现
模块名称规范化
normalize 函数负责将相对路径转换为绝对路径:
javascript
function normalize(name, baseName, applyMap) {
// 1. 处理相对路径 (./ 和 ../)
if (name && name[0] === '.') {
// 基于 baseName 解析相对路径
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
// 2. 去除路径中的 . 和 ..
trimDots(name);
// 3. 应用 map 配置
if (applyMap && map) {
// 查找最长匹配的映射规则
}
// 4. 处理 packages 配置
pkgMain = getOwn(config.pkgs, name);
return pkgMain ? pkgMain : name;
}模块加载机制
脚本加载(浏览器环境)
javascript
req.load = function (context, moduleName, url) {
// 创建 script 标签
node = req.createNode(config, moduleName, url);
// 设置属性
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
// 绑定事件监听器
if (node.attachEvent && !isOpera) {
// IE 6-8 使用 attachEvent
useInteractive = true;
node.attachEvent('onreadystatechange', context.onScriptLoad);
} else {
// 现代浏览器使用 addEventListener
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
}
node.src = url;
head.appendChild(node);
};Web Worker 环境
javascript
if (isWebWorker) {
setTimeout(function() {}, 0); // 避免 WebKit GC 问题
importScripts(url);
context.completeLoad(moduleName);
}define 函数实现
define 参数处理流程
mermaid
flowchart TD
A[define 调用] --> B{参数类型检查}
B -->|name 非字符串| C[匿名模块<br/>name = null]
B -->|name 是字符串| D[具名模块]
C --> E{deps 是否为数组?}
D --> E
E -->|否| F[deps = null<br/>callback = deps]
E -->|是| G[保持 deps]
F --> H{callback 是函数?}
G --> I[准备就绪]
H -->|是| J[扫描函数体<br/>提取 require 调用]
H -->|否| I
J --> K[添加 require/exports/module<br/>到 deps]
K --> I
I --> L{useInteractive?}
L -->|是 IE 6-8| M[获取交互式脚本<br/>提取 name 和 context]
L -->|否| N[使用当前 context]
M --> O[推入 defQueue]
N --> O
O --> P[等待 onScriptLoad<br/>触发 intakeDefines]javascript
define = function (name, deps, callback) {
// 1. 参数归一化
if (typeof name !== 'string') {
callback = deps;
deps = name;
name = null; // 匿名模块
}
if (!isArray(deps)) {
callback = deps;
deps = null;
}
// 2. CommonJS 风格依赖检测
if (!deps && isFunction(callback)) {
deps = [];
callback.toString()
.replace(commentRegExp, '')
.replace(cjsRequireRegExp, function (match, dep) {
deps.push(dep);
});
// 添加 require, exports, module
deps = ['require', 'exports', 'module'].concat(deps);
}
// 3. IE 6-8 交互式脚本处理
if (useInteractive) {
node = currentlyAddingScript || getInteractiveScript();
if (node) {
if (!name) {
name = node.getAttribute('data-requiremodule');
}
context = contexts[node.getAttribute('data-requirecontext')];
}
}
// 4. 将定义推入队列
if (context) {
context.defQueue.push([name, deps, callback]);
context.defQueueMap[name] = true;
} else {
globalDefQueue.push([name, deps, callback]);
}
};
define.amd = { jQuery: true }; // AMD 标识require 函数实现
javascript
req = requirejs = function (deps, callback, errback, optional) {
var context, config, contextName = defContextName;
// 1. 参数解析(支持配置对象)
if (!isArray(deps) && typeof deps !== 'string') {
config = deps;
if (isArray(callback)) {
deps = callback;
callback = errback;
errback = optional;
} else {
deps = [];
}
}
// 2. 获取或创建 context
if (config && config.context) {
contextName = config.context;
}
context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
}
// 3. 应用配置
if (config) {
context.configure(config);
}
// 4. 执行 require
return context.require(deps, callback, errback);
};模块执行流程
Module.check() - 核心执行逻辑
mermaid
stateDiagram-v2
[*] --> 检查状态
检查状态 --> 未初始化: !inited
检查状态 --> 有错误: error
检查状态 --> 执行中: defining
检查状态 --> 准备执行: else
未初始化 --> 触发fetch: !defQueueMap[id]
未初始化 --> 等待: defQueueMap[id]
触发fetch --> [*]
有错误 --> 触发error事件
触发error事件 --> [*]
执行中 --> [*]: 避免重入
准备执行 --> 检查依赖: depCount < 1
准备执行 --> 等待依赖: depCount >= 1
等待依赖 --> [*]
检查依赖 --> 执行工厂函数: isFunction(factory)
检查依赖 --> 使用字面量: else
执行工厂函数 --> 处理返回值
使用字面量 --> 保存exports
处理返回值 --> 检查返回值: exports === undefined
检查返回值 --> 使用module.exports: cjsModule
检查返回值 --> 使用this.exports: usingExports
检查返回值 --> 保存exports: else
使用module.exports --> 保存exports
使用this.exports --> 保存exports
保存exports --> 标记已定义
标记已定义 --> 触发defined事件
触发defined事件 --> [*]javascript
check: function () {
if (!this.enabled || this.enabling) return;
var id = this.map.id,
depExports = this.depExports,
exports = this.exports,
factory = this.factory;
if (!this.inited) {
// 未初始化,触发 fetch
if (!hasProp(context.defQueueMap, id)) {
this.fetch();
}
} else if (this.error) {
// 有错误,触发 error 事件
this.emit('error', this.error);
} else if (!this.defining) {
this.defining = true;
// 依赖全部加载完成
if (this.depCount < 1 && !this.defined) {
if (isFunction(factory)) {
// 执行工厂函数
try {
exports = context.execCb(id, factory, depExports, exports);
} catch (e) {
err = e;
}
// 处理返回值
if (this.map.isDefine && exports === undefined) {
cjsModule = this.module;
if (cjsModule) {
exports = cjsModule.exports;
} else if (this.usingExports) {
exports = this.exports;
}
}
} else {
// 字面量值
exports = factory;
}
this.exports = exports;
// 标记为已定义
if (this.map.isDefine && !this.ignore) {
defined[id] = exports;
}
cleanRegistry(id);
this.defined = true;
}
this.defining = false;
if (this.defined && !this.defineEmitted) {
this.defineEmitted = true;
this.emit('defined', this.exports);
}
}
}依赖管理
Module.enable() - 启用模块及其依赖
mermaid
flowchart TD
A[enable 调用] --> B[标记 enabled = true]
B --> C[标记 enabling = true]
C --> D[遍历 depMaps]
D --> E{depMap 类型?}
E -->|字符串| F[转换为 ModuleMap]
E -->|对象| G[已是 ModuleMap]
F --> H{是特殊依赖?}
G --> H
H -->|require/exports/module| I[使用 handlers 处理]
H -->|普通依赖| J[depCount += 1]
I --> K[直接返回特殊对象]
K --> D
J --> L[监听 defined 事件]
L --> M[监听 error 事件]
M --> N{依赖已注册?}
N -->|是| O{依赖已启用?}
N -->|否| D
O -->|否| P[递归 enable 依赖]
O -->|是| D
P --> D
D --> Q[遍历完成]
Q --> R[启用插件依赖]
R --> S[enabling = false]
S --> T[调用 check]
T --> U[结束]javascript
enable: function () {
enabledRegistry[this.map.id] = this;
this.enabled = true;
this.enabling = true;
// 启用每个依赖
each(this.depMaps, bind(this, function (depMap, i) {
var id, mod, handler;
if (typeof depMap === 'string') {
// 转换为 depMap
depMap = makeModuleMap(depMap, this.map.parentMap, false, !this.skipMap);
this.depMaps[i] = depMap;
// 处理特殊依赖(require, exports, module)
handler = getOwn(handlers, depMap.id);
if (handler) {
this.depExports[i] = handler(this);
return;
}
this.depCount += 1;
// 监听依赖的 defined 事件
on(depMap, 'defined', bind(this, function (depExports) {
if (this.undefed) return;
this.defineDep(i, depExports);
this.check();
}));
// 错误处理
if (this.errback) {
on(depMap, 'error', bind(this, this.errback));
}
}
id = depMap.id;
mod = registry[id];
// 递归启用依赖
if (!hasProp(handlers, id) && mod && !mod.enabled) {
context.enable(depMap, this);
}
}));
this.enabling = false;
this.check();
}特殊依赖处理
javascript
handlers = {
'require': function (mod) {
return mod.require || (mod.require = context.makeRequire(mod.map));
},
'exports': function (mod) {
mod.usingExports = true;
if (mod.map.isDefine) {
return mod.exports || (mod.exports = defined[mod.map.id] = {});
}
},
'module': function (mod) {
return mod.module || (mod.module = {
id: mod.map.id,
uri: mod.map.url,
config: function () {
return getOwn(config.config, mod.map.id) || {};
},
exports: mod.exports || (mod.exports = {})
});
}
};循环依赖检测
javascript
function breakCycle(mod, traced, processed) {
var id = mod.map.id;
if (mod.error) {
mod.emit('error', mod.error);
} else {
traced[id] = true;
each(mod.depMaps, function (depMap, i) {
var depId = depMap.id,
dep = getOwn(registry, depId);
if (dep && !mod.depMatched[i] && !processed[depId]) {
if (getOwn(traced, depId)) {
// 检测到循环依赖
mod.defineDep(i, defined[depId]);
mod.check();
} else {
// 递归检查
breakCycle(dep, traced, processed);
}
}
});
processed[id] = true;
}
}超时检测
javascript
function checkLoaded() {
var waitInterval = config.waitSeconds * 1000,
expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
noLoads = [],
stillLoading = false;
if (inCheckLoaded) return;
inCheckLoaded = true;
// 检查所有启用的模块
eachProp(enabledRegistry, function (mod) {
if (!mod.enabled) return;
if (!mod.error) {
if (!mod.inited && expired) {
// 超时未加载
if (hasPathFallback(modId)) {
stillLoading = true;
} else {
noLoads.push(modId);
removeScript(modId);
}
} else if (!mod.inited && mod.fetched && map.isDefine) {
stillLoading = true;
}
}
});
if (expired && noLoads.length) {
// 抛出超时错误
err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
return onError(err);
}
// 检查循环依赖
if (needCycleCheck) {
each(reqCalls, function (mod) {
breakCycle(mod, {}, {});
});
}
// 继续等待
if ((!expired || usingPathFallback) && stillLoading) {
if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
checkLoadedTimeoutId = setTimeout(function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}, 50);
}
}
inCheckLoaded = false;
}插件系统
RequireJS 支持插件扩展,插件通过 ! 分隔符标识:
javascript
// 使用示例
require(['text!template.html'], function(html) {
// html 是模板内容
});插件加载流程图
mermaid
sequenceDiagram
participant Mod as Module
participant Plugin as Plugin Module
participant Loader as Plugin Loader
participant Resource as Resource
Mod->>Mod: callPlugin()
Mod->>Plugin: 加载插件模块<br/>(text, i18n 等)
activate Plugin
Plugin-->>Mod: plugin.normalize(name)
Mod->>Mod: 创建规范化的 ModuleMap
Mod->>Plugin: plugin.load(name, require, load, config)
Plugin->>Resource: 获取资源<br/>(AJAX, 文件读取等)
Resource-->>Plugin: 资源内容
alt 动态生成模块
Plugin->>Loader: load.fromText(code)
Loader->>Loader: req.exec(code)
Loader->>Mod: completeLoad()
else 直接返回
Plugin->>Loader: load(value)
Loader->>Mod: init([], function() { return value })
end
deactivate Plugin
Mod->>Mod: emit('defined', value)插件加载流程
javascript
callPlugin: function () {
var map = this.map,
id = map.id,
pluginMap = makeModuleMap(map.prefix);
// 监听插件加载完成
on(pluginMap, 'defined', bind(this, function (plugin) {
var load, normalizedMap, normalizedMod,
name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null;
// 如果插件支持 normalize,规范化资源名称
if (plugin.normalize) {
name = plugin.normalize(name, function (name) {
return normalize(name, parentName, true);
}) || '';
}
// 创建规范化的模块映射
normalizedMap = makeModuleMap(map.prefix + '!' + name,
this.map.parentMap, true);
// load 回调
load = bind(this, function (value) {
this.init([], function () { return value; }, null, {
enabled: true
});
});
load.error = bind(this, function (err) {
this.inited = true;
this.error = err;
onError(err);
});
// load.fromText 支持动态生成模块
load.fromText = bind(this, function (text, textAlt) {
var moduleName = map.name,
moduleMap = makeModuleMap(moduleName);
getModule(moduleMap);
try {
req.exec(text); // 执行动态代码
} catch (e) {
return onError(makeError('fromtexteval',
'fromText eval for ' + id + ' failed: ' + e, e, [id]));
}
context.completeLoad(moduleName);
localRequire([moduleName], load);
});
// 调用插件的 load 方法
plugin.load(map.name, localRequire, load, config);
}));
context.enable(pluginMap, this);
this.pluginMaps[pluginMap.id] = pluginMap;
}Shim 配置
Shim 用于加载非 AMD 模块(如传统的全局变量库):
javascript
requirejs.config({
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});Shim 处理逻辑
javascript
makeShimExports: function (value) {
function fn() {
var ret;
if (value.init) {
ret = value.init.apply(global, arguments);
}
return ret || (value.exports && getGlobal(value.exports));
}
return fn;
}
// 在 completeLoad 中处理 shim
if (shim) {
context.makeRequire(this.map, {
enableBuildCallback: true
})(shim.deps || [], bind(this, function () {
return map.prefix ? this.callPlugin() : this.load();
}));
}路径解析
nameToUrl - 模块名转 URL
mermaid
flowchart TD
A[nameToUrl 调用] --> B{是 package?}
B -->|是| C[使用 package main]
B -->|否| D{是 bundle?}
C --> D
D -->|是| E[使用 bundle ID]
D -->|否| F{是 URL 格式?}
E --> A
F -->|是<br/>含 :/ 或 .js| G[直接使用 + ext]
F -->|否| H[应用 paths 配置]
H --> I[分割模块名为数组]
I --> J[从长到短匹配 paths]
J --> K{找到匹配?}
K -->|是| L[替换路径前缀]
K -->|否| M[保持原样]
L --> N[拼接路径]
M --> N
N --> O{需要 .js 后缀?}
O -->|是| P[添加 .js]
O -->|否| Q[保持原样]
P --> R{需要 baseUrl?}
Q --> R
R -->|是| S[添加 baseUrl 前缀]
R -->|否| T[保持原样]
S --> U{有 urlArgs?}
T --> U
U -->|是| V[添加 urlArgs 参数]
U -->|否| W[返回 URL]
V --> Wjavascript
nameToUrl: function (moduleName, ext, skipExt) {
var paths, syms, i, parentModule, url, pkgMain;
// 1. 处理 packages
pkgMain = getOwn(config.pkgs, moduleName);
if (pkgMain) {
moduleName = pkgMain;
}
// 2. 处理 bundles
bundleId = getOwn(bundlesMap, moduleName);
if (bundleId) {
return context.nameToUrl(bundleId, ext, skipExt);
}
// 3. 如果是 URL 格式,直接返回
if (req.jsExtRegExp.test(moduleName)) {
url = moduleName + (ext || '');
} else {
// 4. 应用 paths 配置
paths = config.paths;
syms = moduleName.split('/');
for (i = syms.length; i > 0; i -= 1) {
parentModule = syms.slice(0, i).join('/');
parentPath = getOwn(paths, parentModule);
if (parentPath) {
if (isArray(parentPath)) {
parentPath = parentPath[0]; // 支持 fallback
}
syms.splice(0, i, parentPath);
break;
}
}
// 5. 拼接 URL
url = syms.join('/');
url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
}
// 6. 添加 urlArgs
return config.urlArgs && !/^blob\:/.test(url) ?
url + config.urlArgs(moduleName, url) : url;
}使用示例
基本使用
javascript
// 定义模块
define('moduleA', ['jquery'], function($) {
return {
init: function() {
$('body').append('<div>Module A</div>');
}
};
});
// 使用模块
require(['moduleA'], function(moduleA) {
moduleA.init();
});配置示例
javascript
requirejs.config({
baseUrl: '/js/lib',
paths: {
'jquery': 'jquery-3.6.0.min',
'backbone': 'backbone-1.4.0',
'underscore': 'underscore-1.13.1'
},
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
},
map: {
'*': {
'jquery': 'jquery-private'
},
'jquery-private': {
'jquery': 'jquery'
}
},
waitSeconds: 15
});插件使用
javascript
// 加载文本文件
require(['text!template.html'], function(template) {
document.body.innerHTML = template;
});
// 国际化
require(['i18n!nls/messages'], function(messages) {
console.log(messages.greeting);
});
// DOM 就绪
require(['domReady!'], function(doc) {
console.log('DOM ready');
});CommonJS 风格
javascript
define(function(require, exports, module) {
var $ = require('jquery');
var _ = require('underscore');
exports.helper = function() {
return _.map([1, 2, 3], function(n) {
return n * 2;
});
};
module.exports = {
helper: exports.helper
};
});总结
尽管 RequireJS 在现代前端开发中使用减少,但其源码仍有很高的学习价值:
- 模块化思想:理解模块化的本质和演进
- 异步编程:学习异步加载和依赖管理
- 兼容性处理:了解如何处理浏览器差异
- 架构设计:学习大型库的架构设计思路
- 代码质量:精简、高效的代码实现
参考:
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。
