jQuery教程 template.js学习
阿萨 2018-03-09 来源 :网络 阅读 758 评论 0

摘要:花了点时间,看了下jQuery-template.js,这篇jQuery教程一起来看看

花了点时间,看了下jQuery-template.js,这篇jQuery教程一起来看看

jQuery.each({..},function(){})

jQuery.fn.extend({..})

jQuery.extend({...})

jQuery.extend(jQuery.tmpl,{..})function xx(){}//自定义方法

结构上非常简单,但template插件却提供了不错的模版功能,我们根据API来慢慢看这个框架。

网络资源//www.cnblogs.com/FoundationSoft/archive/2010/05/19/1739257.html  //www.jb51.net/article/27747.htm

如果在原型上添加方法,这一般都是暴露给外部调用的API,我们来看一下,各个方法的流程:

 jQuery教程 template.js学习

我们先看个例子:

HTML结构:

<table id="table1"></table>

js部分:

<script type="text/html" id="template1">

    <tr>

        <td>${ID}</td>

        <td>${Name}</td>

    </tr>

</script>

<script type="text/javascript" src="jquery-1.9.1.min.js"></script>

<script type="text/javascript" src="jquery.tmpl.js"></script>

<script type="text/javascript">

    var users = [

        {

            ID: 'think8848',

            Name: 'Joseph Chan',

            Langs: [

                'Chinese',

                'English'

            ]

        },

        {

            ID: 'aCloud',

            Name: 'Mary Cheung',

            Langs: [

                'Chinese',

                'French'

            ]

        }

    ];

$('#template1').tmpl(users).appendTo('#table1')</script>

可以看到模版被写在了type为text/html的script标签中,其中users是数据元,最后调用了一个$('#template1').tmpl(users)将信息写入模版,最后生成出的信息插入dom中,即完成。ok,来看一下jQuery原型上的tmpl方法

tmpl: function( data, options, parentItem ) {

            return jQuery.tmpl( this[0], data, options, parentItem );//页面调用的时候的入口方法,这会去调用jQuery上的tmpl方法

        }

进入jQuery上的tmpl方法

tmpl: function( tmpl, data, options, parentItem ) {

            var ret, topLevel = !parentItem;

            if ( topLevel ) {

                // This is a top-level tmpl call (not from a nested template using {{tmpl}})

                parentItem = topTmplItem;//{ key: 0, data: {} }

                tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl );//根据参数数量,选择性的执行jQuery.template方法,这里获得了一个先有正则匹配,再经过拼接,最后new Function而得到一个匿名函数

                wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level

            } else if ( !tmpl ) {

                // The template item is already associated with DOM - this is a refresh.

                // Re-evaluate rendered template for the parentItem

                tmpl = parentItem.tmpl;

                newTmplItems[parentItem.key] = parentItem;

                parentItem.nodes = [];

                if ( parentItem.wrapped ) {

                    updateWrapped( parentItem, parentItem.wrapped );

                }

                // Rebuild, without creating a new template item

                return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) ));

            }

            if ( !tmpl ) {

                return []; // Could throw...            }

            if ( typeof data === "function" ) {//传进来的数据看是否存在函数

                data = data.call( parentItem || {} );

            }

            if ( options && options.wrapped ) {

                updateWrapped( options, options.wrapped );

            }

            ret = jQuery.isArray( data ) ?

                jQuery.map( data, function( dataItem ) {

                    return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;

                }) :

                [ newTmplItem( options, parentItem, tmpl, data ) ];

            //进入最后一层加工

            return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret;

        }

对于这个例子,我们需要看一下这段代码的几个部分

第一个部分:

tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl );//根据参数数量,选择性的执行jQuery.template方法,这里获得了一个先有正则匹配,再经过拼接,最后new Function而得到一个匿名函数

tmpl参数则是那个写有模版的script对象,根据这个方法,我们进入jQuery.template方法。

//这里经过几次进入template方法,最终还是将type为text/html的script对象传入template方法的第二个参数中

        template: function( name, tmpl ) {

            if (tmpl) {

                // Compile template and associate with name

                if ( typeof tmpl === "string" ) {//如何该参数是一个字符串,这里支持将模版以字符串形式写入

                    // This is an HTML string being passed directly in.

                    tmpl = buildTmplFn( tmpl );

                } else if ( tmpl instanceof jQuery ) {

                    tmpl = tmpl[0] || {};//获取dom对象否则赋空对象                }

                if ( tmpl.nodeType ) {//如何该参数是一个dom节点// If this is a template block, use cached copy, or generate tmpl function and cache.

                    tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML ));//根据正则生成一个匿名函数返回// Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space.

                    // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x.

                    // To correct this, include space in tag: foo="${ x }" -> foo="value of x"                }

                return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;//jQuery.template方法返回了这个匿名函数,将匿名函数分装在jQuery.template[name]中便于以后调用            }

            // Return named compiled template

            return name ? (typeof name !== "string" ? jQuery.template( null, name ):

                (jQuery.template[name] ||

                    // If not in map, and not containing at least on HTML tag, treat as a selector.

                    // (If integrated with core, use quickExpr.exec)

                    jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null;

        }

这段代码中的一些逻辑判断,会在后面的API描述中介绍,我们先看到一个很重要的自定义方法buildTmplFn,这算是这个插件比较重要的一个部分。传入参数则是模版字符串

buildTmplFn:

function buildTmplFn( markup ) {

        //注意这里在return之前,会将Function构造器里的字符串生成匿名函数,注意这里的写法

        return new Function("jQuery","$item",

            // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10).

            "var $=jQuery,call,__=[],$data=$item.data;" +

 

            // Introduce the data as local variables using with(){}

            "with($data){__.push('" +

 

            // Convert the template into pure JavaScript            jQuery.trim(markup)

                .replace( /([\\'])/g, "\\$1" )//将\或者'前面都添加一个转义符\

                .replace( /[\r\t\n]/g, " " )//将空格符全部转成空字符串

                .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" )//将类似${name}这种写法的转成{{=name}},换句话说,在页面script中也可以使用${name}来赋值,这里都会统一转成{{=name}}格式

                .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,

                //replace的自定义方法中的参数个数表明正则所匹配的分组成员的个数,一般第一个参数是匹配的整个字符串,也就是说,上面的这条正则分组成员应该是6个

                function( all, slash, type, fnargs, target, parens, args ) {

                    /*

                    * type表示你具体需要显示的文本功能,我们这个例子是=,表示仅仅是显示

                    * */

                     var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect;

                    if ( !tag ) {//如何插件中不存在相应配置,抛出异常

                        throw "Unknown template tag: " + type;

                    }

                    def = tag._default || [];

                    if ( parens && !/\w$/.test(target)) {

                        target += parens;//拼接主干信息

                        parens = "";

                    }

                    //从正则的匹配来看,这个target是我们匹配获得的主要成员

                    if ( target ) {

                        target = unescape( target );//去转义符

                        args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : "");

                        // Support for target being things like a.toLowerCase();

                        // In that case don't call with template item as 'this' pointer. Just evaluate...

                        //以下两种方法主要拼接字符串,最后转成函数执行。

                        expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target;

                        exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";

                    } else {

                        exprAutoFnDetect = expr = def.$1 || "null";

                    }

                    fnargs = unescape( fnargs );//去转义符

                    //return的时候,再进行一次拼接,这里源码采用占位符的方式,先split再join的方式实现替换,大家也可以尝试使用正则替换。比较比较执行效率

                    return "');" +

                        tag[ slash ? "close" : "open" ]

                            .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" )//这种方法可以学习一下,先使用占位符站住你需要替换的信息,然后使用split分隔开成数组,再使用join方法加入参数合成字符串,在数组中join的效率还是不错的

                            .split( "$1a" ).join( exprAutoFnDetect )//将之前拼接好的字符串替换占位符$1a

                            .split( "$1" ).join( expr )//替换$1

                            .split( "$2" ).join( fnargs || def.$2 || "" ) +//依旧是替换

                        "__.push('";

                }) +

            "');}return __;"

        );

    }

其实这个方法的作用就是根据内置正则表达式,解析模版字符串,截取相应的数据,拼凑成一个以后使用的匿名函数。这个匿名函数的功能主要将我们之后传入的数据源users根据正则解析,加入到模版字符串中。既然正则是这个方法的核心,那我们就来看一下这些正则,前几个正则比较简单,最后一个正则比较复杂,我们将它做拆解来理解。

/*

    *           \{\{                                 --匹配{{

    *           (\/?)                                --优先匹配/,捕捉匹配结果                                 ($1)slash

    *           (\w+|.)                              --优先匹配字符,捕获匹配结果                              ($2)type

    *           (?:                                  --匹配但不捕获

    *               \(                               --匹配(

    *               (                                --捕获匹配结果                                           ($3)fnargs

    *                   (?:                          --匹配但不捕捉

    *                       [^\}]|\}                 --优先匹配非},如果有},要求匹配这个}后面不能再出现}

    *                       (?!\})                   --否定顺序环视,不能存在}

    *                   )*?                          --非优先匹配设定,尽可能少的去匹配

    *               )?                               --优先匹配

    *               \)                               --匹配)

    *           )?                                   --优先匹配

    *           (?:                                  --匹配但不捕捉

    *               \s+                              --优先匹配,匹配空格符,至少一个

    *               (.*?)?                           --非优先设定,尽可能少的去匹配,但必须要尽量尝试。         ($4)target

    *           )?                                   --优先匹配

    *           (                                    --捕获匹配结果                                          ($5)parens

    *               \(                               --匹配(

    *               (                                --捕获匹配结果                                          ($6)args

    *                   (?:                          --匹配但不捕获

    *                       [^\}]|\}                 --优先匹配非},如果有},要求匹配这个}后面不能再出现}

    *                       (?!\})                   --否定顺序环视,不能存在}

    *                   )*?                          --非优先匹配设定,尽可能少的去匹配

    *               )

    *               \)                               --匹配)

    *            )?                                  --优先匹配

    *            \s*                                 --优先匹配,空白符

    *            \}\}                                --匹配}}

    *            /g                                  --全局匹配

    *

    *

因为replace的解析函数中一共有7个参数,除了第一个参数表示全部匹配外,其他都是分组内的匹配。我在注释中都一一列出,方便我们阅读。观察一下正则,我们可以了解这个插件给与我们的一些语法使用,比如说:

页面模版内可以这样写:

${name}

{{= name}}

这两种写法都是对的,为什么前一条正则就是将${name}转成{{= name}},另外为什么=与name之间需要有空格呢?其实答案在正则里,看一下($4)target匹配的前一段是\s+,这表明必须至少要匹配一个空格符。先将我们缩写的格式转成{{= xx}}再根据(.*?)?查找出xx的内容,也就是name,其实正则的匹配过程并不是像我所说的这样,在js中的正则在量词的出现时,会进行优先匹配,然后再慢慢回溯,我这样只是形象的简单说一下。对于这条正则,我们在后续的API中继续延伸。

对于另外一个让我们学习的地方,那就是使用占位符插入我们所要的信息,一般我们都会使用正则,本插件也提供了一种不错的思路。先使用占位符,然后通过split(占位符)来分隔字符串,最后使用join(信息)来再次拼接字符串。这两个方法都是原生的,效率的话,我不太确定,应该还不错,有兴趣的朋友可以写写正则,在不同浏览器下比比看,谁的效率更高一点。

既然它生成了一个匿名函数,我们可以简单地打印一下看看:

function anonymous(jQuery, $item) {

   var $=jQuery,call,__=[],$data=$item.data;

   with($data){__.push('<tr>         <td>');

   if(typeof(ID)!=='undefined' && (ID)!=null){

      __.push($.encode((typeof(ID)==='function'?(ID).call($item):(ID))));

   }

   __.push('</td>         <td>');

   if(typeof(Name)!=='undefined' && (Name)!=null){

       __.push($.encode((typeof(Name)==='function'?(Name).call($item):(Name))));

}

    __.push('</td>     </tr>');}return __;

}

这里with有延长作用域的作用,在一般的开发中,不建议使用,不太易于维护,那这个with括号里的ID,Name其实都是$data.ID和$data.Name,在没有调用这个匿名函数之前,我们先简单看一下,传入的$item参数拥有data属性,如果这个data的ID和Name不是函数的话就正常显示,如果是函数的话,则这些方法需要通过$item来调用。另外匿名函数中也拥有了这钱我们所写的模版结构,后续的工作就是用真实的数据去替换占位符,前提非空。ok,回到jQuery的tmpl方法中,我们再看一个比较重要的部分。

ret = jQuery.isArray( data ) ?

                jQuery.map( data, function( dataItem ) {

                    return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;

                }) :

                [ newTmplItem( options, parentItem, tmpl, data ) ];

data是用户传入的信息元,就是users,是一个数组,调用jQuery.map来进行遍历,来调用newTmplItem方法,其中tmpl则是刚才我们生成的匿名函数。

function newTmplItem( options, parentItem, fn, data ) {

        // Returns a template item data structure for a new rendered instance of a template (a 'template item').

        // The content field is a hierarchical array of strings and nested items (to be

        // removed and replaced by nodes field of dom elements, once inserted in DOM).

 

        var newItem = {

            data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}),

            _wrap: parentItem ? parentItem._wrap : null,

            tmpl: null,

            parent: parentItem || null,

            nodes: [],

            calls: tiCalls,

            nest: tiNest,

            wrap: tiWrap,

            html: tiHtml,

            update: tiUpdate

        };

        if ( options ) {

            jQuery.extend( newItem, options, { nodes: [], parent: parentItem });

        }

        if ( fn ) {

            // Build the hierarchical content to be used during insertion into DOM

            newItem.tmpl = fn;

            newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem );

            newItem.key = ++itemKey;//表示计数

            // Keep track of new template item, until it is stored as jQuery Data on DOM element

            (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;//这里考虑一个页面可能多处使用模版,这里进行的编号,封装。        }

        return newItem;//最后返回这个newItem对象

    }

如果看到newItem的定义方式,或许之前我们对匿名函数的猜测有了一些佐证,没错,最后通过newItem.tmpl(jQuery,newItem)来调用了这个匿名函数,这个方法除了调用执行了匿名函数,还简单的封装了一下,便于以后我们调用$.tmplItem来获取相应的数据元信息。

 将生成好的ret传入最后一个加工方法build,完成整个模版的赋值

//将函数等细化出来,拼接成字符串

    function build( tmplItem, nested, content ) {

        // Convert hierarchical content into flat string array

        // and finally return array of fragments ready for DOM insertion

        var frag, ret = content ? jQuery.map( content, function( item ) {

            //给所有标签加上_tmplitem=key的属性,也就是这条正则的含义

            return (typeof item === "string") ?

                // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.

                (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) :

                // This is a child template item. Build nested template.                build( item, tmplItem, item._ctnt );

        }) :

        // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.        tmplItem;

        if ( nested ) {

            return ret;

        }

        // top-level template

        ret = ret.join("");//生成最终的模版

        // Support templates which have initial or final text nodes, or consist only of text

        // Also support HTML entities within the HTML markup.

        //这条正则比较简单,我们来看过一下。获得<>内的主要信息

        ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) {

            frag = jQuery( middle ).get();//将生成的jQuery dom对象转成数组集合,集合的每个成员则是对应生成的jQuery对象的原生dom对象

            //解析生成出来的dom            storeTmplItems( frag );

            if ( before ) {

                frag = unencode( before ).concat(frag);

            }

            if ( after ) {

                frag = frag.concat(unencode( after ));

            }

        });

        return frag ? frag : unencode( ret );

    }

 这个里面出现了两条正则,我们分别看一下:

*

    *       /

    *       (                                        --匹配捕获($1)

    *           <\w+                                 --匹配<,字母或数字或下划线或汉字(至少一个,优先匹配)(存在固化分组的含义)

    *       )

    *       (?=[\s>])                                --顺序环视,后面必须有空格和一个>

    *       (?![^>]*_tmplitem)                       --顺序否定环视,后面不能有非>字符,还有_tmplitem这些字符串

    *       (                                        --匹配捕获($2)

    *           [^>]*                                --匹配非>字符,优先匹配,任意多个

    *       )

    *       /g                                       --全局匹配

    *

*

    *       ^                                        --开始

    *       \s*                                      --优先匹配,任意多空白符

    *       (                                        --匹配捕获                                 ($1)before

    *           [^<\s]                               --匹配非<或者是空白符

    *           [^<]*                                --优先匹配,匹配非<

    *       )?                                       --优先匹配

    *       (                                        --匹配捕获                                 ($2)middle

    *           <[\w\W]+>                            --匹配<,任意字符(至少一个,优先匹配),>

    *       )

    *       (                                        --匹配捕获                                 ($3)after

    *           [^>]*                                --匹配非>

    *           [^>\s]                               --匹配非>或者是空白符

    *       )?                                       --优先匹配(0,1次)

    *       \s*                                      --匹配空白符(任意次,优先匹配)

    *       $                                        --结束

    *

    *

前一个正则的作用是给标签加上_tmplitem=key的属性,后一条正则则是获得<>内的主要信息。最后进入storeTmplItems方法

function storeTmplItems( content ) {

        var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;

        for ( i = 0, l = content.length; i < l; i++ ) {

            if ( (elem = content[i]).nodeType !== 1 ) {//如果该节点不是元素节点,则直接跳过

                continue;

            }

            //这里将会找到关键的几个元素节点,在模版中可能会存在注释节点,文本节点。

            //遍历元素节点

            elems = elem.getElementsByTagName("*");

            for ( m = elems.length - 1; m >= 0; m-- ) {//自减的遍历有时候比自增要好很多                processItemKey( elems[m] );

            }

            processItemKey( elem );

        }

作为储存节点的方法,使用processItemKey进行遍历。

function processItemKey( el ) {

            var pntKey, pntNode = el, pntItem, tmplItem, key;

            // Ensure that each rendered template inserted into the DOM has its own template item,

            //确保每个呈现模板插入到DOM项目有自己的模板

            if ( (key = el.getAttribute( tmplItmAtt ))) {//查看这个元素上是否有_tmplitem这个属性,限定了属于某个模版的内容

                while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { }//这种写法也比较不错,使用while不停向上查询pntNode的父节点

                if ( pntKey !== key ) {//父节点存在,但是没有_tmplitem这个属性,一般是文档碎片

                    // The next ancestor with a _tmplitem expando is on a different key than this one.

                    // So this is a top-level element within this template item

                    // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.

                    //如果该元素的父节点不存在,则可能是文档碎片

                    pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0;

                    if ( !(tmplItem = newTmplItems[key]) ) {

                        // The item is for wrapped content, and was copied from the temporary parent wrappedItem.

                        tmplItem = wrappedItems[key];

                        tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] );

                        tmplItem.key = ++itemKey;

                        newTmplItems[itemKey] = tmplItem;

                    }

 

                    if ( cloneIndex ) {

                        cloneTmplItem( key );

                    }

                }

                el.removeAttribute( tmplItmAtt );//最后去除_tmplitem这个属性

            } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) {

                //这是一个元素,呈现克隆在附加或appendTo等等

                //TmplItem存储在jQuery cloneCopyEvent数据已经被克隆。我们必须换上新鲜的克隆tmplItem。

                // This was a rendered element, cloned during append or appendTo etc.

                // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.                cloneTmplItem( tmplItem.key );

                newTmplItems[tmplItem.key] = tmplItem;

                pntNode = jQuery.data( el.parentNode, "tmplItem" );

                pntNode = pntNode ? pntNode.key : 0;

            }

            if ( tmplItem ) {//遍历到最外层的元素

                pntItem = tmplItem;

                //找到父元素的模板项。

                // Find the template item of the parent element.

                // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)

                while ( pntItem && pntItem.key != pntNode ) {//顶级为pntNode为0

                    // Add this element as a top-level node for this rendered template item, as well as for any

                    // ancestor items between this item and the item of its parent element                    pntItem.nodes.push( el );

                    pntItem = pntItem.parent;//向上迭代                }

                // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...

                delete tmplItem._ctnt;//删除属性

                delete tmplItem._wrap;//删除属性

                // Store template item as jQuery data on the element

                jQuery.data( el, "tmplItem", tmplItem );//这样可以$(el).data('tmplItem')读取tmplItem的值            }

            function cloneTmplItem( key ) {

                key = key + keySuffix;

                tmplItem = newClonedItems[key] =

                    (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent ));

            }

        }

根据之前添加的_tmplitem属性,做了完整的向上遍历查找,最后删除掉_tmplitem属性。build方法将frag参数uncode之后返回给jQuery.tmpl方法来返回,最后通过appendTo加入到dom中,生成我们所看到的结果。以上通过一个简单的例子粗略的过了一下插件的运行流程,我们来看一些官方的API。

1.$.template,将HTML编译成模版

 例子1

var markup = '<tr><td>${ID}</td><td>${Name}</td></tr>';

$.template('template', markup);

$.tmpl('template', users).appendTo('#templateRows');

直接看一下$.template方法

if ( typeof tmpl === "string" ) {//如何该参数是一个字符串,这里支持将模版以字符串形式写入

                    // This is an HTML string being passed directly in.

                    tmpl = buildTmplFn( tmpl );

                }

可以看到,我们传入的markup是一个字符串,直接将这个markup传入buildTmplFn中去生成一个匿名函数。

return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;//jQuery.template方法返回了这个匿名函数,将匿名函数分装在jQuery.template[name]中便于以后调用

插件内部将编译好的HTML模版的匿名函数存入了jQuery.template[name]中,便于我们以后调用。

tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl );//根据参数数量,选择性的执行jQuery.template方法,这里获得了一个先有正则匹配,再经过拼接,最后new Function而得到一个匿名函数

这里插件先查找了jQuery.template看是否存在tmpl的已经生成好的匿名函数,有则直接使用,否则重新生成。获得了匿名函数,其他步骤跟之前一样。

 

2.jQuery.tmpl()有两个比较有用的参数$item,$data,其中$item表示当前模版,$data表示当前数据

例子2

<script type="text/html" id="template1">

    <tr>

        <td>${ID}</td>

        <td>${$data.Name}</td>

        <td>${$item.getLangs(';')}</td>

    </tr>

</script>

var users = [

        {

            ID: 'think8848',

            Name: 'Joseph Chan',

            Langs: [

                'Chinese',

                'English'

            ]

        },

        {

            ID: 'aCloud',

            Name: 'Mary Cheung',

            Langs: [

                'Chinese',

                'French'

            ]

        }

    ]

    $('#template1').tmpl(users,{

        getLangs: function(separator){

            return this.data.Langs.join(separator);

        }

    }).appendTo('#table1');

<table id="table1"></table>

乍一看,调用的方式是一样的,你会疑问为什么模版里要用$item和$data这样的形式,其实你仔细看一下上个例子生成的匿名函数,就能发现这里这么写其实是为了更好的拼接。以下是这个例子所生成的匿名函数:

function anonymous(jQuery, $item) {var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('<tr>         <td>');if(typeof(ID)!=='undefined' && (ID)!=null){__.push($.encode((typeof(ID)==='function'?(ID).call($item):(ID))));}__.push('</td>         <td>');if(typeof($data.Name)!=='undefined' && ($data.Name)!=null){__.push($.encode((typeof($data.Name)==='function'?($data.Name).call($item):($data.Name))));}__.push('</td>         <td>');if(typeof($item.getLangs)!=='undefined' && ($item.getLangs)!=null){__.push($.encode($item.getLangs(';')));}__.push('</td>     </tr>');}return __;

$data是$item的一个属性,存储着数据,$item中同样有很多自定义方法。这里getLangs方法里的this在匿名函数具体调用的时候会指向$item,这里需要注意一下。在newTmplItem方法里执行我们生成的匿名函数,这里都没有什么问题,这里我们通过正则简单回看一下这个${ID},${$data.Name}是如何匹配的。这两个匹配其实是一个道理,匹配的正则如下:

/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g

大家对照我之前的分解表看比较方便。我们拿${$data.Name}举例,不过使用之前,它已经转成{{= $data.Name}}

1:匹配{{

2:尝试匹配(\/?),?表示优先匹配,但是{{后面没有/,所以匹配无效,?表示最多成功匹配一个。继续后面的匹配

3:尝试匹配(\w+|.),如果|左边的能匹配成功则不需要进行右边的匹配,所以\w+会尽可能去匹配,但是\w无法匹配=所以,尝试用|右边的.去匹配,.可以匹配=,因为没有量词,所以只能匹配这个=

4:尝试匹配(?:\(((?:[^\}]|\}(?!\}))*?)?\))?

  4.1:(?:)表示匹配但不捕获,其里面第一个要匹配的是(,可以看到{{=后面是空格而不是(所以匹配失败,加上这不捕获的分组使用的是优先量词?,允许匹配为空,继续后面的匹配

5:尝试匹配(?:\s+(.*?)?)?

  5.1:分组里第一个匹配\s+,匹配=后面的空格符号,继续尝试匹配,当匹配到$时发现无法匹配,则\s+匹配结束。

  5.2:尝试匹配(.*?)?,分组外围使用的是?,尽可能尝试匹配一个看看,对于(.*?)匹配$,因为(.*?)是惰性匹配(不优先匹配),所以系统选择不匹配,另外外围的?也允许匹配不成功。继续后面的匹配

6:尝试匹配(\(((?:[^\}]|\}(?!\}))*?)\))?

  6.1:如果4的步骤不匹配,那5中的\(同样无法匹配$,所以匹配失败

7:尝试匹配\s*\}\},如果从$开始匹配,果断匹配失败。整个匹配结束了么?其实还没有,开始对惰性匹配继续进行匹配

8:让(.*?)先匹配$,再执行5,6步骤,如果最终匹配失败了,继续让(.*?)匹配$d,依次类推,直到(.*?)匹配到$data.Name,这时6结果匹配成功。整个正则匹配匹配成功。

以上则是该正则的一次简单匹配过程,可以发现该正则使用了惰性匹配一定程度上减少了正则的回溯次数,提高了效率。

 

3.each的用法

例子:

<script type="text/html" id="template1">

    <li>

        ID: ${ID}; Name: ${Name};

        <br />Langs:

        <ul>

            <STRONG>

                {{each(i,lang) Langs}}

                <li>${i + 1}:

                    <label>${lang}. </label>

                </li>

                {{/each}}

            </STRONG>

        </ul>

    </li>

 

</script>var users = [

        {

            ID: 'think8848',

            Name: 'Joseph Chan',

            Langs: [

                'Chinese',

                'English'

            ]

        },

        {

            ID: 'aCloud',

            Name: 'Mary Cheung',

            Langs: [

                'Chinese',

                'French'

            ]

        }

    ];

    $('#template1').tmpl(users).appendTo('#eachList')

<ul id="eachList"></ul>

运行过程基本一致,我们就看两个部分:

3.1:正则匹配

3.2:如何实现each

之前的${ID},${Name}和之前的匹配是一致的,这里就不描述了,看一下这段字符串的匹配。

{{each(i,lang) Langs}}

                <li>${i + 1}:

                    <label>${lang}. </label>

                </li>

                {{/each}}

主要是{{each(i,lang) Langs}}和{{/each}}这两条的匹配

{{each(i,lang) Langs}}

1:匹配{{

2:尝试匹配(\/?),/不能与e相匹配,所以匹配失败,因为存在?量词,继续下面的匹配

3:尝试匹配(\w+|.),其中\W+是优先匹配,所以它一直匹配到each,当它尝试匹配(时,发现匹配失败时,则就返回匹配结果each进入分组,继续下面的匹配

4:尝试匹配(?:\(((?:[^\}]|\}(?!\}))*?)?\))?

  4.1:首先匹配\(

  4.2:尝试匹配((?:[^\}]|\}(?!\}))*?)?

    4.2.1:尝试匹配(?:[^\}]|\}(?!\}))*?,这里实际就是两个部分[^\}]|\}和(?!\}),这里的正则写的有点复杂,其实也不难理解。这两个匹配他使用(?:)*?表示匹配后不捕捉,并且是惰性匹配,而却在它的外层加了()?,表示捕获分组,可想而                        知是为了能更多的捕捉到全部的全部条件的字符串,因为里层的是惰性匹配,所以系统默认不匹配,继续后面的匹配

5:尝试匹配(?:\s+(.*?)?)?,发现i无法与\s+匹配,匹配失败,返回到惰性匹配那。

6:尝试让惰性匹配(?:[^\}]|\}(?!\}))*?去匹配字符串,我们先看一下[^\}]|\}(?!\}),这样看,以|为分割点,左边是[^\}],右边是\}(?!\}),这就清楚了,可以匹配非}的字符,如果匹配失败,就匹配},但是它的后面不能再有},所以系统先使用[^\}]去匹配i,再去执行5,如果5仍不能满足,则继续匹配i,直到5匹配满足,而此时系统已经匹配到了(i,lang)

7:(?:\s+(.*?)?)?中的(.*?)?依旧是惰性匹配,系统先尝试不匹配

8:尝试匹配(?:\(((?:[^\}]|\}(?!\}))*?)?\))?,发现匹配失败,因为量词的缘故,继续后续的匹配

9:尝试匹配\s*\}\},如果从$开始匹配,果断匹配失败。

10:返回到惰性匹配那,让(.*?)尝试匹配L,再执行8,9步,直到它能满足,如果不能正则匹配不成功。最后(.*?)匹配了Langs,完成了整个正则的匹配。

 那{{/each}}则就是一个道理。但要注意这个/,因为如果/匹配了,那replace匹配函数中的slash将会是/,则根据tag[ slash ? "close" : "open" ],它将使用tag['close']来闭合这个each,这也就是为什么拥有open的close的原因。

 

关于each是如何实现的,我们需要看到源码的这个部分:

"each": {

                _default: { $2: "$index, $value" },

                open: "if($notnull_1){$.each($1a,function($2){with(this){",

                close: "}});}"

            }

replace的匹配方法中有7个参数,其中type参数就是each,根据

var tag = jQuery.tmpl.tag[ type ]

这里我们可以看到其实实现each的功能仅仅是将$.each写入字符串中,它的参数有$index和$value,这其实就是jQuery的each方法。代码的后续会将其取出,进行拼接。

 

4.if和else的用法

例子:

<script type="text/html" id="template1">

    <tr>

        <td>${ID}</td>

        <td>${Name}</td>

        <td>

            {{if Langs.length > 1}}

                ${Langs.join('; ')}

            {{else}}

                ${Langs}

            {{/if}}

        </td>

    </tr>

</script>

    var users = [

        {

            ID: 'think8848',

            Name: 'Joseph Chan',

            Langs: [

                'Chinese',

                'English'

            ]

        },

        {

            ID: 'aCloud',

            Name: 'Mary Cheung',

            Langs: [

                'Chinese',

                'French'

            ]

        }

    ]

$('#template1').tmpl(users).appendTo('#table1');

<table id="table1"></table>

其实if,else跟each差不多在正则匹配的时候,这里我就不重复了。看一下对应的函数

"if": {

                open: "if(($notnull_1) && $1a){",

                close: "}"

            },

            "else": {

                _default: { $1: "true" },

                open: "}else if(($notnull_1) && $1a){"

            },

 注意一下,在这里if拥有close而else则没有,反映到模版书写上,闭合的时候我们只需要写{{/if}}就可以了,不需要写{{/else}}

 

5.html占位符

例子5:

<script type="text/html" id="template1">

    <tr>

        <td>${ID}</td>

        <td>${Name}</td>

        <td>{{html Ctrl}}</td>

    </tr>

</script>

var users = [

    {

        ID: 'think8848',

        Name: 'Joseph Chan',

        Ctrl: '<input type="button" value="Demo"/>'

    },

    {

        ID: 'aCloud',

        Name: 'Mary Cheung',

        Ctrl: '<input type="button" value="Demo"/>'

    }

];

    $('#template1').tmpl(users).appendTo('#table1')

    $('table').delegate('tr','click',function(){

        var item = $.tmplItem(this);

        alert(item.data.Name);

    })

<table id="table1"></table>

这里看一下模版的{{html Ctrl}},匹配规则还是一样的。看一下拓展的部分:

"html": {

                // Unecoded expression evaluation.

                open: "if($notnull_1){__.push($1a);}"

            }

注意,这时允许你脚本插入的,也就是如果你插入一个<script type="text/javascript" >alert(1)<\/script>,生成的页面是可以弹出alert(1)的。这跟跟换ID和Name是一个意思。

 

6.{{tmpl}}

 例子6:

<script type="text/html" id="template1">

    <tr>

        <td>${ID}</td>

        <td>${Name}</td>

        <td>{{tmpl($data) '#template2'}}</td>

    </tr>

</script>

<script type="text/html" id="template2">

    {{each Langs}}

        ${$value}

    {{/each}}

</script>var users = [

        {

            ID: 'think8848',

            Name: 'Joseph Chan',

            Langs:[

                'Chinese',

                'English'

            ]

        },

        {

            ID: 'aCloud',

            Name: 'Mary Cheung',

            Langs: [

                'Chinese',

                'French'

            ]

        }

    ];

    $('#template1').tmpl(users).appendTo('#table1');

<table id="table1"></table>

看一下{{tmpl($data) '#template2'}},正则匹配是跟以前一样的。我们看一下扩展

"tmpl": {

                _default: { $2: "null" },

                open: "if($notnull_1){__=__.concat($item.nest($1,$2));}"

                // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)

                // This means that {{tmpl foo}} treats foo as a template (which IS a function).

                // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.

            }

注意里面有个方法nest,找到newTmplItem方法里的我们定义的newItem,看一下,它里面是否有个属性是nest,有,是tiNest,看一下tiNest

function tiNest( tmpl, data, options ) {

        // nested template, using {{tmpl}} tag

        return jQuery.tmpl( jQuery.template( tmpl ), data, options, this );

    }

这里我们大概可以了解这种解析过程,先template1的模版,我们在template1中标记了tmpl,当我们第一次执行匿名函数的时候,它执行nest方法,再次去执行jQuery.tmpl,然后你们懂的,生成关于template2的匿名函数等等。所以这里模版的1中的指向id千万不要写错,否则报错。

看到jQuery.template方法中的这个部分

return name ? (typeof name !== "string" ? jQuery.template( null, name ):

                (jQuery.template[name] ||

                    // If not in map, and not containing at least on HTML tag, treat as a selector.

                    // (If integrated with core, use quickExpr.exec)

                    jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null;

因为我们第一次没有存储匿名函数(保存模板的作用),也不需要存储。所以执行jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )),这里我们看到一条正则

htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /

这个比较简单,留给读者吧,呵呵。匹配结果当然是不满足,我们使用jQuery()去创建jQuery对象,重新执行template方法,生成相应的匿名函数等。

 

7.{{wrap}}包装器

例子:

<script type="text/html" id="myTmpl">

    The following wraps and reorder some HTML content:

    {{wrap "#tableWrapper"}}

    <h3>One</h3>

    <div>

        First: <b>content</b>

    </div>

    <h3>Two</h3>

    <div>

        And <em>more</em> <b>content</b>

    </div>

    {{/wrap}}

</script>

<script type="text/html" id="tableWrapper">

    <table cellspacing="0" cellpadding="3" border="1">

        <tbody>

            <tr>

                {{each $item.html("h3",true)}}

                <td>

                    ${$value}

                </td>

                {{/each}}

            </tr>

            <tr>

                {{each $item.html("div")}}

                <td>

                    {{html $value}}

                </td>

                {{/each}}

            </tr>

        </tbody>

    </table>

</script>

<div id="wrapDemo"></div>

依照惯例,看一下拓展部分

"wrap": {

                _default: { $2: "null" },

                open: "$item.calls(__,$1,$2);__=[];",

                close: "call=$item.calls();__=call._.concat($item.wrap(call,__));"

            }

这里我们看到了两个新方法:calls()和wrap(),找到newTmplItem里面的newItem,来看一下这两个方法

 calls:

function tiCalls( content, tmpl, data, options ) {

        if ( !content ) {

            return stack.pop();

        }

        stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options });

    }

wrap:

function tiWrap( call, wrapped ) {

        // nested template, using {{wrap}} tag

        var options = call.options || {};

        options.wrapped = wrapped;

        // Apply the template, which may incorporate wrapped content,

        return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item );

    }

这跟6的运行模式差不多,很不幸的是,我的源码在执行这个例子的时候出错,后来我找了一段时间后发现问题,将源码修改了一下。恢复正常了。修改tiHtml方法里

return jQuery.map(

            jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : jQuery.trim(wrapped) ).filter( filter || "*" ),

            function(e) {

                return textOnly ?

                    e.innerText || e.textContent :

                    e.outerHTML || outerHtml(e);

            });

7和6例子一样,在匿名函数执行的时候,重新执行了jQuery.tmpl获取了新模板的内容,生成了匿名函数,如果你们有功夫看一下生成的匿名函数,你们会发现里面都很多newItem事先定义好的方法调用,然后在执行这些匿名函数的时候,依次调用这些方法。

 

8.$.tmplItem()

例子可以看例子5,其实这个方法就很简单了。看一下源码

tmplItem: function( elem ) {

            var tmplItem;

            if ( elem instanceof jQuery ) {

                elem = elem[0];

            }

            while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {}//获取data信息,用户传入的内容信息

            return tmplItem || topTmplItem;

        }

可以看到这个while循环不断向上查询,因为在我们第一个例子中,我们在storeTmplItems方法中,进行一定的保存。这里就是查找到显示出来。

 

9.结语

以上基本完成了一个源码的阅读,从中学习的东西有很多,类似模板一类的框架,需要一个强大的正则解析,需要能将数据元与字符串很好结合的方法,而这个框架则是用正则生成这个方法。这个框架也提供了一些向上遍历的方式,大家都可以借鉴。这里暂时不讨论该框架的执行效率。我们以后还会接触到别的更好更强大的框架。这只是个开始。内容不多,时间刚好,这是我的读码体会,可能不全,也会有错误,希望园友们提出来,大家一起探讨学习。

 

希望这篇文章可以帮助到你。总之,同学们,你想要的职坐标IT频道都能找到!

本文由 @阿萨 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程