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获取完成。

时间: 2024-12-18 04:11:58

Knockout源代码精析-怎样解析demo元素,获取到bindings(二)?的相关文章

Knockout源码精析-如何解析demo元素,获取到bindings?

接上文这里开始分析applyBindingsToNodeInternal.applyBindingsToNodeInternal方法如下: function applyBindingsToNodeInternal(node, sourceBindings, bindingContext, bindingContextMayDifferFromDomParentElement) { // Prevent multiple applyBindings calls for the same node,

MVVM大比拼之vue.js源码精析

VUE 源码分析 简介 Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google.vue 如作者自己所说,在api设计上受到了很多来自knockout.angularjs等大牌框架影响,但作者相信 vue 在性能.易用性方面是有优势.同时也自己做了和其它框架的性能对比,在这里.今天以版本 0.10.4 为准 入口 Vue 的入口也很直白: ? 1 var demo = new Vue({ el: '#demo', data: { message: 'Hello V

MVVM大比拼之AngularJS源码精析

MVVM大比拼之AngularJS源码精析 简介 AngularJS的学习资源已经非常非常多了,AngularJS基础请直接看官网文档.这里推荐几个深度学习的资料: AngularJS学习笔记 作者:邹业盛 .这个笔记非常细致,记录了作者对于AngularJS各个方面的思考,其中也不乏源码级的分析. 构建自己的AngularJS .虽然放出第一章后作者就写书去了.但这第一部分已经足以带领读者深入窥探angularJS在核心概念上的实现,特别是dirty check.有愿意继续深入的读者可以去买书

2016计算机考研:数据结构常用算法精析

不知道博客园有没有计算机专业的考研党,希望列举的计算机考研考点能帮助大家吧,以下就是数据结构常用算法精析,如果大家看有什么不对的地方,欢迎纠错指正啊哈哈哈.2016考研加油!!!!!!!!! 内部排序(在内存中进行的排序不需要访问外存的)外部排序(排序量很大,通过分批的读写外存,最终完成排序) 稳定排序和非稳定排序:看相同记录的相对次序是否回发生改变.主要看在排序过程中的比较是不是相邻记录,如果是相邻比较,一定是稳定的排序.如果不是相邻的比较,就是不稳定的. 内排序方法 截止目前,各种内排序方法

2019考研数学题源探析经典1000题习题+解析分册(数一+数二+数三)

资源链接:https://pan.baidu.com/s/1gnDlPrVEQG6bd003Un-5Kg2019考研数学题源探析经典1000题习题+解析分册(数一+数二+数三)考研数学刷题必备!如下: 原文地址:http://blog.51cto.com/14084127/2320170

绝命毒师口语精析(6)

Finally, 我们终于完成了绝命毒师第一季第一集的口语精析-- 从本期开始,就踏入!!第二集啦 我能明显感觉到,我对这部剧的热爱在一点点地被消磨-- 所以从这期开始我就每集做一期,当然每期是不可能涵括所有知识点的,我会整理下来,放在文末,希望感兴趣的童鞋自行了解,欢迎在评论区交流~ 1. dime on someone 原句:He thinks maybe you dimed on him. 每个人的青春都是相似的,相信每个班上总有那么个喜欢打小报告的人,不知道你有没有被小报告过呢? 又扯远

OkHttp框架从入门到放弃,解析图片使用Picasso裁剪,二次封装OkHttpUtils,Post提交表单数据

OkHttp框架从入门到放弃,解析图片使用Picasso裁剪,二次封装OkHttpUtils,Post提交表单数据 我们这片博文就来聊聊这个反响很不错的OkHttp了,标题是我恶搞的,本篇将着重详细的分析,探索OkHttp这个框架的使用和封装 一.追其原理 Android系统提供了两种HTTP通信类 HttpURLConnection HttpClient Google推荐使用HttpURLConnection,这个没必要多说,事实上,我这篇写的应该算是比较晚了,很多优秀的博文都已经提出了这些观

css之子元素获取(未定义高度)父元素的高度

你可能碰到过这样的需求,一个高度不固定的区域(内容由用户创造),当鼠标经过该区域或者其神马操作时,需要出现一个与该区域一样大的模版: 我们用一个span来处理这个mask.由于 .sample-1 和 .sample-2 的高度是不确定的,也就是说我们没有对它们定义 height.如果父元素没有定义高度,子元素仍然可以通过 height:100% 的方式来得到父元素的实际高度. 除了IE6,几乎所有的主流浏览器都支持子元素获取父元素(未定义高度)的高度: 对于这个用户创造内容的区域,高度会在一个

JQuery元素获取

前言:这是本类别博文JQuery即用即查开篇. 因为查手册太麻烦,而且有时候需要深入命令所在详细页面才能了解此命令的具体作用是什么. 写这几篇文章就是为了,工作遇到问题时,一看这几篇文章就可以查到哪个命令的作用正好符合,然后自己再深入手册进行具体学习用法. 写的都是常用的一些,前提是你对jquery命令有一定了解,且有jquery手册. 第一篇:JQuery元素获取常见用法 1.利用标签名获取元素 $("标签名") 2.通过ID获取元素 $("#id_name")