接上文这里開始分析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(二)?的更多相关文章

  1. MVVM大比拼之knockout.js源码精析

    简介 本文主要对源码和内部机制做较深如的分析,基础部分请参阅官网文档. knockout.js (以下简称 ko )是最早将 MVVM 引入到前端的重要功臣之一.目前版本已更新到 3 .相比同类主要有 ...

  2. vue.js源码精析

    MVVM大比拼之vue.js源码精析 VUE 源码分析 简介 Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google.vue 如作者自己所说,在api设计上受到了很多 ...

  3. MVVM大比拼之AngularJS源码精析

    MVVM大比拼之AngularJS源码精析 简介 AngularJS的学习资源已经非常非常多了,AngularJS基础请直接看官网文档.这里推荐几个深度学习的资料: AngularJS学习笔记 作者: ...

  4. FFmpeg的H.264解码器源代码简单分析:解析器(Parser)部分

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  5. jquery uploadify文件上传插件用法精析

      jquery uploadify文件上传插件用法精析 CreationTime--2018年8月2日11点12分 Author:Marydon 一.参数说明 1.参数设置 $("#fil ...

  6. knockout Ajax异步无刷新分页 Demo +mvc+bootstrap

    最近工作中web客户端需要用到knockout,在此记录下一些Demo,以后用到的时候查找起来方便.也希望给新入门的knockout使用者一点经验.knockout官方文档.这儿是一个使用knocko ...

  7. 通常编译亲测56Y国际象棋源代码,精仿56Y国际象棋完整的源代码下载!

    今天公布亲测通常应编译56Y国际象棋源代码,精仿56Y牌源代码.喜欢的能够拿去研究.论坛资源太多.我们会把好的资源都公布出来,同一时候欢迎很多其它的程序猿增加我们! 增加我们的共同学习交流!     ...

  8. FFmpeg的HEVC解码器源代码简单分析:解析器(Parser)部分

    ===================================================== HEVC源代码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpe ...

  9. java 类名.class、object.getClass()和Class.forName()的区别 精析

        1.介绍 getClass()介绍 java是面向对象语言,即万物皆对象,所有的对象都直接或间接继承自Object类: Object类中有getClass()方法,通过这个方法就可以获得一个实 ...

随机推荐

  1. EntboostChat 0.9(越狱版)公布,iOS免费企业IM

    恩布互联公布IOS免费企业IM 0.9越狱预览版本号,支持全部iPhone4/5手机(6未上真机測试),iPad平板,主要功能包含单聊.群聊,企业组织结构,文本.表情.图片.文件.截图.离线消息等: ...

  2. Jmeter简单应用

    JMeter 是Apache组织的开源项目,是一个纯Java桌面应用,用于压力测试和性能测量. 1.安装jmeter jdk1.6以上下载地址:http://www.oracle.com/techne ...

  3. hdu(2846)Repository

    Problem Description When you go shopping, you can search in repository for avalible merchandises by ...

  4. USACO 2.2 Runaround Numbers

    Runaround Numbers Runaround numbers are integers with unique digits, none of which is zero (e.g., 81 ...

  5. windows命令行方式下打印和设置PATH变量

    点击开始菜单,运行=>cmd打印当前变量:echo %PATH%结果:C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;d:\PRO ...

  6. Jmeter执行多条Mysql语句报错

    花了很长时间找原因,Jmeter一直返回的是MySql语法错误,就写了两条很简单的删除语句,并且在MySql里可以正常执行 包括换了jdbc驱动包,更改不同的Query Type等 后来发现两条语句拆 ...

  7. Spring《一》

    1.支持的注入方式 构建注入,set注入 2.bean属性 id.name.class.singleton(true.false).depends-on="date"(初始化依赖) ...

  8. 【翻译】前景img-sprites, 高对比模式分析

    ->译文,原文在这里<- 本文地址: http://www.cnblogs.com/blackmanba/p/img-sprites-high-contrast.html或者http:// ...

  9. 深入分析C++虚函数表

    C++中的虚函数(Virtual Function)是用来实现动态多态性的,指的是当基类指针指向其派生类实例时,可以用基类指针调用派生类中的成员函数.如果基类指针指向不同的派生类,则它调用同一个函数就 ...

  10. Vue学习之路第十篇:简单计算器的实现

    前面九篇讲解了vue的一些基础知识,正所谓:学以致用,今天我们将用前九篇的基础知识,来模拟实现计算器的简单功能,项目价值不高,纯粹是为了加深掌握所学知识. 学前准备: 需要掌握JavaScript的e ...