功能背景
maps提供了很多种模块功能,但是不能完全覆盖所有运营用户的需求,他们有可能需要自定义一些功能,比如从别人网站copy 一份代码等等。
原理分析
自定义前端代码,无非就是css、html 和js 三种语言。
- html 最简单了,使用zepto 直接
html()
方法带页面容器中。 - css 也可以使用
append()
方法插入到head
这个标签中。 - js 就不能直接使用
append
或者html
等方法实现运行时执行,js 本身语法提供了,诸如new Function
和eval
实现运行执行
功能实现
输入代码的窗口如下 这样就会让css、html、js混杂在一起,如果分辨出来相应的代码。

解析代码
如果从众多的代码中识别指定的代码,除了正则没有更好的办法了。但是如果用户没有一定规范混杂写代码,正则识别起来也会比较困,所以得有个规范约束它们。
所以我们要求,css 代码写在<style></style>
js 代码写在<script></scrpt>
,html 写在<html></html>
中 这样我就可以识别这个标签读到指定的代码。如下段代码。
parseCodeHtml:function (str) {
var styleCodeStr = '';
var styleResult;
var styleCodeReg = /<html>([^\r]+)<\/html>/igm;
while ((styleResult = styleCodeReg.exec(str)) !== null) {
styleCodeStr += RegExp.$1
}
this.getBoundingBox().find('.W_custom_1_container').html(styleCodeStr);
},
从上 面不难看出我使用了一个全局匹配,这样我可以读取到多段的html 标签,然后在融合成一个字符串styleCodeStr
。以下是其他语言的识别的正则表达式。
var styleCodeReg = /<script>([^\r]+)<\/script>/igm; //js 正则表达式
var styleCodeReg = /<style>([^\r]+)<\/style>/igm; //css 正则表达式
正如上面所讲,我们识别相应代码的字符串后,我们如何让指定的代码生效呢?
执行代码
html
如果是html 的代码 我们直接html()插入到dom 中
css
如果是css 呢。上面规划的是append 到head 标签中,那我们是不是有更加好的办法呢。
我们注意到styleSheet.insertRule
这个方法,这个方法具体是干什么的呢,就是一条一条将css 执行生效。
相关链接 https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule
方法一:
实现了一个版本,看起来还是比较复杂,大概的步骤就是用正则表达式一行一行的将css 语法提炼出来,然后使用inertrules执行。
缺点:运行了一段时间后发现了一个弊端就是,他识别一些选择器不全。容易导致错误,这个问题出现在我们用正则一行一行识别css语句的时候出现了问题,正则不够健全。在浏览器调试的时候,会被浏览器标记为浏览器默认样式,是不可更改了
parseCodeStyle: function (str) {
var styleCodeStr = '';
var styleCodeReg = /<style>([^\r]+)<\/style>/igm;
var styleResult;
while ((styleResult = styleCodeReg.exec(str)) !== null) {
styleCodeStr += RegExp.$1
}
var styleStr = [];
var styleReg = /([\.\#]?\w+[^{]+)\{([^}]*)\}/igm;
while ((styleResult = styleReg.exec(styleCodeStr)) !== null) {
var temp = [];
temp.push(RegExp.$1);
temp.push(RegExp.$2);
styleStr.push(temp)
}
this.addStylesheetRules(styleStr)
},
addStylesheetRules: function (rules) {
var styleEl = document.createElement('style'),
styleSheet;
document.head.appendChild(styleEl);
styleSheet = styleEl.sheet;
for (var i = 0, rl = rules.length; i < rl; i++) {
var j = 1, rule = rules[i], selector = rules[i][0], propStr = '';
if (Object.prototype.toString.call(rule[1][0]) === '[object Array]') {
rule = rule[1];
j = 0;
}
styleSheet.insertRule(selector + '{' + rule[1] + '}', styleSheet.cssRules.length);
}
}
方法二
将识别出来的style的字符串,用blob
生成二进制流,然后使用window.URL.createObjectURL
生成可连接的资源
目前看下来这个方法没有缺点,webpack 打包css 也是使用类似的原理。该方法能够不改变原来用户输入的内容、然后能够在浏览器产生缓存并且还能能够在开发工具中自由修改。
parseCodeStyle: function (str) {
var styleCodeStr = '';
var styleCodeReg = /<style>([^\r]+)<\/style>/igm;
var styleResult;
while ((styleResult = styleCodeReg.exec(str)) !== null) {
styleCodeStr += RegExp.$1
}
this.addStylesheetRules(styleCodeStr)
},
addStylesheetRules:function (rules) {
var url = window.URL.createObjectURL(new Blob([rules], {type: 'text\/css'}))
if($("#custom").size()==0) {
var style = document.createElement('link');
style.href = url;
style.rel = 'stylesheet';
style.type = 'text/css';
style.id = "custom";
document.getElementsByTagName('HEAD').item(0).appendChild(style);
}else{
$("#custom").attr('href',url)
}
}
js
js 部分就是使用eval
执行运行时的代码,
不过需要注意的eval 的时候的this
的作用域,以及如果报错了,如果忽略,不阻塞其他的js 代码执行。
parseCodeHtml:function (str) {
var styleCodeStr = '';
var styleResult;
var styleCodeReg = /<html>([^\r]+)<\/html>/igm;
while ((styleResult = styleCodeReg.exec(str)) !== null) {
styleCodeStr += RegExp.$1
}
this.getBoundingBox().find('.W_custom_1_container').html(styleCodeStr);
},
parseCodeScript:function (str) {
var styleCodeStr = '';
var styleResult;
var styleCodeReg = /<script>([^\r]+)<\/script>/igm;
while ((styleResult = styleCodeReg.exec(str)) !== null) {
styleCodeStr += RegExp.$1
}
try {
eval(styleCodeStr)
}catch (e){
common.msgShow(e.toString())
}
},
功能总结
讲到这里基本上maps 的自定义模块基本上算是讲完了。大家可以看出来自定义模块的功能的代码的优先级是比较高的。是可以覆盖项目本身的其他任何的代码,除了js 作用域被限制在 eval 当前的作用域。
目前这种策略有一定的安全性的问题的,eval 理论上是可以执行任何程序,所以如果有人放入了恶意的代码的话。所以这个策略还有很多改进的空间的。
Categories: code
发表评论