Recently I saw some friends go to big factory interview, the interviewer almost asked to tear the native code (really tear oh, not just understand the principle). These days, it’s hard to find native code, algorithms, and even programmers. I panicked myself, so I wrote down what I knew.

call

Call is a prototype method of Function that changes the orientation of this in a Function passed in. The specific implementation method is as follows:

Function.prototype.mycall = function (context, ... args) {
    context.fn = thiscontext.fn(... args) context.fn =null
}
Copy the code

The point is to add the FN property to the incoming context for it to call. In JS, this refers to whoever is calling it. This in context.fn() refers to the context, and if context.subcontext.fn(), this refers to subContext.

apply

Apply is a similar method, except that the parameters passed by apply require an array. If use… Args can deconstruct arrays in the same way that call does.

Function.prototype.mycall = function (context, args) {
    context.fn = thiscontext.fn(... args) context.fn =null
}
Copy the code

new

Look at the implementation of New before you implement BIND, because the following bind will need to understand how it works. When a function (class) is new, what does it do? According to MDN, the following happens when new:

When code new Foo(…) When executing, the following things happen:

  1. A new object inherited from foo. prototype is created.
  2. Call the constructor Foo with the specified arguments and bind this to the newly created object. New Foo is the same as new Foo(), where no argument list is specified and Foo is called without any arguments.
  3. The object returned by the constructor is the result of the new expression. If the constructor does not explicitly return an object, the object created in Step 1 is used. (Normally, constructors do not return values, but users can choose to actively return objects to override normal object creation steps.)

From the above, you can probably figure out how to implement it.

function mynew (fn, ... args) { obj = {} const result = fn.call(obj, ... args) obj.__proto__ = fn.prototype return result ! == undefined ? result : obj }Copy the code

bind

The bind approach is a little bit different. It returns a function whose this refers to the context passed in and saves the previous argument.

Let’s do the above first. Since you want to save the parameters passed in, you naturally use closures.

Function.prototype.mybind = function (context, ... firstargs) {
    const self = this
    return function (. args) { firstargs.concat(args) self.call(context, ... firstargs.concat(args)) } }Copy the code

But there’s another feature of BIND to consider

Binding functions can also be constructed using the new operator, which behaves as if the target function has already been constructed. The supplied this value is ignored, but the leading argument is still supplied to the simulation function.

The above is the official description of MDN. For example:

const obj = {a: "12".b: "54".z: "hello world"}

function bind_demo (s) {
    console.log (this.z)
    this.s = s
}

const bind_fn = bind_demo.bind(obj, "added")
bind_fn()  // hello world
console.log(obj) // { a: '12', b: '54', z: 'hello world', s: 'added' }
const new_obj = new bind_fn() // undefined
console.log(new_obj)  // bind_demo { s: 'added' }
console.log(new_obj instanceof bind_fn) // true
Copy the code

If we call the bind function directly, the result is exactly what we expect. However, when new bind_fn, the situation changes and console.log (this.z) becomes undefined, because this does not refer to obj anymore, but to the object created by new, which is an instance of Bind_demo. So you should add a judgment as to whether this refers to the class created by new or to the context passed in.

Function.prototype.mybind = function (context, ... firstargs) {
    const self = this
    const fn_bind =  function (. args) {
       firstargs.concat(args)
       self.call(this instanceof fn_bind ? this: context, ... firstargs.concat(args)) } fn_bind.prototype = self.prototypereturn fn_bind
}
Copy the code

Don’t forget, finally, to consider the prototype of the inheritance function.

An array of pat down

The first thing THAT comes to my mind about array flattening is recursion:

Array.prototype.myflatten = function () {
    return recursiveflat (this[])},function recursiveflat (arr, result) {
    if (!Array.isArray(arr)) {
        throw new Error ("The first parameter is not array.")
    }

    arr.forEach (item= > {
        if (Array.isArray(item)) {
            recursiveflat (item, result)
        } else {
            result.push (item)
        }
    })
    return result
}
Copy the code

But can we not recurse? It will be more efficient. Use some to check if the array has an array element. If so, use Apply to flatten the array passed in, and then merge the array with the empty array.

function flat(arr){
     while(arr.some(item= > Array.isArray(item))){
        arr = [].concat.apply([],arr); // Apply converts the array passed in to arguments
     }
     return arr;
}
var arr = [1.2[3.4.5[6.7.8].9].10[11.12]];
flat(arr);
Copy the code

Deep copy

This is really a recursive thing to do. Deep copy code is not difficult to implement, but the difficulty is to consider all the circumstances. Step by step.

Start with shallow copy:

function shallowClone (obj) {
    const cloneObj = {}
    const objKey = Object.keys(obj)

    objKey.forEach (item= > {
        if (Object.prototype.hasOwnProperty.call(obj, item)) {
            cloneObj[item] = obj[item]
        }
    })
    return cloneObj
}
Copy the code

Deep copy just adds recursion to shallow copy:

function deepClone (obj) {
    const cloneObj = {}
    const objKey = Object.keys(obj)

    objKey.forEach (item= > {
        if (Object.prototype.hasOwnProperty.call(obj, item)) {
            if (typeof obj[item] === 'object') {
                cloneObj[item] = deepClone(obj[item])
            } else {
                cloneObj[item] = obj[item]
            }
        }
    })
    return cloneObj
}
Copy the code

But there are some problems:

  1. Due to thetypeof nullObject, so you have to worry about null
  2. You don’t think about arrays

Here I refer to the writing method of The Great God of Wood Yi Yang, and you can read his implementation in detail:

How to implement a deep copy

First, solve the null problem and encapsulate a real method to determine the object.

function isObject(obj) {
	return typeof obj === 'object'&& obj ! =null;
}
Copy the code

Then start really implementing deep copy:

function isObject(obj) {
	return typeof obj === 'object'&& obj ! =null;
}

function deepClone (obj) {
    if(! isObject (obj)) {return obj 
    }

    const cloneObj = Array.isArray(obj) ? [] : {}
    
    for (let item in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, item)) {
            if (isObject (obj[item])) { // Arrays are also included
                cloneObj[item] = deepClone(obj[item])
            } else {
                cloneObj[item] = obj[item]
            }
        }
    }
    return cloneObj
}
Copy the code

Currie,

Before you know anything about Cremation, you need to know something about functional programming.

The so-called functional programming is a kind of programming idea, simply put, is to package the function in the program into pure function, pure function is the function concept of application mathematics: function is mapping.

So if you pass in parameters to a pure function, you get the same result, just like when you do math problems, you get the same result when you plug in a number. What’s the good of that?

The most intuitive result is predictable, unlike non-functions where the result of each call may be different, such as the pop method on the stack, making the program more robust and easier to test. Another is to write programs that are closer to natural language, such as:

operate (substract (3), add (4), multiply (10), 7)
Copy the code

Even if you don’t know the program, you can easily infer that this is a program that does a bunch of operations.

So much for functional programming, and finally for currization. Currie,

To quote wikipedia’s definition:

In computer science, Currying, also translated as carryization or callization, is a technique of converting a function that takes multiple arguments into a function that takes a single argument (the first argument of the original function), and returning a new function that takes the remaining arguments and returns a result.

See the use of the Currization function in Lodash:

const _ = require('lodash')

// The currified function
function getSum (a, b, c) { return a + b + c }
// The currie function
let curried = _.curry(getSum)
/ / test
curried(1.2.3)
curried(1) (2) (3)
curried(1.2) (3)
Copy the code

All three have the same outcome, the first one Curried is executed directly, and the other two are executed after caching parameters.

We can use closures to implement a Coriolization function:

function currying (fn) {
    return function curryfn (. args) {
        if (fn.length > args.length) {
            return function () {
                returncurryfn (... args.concat (Array.from (arguments)))}}returnfn (... args) } }Copy the code

memorize

Since the return of a pure function is predictable, you can use the cache to save the return of a previously passed argument. If the same argument is passed later, the result is returned directly without calling the function. Vue’s computed works on this principle.

function memorize (fn) {
    const cache = {}

    return function (arg) {
        if (cache[arg]) {
            return cache[arg]
        }

        cache[arg] = fn (arg)
        return cache[arg]
    }
}
Copy the code

Function composition

You can combine functions using the properties of pure functions and Currization. Typically composed functions are executed from right to left. The implementation of the combination is as follows:

function flowRight (. args) {
    return function (initValue) {
        return args.reverse().reduce ((acc, fn) = > fn (acc), initValue)
    }
}

const toUpper = s= > s.toUpperCase()
const reverse = arr= > arr.reverse()
const first = arr= > arr[0]
const f = flowRight(toUpper, first, reverse)
console.log(f(['one'.'two'.'three']))  // THREE
Copy the code

Anti-shake and throttling

It’s easy to confuse these two concepts, because they are similar in that they are intended to prevent the user from calling the function frequently over a period of time. The difference is that the function is called at a certain point in time, and if it is called again before that point in time, the previous function is cancelled.

Throttling means that after a function is called, it cannot be called again for a certain period of time.

Here refer to zhu Delong teacher in the front-end master advanced writing method, his writing method is the most comprehensive I have ever seen, details can read 3 use scenarios to help you use DOM events

First put the anti-shake implementation. It’s usually written online:

function deBounce (fn, wait = 0) {
  let timeout = null

  return function deBounced (. args) {
      if (timeout) {
          clearTimeout (timeout)
          timeout = null
      }
      setTimeout(fn(... args), wait) } }Copy the code

That’s not wrong, but it’s not considered

  1. Whether a function needs to be followed by a callback to another function
  2. Whether you need to call it manually
  3. Uncall a function

Teacher Zhu Delong’s embodiment is as follows:

const debounce = (func, wait = 0) = > {
  let timeout = null
  let args

  function debounced(. arg) {
    args = arg
    if(timeout) {
      clearTimeout(timeout)
      timeout = null 
    }

    // Return the result of the function execution as a Promise
    return new Promise((res, rej) = > {
      timeout = setTimeout(async() = > {try {
          const result = await func.apply(this, args)
          res(result)
        } catch(e) {
          rej(e)
        }
      }, wait)
    })
  }

  // Allow cancellation
  function cancel() {
    clearTimeout(timeout)
    timeout = null
  }

  // Allow immediate execution
  function flush() {
    cancel()
    return func.apply(this, args)
  }

  debounced.cancel = cancel
  debounced.flush = flush

  return debounced 
}
Copy the code

I don’t think the interview would be this detailed, but it’s worth learning.

Function throttling can be implemented in two main ways: timestamp and timer. A timestamp can be invoked only once within a specified period of time.

Timestamp:

function throttle1 (fn, wait = 0) {
   let lastTime = new Date().getTime()
   return function (. args) {
       if (lastTIme - new Date().getTime() >= wait) {
           lastTime = new Date().getTime()
           fn.apply (fn, args)
       }
   }
} 
Copy the code

A timer means that a function can only be called once within a specified time. If it is called again within a specified time, it will be called at the next specified time. The code implementation is as follows:

const throttle = (func, wait = 0) = > {
 let timeout = null
 let args
 let firstCallTimestamp

 function throttled(. arg) {
   if(! firstCallTimestamp) firstCallTimestamp =new Date().getTime()
   if(! args) {console.log('set args:', arg)
     args = arg
   }

   if (timeout) {
     clearTimeout(timeout)
     timeout = null
   }

   // Return the result of the function execution as a Promise
   return new Promise(async(res, rej) => {
     if (new Date().getTime() - firstCallTimestamp >= wait) {
       try {
         const result = await func.apply(this, args)
         res(result)
       } catch (e) {
         rej(e)
       } finally {
         cancel()
       }
     } else {
       timeout = setTimeout(async() = > {try {
           const result = await func.apply(this, args)
           res(result)
         } catch (e) {
           rej(e)
         } finally {
           cancel()
         }
       }, firstCallTimestamp + wait - new Date().getTime())  // Computes the next specified time}})}// Allow cancellation
 function cancel() {
   clearTimeout(timeout)
   args = null
   timeout = null
   firstCallTimestamp = null
 }

 // Allow immediate execution
 function flush() {
   cancel()
   return func.apply(this, args)
 }

 throttled.cancel = cancel
 throttled.flush = flush

 return throttled
}
Copy the code

Promise

I’ve written about this before, let the link go:

Promise implementation principle