zoukankan      html  css  js  c++  java
  • knockout.js的学习笔记

    knockout.js试图将微软历经验证的成功方案MVVM解决方案引进JS,因此很有必要学习下。MVVM是专门为解决富交互频变动的界面开发而生,这与web开发非常相似。产经经理与测试与什么主管,他们看不懂后端的东西,也只能对前端的看得到的东西指手划脚了,因此变动是非常频繁的,每次变动,但伴随着痛若的事件重新绑定与代理,以及与它们相关的业务代码的调整,在JS这种调试特喝别痛苦的语言中,情况就更严重了。每次改版都加剧前端离职的决心,前端换了几波人才把项目做出来。jQuery号称是改变人们写JS的方式,但只是提供了更好的砖瓦而已(原生API是沙石)。想获得后端那样开发效率,必须有Struts2, Spring, rails这般一站式的框架,就开发流程进行控制,开发人员只是在框架里做填空题。这样,看另一个人的代码,就知道应该是哪里开始看起,看完这里就知道下一步应该是哪里。易读性应该由框架来塑造,维护性由框架来提供!

    之所以放弃ember.js的研究是因为她对流程的控制太弱了,代码量一多,还是像狗屎一般的乱!knockout.js虽然有许多不如意的地方,但它对流程的控制是非常好的,只有三个入口。在元素上进行数据绑定,编写viewModel,将viewModel绑到目标的节点上。简单明了,其缺点可以在我通读knockout后再造一个轮子解决!

    一般的数据绑定有三种:

    One-Time,One-Way,Two-way。

    One-Time绑定模式的意思即为从viewModel绑定至UI这一层只进行一次绑定,程序不会继续追踪数据的在两者中任何一方的变化,这种绑定方式很使用于报表数据,数据仅仅会加载一次。

    One-Way绑定模式即为单向绑定,即object-UI的绑定,只有当viewModel中数据发生了变化,UI中的数据也将会随之发生变化,反之不然。

    Two-Way绑定模式为双向绑定,无论数据在Object或者是UI中发生变化,应用程序将会更新另一方,这是最为灵活的绑定方式,同时代价也是最大的。

    数据绑定只是作为元素的自定义属性写上标签内,并不能决定它是何种绑定。

    viewModel是一个结构非常简单的hash,键名为命令,值的定义方式决定其绑定方式。 如果值是通过ko.observable定义的说明是双向绑定,否则为One-Time绑定,在knockout不存在单向绑定。knockout2.0还从ember.js借鉴了计算属性,即用ko.computed定义的,它的值会依赖其他值进行推断,因此就形成了依赖,需要构筑一枚依赖链。

    最后一步是将viewModel绑到节点上,事实上它还会遍历其后代,进行绑定。默认是绑定body上,因此用户的行为只能影响到body里面的元素与URL。如果页面非常复杂,建议还是指定具体节点吧。viewModel还可以绑定注释节点,但有的公司会对页面进行压缩,去空白去注释,因此不太建议使用。

    我们先从ko.applyBindings看起吧。

    ko.applyBindings(viewModel, rootNode)
    //这里会对rootNode进行修正
           ↓
    applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true
    //第三个参数为强制绑定,会影响shouldApplyBindings变量
    //如果是UL与OL会对里面的结构进行修正normaliseVirtualElementDomStructure
    //通过shouldApplyBindings变量决定是否对此节点进行数据绑定
    //通过applyBindingsToNodeInternal判定是否继续绑定到后代中
    //通过applyBindingsToDescendantsInternal绑定到后代中

    applyBindingsToNodeInternal在这里调用时其参数为rootNode, null, viewModel, true。它是一个极恶的方法,大量使用闭包。精简如下:

    function applyBindingsToNodeInternal (node, bindings, viewModel, force) {
        var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
        var parsedBindings;
        function makeValueAccessor(bindingKey) {
            return function () {
                return parsedBindings[bindingKey]
            }
        }
        function parsedBindingsAccessor() {
            return parsedBindings;//这是一个对象
        }
     
        var bindingHandlerThatControlsDescendantBindings;
        ko.dependentObservable(function () {/*略*/ },  null,{
            'disposeWhenNodeIsRemoved' : node
        });
        return {
            //除了html, template命令,都允许在后代节点继续绑定
            shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
        };
    };

    难度在于dependentObservable的第一个回调

    function anonymity() {
        var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
        ? viewModelOrBindingContext
        : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext));
        //将viewModelOrBindingContext整成bindingContext实例
        //bindingContext实例用$data保存viewModel
        var viewModel = bindingContextInstance['$data'];
        //将bindingContextInstance绑定rootNode上
        if (bindingContextMayDifferFromDomParentElement)
            ko.storedBindingContextForNode(node, bindingContextInstance);
        // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
        var evaluatedBindings = (typeof bindings == "function") ? bindings() : bindings;
        //如果bindings不存在,则通过getBindings获取,getBindings会调用parseBindingsString,变成对象
        parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
     
        if (parsedBindings) {
            // First run all the inits, so bindings can register for notification on changes
            if (initPhase === 0) {
                initPhase = 1;
                for (var bindingKey in parsedBindings) {
                    var binding = ko.bindingHandlers[bindingKey];
                    if (binding && node.nodeType === 8)
                        validateThatBindingIsAllowedForVirtualElements(bindingKey);
                    //注释节点只能绑定流程控制命令
                    if (binding && typeof binding["init"] == "function") {
                        var handlerInitFn = binding["init"];
                        //在页中,用户的操作只能影响到元素的value, checked, selectedIndex, hasFocus, placeholder的变化
                        //而更多的变化需要通过绑定事件,通过JS代码调用实现
                        //因此init主要用于绑定事件
                        var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
                        // If this binding handler claims to control descendant bindings, make a note of this
                        if (initResult && initResult['controlsDescendantBindings']) {//这里主要是html,与template命令,只有它们阻止继续在后代中绑定事件
                            if (bindingHandlerThatControlsDescendantBindings !== undefined)
                                throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings +
     " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
                            bindingHandlerThatControlsDescendantBindings = bindingKey;
                        }
                    }
                }
                initPhase = 2;
            }
            if (initPhase === 2) {
                for (var bindingKey in parsedBindings) {
                    var binding = ko.bindingHandlers[bindingKey];
                    if (binding && typeof binding["update"] == "function") {
                        var handlerUpdateFn = binding["update"];//更新UI
                        handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
                    }
                }
            }
        }
    }

    这里存在一个疑惑,像value, checked, event等命令是要绑定事件的,但很难想象事件的回调是怎么调用到这匿名函数的。下一节将深入到其发布者订阅者机制看看。

     
     
  • 相关阅读:
    《linux 必读》
    ldd ldconfig
    rpm 数据库
    /bin, /sbin & /usr/bin, /usr/sbin & /usr/local/bin, /usr/local/sbin & glibc
    POSIX
    CentOS 下载地址
    insert into TABLE by SELECT ...
    httpd 处理模型
    http 状态码
    IP地址 0.0.0.0 是什么意思?
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2552727.html
Copyright © 2011-2022 走看看