接上文这里开始分析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获取完毕。