Do you want to see these interesting functional methods?

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.

  1. 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

  2. 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.

  1. SorceFunc: The original function, the function to be bound

  2. boundFunc: Post-binding function

  3. Context: The context to which function this points after binding

  4. CallngContext: The execution context of a bound function, usually this

  5. 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 🌙

Keywords: Javascript network JQuery

Added by IanMartins on Fri, 28 Jun 2019 04:33:06 +0300