Skip to content

Instantly share code, notes, and snippets.

@supersha
Last active December 19, 2015 19:38
Show Gist options
  • Save supersha/6007327 to your computer and use it in GitHub Desktop.
Save supersha/6007327 to your computer and use it in GitHub Desktop.
检查页面不符合最佳实践的方面
//检查全部
inspect.check();
//检查指定的任务
inspect.check("check-meta-charset");
//检查多个任务
inspect.check(["check-meta-charset","check-doctype"]);
;(function(win){
var nativeSlice = Array.prototype.slice;
var util = {};
/* Get all stylesheet */
util.getAllStyleSheets = function(){
var rules = [],
sheets = nativeSlice.call(document.styleSheets),
inlineSheets = [],
externalSheets = [],
externalHrefs = [],
selectorTexts = [],
inlineCount = 0,
externalCount = 0;
if(!sheets.length){ return; }
sheets.forEach(function(item,index){
if(item.href){
externalCount++;
externalSheets.push(item);
externalHrefs.push(item.href);
}else{
inlineCount++;
inlineSheets.push(item);
}
if(item.cssRules){
nativeSlice.call(item.cssRules).forEach(function(it,i){
it.cssText && rules.push(it.cssText);
it.selectorText && selectorTexts.push(it.selectorText);
});
}
});
return {
inlineSheets : inlineSheets,
externalSheets : externalSheets,
inlineCount : inlineCount,
externalCount : externalCount,
externalHrefs : externalHrefs,
rules : rules,
sheets : sheets,
selectorTexts : selectorTexts,
count : sheets.length
}
}
/* Get all scripts */
util.getAllScripts = function(){
var scripts = nativeSlice.call(document.getElementsByTagName("script")),
inlineScripts = [],
inlineCount = 0,
externalScripts = [],
externalCount = 0,
externalSrcs = [];
scripts.forEach(function(item,index){
if(item.src){
externalCount++;
externalScripts.push(item);
externalSrcs.push(item.src);
}else{
inlineCount++;
inlineScripts.push(item);
}
});
return {
inlineCount : inlineCount,
inlineScripts : inlineScripts,
externalCount : externalCount,
externalScripts : externalScripts,
externalSrcs : externalSrcs,
scripts : scripts,
count : scripts.length
}
}
/* Get all elements by tag */
util.getAllElementsByTag = function(tag){
return function(){
var elements = nativeSlice.call(document.querySelectorAll(tag));
return {
count : elements.length,
elements : elements
}
}
}
/* Get all images, except of css background-image */
util.getAllImages = util.getAllElementsByTag("img");
/* Get all hyperlinks */
util.getAllHyperLinks = util.getAllElementsByTag("a");
/* Get all iframes */
util.getAllIframes = util.getAllElementsByTag("iframe");
/* Get all metas */
util.getAllMetas = util.getAllElementsByTag("meta");
/* wrap document.querySelectorAll */
util.query = util.q = function(selector,context){
context = context || document;
return nativeSlice.call(context.querySelectorAll(selector));
}
util.trim = function(str){
return str.replace(/^\s+|\s+$/,"");
}
util.toArray = function(obj){
if(!obj){ return obj; }
if(typeof obj !== "object"){ return [obj]; }
return nativeSlice.call(obj);
}
// Source: https://gist.github.com/cowboy/958000
util.walk = function(node, callback) {
var skip, tmp;
// This depth value will be incremented as the depth increases and
// decremented as the depth decreases. The depth of the initial node is 0.
var depth = 0;
// Always start with the initial element.
do {
if ( !skip ) {
// Call the passed callback in the context of node, passing in the
// current depth as the only argument. If the callback returns false,
// don't process any of the current node's children.
skip = callback.call(node, depth) === false;
}
if ( !skip && (tmp = node.firstChild) ) {
// If not skipping, get the first child. If there is a first child,
// increment the depth since traversing downwards.
depth++;
} else if ( tmp = node.nextSibling ) {
// If skipping or there is no first child, get the next sibling. If
// there is a next sibling, reset the skip flag.
skip = false;
} else {
// Skipped or no first child and no next sibling, so traverse upwards,
tmp = node.parentNode;
// and decrement the depth.
depth--;
// Enable skipping, so that in the next loop iteration, the children of
// the now-current node (parent node) aren't processed again.
skip = true;
}
// Instead of setting node explicitly in each conditional block, use the
// tmp var and set it here.
node = tmp;
// Stop if depth comes back to 0 (or goes below zero, in conditions where
// the passed node has neither children nore next siblings).
} while ( depth > 0 );
}
// Source: https://github.com/franzenzenhofer/parseUri
util.parseUri = function(str) {
var o = util.parseUri.options,
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
while (i--) uri[o.key[i]] = m[i] || "";
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) uri[o.q.name][$1] = $2;
});
return uri;
};
util.parseUri.options = {
strictMode: false,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
};
util.log = function(type){
return function(description,message){
description && console[type](description);
message && console.log(message);
}
}
util.log.error = util.log("error");
util.log.warn = util.log("warn");
/* inspect object */
var inspect = {};
inspect.rules = {};
inspect.addRule = function(name, initialize){
if(!name){ throw new Error("Rule name cannot be empty."); return; }
this.rules[name] = {
disabled : false,
initialize : initialize || function(){}
}
}
inspect.check = function(name){
if(name){
if(typeof name === "string"){
!this.rules[name]['disabled'] && this.rules[name]['initialize'].call(this,util);
return;
}else{
name.forEach(function(item,index){
!this.rules[item]['disabled'] && this.rules[item]['initialize'].call(this,util);
});
return;
}
}
for(var key in this.rules){
if(this.rules.hasOwnProperty(key)){
!this.rules[key]['disabled'] && this.rules[key]['initialize'].call(this,util);
}
}
}
inspect.getAllRules = function(){
return this.rules;
}
inspect.getRule = function(name){
return this.rules[name];
}
inspect.setDisabled = function(name){
this.rules[name].disabled = true;
}
win.inspect = inspect;
})(window);
//检查body标签内是否存在外联的样式
inspect.addRule("check-body-externallink",function(util){
var links = util.q("body link");
if(links.length){
util.log.warn("body标签内存在外联的CSS,影响页面渲染的速度",links);
}
});
//检查是否有重复ID的声明
inspect.addRule("check-repeat-ids",function(util){
var elementWithIds = util.q("[id]"),
temp = {},
repeatIds = [];
elementWithIds.forEach(function(item,index){
if(temp[item.id]) { repeatIds.push(item.id); return; }
temp[item.id] = true;
});
if(repeatIds.length){
util.log.warn("页面存在重复的id属性声明", repeatIds);
}
});
//head标签中是否使用了外联的JS脚本声明
inspect.addRule("check-head-externalscripts",function(util){
var headScripts = util.q("head script[src]");
if(headScripts.length){
util.log.warn("head标签内存在外联的JS脚本,影响加载速度",headScripts);
}
});
//是否设置viewport的meta
inspect.addRule("check-meta-viewport",function(util){
var metas = util.getAllMetas(),
has = false;
metas.elements.forEach(function(item,index){
if(item.name === "viewport"){
has = true;
}
});
if(!has){
util.log.warn("页面不存在viewport的meta声明,对mobile设备不友好");
}
});
//检测是否声明了charset,并且是否是HTML5的charset声明方式
inspect.addRule("check-meta-charset",function(util){
var metas = util.getAllMetas(),
hasCharset = false,
isHTML5Charset = false;
metas.elements.forEach(function(item,index){
if(item.getAttribute("charset")){
isHTML5Charset = true;
hasCharset = true;
}
if(item.getAttribute("content") && (item.getAttribute("content").indexOf("charset") !== -1)){
hasCharset = true;
}
});
if(!hasCharset){
util.log.error("页面不存在charset的meta声明,页面显示可能存在乱码");
}
if(hasCharset && !isHTML5Charset){
util.log.warn("页面声明了charset,但不是HTML5的charset声明方式");
}
});
//检测是否声明了doctype
inspect.addRule("check-doctype",function(util){
if(!document.doctype){
util.log.warn("页面没有声明doctype");
}
});
//检测页面是否有keyword和description的meta声明
inspect.addRule("check-keyword-description-meta", function(util){
var metas = util.getAllMetas(),
hasKeyword = false,
hasDescription = false;
metas.elements.forEach(function(item,index){
if(item.name === "keyword"){
hasKeyword = true;
}
if(item.name === "description"){
hasDescription = true;
}
});
if(!hasKeyword){
util.log.warn("页面缺少keyword的meta声明,对搜索引擎不友好");
}
if(!hasDescription){
util.log.warn("页面缺少description的meta声明,对搜索引擎不友好");
}
});
//检测页面是否使用了iframe
inspect.addRule("check-iframes", function(util){
var iframes = util.getAllIframes();
if(iframes.length){
util.log.warn("页面存在iframe,影响页面加载速度",iframes);
}
});
//检测页面外联css和js的个数
inspect.addRule("check-external-css-js-count", function(util){
var links = util.getAllStyleSheets(),
scripts = util.getAllScripts();
if(links && links.externalCount >= 3){
util.log.warn("页面外联的CSS超过了3个,是否该合并一下",links.externalHrefs);
}
if(scripts && scripts.externalCount >= 3){
util.log.warn("页面外联的JS超过了3个,是否该合并一下",scripts.externalSrcs);
}
});
//超链接的href是否使用了javascript:void(0)的声明
inspect.addRule("check-hyperlink-href", function(util){
var links = util.getAllHyperLinks(),
temp = [];
links.elements.forEach(function(item,index){
var href = item.getAttribute('href');
if(/^javascript:/.test(href)){
temp.push(item);
}
});
if(temp.length){
util.log.warn("页面超链接存在javascript:伪协议,IE6下可能存在问题",temp);
}
});
//超链接是否带有title标签声明
inspect.addRule("check-hyperlink-title",function(util){
var links = util.getAllHyperLinks(),
temp = [];
links.elements.forEach(function(item,index){
//如果不存在title标签,并且存在innerHTML,则提示
if(!item.getAttribute("title") && util.trim(item.innerHTML)){
temp.push(item);
}
});
if(temp.length){
util.log.warn("页面存在没有声明title属性的超链接,对屏幕阅读器不友好",temp);
}
});
//图片是否带有width和height声明,以及alt声明
inspect.addRule("check-hyperlink-title",function(util){
var images = util.getAllImages(),
altTemp = [],
widthHeightTemp = [];
images.elements.forEach(function(item,index){
if(!item.getAttribute("alt")){
altTemp.push(item);
}
if(!item.getAttribute("width") && !item.getAttribute("height")){
widthHeightTemp.push(item);
}
});
if(altTemp.length){
util.log.warn("页面存在没有声明alt的图片,对屏幕阅读器和加载失败后不友好",altTemp);
}
if(widthHeightTemp.length){
util.log.warn("页面存在没有声明width和height的图片,影响页面渲染速度",widthHeightTemp);
}
});
//检测style和script不需要声明type属性
inspect.addRule("check-style-script-type",function(util){
var scripts = util.q("script"),
styles = util.q("style"),
links = util.q("link[rel=stylesheet]"),
all = [].concat(scripts,styles,links),
temp = [];
all.forEach(function(item,index){
if(item.getAttribute("type")){
temp.push(item);
}
});
if(temp.length){
util.log.warn("页面中存在声明了type属性的样式和脚本标签",temp);
}
});
//检测静态文件的域名是否跟主域名不同
inspect.addRule("check-static-resource-domain", function(util){
var domain = window.location.host,
scripts = util.q("script[src]"),
links = util.q("link[rel=stylesheet]"),
images = util.q("img"),
all = [].concat(scripts,links,images),
temp = [];
all.forEach(function(item,index){
var url = item.href || item.src,
urlinfo = util.parseUri(url);
if(!urlinfo.host || (urlinfo.host == domain)){
temp.push(item);
}
});
if(temp.length){
util.log.warn("页面存在静态文件的域名跟主域名相同,影响页面加载速度",temp);
}
});
//页面是否使用了HTML5中废弃的标签(font等)
inspect.addRule("check-html5-abandon-tag",function(util){
var fonts = util.q("font");
if(fonts.length){
util.log.warn("页面存在HTML5中已声明废弃的font标签",fonts);
}
});
//样式写在了标签上
inspect.addRule("check-style-in-tag",function(util){
var styleInTags = [],
scriptInTags = [],
scriptHasEvents = [],
onEvents = ["onabort","onbeforecopy","onbeforecut","onbeforepaste","onblur","onchange","onclick","oncontextmenu","oncopy","oncut","ondblclick","ondrag","ondragend","ondragenter","ondragleave","ondragover","ondragstart","ondrop","onerror","onfocus","oninput","oninvalid","onkeydown","onkeypress","onkeyup","onload","onmousedown","onmousemove","onmouseout","onmouseover","onmouseup","onmousewheel","onpaste","onreset","onscroll","onsearch","onselect","onselectstart","onsubmit"];
util.walk(document.body,function(depth){
var elem = this,
attributes = util.toArray(elem.attributes),
hasEventAttribute = false;
if(attributes && (attributes.length > 0)){
attributes.forEach(function(item,index){
var nodeName = item.nodeName.toLowerCase();
if(nodeName === "style"){
styleInTags.push(elem);
}
if(onEvents.indexOf(nodeName) !== -1){
scriptInTags.push(elem);
hasEventAttribute = true;
}
});
}
if(!hasEventAttribute){
onEvents.forEach(function(item,index){
if(elem[item]){
scriptHasEvents.push(elem);
}
});
}
});
if(styleInTags.length){
util.log.warn("页面存在直接在标签中写style样式的Tag标签,影响页面渲染速度",styleInTags);
}
if(scriptInTags.length){
util.log.warn("页面存在直接在标签中写脚本的Tag标签,不符合脚本和标签分离的最佳实践",scriptInTags);
}
if(scriptHasEvents.length){
util.log.warn("页面存在在脚本中直接使用onxxx的方式给元素添加事件的逻辑",scriptHasEvents);
}
});
//页面没有使用到的样式|CSS背景图片没有使用data uri
inspect.addRule("check-nousage-style",function(util){
var allSheet = util.getAllStyleSheets(),
selectorTexts = allSheet && allSheet.selectorTexts || [],
rules = allSheet && allSheet.rules || [],
temp = [],
notBase64Backgrounds = [],
bgReg = /url\(([^\)]+)\)/,
base64Reg = /^data:/;
selectorTexts.forEach(function(item,index){
if(!document.querySelectorAll(item).length){
temp.push(item);
}
});
rules.forEach(function(item,index){
if(bgReg.test(item)){
if(!base64Reg.test(RegExp.$1)){
notBase64Backgrounds.push(item);
}
}
});
if(temp.length){
util.log.warn("页面存在没有使用到的样式,影响页面渲染速度",temp);
}
if(notBase64Backgrounds.length){
util.log.warn("页面存在CSS背景图片样式没有使用data uri的方式",notBase64Backgrounds);
}
});
//检测页面表单的id和name是否为同一个元素
inspect.addRule("check-id-and-name",function(util){
var elemHaveDifferenceIdAndNames = [],
forms = util.q("form"),
cacheObj = {};
forms.forEach(function(item,index){
var childrens = util.q("*",item);
childrens.forEach(function(it,idx){
var idOrName = it.id || it.getAttribute("name") || "";
if(cacheObj[idOrName]){ return; }
if(idOrName && (item[idOrName].length >= 2)){
elemHaveDifferenceIdAndNames.push(item[idOrName]);
cacheObj[idOrName] = 1;
}
});
});
if(elemHaveDifferenceIdAndNames.length){
util.log.warn("页面表单中存在id和name相同但是元素不同的情况,请检查",elemHaveDifferenceIdAndNames);
}
});
//检查页面图片是否被缩放
inspect.addRule("check-image-scale",function(util){
var images = util.getAllImages().elements,
imageWithScales = [];
images.forEach(function(item,index){
var currentWidth = item.width || parseInt(item.getAttribute("width"),10),
currentHeight = item.height || parseInt(item.getAttribute("height"),10),
realWidth,
realHeight;
if(!item.getAttribute("src")){ return; }
item.setAttribute("style","width:auto!important;height:auto!important");
realWidth = item.width;
realHeight = item.height;
item.setAttribute("style","width:" + currentWidth + "px!important;height:" + currentHeight + "px!important");
if((currentWidth !== realWidth) && (currentHeight !== realHeight)){
imageWithScales.push(item);
}
});
if(imageWithScales.length){
util.log.warn("页面中存在被缩放过的展示图片,影响页面渲染速度",imageWithScales);
}
});
//检测页面的链接资源必须要在白名单之内
inspect.addRule("check-resources-in-whitelist",function(util){
var whiteList = ["tmall.com","taobao.com", "alibaba.com","1688.com","alimama","yunos.com","etao.com","aliexpress.com","g.tbcdn.cn","a.tbcdn.cn","mmcdn.cn"],
elemNotInWhitelist = [],
elements = util.q("img,a,iframe,script,link");
elements.forEach(function(item,index){
var hrefOrSrc = item.getAttribute("href") || item.getAttribute("src"),
urlInfo = util.parseUri(hrefOrSrc),
flag = false;
if(!hrefOrSrc || !urlInfo.host){ return; }
whiteList.forEach(function(it,idx){
if(urlInfo.host.indexOf(it) !== -1){
flag = true;
}
});
if(!flag){
elemNotInWhitelist.push(item);
}
});
if(elemNotInWhitelist.length){
util.log.warn("页面中有资源不在配置的域名白名单中,请检查",elemNotInWhitelist);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment