jsVelocity – An JavaScript Template Engine of Velocity
模板引擎以前一直在使用自己模仿mustache语法实现的轻量级模板系统。由于使用习惯问题,一直喜欢velocity的语法,于是最近按照velocity语法重新开发了一个模板系统jsVelocity,已经放到了github上。支持#set、#if、#foreach、#macro,并且提供了简单的模板管理系统。
下面是一个helloword程序:
'#set(${name}="vapour")',
'#if(${name})',
'<div>${name}</div>',
'#else',
'<div>${web}</div>',
'#end',
'<ul>',
'#foreach(${web} in ${website})',
'<li>${velocityCount} ${web.name} : ${web.url} #macro(isGoogle ${web.name})</li>',
'#end',
'</ul>'
]);
var html = Template.render('t1', {
website: [{
name: 'google',
url: 'http://www.google.com/';
}, {
name: 'yahoo',
url: 'http://www.yahoo.com/';
}, {
name: 'facebook',
url: 'http://www.facebook.com/'
}, {
name: 'twitter',
url: 'http://www.twitter.com/';
}],
isGoogle: function(name){
return name === 'google' ? ', is google' : '';
}
});
模板变量必须是${variable}这种形式。
下面是实现代码,包含模板管理、velocity语法解析器、调试模块。
'use strict';
var util,
templates = {},
SENTENCE = /#([a-z]+)\(([\s\S]+?)\)/,
VARIABLE = /\$\{\w[\w\.]*\}/g,
velocity;
util = {
each: function (arr, cb) {
var i = 0, l = arr.length;
for (; i < l; i++) {
cb(arr[i], i, arr);
}
},
trim: function (str) {
return str.replace(/^\s+|\s+$/g, '');
},
log: function(msg, type){
Template.log(msg, type);
}
};
velocity = {
parse: function (str, params) {
var result;
params = params || {};
while ((result = SENTENCE.exec(str)) !== null) {
switch (result[1]) {
case 'set':
str = this.parseSet(str, params, result);
break;
case 'if':
str = this.parseIf(str, params, result);
break;
case 'foreach':
str = this.parseForeach(str, params, result);
break;
case 'macro':
str = this.parseMacro(str, params, result);
break;
}
//break; //debug开发阶段只执行一次
}
str = this.replaceVariable(str, params);
return str;
},
parseSet: function (str, params, result) { //parse #set
var start = result.index,
end = start + result[0].length,
expression = result[2];
if (expression.indexOf('=') > -1) {
expression = expression.split('=');
params[expression[0].slice(2, -1)] = this.express(expression[1], params);
} else {
util.log('#set syntac error', 'warn');
}
str = str.slice(0, start) + str.slice(end);
return str;
},
parseIf: function (str, params, result) { //parse #if
var flag = this.express(this.velocityToVariable(result[2], params), params),
start = result.index,
middle = this.matchElse(str),
end = this.matchEnd(str);
if (end === -1) {
util.log('syntax error,miss #end', 'warn');
return str;
}
//#if(expression) length=5+expression.length
//#end length=4
//#else length=5
if (middle > -1 && middle < end) {
if (flag) {
str = str.slice(0, start) + str.slice(start + result[2].length + 5, middle) + str.slice(end + 4);
} else {
str = str.slice(0, start) + str.slice(middle + 5, end) + str.slice(end + 4);
}
} else {
if (flag) {
str = str.slice(0, start) + str.slice(start + result[2].length + 5, end) + str.slice(end + 4);
} else {
str = str.slice(0, start) + str.slice(end + 4);
}
}
return str;
},
//#foreach(expression) length=10+expression.length
//#end length=4
parseForeach: function (str, params, result) { //parse #foreach
var start = result.index,
end = this.matchEnd(str),
context = str.slice(start + result[2].length + 10, end),
arr = result[2].split(' in '),
list, item, html = '';
if (arr.length !== 2) {
util.log('syntax error: ' + result[0], 'error');
return str;
}
item = util.trim(arr[0]).slice(2, -1);
list = this.getVariable(util.trim(arr[1]).slice(2, -1), params);
if (list) {
util.each(list, function (o, index) {
var p = velocity.clone(params);
p[item] = list[index];
p.velocityCount = index + 1;
html += velocity.parse(context, p);
});
}
str = str.slice(0, start) + html + str.slice(end + 4);
return str;
},
//#macro(funcName $variable1 $variable2);
parseMacro: function(str, params, result){ //parse #macro
var start = result.index,
end = start + result[0].length,
ret = '', func,
arr = [];
//filter whitespace
util.each(result[2].split(' '), function(v, index){
v = util.trim(v);
if (v !== '') {
if (index > 0) {
arr.push(velocity.getVariable(v.slice(2, -1), params));
} else {
arr.push(v);
}
}
});
func = arr[0];
if (func) {
func = params[func];
if (func) {
ret = func.apply(null, arr.slice(1)) || '';
} else {
util.log('#macro ' + arr[0] + ' is undefined', 'error');
}
} else {
util.log('#macro syntax error, macro name is undefined', 'error')
}
str = str.slice(0, start) + ret + str.slice(end);
return str;
},
clone: function () { //clone object
var ret = {},
i, len, o, p;
for (i = 0, len = arguments.length; i < len; i++) {
o = arguments[i];
for (p in o) {
if (o.hasOwnProperty(p) && !ret[p]) {
ret[p] = o[p];
}
}
}
return ret;
},
express: function (expression, params) {
return eval('(' + expression + ')');
},
velocityToVariable: function (str, params) { //velocity to JS
str = str.replace(VARIABLE, function (all) {
return 'params.' + all.slice(2, -1);
});
return str;
},
replaceVariable: function (str, params) { //display velocity variables
str = str.replace(VARIABLE, function (all) {
var name = all.slice(2, -1),
value = velocity.getVariable(name, params);
if (value !== undef) {
return value;
} else {
util.log('Variables ' + all + ' is undefined', 'warn');
return all;
}
});
return str;
},
getVariable: function (name, params) {
var ret, tmp, arr, i, len;
if (name.indexOf('.') === -1) {
ret = params[name];
} else {
ret = params;
arr = name.split('.');
for (i = 0, len = arr.length; i < len; i++) {
if (ret[arr[i]] !== undef) {
ret = ret[arr[i]];
} else {
util.log('Variables ' + name + ' is undefined', 'warn');
ret = '';
break;
}
}
}
return ret;
},
matchEnd: function (str) { //find the position of #end
var reg = /#(foreach|if|end)/g,
start = 0,
index = -1,
result;
while ((result = reg.exec(str)) !== null) {
switch (result[1]) {
case 'foreach':
case 'if':
start += 1;
break;
case 'end':
start -= 1;
if (start === 0) {
index = result.index;
}
break;
default:
break;
}
if (index > -1) {
break;
}
}
return index;
},
matchElse: function (str) { //find the position of #else
var reg = /#(if|else|end)/g,
start = 0,
index = -1,
result;
while ((result = reg.exec(str)) !== null) {
switch (result[1]) {
case 'if':
start += 1;
break;
case 'end':
start -= 1;
break;
case 'else':
if (start === 1) {
index = result.index;
}
break;
default:
break;
}
if (index > -1) {
break;
}
}
return index;
}
};
return {
render: function (name, params) {
if (typeof templates[name] !== 'string') {
util.log('Template ' + name + ' not found!', 'warn');
return '';
} else {
return velocity.parse(templates[name], params);
}
},
define: function (name, template) {
if (typeof template == 'string') { //string
templates[name] = template;
} else { //array
templates[name] = template.join('');
}
}
};
})();
Template.log = (function () {
'use strict';
var debug = window.location.href.indexOf('debug') > -1,
log = function () {
var div = document.createElement('div');
div.style.cssText = 'position:fixed;width:100%;bottom:0;height:120px;border-top:2px solid #ccc;backround-color:#efefef;overflow-y:scroll';
document.body.appendChild(div);
log = function (msg, type) {
var d = document.createElement('div');
d.style.padding = '4px';
switch (type) {
case 'warn':
d.style.color = 'red';
break;
case 'info':
break;
}
d.innerHTML = msg;
div.appendChild(d);
};
};
if (debug) {
if (window.console && console.log) {
return function (msg, type) {
switch (type) {
case 'info':
console.info(msg);
break;
case 'warn':
console.warn(msg);
break;
default:
console.log(msg);
break;
}
};
} else {
log();
return log;
}
} else {
return function () {};
}
})();
Math.max() < Math.min()
在firefox(或其他浏览器开发者工具)的命令行中运行:
// true
对这个结果是不是有点惊讶,我们看下ECMA-262
ECMA-262中关于max和min的规范如下:
15.8.2.11 max([value1[, value2[, ...]]])
Given zero or more arguments, calls ToNumber on each of the arguments and returns the largest of the resulting values.
- If no arguments are given, the result is -∞
- If any value is NaN, the result is NaN.
- The comparison of values to determine the largest value is done as in 11.8.5 except that +0 is considered to be larger than -0.
The length property of the max method is 2.
15.8.2.12 min ( [ value1 [ , value2 [ , … ] ] ] )
Given zero or more arguments, calls ToNumber on each of the arguments and returns the smallest of the resulting values.
- If no arguments are given, the result is +∞.
- If any value is NaN, the result is NaN.
- The comparison of values to determine the smallest value is done as in 11.8.5 except that +0 is considered to be larger than -0.
The length property of the min method is 2.
ECMA中规定,当max和min的参数为空时,输出-∞或∞。
google chrome v8引擎Math.max和Math.min代码:
function MathMax(arg1, arg2) { // length == 2
var length = % _ArgumentsLength();
if (length == 2) {
if (!IS_NUMBER(arg1)) arg1 = NonNumberToNumber(arg1);
if (!IS_NUMBER(arg2)) arg2 = NonNumberToNumber(arg2);
if (arg2 > arg1) return arg2;
if (arg1 > arg2) return arg1;
if (arg1 == arg2) {
// Make sure -0 is considered less than +0. -0 is never a Smi, +0 can be
// a Smi or a heap number.
return (arg1 == 0 && ! % _IsSmi(arg1) && 1 / arg1 < 0) ? arg2 : arg1;
}
// All comparisons failed, one of the arguments must be NaN.
return 0 / 0; // Compiler constant-folds this to NaN.
}
if (length == 0) {
return -1 / 0; // Compiler constant-folds this to -Infinity.
}
var r = arg1;
if (!IS_NUMBER(r)) r = NonNumberToNumber(r);
if (NUMBER_IS_NAN(r)) return r;
for (var i = 1; i < length; i++) {
var n = % _Arguments(i);
if (!IS_NUMBER(n)) n = NonNumberToNumber(n);
if (NUMBER_IS_NAN(n)) return n;
// Make sure +0 is considered greater than -0. -0 is never a Smi, +0 can be
// a Smi or heap number.
if (n > r || (r == 0 && n == 0 && ! % _IsSmi(r) && 1 / r < 0)) r = n;
}
return r;
}
// ECMA 262 - 15.8.2.12
function MathMin(arg1, arg2) { // length == 2
var length = % _ArgumentsLength();
if (length == 2) {
if (!IS_NUMBER(arg1)) arg1 = NonNumberToNumber(arg1);
if (!IS_NUMBER(arg2)) arg2 = NonNumberToNumber(arg2);
if (arg2 > arg1) return arg1;
if (arg1 > arg2) return arg2;
if (arg1 == arg2) {
// Make sure -0 is considered less than +0. -0 is never a Smi, +0 can be
// a Smi or a heap number.
return (arg1 == 0 && ! % _IsSmi(arg1) && 1 / arg1 < 0) ? arg1 : arg2;
}
// All comparisons failed, one of the arguments must be NaN.
return 0 / 0; // Compiler constant-folds this to NaN.
}
if (length == 0) {
return 1 / 0; // Compiler constant-folds this to Infinity.
}
var r = arg1;
if (!IS_NUMBER(r)) r = NonNumberToNumber(r);
if (NUMBER_IS_NAN(r)) return r;
for (var i = 1; i < length; i++) {
var n = % _Arguments(i);
if (!IS_NUMBER(n)) n = NonNumberToNumber(n);
if (NUMBER_IS_NAN(n)) return n;
// Make sure -0 is considered less than +0. -0 is never a Smi, +0 can be a
// Smi or a heap number.
if (n < r || (r == 0 && n == 0 && ! % _IsSmi(n) && 1 / n < 0)) r = n;
}
return r;
}
注意:
∞ == 1/0 == Infinity == Number.POSITIVE_INFINITY
-∞ == -1/0 == -Infinity == Number.NEGTIVE_INFINITY
规范这么规定原因可能是:
You see, the min/max implementations need something to compare to and Infinity and -Infinity are the only safe values to use for that comparison.
参考:
非字母数字的JS(二)
在2011年的BlackHat DC 2011大会上Ryan Barnett给出了一段关于XSS的示例javascript代码:
上面这段代码在浏览器中输出alert(1),这段代码中巧妙利用sort函数执行返回window对象来获取window对象,然后执行window['alert'](1)。关于这段代码的详细解释可查看这里。
关于上面代码的实现技术原理,可以查看《非字母数字组成的JavaScript》。
由上面的代码,引发思考能不能输出数字(0-9)和全部26个字母,于是进行了下面尝试:
console.log(+[]); //0
console.log(-~[]); //1
console.log(-~-~[]); //2
console.log(-~-~-~[]); //3
console.log(-~-~-~-~[]); //4
console.log(-~-~-~-~-~[]); //5
console.log(-~-~-~-~-~-~[]); //6
console.log(-~-~-~-~-~-~-~[]); //7
console.log(-~-~-~-~-~-~-~-~[]); //8
console.log(-~-~-~-~-~-~-~-~-~[]); //9
通过上面这种方法,可以得到0-9,再组合0-9就可以得到任意数值了。
//[{}]+[] [object Object]
console.log(([{}]+[])[-~[]]); //o
console.log(([{}]+[])[-~-~[]]); //b
console.log(([{}]+[])[-~-~-~[]]); //j
console.log(([{}]+[])[-~-~-~-~[]]); //e
console.log(([{}]+[])[-~-~-~-~-~[]]); //c
console.log(([{}]+[])[-~-~-~-~-~-~[]]); //t
//![]+[] false
console.log((![] + [])[+[]]); //f
console.log((![] + [])[-~[]]); //a
console.log((![] + [])[-~-~[]]); //l
console.log((![] + [])[-~-~-~[]]); //s
console.log((![] + [])[-~-~-~-~[]]); //e
//!![]+[] true
console.log((!![]+[])[+[]]); //t
console.log((!![]+[])[-~[]]); //r
console.log((!![]+[])[-~-~[]]); //u
console.log((!![]+[])[-~-~-~[]]); //e
//({}[(!![]+[])[+[]]])+[] undefined
console.log((({}[(!![]+[])[+[]]])+[])[-~[]]); //n
console.log((({}[(!![]+[])[+[]]])+[])[-~-~[]]); //d
console.log((({}[(!![]+[])[+[]]])+[])[-~-~-~-~-~[]]); //i
经过尝试,暂时只能得到a b c d e f i j l n o r s t u共15个字母。欢迎补充
轻量级模板系统
开发问卷系统时,开发了一个简单的模板管理功能。实现了简单的变量替换、循环、自定义函数、并提供了循环变量tmCount(从0开始)和tmNumber(从1开始)。不多说,先上代码:
var templates = {},
tmp,
supplant = {
init: function (str, params) {
var value, prop, tmp, len;
for (prop in params) {
if (params.hasOwnProperty(prop)) {
value = params[prop];
switch (Object.prototype.toString.call(value)) {
case '[object Number]':
case '[object String]':
tmp = {};
tmp[prop] = value;
str = supplant.replaceString(str, tmp);
break;
case '[object Function]':
str = supplant.replaceFunc(str, prop, value, params);
break;
case '[object Array]':
str = supplant.replaceArray(str, prop, value);
break;
default:
break;
}
}
}
return str;
},
replaceString: function (str, params) {
var prop;
for (prop in params) {
if (params.hasOwnProperty(prop)) {
str = str.split('{' + prop + '}').join(params[prop]);
}
}
return str;
},
replaceFunc: function (str, prop, func, item) {
return str.split('{' + prop + '}').join(func(item));
},
replaceArray: function (str, prop, data) {
var i = 0,
left,
middle,
right,
s = '{#' + prop + '}',
e = '{/' + prop + '}',
len = data.length,
start = 0,
end = 0;
start = str.indexOf(s);
end = str.indexOf(e, start);
left = str.slice(0, start);
middle = str.slice(start + s.length, end);
right = str.slice(end + e.length);
if (len > 0 && start > -1) {
for (; i < len; ++i) {
data[i].tmCount = i;
data[i].tmNumber = i + 1;
if (data[i].content !== null) { //如果content属性不等于null
left += supplant.init(middle, data[i]);
} else {
left += data[i].replace;
}
}
str = left + right;
if (str.indexOf(s) > -1) {//如果包括多个循环
str = supplant.replaceArray(str, prop, data);
}
}
if (len == 0 && start > -1) {
str = left + right;
}
return str;
}
};
return {
render: function (name, params) {
if (typeof templates[name] !== 'string') {
throw 'Template ' + name + ' not found!';
}
tmp = name;
return supplant.init(templates[name], params);
},
defineTemplate: function (name, template) {
if (typeof template == 'string') {
templates[name] = template;
} else { //数组
templates[name] = template.join('');
}
}
};
})();
上面代码中定义一个templateManager对象用来管理模板,提供了两个接口:
- defineTemplate 用来定义模板
- render 用来渲染模板
内部私有的supplant对象实现模板引擎,提供了替换变量、替换循环、替换函数的功能。
文档和示例:
templateManager.defineTemplate('profile', '姓名:{name},blog:{blog}');
templateManager.render('profile', {name: 'vapour', blog: 'http://dovapour.info/'});
//结果 姓名:vapour,blog:http://dovapour.info/
//实现循环
templateManager.defineTemplate('website', [
'<ul>',
'{#webs}<li><a href="{url}">{name}<_buff_29_buff_li>{/webs}',
'<ul>'
]);
templateManager.render('website', {
webs: [{
name: '百度',
url: 'http://www.baidu.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '淘宝',
url: 'http://www.taobao.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '腾讯',
url: 'http://www.qq.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '网易',
url: 'http://163.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '新浪',
url: 'http://www.sina.com/',
getLen: function (item) {
return item.url.length;
}
}]
});
/*
<ul>
<li><a href="http://www.baidu.com/">百度</a>,url长度21</li>
<li><a href="http://www.taobao.com/">淘宝</a>,url长度22</li>
<li><a href="http://www.qq.com/">腾讯</a>,url长度18</li>
<li><a href="http://163.com/">网易</a>,url长度15</li>
<li><a href="http://www.sina.com/">新浪</a>,url长度20</li>
<ul>
*/
ie中的跨域和ckeditor
在IE中修改了document.domain进行提权后(无论修改前后域名是否不同),iframe都会出现跨域问题。
ckeditor中检查跨域的代码,是根据判断document.domain和window.location.hostname是否相同来来判断跨域的。
if (!this.ie) return false;
var g = document.domain,
h = window.location.hostname;
return g != h && g != '[' + h + ']';
}
当我们执行在页面执行 document.domain = document.domain 后,执行 isCustomDomain 会返回false,这时ie会因为跨域,报拒绝访问的错误。
所以当我们,在修改document.domain时要进行判断,当新域和旧域不同时再修改document.domain
newDomain = 'xxx.com';
if (newDomain != oldDomain) {
document.domain = newDomain;
}
跨浏览器实现全选功能
跨浏览器实现全选功能,IE一行代码就OK,标准浏览器就比较麻烦了,需要使用selection、range、addRange实现。
if (arale.isIE()) { //IE
return function (win) {
win = win || window;
win.document.body.createControlRange().execCommand('SelectAll');
}
} else { //标准浏览器
return function (win) {
var s, r;
win = win || window;
doc = win.document;
s = win.getSelection();
r = doc.createRange();
r.selectNode(doc.body);
s.addRange(r);
}
}
})();
使用动态类型、对象构造而非继承
今天读了OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance,作者提倡使用动态类型(duck typing)、对象构造来代替继承。
关于动态类型的示例代码:
if ( ! (foo instanceof Bar) ) {
throw new TypeError("This is an annoying restriction!");
}
return foo.baz(foo.quux(10));
}
function good (foo) {
if ( !foo.baz || !foo.quux ) {
throw new TypeError("We need foo to have baz and quux methods.");
}
return foo.baz(foo.quux(10));
}
下面是作者给出使用动态类型和对象构造的一个操作DOM的示例:
var k, ctor;
for ( k in definition ) {
ctor = definition[k];
if ( ctor === Number ) {
if ( Object.prototype.toString.call(obj[k]) !== "[object Number]" || isNaN(obj[k]) ) {
return false;
}
} else if ( ctor === String ) {
if ( Object.prototype.toString.call(obj[k]) !== "[object String]" ) {
return false;
}
} else if ( ! (obj[k] instanceof ctor) ) {
return false;
}
}
return true;
};
Object.combine = function () {
var newObj = {},
i = 0,
args = Array.prototype.slice.call(arguments),
len = args.length;
function copyProperty(k) {
newObj[k] = args[i][k];
}
for ( ; i < len; i++ ) {
Object.getOwnPropertyNames(args[i]).forEach(copyProperty);
}
return newObj;
};
window.$ = (function () {
var undef, listen, customAPI;
// Private function to turn 'thing' in to an array,
// optionally slicing the first 'n' elems off.
function toArray(thing, n) {
return Array.prototype.slice.call(thing, n || 0);
}
// Private function to partially apply the first n arguments
// to 'fn'.
function curry (fn) {
var args = toArray(arguments, 1);
return function () {
return fn.apply(this, args.concat(toArray(arguments)));
};
}
// Private function to set the attribute 'attr' of a single
// DOM node 'node' to 'val'.
function nodeAttributeSetter (attr, val, node) {
node.setAttribute(attr, val);
}
// Private function to get the value of the attribute 'attr'
// on the DOM node 'node'.
function nodeAttributeGetter (attr, node) {
return node.getAttribute(attr);
}
// Do the work to determine the correct method for attaching
// event listeners only once by using 'quacksLike' and an
// immediately invoked function.
listen = (function () {
var w3c = {
addEventListener: Function
},
ie = {
attachEvent: Function
};
if ( quacksLike(document.body, w3c) ) {
return function (event, fn, node) {
node.addEventListener(event, fn, false);
};
} else if ( quacksLike(document.body, ie) ) {
return function (event, fn, node) {
node.attachEvent("on" + event, fn);
};
} else {
throw new TypeError("Do not know how to attach event listeners.");
}
}());
// This object is a mixin to be composed with the NodeList
// results of querying the DOM by CSS selector. It provides
// handy methods for getting and setting attributes,
// attaching event listeners, and performing sub-queries by
// CSS on these elements.
customAPI = {
attr: function (attr, val) {
if ( val !== undef ) {
this.forEach(curry(nodeAttributeSetter, attr, val));
return this;
} else {
return this.length > 0 ? nodeAttributeGetter(attr, this[0]) : "";
}
},
on: function (event, fn) {
this.forEach(curry(listen, event, fn));
return this;
},
find: function (selector) {
var res = [];
this.forEach(function (node) {
res.push.apply(res, toArray(node.querySelectorAll(selector)));
});
return Object.combine(res, customAPI);
}
};
return function (selector, context) {
context = context || document;
return Object.combine(Array.prototype, context.querySelectorAll(selector), customAPI);
};
}());
//The mini DOM library might be used like this:
// The following two forms are equivalent.
// They select the same elements.
$("div").find("p");
$("div p");
// Disable all links.
$("a").on("click", function (event) {
event.preventDefault();
alert("Don't leave me all alone!");
});
// Get the id of the first paragraph.
$("p").attr("id");
// Disable all input elementss.
$(input").attr("disabled", "disabled");
// Do stuff with each matched element.
$("div.foo").forEach(function (el) {
doStuffWith(el);
});
label导致冒泡事件的诡异问题
今天遇到一个很怪异的问题,给label标签绑定了click事件,但是每次单击时,click事件都会执行两次。开始以为自己的代码有问题,反复痛苦的检查了N久以后,才发现自己代码没问题。根本原因在于事件的冒泡机制和label标签共同作用引起的。
<li><label for="q1"><input id="q1" type="text" value="" />工商</label></li>
<li><label for="q2"><input id="q2" type="text" value="" />农业</label></li>
<li><label for="q3"><input id="q3" type="text" value="" />招商</label></li>
<li><label for="q4"><input id="q4" type="text" value="" />建设</label></li>
<li><label for="q5"><input id="q5" type="text" value="" />浦发</label></li>
</ul>
执行两次的过程如下:
- 单击label标签,第一次执行click事件
- 由于label标签的特殊性,事件向下传递到input标签
- 事件开始冒泡几上传递
- 冒泡到label标签时,第二次执行click事件
为了更便于说明上面的过程,请看下面的示例:
<input id="txtUser" type="text" value="单击这里alert二次" />
单击这里alert三次
</label>
<script type="text/javascript">
$('lbUser').click(function(){
alert('单击label');
});
$('txtUser').click(function(){
alert('单击input');
});
</script>
当点击label中的文字时,依次弹出:
- alert('单击label')
- alert('单击input');
- alert('单击label');
当直接点击input输入框时,依次弹出:
- alert('单击input');
- alert('单击label');
总结:这个问题让自己纠结了,很长时间。主要原因时没有考虑到冒泡,需要注意的这里我们注册的事件都是绑定在冒泡期间,但是因为label(会自动将焦点转到和标签相关的表单控件上),所以导致了这个意外。
非字母数字的JavaScript
由非字母数字组成的JavaScript,这是几年前一个日本安全专家Yosuke Hasegawa在slackers论坛上提出的。这种技术主要是利用JavaScript的数据类型自动转换的特性,例如布尔类型true、false可以转换成字符类型"true"、"false"。
现在我们利用上面这种技术来获取字母o,我们可以利用对象调用toString方法转换成字符串[object Object]来得到字母o。
1 首页我们需要获得对象字面量[object Object]:
2 得到数字0
3 得到数字1,在0的基础上增加1
4最后合并上面代码,得到字母o
如果对这方面感兴趣,可以阅读:
YAUC Less chars needed to run arbitrary JS code = 6! (JS GREAT WALL)
http://sla.ckers.org/forum/
Diminuitive NonAlNum JS - Arbitrary:-
http://sla.ckers.org/forum/
Java/script: no alnum cheat sheets:-
http://sla.ckers.org/forum/read.php?24,33349
Diminutive JS Code Challenge, from OWASP :-
http://sla.ckers.org/forum/read.php?24,30015