This article will introduce the simple and efficient method of writing a JSON queryer using a function template. If you need it, you can refer to JSON as the highlights of JavaScript, it can use elegant and concise code to initialize objects and arrays. It is also a text-based data definition, which is more semantic than symbol separation and more concise than XML. Therefore, more and more JS developers are using it for data transmission and storage.
JS arrays have built-in many useful methods to facilitate data query and filtering. For example, we have a bunch of data:
The Code is as follows:
Var heros = [
// Name = intelligence ====
{Name: 'ice room witch ', DP: 38, AP: 1.3, Str: 16, Agi: 16, Int: 21 },
{Name: 'silent locker', DP: 39, AP: 1.1, Str: 17, Agi: 16, Int: 21 },
{Name: 'naga haidao ', DP: 51, AP: 6.0, Str: 21, Agi: 21, Int: 18 },
{Name: 'bounty hunter ', DP: 39, AP: 4.0, Str: 17, Agi: 21, Int: 16 },
{Name: 'highly toxic wareer', DP: 45, AP: 3.1, Str: 18, Agi: 22, Int: 15 },
{Name: 'guard of light ', DP: 38, AP: 1.1, Str: 16, Agi: 15, Int: 22 },
{Name: 'authorization', DP: 49, AP: 0.6, Str: 25, Agi: 11, Int: 25}
//...
];
To query heroes whose attacks are greater than 40 and whose defenses are less than 4, we can use the filter method of Array:
The Code is as follows:
Var match = heros. filter (function (e ){
Return e. DP> 40 & e. AP <4;
});
Returns an array containing two results that meet the conditions.
Compared with manual write loop judgment, the filter method provides us with great convenience. However, it is based on function callback. Therefore, you must write a function every time you use it. This is cumbersome for simple queries, and the callback efficiency is greatly reduced. However, there is no way to do this. To be simple, you must sacrifice certain performance. It would be perfect if you could use a simpler statement than this and have a full code expansion validity rate.
First, you can imagine that you can write the above Code as follows, and the query speed is the same as that of handwritten traversal:
The Code is as follows:
Var match = heros. select ('@ DP> 40 AND @ AP <4 ');
It looks a bit like SQL, and even the syntax has changed? In this case, I don't want to write a lot of script engine functions such as lexical analysis, semantic interpretation, and so on. Even a few thousands of lines of code are not fixed, and the efficiency is definitely worse... If the thought is so complicated, you have not understood the essence of the script. However, all scripting languages have interfaces for dynamically interpreting code during runtime, such as execute () of vbs, eval (), new Function () of js (), you can even create a script to dynamically write code.
Obviously, if another language can be translated into js Code, it can be directly handed over to the host for execution!
For example, replace "@" with "e. "," AND "is replaced with" & ", so it becomes a valid js expression AND can be executed by eval.
So what we need to do is to translate the original statement into a js statement for execution. In addition, to improve efficiency, the translated js expressions are inline into a context environment to generate an executable function body, instead of being judged by callback in each traversal.
Therefore, the function template will be used.
Function template Introduction
In C ++, there is such a macro and class template that can complete some calculations in the compilation phase, greatly improving the performance of code in the runtime era. Although the script is not compiled in a strict sense, it will be parsed and fully optimized during the first execution. This is the competition between mainstream browsers. Therefore, we need to embed the code that repeats eval into the model function provided in advance: a function that is ready for negative expression calculation:
The Code is as follows:
/**
* Template: tmplCount
* Function: counts the number of $ express expressions in the arr array.
*/
Function tmplCount (arr ){
Var count = 0;
For (var I = 0; I <arr. length; I ++ ){
Var e = arr [I];
If ($ express ){
Count ++;
}
}
Return count;
}
The preceding is a template function that traverses the arr parameter [] and counts the number of matching $ express. Except for the expressions in if (...), they are all ready. The $ express character can also be changed to another identifier, as long as it does not conflict with other characters in the function.
When we need to instantiate, we first use tmplCount. toString () converts a Function to a string format, replaces $ express with the expected expression, and finally eval to get a Function-type variable, an instance of a template function is generated!
Below is a simple demonstration:
The Code is as follows:
/**
* Function: createInstance
* Parameter: exp
* A js expression string used to replace $ express in the tmplCount Template
* Return value:
* Returns a Function, an instance of the template tmplCount.
*/
Function createInstance (exp)
{
// Replace the expression in the template
Var code = tmplCount. toString ()
. Replace ('$ express', exp );
// Prevent direct eval error of anonymous Functions
Var fn = eval ('0, '+ code );
// Return to the template instance
Return fn;
}
// Test parameters
Var student = [
{Name: 'Jane ', age: 14 },
{Name: 'jack', age: 20 },
{Name: 'Adam ', age: 18}
];
// Demo1
Var f1 = createInstance ('E. age <16 ');
Alert (f1 (student); // One
// Demo2
Var f2 = createInstance ('E. name! = "Jack" & e. age> = 14 ');
Alert (f2 (student); // 2
Note that the createInstance () parameter contains an object named e, which is defined in the tmplCount template and refers to the specific elements of the time. The returned f1 and f2 are two instances of the tmplCount template. The final f1 and f2 functions have embedded our expression statements, just as we have written two functions with the same function in advance, so we can directly run the expressions during traversal, without callback or anything, the efficiency is greatly improved.
To put it bluntly, the existence of tmplCount is only to provide the string of this function, and it will never be called. In fact, it is also defined in the form of strings, but it is more intuitive to use functions for testing.
It is worth noting that, if the script needs compression optimization later, the tmplCount template cannot participate, otherwise the corresponding "e." and "$ express" may change.
Basic JSON query function
The usage and implementation of function templates are complete. Let's look back at the previous JSON query language. We only need to translate SQL-like statements into js expressions and generate a function template instance. For the same statement, we can cache it to avoid translation every time.
First, we implement the queryer template:
The Code is as follows:
Var _ proto = Object. prototype;
//
// Template: _ tmpl
// Parameter: $ C
// Description: records and returns the Element Set matching $ C in the _ list object.
//
Var _ tmpl = function (_ list ){
Var _ ret = [];
Var _ I =-1;
For (var _ k in _ list ){
Var _ e = _ list [_ k];
If (_ e & _ e! = _ Proto [_ k]) {
If ($ C)
_ Ret [++ _ I] = _ e;
}
}
Return _ ret;
}. ToString ();
Then begin to write the select method of the Object:
The Code is as follows:
//
// Select method implementation
//
Var _ cache = {};
_ Proto. select = function (exp ){
If (! Exp)
Return [];
Var fn = _ cache [exp];
Try {
If (! Fn ){
Var code = _ interpret (exp); // interpreted expression
Code = _ tmpl. replace ('$ C', code); // apply it to the template
Fn = _ cache [exp] = _ compile (code); // instantiate a function
}
Return fn (this); // query the current object
}
Catch (e ){
Return [];
}
}
The _ cache table caches query statements. Duplicate queries can greatly improve the performance.
The Code is as follows:
Function _ compile (){
Return eval ('0, '+ arguments [0]);
}
_ Compile is written in an empty function separately to have a clean Context Environment for eval.
_ Interpret is the top priority of the system. It is responsible for translating query statements into js statements. Its implementation is insightful, but it is as simple as possible. Do not over-analyze the syntax.
View the code: jsonselect.rar
For demonstration purposes, only some basic functions are implemented currently. You can also add LIKE, BETWEEN, order by, and other common functions in the future.
Demo
The Code is as follows:
Var heros = [
// Name = intelligence ====
{Name: 'ice room witch ', DP: 38, AP: 1.3, Str: 16, Agi: 16, Int: 21 },
{Name: 'silent locker', DP: 39, AP: 1.1, Str: 17, Agi: 16, Int: 21 },
{Name: 'naga haidao ', DP: 51, AP: 6.0, Str: 21, Agi: 21, Int: 18 },
{Name: 'bounty hunter ', DP: 39, AP: 4.0, Str: 17, Agi: 21, Int: 16 },
{Name: 'highly toxic wareer', DP: 45, AP: 3.1, Str: 18, Agi: 22, Int: 15 },
{Name: 'guard of light ', DP: 38, AP: 1.1, Str: 16, Agi: 15, Int: 22 },
{Name: 'authorization', DP: 49, AP: 0.6, Str: 25, Agi: 11, Int: 25}
//...
];
The Code is as follows:
// Query: The power and agility exceed 20
// Result: nagale
Var match = heros. select ('@ Str> 20 AND @ Agi> 20 ');
// Query:
// Result: silent locks, highly toxic locks, and alchemy locks
Var match = heros. select ('Right (@ name, 1) = "Shi "');
// Query: If the lifecycle value exceeds 500
// Result: Alchemy
Var match = heros. select ('2017 + @ Str * 19> 100 ');