开篇

  关于knockout的文章,园里已经有很多大神写过了,而且都写得很好。其实knockout学习起来还是很容易的,看看官网的demo和园里的文章,练习练习就可以上手了(仅限使用,不包含研究源码)。之所以想写这个系列,主要是想记录自己的学习和应用过程,也希望能给初学者一点帮助。

  既然是学习过程就一步一步来,从最开始的解决方案,到优化过程,到最后的实现方案。有了思考和对比,才会更加明白这个东西有什么好处,为什么使用它、什么情况要使用它。ok, 官网学习链接为☺:knockoutJS

准备例子  

  过程是这样的:前台发送ajax请求,后台返回json字符串,前台生成html,插入到dom。这个过程我们再熟悉不过了,接下来我们就用多种方式完成这个例子。

  先用jquery简单写一个发送请求的方法,如下:

window.Tester = {
callback: function(fn) {
$.ajax({
url: "../Handlers/GetCourse.ashx",
success: function(data) {
data = $.parseJSON(data);
fn(data);
}
});
}
}

  后台对应的实体对象,如下:

    public class CourseInfo
{
public string CourseID { get; set; }
public string IconPath { get; set; }
public string CourseName { get; set; }
public string TeacherName { get; set; }
public string CreatedDate { get; set; }
public int StudyNumber { get; set; }
}

  html如下:

        <ul id="course">
<li>
<a href="/Default.aspx?courseID=001">
<div class="course-img">
<img src="../Image/1.jpg" />
</div>
<div class="course-info">
<div class="names">
<span>jquery源码解析</span>
<span class="fr">李老师</span>
</div>
<div class="pros">
<span>2015-08-08</span>
<span class="fr">100人学习</span>
</div>
</div>
</a>
</li>
</ul>

  界面效果:

一、拼接字符串

  相信很多人开始都用过拼接字符串来生成dom元素,然后越写越多,越写越乱...,写到自己都看不太懂了,最后干脆挥挥手留给别人去看。我们都不希望这样做,有代码洁癖的朋友,看到这些应该会发狂。  

  我们来看一下实现上面的效果,用拼接字符串是怎么样的,代码如下:

    Tester.callback(function(data) {
for (var i = 0; i < data.length; i++) {
var courseImg = "<div class='course-img'><img src='" + data[i].IconPath + "' alt='" + data[i].CourseName + "'/></div>";
var names = "<div class='names'><span>" + data[i].CourseName + "</span><span class='fr'>" + data[i].TeacherName + "</span></div>";
var pros = "<div class='pros'><span>" + data[i].CreatedDate + "</span><span class='fr'>" + data[i].StudyNumber + "人学习</span></div>";
var item = "<li><a target='_blank' href='Default.aspx?courseID=" + data[i].CourseID + "'>" + courseImg + "<div class='course-info'>" + names + pros + "</div></a></li>";
$("#course").append(item);
}
});

  可以很快得出下面几点:1.拼接写起来很麻烦  2.不能给人清晰的dom结构 3.到处都是字符串修改起来很麻烦。实际项目中,我们应该尽量避免这种情况。

二、clone dom

  为了解决上面的缺点,我们可以把html模板先写好,并隐藏。等到需要时,再clone一份,生成html。代码如下:  

    <div id="tmp" class="noen">
<ul>
<li id="tmpItem">
<a>
<div class="course-img">
<img/>
</div>
<div class="course-info">
<div class="names">
<span></span>
<span class="fr"></span>
</div>
<div class="pros">
<span></span>
<span class="fr"></span>
</div>
</div>
</a>
</li>
</ul>
</div>
    Tester.callback(function(data) {
for (var i = 0; i < data.length; i++) {
var item = $("#tmpItem").clone();
item.find("a").attr("href", "Default.aspx?CourseID=" + data[i].CourseID);
item.find(".course-img>img").attr({ "src": data[i].IconPath, "alt": data[i].CourseName });
item.find(".names>span:eq(0)").text(data[i].CourseName);
item.find(".names>span:eq(1)").text(data[i].TeacherName);
item.find(".pros>span:eq(0)").text(data[i].CreatedDate);
item.find(".pros>span:eq(1)").text(data[i].StudyNumber + "人学习");
$("#course").append(item);
}
});

  看起来比拼接字符串好多了。这里我们提到了“模板”的概念,但它还不是真正意义上的模板,所谓模板应该是:基础内容准备好了,就差数据,只要把数据传递过来,就可以生成完整内容。可以看到,我们上面还是自己去解析数据,然后生成内容,而不是自动化的过程。如果可以这样生成html就最好了:var html = template("#tmpID",data); tmpID 表示模板的id,data 是数据,这样生成html,不用自己去for遍历。没错,这就是大多数模板引擎的实现思路。

三、模板引擎

  关于js模板引擎有很多,我也会在下一篇文章单独介绍。不过在这里我不想马上就用现成的,我们自己先实现试试看!

3.1 基础版

  首先我们需要找到字符串中真实数据的位置,这通常是通过“占位符”来实现的,例如:${ $};然后再将占位符替换为真实的数据。查找占位符可以用正则表达式实现,替换占位符用字符串操作即可。

  例如字符串:my name is ${name$}, i am ${year$} years old。 数据为:{name : "tom", year : 18}。我们希望生成最后的结果是: my name is tom, i am 18 years old。

  先编写匹配占位符的正则表达式:/\${((?:.(?!\$}))*.)?\$}/g (说明:正则水平一般,卡了好久...,厉害的朋友在回复写出更好的!)。实现代码如下:

    var reg = /\${((?:.(?!\$}))*.)?\$}/g;
var str = "my name is ${name$}, i am ${year$} years old";
var data = {
name : "tom",
year : 18
}
var match;
while (match = reg.exec(str)) {
str = str.replace(match[0], data[match[1]]);
}
console.log(str);//my name is tom, i am 18 years old

  简单解释一下:核心是exec方法,它返回的是一个数组,包括匹配到字符串的值,和其位置等。match[0] 是占位符;match[1] 是占位为内的内容(如name)。这样通过一个循环,就可以将所有匹配找到。

 3.2 改进版

  上面例子实在太简单了,看一个稍微复杂点的结构。字符串是:my name is ${name$}, i am ${info.age$} years old。数据为:{name: "tom", info: {age:18}}。按上面的做法就不能得到正确的结果了,因为匹配后 match[1] 为 “info.age”,而 data["info.age"] 显然不能获取到18。如果可以在字符串里写js呢,例如:this.name或this.info.age,运行时this由我们传递并执行,这样问题就解决了。这里有两个问题:1. 如何在字符串里写js代码?  2.this 如何动态决定?

  要在字符串里写代码执行,Function 就可以实现。Function接收字符串类型的参数,前面的是函数的参数,最后一个是函数的执行体。例如:var fn = new Function("arg1","arg2","return arg1 + arg2;"); fn 就是一个函数,接收两个参数。可以执行得到结果:console.log(fn(1,2)); //3。那么 this 如何由我们动态决定呢?答案就是:对象冒充。js 的 call, apply 就是用来实现对象冒充的。

  解决了这两个问题,实现起来就轻松多了,如下:

    var code = "return 'my name is ' + this.name + ', i am ' + this.info.age + ' years old';";
var fn = new Function(code).apply(data);
console.log(fn);

  这里我们创建一个函数,函数执行体就是code,this指向了data对象。注意,这里 this.name 不能加'',否则就作为普通字符串进行拼接了。字符串拼接太麻烦了,在网上看到一种很好的做法,通过数组实现,代码如下:

    var code = "var result = [];"
code += "result.push('my name is ');";
code += "result.push(this.name);";
code += "result.push(' i am ');";
code += "result.push(this.info.age);";
code += "result.push(' years old');";
code += "return result.join('');";
var fn = new Function(code).apply(data);
console.log(fn());

  同样,数据部分不能加''。这种方式很巧妙,fn 执行时,会从 var  result = []; 开始执行,this 就是 data 对象,最后生成字符串返回。这里我们简单封装一下:

    var str = "my name is ${this.name$}, i am ${this.info.age$} years old";
var data = {
name: "tom",
info: {age:18}
}
function template(html, data) {
if (!html) {
return;
}
var reg = /\${((?:.(?!\$}))*.)?\$}/g;
var cursor = 0;
var code = "var result = [];\n";
var match;
while (match = reg.exec(html)) {
code += "result.push('" + html.substring(cursor, match.index) + "');\n";
code += "result.push(" + match[1] + ");\n";
cursor = match.index + match[0].length;
}
code += "result.push('" + html.substring(cursor) + "');\n";
code += "return result.join('')";
//console.log(code);
return new Function(code.replace(/\n/g,"")).apply(data);
}
console.log(template(str, data));

3.3 最终版

  许多时候后台返回的是json数组字符串,这时需用使用逻辑判断和循环来处理。这里需要一个正则:/(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g 用来匹配判断循环关键字。需要注意的是,当遇到这些关键字的时候,就不能push到数组里了,而应该是作为程序的一部分执行,例如:

  var result = [];

  for(var i=0;i<10;i++){

    result.push(this.name);

  }

  ...

  结合上面的,封装一个最终版,如下:

    function template(id, data) {
if (!id) {
throw new Error("模板id不能为空!");
}
var jTmpl = $(id);
if(jTmpl.length <= 0){
throw new Error("找不到id为:"+id+"的模板");
}
var html = jTmpl.html();
if(!html){
return html;
}
html = html.replace(/\"/g,"\\\"");
var reg = /\${((?:.(?!\$}))*.)?\$}/g;
var logicReg = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g;
var cursor = 0;
var code = "var result = [];\n";
var match;
var key;
while (match = reg.exec(html)) {
code += "result.push('" + html.substring(cursor, match.index) + "');\n";
code += match[1].match(logicReg) ? match[1] : "result.push(" + match[1] + ");";
code += "\n";
cursor = match.index + match[0].length;
}
code += "result.push('" + html.substring(cursor) + "');\n";
code += "return result.join('')";
//console.log(code.replace(/\n/g, ""));
return new Function(code.replace(/\n/g, "")).apply(data);
}

  我们试着用这个模板完成上面拼接字符串和clone dom 相同的功能。先定义模板:

<script type="text/tmpl" id="courseTmpl">
${for(var i=0,length=this.length;i<length;i++){$}
<li>
<a href="Default.aspx?courseID=${this[i].CourseID$}">
<div class="course-img">
<img src="${this[i].IconPath$}" alt="${this[i].CourseName$}"/>
</div>
<div class="course-info">
<div class="names">
<span>${this[i].TeacherName$}</span>
<span class="fr">${this[i].CourseName$}</span>
</div>
<div class="pros">
<span>${this[i].CreatedDate$}</span>
<span class="fr">${this[i].StudyNumber$}人学习</span>
</div>
</div>
</a>
</li>
${}$}
</script>

  模板定义好后,执行代码就只有一行了!如下:

    Tester.callback(function(data) {
$("#course").html(template("#courseTmpl",data));
});

  通过使用模板引擎,我只需要定义好模板,传递数据,渲染工作就由模板引擎自动完成了。

  这里还有一个小知识点,script的type属性设置为:text/tmpl,这个属性是浏览器不认识的。如果script的type是浏览器支持的(如text/javascript),就会当做脚本执行或通过src属性请求下载脚本再执行,如果是浏览器不支持的,就会忽略。所以这里可以用来存储数据,大多数模板也都是定义在这个地方。

四、总结

  上面的模板引擎很简单,只有30行左右,但它其实已经可以解决一些简单的问题了。实际它还有许多问题没考虑,书写起来还是比较复杂的,也不可能针对多变的需求都适用,所以还是建议用于简单的应用或学习。很好的是,它让我们明白了整个解决思路和模板运行的过程。

  实际上现成的模板引擎已经很多了,接下来一篇就将介绍其中一个。

knockoutJS学习笔记01:从拼接字符串到编写模板引擎的更多相关文章

  1. SaToken学习笔记-01

    SaToken学习笔记-01 SaToken版本为1.18 如果有排版方面的错误,请查看:传送门 springboot集成 根据官网步骤maven导入依赖 <dependency> < ...

  2. Redis:学习笔记-01

    Redis:学习笔记-01 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 1. Redis入门 2.1 ...

  3. 软件测试之loadrunner学习笔记-01事务

    loadrunner学习笔记-01事务<转载至网络> 事务又称为Transaction,事务是一个点为了衡量某个action的性能,需要在开始和结束位置插入一个范围,定义这样一个事务. 作 ...

  4. C++ GUI Qt4学习笔记01

    C++ GUI Qt4学习笔记01   qtc++signalmakefile文档平台 这一章介绍了如何把基本的C++只是与Qt所提供的功能组合起来创建一些简单的图形用户界面应用程序. 引入两个重要概 ...

  5. Yii框架学习笔记(二)将html前端模板整合到框架中

    选择Yii 2.0版本框架的7个理由 http://blog.chedushi.com/archives/8988 刚接触Yii谈一下对Yii框架的看法和感受 http://bbs.csdn.net/ ...

  6. Hadoop学习笔记(4) ——搭建开发环境及编写Hello World

    Hadoop学习笔记(4) ——搭建开发环境及编写Hello World 整个Hadoop是基于Java开发的,所以要开发Hadoop相应的程序就得用JAVA.在linux下开发JAVA还数eclip ...

  7. Node学习之(第三章:art-template模板引擎的使用)

    前言 大家之前都有使用过浏览器中js模板引擎,其实在Node.js中也可以使用模板引擎,最早使用模板引擎的概念是在服务端新起的. art-template art-template是一款高性能的Jav ...

  8. KnockoutJs学习笔记(五)

    作为一名初学者来说,一篇篇的按顺序看官网上的文档的确是一件很痛苦的事情,毕竟它的排列也并非是由浅及深的排列,其中的顺序也颇耐人寻味,于是这篇文章我又跳过了Reference部分,进而进入到具体的bin ...

  9. PHP 学习笔记 01

    例子: 为什么要学PHP 主观原因: 前段时间在学校处理了毕业的一些事情,回到上海后开始了找工作的旅程.意向工作是WPF开发或者ASP.NET 作为后端的WEB开发. 陆陆续续一直在面试,其中有一家公 ...

随机推荐

  1. 【小贴士】【stringify神BUG】【localstorage失效】【消灭Safari alert框】【是否延迟加载】【页面10px白屏】

    前言 最近碰到几个恶心问题,也发现一点优化技巧,以及对Hybrid知识的一些整理,这里便一并拿出来做分享了,关于Hybrid的调试,会是我今后一个重点 我的博客首先是学习笔记,方便自己做知识沉淀,以后 ...

  2. 设计一个自动生成棋盘格子的JS小程序

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  3. javascript移动设备Web开发中对touch事件的封装实例

    在触屏设备上,一些比较基础的手势都需要通过对 touch 事件进行二次封装才能实现.zepto 是移动端上使用率比较高的一个类库,但是其 touch 模块模拟出来的一些事件存在一些兼容性问题,如 ta ...

  4. jq样式方法总结

    .html()方法 获取集合中第一个匹配元素的HTML内容 或 设置每一个匹配元素的html内容,具体有3种用法: .html() 不传入值,就是获取集合中第一个匹配元素的HTML内容 .html( ...

  5. Android开发4: Notification编程基础、Broadcast的使用及其静态注册、动态注册方式

    前言 啦啦啦~(博主每次开篇都要卖个萌,大家是不是都厌倦了呢~) 本篇博文希望帮助大家掌握 Broadcast 编程基础,实现动态注册 Broadcast 和静态注册 Broadcast 的方式以及学 ...

  6. Sharepoint学习笔记—ECM系列—文档列表的Metedata Navigation与Key Filter功能的实现

    如果一个文档列表中存放了成百上千的文档,想要快速的找到你想要的还真不是件容易的事,Sharepoint提供了Metedata Navigation与Key Filter功能可以帮助我们快速的过滤和定位 ...

  7. android gradle NDK简介

    本章介绍在Android开发中,关于NDK,gradle相关的知识点. 1.NDK简介 (1)NDK是一系列工具的集合 NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将s ...

  8. Linux SSH登录慢案例分析

    手头有台Linux服务器ssh登录时超级慢,需要几十秒.其它服务器均没有这个问题.平时登录操作都默默忍了.今天终于忍不住想搞清楚到底什么原因.搜索了一下发现了很多关于ssh登录慢的资料,于是自己也学着 ...

  9. windows系统快捷操作の进阶篇

    上次介绍了windows系统上一些自带的常用快捷键,有些确实很方便,也满足了我们的一部分需求.但是我们追求效率的步伐怎会止步于此?这一次我将会进一步介绍windows上提升效率的方法. 一:运行 打开 ...

  10. docker核心原理

    容器概念. docker是一种容器,应用沙箱机制实现虚拟化.能在一台宿主机里面独立多个虚拟环境,互不影响.在这个容器里面可以运行着我饿们的业务,输入输出.可以和宿主机交互. 使用方法. 拉取镜像 do ...