此版本仍然有许多knouckoutjs的影子,其中最下方那个normalizeJSON直接抄自knouckoutjs,我深感内疚。
knouckoutjs的声明式绑定的值部分是非常复杂,它允许用户直接在里面使用函数,表达式什么,然后再往它两边花括号一包就是一个JSON的字面量。但直接转换为JSON是不行的,IE对键名为关键字的对象肯定报错,必须要用双引号括起。于是才有了normalizeJSON这东西。另一方面,knouckoutjs为VM的每个字段建立关联的方式非常复杂冗长,大量使用闭包。这个我的MVVM框架avalon做了改进。我非常讨厌knouckoutjs的事件绑定方式,那还不如直接用onclick=xxx绑好了,或结合jQuery, mass Framework能得到更大的回报。不过在v2中,我还没有想到更好的方法,既不跟风照搬,也没有搞出相应方案,因此事件绑定部分是一片空白。
说说avalon v2的优点吧。只有730行,非常精简,虽然建立在功能残缺的基础上,没有knouckoutjs的虚拟结点,没有事件绑定,没有options绑定。
双向绑定链的许多概念已经成形。我们做一件事,要做好,就要知道怎么去做,什么分步做。这些步骤,其中细节,全部都要抽象成特定的术语才能指导我们行动。knouckoutjs之所以能在前端MVC拥有一席之地,是因为它确定了许多以后被大家抄袭的东西。knouckout的三大理论基石:
- 位于VM中的监控属性, 监控数组, 依赖监控属性。
- 位于视图中的声明式绑定,以data-bind的形式写在标签中。
- 位于视图中的动态模板,就是那些被声明式绑定圈起来的区域。
avalon也建立对应的概念来指导自己的发展:
- 位于VM中的原子监控者,依赖监控者,监控数组与绑定监控者。原子监控者对应ko的监控属性,它是由M中字段抽象而成,是一个函数。依赖监控者对应ko的依赖监控属性。在VM中,有些字段是建立在M的两个或多个字段的基础上。比如fullName 对应为 firstName+" "+lastName,但在M中是找不到它的跟影,但它在视图中却又是一个独立的个体,有专门的文本域给它。绑定监控者是我独创的,它是根据视图中的数据绑定转化而为,在ko中这些依然称之为依赖监控属性。但这一类监控函数分明是真正用于连结DOM树与VM的。它通过框架提供的默认绑定器将自己中数据渲染到DOM中。它的结构比一般的依赖监控属性复杂多了。依赖监控属性,knouckoutjs称之为Dependent Observables,在另一个著名的emberjs框架中,它叫做computed。在绑定监控者这类东西显然不需要返回值,它一般将比它低一级的原子监控者,依赖监控者当作事件回调来监控用户行为就行了。当用户改变了文本域的值,就相当于对这个原子监控者进行写入,从而引发整条依赖的更新。绑定监控者是与DOM树某个节点或某一些节点交道,当底层涌上来的变动要求它也刷新时,它就调用框架自带的绑定器进行干活。因此你看到绑定器都有一个叫update的函数,就是干这事。
- 位于视图中的声明式绑定,我使用bind,但也可以配置你喜欢的属性名。我个人喜欢称之为数据绑定,其实它还兼容流程控制,事件绑定的活儿。
- 位于视图中的动态模板,就是那些被声明式绑定圈起来的区域。动态模板与流程绑定最密切,它的实现好坏,牵及到最小化刷新的实现与内存的占有率。这个v2有实现比knouckout要高明多了。
avalon在数据绑定上做了许多友好的改进,虽然与knouckoutjs一样支持表达式,是没有花括号的JSON。但它要求用户不用写双引号,这辛苦活由框架去实现了。
avalon的自带绑定器有text绑定,html绑定,value绑定,class绑定,style绑定,attr绑定,display绑定,checked绑定,with绑定,if绑定,unless绑定,foreach绑定
有关它们的使用,可以查看这里
不过大家也急着看它们是怎么用的,因为写这文章时,我已经完成非常先进的v3了。这世界就是这样,当日本把小灵通卖给中国时,人家已经普及下下一代的方案。另一更让人心痛的比喻时,当中国在炫耀那垃圾的J10时,美国早在几十年前把飞碟,水上飞机,鸭嘴机玩一遍了。这就是差距。比差距更可怕的是知识的断层。面试时有人问我既然Sizzle这么快了,为什么还要自己写个?这说得那啥的,人家兜里有一百万跟我屁事啊!反正,我是搞出选择器,加载器,动画引擎,异步列队,操作流等东西出来。知识体系,至少是在前端的应用层是完整的。MVVM是前端究极解决方案。从 v1到v2,起码我算是逐渐吃透相关概念,形成自己的解决方案。这个对于后来想进入这领域的国人来说,也留下一点带路石。
define("avalon",["$attr","$event"], function(){ $.log("已加载avalon v2", 7); var BINDING = $.config.bindname || "bind", bridge = {}, uuid = 0, expando = new Date - 0; //将一个普通的对象转换为ViewModel,ViewModel里面每个对象都是监控者(监控函数或监控数组) $.ViewModel = function(data, model){ model = model || {}; if(Array.isArray(data)){ return listWatch(data); } for(var p in data) { if(data.hasOwnProperty(p)) { addWatchs(p, data[p], model); } } return model; } //监控数组,listWatch,它实质上就是一个数组,不过它的许多方法都被覆写了, //以便与DOM实现同步在您添加、删除、移动、刷新或替换数组中的项目时触发对应元素的局部刷新 //我们可以在页面通过foreach绑定此对象 function listWatch(array, models){ models = models || []; for(var index = 0; index < array.length; index++){ var f = addWatchs(index, array[index], models); f.$value = f.$value || f; } String("push,pop,shift,unshift,splice,sort,reverse").replace($.rword, function(method){ var nativeMethod = models[ method ]; models[ method ] = function(){ nativeMethod.apply( models, arguments) var Watchs = models["$"+expando]; for(var i = 0, Watch; Watch = Watchs[i++];){ Watch(method, arguments); } } }); models.removeAt = function(index){//移除指定索引上的元素 models.splice(index, 1); } models.remove = function(item){//移除第一个等于给定值的元素 var array = models.map(function(el){ return el(); }) var index = array.indexOf(item); models.removeAt(index); } //模拟监控函数的行为,监控函数在foreach都会生成一个$value函数 models.$value = function(){ return models } models.$value.$uuid = ++uuid; return models; } //将ViewMode绑定到元素节点上,没有指定默认是绑在body上 $.View = function(model, node){ node = node || document.body; //开始在其自身与孩子中绑定 return setBindingsToElementAndChildren.call( node, model ); } //我们根据用户提供的最初普通对象的键值,选用不同的方式转换成各种监控函数或监控数组 var err = new Error("只能是字符串,数值,布尔,Null,Undefined,函数以及纯净的对象") function addWatchs( key, val, model ){ switch( $.type( val )){ case "Null": case "Undefined": case "String": case "Number": case "Boolean": return atomWatch( key, val, model ); case "Function": return depsWatch( key, val, model, "get"); case "Array": var models = model[key] || (model[key] = []); return listWatch( val, models ); case "Object": if($.isPlainObject( val )){ if( $.isFunction( val.setter ) && $.isFunction( val.getter )){ return depsWatch( key, val, model, "setget"); }else{ var object = model[key] || (model[key] = {}); $.ViewModel( val,object ); object.$value = function(){ return object } return object } }else{ throw err } break; default: throw err } } //atomWatch,原子监控者,它是最简单的监控函数,是指在ViewModel定义时,键值为基础类型的个体 //它们是位于双向依赖链的最底层。不需要依赖于其他Watch! function atomWatch( key, val, host ){ function Watch( neo ){ if( bridge[ expando ] ){ //收集依赖于它的depsWatch,以便它的值改变时,通它们更新自身 $.Array.ensure( Watch.$deps, bridge[ expando ] ); } if( arguments.length ){//在传参不等于已有值时,才更新自已,并通知其的依赖 if( Watch.$val !== neo ){ Watch.$val = neo; updateDeps( Watch ); } } return Watch.$val; } Watch.$val = val; Watch.$uuid = ++uuid return addWatch( key, Watch, host ); } //depsWatch,依赖监控者,是指在ViewModel定义时,值为类型为函数,或为一个拥有setter、getter函数的对象。 //它们是位于双向绑定链的中间层,需要依赖于其他atomWatch或depsWatch的返回值计算自己的value。 //当顶层的VM改变了,通知底层的改变 //当底层的VM改变了,通知顶层的改变 //当中间层的VM改变,通知两端的改变 function depsWatch( key, val,host, type){ var getter, setter//构建一个至少拥有getter,scope属性的对象 if(type == "get"){//getter必然存在 getter = val; }else if(type == "setget"){ getter = val.getter; setter = val.setter; host = val.scope || host; } function Watch( neo ){ if( bridge[ expando ] ){ //收集依赖于它的depsWatch与bindWatch,以便它的值改变时,通知它们更新自身 $.Array.ensure( Watch.$deps, bridge[ expando ] ); } var change = false; if( arguments.length ){//写入新值 if( setter ){ setter.apply( host, arguments ); } }else{ if( !("$val" in Watch) ){ if( !Watch.$uuid ){ bridge[ expando ] = Watch; Watch.$uuid = ++uuid; } neo = getter.call( host ); change = true; delete bridge[ expando ]; } } //放到这里是为了当是最底层的域的值发出改变后,当前域跟着改变,然后再触发更高层的域 if( change && (Watch.$val !== neo) ){ Watch.$val = neo; //通知此域的所有直接依赖者更新自身 updateDeps( Watch ); } return Watch.$val; } return addWatch( key, Watch, host ); } //bindWatch,绑定监控者,用于DOM树或节点打交道的Watch,它们仅在用户调用了$.View(viewmodel, node ), //把写在元素节点上的bind属性的分解出来之时生成的。 //names values 包含上一级的键名与值 function bindWatch (node, names, values, key, str, binding, model ){ function Watch( neo ){ if( !Watch.$uuid ){ //只有在第一次执行它时才进入此分支 if( key == "foreach" ){ var arr = model[str] var p = arr["$"+expando] || ( arr[ "$"+ expando] = [] ); $.Array.ensure( p ,Watch); arguments = ["start"]; } bridge[ expando ] = Watch; } var callback, val; try{ val = Function(names, "return "+ str).apply(null, values ); }catch(e){ return $.log(e, 3) } if(typeof val == "function" ){ //&& isFinite( val.$uuid ) 如果返回值也是个域 callback = val; //这里的域为它所依赖的域 val = callback();//如果是监控体 } if( !Watch.$uuid ){ delete bridge[ expando ]; Watch.$uuid = ++uuid; //第四个参数供流程绑定使用 binding.init && binding.init(node, val, callback, Watch); } var method = arguments[0], args = arguments[1] if( typeof binding[method] == "function" ){ //处理foreach.start, sort, reserve, unshift, shift, pop, push.... var ret = binding[method]( Watch, val, Watch.fragments, method, args ); if(ret){ val = ret; } } //只有执行到这里才知道要不要中断往下渲染 binding.update(node, val, Watch, model, names, values); return Watch.$val = val; } return addWatch( "interacted" ,Watch, node); } //执行绑定在元素标签内的各种指令 //MVVM不代表什么很炫的视觉效果之类的,它只是组织你代码的一种方式。有方便后期维护,松耦合等等优点而已 var inputOne = $.oneObject("text,password,textarea,tel,url,search,number,month,email,datetime,week,datetime-local") $.ViewBindings = { text: { update: function( node, val ){ val = val == null ? "" : val+"" if(node.childNodes.length === 1 && node.firstChild.nodeType == 3){ node.firstChild.data = val; }else{ $( node ).text( val ); } } }, value:{ init: function(node, val, Watch){ if(/input|textarea/i.test(node.nodeName) && inputOne[node.type]){ $(node).on("input",function(){ Watch(node.value) }); } }, update: function( node, val ){ node.value = val; } }, html: { update: function( node, val ){ $( node ).html( val ); }, stopBindings: true }, //通过display样式控制显隐 display: { update: function( node, val ){ node.style.display = val ? "" : "none"; } }, enable: { update: function( node, val ){ if (val && node.disabled) node.removeAttribute("disabled"); else if ((!val) && (!node.disabled)) node.disabled = true; } }, style: { update: function( node, val ){ var style = node.style, styleName; for (var name in val) { styleName = $.cssName(name, style) || name; style[styleName] = val[ name ] || ""; } } }, "class": { update: function( node, val ){ if (typeof val == "object") { for (var className in val) { var shouldHaveClass = val[className]; toggleClass(node, className, shouldHaveClass); } } else { val = String(val || ''); toggleClass(node, val, true); } } } , attr: { update: function( node, val ){ for (var name in val) { $.attr(node, name, val[ name ] ); } } }, checked: { init: function( node, val, Watch ){ if(typeof Watch !== "function"){ throw new Error("check的值必须是一个Feild") } $(node).bind("change",function(){ Watch(node.checked); }); }, update:function( node, val ){ if ( node.type == "checkbox" ) { if (Array.isArray( val )) { node.checked = val.indexOf(node.value) >= 0; } else { node.checked = val; } } else if (node.type == "radio") { node.checked = ( node.value == val ); } } }, template: { //它暂时只供内部使用 update: function( node, val, callback, model, names, values){ var transfer = callback(), code = transfer[0], Watch = transfer[1]; var fragment = Watch.fragments[0]; //取得原始模板 if( code > 0 ){ //处理with if 绑定 fragment.recover(); //将Watch所引用着的节点移出DOM树 var elems = getChildren( fragment ); //取得它们当中的元素节点 node.appendChild( fragment ); //将Watch所引用着的节点放回DOM树 if( elems.length ){ if( code == 2 ){ //处理with 绑定 model = transfer[2] } return setBindingsToChildren.call( elems, model, true, names, values ) } }else if( code === 0 ){ //处理unless 绑定 fragment.recover(); } if( code < 0 && val ){ //处理foreach 绑定 var fragments = Watch.fragments, models = val; if(!models.length){ fragments[0].recover(); return } for( var i = 0, el ; el = fragments[i]; i++){ el.recover(); //先回收,以防在unshift时,新添加的节点就插入在后面 elems = getChildren( el ); node.appendChild( el ); //继续往元素的子节点绑定数据 setBindingsToChildren.call( elems, models[i], true, names, values ); } } }, stopBindings: true } } $.ViewBindings.disable = { update: function( node, val ){ $.ViewBindings.enable.update(node, !val); } } //if unless with foreach四种bindings都是基于template bindings "if,unless,with,foreach,case".replace($.rword, function( type ){ $.ViewBindings[ type ] = { init: function(node, _, _, Watch){ node.normalize(); //合并文本节点数 var fragment = node.ownerDocument.createDocumentFragment(), el while((el = node.firstChild)){ fragment.appendChild(el); //将node中的所有节点移出DOM树 } Watch.fragments = []; //添加一个数组属性,用于储存经过改造的文档碎片 Watch.fragment = fragment; //最初的文档碎片,用于克隆 Watch.cloneFragment = function( dom, unshift ){ //改造文档碎片并放入数组 dom = dom || Watch.fragment.cloneNode(true); var add = unshift == true ? "unshift" : "push" Watch.fragments[add]( patchFragment(dom) ); return dom; } var clone = Watch.cloneFragment(); //先改造一翻,方便在update时调用recover方法 node.appendChild( clone ); //将文档碎片中的节点放回DOM树 }, update : function(node, val, Watch, model, names, values){ $.ViewBindings['template']['update'](node, val, function(){ switch(type){//返回结果可能为 -1 0 1 2 case "if": return [ !!val - 0, Watch];//1 if case "unless": return [!val - 0, Watch]; //0 unless case "with": return [2, Watch, val]; //2 with default: return [-1, Watch]; //-1 foreach } }, model, names, values); }, stopBindings: true } }); //foreach绑定拥有大量的子方法,用于同步数据的增删改查与排序 var foreach = $.ViewBindings.foreach; foreach.start = function( Watch, models, fragments, method, args ){ if(!Array.isArray(models)){ var array = [] for(var key in models){ //通过这里模拟数组行为 if(models.hasOwnProperty(key) && (key !== "$value") && (key != "$"+expando)){ var value = models[key]; value.$value = value; array.push( value ); } } models = array } for(var i = 1; i < models.length; i++ ){ Watch.cloneFragment(); } return models }; //push ok foreach.push = function( Watch, models, fragments, method, args ){ var l = fragments.length for(var index = 0; index < args.length; index++ ){ var n = index + l; var f = addWatchs(n, models[n], models); f.$value = f; Watch.cloneFragment() } } //unshift ok foreach.unshift = function( Watch, models, fragments, method, args ){ for(var index = 0; index < args.length; index++ ){ var f = addWatchs(index, models[index], models); f.$value = f; Watch.cloneFragment(0, true) } for( index = 0; index < models.length; index++ ){ models[index].$key = index } } // shift pop ok foreach.shift = function( Watch, models, fragments, method, args ){ var fragment = fragments[method]() fragment.recover(); for(var index = 0; index < models.length; index++ ){ models[index].$key = index } } foreach.pop = foreach.shift; foreach.splice = function( Watch, models, fragments, method, args ){ var start = args[0], n = args.length - 2; var removes = fragments.splice(start, args[1]); //移除对应的文档碎片 for(var i = 0; i < removes.length; i++){ removes[i].recover(); } for(i = 0; i < n; i++ ){ //将新数据封装成域 var index = start + i var f = addWatchs(index, models[ index ], models); f.$value = f; //为这些新数据创建对应的文档碎片 var dom = Watch.fragment.cloneNode(true); Watch.fragments.splice(index, 0, patchFragment(dom) ); } for( index = start+n; index < models.length; index++ ){ models[index].$key = index } } //对文档碎片进行改造,通过nodes属性取得所有子节点的引用,以方便把它们一并移出DOM树或插入DOM树 function patchFragment( fragment ){ fragment.nodes = $.slice( fragment.childNodes ); fragment.recover = function(){ this.nodes.forEach(function( el ){ this.appendChild(el) },this); } return fragment; } //$.ViewBindings.class的辅助方法 var toggleClass = function (node, className, shouldHaveClass) { var classes = (node.className || "").split(/\s+/); var hasClass = classes.indexOf( className) >= 0;//原className是否有这东西 if (shouldHaveClass && !hasClass) { node.className += (classes[0] ? " " : "") + className; } else if (hasClass && !shouldHaveClass) { var newClassName = ""; for (var i = 0; i < classes.length; i++) if (classes[i] != className) newClassName += classes[i] + " "; node.className = newClassName.trim(); } } //为当前元素把数据隐藏与视图模块绑定在一块 //参数分别为model, pnames, pvalues $.fn.model = function(){ return $._data(this[0], "$model") } $.fn.$value = function(){ var watch = $(this).model() if(typeof watch == "function"){ return watch(); } } //取得标签内的属性绑定,然后构建成bindWatch,并与ViewModel关联在一块 function setBindingsToElement( model, pnames, pvalues ){ var node = this; pnames = pnames || []; pvalues = pvalues || []; var attr = node.getAttribute( BINDING ), names = [], values = [], continueBindings = true, key, val, binding; $._data(node,"$model",model); for(var name in model){ if(model.hasOwnProperty(name)){ names.push( name ); values.push( model[ name ] ); } } if(pnames.length){ pnames.forEach(function(name, i){ if(names.indexOf(name) === -1){ names.push(name); values.push(pvalues[i]) } }) } var array = normalizeJSON("{"+ attr+"}",true); for(var i = 0; i < array.length; i += 2){ key = array[i] val = array[i+1]; binding = $.ViewBindings[ key ]; if( binding ){ if( binding.stopBindings ){ continueBindings = false; } if(key == "foreach" && Array.isArray(model[key]) && !model[key].length ){ continueBindings = false; // continue } bindWatch(node, names, values, key, val, binding, model); } } return continueBindings; } //在元素及其后代中将数据隐藏与viewModel关联在一起 //参数分别为model, pnames, pvalues function setBindingsToElementAndChildren(){ if ( this.nodeType === 1 ){ var continueBindings = true; if( hasBindings( this ) ){ continueBindings = setBindingsToElement.apply(this, arguments); } if( continueBindings ){ var elems = getChildren( this ); elems.length && setBindingsToChildren.apply(elems, arguments); } } } //参数分别为model, pnames, pvalues function setBindingsToChildren( ){ for(var i = 0, n = this.length; i < n ; i++){ setBindingsToElementAndChildren.apply( this[i], arguments ); } } //通知此监控函数或数组的所有直接依赖者更新自身 function updateDeps(Watch){ var list = Watch.$deps || [] ; if( list.length ){ var safelist = list.concat(); for(var i = 0, el; el = safelist[i++];){ delete el.$val; el() } } } //判定是否设置数据绑定的标记 function hasBindings( node ){ var str = node.getAttribute( BINDING ); return typeof str === "string" && str.indexOf(":") > 1 } //取得元素的所有子元素节点 function getChildren( node ){ var elems = [] ,ri = 0; for (node = node.firstChild; node; node = node.nextSibling){ if (node.nodeType === 1){ elems[ri++] = node; } } return elems; } //为监控函数添加更多必须的方法或属性 function addWatch( key, Watch, host ){ //收集依赖于它的depsWatch与bindWatch,以便它的值改变时,通知它们更新自身 Watch.toString = Watch.valueOf = function(){ if( bridge[ expando ] ){ $.Array.ensure( Watch.$deps, bridge[ expando ] ); } return Watch.$val } if(!host.nodeType){ Watch.$key = key; host[ key ] = Watch; } Watch.$deps = []; Watch(); return Watch; } //============================================================ // 将bindings变成一个对象或一个数组 by 司徒正美 //============================================================ function normalizeJSON(json, array){ var keyValueArray = parseObjectLiteral(json),resultStrings = [],keyValueEntry; for (var i = 0; keyValueEntry = keyValueArray[i]; i++) { if (resultStrings.length > 0 && !array) resultStrings.push(","); if (keyValueEntry['key']) { var key = keyValueEntry['key'].trim(); var quotedKey = ensureQuoted(key, array), val = keyValueEntry['value'].trim(); resultStrings.push(quotedKey); if(!array) resultStrings.push(":"); if(val.charAt(0) == "{" && val.charAt(val.length - 1) == "}"){ val = normalizeJSON( val );//逐层加引号 } resultStrings.push(val); } else if (keyValueEntry['unknown']) { resultStrings.push(keyValueEntry['unknown']);//基于跑到这里就是出错了 } } if(array){ return resultStrings } resultStrings = resultStrings.join(""); return "{" +resultStrings +"}"; }; //============================================================ // normalizeJSON的辅助函数 by 司徒正美 //============================================================ var restoreCapturedTokensRegex = /\@mass_token_(\d+)\@/g; function restoreTokens(string, tokens) { var prevValue = null; while (string != prevValue) { //把原字符串放回占位符的位置之上 prevValue = string; string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) { return tokens[tokenIndex]; }); } return string; } function parseObjectLiteral(objectLiteralString) { var str = objectLiteralString.trim(); if (str.length < 3) return []; if (str.charAt(0) === "{")// 去掉最开始{与最后的} str = str.substring(1, str.length - 1); // 首先用占位符把字段中的字符串与正则处理掉 var tokens = []; var tokenStart = null, tokenEndChar; for (var position = 0; position < str.length; position++) { var c = str.charAt(position);//IE6字符串不支持[],开始一个个字符分析 if (tokenStart === null) { switch (c) { case '"': case "'": case "/": tokenStart = position;//索引 tokenEndChar = c;//值 break; }//如果再次找到一个与tokenEndChar相同的字符,并且此字符前面不是转义符 } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) { var token = str.substring(tokenStart, position + 1); tokens.push(token); var replacement = "@mass_token_" + (tokens.length - 1) + "@";//对应的占位符 str = str.substring(0, tokenStart) + replacement + str.substring(position + 1); position -= (token.length - replacement.length); tokenStart = null; } } // 将{},[],()等括起来的部分全部用占位符代替 tokenEndChar = tokenStart = null; var tokenDepth = 0, tokenStartChar = null; for (position = 0; position < str.length; position++) { var c = str.charAt(position); if (tokenStart === null) { switch (c) { case "{": tokenStart = position; tokenStartChar = c; tokenEndChar = "}"; break; case "(": tokenStart = position; tokenStartChar = c; tokenEndChar = ")"; break; case "[": tokenStart = position; tokenStartChar = c; tokenEndChar = "]"; break; } } if (c === tokenStartChar) tokenDepth++; else if (c === tokenEndChar) { tokenDepth--; if (tokenDepth === 0) { var token = str.substring(tokenStart, position + 1); tokens.push(token); replacement = "@mass_token_" + (tokens.length - 1) + "@"; str = str.substring(0, tokenStart) + replacement + str.substring(position + 1); position -= (token.length - replacement.length); tokenStart = null; } } } //拆解字段,还原占位符的部分 var result = []; var keyValuePairs = str.split(","); for (var i = 0, j = keyValuePairs.length; i < j; i++) { var pair = keyValuePairs[i]; var colonPos = pair.indexOf(":"); if ((colonPos > 0) && (colonPos < pair.length - 1)) { var key = pair.substring(0, colonPos); var value = pair.substring(colonPos + 1); result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) }); } else {//到这里应该抛错吧 result.push({ 'unknown': restoreTokens(pair, tokens) }); } } return result; } function ensureQuoted(key, array) { var trimmedKey = key.trim() if(array){ return trimmedKey; } switch (trimmedKey.length && trimmedKey.charAt(0)) { case "'": case '"': return key; default: return "'" + trimmedKey + "'"; } } })
相关链接:avalon v1