preface

Can tear a variety of JavaScript native functions, can be said to be necessary into the factory! At the same time, the learning and implementation of JavaScript source code can also help us quickly and solidly improve their front-end programming ability.

Recently many people and I are actively preparing for the front-end interview written test, so I organized some front-end interview written test is very easy to be asked about the implementation of the native function and a variety of front-end principle, which part of the source poke here.

Implement a new operator

We first know what new does:

  1. Create an empty simple JavaScript object (that is {});
  2. Link this object (that is, set its constructor) to another object;
  3. Use the newly created object of step (1) as the context for this;
  4. If the function does not return an object, this is returned.

We know what new does, so let’s implement it, okay

function create(Con, ... args){
  // Create an empty object
  this.obj = {};
  // Point the empty object to the constructor's prototype chain
  Object.setPrototypeOf(this.obj, Con.prototype);
  // obj binds to the constructor to access properties in the constructor, this.obj.con (args)
  let result = Con.apply(this.obj, args);
  Return if result is an object
  // The new method is invalid, otherwise obj is returned
  return result instanceof Object ? result : this.obj;
}
Copy the code

Implement an array.isarray

Thinking is very simple, is to use the Object. The prototype. ToString

Array.myIsArray = function(o) { 
  return Object.prototype.toString.call(Object(o)) === '[object Array]'; 
}; 
Copy the code

Implement an object.create () method

function create =  function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
};
Copy the code

Implement an EventEmitter

Real experience, recently in bytedance interview was asked by the interviewer, asked to write a simple Event class.

class Event {
  constructor () {
    // Store the event data structure
    // For quick lookups, use objects (dictionaries)
    this._cache = {}
  }

  / / binding
  on(type, callback) {
    // For convenience and space saving
    // Place events of the same type in an array
    // The array is a queue, followed by first-in, first-out
    // The newly bound event is emitted first
    let fns = (this._cache[type] = this._cache[type] || [])
    if(fns.indexOf(callback) === - 1) {
      fns.push(callback)
    }
    return this
	}

  / / unbundling
  off (type, callback) {
    let fns = this._cache[type]
    if(Array.isArray(fns)) {
      if(callback) {
        let index = fns.indexOf(callback)
        if(index ! = =- 1) {
          fns.splice(index, 1)}}else {
        // Empty all
        fns.length = 0}}return this
  }
  / / triggers emit
  trigger(type, data) {
    let fns = this._cache[type]
    if(Array.isArray(fns)) {
      fns.forEach((fn) = > {
        fn(data)
      })
    }
    return this
  }

  // One-time binding
  once(type, callback) {
    let wrapFun = (a)= > {
      callback.call(this);
      this.off(type, wrapFun);
    };
    this.on(type, wrapFun);
    return this; }}let e = new Event()

e.on('click'.function(){
  console.log('on')})// e.trigger('click', '666')
console.log(e)

Copy the code

Implement an array.prototype.reduce

First look at the array.prototype.reduce syntax

Array.prototype.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
Copy the code

Then you can implement it:

Array.prototype.myReduce = function(callback, initialValue) {
  let accumulator = initialValue ? initialValue : this[0];
  for (let i = initialValue ? 0 : 1; i < this.length; i++) {
    let _this = this;
    accumulator = callback(accumulator, this[i], i, _this);
  }
  return accumulator;
};

/ / use
let arr = [1.2.3.4];
let sum = arr.myReduce((acc, val) = > {
  acc += val;
  return acc;
}, 5);

console.log(sum); / / 15
Copy the code

Implement a call or apply

Let’s start with a call example to see what call does:

let foo = {
  value: 1
};
function bar() {
  console.log(this.value);
}
bar.call(foo); / / 1
Copy the code

From the code’s execution, we can see that call first changes the reference to this so that the function’s this points to foo, and then causes the bar function to execute. To sum up:

  1. Call changes the function this to point to
  2. Call a function

Think about it: How can we achieve this? The code modification is as follows:

Function.prototype.myCall = function(context) {
  context = context || window;
  // Mount the function to the object's FN property
  context.fn = this;
  // Process the parameters passed in
  const args = [...arguments].slice(1);
  // Call this method from an object's property
  constresult = context.fn(... args);// Delete this attribute
  delete context.fn;
  return result
};
Copy the code

Let’s look at the code above:

  1. The first thing we do is we do compatibility with the context parameter, we don’t pass the value, the default value of context is window;
  2. Then we mount the function onto the context,context.fn = this;
  3. Process parameters by intercepting the parameters passed into myCall, removing the first digit, and converting it to an array;
  4. Call context.fn, where fn’s this points to the context;
  5. Delete context.fn;
  6. Return the result.

By the way, let’s implement apply, the only difference is the processing of parameters, the code is as follows:

Function.prototype.myApply = function(context) {
  context = context || window
  context.fn = this
  let result
  // myApply takes the form of (obj,[arg1,arg2,arg3]);
  // So myApply's second argument is [arg1,arg2,arg3]
  // We use the extension operator to handle the way arguments are passed in
  if (arguments[1]) {result = context.fn(...arguments[1])}else {
    result = context.fn()
  }
  delete context.fn;
  return result
};
Copy the code

This is a mock implementation of Call and Apply, the only difference being how parameters are handled.

Implement a function.prototype.bind

function Person(){
  this.name="zs";
  this.age=18;
  this.gender="Male"
}
let obj={
  hobby:"Reading"
}
// Bind this to obj
let changePerson = Person.bind(obj);
// Call the constructor directly, the function will manipulate the obj object, add three attributes to it;
changePerson();
// output obj
console.log(obj);
// Create a new instance with the constructor to which this points
let p = new changePerson();
// output obj
console.log(p);
Copy the code

Take a close look at the code above and see the output.

We use bind on the Person class to point its this to obj, and we get the ChangePerson function, where if we call ChangePerson directly it will change obj, Calling ChangePerson with new gives instance P, and its __proto__ points to Person, and we see that bind is broken.

We conclude that using bind changes the function to which this refers, and that if called with the new operator, bind will be invalidated.

This object is an instanceof the constructor, so we can determine if the function is called by the new operator by simply executing the this instanceof constructor inside the function to check whether the result is true. If the result is true, the function is called by the new operator.

/ / the bind
Function.prototype.mybind = function(){
  // save the function
  let _this = this;
  // 2. Save the target object
  let context = arguments[0] | |window;
  // 3. Save the parameters outside the target object and convert them to arrays.
  let rest = Array.prototype.slice.call(arguments.1);
  // return a function to be executed
  return function F(){
    // 5. Convert the parameters passed twice to an array;
    let rest2 = Array.prototype.slice.call(arguments)
    if(this instanceof F){
      // if the new operator is used, the original function is called with new and the extension operator is passed as arguments
      return new_this(... rest2) }else{
      Context._this (rest.concat(rest2)); // use apply to call the function saved in the first step and bind this, passing the merged array of parameters._this.apply(context,rest.concat(rest2)); }}};Copy the code

Implement a JS function kerrization

The concept of Currying is not complicated, in plain English: call a function by passing it only a few arguments and having it return a function that handles the rest of the arguments.

function progressCurrying(fn, args) {

    let _this = this
    let len = fn.length;
    let args = args || [];

    return function() {
        let _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // If the number of arguments is less than the original fn.length, the recursive call continues to collect arguments
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // After parameters are collected, run fn
        return fn.apply(this, _args); }}Copy the code

Debouncing and Throttling

The throttle

The anti-vibration function onScroll will trigger once at the end of the function to delay execution

function debounce(func, wait) {
  let timeout;
  return function() {
    let context = this; // point to global
    let args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout((a)= > {
      func.apply(context, args); // context.func(args)
    }, wait);
  };
}
/ / use
window.onscroll = debounce(function() {
  console.log('debounce');
}, 1000);

Copy the code
The throttle

The throttle function onscroll is triggered every once in a while, like a water drop

function throttle(fn, delay) {
  let prevTime = Date.now();
  return function() {
    let curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(this.arguments); prevTime = curTime; }}; }/ / use
var throtteScroll = throttle(function() {
  console.log('throtte');
}, 1000);
window.onscroll = throtteScroll;
Copy the code

Hand write a deep copy of JS

The beggar version

JSON.parse(JSON.stringfy));
Copy the code

Very simple, but the pitfalls are obvious, such as copying other reference types, copying functions, circular references, and so on.

Basic version

function clone(target){
  if(typeof target === 'object') {let cloneTarget = {};
    for(const key in target){
      cloneTarget[key] = clone(target[key])
    }
    return cloneTarget;
  } else {
    return target
  }
}
Copy the code

Writing here has helped you cope with some interviewers who are testing your recursive problem-solving skills. But obviously, there are some problems with this deep-copy function.

For a more complete deep-copy function, consider both objects and arrays, and consider circular references:

function clone(target, map = new WeakMap()) {
  if(typeof target === 'object') {let cloneTarget = Array.isArray(target) ? [] : {};
    if(map.get(target)) {
      return target;
    }
    map.set(target, cloneTarget);
    for(const key in target) {
      cloneTarget[key] = clone(target[key], map)
    }
    return cloneTarget;
  } else {
    returntarget; }}Copy the code

Implement an instanceOf

L proto = r. prototype, not l.proto. Proto until proTO is null

// L represents the left expression, R represents the right expression
function instance_of(L, R) {
	var O = R.prototype;
  L = L.__proto__;
  while (true) {
		if (L === null) {return false;
		}
    	// Return true if O is strictly L
    	if (O === L) {
			return true; } L = L.__proto__; }}Copy the code

Implement a prototype chain inheritance

function myExtend(C, P) {
    var F = function(){};
    F.prototype = P.prototype;
    C.prototype = new F();
    C.prototype.constructor = C;
    C.super = P.prototype;
}
Copy the code

Implement an async/await

The principle of

The idea is to use generators to split snippets of code. Then we use a function that iterates over itself, with each yield wrapped in a promise. The timing of the next step is controlled by the promise

implementation
function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    // Promise the return value
    return new Promise(function(resolve, reject) {
      // Get an iterator instance
      var gen = fn.apply(self, args);
      // Go to the next step
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
      }
      // Throw an exception
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
      }
      // The first time
      _next(undefined);
    });
  };
}
Copy the code

Implement an array.prototype.flat () function

Recently, byteDance’s front-end interview was also asked by the interviewer, asking for handwritten implementation.

Array.prototype.myFlat = function(num = 1) {
  if (Array.isArray(this)) {
    let arr = [];
    if (!Number(num) || Number(num) < 0) {
      return this;
    }
    this.forEach(item= > {
      if(Array.isArray(item)){
        let count = num
        arr = arr.concat(item.myFlat(--count))
      } else {
        arr.push(item)
      }  
    });
    return arr;
  } else {
    throw tihs + ".flat is not a function"; }};Copy the code

Implement an event broker

This question also generally leads you to talk about event bubbling and event capture mechanisms

<ul id="color-list"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul> <script> (function () { var color_list = document.getElementById('color-list'); color_list.addEventListener('click', showColor, true); function showColor(e) { var x = e.target; if (x.nodeName.toLowerCase() === 'li') { alert(x.innerHTML); }}}) (); </script>Copy the code

Implement a Vue bidirectional binding

Object. DefineProperty version of Vue 2.x

/ / data
const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// Data hijacking
Object.defineProperty(data, 'text', {
  // Data changes - > Modify viewset(newVal) { input.value = newVal; span.innerHTML = newVal; }});// View changes --> Data changes
input.addEventListener('keyup'.function(e) {
  data.text = e.target.value;
});
Copy the code

Vue 3.x Proxy version

/ / data
const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// Data hijacking
const handler = {
  set(target, key, value) {
    target[key] = value;
    // Data changes - > Modify view
    input.value = value;
    span.innerHTML = value;
    returnvalue; }};const proxy = new Proxy(data, handler);

// View changes --> Data changes
input.addEventListener('keyup'.function(e) {
  proxy.text = e.target.value;
});

Copy the code

What are the advantages of using ES6 Proxy over Object.defineProperty for Vue bidirectional binding implementation?

Implement array.prototype.map ()

Take a look at the use of Reduce and Map

let new_array = arr.map(function callback(currentValue[, index[,array) {}[, thisArg])

let result = arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
Copy the code

Let’s start with a for loop:

Array.prototype.myMap = function(callback, thisArg) {
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    arr.push(callback.call(thisArg, this[i], i, this));
  }
  return arr;
};
Copy the code

Then reduce is used

Array.prototype.myMap2 = function(callback, thisArg) {
  let result = this.reduce((accumulator, currentValue, index, array) = > {
    accumulator.push(callback.call(thisArg, currentValue, index, array));
    returnaccumulator; } []);return result;
};
Copy the code

conclusion

Read feel to you have the help of trouble a praise encouragement, study makes me happy!

Reference:

Rewrite manual implementation of bind function – cloud + community – Tencent cloud

All kinds of source code implementation, you want to have here


I’m Cloudy, now living in Shanghai, a young front-end siege lion, love research, love technology, love to share. Personal notes, not easy to organize, thanks for your attention, reading, like and favorites. The article has any problem welcome to point out, also welcome to communicate with all kinds of front-end problems!