[后端人员耍前端系列]KnockoutJs篇:使用WebApi+Bootstrap+KnockoutJs打造单页面程序

13 篇文章 0 订阅

一、前言

  在前一个专题快速介绍了KnockoutJs相关知识点,也写了一些简单例子,希望通过这些例子大家可以快速入门KnockoutJs。为了让大家可以清楚地看到KnockoutJs在实际项目中的应用,本专题将介绍如何使用WebApi+Bootstrap+KnockoutJs+Asp.net MVC来打造一个单页面Web程序。这种模式也是现在大多数公司实际项目中用到的。

二、SPA(单页面)好处

  在介绍具体的实现之前,我觉得有必要详细介绍了SPA。SPA,即Single Page Web Application的缩写,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。

  单页面程序的好处在于:

  1. 更好的用户体验,让用户在Web app感受native app的速度和流畅。
  2. 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起。
  3. 减轻服务器压力,服务器只用生成数据就可以,不用管展示逻辑和页面逻辑,增加服务器吞吐量。MVC中Razor语法写的前端是需要服务器完成页面的合成再输出的。
  4. 同一套后端程序,可以不用修改直接用于Web界面、手机、平板等多种客户端。

  当然单页面程序除了上面列出的优点外,也有其不足:

  1. 不利于SEO。这点如果是做管理系统的话是没影响的
  2. 初次加载时间相对增加。因为所有的JS、CSS资源会在第一次加载完成,从而使得后面的页面流畅。对于这点可以使用Asp.net MVC中Bundle来进行文件绑定。关于Bundle的详细使用参考文章:http://www.cnblogs.com/xwgli/p/3296809.htmlhttp://www.cnblogs.com/wangiqngpei557/p/3309812.html
  3. 导航不可用。如果一定要导航需自行实现前进、后退。对于这点,可以自行实现前进、后退功能来弥补。其实现在手机端网页就是这么干的,现在还要上面导航的。对于一些企业后台管理系统,也可以这么做。
  4. 对开发人员技能水平、开发成本高。对于这点,也不是事,程序员嘛就需要不断学习来充电,好在一些前端框架都非常好上手。

三、使用Asp.net MVC+WebAPI+Bootstrap+KnockoutJS实现SPA

  前面详细介绍了SPA的优缺点,接下来,就让我们使用Asp.net MVC+WebAPI+BS+KO来实现一个单页面程序,从而体验下SPA流畅和对原始Asp.net MVC +Razor做出来的页面进行效果对比。

  1. 使用VS2013创建Asp.net Web应用程序工程,勾选MVC和WebAPI类库。具体见下图:

  

  2. 创建对应的仓储和模型。这里演示的是一个简单任务管理系统。具体的模型和仓储代码如下:

  任务实体类实现:

复制代码
public enum TaskState
    {
        Active = 1,
        Completed =2
    }

    /// <summary>
    /// 任务实体
    /// </summary>
    public class Task
    {
        public int Id { get; set; }

        public string Name { get; set; }
        public string Description { get; set; }

        public DateTime CreationTime { get; set; }

        public DateTime FinishTime { get; set; }

        public string Owner { get; set; }
        public TaskState State { get; set; }

        public Task()
        {
            CreationTime = DateTime.Parse(DateTime.Now.ToLongDateString());
            State = TaskState.Active;
        }
    }
复制代码

 

  任务仓储类实现:

复制代码
/// <summary>
    /// 这里仓储直接使用示例数据作为演示,真实项目中需要从数据库中动态加载
    /// </summary>
    public class TaskRepository
    {
        #region Static Filed
        private static Lazy<TaskRepository> _taskRepository = new Lazy<TaskRepository>(() => new TaskRepository());

        public static TaskRepository Current
        {
            get { return _taskRepository.Value; }
        }

        #endregion 

        #region Fields
        private readonly List<Task> _tasks = new List<Task>()
        {
            new Task
            {
                Id =1,
                Name = "创建一个SPA程序",
                Description = "SPA(single page web application),SPA的优势就是少量带宽,平滑体验",
                Owner = "Learning hard",
                FinishTime = DateTime.Parse(DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture))
            },
            new Task
            {
                Id =2,
                Name = "学习KnockoutJs",
                Description = "KnockoutJs是一个MVVM类库,支持双向绑定",
                Owner = "Tommy Li",
                FinishTime = DateTime.Parse(DateTime.Now.AddDays(2).ToString(CultureInfo.InvariantCulture))
            },
            new Task
            {
                Id =3,
                Name = "学习AngularJS",
                Description = "AngularJs是MVVM框架,集MVVM和MVC与一体。",
                Owner = "李志",
                FinishTime = DateTime.Parse(DateTime.Now.AddDays(3).ToString(CultureInfo.InvariantCulture))
            },
            new Task
            {
                Id =4,
                Name = "学习ASP.NET MVC网站",
                Description = "Glimpse是一款.NET下的性能测试工具,支持asp.net 、asp.net mvc, EF等等,优势在于,不需要修改原项目任何代码,且能输出代码执行各个环节的执行时间",
                Owner = "Tonny Li",
                FinishTime = DateTime.Parse(DateTime.Now.AddDays(4).ToString(CultureInfo.InvariantCulture))
            },
        };

        #endregion 

        #region Public Methods
        public IEnumerable<Task> GetAll()
        {
            return _tasks;
        }

        public Task Get(int id)
        {
            return _tasks.Find(p => p.Id == id);
        }

        public Task Add(Task item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            item.Id = _tasks.Count + 1;
            _tasks.Add(item);
            return item;
        }

        public void Remove(int id)
        {
            _tasks.RemoveAll(p => p.Id == id);
        }

        public bool Update(Task item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            var taskItem = Get(item.Id);
            if (taskItem == null)
            {
                return false;
            }

            _tasks.Remove(taskItem);
            _tasks.Add(item);
            return true;
        }
        #endregion 
    }
复制代码

  3. 通过Nuget添加Bootstrap和KnockoutJs库。

  4. 实现后端数据服务。这里后端服务使用Asp.net WebAPI实现的。具体的实现代码如下:

复制代码
  /// <summary>
    /// Task WebAPI,提供数据服务
    /// </summary>
    public class TasksController : ApiController
    {
        private readonly TaskRepository _taskRepository = TaskRepository.Current;

        public IEnumerable<Task> GetAll()
        {
            return _taskRepository.GetAll().OrderBy(a => a.Id);
        }

        public Task Get(int id)
        {
            var item = _taskRepository.Get(id);
            if (item == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            return item;
        }

        [Route("api/tasks/GetByState")]
        public IEnumerable<Task> GetByState(string state)
        {
            IEnumerable<Task> results = new List<Task>();
            switch (state.ToLower())
            {
                case "":
                case "all":
                    results = _taskRepository.GetAll();
                    break;
                case "active":
                    results = _taskRepository.GetAll().Where(t => t.State == TaskState.Active);
                    break;
                case "completed":
                    results = _taskRepository.GetAll().Where(t => t.State == TaskState.Completed);
                    break;
            }

            results = results.OrderBy(t => t.Id);
            return results;
        }

        [HttpPost]
        public Task Create(Task item)
        {
           return _taskRepository.Add(item);
        }

        [HttpPut]
        public void Put(Task item)
        {
            if (!_taskRepository.Update(item))
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }

        public void Delete(int id)
        {
            _taskRepository.Remove(id);
        }
    }
复制代码
View Code

  5. 使用Asp.net MVC Bundle对资源进行打包。对应的BundleConfig实现代码如下:

复制代码
/// <summary>
    /// 只需要补充一些缺少的CSS和JS文件。因为创建模板的时候已经添加了一些CSS和JS文件
    /// </summary>
    public class BundleConfig
    {
        // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.validate*"));

            // Use the development version of Modernizr to develop with and learn from. Then, when you're
            // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                        "~/Scripts/modernizr-*"));

            bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                      "~/Scripts/bootstrap.js",
                      "~/Scripts/bootstrap-datepicker.min.js"));

            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/bootstrap-datepicker3.min.css",
                      "~/Content/site.css"));

            bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
                      "~/Scripts/knockout-{version}.js",
                    "~/Scripts/knockout.validation.min.js",
                    "~/Scripts/knockout.mapping-latest.js"));

            bundles.Add(new ScriptBundle("~/bundles/app").Include(
                "~/Scripts/app/app.js"));
        }
    }
复制代码
View Code

  6. 因为我们需要在页面上使得枚举类型显示为字符串。默认序列化时会将枚举转换成数值类型。所以要对WebApiConfig类做如下改动:

复制代码
public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            // 使得序列化使用驼峰式大小写风格序列化属性
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            // 将枚举类型在序列化时序列化字符串
            config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());
        }
    }
复制代码

  注:如果上面没有使用驼峰小写风格序列化的话,在页面绑定数据的时候也要进行调整。如绑定的Name属性的时候直接使用Name大写,如果使用name方式会提示这个属性没有定义错误。由于JS是使用驼峰小写风格对变量命名的。所以建议大家加上使用驼峰小写风格进行序列化,此时绑定的时候只能使用"name"这样的形式进行绑定。这样也更符合JS代码的规范。 

  7. 修改对应的Layout文件和Index文件内容。

  Layout文件具体代码如下:

复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> Learninghard SPA Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
    <body>
        <div class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
                <div class="navbar-header">
                    <p class="navbar-brand">简单任务管理系统</p>
                </div>
                <div class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                        <li class="active"><a href="/">主页</a></li>
                    </ul>
                </div>
            </div>
        </div>

        <div class="container body-content" id="main">
            @RenderBody()
            <hr />
            <footer>
                <p>&copy; @DateTime.Now.Year - Learninghard SPA Application</p>
            </footer>
        </div>

        @Scripts.Render("~/bundles/jquery")
        @Scripts.Render("~/bundles/bootstrap")
        @Scripts.Render("~/bundles/knockout")
        @Scripts.Render("~/bundles/app")
    </body>
</html>
复制代码
View Code

  Index页面代码如下:

复制代码
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}


<div id="list" data-bind="if:canCreate">
<h2>Tasks</h2>
<div class="table-responsive">
    <table class="table table-striped">
        <thead>
            <tr>
                <th>编号</th>
                <th>名称</th>
                <th>描述</th>
                <th>负责人</th>
                <th>创建时间</th>
                <th>完成时间</th>
                <th>状态</th>
                <th></th>
            </tr>
        </thead>
        <tbody data-bind="foreach:tasks">
            <tr>
                <td data-bind="text: id"></td>
                <td><a data-bind="text: name, click: handleCreateOrUpdate"></a></td>
                <td data-bind="text: description"></td>
                <td data-bind="text: owner"></td>
                <td data-bind="text: creationTime"></td>
                <td data-bind="text: finishTime"></td>
                <td data-bind="text: state"></td>
                <td><a class="btn btn-xs btn-primary" data-bind="click:remove" href="javascript:void(0)">Remove</a></td>
            </tr>
        </tbody>
    </table>
</div>
<div class="col-sm-4">
    <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('all') }">All </a> |
    <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('active') }"> Active</a> |
    <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('completed') }"> Completed</a>
</div>
<div class="col-sm-2 col-sm-offset-6">
    <a href="javascript:void(0)" data-bind="click: handleCreateOrUpdate">添加任务</a>
</div>
</div>

<div id="create" style="visibility: hidden">
    <h2>添加任务</h2>
    <br/>
    <div class="form-horizontal">
        <div class="form-group">
            <label for="taskName" class="col-sm-2 control-label">名称 *</label>
            <div class="col-sm-10">
                <input type="text" data-bind="value: name" class="form-control" id="taskName" name="taskName" placeholder="名称">
            </div>
        </div>
        <div class="form-group">
            <label for="taskDesc" class="col-sm-2 control-label">描述</label>
            <div class="col-sm-10">
                <textarea class="form-control" data-bind="value: description" rows="3" id="taskDesc" name="taskDesc" placeholder="描述"></textarea>
            </div>
        </div>
        <div class="form-group">
            <label for="taskOwner" class="col-sm-2 control-label">负责人 *</label>
            <div class="col-sm-10">
                <input class="form-control" id="taskOwner" name="taskOwner" data-bind="value: owner" placeholder="负责人">
            </div>
        </div>
        <div class="form-group">
            <label for="taskFinish" class="col-sm-2 control-label">预计完成时间 *</label>
            <div class="col-sm-10">
                <input class="form-control datepicker" id="taskFinish" data-bind="value: finishTime" name="taskFinish">
            </div>
        </div>
        <div class="form-group">
            <label for="taskOwner" class="col-sm-2 control-label">状态 *</label>
            <div class="col-sm-10">
                <select id="taskState" class="form-control" data-bind="value: state">
                    <option>Active</option>
                    <option>Completed</option>
                </select>
                
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button class="btn btn-primary" data-bind="click:handleSaveClick">Save</button>
                <button data-bind="click: handleBackClick" class="btn btn-primary">Back</button>
            </div>
        </div>
    </div>
</div>
复制代码
View Code

  8. 创建对应的前端脚本逻辑。用JS代码来请求数据,并创建对应ViewModel对象来进行前端绑定。具体JS实现代码如下:

复制代码
var taskListViewModel = {
    tasks: ko.observableArray(),
    canCreate:ko.observable(true)
};

var taskModel = function () {
    this.id = 0;
    this.name = ko.observable();
    this.description = ko.observable();
    this.finishTime = ko.observable();
    this.owner = ko.observable();
    this.state = ko.observable();
    this.fromJS = function(data) {
        this.id = data.id;
        this.name(data.name);
        this.description(data.description);
        this.finishTime(data.finishTime);
        this.owner(data.owner);
        this.state(data.state);
    };
};

function getAllTasks() {
    sendAjaxRequest("GET", function (data) {
        taskListViewModel.tasks.removeAll();
        for (var i = 0; i < data.length; i++) {
            taskListViewModel.tasks.push(data[i]);
        }
    }, 'GetByState', { 'state': 'all' });
}

function setTaskList(state) {
    sendAjaxRequest("GET", function(data) {
        taskListViewModel.tasks.removeAll();
        for (var i = 0; i < data.length; i++) {
            taskListViewModel.tasks.push(data[i]);
        }},'GetByState',{ 'state': state });
}

function remove(item) {
    sendAjaxRequest("DELETE", function () {
        getAllTasks();
    }, item.id);
}

var task = new taskModel();

function handleCreateOrUpdate(item) {
    task.fromJS(item);
    initDatePicker();
    taskListViewModel.canCreate(false);
    $('#create').css('visibility', 'visible');
}

function handleBackClick() {
    taskListViewModel.canCreate(true);
    $('#create').css('visibility', 'hidden');
}

function handleSaveClick(item) {
    if (item.id == undefined) {
        sendAjaxRequest("POST", function (newItem) { //newitem是返回的对象。
            taskListViewModel.tasks.push(newItem);
        }, null, {
            name: item.name,
            description: item.description,
            finishTime: item.finishTime,
            owner: item.owner,
            state: item.state
        });
    } else {
        sendAjaxRequest("PUT", function () {
            getAllTasks();
        }, null, {
            id:item.id,
            name: item.name,
            description: item.description,
            finishTime: item.finishTime,
            owner: item.owner,
            state: item.state
        });
    }
    
    taskListViewModel.canCreate(true);
    $('#create').css('visibility', 'hidden');
}
function sendAjaxRequest(httpMethod, callback, url, reqData) {
    $.ajax("/api/tasks" + (url ? "/" + url : ""), {
        type: httpMethod,
        success: callback,
        data: reqData
    });
}

var initDatePicker = function() {
    $('#create .datepicker').datepicker({
        autoclose: true
    });
};

$('.nav').on('click', 'li', function() {
    $('.nav li.active').removeClass('active');
    $(this).addClass('active');
});

$(document).ready(function () {
    getAllTasks();
    // 使用KnockoutJs进行绑定
    ko.applyBindings(taskListViewModel, $('#list').get(0));
    ko.applyBindings(task, $('#create').get(0));
});
复制代码
View Code

  到此,我们的单页面程序就开发完毕了,接下来我们来运行看看其效果。

   从上面运行结果演示图可以看出,一旦页面加载完之后,所有的操作都好像在一个页面操作,完全感觉浏览器页面转圈的情况。对比于之前使用Asp.net MVC +Razor开发的页面,你是否感觉了SPA的流畅呢?之前使用Asp.net MVC +Razor开发的页面,你只要请求一个页面,你就可以感受整个页面刷新的情况,这样用户体验非常不好。

四、与Razor开发模式进行对比

  相信大家从效果上已经看出SPA优势了,接下来我觉得还是有必要与传统实现Web页面方式进行一个对比。与Razor开发方式主要有以下2点不同:

  1. 页面被渲染的时候,数据在浏览器端得到处理。而不是在服务器上。将渲染压力分配到各个用户的浏览器端,从而减少网站服务器的压力。换做是Razor语法,前端页面绑定语句应该就是如下:
复制代码
@Model IEnumerable<KnockoutJSSPA.Models.Task>  
@foreach (var item in Model)
{
    <tr>
        <td>@item.Name</td>
        <td>@item.Description</td>
    </tr>
}
复制代码

 

  这些都是在服务器端由Razor引擎渲染的。这也是使用Razor开发的页面会看到页面转圈的情况的原因。因为你每切换一个页面的时候,都需要请求服务端进行渲染,服务器渲染完成之后再将html返回给客户端进行显示。

  2. 绑定的数据是动态的。意味着数据模型的改变会马上反应到页面上。这效果归功于KnockoutJs实现的双向绑定机制。

  采用这种方式,对于程序开发也简单了,Web API只负责提供数据,而前端页面也减少了很多DOM操作。由于DOM操作比较繁琐和容易出错。这样也意味着减少了程序隐性的bug。并且,一个后端服务,可以供手机、Web浏览器和平台多个平台使用,避免重复开发。

五、总结

  到此,本文的介绍就介绍了。本篇主要介绍了使用KnockoutJs来完成一个SPA程序。其实在实际工作中,打造单页面程序的模式更多的采用AngularJS。然后使用KnockoutJs也有很多,但是KnockoutJs只是一个MVVM框架,其路由机制需要借助其他一些类库,如我们这里使用Asp.net MVC中的路由机制,你还可以使用director.js前端路由框架。相对于KnockoutJs而言,AngularJs是一个MVVM+MVC框架。所以在下一个专题将介绍使用如何使用AngularJs打造一个单页面程序(SPA)。

  本文所有源码下载:SPAWithKnockoutJs

  另外,如果觉得本文对你有帮助,请帮忙点下推荐或者扫描二维码对我进行打赏,你们的支持也是我继续为大家分享好文章的动力,希望大家可以对我支持。谢谢

这个文档是我自己原创制作的,在别的网上肯定是没有的。 而且做得非常好看,和非常准确。 如果下载的人多,将会把中英文对照的版本也上传。 Knockout是一个以数据模型(data model)为基础的能够帮助你创建富文本,响应显示和编辑用户界面的JavaScript类库。任何时候如果你的UI需要自动更新(比如:更新依赖于用户的行为或者外部数据源的改变),KO能够很简的帮你实现并且很容易维护。 重要特性: 优雅的依赖追踪 - 不管任何时候你的数据模型更新,都会自动更新相应的内容。 Elegant dependency tracking - automatically updates the right parts of your UI whenever your data model changes. 声明式绑定 - 浅显易懂的方式将你的用户界面指定部分关联到你的数据模型上。 Declarative bindings - a simple and obvious way to connect parts of your UI to your data model. You can construct a complex dynamic UIs easily using arbitrarily nested binding contexts. 轻易可扩展 - 几行代码就可以实现自定义行为作为新的声明式绑定。 Trivially extensible - implement custom behaviors as new declarative bindings for easy reuse in just a few lines of code. 额外的好处: 纯JavaScript类库 – 兼容任何服务器端和客户端技术 Pure JavaScript library - works with any server or client-side technology 可添加到Web程序最上部 – 不需要大的架构改变 Can be added on top of your existing web application - without requiring major architectural changes 简洁的 - Gzip之前大约25kb Compact - around 13kb after gzipping 兼容任何主流浏览器 - (IE 6+、Firefox 2+、Chrome、Safari、其它) Works on any mainstream browser - (IE 6+, Firefox 2+, Chrome, Safari, others) 采用行为驱动开发 - 意味着在新的浏览器和平台上可以很容易通过验证。 Comprehensive suite of specifications - (developed BDD-style) means its correct functioning can easily be verified on new browsers and platforms 开发人员如果用过Silverlight或者WPF可能会知道KO是MVVM模式的一个例子。如果熟悉 Ruby on Rails 或其它MVC技术可能会发现它是一个带有声明式语法的MVC实时form。换句话说,你可以把KO当成通过编辑JSON数据来制作UI用户界面的一种方式… 不管它为你做什么 Developers familiar with Ruby on Rails, ASP.NET MVC, or other MV* technologies may see MVVM as a real-time form of MVC with declarative syntax. In another sense, you can think of KO as a general way to make UIs for editing JSON data… whatever works for you :) OK, 如何使用它? 简来说:声明你的数据作为一个JavaScript 模型对象(model object),然后将DOM 元素或者模板(templates)绑定到它上面. The quickest and most fun way to get started is by working through the interactive tutorials. Once you’ve got to grips with the basics, explore the live examples and then have a go with it in your own project. KO和jQuery (或Prototype等)是竞争关系还是能一起使用? 所有人都喜欢jQuery! 它是一个在页面里操作元素和事件的框架,非常出色并且易使用,在DOM操作上肯定使用jQuery,KO解决不同的问题。 Everyone loves jQuery! It’s an outstanding replacement for the clunky, inconsistent DOM API we had to put up with in the past. jQuery is an excellent low-level way to manipulate elements and event handlers in a web page. I certainly still use jQuery for low-level DOM manipulation. KO solves a different problem. 如果页面要求复杂,仅仅使用jQuery需要花费更多的代码。 例如:一个表格里显示一个列表,然后统计列表的数量,Add按钮在数据行TR小于5调的时候启用,否则就禁用。jQuery 没有基本的数据模型的概念,所以需要获取数据的数量(从table/div或者专门定义的CSS class),如果需要在某些SPAN里显示数据的数量,当添加新数据的时候,你还要记得更新这个SPAN的text。当然,你还要判断当总数>=5条的时候禁用Add按钮。 然后,如果还要实现Delete功能的时候,你不得不指出哪一个DOM元素被点击以后需要改变。 As soon as your UI gets nontrivial and has a few overlapping behaviors, things can get tricky and expensive to maintain if you only use jQuery. Consider an example: you’re displaying a list of items, stating the number of items in that list, and want to enable an ‘Add’ button only when there are fewer than 5 items. jQuery doesn’t have a concept of an underlying data model, so to get the number of items you have to infer it from the number of TRs in a table or the number of DIVs with a certain CSS class. Maybe the number of items is displayed in some SPAN, and you have to remember to update that SPAN’s text when the user adds an item. You also must remember to disable the ‘Add’ button when the number of TRs is 5. Later, you’re asked also to implement a ‘Delete’ button and you have to figure out which DOM elements to change whenever it’s clicked. Knockout的实现有何不同? 使用KO非常简。将你的数据描绘成一个JavaScript数组对象myItems,然后使用模板(template)转化这个数组到表格里(或者一组DIV)。不管什么时候数组改变, UI界面也会响应改变(不用指出如何插入新行或在哪里插入),剩余的工作就是同步了。例如:你可以声明绑定如下一个SPAN显示数据数量(可以放在页面的任何地方,不一定非要在template里): It’s much easier with KO. It lets you scale up in complexity without fear of introducing inconsistencies. Just represent your items as a JavaScript array, and then use a foreach binding to transform this array into a TABLE or set of DIVs. Whenever the array changes, the UI changes to match (you don’t have to figure out how to inject new TRs or where to inject them). The rest of the UI stays in sync. For example, you can declaratively bind a SPAN to display the number of items as follows: There are <span data-bind="text: myItems().count"></span> items就是这些!你不需要写代码去更新它,它的更新依赖于数组myItems的改变。同样, Add按钮的启用和禁用依赖于数组 myItems 的长度,如下: That’s it! You don’t have to write code to update it; it updates on its own when the myItems array changes. Similarly, to make the ‘Add’ button enable or disable depending on the number of items, just write: <button data-bind="enable: myItems().count < 5">Add</button>之后,如果你要实现Delete功能,不必指出如何操作UI元素,只需要修改数据模型就可以了。 Later, when you’re asked to implement the ‘Delete’ functionality, you don’t have to figure out what bits of the UI it has to interact with; you just make it alter the underlying data model. 总结:KO没有和jQuery或类似的DOM 操作API对抗竞争。KO提供了一个关联数据模型和用户界面的高级功能。KO本身不依赖jQuery,但是你可以一起同时使用jQuery, 生动平缓的UI改变需要真正使用jQuery。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值