In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article is to share with you about jQuery 2.0.3 how to use source code to analyze the Sizzle engine, the editor thinks it is very practical, so I share it with you to learn. I hope you can get something after reading this article.
What is the "precompilation" of JavaScript?
Function Aaron () {alert ("hello");}; Aaron (); / / call Aaron here to output world instead of hello function Aaron () {alert ("world");}; Aaron (); / / call Aaron here and output world, of course
In theory, two functions with identical signatures should be illegal in other programming languages. But in JavaScript, that's right. However, after the program runs, it finds a strange phenomenon: both calls are just values output from that function! Obviously, the * functions didn't work at all. And why is that?
The JavaScript execution engine does not analyze and execute programs line by line, but is precompiled segment by segment and then executed later. Moreover, in the same program, the function is predefined before it is executed, and the later defined function with the same name overrides the previously defined function. When a function is called, only the latter predefined function is called (because the latter predefined function overrides the previous predefined function). In other words, the code logic defined by * function statements has been overwritten by the second function definition statement before * calls to myfunc. So, both calls execute a function logic.
Let's prove it in practice:
/ / * Section code function Aaron () {alert ("hello");}; Aaron (); / / hello / / second paragraph code function Aaron () {alert ("world");}; Aaron (); / / world
A defined function statement in a piece of code takes precedence, which seems a bit like the compilation concept of a static language. Therefore, this feature is also called "pre-compilation" of JavaScript by some people.
So to sum up: JS parsers "precompile" function declarations and variable definitions before executing statements, and this "precompilation" is not "precompiled" page by page, but segment by segment, so-called segment is a block.
So let's take another look.
What is a compiled function?
As for this concept, I will only express it in my own language. Let's take a look at one of my uses in practical projects.
Here is a general introduction, I do is a phonegap project, basically realized a set of ppt template animation
Function setting of PPT (support for generating applications on 3 platforms)
Directly describe the user behavior data through this PPT, and then directly package to generate the corresponding implementation application, the implementation part is JS+CSS3+html5, the key is that it can be cross-platform.
Effects on PC
The elements of the page are dynamic, runnable and interactive.
The effect of mobile end
Compiled APK
Generated by a set of PPT software, the page has a large number of animation, sound, video, path animation, interaction, drag and other effects, without going into detail here, so I introduce the concept of compiler function what am I used to do?
A large system, process control is very important. To put it simply, it is which event to do at a certain stage.
But JS is actually a model of asynchronous programming.
It is common to write asynchronous code, such as common asynchronous operations:
Ajax (XMLHttpRequest)
Image Tag,Script Tag,iframe (similar principle)
SetTimeout/setInterval
CSS3 Transition/Animation
HTML5 Web Database
PostMessage
Web Workers
Web Sockets
And more...
JavaScript is a single-threaded language, so once an API blocks the current thread, it blocks the entire program, so async plays an important role in JavaScript programming. The benefits of asynchronous programming to program execution will not be discussed here, but asynchronous programming is very troublesome for developers, it will break up the program logic and lose the semantics completely. As a result, many programmers are building API that are already relevant to asynchronous programming models to simplify asynchronous programming work, such as the Promise model
Nowadays, some asynchronous process controls are mostly based on CommonJS Promises specifications, such as jsdeferred,jQuery 's own deferred and so on.
From the user's point of view, more powerful libraries often mean more API and more learning time, so that developers can choose the most appropriate method according to their own needs.
From the developer's point of view, the granularity of API, the larger the granularity, the stronger the function of API, which can complete a lot of work through a small number of calls, but large granularity often means that it is difficult to reuse. The finer-grained API tends to be more flexible, and it can be flexible enough through limited API combinations, but the combination requires "expressive force" as a cost. JavaScript has some hard wounds in terms of expressiveness.
It seems that this is a bit beside the point. Generally speaking, various asynchronous programming models are abstract. They are a set of targeted API designed to implement some commonly used asynchronous programming patterns. However, in the process of practical use, we may encounter ever-changing problems. Once we encounter a scenario in which the model does not have a "positive response", or touch upon the limitations of this model, developers often have to use some relatively ugly ways to "evade the problem".
Well, in our actual development, we use JS to express a piece of logic. Because there are different asynchronous scenarios in various environments, the code execution process will be "paused" here, waiting for the asynchronous operation to finish, and then continue to execute the subsequent code.
If this is the case,
Var a = 1; setTimeout (function () {astatine;}, 1000) alert (a) / / 1
This code is very simple, but the result is not what we want. Let's modify it.
Var a = 1; var b = function (callback) {setTimeout (function () {astatine; callback ();}, 1000)} b (function () {alert (a) / / 2})
Any ordinary JavaScript programmer can easily understand the meaning of this code, the "callback" here is not "blocking", but will free up the thread of execution until the operation is complete. And if the system itself doesn't provide a blocking API, we don't even have a way to "block" the code (of course, it shouldn't be).
What exactly is the concept of compiling functions?
JavaScript is single-threaded, and the code is executed synchronously from top to bottom, and the execution process will not be paused casually. When we encounter an asynchronous situation, which changes the whole execution process, we need to rewrite the code automatically, that is, to dynamically generate and execute new code in the process of program execution. I think this process is called a kind of application of compiler function.
I personally understand that this is just a concept, a way to express closures, like MVVM's angular has come up with a bunch of concepts, such as HTML compilers, instructions, expressions, dependency injection, etc., of course, it has something to do with Javaer.
Going back to my previous project, I personally introduced this compiler function to solve the execution error of the whole process caused by asynchronism in some part of the process, so after JS asynchronism, I will compile the whole synchronous code into a closure function, because this can retain the access of the whole scope, so when the asynchronous processing is finished, the compiler function can be called directly to match. So in the asynchronous phase, the synchronous code is also processed at the same time.
In fact, to put it bluntly, it is the use of closures, just changing an elegant word in different scenarios, so what problem does the introduction of this compiler function solve in sizzle?
Sizzle compiled function
As mentioned at the beginning of the article, the main function of sizzle in introducing this implementation is to filter word segmentation and improve the efficiency of matching one by one.
Here we go on to the previous chapter to analyze the principle.
After lexical analysis, simple filtering, and finding a suitable seed set,
The final selector extracts the seed collection seed of input
Reorganized selector selector
Div > p + div.aaron input [type= "checkbox"]
There is also a lexical analysis collection group
Meta-matcher in Sizzle
The group finally classified by tokenize has several corresponding type.
Every kind of type will have corresponding processing methods.
Expr.filter = {ATTR: function (name, operator, check) {CHILD: function (type, what, argument, first, last) {CLASS: function (className) {ID: function (id) {PSEUDO: function (pseudo, argument) {TAG: function (nodeNameSelector) {}
"Yuan" can be understood as "atom", that is, the smallest matcher. The smallest unit of each selector rule can be divided into: ATTR | CHILD | CLASS | ID | PSEUDO | TAG
There are some factory methods in Sizzle to generate the corresponding meta-matchers, which is Expr.filter.
Give two examples (the matcher of the ID type is generated by Expr.filter ["ID"], which should determine whether the id attribute of the elem is consistent with the target attribute)
Come up with 2 source codes
/ / ID meta matcher factory Expr.filter ["ID"] = function (id) {var attrId = id.replace (runescape, funescape); / / generate a matcher, return function (elem) {var node = typeof elem.getAttributeNode! = = strundefined & & elem.getAttributeNode ("id"); / / remove the id of the node to determine whether it is consistent with the target return node & & node.value = = attrId;};} / / attribute element matcher factory / / name: attribute name / / operator: operator / / check: value to check / / for example, in selector [type= "checkbox"] Name= "type" operator= "=" check= "checkbox"ATTR": function (name, operator, check) {/ / returns a meta-matcher return function (elem) {/ / fetch the attribute value var result = Sizzle.attr (elem, name) corresponding to the node first / / check to see if there is an attribute value! If (result = = null) {/ / returns true if the operator is an unequal sign, because the current property is empty and return operator = = "! =";} / / if there is no operator, then directly pass the rule if (! operator) {return true;} result + = "" / / if it is an equal sign, is it true to determine whether the target value is equal to the current attribute value? return operator = "="? Result = check: / / if it is an unequal sign, is it true to determine whether the target value is not equal to the current attribute value? is it true operator = = "! ="? Result! = = check: / / if the start is equal, determine whether the target value is in the header of the current attribute value operator = = "^ ="? Check & & result.indexOf (check) = 0: / / explain this: lang*=en matches such nodes operator = = "* ="? Check & & result.indexOf (check) >-1: / / if the end is equal, determine whether the target value is at the end of the current attribute value operator = "$="? Check & & result.slice (- check.length) = check: / / explain it this way: lang~=en matches such nodes operator = = "~ ="? ("" + result + ") .indexOf (check) >-1: / / explain it this way: lang= | en matches such nodes operator = =" | = "? Result = = check | | result.slice (0, check.length + 1) = check + "-": / / the operation symbol in other cases indicates that it does not match false;};}
Here you should think that Sizzle is actually through the selector to do "participle", break up and then find the corresponding methods from the Expr.filter to perform specific queries or filtering operations?
The answer is basically yes.
But this conventional approach is logically OK, but how efficient is it?
So Sizzle has a more specific and ingenious approach.
Sizzle introduced the concept of compiled function here.
Through the internal Sizzle.compile method
MatcherFromTokens matcherFromGroupMatchers
Analyze the relation table and generate a function to match a single selector group
MatcherFromTokens, which acts as the series and link between the selector "participle" and the matching method defined in Expr, can be said to be adaptable to all kinds of permutations and combinations of selectors. The ingenious thing about Sizzle is that it does not directly match the results of the "word segmentation" with the methods in Expr one by one, but first combines a large matching method according to the rules, and executes it step by step.
Let's take a look at how to use matcherFromTokens to generate matchers for Token?
Paste the source code first
Sizzle.compile
/ / compile function mechanism / / generate matchers by passing selector and match: compile = Sizzle.compile = function (selector, group / * Internal Use Only * /) {var I, setMatchers = [], elementMatchers = [], cached = compilerCache [selector + "] If (! cached) {/ / still see if there is a cache / / Generate a function of recursive functions that can be used to check each element if (! group) {/ / if there is no lexical parsing group = tokenize (selector);} I = group.length / / generate matcher from behind / / if there is a parallel selector, there are several equal cycles of while (iMel -) {/ / here matcherFromTokens is used to generate the matcher cached = matcherFromTokens (group [I]) of the corresponding Token. If (cached [expando]) {setMatchers.push (cached);} else {/ / ordinary matchers are pressed into the elementMatchers inside elementMatchers.push (cached) }} / / Cache the compiled function / / as you can see here, the final matcher cached = compilerCache (selector, matcherFromGroupMatchers (elementMatchers, setMatchers)) is generated through the function matcherFromGroupMatchers. } / / return the * * matcher to the select function return cached;}
MatcherFromTokens
1: / / generate function 2: / / to match a single selector group 2: / / acts as a series and link between selector "tokens" and the matching method defined in Expr. 3: / / it can be said that all kinds of permutations and combinations of selectors can adapt 4: / / Sizzle is that it does not directly match the results of the "participle" with the methods in Expr one by one. 5: / / but first combine a large matching method according to the rules, and execute it in one step. But how to execute 6: function matcherFromTokens (tokens) {7: var checkContext, matcher, j, 8: len = tokens.length, 9: leadingRelative = Expr.relative [tokens [0] .type], 10: implicitRelative = leadingRelative | | Expr.relative ["], / / affinity relationship 11: I = leadingRelative? 1: 0 12: 13: / / The foundational matcher ensures that elements are reachable from top-level context (s) 14: / / make sure that these elements can be found in context 15: matchContext = addCombinator (function (elem) {16: return elem = checkContext 17:}, implicitRelative, true), 18: matchAnyContext = addCombinator (function (elem) {19: return indexOf.call (checkContext, elem) >-1 20:}, implicitRelative, true), 21: 22: / / is used to determine which context 23: matchers = [24: function (elem, context) the element is in. Xml) {25: return (! leadingRelative & & (xml | | context! = = outermostContext)) | (26: (checkContext = context) .nodeType? 27: matchContext (elem, context, xml): 28: matchAnyContext (elem, context, xml) 29:} 30:]; 31: 32: for (; I
< len; i++) { 33: // Expr.relative 匹配关系选择器类型 34: // "空 >~ + "35: if ((matcher = Expr.relative [tokens [I] .type]) {36: / / when a relational selector is encountered, the elementMatcher function generates a function from the matchers array 37: / / (elementMatcher uses closures so matchers is always in memory) 38: matchers = [addCombinator (elementMatcher (matchers), matcher)] Else {40: / / filter ATTR CHILD CLASS ID PSEUDO TAG 41: matcher = Expr.filter [tokens [I] .type] .apply (null, tokens [I] .filter) 42: 43: / / Return special upon seeing a positional matcher 44: / / returns a special position matching function 45: / / pseudo-class divides the selector into two parts 46: if (matcher [expando]) {47: / / Find the next relative operator (if any) for proper handling 48 : / / find the next relational operator (if any) and do appropriate processing 49: J = + + I 50: for (; j
< len; j++) { 51: if (Expr.relative[tokens[j].type]) { //如果位置伪类后面还有关系选择器还需要筛选 52: break; 53: } 54: } 55: return setMatcher( 56: i >1 & & elementMatcher (matchers), 57: I > 1 & & toSelector (58: / / If the preceding token was a descendant combinator, insert an implicit any-element `* `59: tokens.slice (0) Concat ({60: value: tokens [I-2] .type = = ""? "*": "61:}) 62:) .replace (rtrim," $1 "), 63: matcher, 64: I
< j && matcherFromTokens(tokens.slice(i, j)), //如果位置伪类后面还有选择器需要筛选 65: j < len && matcherFromTokens((tokenstokens = tokens.slice(j))), //如果位置伪类后面还有关系选择器还需要筛选 66: j < len && toSelector(tokens) 67: ); 68: } 69: matchers.push(matcher); 70: } 71: } 72: 73: return elementMatcher(matchers); 74: } 重点就是 cached = matcherFromTokens(group[i]); cached 的结果就是matcherFromTokens返回的matchers编译函数了 matcherFromTokens的分解是有规律的: 语义节点+关系选择器的组合 div >P + div.aaron input [type= "checkbox"]
Expr.relative matching relationship selector type
When a relational selector is encountered, the elementMatcher function generates a function from the matchers array.
When recursively decomposing lexical elements in tokens
Put forward the processing method of * typ matching to corresponding processing.
Matcher = Expr.filter [tokens [I] .type] .apply (null, tokens [I] .types); "TAG": function (nodeNameSelector) {var nodeName = nodeNameSelector.replace (runescape, funescape). ToLowerCase (); return nodeNameSelector = = "*"? Function () {return true;}: function (elem) {return elem.nodeName & & elem.nodeName.toLowerCase () = = nodeName;};}
In fact, the final result of matcher returns a Bool value, but the return here is just a closure function and will not be executed right away. In other words, this process is compiled into an anonymous function.
Continue to decompose.
If the relationship is selected, the grouping will be merged.
Matchers = [addCombinator (elementMatcher (matchers), matcher)]
Generate a * * matcher through elementMatcher
Function elementMatcher (matchers) {/ / generate a * * matcher return matchers.length > 1? / / in the case of multiple matchers, then elem is required to comply with all matcher rules function (elem, context, xml) {var I = matchers.length / / match while from right to left {/ / if there is no match, then the node elem does not conform to the rule if (! matchers [I] (elem, context, xml)) {return false }} return true;}: / / if a single matcher is returned, you can matchers [0];}
If you look at the code, you will probably know that this sub-matcher is decomposed and another curry function is returned to the addCombinator method.
The / / addCombinator method is used to generate matchers with positional morphemes. Function addCombinator (matcher, combinator, base) {var dir = combinator.dir, checkNonElements = base & & dir = = "parentNode", donedoneName = done++ / / which relational selector return combinator.first? / / Check against closest ancestor/preceding element / / check the nearest ancestor element / / if it is a closely related location morpheme function (elem, context) Xml) {while ((elemelem = Elm [dir])) {if (elem.nodeType = 1 | | checkNonElements) {/ / find * intimate nodes Immediately use the * matcher to determine whether the node conforms to the previous rule return matcher (elem, context, xml). }: / / Check against all ancestor/preceding elements / / check the nearest ancestor element or sibling element (outline >, ~, +, and space check) / / if it is not closely related location morpheme function (elem, context, xml) {var data, cache, outerCache Dirkey = dirruns + "" + doneName / / We can't set arbitrary data on XML nodes, so they don't benefit from dir caching / / We cannot set any data on the xml node So they will not benefit from dir cache if (xml) {while ((elemelem = Elm [dir])) {if (elem.nodeType = 1 | | checkNonElements) {if (elem, context, xml)) {return true } else {while ((elemelem = ELEM [dir])) {/ / if it is not a close positional relationship / / then match until true / / for example, if the ancestral relationship Keep looking for the parent node until one ancestor node meets the rule if (elem.nodeType = 1 | | checkNonElements) {outerCache = elem [expando] | | (elem [expando] = {}) / / if there is a cache and the following conditions are met, you do not have to call the matcher function if ((cache = outerCache [dir]) & & cache [0] = dirkey) {if ((data = cache [1]) = true | | data = cachedruns) {return data = true }} else {cache = outerCache [dir] = [dirkey]; cache [1] = matcher (elem, context, xml) | | cachedruns / / cachedruns// is matching the element if (cache [1] = true) {return true;};}
Matcher is the "matcher" before the current morpheme.
Combinator is a positional morpheme
Check according to the relational selector
If it's this kind of selector without positional morphemes:'# id.aaron [name= "checkbox"]'
Just see if the current node elem matches the rule from right to left. But because of the positional morpheme
Then it is not simply to judge the current node when judging.
It may be necessary to determine whether the elem's brother or father node conforms to the rules in turn.
This is a recursive deep search process.
So matchers went through another layer of packaging.
Then recursive in the same way, directly to the end of tokens decomposition
The result returned is a deeply nested closure function grouped according to the relational selector.
Look at the structure.
But how do you do it after the combination?
The superMatcher method is return from the matcherFromGroupMatchers (elementMatchers, setMatchers) method, but it is important to execute it.
This is how jQuery 2.0.3 uses source code to analyze the Sizzle engine. The editor believes that there are some knowledge points that we may see or use in our daily work. I hope you can learn more from this article. For more details, please follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.