Preface
This is the sixth article of underscore.js source code analysis. If you are interested in this series, please click
underscore-analysis/ watch, you can see dynamic updates at any time.
There are many interesting ways to solve the problems we encounter in our daily life, such as after, before, defer, and so on. Maybe you have used them. Today, let's go deep into the source code and find out how they are realized.
<!--more-->
Specify the number of calls (after, before)
Putting these two methods ahead is also because they can solve at least two problems in our work.
If you wait for multiple asynchronous requests to complete before performing an operation fn, then you can use. after instead of writing multi-tier asynchronous callback hell to fulfill the requirements
Some applications may need to initialize and only need one initialization. The general practice is to judge a variable at the entrance. If it is true, it is thought that it has been initialized and return ed directly. If it is false, the initialization of the parameter is carried out and the variable is set to true after initialization, then the next time it enters, it will not be true. Initialization must be repeated.
For Question 1
let async1 = (cb) => { setTimeout(() => { console.log('Asynchronous Task 1 is over') cb() }, 1000) } let async2 = (cb) => { setTimeout(() => { console.log('Asynchronous Task 2 is over') cb() }, 2000) } let fn = () => { console.log('I did both tasks after they were over.') }
If task 1 and task 2 are finished before the fn task is performed, what is our general way of writing?
It may be as follows
async1(() => { async2(fn) })
This can ensure that the task fn is completed after the first two asynchronous tasks, but I believe you do not like the callback writing, here only two asynchronous tasks, if more, I am afraid it will be painful. Don't hurt. Use the underlined after function to save you.
fn = _.after(2, fn) async1(fn) async2(fn)
Operational screenshots
There is a very cool wood, do not need to be written in the form of callback hell. Then let's look at how the source code is implemented.
after Source Code Implementation
_.after = function(times, func) { return function() { // The func operation is performed only after the returned function has been called times if (--times < 1) { return func.apply(this, arguments); } }; };
Source code is simple to death, but it is so magical, properly solved our problem 1.
For Question 2
let app = { init (name, sex) { if (this.initialized) { return } // Initialization of parameters this.name = name this.sex = sex // Initialization completed, setting flags this.initialized = true }, showInfo () { console.log(this.name, this.sex) } } // Initialization of application by passing parameters app.init('qianlonog', 'boy') app.init('xiaohuihui', 'girl') app.showInfo() // qianlonog boy note that the first parameter to be passed in is printed here
Generally, when we need and only do parameter initialization once, we may do the same as above. But in fact, if we use the before method in the underline, we can do the same.
let app = { init: _.before(2, function (name, sex) { // Initialization of parameters this.name = name this.sex = sex }) , showInfo () { console.log(this.name, this.sex) } } // Initialization of application by passing parameters app.init('qianlonog', 'boy') app.init('xiaohuihui', 'girl') app.showInfo() // qianlonog boy note that the first parameter to be passed in is printed here
It's fun. Let's see how before works.
// Create a function that calls no more than times // If the number of times >= times, the return value of the last call function will be remembered and returned all the time. _.before = function(times, func) { var memo; return function() { // Each call to the returned function subtracts time by 1 if (--times > 0) { // Call func and pass in the parameters passed in from outside // It should be noted that the return value of the latter call overrides the previous one. memo = func.apply(this, arguments); } // When the number of calls is enough, set func destruction to null if (times <= 1) func = null; return memo; }; };
Let function have memory function
In the program, we often have to do some calculation operations. When we encounter time-consuming operations, if there is a mechanism, for the same input, we must get the same output, and for the same input, the subsequent calculation will be read directly from the cache, no longer need to run the calculation program is very commendable.
Give an example
let calculate = (num, num2) => { let result = 0 let start = Date.now() for (let i = 0; i< 10000000; i++) { // Here's just a simulation of time-consuming operations result += num } for (let i = 0; i< 10000000; i++) { // Here's just a simulation of time-consuming operations result += num2 } let end = Date.now() console.log(end - start) return result } calculate(1, 2) // 30000000 // log gets 235 calculate(1, 2) // 30000000 // log gets 249
For the above calculate function, the same input 1, 2, and the output of two calls are the same, and two time-consuming cycles have been taken. Look at the memoize function in the underline, how to save the second time-consuming operation for us, and give the return value of 300,000 directly.
let calculate = _.memoize((num, num2) => { let start = Date.now() let result = 0 for (let i = 0; i< 10000000; i++) { // Here's just a simulation of time-consuming operations result += num } for (let i = 0; i< 10000000; i++) { // Here's just a simulation of time-consuming operations result += num2 } let end = Date.now() console.log(end - start) return result }, function () { return [].join.call(arguments, '@') // This is to specify a unique cache key for the same input }) calculate(1, 2) // 30000000 // log gets 238 calculate(1, 2) // 30000000 // log is not printed out because it is read directly from the cache
Source Code Implementation
_.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; // Note that hasher, if passed, uses the result of hasher() execution as the key to cache the result of func() execution var address = '' + (hasher ? hasher.apply(this, arguments) : key); // If you don't find the key in the cache, calculate it once and cache it. if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); // Return results return cache[address]; }; memoize.cache = {}; return memoize; // Returns a function with cache static attributes };
I believe you have understood the source code implementation, is it simple, but also very practical and interesting?
Let's have a delay (. delay and. defer)
In the underline, some modifications are made on the basis of the original delay function setTimeout to produce the above two functions.
_.delay(function, wait, *arguments)
It's delaying wait time to execute the function, and the parameters required for the function are provided by * arguments.
Use examples
var log = _.bind(console.log, console) _.delay(log, 1000, 'hello qianlongo') // Print hello qianlongo in 1 second
Source Code Implementation
_.delay = function(func, wait) { // Read the other parameters starting with the third parameter var args = slice.call(arguments, 2); return setTimeout(function(){ // Execute func and pass in parameters. Note that when null guards undefined, this inside func refers to global window s or global return func.apply(null, args); }, wait); };
But it's worth noting that this in. delay(function, wait, *arguments)`function refers to window s or global.`
_.defer(function, *arguments)
Delay calling function until the current call stack is empty, similar to using the setTimeout method with a delay of 0. It is very useful for executing expensive calculations and HTML rendering without blocking UI threads. If the arguments parameter is passed, when the function function is executed, the arguments are passed in as a parameter.
Source Code Implementation
_.defer = _.partial(_.delay, _, 1);
So the main thing is to see what. partial is.
Function partial that can preset parameters
A function is applied locally to fill in any number of parameters without changing its dynamic this value. It is similar to the bind method. You can pass in your parameter list to specify a parameter, which should not be pre-filled.( underscore Chinese Network Translation)
Use examples
let fn = (num1, num2, num3, num4) => { let str = `num1=${num1}` str += `num2=${num2}` str += `num3=${num3}` str += `num4=${num4}` return str } fn = _.partial(fn, 1, _, 3, _) fn(2,4)// num1=1num2=2num3=3num4=4
As you can see, we pass in (in this case refers to the underline itself) to occupy the space, followed by 2 and 4 to fill the corresponding position.
How to implement the source code?
_.partial = function(func) { // Gets the preconditions other than the incoming callback function var boundArgs = slice.call(arguments, 1); var bound = function() { var position = 0, length = boundArgs.length; // First create an empty array equal in length to boundArgs var args = Array(length); // Processing placeholder elements_ for (var i = 0; i < length; i++) { // If a placeholder element of _is found in boundArgs, the boundArgs are replaced by the elements in arguments in turn. args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } // Adding other elements in auguments to boundArgs while (position < arguments.length) args.push(arguments[position++]); // Finally, executeBound is executed. Next, let's see what executeBound is. return executeBound(func, bound, this, this, args); }; return bound; };
In the last article How to write a practical bind?
Here's a review of the details.
executeBound
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { // If the invocation is not in the form of new func, call sourceFunc directly and give the corresponding parameters. if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); // Processing the form of a new call var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; };
Let's first look at what these parameters mean.
SorceFunc: The original function, the function to be bound
boundFunc: Post-binding function
Context: The context to which function this points after binding
CallngContext: The execution context of a bound function, usually this
args: The parameters required to execute the bound function
This is where the sentence is actually executed, so the key is to deal with the logic of the pre-parameters and the subsequent parameters.
sourceFunc.apply(context, args);
Pipeline function combination
You may have encountered a scenario where task A, task B and task C must be executed sequentially, and the output of A is the input of B, the output of B is the input of C, and the result is obtained after left. The following is shown in a chart.
So what is the general practice?
let funcA = (str) => { return str += '-A' } let funcB = (str) => { return str += '-B' } let funcC = (str) => { return str += '-C' } funcC(funcB(funcA('hello'))) // "hello-A-B-C"
What to do with the compose method in the underline?
let fn = _.compose(funcC, funcB, funcA) fn('hello') // "hello-A-B-C"
It doesn't seem to be the usual way to go in layers, but to use it in a very flat way.
Let's also see how the source code is implemented.
_ Compoose source code
_.compose = function() { var args = arguments; // Processing starts with the last parameter var start = args.length - 1; return function() { var i = start; // Execute the last function and get the result var result = args[start].apply(this, arguments); // Call the incoming functions one by one from back to front, and pass the results of the last execution as parameters to the next function. while (i--) result = args[i].call(this, result); // Finally, the results are derived. return result; }; };
Bind the same context to multiple functions (. bindAll(object, *methodNames))
Binding multiple function methodNames to an object context
I am very sleepy. It takes time and energy to write articles. I have been writing here for almost three hours. It seems like I am lying down and sleeping at night!!! Ah ah ah, wait and see. (I hope it won't mislead my children.)
var buttonView = { label : 'underscore', onClick: function(){ alert('clicked: ' + this.label); }, onHover: function(){ console.log('hovering: ' + this.label); } }; _.bindAll(buttonView, 'onClick', 'onHover'); $('#underscore_button').bind('click', buttonView.onClick);
Let's use the example given on the official website to illustrate that this in the default jQuery $(selector).on(eventName, callback)callback refers to the current element itself, and after the above processing, underscore pops up.
_ Implementation of. bindAll Source Code
_.bindAll = function(obj) { var i, length = arguments.length, key; // You must specify the function parameters that need to be bound to obj if (length <= 1) throw new Error('bindAll must be passed function names'); // Starting with the first argument, these are functions that need to bind this scope to obj for (i = 1; i < length; i++) { key = arguments[i]; // Call the internal bind method for this binding obj[key] = _.bind(obj[key], obj); } return obj; };
Internal use of. bind for binding, if you can see how. bind native is implemented here How to write a practical bind?
Pick up
Finally, there are two functions in the function chapter of underscore.js. In addition, throttle and debounce_will write a separate article to introduce them. Welcome to go. underscore-analysis/ watch, you can see dynamic updates at any time.
_.wrap(function, wrapper)
Wrapper encapsulates the first function into wrapper and passes function as the first parameter to wrapper. This allows wrapper to execute code before and after function, adjust parameters and then execute conditionally.
Look directly at the source code implementation
_.wrap = function(func, wrapper) { return _.partial(wrapper, func); };
Remember the partial mentioned earlier. He will return a function, wrapper will be executed internally, and func will be passed in as a parameter of wrapper.
_.negate(predicate)
The result of predicate function execution is reversed.
Use examples
let fn = () => { return true } _.negate(fn)() // false
It doesn't seem to be very soft, but...
let arr = [1, 2, 3, 4, 5, 6] let findEven = (num) => { return num % 2 === 0 } arr.filter(findEven) // [2, 4, 6]
What if we want to find an odd number?
let arr = [1, 2, 3, 4, 5, 6] let findEven = (num) => { return num % 2 === 0 } arr.filter(_.negate(findEven)) // [1, 3, 5]
Source Code Implementation
_.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; };
The source code is very simple, which is to reverse the result of the predicate function you passed in, but there are still a lot of applications.
Ending
These are the function-related APIs in underscore library, most of which have been finished, if you have a little help.
Point out a little star _______________.
Point out a little star _______________.
Point out a little star _______________.
good night 🌙