Mustache.js前端引擎源码解读

时间:2023-07-10 07:48:08 其他范文 收藏本文 下载本文

Mustache.js前端引擎源码解读(精选3篇)由网友“张鹤玄”投稿提供,今天小编在这给大家整理过的Mustache.js前端引擎源码解读,我们一起来看看吧!

Mustache.js前端引擎源码解读

篇1:Mustache.js前端引擎源码解读

mustache是一个很轻的前端模板引擎,因为之前接手的项目用了这个模板引擎,自己就也继续用了一会觉得还不错,最近项目相对没那么忙,于是就抽了点时间看了一下这个的源码,源码很少,也就只有六百多行,所以比较容易阅读。做前端的话,还是要多看优秀源码,这个模板引擎的知名度还算挺高,所以其源码也肯定有值得一读的地方。

本人前端小菜,写这篇博文纯属自己记录一下以便做备忘,同时也想分享一下,希望对园友有帮助。若解读中有不当之处,还望指出。

如果没用过这个模板引擎,建议 去 github.com/janl/mustache.js/ 试着用一下,上手很容易。

摘取部分官方demo代码(当然还有其他基本的list遍历输出):

数据:

{

“name”: {

“first”: “Michael”,

“last”: “Jackson”

},

“age”: “RIP”

}

模板写法:

* {{name.first}} {{name.last}}

* {{age}}

渲染效果:

* Michael Jackson

* RIP

OK,那就开始来解读它的源码吧:

首先先看下源码中的前面多行代码:

var Object_toString = Object.prototype.toString;

var isArray = Array.isArray || function (object) {

return Object_toString.call(object) === '[object Array]';

};

function isFunction(object) {

return typeof bject === 'function';

}

function escapeRegExp(string) {

return string.replace(/[\-\[\]{}*+?.,\\\^$|#\s]/g, “\\$&”);

}

// Workaround for issues.apache.org/jira/browse/COUCHDB-577

// See github.com/janl/mustache.js/issues/189

var RegExp_test = RegExp.prototype.test;

function testRegExp(re, string) {

return RegExp_test.call(re, string);

}

var nonSpaceRe = /\S/;

function isWhitespace(string) {

return !testRegExp(nonSpaceRe, string);

}

var entityMap = {

“&”: “&”,

“<”: “<”,

“>”: “>”,

'“': '”',

“'”: ''',

“/”: '/'

};

function escapeHtml(string) {

return String(string).replace(/[&“'\/]/g, function (s) {

return entityMap[s];

});

}

var whiteRe = /\s*/;  //匹配0个或以上空格

var spaceRe = /\s+/;  //匹配一个或以上空格

var equalsRe = /\s*=/; //匹配0个或者以上空格再加等于号

var curlyRe = /\s*\}/; //匹配0个或者以上空格再加}符号

这些都比较简单,都是一些为后面主函数准备的工具函数,包括

· toString和test函数的简易封装

· 判断对象类型的方法

· 字符过滤正则表达式关键符号的方法

· 判断字符为空的方法

· 转义字符映射表 和 通过映射表将html转码成非html的方法

· 一些简单的正则。

一般来说mustache在js中的使用方法都是如下:

var template = $('#template').html();

Mustache.parse(template); // optional, speeds up future uses

var rendered = Mustache.render(template, {name: ”Luke“});

$('#target').html(rendered);

所以,我们接下来就看下parse的实现代码,我们在源码里搜索parse,于是找到这一段

mustache.parse = function (template, tags) {

return defaultWriter.parse(template, tags);

};

再通过找defaultWriter的原型Writer类后,很容易就可以找到该方法的核心所在,就是parseTemplate方法,这是一个解析器,不过在看这个方法之前,还得先看一个类:Scanner,顾名思义,就是扫描器,源码如下

/**

* 简单的字符串扫描器,用于扫描获取模板中的模板标签

*/

function Scanner(string) {

this.string = string; //模板总字符串

this.tail = string;  //模板剩余待扫描字符串

this.pos = 0; //扫描索引,即表示当前扫描到第几个字符串

}

/**

* 如果模板被扫描完则返回true,否则返回false

*/

Scanner.prototype.eos = function () {

return this.tail === ”“;

};

/**

* 扫描的下一批的字符串是否匹配re正则,如果不匹配或者match的index不为0;

* 即例如:在”abc{{“中扫描{{结果能获取到匹配,但是index为4,所以返回”“;如果在”{{abc“中扫描{{能获取到匹配,此时index为0,即返回{{,同时更新扫描索引

*/

Scanner.prototype.scan = function (re) {

var match = this.tail.match(re);

if (!match || match.index !== 0)

return '';

var string = match[0];

this.tail = this.tail.substring(string.length);

this.pos += string.length;

return string;

};

/**

* 扫描到符合re正则匹配的字符串为止,将匹配之前的字符串返回,扫描索引设为扫描到的位置

*/

Scanner.prototype.scanUntil = function (re) {

var index = this.tail.search(re), match;

switch (index) {

case -1:

match = this.tail;

this.tail = ”“;

break;

case 0:

match = ”“;

break;

default:

match = this.tail.substring(0, index);

this.tail = this.tail.substring(index);

}

this.pos += match.length;

return match;

};

扫描器,就是用来扫描字符串,在mustache用于扫描模板代码中的模板标签。扫描器中就三个方法:

eos:判断当前扫描剩余字符串是否为空,也就是用于判断是否扫描完了

scan:仅扫描当前扫描索引的下一堆匹配正则的字符串,同时更新扫描索引,注释里我也举了个例子

scanUntil:扫描到匹配正则为止,同时更新扫描索引

看完扫描器,我们再回归一下,去看一下解析器parseTemplate方法,模板的标记标签默认为”{{}}“,虽然也可以自己改成其他,不过为了统一,所以下文解读的时候都默认为{{}}:

function parseTemplate(template, tags) {

if (!template)

return [];

var sections = [];  // 用于临时保存解析后的模板标签对象

var tokens = [];   // 保存所有解析后的对象

var spaces = [];   // 保存空格对象在tokens里的索引

var hasTag = false;

var nonSpace = false;

// 去除保存在tokens里的空格标记

function stripSpace() {

if (hasTag && !nonSpace) {

while (spaces.length)

delete tokens[spaces.pop()];

} else {

spaces = [];

}

hasTag = false;

nonSpace = false;

}

var openingTagRe, closingTagRe, closingCurlyRe;

//将tag转成正则,默认的tag为{{和}},所以转成匹配{{的正则,和匹配}}的正则,已经匹配}}}的正则(因为mustache的解析中如果是{{{}}}里的内容则被解析为html代码)

function compileTags(tags) {

if (typeof tags === 'string')

tags = tags.split(spaceRe, 2);

if (!isArray(tags) || tags.length !== 2)

throw new Error('Invalid tags: ' + tags);

peningTagRe = new RegExp(escapeRegExp(tags[0]) + '\\s*');

closingTagRe = new RegExp('\\s*' + escapeRegExp(tags[1]));

closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tags[1]));

}

compileTags(tags || mustache.tags);

var scanner = new Scanner(template);

var start, type, value, chr, token, openSection;

while (!scanner.eos()) {

start = scanner.pos;

// Match any text between tags.

// 开始扫描模板,扫描至{{时停止扫描,并且将此前扫描过的字符保存为value

value = scanner.scanUntil(openingTagRe);

if (value) {

//遍历{{前的字符

for (var i = 0, valueLength = value.length; i < valueLength; ++i) {

chr = value.charAt(i);

//如果当前字符为空格,则用spaces数组记录保存至tokens里的索引

if (isWhitespace(chr)) {

spaces.push(tokens.length);

} else {

nonSpace = true;

}

tokens.push([ 'text', chr, start, start + 1 ]);

start += 1;

// 如果遇到换行符,则将前一行的空格去掉

if (chr === '\n')

stripSpace();

}

}

// 判断下一个字符串中是否有{[,同时更新扫描索引至{{后一位

if (!scanner.scan(openingTagRe))

break;

hasTag = true;

//扫描标签类型,是{{#}}还是{{=}}还是其他

type = scanner.scan(tagRe) || 'name';

scanner.scan(whiteRe);

//根据标签类型获取标签里的值,同时通过扫描器,刷新扫描索引

if (type === '=') {

value = scanner.scanUntil(equalsRe);

//使扫描索引更新为\s*=后

scanner.scan(equalsRe);

//使扫描索引更新为}}后,下面同理

scanner.scanUntil(closingTagRe);

} else if (type === '{') {

value = scanner.scanUntil(closingCurlyRe);

scanner.scan(curlyRe);

scanner.scanUntil(closingTagRe);

type = '&';

} else {

value = scanner.scanUntil(closingTagRe);

}

// 匹配模板闭合标签即}},如果没有匹配到则抛出异常,同时更新扫描索引至}}后一位,至此时即完成了一个模板标签{{#tag}}的扫描

if (!scanner.scan(closingTagRe))

throw new Error('Unclosed tag at ' + scanner.pos);

// 将模板标签也保存至tokens数组中

token = [ type, value, start, scanner.pos ];

tokens.push(token);

//如果type为#或者^,也将tokens保存至sections

if (type === '#' || type === '^') {

sections.push(token);

} else if (type === '/') { //如果type为/则说明当前扫描到的模板标签为{{/tag}},则判断是否有{{#tag}}与其对应

// 检查模板标签是否闭合,{{#}}是否与{{/}}对应,即临时保存在sections最后的{{#tag}},是否跟当前扫描到的{{/tag}}的tagName相同

// 具体原理:扫描第一个tag,sections为[{{#tag}}],扫描第二个后sections为[{{#tag}} , {{#tag2}}]以此类推扫描多个开始tag后,sections为[{{#tag}} , {{#tag2}} ... {{#tag}}]

// 所以接下来如果扫描到{{/tag}}则需跟sections的最后一个相对应才能算标签闭合。同时比较后还需将sections的最后一个删除,才能进行下一轮比较

penSection = sections.pop();

if (!openSection)

throw new Error('Unopened section ”' + value + '“ at ' + start);

if (openSection[1] !== value)

throw new Error('Unclosed section ”' + openSection[1] + '“ at ' + start);

} else if (type === 'name' || type === '{' || type === '&') {

nonSpace = true;

} else if (type === '=') {

compileTags(value);

}

}

// 保证sections里没有对象,如果有对象则说明标签未闭合

penSection = sections.pop();

if (openSection)

throw new Error('Unclosed section ”' + openSection[1] + '“ at ' + scanner.pos);

//在对tokens里的数组对象进行筛选,进行数据的合并及剔除

return nestTokens(squashTokens(tokens));

}

解析器就是用于解析模板,将html标签即内容与模板标签分离,整个解析原理为遍历字符串,通过最前面的那几个正则以及扫描器,将普通html和模板标签{{#tagName}}{{/tagName}}{{^tagName}}扫描出来并且分离,将每一个{{#XX}}、{{^XX}}、{{XX}}、{{/XX}}还有普通不含模板标签的html等全部抽象为数组保存至tokens,

tokens的存储方式为:

token[0]为token的type,可能值为:# ^ / & name text等分别表示{{#XX}}、{{^XX}}、{{/XX}}、{{&XX}}、{{XX}}、以及html文本等

token[1]为token的内容,如果是模板标签,则为标签名,如果为html文本,则是html的文本内容

token[2],token[3]为匹配开始位置和结束位置,后面将数据结构转换成树形结构的时候还会有token[4]和token[5]

具体的扫描方式为以{{}}为扫描依据,利用扫描器的scanUtil方法,扫描到{{后停止,通过scanner的scan方法匹配tagRe正则(/#|\^|\/|>|\{|&|=|!/)从而判断出{{后的字符是否为模板关键字符,再用scanUtil方法扫描至}}停止,获取获取到的内容,此时就可以获取到tokens[0]、tokens[1]、tokens[2],再调用一下scan更新扫描索引,就可以获取到token[3]。同理,下面的字符串也是如此扫描,直至最后一行return nestTokens(squashTokens(tokens))之前,扫描出来的结果为,模板标签为一个token对象,如果是html文本,则每一个字符都作为一个token对象,包括空格字符。这些数据全部按照扫描顺序保存在tokens数组里,不仅杂乱而且量大,所以最后一行代码中的squashTokens方法和nestTokens用来进行数据筛选以及整合。

首先来看下squashTokens方法,该方法主要是整合html文本,对模板标签的token对象没有进行处理,代码很简单,就是将连续的html文本token对象整合成一个。

function squashTokens(tokens) {

var squashedTokens = [];

var token, lastToken;

for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {

token = tokens[i];

if (token) {

if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {

lastToken[1] += token[1];

lastToken[3] = token[3];

} else {

squashedTokens.push(token);

lastToken = token;

}

}

}

return squashedTokens;

}

整合完html文本的token对象后,就通过nestTokens进行进一步的整合,遍历tokens数组,如果当前token为{{#XX}}或者{{^XX}}都说明是模板标签的开头标签,于是把它的第四个参数作为收集器存为collector进行下一轮判断,如果当前token为{{/}}则说明遍历到了模板闭合标签,取出其相对应的开头模板标签,再给予其第五个值为闭合标签的开始位置。如果是其他,则直接扔进当前的收集器中。如此遍历完后,tokens里的token对象就被整合成了树形结构

function nestTokens(tokens) {

var nestedTokens = [];

//collector是个收集器,用于收集当前标签子元素的工具

var collector = nestedTokens;

var sections = [];

var token, section;

for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {

token = tokens[i];

switch (token[0]) {

case '#':

case '^':

collector.push(token);

sections.push(token); //存放模板标签的开头对象

collector = token[4] = []; //此处可分解为:token[4]=[];collector = token[4];即将collector指向当前token的第4个用于存放子对象的容器

break;

case '/':

section = sections.pop(); //当发现闭合对象{{/XX}}时,取出与其相对应的开头{{#XX}}或{{^XX}}

section[5] = token[2];

collector = sections.length >0 ? sections[sections.length - 1][4] : nestedTokens; //如果sections未遍历完,则说明还是有可能发现{{#XX}}开始标签,所以将collector指向最后一个sections中的最后一个{{#XX}}

break;

default:

collector.push(token);   //如果是普通标签,扔进当前的collector中

}

}

//最终返回的数组即为树形结构

return nestedTokens;

经过两个方法的筛选和整合,最终出来的数据就是精简的树形结构数据:

至此,整个解析器的代码就分析完了,然后我们来分析渲染器的代码。

parseTemplate将模板代码解析为树形结构的tokens数组,按照平时写mustache的习惯,用完parse后,就是直接用 xx.innerHTML = Mustache.render(template , obj),因为此前会先调用parse解析,解析的时候会将解析结果缓存起来,所以当调用render的时候,就会先读缓存,如果缓存里没有相关解析数据,再调用一下parse进行解析。

Writer.prototype.render = function (template, view, partials) {

var tokens = this.parse(template);

//将传进来的js对象实例化成context对象

var context = (view instanceof Context) ? view : new Context(view);

return this.renderTokens(tokens, context, partials, template);

};

可见,进行最终解析的renderTokens函数之前,还要先把传进来的需要渲染的对象数据进行处理一下,也就是把数据包装成context对象。所以我们先看下context部分的代码:

function Context(view, parentContext) {

this.view = view == null ? {} : view;

this.cache = { '.': this.view };

this.parent = parentContext;

}

/**

* 实例化一个新的context对象,传入当前context对象成为新生成context对象的父对象属性parent中

*/

Context.prototype.push = function (view) {

return new Context(view, this);

};

/**

* 获取name在js对象中的值

*/

Context.prototype.lookup = function (name) {

var cache = this.cache;

var value;

if (name in cache) {

value = cache[name];

} else {

var context = this, names, index;

while (context) {

if (name.indexOf('.') >0) {

value = context.view;

names = name.split('.');

index = 0;

while (value != null && index < names.length)

value = value[names[index++]];

} else if (typeof context.view == 'object') {

value = context.view[name];

}

if (value != null)

break;

context = context.parent;

}

cache[name] = value;

}

if (isFunction(value))

value = value.call(this.view);

console.log(value)

return value;

};

context部分代码也是很少,context是专门为树形结构提供的工厂类,context的构造函数中,this.cache = {'.':this.view}是把需要渲染的数据缓存起来,同时在后面的lookup方法中,把需要用到的属性值从this.view中剥离到缓存的第一层来,也就是lookup方法中的cache[name] = value,方便后期查找时先在缓存里找

context的push方法比较简单,就是形成树形关系,将新的数据传进来封装成新的context对象,并且将新的context对象的parent值指向原来的context对象。

context的lookup方法,就是获取name在渲染对象中的值,我们一步一步来分析,先是判断name是否在cache中的第一层,如果不在,才进行深度获取。然后将进行一个while循环:

先是判断name是否有.这个字符,如果有点的话,说明name的格式为XXX.XX,也就是很典型的键值的形式。然后就将name通过.分离成一个数组names,通过while循环遍历names数组,在需要渲染的数据中寻找以name为键的值。

如果name没有.这个字符,说明是一个单纯的键,先判断一下需要渲染的数据类型是否为对象,如果是,就直接获取name在渲染的数据里的值。

通过两层判断,如果没找到符合的值,则将当前context置为context的父对象,再对其父对象进行寻找,直至找到value或者当前context无父对象为止。如果找到了,将值缓存起来。

看完context类的代码,就可以看渲染器的代码了:

Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {

var buffer = '';

var self = this;

function subRender(template) {

return self.render(template, context, partials);

}

var token, value;

for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {

token = tokens[i];

switch (token[0]) {

case '#':

value = context.lookup(token[1]); //获取{{#XX}}中XX在传进来的对象里的值

if (!value)

continue; //如果不存在则跳过

//如果为数组,说明要复写html,通过递归,获取数组里的渲染结果

if (isArray(value)) {

for (var j = 0, valueLength = value.length; j < valueLength; ++j) {

//获取通过value渲染出的html

buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);

}

} else if (typeof value === 'object' || typeof value === 'string') {

//如果value为对象,则不用循环,根据value进入下一次递归

buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);

} else if (isFunction(value)) {

//如果value是方法,则执行该方法,并且将返回值保存

if (typeof originalTemplate !== 'string')

throw new Error('Cannot use higher-order sections without the original template');

// Extract the portion of the original template that the section contains.

value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);

if (value != null)

buffer += value;

} else {

buffer += this.renderTokens(token[4], context, partials, originalTemplate);

}

break;

case '^':

//如果为{{^XX}},则说明要当value不存在(null、undefine、0、'')或者为空数组的时候才触发渲染

value = context.lookup(token[1]);

// Use JavaScript's definition of falsy. Include empty arrays.

// See github.com/janl/mustache.js/issues/186

if (!value || (isArray(value) && value.length === 0))

buffer += this.renderTokens(token[4], context, partials, originalTemplate);

break;

case '>':

//防止对象不存在

if (!partials)

continue;

//>即直接读取该值,如果partials为方法,则执行,否则获取以token为键的值

value = isFunction(partials) ? partials(token[1]) : partials[token[1]];

if (value != null)

buffer += this.renderTokens(this.parse(value), context, partials, value);

break;

case '&':

//如果为&,说明该属性下显示为html,通过lookup方法获取其值,然后叠加到buffer中

value = context.lookup(token[1]);

if (value != null)

buffer += value;

break;

case 'name':

//如果为name说明为属性值,不作为html显示,通过mustache.escape即escapeHtml方法将value中的html关键词转码

value = context.lookup(token[1]);

if (value != null)

buffer += mustache.escape(value);

break;

case 'text':

//如果为text,则为普通html代码,直接叠加

buffer += token[1];

break;

}

}

return buffer;

};

原理还是比较简单的,因为tokens的树形结构已经形成,渲染数据就只需要按照树形结构的顺序进行遍历输出就行了。

不过还是大概描述一下,buffer是用来存储渲染后的数据,遍历tokens数组,通过switch判断当前token的类型:

如果是#,先获取到{{#XX}}中的XX在渲染对象中的值value,如果没有该值,直接跳过该次循环,如果有,则判断value是否为数组,如果为数组,说明要复写html,再遍历value,通过递归获取渲染后的html数据。如果value为对象或者普通字符串,则不用循环输出,直接获取以value为参数渲染出的html,如果value为方法,则执行该方法,并且将返回值作为结果叠加到buffer中。如果是^,则当value不存在或者value是数组且数组为空的时候,才获取渲染数据,其他判断都是差不多。

通过这堆判断以及递归调用,就可以把数据完成渲染出来了。

至此,Mustache的源码也就解读完了,Mustache的核心就是一个解析器加一个渲染器,以非常简洁的代码实现了一个强大的模板引擎。

篇2:Normalize.css 介绍与源码解读

malize.css 是一个可定制的 CSS 文件,使浏览器呈现的所有元素,更一致和符合现代标准;是在现代浏览器环境下对于CSS reset的替代, 它正是针对只需要统一的元素样式。该项目依赖于研究浏览器默认元素风格之间的差异,精确定位需要重置的样式。 这是一个现代的,HTML5-ready 的 CSS 重置样式集。著名的bootstrap就使用了它,github的地址为:github.com/necolas/normalize.css/, 据作者描述,Normalize.css做了以下几件事:

Preserves useful defaults, unlike many CSS resets. - 保护有用的浏览器默认样式而不是完全去掉它们

Normalizes styles for a wide range of elements. - 为大部分HTML元素提供一般化的样式

Corrects bugs and common browser inconsistencies. - 修复浏览器自身的bug并保证各浏览器的一致性

Improves usability with subtle improvements. - 用一些小技巧优化CSS可用性

Explains what code does using detailed comments. - 用注释和详细的文档来解释代码

Normalize对比Reset的优势

1. Normalize.css 保护了有价值的默认值

Reset通过为几乎所有的元素施加默认样式,强行使得元素有相同的视觉效果。 相比之下,Normalize.css保持了许多默认的浏览器样式。 这就意味着你不用再为所有公共的排版元素重新设置样式。 当一个元素在不同的浏览器中有不同的默认值时,Normalize.css会力求让这些样式保持一致并尽可能与现代标准相符合。

2. Normalize.css 修复了浏览器的bug

它修复了常见的桌面端和移动端浏览器的bug。这往往超出了Reset所能做到的范畴。 关于这一点,Normalize.css修复的问题包含了HTML5元素的显示设置、预格式化文字的font-size问题、在IE9中SVG的溢出、许多出现在各浏览器和操作系统中的与表单相关的bug。

3. Normalize.css 不会让你的调试工具变的杂乱

使用Reset最让人困扰的地方莫过于在浏览器调试工具中大段大段的继承链,如下图所示。在Normalize.css中就不会有这样的问题,因为在我们的准则中对多选择器的使用时非常谨慎的,我们仅会有目的地对目标元素设置样式。

4. Normalize.css 是模块化的

这个项目已经被拆分为多个相关却又独立的部分,这使得你能够很容易也很清楚地知道哪些元素被设置了特定的值。因此这能让你自己选择性地移除掉某些永远不会用到部分(比如表单的一般化)。

5. Normalize.css 拥有详细的文档

Normalize.css的代码基于详细而全面的跨浏览器研究与测试。这个文件中拥有详细的代码说明并在Github Wiki中有进一步的说明。这意味着你可以找到每一行代码具体完成了什么工作、为什么要写这句代码、浏览器之间的差异,并且你可以更容易地进行自己的测试。 这个项目的目标是帮助人们了解浏览器默认是如何渲染元素的,同时也让人们很容易地明白如何改进浏览器渲染。

源码解读

参考了很多内容,代码可以在这里找到,以下分模块逐个分析:

HTML与BODY

html {

font-family: sans-serif; // 1

-ms-text-size-adjust: 100%; // 2

-webkit-text-size-adjust: 100%; // 2

}

body {

margin: 0;

}

设置所有的字体为sans-serif,关于text-size-adjust是这样的:iOS设备旋转后可能会自动调整字体大小(e.g. 竖着的时候是14px,横着就自动调整成20px)。 将这个属性设置为100%后Safari就会不会自作主张调整大小。 设置成100%和设置成none的区别是前者在防止浏览器自动插手字体大小的同时,可以让用户通过缩放控制字体大小,后者会很恼人地让用户无法放大缩小字体。 MDN的文档里有提到如果将这个属性设为none,基于webkit的电脑浏览器也会受到影响,无法放大缩小。 以前有人利用这个特性来绕过电脑chrome字体大小不能小于12px的限制,但是chrome27后已经取消了对这个特性的支持。 不过一般说来,还是不要设none的好,多少让用户有点自由控制的余地。具体请参考这里:text-size-adjust和 一定不能O定成-webkit-text-size-adjust:none的原因, 至于body的默认边距问题,在各个浏览器上也都不一致,统一设置。

HTML5 新标签的display兼容性解决

article,

aside,

details,

figcaption,

figure,

footer,

header,

hgroup,

main,

menu,

nav,

section,

summary {

display: block;

}

audio,

canvas,

progress,

video {

display: inline-block; // 1

vertical-align: baseline; // 2

}

audio:not([controls]) {

display: none;

height: 0;

}

[hidden],

template {

display: none;

}

IE8不认识HTML5的新元素 beta版的IE9认识新元素,但没有定义正确的显示 IE10/11的details 和 summary不是block-level IE11的main不是block-level Chrome, Firefox, 和Opera的progress没有以baseline垂直对齐,如果audio标签没有控制栏,则应该隐藏,有点暴力啊!hidden属性是在HTML5中新加入的属性,可能有些人觉得和规范一直倡导的样式分离有所背离,但HTML5设计的一条重要的原理就是实用性。 这个属性会帮助屏幕阅读器更方便地识别。template标签用于HTML模板,现代Web开发中,HTML模板使用很多,这个标签是顺应实际需求。 但模板又要求不能在界面上显示的,所以统一样式,兼容旧浏览器。关于垂直居中,可以参见 CSS vertical-align 属性和我对css-vertical-align的一些理解与认识(一)

链接修复

a {

background-color: transparent;

}

a:active,

a:hover {

outline: 0;

}

在IE10下,在点击超链接(active)的时候,会出现一个灰色背景,去掉。在active或hover时,把默认的outline样式去掉(针对所有浏览器)。

语义化文字标签修复

abbr[title] {

border-bottom: 1px dotted;

}

b,

strong {

font-weight: bold;

}

dfn {

font-style. italic;

}

h1 {

font-size: 2em;

margin: 0.67em 0;

}

mark {

background: #ff0;

color: #000;

}

small {

font-size: 80%;

}

sub,

sup {

font-size: 75%;

line-height: 0;

position: relative;

vertical-align: baseline;

}

sup {

top: -0.5em;

}

sub {

bottom: -0.25em;

}

abbr标签的语义是表示缩小,在标签的title属性中会添加此缩写的完整版本,

此标签在FF中默认有下边框(1px dotted),在Safari和Chrome中则无此样式,此处统一设置了下边框。

Firefox 4+, Safari和Chrome给b和strong设置的其实是bolder而不是bold,对于一些有一整套web font的网站会落到不想要的字重。但是HTML标准里已经说了要bolder啊 而且设为bold的话是不能叠加着越来越粗的

dfn标签可标记那些对特殊术语或短语的定义,在Safari和Chrome里不是斜体

重置h1样式

mark标签是HTML5中的标签,IE8/9不支持,所以需要重置样式。

不同浏览器下的small大小不一致,这里定为80%

HTML标准里对small,sub和sup的大小要求都是smaller,但是normalize.css给small设的是80%,sub和sup却是75%,这里为了保持一致+不影响其他元素的行高,把两者的line-height设为0,vertical-align从baseline开始,然后用top和bottom手动设置两者偏移量。

其他标签修复

img {

border: 0;

}

svg:not(:root) {

overflow: hidden;

}

figure {

margin: 1em 40px;

}

hr {

-moz-box-sizing: content-box;

box-sizing: content-box;

height: 0;

}

pre {

overflow: auto;

}

code,

kbd,

pre,

samp {

font-family: monospace, monospace;

font-size: 1em;

}

在旧版IE浏览器中,图片默认会出现一个蓝色的外框。

IE 9/10/11中,SVG的hidden显示不正常

figure的margin在IE 8/9 and Safari不生效

在FF中,hr元素的默认样式很多,和其它浏览器主要的差异是设置了height为2px,box-sizing为border-box,样式中正是重置了这两个影响布局的样式。关于box-sizing请看: CSS3 Box-sizing

大部分浏览器的pre在overflow的时候会直接溢出去,这里加上overflow:auto让它出现滚动条

Form系

button,

input,

optgroup,

select,

textarea {

color: inherit; // 1

font: inherit; // 2

margin: 0; // 3

}

button {

overflow: visible;

}

button,

select {

text-transform. none;

}

button,

html input[type=”button“], // 1

input[type=”reset“],

input[type=”submit“] {

-webkit-appearance: button; // 2

cursor: pointer; // 3

}

button[disabled],

html input[disabled] {

cursor: default;

}

button::-moz-focus-inner,

input::-moz-focus-inner {

border: 0;

padding: 0;

}

input {

line-height: normal;

}

input[type=”checkbox“],

input[type=”radio“] {

box-sizing: border-box; // 1

padding: 0; // 2

}

input[type=”number“]::-webkit-inner-spin-button,

input[type=”number“]::-webkit-outer-spin-button {

height: auto;

}

input[type=”search“] {

-webkit-appearance: textfield; // 1

-moz-box-sizing: content-box;

-webkit-box-sizing: content-box; // 2

box-sizing: content-box;

}

input[type=”search“]::-webkit-search-cancel-button,

input[type=”search“]::-webkit-search-decoration {

-webkit-appearance: none;

}

fieldset {

border: 1px solid #c0c0c0;

margin: 0 2px;

padding: 0.35em 0.625em 0.75em;

}

legend {

border: 0; // 1

padding: 0; // 2

}

textarea {

overflow: auto;

}

optgroup {

font-weight: bold;

}

部分浏览器会把form里面的输入框(textarea,text,button, select)的字体设置为用户的系统字体或者是浏览器本身的字体(还有颜色),并不会继承自父元素。所以需要重置输入框的默认样式。

IE 8/9/10/11里的button默认的overflow是hidden,这里设为和群众一致的visible

可点击的按钮,设置鼠标样式为pointer,提高了可用性。 关于-webkit-appearance 参见: 使用CSS3的appearance属性改变元素的外观

给disabled的再补充一个cursor:default

移除 Firefox 4+ 内部的内边距

统一search类型输入框的默认样式,让search类型输入框和普通输入框样式一样。

fieldset元素的默认样式在各浏览器中的差异较大,尤其是IE浏览器和其它浏览器,统一一下很有必要。

IE里的文本框就算文本高度没有超过指定高度,都会默认加上一个没有滚动条的滚动栏,设置overflow: auto可以去掉

关于form的box-sizing方法的纠正,清继续参考 CSS3 Box-sizing

Table系

table {

border-collapse: collapse;

border-spacing: 0;

}

td,

th {

padding: 0;

}

table的默认样式很难看,大部分浏览器设置table的border-collapse为separate,border-spacing为2,一般项目中都会重置此样式。

参考

篇3:spring beans源码解读之Bean的定义及包装

bean的定义,包装是java bean的基础,再怎么强调它的重要性都不为过,因此深入 了解这块的代码对以后的代码研究可以起到事半功倍的功效。

1. Bean的定义BeanDefinition

1.1 BeanDefinition 作用

一个BeanDefinition描述了一个bean的实例,包括属性值,构造方法参数值和继承自它的类的更多信息。BeanDefinition仅仅是一个最简单的接口,主要功能是允许BeanFactoryPostProcessor 例如PropertyPlaceHolderConfigure 能够检索并修改属性值和别的bean的元数据。

1.2 BeanDefinition的继承关系

父接口:

AttributeAccessor, BeanMetadataElement

子接口:

AnnotatedBeanDefinition

子类:

AbstractBeanDefinition, AnnotatedGenericBeanDefinition, ChildBeanDefinition, GenericBeanDefinition, RootBeanDefinition, ScannedGenericBeanDefinition

其中,AttributeAccessor接口定义了最基本的对任意对象的元数据的修改或者获取,主要方法有:

String[] attributeNames()

Object getAttribute(String name)

boolean hasAttribute(String name)

Object removeAttribute(String name)

void setAttribute(String name, Object value)

BeanMetadataElement接口提供了一个getResource()方法,用来传输一个可配置的源对象。

1.3 BeanDefinition的抽象类AbstractBeanDefinition

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor

implements BeanDefinition, Cloneable {

}

其中,BeanMetadataAttributeAccessor接口既实现了BeanMetadataElement接口提供的getResource()方法也提供了AttributeAccessorSupport 针对属性的增删改查,如上AttributeAccessor的方法。

public class BeanMetadataAttributeAccessor extends AttributeAccessorSupport implements BeanMetadataElement {

}

我们来看看一个抽象的beanDefinition 的创建涉及到哪些属性?

构造方法之一,创建一个新的默认设置的AbstractBeanDefinition,如下面代码所示,具体信息也可以参考BeanDefinitionDefaults这个类。

构造方法之二,根据构造参数值和属性参数值来创建。

/**

* Create a new AbstractBeanDefinition with default settings.

*/

protected AbstractBeanDefinition() {

this(null, null);

}

/**

* Create a new AbstractBeanDefinition with the given

* constructor argument values and property values.

*/

protected AbstractBeanDefinition(ConstructorArgumentValues cargs, MutablePropertyValues pvs) {

setConstructorArgumentValues(cargs);

setPropertyValues(pvs);

}

构造方法之三是深复制一个原有的beandefinition。

/**

* Create a new AbstractBeanDefinition as a deep copy of the given

* bean definition.

* @param original the original bean definition to copy from

*/

protected AbstractBeanDefinition(BeanDefinition original) {

setParentName(original.getParentName());

setBeanClassName(original.getBeanClassName());

setFactoryBeanName(original.getFactoryBeanName());

setFactoryMethodName(original.getFactoryMethodName());

setScope(original.getScope());

setAbstract(original.isAbstract());

setLazyInit(original.isLazyInit());

setRole(original.getRole());

setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues()));

setPropertyValues(new MutablePropertyValues(original.getPropertyValues()));

setSource(original.getSource());

copyAttributesFrom(original);

if (original instanceof AbstractBeanDefinition) {

AbstractBeanDefinition riginalAbd = (AbstractBeanDefinition) original;

if (originalAbd.hasBeanClass()) {

setBeanClass(originalAbd.getBeanClass());

}

setAutowireMode(originalAbd.getAutowireMode());

setDependencyCheck(originalAbd.getDependencyCheck());

setDependsOn(originalAbd.getDependsOn());

setAutowireCandidate(originalAbd.isAutowireCandidate());

copyQualifiersFrom(originalAbd);

setPrimary(originalAbd.isPrimary());

setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed());

setLenientConstructorResolution(originalAbd.isLenientConstructorResolution());

setInitMethodName(originalAbd.getInitMethodName());

setEnforceInitMethod(originalAbd.isEnforceInitMethod());

setDestroyMethodName(originalAbd.getDestroyMethodName());

setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod());

setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));

setSynthetic(originalAbd.isSynthetic());

setResource(originalAbd.getResource());

}

else {

setResourceDescription(original.getResourceDescription());

}

}

上述三个方法涉及到的接口和类有:

ConstructorArgumentValues:构造参数,保存了构造方法所有的参数值,通常作为bean definition的一部分来使用。它不仅支持类型匹配的普通参数,也支持根据参数列表中的索引位置来提供参数。提供了两个变量来保存参数:带索引的和不带索引的

public class ConstructorArgumentValues {

private final MapindexedArgumentValues = new LinkedHashMap(0);

private final ListgenericArgumentValues = new LinkedList();

}

MutablePropertyValues:PropertyValues接口的默认实现,支持对属性的简单操作,为构造方法提供深度复制和使用map获取构造的支持。

2. Bean的包装BeanWrapper

2.1 作用:提供对标准javabean的分析和操作方法:单个或者批量获取和设置属性值,获取属性描述符,查询属性的可读性和可写性等。支持属性的嵌套设置,深度没有限制。

2.2 继承关系:

public interface BeanWrapper extends ConfigurablePropertyAccessor {

}

public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {

}

其中,ConfigurablePropertyAccessor 接口封装了PropertyAccessor的配置方法,同时继承了PropertyEditorRegistry接口,具有了对PropertyEditor进行管理的方法,

该接口的固有方法:

ConversionService getConversionService() 返回关联的ConversionService,如果存在的话。

boolean isExtractOldValueForEditor() 返回标示位。如果为true,表示当使用属性编辑器编辑一个属性的值时提取旧的属性值,如果为false,则表示不提取旧值。

void setConversionService(ConversionService conversionService) conversionservice是从spring3.0引入的,可以作为java bean 属性编辑器的替代功能,作用是改变属性的值。

void setExtractOldValueForEditor(boolean extractOldValueForEditor) 设置是否提取旧的属性值标示位,如上描述。

另外,ConfigurablePropertyAccessor 接口继承的方法有:

从PropertyAccessor继承的方法有:getPropertyType, getPropertyTypeDescriptor, getPropertyValue, isReadableProperty, isWritableProperty, setPropertyValue, setPropertyValue, setPropertyValues, setPropertyValues, setPropertyValues, setPropertyValues;

从PropertyEditorRegistry继承的方法有:findCustomEditor, registerCustomEditor, registerCustomEditor;

从TypeConverter 继承的方法有:convertIfNecessary, convertIfNecessary, convertIfNecessary 。

2.3 BeanWrapper的实现类:BeanWrapperImpl

BeanWrapperImpl作用:可以根据需求,将集合与数组的值转换到对应目标对象的集合和数组。自定义的属性编辑器通过属性编辑器的setValue,setAsText方法实现上述的转换功能。

BeanWrapperImpl 默认的PropertyEditor的实现如下:(PropertyEditorRegistrySupport.java)

private void createDefaultEditors() {

this.defaultEditors = new HashMap

// Simple editors, without parameterization capabilities.

// The JDK does not contain a default editor for any of these target types.

this.defaultEditors.put(Charset.class, new CharsetEditor());

this.defaultEditors.put(Class.class, new ClassEditor());

this.defaultEditors.put(Class[].class, new ClassArrayEditor());

this.defaultEditors.put(Currency.class, new CurrencyEditor());

this.defaultEditors.put(File.class, new FileEditor());

this.defaultEditors.put(InputStream.class, new InputStreamEditor());

this.defaultEditors.put(InputSource.class, new InputSourceEditor());

this.defaultEditors.put(Locale.class, new LocaleEditor());

this.defaultEditors.put(Pattern.class, new PatternEditor());

this.defaultEditors.put(Properties.class, new PropertiesEditor());

this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());

this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());

this.defaultEditors.put(URI.class, new URIEditor());

this.defaultEditors.put(URL.class, new URLEditor());

this.defaultEditors.put(UUID.class, new UUIDEditor());

if (zoneIdClass != null) {

this.defaultEditors.put(zoneIdClass, new ZoneIdEditor());

}

// Default instances of collection editors.

// Can be overridden by registering custom instances of those as custom editors.

this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));

this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));

this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));

this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));

this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));

// Default editors for primitive arrays.

this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());

this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());

// The JDK does not contain a default editor for char!

this.defaultEditors.put(char.class, new CharacterEditor(false));

this.defaultEditors.put(Character.class, new CharacterEditor(true));

// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.

this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));

this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));

// The JDK does not contain default editors for number wrapper types!

// Override JDK primitive number editors with our own CustomNumberEditor.

this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));

this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));

this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));

this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));

this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));

this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));

this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));

this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));

this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));

this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));

this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));

this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));

this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));

this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));

// Only register config value editors if explicitly requested.

if (this.configValueEditorsActive) {

StringArrayPropertyEditor sae = new StringArrayPropertyEditor();

this.defaultEditors.put(String[].class, sae);

this.defaultEditors.put(short[].class, sae);

this.defaultEditors.put(int[].class, sae);

this.defaultEditors.put(long[].class, sae);

}

}

其中涉及到很多编辑器,在此就不赘叙了,如有兴趣,可以自行查找。

HTML前端开发面试题及前端知识

C.net web开发面试题

java前端开发面试题

阿里 社招 面试

面试经验:php程序员深圳面试经历

如何才能正确有效的学习js

如何寻找关键词和优化关键词的方法

谈新版豆瓣的“首页”标签问题网页设计

HTML5开发工程师岗位的具体职责概述

web前端面试题

Mustache.js前端引擎源码解读
《Mustache.js前端引擎源码解读.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

【Mustache.js前端引擎源码解读(精选3篇)】相关文章:

阿里实习转正面试总结2022-06-16

三言两语:美国雅虎首页改版网页设计2023-08-30

企业网站建设合同2022-11-21

web前端工程师的求职信2022-10-11

游艇专业英语词汇(F1)2023-03-13

改版-我的工作汇报2022-09-22

seo面试的简历2022-08-15

软件架构师岗位的基本职责简述2023-05-31

电气工程师面试题2022-05-08

接触人事招聘,如何面试网络美工和PHP程序员?2023-03-01

点击下载本文文档