Knockout源代码精析-怎样解析demo元素,获取到bindings?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Knockout源代码精析-怎样解析demo元素,获取到bindings?相关的知识,希望对你有一定的参考价值。

接上文这里開始分析applyBindingsToNodeInternal.applyBindingsToNodeInternal方法例如以下:

     

function applyBindingsToNodeInternal(node, sourceBindings, bindingContext, bindingContextMayDifferFromDomParentElement) {
        // Prevent multiple applyBindings calls for the same node, except when a binding value is specified
        var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
        if (!sourceBindings) {
            if (alreadyBound) {
                throw Error("You cannot apply bindings multiple times to the same element.");
            }
            ko.utils.domData.set(node, boundElementDomDataKey, true);
        }

        // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
        // we can easily recover it just by scanning up the node's ancestors in the DOM
        // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
        if (!alreadyBound && bindingContextMayDifferFromDomParentElement)
            ko.storedBindingContextForNode(node, bindingContext);

        // Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
        var bindings;
        if (sourceBindings && typeof sourceBindings !== 'function') {
            bindings = sourceBindings;
        } else {
            var provider = ko.bindingProvider['instance'],
                getBindings = provider['getBindingAccessors'] || getBindingsAndMakeAccessors;

            // Get the binding from the provider within a computed observable so that we can update the bindings whenever
            // the binding context is updated or if the binding provider accesses observables.
            var bindingsUpdater = ko.dependentObservable(
                function() {
                    bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
                    // Register a dependency on the binding context to support obsevable view models.
                    if (bindings && bindingContext._subscribable)
                        bindingContext._subscribable();
                    return bindings;
                },
                null, { disposeWhenNodeIsRemoved: node }
            );

            if (!bindings || !bindingsUpdater.isActive())
                bindingsUpdater = null;
        }

        var bindingHandlerThatControlsDescendantBindings;
        if (bindings) {
            // Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
            // context update), just return the value accessor from the binding. Otherwise, return a function that always gets
            // the latest binding value and registers a dependency on the binding updater.
            var getValueAccessor = bindingsUpdater
                ? function(bindingKey) {
                    return function() {
                        return evaluateValueAccessor(bindingsUpdater()[bindingKey]);
                    };
                } : function(bindingKey) {
                    return bindings[bindingKey];
                };

            // Use of allBindings as a function is maintained for backwards compatibility, but its use is deprecated
            function allBindings() {
                return ko.utils.objectMap(bindingsUpdater ?

bindingsUpdater() : bindings, evaluateValueAccessor); } // The following is the 3.x allBindings API allBindings['get'] = function(key) { return bindings[key] && evaluateValueAccessor(getValueAccessor(key)); }; allBindings['has'] = function(key) { return key in bindings; }; // First put the bindings into the right order var orderedBindings = topologicalSortBindings(bindings); // Go through the sorted bindings, calling init and update for each ko.utils.arrayForEach(orderedBindings, function(bindingKeyAndHandler) { // Note that topologicalSortBindings has already filtered out any nonexistent binding handlers, // so bindingKeyAndHandler.handler will always be nonnull. var handlerInitFn = bindingKeyAndHandler.handler["init"], handlerUpdateFn = bindingKeyAndHandler.handler["update"], bindingKey = bindingKeyAndHandler.key; if (node.nodeType === 8) { validateThatBindingIsAllowedForVirtualElements(bindingKey); } try { // Run init, ignoring any dependencies if (typeof handlerInitFn == "function") { ko.dependencyDetection.ignore(function() { var initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext); // If this binding handler claims to control descendant bindings, make a note of this if (initResult && initResult['controlsDescendantBindings']) { 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; } }); } // Run update in its own computed wrapper if (typeof handlerUpdateFn == "function") { ko.dependentObservable( function() { handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, bindingContext['$data'], bindingContext); }, null, { disposeWhenNodeIsRemoved: node } ); } } catch (ex) { ex.message = "Unable to process binding \"" + bindingKey + ": " + bindings[bindingKey] + "\"\nMessage: " + ex.message; throw ex; } }); } return { 'shouldBindDescendants': bindingHandlerThatControlsDescendantBindings === undefined }; };

这里有代码段例如以下  在上一节的小样例里sourceBindings为空

 if (sourceBindings && typeof sourceBindings !== 'function') {
            bindings = sourceBindings;
        } else {
            var provider = ko.bindingProvider['instance'],
                getBindings = provider['getBindingAccessors'] || getBindingsAndMakeAccessors;

            // Get the binding from the provider within a computed observable so that we can update the bindings whenever
            // the binding context is updated or if the binding provider accesses observables.
            var bindingsUpdater = ko.dependentObservable(
                function() {
                    bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
                    // Register a dependency on the binding context to support obsevable view models.
                    if (bindings && bindingContext._subscribable)
                        bindingContext._subscribable();
                    return bindings;
                },
                null, { disposeWhenNodeIsRemoved: node }
            );

            if (!bindings || !bindingsUpdater.isActive())
                bindingsUpdater = null;
        }

这里的bindings非常关键是bindings是怎样获取的?

上面说了sourceBinds为空所以程序走else 这里有

     

var bindingsUpdater = ko.dependentObservable(
                function() {
                    bindings = sourceBindings ? sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext);
                    // Register a dependency on the binding context to support obsevable view models.
                    if (bindings && bindingContext._subscribable)
                        bindingContext._subscribable();
                    return bindings;
                },
                null, { disposeWhenNodeIsRemoved: node }
            );
            非常明显bindings是通过ko.dependentObservable的回调函数设置的那ko.dependentObservable是怎样运行这个回调函数的呢?

这里先简单说一下,后面会具体解说。这里的回调函数会赋值给一个局部变量readFunction,而这里的readFunction会在74行的一个闭包的函数evaluateImmediate里被调用具体调用在130行 例如以下 var newValue = evaluatorFunctionTarget ?

readFunction.call(evaluatorFunctionTarget) : readFunction();而在343行有evaluateImmediate() 会调用这种方法运行这个内联函数,当然这里说的都是定义ko.dependentObservable的js文件dependentObservable.js这个文件中了

也就是说终于binds会通过这个回调函数赋值 sourceBindings为空 自然会通过getBindings.call来赋值

getBindings是什么?var provider = ko.bindingProvider[‘instance‘],
                getBindings = provider[‘getBindingAccessors‘] || getBindingsAndMakeAccessors;

使用ctrl+f 搜到ko.bindingProvider[‘instance‘] = new ko.bindingProvider(); 在这个bindingProvider.js里面而这个js就是解析所谓data-binding的核心js文件

也就是通过 这个例如以下构造函数new出来的:

   ko.bindingProvider = function() {
        this.bindingCache = {};
    };
咋一看没有getBindingAccessors属性可是这里有
 ko.utils.extend(ko.bindingProvider.prototype, {
        'nodeHasBindings': function(node) {
            switch (node.nodeType) {
                case 1: // Element
                    return node.getAttribute(defaultBindingAttributeName) != null
                        || ko.components['getComponentNameForNode'](node);
                case 8: // Comment node
                    return ko.virtualElements.hasBindingValue(node);
                default: return false;
            }
        },

        'getBindings': function(node, bindingContext) {
            var bindingsString = this['getBindingsString'](node, bindingContext),
                parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
            return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false);
        },

        'getBindingAccessors': function(node, bindingContext) {
            var bindingsString = this['getBindingsString'](node, bindingContext),
                parsedBindings = bindingsString ?

this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null; return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true); }, // The following function is only used internally by this default provider. // It's not part of the interface definition for a general binding provider. 'getBindingsString': function(node, bindingContext) { switch (node.nodeType) { case 1: return node.getAttribute(defaultBindingAttributeName); // Element case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node default: return null; } }, // The following function is only used internally by this default provider. // It's not part of the interface definition for a general binding provider. 'parseBindingsString': function(bindingsString, bindingContext, node, options) { try { var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options); return bindingFunction(bindingContext, node); } catch (ex) { ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message; throw ex; } } });

也就是说给这个构造函数的原型设置了一大堆属性。这样通过构造函数new出来的对象就会拥有这些属性。而provider[‘getBindingAccessors‘]也在这个原型对象里面例如以下:

 'getBindingAccessors': function(node, bindingContext) {
            var bindingsString = this['getBindingsString'](node, bindingContext),
                parsedBindings = bindingsString ?

this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null; return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ true);


第一句话调用例如以下方法:

 var bindingsString = this['getBindingsString'](node, bindingContext)

 'getBindingsString': function(node, bindingContext) {
            switch (node.nodeType) {
                case 1: return node.getAttribute(defaultBindingAttributeName);   // Element
                case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
                default: return null;
            }
        }
node.nodeType为1 假设是一个正常节点会调用node.getAttribute(defaultBindingAttributeName); 而这里的var defaultBindingAttributeName = "data-bind"; data-bind最终出现了 所谓<input data-bind="value:lastName" />的解析的过程也就是从这里開始完毕的。

获取到绑定的字符串之后赋值给bindingsString 也就是说bindsString获取到的是"value:lastName"。

然后分析第二句话

 parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node, { 'valueAccessors': true }) : null;
'parseBindingsString': function(bindingsString, bindingContext, node, options) {
            try {
                var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options);
                return bindingFunction(bindingContext, node);
            } catch (ex) {
                ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
                throw ex;
            }
        }
function createBindingsStringEvaluatorViaCache(bindingsString, cache, options) {
        var cacheKey = bindingsString + (options && options['valueAccessors'] || '');
        return cache[cacheKey]
            || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
    }

因为这里的options为{ ‘valueAccessors‘: true }所以这里返回为类似"value:lastNametrue" 非常明显是做缓存操作createBindingsStringEvaluator为:

 function createBindingsStringEvaluator(bindingsString, options) {
        // Build the source for a function that evaluates "expression"
        // For each scope variable, add an extra level of "with" nesting
        // Example result: with(sc1) { with(sc0) { return (expression) } }
        var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
            functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
        return new Function("$context", "$element", functionBody);
    }

这种方法非常有意思:

首先这里是调用的这种方法

 function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) {
        bindingOptions = bindingOptions || {};

        function processKeyValue(key, val) {
            var writableVal;
            function callPreprocessHook(obj) {
                return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
            }
            if (!bindingParams) {
                if (!callPreprocessHook(ko['getBindingHandler'](key)))
                    return;

                if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
                    // For two-way bindings, provide a write method in case the value
                    // isn't a writable observable.
                    propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
                }
            }
            // Values are wrapped in a function so that each value can be accessed independently
            if (makeValueAccessors) {
                val = 'function(){return ' + val + ' }';
            }
            resultStrings.push("'" + key + "':" + val);
        }

        var resultStrings = [],
            propertyAccessorResultStrings = [],
            makeValueAccessors = bindingOptions['valueAccessors'],
            bindingParams = bindingOptions['bindingParams'],
            keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?

parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray; ko.utils.arrayForEach(keyValueArray, function(keyValue) { processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value); }); if (propertyAccessorResultStrings.length) processKeyValue('_ko_property_writers', "{" + propertyAccessorResultStrings.join(",") + " }"); return resultStrings.join(","); }


注意这句话  keyValueArray = typeof bindingsStringOrKeyValueArray === "string" ?


                parseObjectLiteral(bindingsStringOrKeyValueArray) : bindingsStringOrKeyValueArray;

这里的bindingsString是字符串所以运行

function parseObjectLiteral(objectLiteralString) {
        // Trim leading and trailing spaces from the string
        var str = ko.utils.stringTrim(objectLiteralString);

        // Trim braces '{' surrounding the whole object literal
        if (str.charCodeAt(0) === 123) str = str.slice(1, -1);

        // Split into tokens
        var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;

        if (toks) {
            // Append a comma so that we don't need a separate code block to deal with the last item
            toks.push(',');

            for (var i = 0, tok; tok = toks[i]; ++i) {
                var c = tok.charCodeAt(0);
                // A comma signals the end of a key/value pair if depth is zero
                if (c === 44) { // ","
                    if (depth <= 0) {
                        result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
                        key = depth = 0;
                        values = [];
                        continue;
                    }
                // Simply skip the colon that separates the name and value
                } else if (c === 58) { // ":"
                    if (!depth && !key && values.length === 1) {
                        key = values.pop();
                        continue;
                    }
                // A set of slashes is initially matched as a regular expression, but could be division
                } else if (c === 47 && i && tok.length > 1) {  // "/"
                    // Look at the end of the previous token to determine if the slash is actually division
                    var match = toks[i-1].match(divisionLookBehind);
                    if (match && !keywordRegexLookBehind[match[0]]) {
                        // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
                        str = str.substr(str.indexOf(tok) + 1);
                        toks = str.match(bindingToken);
                        toks.push(',');
                        i = -1;
                        // Continue with just the slash
                        tok = '/';
                    }
                // Increment depth for parentheses, braces, and brackets so that interior commas are ignored
                } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '['
                    ++depth;
                } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
                    --depth;
                // The key will be the first token; if it's a string, trim the quotes
                } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
                    tok = tok.slice(1, -1);
                }
                values.push(tok);
            }
        }
        return result;
    }

这种方法是一个算法 类似逆波兰匹配算法 

我这里略微说一下这个算法详细的还得大家自己去理解,算法这东西嘛大家懂的。

第一句话 if (str.charCodeAt(0) === 123) str = str.slice(1, -1);代表假设这里的绑定字符串 存在{左括号 去除两边的大括号 unicode码123相应左括号

toks = str.match(bindingToken) 这里通过控制台输出bindingToken获取正则

/"(?:[^"\\]|\\.)*"|‘(?:[^‘\\]|\\.)*‘|\/(?:[^\/\\]|\\.)*\/w*|[^\s:,\/][^,"‘{}()\/:[\]]*[^\s,"‘{}()\/:[\]]|[^\s]/g

这个正则会将bindingsString分隔 如value:lastName 会被分隔成一个数组 分别存 value , :  , lastName

然后会运行toks.push(‘,‘);给数组加上一个逗号而且遍历 假设取出的项第一个字符是普通字符那么会走 values.push(tok); 而遇到分号的时候会走

 else if (c === 58) { // ":"
                    if (!depth && !key && values.length === 1) {
                        key = values.pop();
                        continue;
                    }

会把上一个分号前的字符串 存到key里面由于分号预示着一个key value对的出现 后面lastName的l又是个普通字符会走values.push(tok);  终于遇到逗号 逗号也就是结束符号走

 if (c === 44) { // ","
                    if (depth <= 0) {
                        result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
                        key = depth = 0;
                        values = [];
                        continue;
                    }


给result加入 json对象 key为上次保存的key value为values集合存的value的连接

然后得到这个result数组之后会遍历他

 ko.utils.arrayForEach(keyValueArray, function(keyValue) {
            processKeyValue(keyValue.key || keyValue['unknown'], keyValue.value);
        });

function processKeyValue(key, val) {
            var writableVal;
            function callPreprocessHook(obj) {
                return (obj && obj['preprocess']) ?

(val = obj['preprocess'](val, key, processKeyValue)) : true; } if (!bindingParams) { if (!callPreprocessHook(ko['getBindingHandler'](key))) return; if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) { // For two-way bindings, provide a write method in case the value // isn't a writable observable. propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}"); } } // Values are wrapped in a function so that each value can be accessed independently if (makeValueAccessors) { val = 'function(){return ' + val + ' }'; } resultStrings.push("'" + key + "':" + val); }

通过这 if (makeValueAccessors) {
                val = ‘function(){return ‘ + val + ‘ }‘;
            }
            resultStrings.push("‘" + key + "‘:" + val);可知这种方法将刚才的key value对封装成了一个字符串

这里我用断点拿到的类似这种: ‘text‘:function(){return lastName }终于返回 这个字符串的奇妙后面立即就会有所体现

回到刚才的方法

 function createBindingsStringEvaluator(bindingsString, options) {
        // Build the source for a function that evaluates "expression"
        // For each scope variable, add an extra level of "with" nesting
        // Example result: with(sc1) { with(sc0) { return (expression) } }
        var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
            functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
        return new Function("$context", "$element", functionBody);
    }

这里的functionbody类似于

with($context){with($data||{}){return{‘text‘:function(){return lastName }}}}

而终于返回的方法应该类似这样的

function anonymous($context,$element
/**/) {
with($context){with($data||{}){return{'text':function(){return lastName }}}}
这种方法非常奇妙。后面会说他奇妙在哪儿。得到这种方法后返回.而且存到cache里return回去

 function createBindingsStringEvaluatorViaCache(bindingsString, cache, options) {
        var cacheKey = bindingsString + (options && options['valueAccessors'] || '');
        return cache[cacheKey]
            || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
    }

'parseBindingsString': function(bindingsString, bindingContext, node, options) {
            try {
                var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache, options);
                return bindingFunction(bindingContext, node);
            } catch (ex) {
                ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
                throw ex;
            }
        }

而且在上一层return的时候调用注意是调用而參数是bindContext和node也就是说bindContext是$Context

node是$element 注意bindContext是什么?

上一节有截图这里还是继续贴一下

技术分享

这里的$data正是ViewModel的封装也就是说通过with语法 我们全然能够通过这种方法轻松获取到相应的ViewModel中的方法比方这里就是返回的ViewModel中的lastName方法也就是返回的是

ko.observable("Gates")!!这样就串上了

也就是说这里的

getBindings': function(node, bindingContext) {
            var bindingsString = this['getBindingsString'](node, bindingContext),
                parsedBindings = bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
            return ko.components.addBindingsForCustomElement(parsedBindings, node, bindingContext, /* valueAccessors */ false);
        },

parsedBindings为一个json对象{text: function} key是text value是刚才说的ko.observable("Gates")

终于返回调用

 ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
        // Determine if it's really a custom element matching a component
        if (node.nodeType === 1) {
            var componentName = ko.components['getComponentNameForNode'](node);
            if (componentName) {
                // It does represent a component, so add a component binding for it
                allBindings = allBindings || {};

                if (allBindings['component']) {
                    // Avoid silently overwriting some other 'component' binding that may already be on the element
                    throw new Error('Cannot use the "component" binding on a custom element matching a component');
                }

                var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) };

                allBindings['component'] = valueAccessors
                    ?

function() { return componentBindingValue; } : componentBindingValue; } } return allBindings; }

这种方法注意是处理了一下大写和小写冲突的问题终于返回的还是第一个參数也就是parsedBindings

也就是终于bindings 获取的就是这里的parsedBindings 也就是一个json对象{text: function} key是text value是刚才说的ko.observable("Gates")

  var bindingsUpdater = ko.dependentObservable(
                function() {
                    bindings = sourceBindings ?

sourceBindings(bindingContext, node) : getBindings.call(provider, node, bindingContext); // Register a dependency on the binding context to support obsevable view models. if (bindings && bindingContext._subscribable) bindingContext._subscribable(); return bindings; }, null, { disposeWhenNodeIsRemoved: node } );


到这里bindings获取完成。
























以上是关于Knockout源代码精析-怎样解析demo元素,获取到bindings?的主要内容,如果未能解决你的问题,请参考以下文章

c# 怎样能写个sql的解析器

Knockout js在第二次绑定后继续显示/隐藏元素

通过PhantomJs下载带有Knockout绑定的页面

Knockout.JS如何绑定dom元素绑定

有条件地在 knockout.js 中添加元素属性

knockout为绑定元素生成id