This series summarizes common mock implementations from an interview point of view around JavaScript, Node.js(NPM package), and frameworks. The source code is on the Github project for long-term updates and maintenance

Array to heavy

The most primitive way to deduplicate (one-dimensional) arrays is to use a double-layer loop, which loops through the original array and creates a new one. Or we could use indexOf to simplify the inner loop; Or you can sort the original array and then rehash it, which will reduce a loop and only need to compare the two numbers before and after; Of course, we can use ES5 and ES6 methods to simplify the de-duplication method. For example, we can use filter to simplify the inner loop, or use Set, Map, and extension operators, which are easier to use, but should not be better than the original method. A two-dimensional array can be de-duplicated on the basis of the above method to determine whether the element is an array, if so, then recursive processing.

Double loop

var array = [1.1.'1'.'1'];

function unique(array) {
    var res = [];
    for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
        for (var j = 0, resLen = res.length; j < resLen; j++ ) {
            if (array[i] === res[j]) {
                break; }}if (j === resLen) {
            res.push(array[i])
        }
    }
    return res;
}

console.log(unique(array)); / / / 1, "1"
Copy the code

Using the indexOf

var array = [1.1.'1'];

function unique(array) {
    var res = [];
    for (var i = 0, len = array.length; i < len; i++) {
        var current = array[i];
        if (res.indexOf(current) === -1) {
            res.push(current)
        }
    }
    return res;
}

console.log(unique(array));
Copy the code

Sort and de-weight

var array = [1.1.'1'];

function unique(array) {
    var res = [];
    var sortedArray = array.concat().sort();
    var seen;
    for (var i = 0, len = sortedArray.length; i < len; i++) {
        // If the first element or adjacent elements are different
        if(! i || seen ! == sortedArray[i]) { res.push(sortedArray[i]) } seen = sortedArray[i]; }return res;
}

console.log(unique(array));
Copy the code

filter

Filters can be used to simplify the outer loop

Using indexOf:

var array = [1.2.1.1.'1'];

function unique(array) {
    var res = array.filter(function(item, index, array){
        return array.indexOf(item) === index;
    })
    return res;
}

console.log(unique(array));
Copy the code

Sort de-duplication:

var array = [1.2.1.1.'1'];

function unique(array) {
    return array.concat().sort().filter(function(item, index, array){
        return! index || item ! == array[index -1]})}console.log(unique(array));
Copy the code

ES6 method

Set:

var array = [1.2.1.1.'1'];

function unique(array) {
   return Array.from(new Set(array));
}

console.log(unique(array)); / / [1, 2, "1"]
Copy the code

To simplify the

function unique(array) {
    return [...new Set(array)];
}

/ / or
var unique = (a) = > [...new Set(a)]
Copy the code

The Map:

function unique (arr) {
    const seen = new Map(a)return arr.filter((a) = >! seen.has(a) && seen.set(a,1))}Copy the code

Type judgment

Note the following points when determining the type

  • Typeof returns Undefined, Null, Boolean, Number, String, Object (uppercase)

    Undefined, object, Boolean, number, string, object (lowercase), Null and object return object strings. Typeof, however, can detect function types; In summary, Typeof can detect six types, but cannot detect null types and subdivided types of Object, such as Array, Function, Date, RegExp,Error, etc.

  • Object. The prototype. ToString function is very powerful, it can detect the basic data types and Object segmentation type, even like

Math,JSON,arguments it can detect their specific types and return results in the form [object Number] (note that the last datatype is uppercase). So, the Object. The prototype. ToString basically can detect all types, but sometimes need to take into account the low version of the browser compatibility problems.

Generic API

    
Boolean Number String Function Array Date RegExp Object Error,
// All other types return object instead of making detailed judgments
// ES6 has new Symbol, Map, Set, etc
var classtype = {};


"Boolean Number String Function Array Date RegExp Object Error".split("").map(function(item) {
    classtype["[object " + item + "]"] = item.toLowerCase();
})


function type(obj) {
    / / solve null, and undefined in IE6 will be Object. The prototype. ToString identification into [Object Object]
    if (obj == null) {
        return obj + "";
    }

    / / if it is after the typeof types for object segmentation type (Array, the Function, the Date, the RegExp, Error) or object type, is to use the object. The prototype. ToString
    // Since the new Symbol, Map, Set types in ES6 are not in the classtype list, use the type function to return object
    return typeof obj === "object" || typeof obj === "function" ?
        classtype[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}
Copy the code

Determining empty objects

The for loop checks if it has an attribute, and returns false if it does

function isEmptyObject( obj ) {
        var name;
        for ( name in obj ) {
            return false;
        }
        return true;
}

console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(1)); // true
console.log(isEmptyObject(' ')); // true
console.log(isEmptyObject(true)); // true
Copy the code

We can see that isEmptyObject is actually judging for more than just empty objects. {} and {a: 1} are sufficient for isEmptyObject. If only {} is true, you can use the type function described in the previous article to filter out cases that do not fit.

Judge the Window object

A Window object has a Window attribute pointing to itself, which you can use to determine if it is a Window object

function isWindow( obj ) {
    returnobj ! =null && obj === obj.window;
}
Copy the code

Judge array

IsArray is a built-in data type judgment function for array types, but it has compatibility problems. A polyfill is shown below

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

Array of judgment classes

Jquery implements isArrayLike, which returns true for both arrays and class arrays. So if isArrayLike returns true, one of three conditions must be met:

  1. Is an array

  2. Length === 0 (” length === 0 “); false (” length === 0 “)

    function a(){
        console.log(isArrayLike(arguments))
    }
    a();
    Copy the code
  3. Lengths attribute is of type greater than 0 and obj[length-1] must exist (consider arr = [,,3])

function isArrayLike(obj) {

    // obj must have the length attribute
    varlength = !! obj &&"length" in obj && obj.length;
    var typeRes = type(obj);

    // Exclude functions and Window objects
    if (typeRes === "function" || isWindow(obj)) {
        return false;
    }

    return typeRes === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}
Copy the code

Judge NaN

Determining whether a number is a NaN cannot be done by simply using === because NaN is not equal to any number, including itself. Note that in ES6’s isNaN only NaN of numeric type returns true

isNaN: function(value){
  return isNumber(value) && isNaN(value);
}
Copy the code

Judge the DOM element

Using the DOM object’s unique nodeType attribute (

isElement: function(obj){
  return!!!!! (obj && obj.nodeType ===1);
    // Two exclamation points convert the value to a Boolean value
}
Copy the code

Judge arguments objects

Argument low version of the browser Object through the Object. The prototype. ToString returned after judgment is [Object Object], so need to be compatible

isArguments: function(obj){
  return Object.prototype.toString.call(obj) === '[object Arguments]'|| (obj ! =null && Object.hasOwnProperty.call(obj, 'callee'));
}
Copy the code

Depth copy

If it is an array, implement shallow copy, than can slice, concat returns a new array feature to implement; Parse and json.stringify can be used to implement deep copy, but there is a problem that the function cannot be copied (the array returned after copying is null). While the above methods are all tricks, let’s consider how to make a deep and shallow copy of an object or array. Shallow copy copies only one layer, which can be implemented through object.assign (). Note that when the target Object has only one layer, the deep copy, array concat(), slice() methods, etc. Deep copy is full copy, multi-layer copy, implemented by loadAsh library, extend method of jQuery, json.parse (json.stringify ()) or handwritten recursive method, etc.

Shallow copy

The idea is simple: walk through the object and put the properties and their values in a new object

var shallowCopy = function(obj) {
    // Only objects are copied
    if (typeofobj ! = ='object') return;
    // Determine whether to create an array or object based on the type of obj
    var newObj = obj instanceof Array ? [] : {};
    // Iterate over obj and determine if it is obj's property to copy
    for (var key in obj) {
        if(obj.hasOwnProperty(key)) { newObj[key] = obj[key]; }}return newObj;
}
Copy the code

Deep copy

The idea is also very simple, is in the copy of the time to determine the type of the attribute value, if the object, recursive call depth copy function is OK

var deepCopy = function(obj) {
    if (typeofobj ! = ='object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object'? deepCopy(obj[key]) : obj[key]; }}return newObj;
}
Copy the code

flat

recursive

Loop through the array element, and if it is still an array, call the method recursively

1 / / method
var arr = [1[2[3.4]]];

function flatten(arr) {
    var result = [];
    for (var i = 0, len = arr.length; i < len; i++) {
        if (Array.isArray(arr[i])) {
            result = result.concat(flatten(arr[i]))
        }
        else {
            result.push(arr[i])
        }
    }
    return result;
}


console.log(flatten(arr))
Copy the code

toString()

You can use this method if the elements of the array are all numbers

2 / / method
var arr = [1[2[3.4]]];

function flatten(arr) {
    return arr.toString().split(', ').map(function(item){
        return +item // + causes the string to be typed})}console.log(flatten(arr))
Copy the code

reduce()

3 / / method
var arr = [1[2[3.4]]];

function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}

console.log(flatten(arr))
Copy the code

.

// Flatten a one-dimensional array
var arr = [1[2[3.4]]];
console.log([].concat(... arr));// [1, 2, [3, 4]]

// You can flatten multidimensional arrays
var arr = [1[2[3.4]]];

function flatten(arr) {

    while (arr.some(item= > Array.isArray(item))) { arr = [].concat(... arr); }return arr;
}

console.log(flatten(arr))
Copy the code

Currie,

General version

function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs); }}}function multiFn(a, b, c) {
    return a * b * c;
}

var multi = curry(multiFn);

multi(2) (3) (4);
multi(2.3.4);
multi(2) (3.4);
multi(2.3) (4);
Copy the code

ES6 version

const curry = (fn, arr = []) = > (. args) = > (
  arg= >arg.length === fn.length ? fn(... arg) : curry(fn, arg) )([...arr, ...args])let curryTest=curry((a,b,c,d) = >a+b+c+d)
curryTest(1.2.3) (4) / / return 10
curryTest(1.2) (4) (3) / / return 10
curryTest(1.2) (3.4) / / return 10
Copy the code

Anti-shake and throttling

Image stabilization

function debounce(fn, wait) {
    var timeout = null;
    return function() {
        if(timeout ! = =null) 
        {
                clearTimeout(timeout);
        }
        timeout = setTimeout(fn, wait); }}// handle the function
function handle() {
    console.log(Math.random()); 
}
// Scroll events
window.addEventListener('scroll', debounce(handle, 1000));
Copy the code

The throttle

Use the timestamp implementation

   var throttle = function(func, delay) {
            var prev = 0;
            return function() {
                var context = this;
                var args = arguments;
                var now = Date.now();
                if (now - prev >= delay) {
                    func.apply(context, args);
                    prev = Date.now(); }}}function handle() {
            console.log(Math.random());
        }
        window.addEventListener('scroll', throttle(handle, 1000));
Copy the code

Using timer to achieve

   var throttle = function(func, delay) {
            var timer = null;
            return function() {
                var context = this;
                var args = arguments;
                if(! timer) { timer =setTimeout(function() {
                        func.apply(context, args);
                        timer = null; }, delay); }}}function handle() {
            console.log(Math.random());
        }
        window.addEventListener('scroll', throttle(handle, 1000));
Copy the code

Use timestamp + timer

Throttling with a timestamp or timer is ok. More precisely, you can use a timestamp + timer to execute an event handler immediately after the first event is fired and again after the last event is fired.

var throttle = function(func, delay) {
     var timer = null;
     var startTime = 0;
     return function() {
             var curTime = Date.now();
             var remaining = delay - (curTime - startTime);
             var context = this;
             var args = arguments;
             clearTimeout(timer);
              if (remaining <= 0) {
                    func.apply(context, args);
                    startTime = Date.now();
              } else {
                    timer = setTimeout(func, remaining); }}}function handle() {
      console.log(Math.random());
}
 window.addEventListener('scroll', throttle(handle, 1000));
Copy the code

Simulation of the new

  • newThe generated instance is accessibleConstructorYou can also access the properties in theConstructor.prototypeThe former can pass theapplyThe latter can be implemented by putting the instanceprotoProperty pointing to the constructorprototypeTo implement the
  • We also need to determine if the value returned is an object, and if it is an object, we return that object, and if not, what do we return
function New(){
    var obj=new Object(a);// Take the first argument, which is the constructor we pass in; Additionally, since Shift modifies the array, arguments is removed from the first argument
    Constructor=[].shift.call(arguments);
    // Point obj's prototype to the constructor so that obj can access properties in the constructor prototype
    obj._proto_=Constructor.prototype;
    // Use apply to change the reference of the constructor this to the newly created object, so that obj can access the properties in the constructor
    var ret=Constructor.apply(obj,arguments);
    // return to obj
    return typeof ret === 'object' ? ret:obj;
}
Copy the code
function Otaku(name,age){
	this.name=name;
	this.age=age;
	this.habit='Games'
}

Otaku.prototype.sayYourName=function(){
    console.log("I am" + this.name);
}

var person=objectFactory(Otaku,'Kevin'.'18')

console.log(person.name)//Kevin
console.log(person.habit)//Games
console.log(person.strength)/ / 60
Copy the code

Simulation of the call

  • call()Method calls a function or method that takes a specified this value and several specified parameter values
  • The emulated steps are: set the function to an object property – > execute the function – > delete the function
  • thisParameters can be passednullWhen tonullIs regarded as pointing towindow
  • Functions can have return values

Simple version of

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

Perfect version

Function.prototype.call2 = function(context) {
    var context=context||window
    context.fn = this;
    let args = [...arguments].slice(1);
    letresult = context.fn(... args);delete context.fn;
    return result;
}
let foo = {
    value: 1
}
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
Value = foo. Value = foo. Value = foo
bar.call2(foo, 'black'.'18') // black 18 1
Copy the code

To simulate the apply

  • apply()The implementation andcall()Similar, but with different forms of parameters
Function.prototype.apply2 = function(context = window) {
    context.fn = this
    let result;
    // Check if there is a second argument
    if(arguments[1]) { result = context.fn(... arguments[1])}else {
        result = context.fn()
    }
    delete context.fn
    return result
}
Copy the code

To simulate the bind

Function.prototype.bind2=function(context){
    var self=this
    var args=Array.prototype.slice.call(arguments.1);
    
    var fNOP=function(){};
    var fBound=function(){
        var bindArgs=Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindAt))
    }
}
Copy the code

Simulation instanceof

function instanceOf(left,right) {

    let proto = left.__proto__;
    let prototype = right.prototype
    while(true) {
        if(proto === null) return false
        if(proto === prototype) return trueproto = proto.__proto__; }}Copy the code

Simulation of JSON. Stringify

JSON.stringify(value[, replacer [, space]])

  • Boolean | Number | type String is automatically converted to the corresponding original value.

  • Undefined, arbitrary functions, and symbol are ignored (when present in an attribute value of a non-array object) or converted to NULL (when present in an array).

  • Properties that are not enumerable are ignored

  • Properties are also ignored if the value of an object’s property refers back to the object itself in some indirect way, i.e. a circular reference.

function jsonStringify(obj) {
    let type = typeof obj;
    if(type ! = ="object") {
        if (/string|undefined|function/.test(type)) {
            obj = '"' + obj + '"';
        }
        return String(obj);
    } else {
        let json = []
        let arr = Array.isArray(obj)
        for (let k in obj) {
            let v = obj[k];
            let type = typeof v;
            if (/string|undefined|function/.test(type)) {
                v = '"' + v + '"';
            } else if (type === "object") {
                v = jsonStringify(v);
            }
            json.push((arr ? "" : '"' + k + '" :) + String(v));
        }
        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
    }
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1."false".false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"
Copy the code

Simulation of JSON. The parse

JSON.parse(text[, reviver])

Used to parse JSON strings and construct JavaScript values or objects described by strings. Provides optional reviver functions to perform transformations (operations) on the resulting object before returning.

Using the eval

function jsonParse(opt) {
    return eval('(' + opt + ') ');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1."false".false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}
Copy the code

Avoid using eval() unnecessarily. Eval () is a dangerous function that executes code in executor rights. If the string code you run with Eval () is manipulated by a malicious party (someone with bad intentions), you could end up running malicious code on your web page/extension’s computer

Using the new Function ()

Function has the same string argument property as Eval. Eval and Function both have the ability to dynamically compile JS code, but are not recommended in practical programming.

var func = new Function(arg1, arg2, … , functionBody)

var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();
Copy the code

Create an object

The easiest way to create a custom Object is to create an instance of an Object and then add properties and methods to it. Early developers often used this pattern to create objects, but Object literals became the preferred method. Although methods of object constructors or object literals can be used to create objects, these methods create many objects using the same interface, resulting in a lot of repetitive code. In order to solve this problem, people began to use a variety of patterns to create objects, in these patterns, generally it is recommended to use four kinds of ways, including the constructor model, prototype model, constructors and prototypes portfolio model, dynamic prototype model, use other ways, including factory pattern, parasitic constructor, err on the side of the constructor usually use less. The most commonly used and recommended of these approaches are composite and dynamic prototyping

Constructor and stereotype composition patterns

Advantages:

  1. Addresses the shortcomings of the stereotype pattern for reference objects
  2. Solved the drawback of the prototype pattern not being able to pass parameters
  3. Resolved the drawback that constructor patterns cannot share methods
function Person(name) {
  this.name = name
  this.friends = ['lilei']
}
Person.prototype.say = function() {
  console.log(this.name)
}

var person1 = new Person('hanmeimei')
person1.say() //hanmeimei
Copy the code

Dynamic prototype pattern

Advantages:

  1. The prototype object can be modified the first time the constructor is called
  2. Modifications can be made in all instances
function Person(name) {
  this.name = name
    // Check whether say is a function
    // The sayName method is actually only added to the prototype when it was not created the first time
    If () {if () {if () {if () {if () {if () {if () {if ();
// We can use the constructor to bind the scope of the new object to the constructor. If we use the constructor, we can create a new object and break the connection.
  if(typeof this.say ! ='function') {
    Person.prototype.say = function(
    alert(this.name)
  }
}
Copy the code

inheritance

Not only does stereotype chain inheritance introduce reference defects, but we also can’t initialize inherited properties for different instances. Constructor inheritance can avoid the defects of class inheritance, but we can’t get the common methods of the parent class, that is, methods bound by prototype; Composite inheritance solves the problem of both approaches, but it calls the parent constructor twice; Parasitic combinatorial inheritance is enhanced by eliminating one extra call to the parent class constructor. Combined inheritance, parasitic inheritance, and ES6 extends inheritance are recommended. It is recommended to use ES6 inheritance directly in actual production.

Combination of inheritance

// Declare the parent class
function Animal(color) {    
  this.name = 'animal';    
  this.type = ['pig'.'cat'];    
  this.color = color;   
}     

// Add a common method
Animal.prototype.greet = function(sound) {    
  console.log(sound);   
}     

// Declare subclasses
function Dog(color) { 
  // Constructor inheritance
  Animal.apply(this.arguments);   
}   

// Class inheritance
Dog.prototype = new Animal();   

var dog = new Dog('white');   
var dog2 = new Dog('black');     

dog.type.push('dog');   
console.log(dog.color); // "white"
console.log(dog.type);  // ["pig", "cat", "dog"]

console.log(dog2.type); // ["pig", "cat"]
console.log(dog2.color);  // "black"
dog.greet('wang wang');  // "woof"
Copy the code

Prototype = Animal. Prototype; dog. prototype = Animal. Prototype; dog. prototype = Animal. T prototype. The constructor = Dog to optimize combination of inheritance, of course, the ultimate optimization way is below the parasitic combinations. Those who want to learn more about the specific optimization of composite inheritance can refer to an in-depth understanding of JavaScript prototype chains and inheritance

Parasitic combinatorial inheritance

function Animal(color) {
  this.color = color;
  this.name = 'animal';
  this.type = ['pig'.'cat'];
}

Animal.prototype.greet = function(sound) {
  console.log(sound);
}


function Dog(color) {
  Animal.apply(this.arguments);
  this.name = 'dog';
}
/* Notice the following two lines */
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.getName = function() {
  console.log(this.name);
}


var dog = new Dog('white');   
var dog2 = new Dog('black');     

dog.type.push('dog');   
console.log(dog.color);   // "white"
console.log(dog.type);   // ["pig", "cat", "dog"]

console.log(dog2.type);  // ["pig", "cat"]
console.log(dog2.color);  // "black"
dog.greet('wang wang');  // "woof"
Copy the code

The shallow copy of object.create () functions as follows:

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

Note that the constructor property on doggy. Prototype was overwritten because the Animal prototype was copied and assigned to doggy. Prototype, so we’ll fix this:

Dog.prototype.constructor = Dog;
Copy the code

Extends inheritance

class Animal {   
  constructor(color) {   
    this.color = color;   
  }   
  greet(sound) {   
    console.log(sound); }}class Dog extends Animal {   
  constructor(color) {   
    super(color);   
    this.color = color; }}let dog = new Dog('black');  
dog.greet('wang wang');  // "woof"
console.log(dog.color); // "black"
Copy the code

To realize the map

Key points: 1. What are the parameters of the callback function and how to deal with the return value? 2. Do not modify the original array.

Array.prototype.MyMap = function(fn, context){ var arr = Array.prototype.slice.call(this); // Since it is ES5, there is no need to... Var mappedArr = []; for (var i = 0; i < arr.length; i++ ){ mappedArr.push(fn.call(context, arr[i], i, this)); } return mappedArr; }Copy the code

To realize the reduce

Key points:

1. What if the initial value is not transmitted

2. What are the parameters of the callback function and how to deal with the return value?

Array.prototype.myReduce = function(fn, initialValue) { var arr = Array.prototype.slice.call(this); var res, startIndex; res = initialValue ? initialValue : arr[0]; startIndex = initialValue ? 0:1; for(var i = startIndex; i < arr.length; i++) { res = fn.call(null, res, arr[i], i, this); } return res; }Copy the code

To realize the Object. The create

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

Simulation of ajax

  • ajaxRequest process: CreateXMLHttpRequestObject, connect to server, send request, receive response data
  • After creationXMLHttpRequestObject instances have many methods and properties
    • openMethod is similar to initialization and does not initiate a real request;sendParty sends the request and accepts an optional parameter
    • When the request mode ispost, you can pass in the parameters of the request body; When the request mode isgetCan not pass or passnull;
    • Whether it’sgetandpost, all parameters need to passencodeURIComponentPost-coding splicing

General version

// Format the request data
function formateData(data) {
    let arr = [];
    for (let key in data) {
        // Avoid &,=,? Character, serialize these characters
        arr.push(encodeURIComponent(key) + '=' + data[key])
    }
    return arr.join('&');
}

function ajax(params) {
    // Process params first to prevent it from being empty
    params = params || {};
    params.data = params.data || {};

    // Common GET and POST requests
    params.type = (params.type || 'GET').toUpperCase();
    params.data = formateData(params.data);
    // In IE6, XMLHttoRequest does not exist and should be called ActiveXObject;
    let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
    if (params.type === 'GET') {
        xhr.open(params.type, params.url + '? ' + params.data, true);
        xhr.send();
    } else {
        xhr.open(params.type, params.url, true);
        xhr.setRequestHeader("Content-type"."application/x-www-form-urlencoded")
        xhr.send(params.data);
    }
    If xhr.readyState===4, the onload event is triggered, and the callback function is handled directly by the onload event
    xhr.onload = function () {
        if (xhr.status === 200 || xhr.status === 304 || xhr.status === 206) {
            var res;

            if (params.success && params.success instanceof Function) {
                res = JSON.parse(xhr.responseText); params.success.call(xhr, res); }}else {
            if (params.error && params.error instanceof Function) { res = xhr.responseText; params.error.call(xhr, res); }}}// If xhr.readyState===4, the request was returned successfully
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            // perform the onload handler}}}Copy the code

Promise version

// Implement a simple Ajax using Promise

/** * First, you might use the XHR method or attribute * onloadStart // firing at the start of sending * onloadEnd // firing at the end of sending, Success or failure * onload // get the response * onProgress // download data from the server, Every 50ms * onuploadprogress // upload to the server callback * onerror // request error * onabort // call abort * status // return status code * SetRequestHeader // set the request header * responseType // request incoming data */

// Default Ajax parameters
let ajaxDefaultOptions = {
  url: The '#'.// Request address, empty by default
  method: 'GET'.// Request mode. The default is GET request
  async: true.// Request synchronous or asynchronous, default asynchronous
  timeout: 0.// Request timeout
  dataType: 'text'.// The requested data format, default is text
  data: null.// Request parameters, default empty
  headers: {}, // Request header, empty by default
  onprogress: function () {}, // A callback to download data from the server
  onuploadprogress: function () {}, // Handles the callback for uploading files to the server
  xhr: null // Allow the XHR pass to be created outside the function, but it must not be used
};

function _ajax(paramOptions) {
  let options = {};
  for (const key in ajaxDefaultOptions) {
    options[key] = ajaxDefaultOptions[key];
  }
  // If the asynchrony passed in is the same as the default, the default is used, otherwise the parameter passed in is used
  options.async = paramOptions.async === ajaxDefaultOptions.async ? ajaxDefaultOptions.async : paramOptions.async;
  // Check whether the method passed in is GET or POST, otherwise pass GET or write the judgment inside a promise, reject
  options.method = paramOptions.method ? ("GET" || "POST") : "GET";
  // If XHR is passed externally, create one otherwise
  let xhr = options.xhr || new XMLHttpRequest();
  // return Promise object
  return new Promise(function (resolve, reject) {
    xhr.open(options.method, options.url, options.async);
    xhr.timeout = options.timeout;
    // Set the request header
    for (const key in options.headers) {
      xhr.setRequestHeader(key, options.headers[key]);
    }
    // Register XHR object events
    xhr.responseType = options.dataType;
    xhr.onprogress = options.onprogress;
    xhr.onuploadprogress = options.onuploadprogress;
    // Start registration event
    // The request succeeded
    xhr.onloadend = function () {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        resolve(xhr);
      } else {
        reject({
          errorType: "status_error".xhr: xhr }); }};// The request timed out
    xhr.ontimeout = function () {
      reject({
        errorType: "timeout_error".xhr: xhr
      });
    }
    // Request error
    xhr.onerror = function () {
      reject({
        errorType: "onerror".xhr: xhr
      });
    }
    // Abort an error.
    xhr.onabort = function () {
      reject({
        errorType: "onabort".xhr: xhr
      });
    }
    // Catch an exception
    try {
      xhr.send(options.data);
    } catch (error) {
      reject({
        errorType: "send_error".error: error }); }}); }// Call an example
_ajax({
  url: 'http://localhost:3000/suc'.async: true.onprogress: function (evt) {
    console.log(evt.position / evt.total);
  },
  dataType: 'text/json'
}).then(
  function (xhr) {
    console.log(xhr.response);
  },
  function (e) {
    console.log(JSON.stringify(e))
  });
Copy the code

To simulate the json

// function foo(data) {console.log(' get background data via jsonp :', data); document.getElementById('data').innerHTML = data; } /** * bypass the cross-domain problem */ (function jsonp() {let head =) by manually creating a script tag to send a get request * and by using the browser's cross-domain feature on <script> document.getElementsByTagName('head')[0]; Let js = document.createElement('script'); js.src = 'http://domain:port/testJSONP? a=1&b=2&callback=foo'; // set the request address head.appendChild(js); // This step will send the request})(); Foo (3); foo(3); foo(3); foo(3); foo(3 testJSONP(callback, a, b) { return `${callback}(${a + b})`; }Copy the code

Simulate the publish and subscribe model

class Pubsub { constructor() { this.handles = {} } subscribe(type, handle) { if (! this.handles[type]) { this.handles[type] = [] } this.handles[type].push(handle) } unsubscribe(type, handle) { let pos = this.handles[type].indexOf(handle) if (! handle) { this.handles.length = 0 } else { ~pos && this.handles[type].splice(pos, 1) } } publish() { let type = Array.prototype.shift.call(arguments) this.handles[type].forEach(handle => { handle.apply(this, arguments) }) } } const pub = new Pubsub() pub.subscribe('a', function() {console.log('a', ... arguments)}) pub.publish('a', 1, 2, 3) // a 1 2 3Copy the code

Use setTimeout to simulate setInterval

Call setTimeout again in the setTimeout method, you can achieve the purpose of intermittent call. So why is setTimeout recommended instead of setInterval? What is the difference between a setTimeout type intermittent call and a traditional setInterval intermittent call?

The difference is that setInterval interval calls are timed before the execution of the previous method. For example, if the interval is 500ms, the later method will be added to the execution sequence regardless of whether the previous method has completed execution at that time. The problem is that if the former method takes more than 500ms to execute and the addition is 1000ms, it means that the latter method will execute as soon as the former method completes because the interval is already more than 500ms.

“SetInterval is rarely used in a development environment because the latter is likely to start before the previous one ends.”

Simple version of

Recursively call the setTimeout function

Warning: Version 5 of ECMAScript (ES5) disallows the use of arguments.callee() in strict mode. Avoid using arguments.callee() when a function must call itself, by either giving the function expression a name or using a function declaration.

var executeTimes = 0; var intervalTime = 500; //var intervalId = null; setTimeout(timeOutFun,intervalTime); function timeOutFun(){ executeTimes++; The console. The log (" doTimeOutFun - "+ executeTimes); if(executeTimes<5){ setTimeout(arguments.callee,intervalTime); }} <! --// Release the following comments and run setInterval Demo--> <! --intervalId = setInterval(intervalFun,intervalTime); -- > <! --function intervalFun(){--> <! -- executeTimes++; -- > <! -- the console. The log (" doIntervalFun - "+ executeTimes); -- > <! -- if(executeTimes==5){--> <! -- clearInterval(intervalId); -- > <! -} -- > <! -} -- >Copy the code

Enhanced version

Let timeMap = {} let id = 0 const mySetInterval = (cb, Let fn = () => {cb() timeMap[timeId] = setTimeout(() => {cb() timeMap[timeId] = setTimeout(() => {  fn() }, time) } timeMap[timeId] = setTimeout(fn, Time) return timeId // Return timeId} const myClearInterval = (ID) => {clearTimeout(timeMap[id]) // Obtain the real ID from timeMap[id] delete timeMap[id] }Copy the code

A simulated implementation of Promise

The realization of the Promise

  • For implementingthenMethod, we need to take into account the asynchronous case, that is, whenresolveinsetTimeoutIn implementation,thenwhenstateorpendingState, we need to be inthenWhen called, the success and failure are saved into their respective arrays oncerejectorresolve, call them
  • The other thing to watch out for is how to implement itthenMethodWe default to the first onethenMethod returns onepromise, the source code provides a method, is inthenMethod returns a new onepromise, known as thePromise2: promise2 = new Promise ((resolve, reject) = > {})
    • Will thispromise2The returned value is passed to the nextthenIn the
    • If a normal value is returned, the normal value is passed to the next onethenIn the
  • resolvePromiseThe implementation of the function is a key point;promiseAs stipulated in the codeonFullfilled()oronRejected()Is the first onethenThe value returned is calledxTo determinexThe function of phi is calledresolvePromise. Specifically, first of all, we have to see if x ispromise. If it ispromise, then take its result as the newpromise2If the result of success is a normal value, it is directly usedpromise2So you have to compare x with xpromise2.resolvePromiseThe parameters arepromise2(Returned by defaultpromise),x(OurselvesreturnObject of),resolve,reject;resolveandrejectispromise2the
class Promise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); }}; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); }}; try{ executor(resolve, reject); } catch (err) { reject(err); } then(onFulfilled, onFulfilled) {// onFulfilled, onFulfilled, onFulfilled) Return value onFulfilled = typeof onFulfilled === 'function'? onFulfilled : value => value; OnRejected = typeof onRejected === 'function'? onRejected : err => { throw err }; let promise2 = new Promise((resolve, This is a big pity (forget) => {if (this. State === 'depressing ') {// async setTimeout(() => {try {let x = ondepressing (this. resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }; If (this.state === 'rejected') {// setTimeout(() => {// Let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }; If (this. State = = = 'pending') {this. OnResolvedCallbacks. Push (asynchronous setTimeout () = > {/ / (() = > {try {let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }); This. OnRejectedCallbacks. Push (asynchronous setTimeout () = > {/ / (() = > {try {let x = onRejected (enclosing a tiny); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}}, 0)); }; }); // Return promise, complete chain return promise2; } } function resolvePromise(promise2, x, resolve, Reject){// Reject error if(x === promise2){// reject error return Reject (new TypeError('Chaining cycle detected for promise')); } // Prevent multiple lets called; // x is not null and x is an object or function if (x! = = = = null && (typeof x 'object' | | typeof x = = = 'function')) {try {/ / A + rules, method statement then = x then let then = x.t hen; // If then is a function, If (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') {if (typeof then === 'function') { Y => {// Only one if (called) return can be called; called = true; ResolvePromise (promise2, y, resolve, reject); }, err => {// Success and failure can only call one if (called) return; called = true; reject(err); })} else {resolve(x); }} catch (e) {// Also called return; called = true; Reject (e); reject(e); } } else { resolve(x); }}Copy the code

Resolve, Reject, race and the implementation of race methods

Reject = function(val){return new Promise((resolve,reject)=>{reject(val)}); Return Promise((resolve,reject)=>{for(let I =0; i<promises.length; i++){ promises[i].then(resolve,reject) }; // promise. All = function(promises){let arr = []; let i = 0; function processData(index,data){ arr[index] = data; i++; if(i == promises.length){ resolve(arr); }; }; return new Promise((resolve,reject)=>{ for(let i=0; i<promises.length; i++){ promises[i].then(data=>{ processData(i,data); },reject); }; }); }Copy the code

Postscript:

Please go to the Github project home page for some specific code implementation. If you think it is helpful, please fork this project. It is not easy to move the brick.

The front-end routing

Hash way

Interface {constructor() {// Store hash and callback key pairs this.routes = {}; // Current hash this.currentUrl = "; // Record hash this.history = []; CurrentIndex = this.history.length - 1; // As a pointer to the end of this.history by default, hash this.currentIndex = this.history. this.refresh = this.refresh.bind(this); this.backOff = this.backOff.bind(this); This. isBack = false; window.addEventListener('load', this.refresh, false); window.addEventListener('hashchange', this.refresh, false); } route(path, callback) { this.routes[path] = callback || function() {}; } refresh() { this.currentUrl = location.hash.slice(1) || '/'; if (! This.isback) {// If it is not a back operation and the current pointer is smaller than the total length of the array, just cut off the previous part of the pointer and store it If (this.currentIndex < this.history.length-1) this.history =. If (this.currentIndex < this.history.length-1) this.history = this.history.slice(0, this.currentIndex + 1); this.history.push(this.currentUrl); this.currentIndex++; } this.routes[this.currentUrl](); Console. log(' pointer :', this.currentIndex, 'history:', this.history); this.isBack = false; } // backOff() {// backOff is set to true this.isback = true; this.currentIndex <= 0 ? (this.currentIndex = 0) : (this.currentIndex = this.currentIndex - 1); location.hash = `#${this.history[this.currentIndex]}`; this.routes[this.history[this.currentIndex]](); }}Copy the code

The History way

Interface {constructor() {// Store hash and callback key pairs this.routes = {}; // Current hash this.currentUrl = "; // Record hash this.history = []; CurrentIndex = this.history.length - 1; // As a pointer to the end of this.history by default, hash this.currentIndex = this.history. this.refresh = this.refresh.bind(this); this.backOff = this.backOff.bind(this); This. isBack = false; window.addEventListener('load', this.refresh, false); window.addEventListener('hashchange', this.refresh, false); } route(path, callback) { this.routes[path] = callback || function() {}; } refresh() { this.currentUrl = location.hash.slice(1) || '/'; if (! This.isback) {// If it is not a back operation and the current pointer is smaller than the total length of the array, just cut off the previous part of the pointer and store it If (this.currentIndex < this.history.length-1) this.history =. If (this.currentIndex < this.history.length-1) this.history = this.history.slice(0, this.currentIndex + 1); this.history.push(this.currentUrl); this.currentIndex++; } this.routes[this.currentUrl](); Console. log(' pointer :', this.currentIndex, 'history:', this.history); this.isBack = false; } // backOff() {// backOff is set to true this.isback = true; this.currentIndex <= 0 ? (this.currentIndex = 0) : (this.currentIndex = this.currentIndex - 1); location.hash = `#${this.history[this.currentIndex]}`; this.routes[this.history[this.currentIndex]](); }}Copy the code

Lazy loading of images

< script > / / get all the pictures of tag const imgs = document. The getElementsByTagName (' img) / / get the height of the viewing area const viewHeight = Window. The innerHeight | | document. DocumentElement. ClientHeight / / num for statistical the currently displayed by which a picture, Function lazyload(){for(let I =num; i<imgs.length; I ++) {let distance = viewHeight - imgs[I].getBoundingClientRect().top If the height of the visible area is greater than or equal to the height from the top of the element to the top of the visible area, the element is exposed if(distance >= 0){// Write an actual SRC to the element, Imgs [I].src = imgs[I].getAttribute('data-src') Num = I +1}}} // Listen for the Scroll event window.addEventListener(' Scroll ', lazyload, false); </script>Copy the code

References:

  • Ryo Hu’s JavaScript feature series
  • “Intermediate and advanced front-end interview” JavaScript handwritten code unbeatable secrets
  • Handwritten code for front-end written Test (1)