Last active
December 19, 2015 19:38
-
-
Save supersha/6007327 to your computer and use it in GitHub Desktop.
检查页面不符合最佳实践的方面
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//检查全部 | |
inspect.check(); | |
//检查指定的任务 | |
inspect.check("check-meta-charset"); | |
//检查多个任务 | |
inspect.check(["check-meta-charset","check-doctype"]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;(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