Objective
One of the reasons for Lodash's popularity is its excellent computational performance. And its performance can have so outstanding performance, much of it is derived from its use of the algorithm-lazy evaluation.
This article will describe the principle and implementation of lazy evaluation in Lodash source code.
An analysis of the principle of inertia evaluation
Lazy Evaluation (lazy Evaluation), also known as lazy computing, lazy evaluation, called call to Demand (Call-by-need), is a concept in computer programming, its purpose is to minimize the computer to do the work .
The parameters in the lazy evaluation are not evaluated until they are needed. This kind of program actually starts from the end and executes backwards . It will determine what it needs to return and continue backwards to determine what values are required to do so.
What are the following how to speed up lo-dashx100? Introducing Lazy Evaluation. (How to improve the Lo-dash?) An introduction to lazy computing) The example in this article shows the lazy evaluation visually.
function priceLt(x) { return function(item) { return item.price < x; };}var gems = [ { name: 'Sunstone', price: 4 }, { name: 'Amethyst', price: 15 }, { name: 'Prehnite', price: 20}, { name: 'Sugilite', price: 7 }, { name: 'Diopside', price: 3 }, { name: 'Feldspar', price: 13 }, { name: 'Dioptase', price: 2 }, { name: 'Sapphire', price: 20 }]; var chosen = _(gems).filter(priceLt(10)).take(3).value();
The purpose of the program is to filter the data set to gems
Select 3 price
less than 10 of the data.
1.1 General Procedure
If you throw away lodash
this library and let you do it in a normal way, you can do it in the var chosen = _(gems).filter(priceLt(10)).take(3)
following ways:
_(gems)
Get the data set and cache it up.
Execute the filter
method again, iterate over the gems
array (length 10), and remove the data that matches the criteria:
[ { name: 'Sunstone', price: 4 }, { name: 'Sugilite', price: 7 }, { name: 'Diopside', price: 3 }, { name: 'Dioptase', price: 2 }]
Then, execute take
the method to extract the first 3 data.
[ { name: 'Sunstone', price: 4 }, { name: 'Sugilite', price: 7 }, { name: 'Diopside', price: 3 }]
The total number of traversal is: 10+3
.
An example diagram of execution is as follows:
1.2 Lazy Evaluation Procedure
The common practice has a problem: Each method does everything, does not coordinate to waste a lot of resources.
It would be better if we could write down the things we want to do first, and then wait until the data is really out, and then use the fewest number of times to achieve the goal.
This is how lazy computing is done.
Here are the ideas for implementation:
_(gems)
Get the data set, cache it.
filter
come up with a method, write it down first
take
come up with a method, write it down first
- Come
value
up with a way to explain the time
- Take the little books out and look at the requirements: to take out 3 numbers, price<10
- Use
filter
the method to judge the priceLt
data
[ { name: 'Sunstone', price: 4 }, => priceLt裁决 => 符合要求,通过 => 拿到1个 { name: 'Amethyst', price: 15 }, => priceLt裁决 => 不符合要求 { name: 'Prehnite', price: 20}, => priceLt裁决 => 不符合要求 { name: 'Sugilite', price: 7 }, => priceLt裁决 => 符合要求,通过 => 拿到2个 { name: 'Diopside', price: 3 }, => priceLt裁决 => 符合要求,通过 => 拿到3个 => 够了,收工! { name: 'Feldspar', price: 13 }, { name: 'Dioptase', price: 2 }, { name: 'Sapphire', price: 20 }]
As shown above, the results are taken only 5 times.
An example diagram of execution is as follows:
1.3 Summary
The characteristics of lazy calculation can be obtained from the above example:
- delay calculation , the calculation to be done first cache, do not execute
- Data Pipeline , data through the "adjudication" method, in this similar security procedures, the clearance of operations, and finally left only the data that meets the requirements
- Trigger Time , method cache, then a method is required to trigger execution. Lodash is
value
the use of methods that inform the real start of the calculation
Second, the realization of the lazy evaluation
According to the above characteristics, I will lodash the lazy evaluation of the implementation of the following parts of the extraction:
2.1 Caching for deferred computation
Implementation _(gems)
. I'm here for the sake of clarity, using lazy(gems)
substitution.
var MAX_ARRAY_LENGTH = 4294967295; // 最大的数组长度// 缓存数据结构体function LazyWrapper(value){ this.__wrapped__ = value; this.__iteratees__ = []; this.__takeCount__ = MAX_ARRAY_LENGTH;}// 惰性求值的入口function lazy(value){ return new LazyWrapper(value);}
this.__wrapped__
Cache data
this.__iteratees__
How to "adjudicate" in a cached data pipeline
this.__takeCount__
Record the number of data sets required to meet the requirements
In this way, a basic structure is completed.
2.2 Implementation
filter
Method
var LAZY_FILTER_FLAG = 1; // filter方法的标记// 根据 筛选方法iteratee 筛选数据function filter(iteratee){ this.__iteratees__.push({ 'iteratee': iteratee, 'type': LAZY_FILTER_FLAG }); return this;}// 绑定方法到原型链上LazyWrapper.prototype.filter = filter;
filter
method to cache the ruling method iteratee
. One important point here is the type of record that needs to be recorded iteratee
type
.
Because in lodash
, there are map
methods to filter the data, but also to pass an adjudication method iteratee
. Because filter
methods and map
methods are filtered differently, tags are used type
.
Here's another tip:
(function(){ // 私有方法 function filter(iteratee){ /* code */ } // 绑定方法到原型链上 LazyWrapper.prototype.filter = filter;})();
The prototype method is first declared with a normal function and then bound to the prototype. If you need to use the tool internally filter
, use a private, declarative method.
The advantage is that external changes LazyWrapper.prototype.filter
, within the tool, have no effect.
2.3 Implementation
take
Method
// 截取n个数据function take(n){ this.__takeCount__ = n; return this;};LazyWrapper.prototype.take = take;
2.4 implementation
value
Method
Lazy evaluation Function LazyValue () {var array = this.__wrapped__; var length = Array.Length; var resindex = 0; var takecount = this.__takecount__; var iteratees = this.__iteratees__; var iterlength = iteratees.length; var index =-1; var dir = 1; var result = []; Label Statement Outer:while (length--&& Resindex < Takecount) {//outer loop array to be processed index = = dir; var iterindex =-1; var value = Array[index]; while (++iterindex < iterlength) {//inner loop processing on chain method var data = Iteratees[iterindex]; var iteratee = data.iteratee; var type = Data.type; var computed = iteratee (value); Handling data that does not meet the requirements if (!computed) {if (type = = Lazy_filter_flag) {continue outer; }else{break outer; }}}//Through the inner loop, conforming to the required data result[resindex++] = value; } return result;}lazywrapper.prototype.value = LazyValue;
One of the key points here is: tag statements
outer: while(length-- && resIndex < takeCount){ // 外层循环待处理的数组 index += dir; var iterIndex = -1; var value = array[index]; while(++iterIndex < iterLength){ // 内层循环处理链上的方法 var data = iteratees[iterIndex]; var iteratee = data.iteratee; var type = data.type; var computed = iteratee(value); // 处理数据不符合要求的情况 if(!computed){ if(type == LAZY_FILTER_FLAG){ continue outer; }else{ break outer; } } } // 经过内层循环,符合要求的数据 result[resIndex++] = value; }
The current method of data pipeline implementation, is actually the inner layer of the while
loop. The iteratees
current data is adjudicated by removing the ruling method in the cache value
.
If the result of the award is not met, that is false
. At this time, there is no need to use the subsequent adjudication method to judge. Instead, you should jump out of the current loop.
And if you break
jump out of the inner Loop, the outer loop result[resIndex++] = value;
will still be executed, which is what we don't want to see.
It is correct to jump out of both inside and outside loops and continue the outer loop.
Tag statement, just to meet this requirement.
2.5 Small Detection
var testArr = [1, 19, 30, 2, 12, 5, 28, 4];lazy(testArr) .filter(function(x){ console.log('check x='+x); return x < 10 }) .take(2) .value();// 输出如下:check x=1check x=19check x=30check x=2// 得到结果: [1, 2]
2.6 Summary
The entire lazy evaluation of the implementation, the focus is on the data pipeline this block. Well, the label statement here is magical. In fact, the way to achieve is not just the current. However, the main point is also mentioned in the previous three. It's easy to master the essence and make it work.
Conclusion
Lazy evaluation, is I read the lodash
source code, found the biggest shining spot.
At the beginning of the lazy evaluation is not very understanding, want to see the implementation of JavaScript, but the Internet only found a document mentioned above.
The rest of the choice is to perform a split analysis of the Lodash. Also because of this, only the birth of this article.
I hope this article will be of some help to you. If you can, give a star:)
Finally, attach the simplified version of this article lazy.js
full Source: