以Mockjs核心功能解释策略模式
一、从模板到数据
Mock.js的核心功能是将特殊标记的语法转换为对应的模拟数据,也可以将其看作一种简易的DSL(Domain-Specific Language,领域特定语言)转换器。DSL并不是一种具体的语言,而是泛指任何针对指定领域的语言,它并不一定会有严格的标准,也可能仅仅是一种约定规范。在未来的前端开发中还会出现很多类似的任务,比如当下非常热门的“可视化搭建”技术,就是围绕DSL解析来实现的。Mock.js使用了一种非常简单的标记语法来描述数据的类型和结构,对于提供模拟业务数据这样的需求而言它已经够用了。阿里妈妈前端团队出品的开源接口管理工具Rap2[插图]在实现Mock功能时也使用了这样的语法。如果想要描述更复杂的数据结构,比如对表单项或是前端UI进行抽象描述,简易的模板语法可能会显得力不从心,此时就可以考虑使用更为通用的JSON Schema[插图]格式,如果希望构造出更加强大的模板语法,则还需要学习编译原理方面的知识。
以常见类型的语法解析为例来讲解从模板到数据的转换过程,示例代码如下:
//转换策略单例
Strategies = {
'String':(rule, value)=>{//...字符串类型的转换处理函数},
'Number':(rule, value)=>{//...数字类型的转换处理函数},
'Boolean':(rule, value)=>{//...布尔类型的转换处理函数},
'Array':(rule, value)=>{//...数组类型的转换处理函数},
'Placeholder':(rule, value)=>{//...占位符类型的转换处理函数}
//...其他类型的转换策略
}
//模板转换函数
function parseTemplate(schema = {}){
let result = {};
for ( let prop of Object.keys(schema)){
let [name, rule] = prop.split('|');
let value = tplObj[prop];
let type = value.startsWith('@')?
'Placeholder':
Object.prototype.toString.call(value).slice(8,-1);
result[name] = Strategies[type](rule, value);
}
return result;
}
上面的代码并不难理解,首先使用一个对象来封装模板中不同类型所对应的模板转换函数,模板转换函数的返回结果即所生成的虚拟数据,这是一个典型的策略模式应用场景。当你想要增加新的类型时,只需要将新的解析函数以键值对的形式加入策略(Strategies)对象中即可,不必修改旧的代码。这样我们只需要遍历模板对象中的每条规则并拆分出关键信息即可,如果初始值是以@开头的字符串,则需要映射为占位符,否则就直接根据值的类型来找到对应的转换函数,最后将拆分出的信息作为参数传入对应的转换函数中就可以得到模拟数据。
二、代码详细解释
转换策略单例:Strategies
Strategies = {
'String': (rule, value) => { /* 处理字符串类型的转换逻辑 */ },
'Number': (rule, value) => { /* 处理数字类型的转换逻辑 */ },
'Boolean': (rule, value) => { /* 处理布尔类型的转换逻辑 */ },
'Array': (rule, value) => { /* 处理数组类型的转换逻辑 */ },
'Placeholder': (rule, value) => { /* 处理占位符类型的转换逻辑 */ }
// ...其他类型的转换策略
}
这里的 Strategies
是一个 JavaScript 对象,它定义了多个策略函数,分别处理不同的数据类型(如 String
、Number
、Boolean
、Array
等)。这些函数会在后面的 parseTemplate
函数中根据数据的类型来调用,传入两个参数:
-
rule
: 一些与数据转换相关的规则。 -
value
: 需要转换的数据值。
通过将转换逻辑封装在 Strategies
对象中,代码实现了良好的扩展性和维护性。如果以后需要支持更多的数据类型,只需要向 Strategies
中添加新的策略函数即可。
模板转换函数:parseTemplate(schema = {})
这个函数负责将传入的 schema
对象中的数据根据不同的类型,利用上面定义的 Strategies
转换策略进行处理。schema
对象包含了待处理的数据和规则。
function parseTemplate(schema = {}) {
let result = {};
for ( let prop of Object.keys(schema)) {
let [name, rule] = prop.split('|');
let value = tplObj[prop];
let type = value.startsWith('@') ?
'Placeholder' :
Object.prototype.toString.call(value).slice(8, -1);
result[name] = Strategies[type](rule, value);
}
return result;
}
细节解释:
-
Object.keys(schema)
:-
Object.keys(schema)
返回schema
对象的所有属性名(即键)。这些属性名可能包含一个管道符号|
,表示属性名与规则的组合。 -
例如,
schema
对象可能是这样的:schema = { "age|number": 30, "name|string": "张三", "isStudent|boolean": false }
-
-
for (let prop of Object.keys(schema))
:- 通过
for...of
循环,逐个遍历schema
对象中的每个键(即属性名)。
- 通过
-
let [name, rule] = prop.split('|')
:-
通过
split('|')
把属性名(prop
)分成name
和rule
两个部分。假设prop
是"age|number"
,那么分解后:-
name
是"age"
-
rule
是"number"
-
-
-
let value = tplObj[prop]
:-
假设有一个
tplObj
对象,这行代码从中取出与prop
对应的值。这个tplObj
可能与schema
对应,比如:tplObj = { "age|number": 30, "name|string": "张三", "isStudent|boolean": false };
-
-
let type = value.startsWith('@') ? 'Placeholder' : Object.prototype.toString.call(value).slice(8, -1);
:-
这一行代码确定数据的类型:
- 如果
value
是以@
开头的字符串(如@user
),则它被认为是“占位符”类型,返回'Placeholder'
。 - 否则,使用
Object.prototype.toString.call(value)
来检测值的类型。slice(8, -1)
会从结果中提取出类型名。例如,[object String]
会被处理成'String'
,[object Number]
会被处理成'Number'
。
- 如果
-
-
result[name] = Strategies[type](rule, value);
:-
这行代码根据前面确定的
type
,从Strategies
中选择对应的处理函数,并执行这个函数。函数的两个参数分别是:-
rule
: 转换的规则(如'number'
、'string'
等) -
value
: 数据值
-
-
处理后的结果存储在
result[name]
中。
-
-
return result;
:- 最终,函数返回处理后的
result
对象。
- 最终,函数返回处理后的
示例,假设 schema
和 tplObj
如下:
schema = {
"age|number": 30,
"name|string": "张三",
"isStudent|boolean": false
};
tplObj = {
"age|number": 30,
"name|string": "张三",
"isStudent|boolean": false
};
在 parseTemplate(schema)
中:
-
Object.keys(schema)
返回["age|number", "name|string", "isStudent|boolean"]
。 -
对于
"age|number"
:-
prop.split('|')
结果是name = "age"
,rule = "number"
。 -
value = tplObj["age|number"]
即30
。 -
Object.prototype.toString.call(30).slice(8, -1)
结果是'Number'
,所以选择Strategies["Number"]
,并调用它处理30
。
-
-
这个过程依次处理
name|string
和isStudent|boolean
,将结果放入result
对象中。
最终返回的 result
可能类似:
{
age: 30, // 转换后的 age
name: "张三", // 转换后的 name
isStudent: false // 转换后的 isStudent
}