Intend to update a question every day, synchronous cattle update

Js function handwriting (1)

Js function handwriting (2)

40. String lookup

Use the most basic traversal implementation to determine if string A is included in string B and return the position of its first occurrence (-1 if not found).

Violent solution

Ideas and Algorithms

We can make the string needle match all m substrings of the string haystack once.

To reduce unnecessary matches, each time a match fails, we immediately stop matching the current substring and continue matching the next substring. If the current substring matches, we simply return the start of the current substring. If all substrings fail to match, -1 is returned.

Time complexity: O(n x m), where nn is the length of the string haystack and m is the length of the string needle. In the worst case we need to match the string needle once with all m substrings of the string haystack.

Space complexity: O(1). We just need constant space to hold some variables.

var strStr = function(haystack, needle) { let m = haystack.length; let n = needle.length; for (let i = 0; i <= m - n; i++) { let j; for (j = 0; j < n; j++) { if (needle[j] ! == haystack[i + j]) break; } // if (j === n) return I; } // Needle return-1 does not exist in haystack; };Copy the code

KMP algorithm

KMP algorithm is a fast search matching string algorithm, its role is actually the question: how to quickly find “matching string” in the “original string”.

The above naive solution has a complexity of O(m∗n) without pruning, while the complexity of THE KMP algorithm is O(m+n).

The reason why KMP can complete the search within O(m+ N) complexity is that it can extract effective information in the process of “incomplete matching” and reuse it to reduce the consumption of “repeated matching”.

  1. Matching process

Before simulating the KMP matching process, we first establish two concepts:

Prefix: For the string abcxxxxefg, we say ABC belongs to a prefix of abcXXXXefg. Suffix: For the string abcxxxxefg, we say that efg belongs to a suffix of abcXXXXefg. Then we assume that the original string is abeababeabf and the matching string is abeabf:

We can start by looking at how the match would work without using KMP (without using the substring function).

First, there is a pointer to the current matching position in the “original string” and “match string” respectively.

The “start point” for the first match is the first character, a. Obviously, the subsequent ABEAb matches, and both Pointers move to the right at the same time (black bar).

“Naive matching” is no different from “KMP” in the parts where abeAB matches.

Until the first different position appears (red label) :

Here’s where “naive matching” and “KMP” differ:

Let’s start with the “naive matching” logic:

  1. Move the pointer of the original string to the next position (b character) of the “start point”; The pointer to the matching string moves to the starting position.

  2. If a match is not found, the pointer to the original string is moved back until it matches the position of the matching string.

As shown in figure:

That is, in the case of naive matching, if a match fails, the pointer to the original string is moved to the next “start point,” the pointer to the matching string is moved to the start position, and the match is attempted again.

It is easy to understand why the complexity of “naive matching” is O(m∗n).

Then we look at the “KMP matching” process: first the matching string checks to see if the same “prefix” and “suffix” exists in the previously matched parts. If so, skip to the next position of the “prefix” to continue matching:

After jumping to the next matching position and trying to match, it is found that the characters of the two Pointers do not match, and there is no same “prefix” and “suffix” in front of the matching string pointer. At this time, you can only go back to the starting position of the matching string and start again:

At this point, you should know why KMP is faster than the naive solution:

Because KMP uses the same “prefix” and “suffix” in the matched part to speed up the next match.

Because the KMP string pointer does not backtrack (there is no naive matching to the next “start point”).

The first point is intuitive and easy to understand.

We can focus on the second point, what does it mean that the original string does not go back to the “origin point”?

What this means is that as the matching process progresses and the string pointer moves to the right, we are essentially constantly rejecting “impossible” solutions.

When we move the pointer back from I to j, it does not only mean that the characters whose subscripts are [I,j)[I,j) match or do not match the characters whose subscripts are [I,j)[I,j), but also that we reject the subset whose subscripts are [I,j)[I,j) as “matching origination points”.

  1. Analysis of the implementation

Is this the end of it? Ready to start implementing the matching process?

We can look at the complexity first. If we follow the above solution strictly, the worst case is that we need to scan the whole string, order n order n. At the same time, if the match fails, check the same prefix and suffix of the matched part and go to the corresponding position. If the match fails, check whether the previous part has the same prefix and suffix and go to the corresponding position. The complexity of this part is O(m^2)O(m 2), so the overall complexity is O(n * m^2)O(n∗m 2), whereas our naive solution is O(m * n)O(m∗n).

So there are some properties that we haven’t used yet.

Obviously, scanning the entire string is unavoidable, and the only thing we can optimize is the process of “checking for the same prefix and suffix for matched parts”.

Further, the purpose of checking “prefix” and “suffix” is “to determine where the next segment in the matching string starts to match”.

At the same time, we find that for any position in the matching string, the position of the next matching point initiated by this position is actually independent of the original string.

For example 🌰, for character D matching string abcabd, the next matching point jump initiated by it must be the position of character C. Because the same “prefix” and “suffix” of character D is the character C next to character AB.

It can be seen that the process of jumping from a certain position in the matching string to the next matching position is independent of the original string, which is called finding the next point.

Obviously we can preprocess the next array, and the value of each position in the array is the target position that the subscript should jump to (the next point).

What is the complexity when we do this optimization?

The complexity of the preprocessing next array is unknown, and the matching process can scan at most the whole string, and the complexity is O(n).

So if we want the entire KMP process to be O(m+n), we need to preprocess the next array in O(m) complexity.

So our focus is on how to handle the next array in O(m) complexity.

  1. Build the next array

Next, let’s look at how the next array is preprocessed in O(m)O(m) complexity.

Assuming there is a matching string aaABBAB, let’s see how the corresponding next is constructed.

This is how the entire next array is built, with O*(*m).

So far the complexity of the whole KMP matching process is O*(m+* N).

var strStr = function(haystack, needle) {
    // Next Pointer to the current bit of the array, the length of the original and matching strings
    let k = -1, n = haystack.length, p = needle.length;
    if (p == 0) return 0;
    // -1 indicates that the maximum prefix and suffix do not have the same value
    let next = Array(p).fill(-1);
    // Compute the next array
    calNext(needle, next);
    for (let i = 0; i < n; i++) {
        while (k > -1 && needle[k + 1] != haystack[i]) {
            // There is a partial match
            k = next[k];
        }
        if (needle[k + 1] == haystack[i]) {
            k++;
        }
        if (k == p - 1) {
            // indicate that k moves to the end of the needle and returns to the corresponding position
            return i - p + 1; }}return -1;
};

// The auxiliary function - evaluates the next array
function calNext(needle, next) {
    // start with j = 1, p = -1
    for (let j = 1, p = -1; j < needle.length; j++) {
        while (p > -1 && needle[p + 1] != needle[j]) {
            // If the next digit is different, go back
            p = next[p];
        }
        if (needle[p + 1] == needle[j]) {
            If the next bit is the same, update the same maximum prefix and maximum suffix length
            p++;
        }
        // Update the longest prefix at position jnext[j] = p; }}Copy the code

Horse car level is not enough, see did not understand, do not write.

41. Implement thousands separator

Reverse integer part

The idea is to convert a number into an array of characters, loop through the array, add a comma for every three digits, and merge it into a string. Because delimiters are added backwards in order: for example, 1234567 was added to 1,234,567 instead of 123,456,7, it is convenient to reverse the array first, and then reverse it back to the normal order. Note that if numbers have decimals, separate the decimals.

function numFormat(num) {
    // Divide by decimal point
    num = Number(num).toString().split('.');
    // Convert the integer part into a character array and sort it in reverse order
    let arr = num[0].split('').reverse();
    // store the integer to which ',' is added
    let res = [arr[0]].for (let i = 1; i < arr.length; i++) {
        // Add a delimiter
        if (i % 3 = = = 0) res.push(',');
        res.push(arr[i]);
    }
    // Reverse the integer parts into the correct order again and concatenate them into a string
    res = res.reverse().join('');
    // Add decimals if there are decimals
    if (num[1]) {
        res + ='. '+ num[1];
    }
    return res;
}

let a = 1234567894532;
let b = 673439.4542;
console.log(numFormat(a)); / / "1234567894532"
console.log(numFormat(b)); / / "673439454"
Copy the code

ToLocaleSting is included

Use the javascript function toLocaleString

Syntax: numObj. ToLocaleString ([locales [, options]])

The toLocaleString() method returns the locale-specific representation string for this number.

let a = 1234567894532;
let b = 673439.4542;

console.log(a.toLocaleString()); / / "1234567894532"
console.log(b.toLocaleString()); // "673,439.454"
Copy the code

Note that this function returns a string formatted with the default locale and default options when there is no basic use of the locale, so there may be some differences between locale numbers. It is best to make sure that the locales parameter specifies the language to use. Note: I tested the environment in which the decimals were rounded to only three places.

Regular expression

Using regular expressions and the replace function, I prefer this approach to the first two, although the regex is a little hard to understand.

The replace syntax: STR. Replace (regexp | substr, newSubStr | function)

What the first RegExp object or its literal matches is replaced by the return value of the second argument.

function numFormat(num) {
    let res = num.toString().replace(/\d+/.function(n) {
        // First extract the integer part
        // console.log(n);
        return n.replace(/(\d)(? =(\d{3})+$)/g.function($1) {
            // Forward search is followed by 3 multiples of the number
            // console.log($1);
            return $1 + ","; })})return res;
}

let a = 1234567894532;
let b = 673439.4542;
console.log(numFormat(a)); / / "1234567894532"
console.log(numFormat(b)); / / "673439454"
Copy the code

42. Basic use of regular expressions

Check if it’s a phone number

function isPhone(tel) {
	let regx = /^1[345789]\d{9}$/;
	return regx.test(tel);
}
Copy the code

Verify whether it is a mailbox

function isEmail(email) {
	let regx = /^([a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+\.) +([a-zA-Z]+)$/;
    return regx.test(email);
}
Copy the code

Verify id

function isCardNo(number) {
    // 15 bits id, 18 bits ID,
    let regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    return regx.test(number);
}
Copy the code

43. The handwritten trim

Remember that the escape character for Spaces is \s

String split array

String.prototype.myTrim = function() {
    let arr = this.split(' ');
    let i = 0;
    while (arr[i] === ' ') {
        arr.shift();
    }
    i = arr.length - 1;
    while (arr[i] === ' ') {
        arr.pop();
        i--;
    }
    return arr.join(' ');
}

console.log(' ab cdd '.myTrim());
Copy the code

Regular expression

String.prototype.myTrim = function() {
    return this.replace(/^\s+/.' ').replace(/\s+$/.' ');
}

console.log(' ab cdd '.myTrim());
Copy the code

You can combine them with the G suffix

String.prototype.myTrim = function() {
    return this.replace(/^\s+|\s+$/g.' ');
}

console.log(' ab cdd '.myTrim());
Copy the code

The above method assumes at least one whitespace character, so it is less efficient to write as follows

String.prototype.myTrim = function() {
    return this.replace(/^\s\s*/.' ').replace(/\s\s*$/.' ');
}

console.log(' ab cdd '.myTrim());
Copy the code

String interception

The normal native string interception method is far better than regular substitution, although it is a bit more complicated. However, as long as the re is not too complex, we can take advantage of the browser’s re optimization to improve program performance.

String.prototype.myTrim = function() {// trim trim = this.replace(/^\s\s*/, "); let ws = /\s/; Let I = str.length; While (ws.test(str.charat (-- I))); return str.slice(0, i + 1); } console.log(' ab cdd '.myTrim());Copy the code

See JavaScript trim function for more methods and efficiency analysis

44. Version number comparison

Take an array of input strings, sort them by version number,

For example: input: var versions = [‘ 1.45.0 ‘, ‘1.5’, ‘6’, ‘3.3.3.3.3.3.3] output: var sorted = [‘ 1.5’, ‘1.45.0’, ‘3.3.3.3.3.3’, ‘6’]

// Compare the sizes of the two versions
function compareVersion(version1, version2) {
	// Check whether the two version numbers are strings
	if(! version1 || ! version2 ||Object.prototype.toString.call(version1) ! = ='[object String]'|| Object.prototype.toString.call(version2) ! = ='[object String]') throw new Error("Version is null!");
	// Press. To split the two versions
	let arr1 = version1.trim().split('. ');
	let arr2 = version2.trim().split('. ');
	
	/ / the length
	const len = Math.min(arr1.length, arr2.length);
	for(let i = 0; i < len; i++) {
		if (Number(arr1[i]) < Number(arr2[i])) return - 1;
		else if (Number(arr1[i]) > Number(arr2[i])) return 1;
	}
	return 0;
}
let versions = ['1.45.0'.'1.5'.'6'.'3.3.3.3.3.3.3'];
console.log(versions.sort((a, b) = > compareVersion(a, b)));
Copy the code

45. The handwritten Object. Freeze

Object.freeze() function description

Object.freeze Freezes an Object so that it cannot add/delete attributes, modify the enumerability, configurability and writability of existing attributes, modify the values of existing attributes and its prototype attributes, and returns an Object with the same parameters as the one passed in

You need to use ** object.seal ()**, which seals an Object, prevents new attributes from being added and marks all existing attributes as unconfigurable. The value of the current property can be changed as long as it was previously writable.

function freeze(obj){
    // Check whether the parameter is Object
    if (obj instanceof Object) {
        // Enclose the object
        Object.seal(obj);
    }
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // Set it to read-only
            Object.defineProperty(obj, key, {
                writable: false
            });
            // If the property value is still an object, further freezing is done by recursion
            if(isObject(obj[key])) freeze(obj[key]); }}}Copy the code

46. Implement ES6 extends

Object.setPrototypeOf():

This method sets the Prototype of a given object (that is, the internal [[Prototype]] property) to another object or to NULL.

grammar

Object.setPrototypeOf(obj, proto);
Copy the code

parameter

Obj: The object to set the prototype object.

Proto: The new prototype object of this object or null, otherwise TypeError is raised.

The return value

Object that sets the new prototype object.

Object.getPrototypeOf():

This method is used to get the prototype object of the specified object.

grammar

Object.getPrototypeOf(obj);
Copy the code

parameter

Obj: The object to retrieve the prototype object.

The return value

Returns a prototype object or NULL for the specified object.

function B(name) {
	this.name = name;
}

function A(name, age) {
	// 1. Point A to B
	Object.setPrototypeOf(A, B);
	// 2. Call B with an instance of A as this and get the instance after B. This step is equivalent to calling super
	Object.getPrototypeOf(A).call(this, name);
	// 3. Add the attributes of A to the new instance
	this.age = age;
	// 4. Return a new instance object
	return this;
}

let a = new A('poetry'.22);
console.log(a);
/* age: 22 name: "poetry" */
Copy the code

48. Implement Map by hand

Map is also a constructor that ES6 provides to build a new structure for storing data. It’s essentially a collection of key-value pairs. Key corresponds to value. Key and value are unique, and any value can be used as an attribute. The problem with my handwritten version is similar to that of Set.

class MyMap {
    constructor(iterator = []) {
        // Determine whether the constructed initial data is an iterable
        if (typeof iterator[Symbol.iterator] ! = ='function') {
            throw new Error('You provided it${iterator}Is not an iterable object);
        }
        // Store data
        this.items = {};
        / / length;
        this.size = 0;
        // Loop over the iterable and add the result to MySet
        for (const item of iterator) {
            // item is an iterable object
            if (typeof item[Symbol.iterator] ! = ="function") {
                throw new Error('You provided it${item}Is not an iterable object)}const iterator = item[Symbol.iterator]();
            const key = iterator.next().value;
            const value = iterator.next().value;
            this.set(key, value); }}// Set the value of the key in MyMap. Returns the MyMap object.
    set(key, value) {
        if (!this.items.hasOwnProperty(key)) {
            this.size++;
        }
        this.items[key] = value;
        return this;
    }
    // Returns a Boolean value indicating whether the MyMap instance contains the value corresponding to the key
    has(key) {
        return this.items.hasOwnProperty(key);
    }
    // Return the value of the key, or undefined if none exists.
    get(key) {
        if (this.items.hasOwnProperty(key)) {
            return this.items[key];
        } else {
            return undefined; }}// If the element is present in MyMap, remove it and return true; Otherwise return false if the element does not exist
    delete(key) {
        if (this.items.hasOwnProperty(key)) {
            delete this.items[key];
            this.size--;
            return true;
        } else {
            return false; }}// Remove all elements from MyMap.
    clear() {
        this.items = {};
        this.size = 0;
    }
    // Returns a new iterator object containing the values of all elements in MyMap in insertion order.
    keys() {
        let keys = [];
        for (let key in this.items) {
            if (this.items.hasOwnProperty(key)) { keys.push(key); }}return keys;
    }
    // Returns a new iterator object containing the values of all elements in MyMap in insertion order.
    values() {
        let values = [];
        for (let key in this.items) {
            if (this.items.hasOwnProperty(key)) {
                values.push(this.items[key]); }}return values;
    }
    // Returns a new iterator object that contains the [value, value] array of the values of all elements in Set in insertion order. To keep the method similar to the Map object, the keys and values of each value are equal.
    entries() {
        let entries = [];
        for (let key in this.items) {
            if (this.items.hasOwnProperty(key)) {
                entries.push([key, this.items[key]]); }}return entries;
    }
    // Iterate over, returns a new iterator object that contains the values of all elements in MyMap in insertion order.* [Symbol.iterator]() {
        for (const key in this.items) {
            yield [this.items[key], key]; }}// callBackFn is called once for each value in the MyMap object, in insertion order. If thisArg argument is provided, this in the callback will be that argument.
    forEach(callBackFn, thisArgs = this) {
        for (const key in this.items) {
            callBackFn.call(thisArgs, this.items[key], key, this.items); }}}let myMap = new MyMap();

let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';

/ / add the key
myMap.set(keyString, "Value associated with key 'a string'");
myMap.set(keyObj, "Value associated with keyObj");
myMap.set(keyFunc, "Value associated with keyFunc");
console.log(myMap);
console.log(myMap.size); / / 3

/ / read the values
console.log(myMap.get(keyString));    // "Value associated with key 'a string'"
console.log(myMap.get(keyObj));       // "Value associated with keyObj"
console.log(myMap.get(keyFunc));      // "Value associated with keyFunc"

console.log(myMap.get('a string'));   // "Value associated with key 'a string'"
                         // Because keyString === 'a string'
console.log(myMap.get({}));           // undefined because keyObj! = = {}
console.log(myMap.get(function() {})); // undefined because keyFunc! == function () {}
Copy the code

49. Detect object cyclic references

The object itself is checked for circular references, which is already included in the improved deep copy

For this reason, WeakSet is well suited to handle this situation using WeakSet simplification, note the need to create WeakSet on the first run and pass it along with each subsequent function call (using the internal parameter _refs). WeakSet can only hold objects, and the number of objects or the order in which they are traversed doesn’t matter, so WeakSet is better for (and performs) tracking object references than Set, especially if a large number of objects are involved.

// Check for circular references to the obj object passed in
function execRecursively(obj) {
    // Store pre-hierarchy objects
    let ws = new WeakSet(a);/ / sign
    let flag = false;
    function dp(obj) {
        // Ensure that the current element is an object, or if there is already a loop, return it directly
        if (typeofobj ! = ="object" || flag) return;
        // Store the current layer object
        let cws = new WeakSet(a);if(! ws.has(obj)) ws.add(obj);// Iterate once to check if the current layer has the same element
        for (let key in obj) {
            if (typeof obj[key] === "object") {
                // If the reference at the same level is the same, delete it
                if (cws.has(obj[key])) {
                    // Find the circular reference
                    delete obj[key];
                } else{ cws.add(obj[key]); }}}// Iterate over the current layer to see if there is a circular reference
        for (let key in obj) {
            if (typeof obj[key] === "object") {
                if (ws.has(obj[key])) {
                    // Find the circular reference
                    flag = true;
                    break;
                } else {
                    ws.add(obj[key]);
                }
                // Recursively check for recycling
                dp(obj[key]);
            }
        }
    }
    dp(obj);
    return flag;
}

let obj1 = { a: "1" };
obj1.b = {};
obj1.b.a = obj1.b;
obj1.b.b = obj1.b;


let obj2 = { a: { c: "1"}}; obj2.a.b = obj2;let obj3 = { a: 1.b: 2.c: { d: 4 }, d: {}, e: {}}let obj4 = { a: "1" };
obj4.b = { c: 1 };
obj4.aa = obj4.b;
obj4.bb = obj4.b;

let obj5 = { a: "1" };
obj5.b = {};
obj5.b.a = obj5.b;
obj5.b.b = obj5.b;

let obj6 = { a: {c: "1"}}; obj6.b = {}; obj6.b.d = obj6.a;console.log(execRecursively(obj1)); // true
console.log(execRecursively(obj2)); // true
console.log(execRecursively(obj3)); // false
console.log(execRecursively(obj4)); // false
console.log(execRecursively(obj5)); // true
console.log(execRecursively(obj6)); // false
Copy the code

50. Singleton pattern

Only create objects when appropriate, and only one. The responsibility for creating objects and managing singletons in the singleton pattern is split between two different methods that together have the power of the singleton pattern.

Using closures to implement the singleton pattern, which I wrote is also known as the lazy singleton pattern, without instantiating the class from the start:

function Singleton (name) {
	this.name = name;
};

Singleton.getInstance = (function(name) {
	// Instance object
	let instance;
	return function(name) {
		if(! instance) { instance =new Singleton (name);
		}
		return instance;
	}
})();

/ / test
var a = Singleton.getInstance('ConardLi');
var b = Singleton.getInstance('ConardLi2');

console.log(a === b);   //true
Copy the code

51. Observer mode

First, I want to analyze the similarities and differences between the observer model and the publish/subscribe model

The observer pattern is different from the publish/subscribe pattern

When looking through the materials, some people equate the Observer mode with the Publish/Subscribe mode, while others think there are differences between the two modes. In my opinion, there are indeed differences, and the essential difference is the difference in scheduling.

Observer model

The concept of comparison is that the target and observer are base classes, the target provides a set of methods for maintaining the observer, and the observer provides an update interface. The specific observer and the specific target inherit their respective base classes, and then the specific observer registers itself with the specific target, scheduling the update method of the observer when the specific target changes.

For example, there is A specific target, A, of the “weather center”, which is dedicated to monitoring the weather changes, and an observer, B, who has an interface to display the weather, will register himself in A. When A triggers the weather changes, B will schedule the update method of B, and bring its own context.

Publish/subscribe

The definition of comparison is that subscribers register the event they want to subscribe to with the dispatch center. When the event is triggered, the publisher publishes the event to the dispatch center (with context), and the dispatch center schedules the processing code that subscribers register with the dispatch center.

For example, if an interface displays the weather in real time, it subscribs to weather events (registering with the dispatch center, including handlers), and when the weather changes (regularly retrieving data), it publishes weather information as a publisher to the dispatch center, which dispatches the subscriber’s weather handlers.

conclusion

1. As can be seen from the two pictures, the biggest difference is the place of scheduling.

Although the two modes are subscribers and publishers (specific observer can be regarded as the subscriber, specific goals can be considered a publisher), but the observer pattern is scheduled by specific goals, and publish/subscribe pattern is unified by the dispatching center, so the observer pattern between subscribers and publishers are dependent, and publish/subscribe pattern will not.

2. Both patterns can be used for loose coupling, improved code management, and potential reuse.

Implementation of the observer pattern

Advantages of the observer model

  • Can be widely used in asynchronous programming, it can replace our traditional callback function
  • We do not care about the internal state of the object during the asynchronous execution phase, we only care about the point in time at which the event is completed
  • The role is clear, without an event scheduler as an intermediary one object does not have to explicitly call another object’s interface, but rather is loosely coupled together. The target objectSubjectAnd the observerObserverAll implement the convention’s member methods.
  • The two parties are closely connected, and the target object has a strong initiative, collecting and maintaining the observer themselves, and actively notifying the observer of updates when the status changes. Although they do not know the details of each other, it does not affect their communication. More importantly, changes in one object do not affect the other.

The capability of a subscriber is very simple. As a passive party, it has only two actions — to be notified and to perform (essentially to accept a call from the publisher, which we have already done in the publisher).

The basic actions of publishers are first to add subscribers, then to notify subscribers, and finally to remove subscribers.

/ / observer
class Observer {
    /** * constructor *@param {Function} Cb callback function that executes */ when receiving a notification from the target object
    constructor(cb) {
    	if (typeof cb === 'function') {
    		this.cb = cb;
    	} else {
    		throw new Error('Observer constructor must pass function type! '); }}/** * executes the callback function */ when notified by the target object
    update() {
    	this.cb(); }}// Target object, publisher class
class Subject {
	constructor() {
		// Maintain a list of observers
		this.observers = [];
	}
	/** * Add an observer *@param {Observer} Observer Observer instance */
    add(observer) {
    	this.observers.push(observer);
    }
	/** * Removes an observer *@param {Observer} Observer Observer instance */
    remove(observer) {
    	this.observers.forEach((item, i) = > {
    		if (item === observer) {
    			this.observers.splice(i, 1); }}); }/** * notify all observers */
    notify() {
    	this.observers.forEach(observer= >{ observer.update(); }); }}const observerCallback = function() {
    console.log('I've been informed.');
}
const observer = new Observer(observerCallback);

const subject = new Subject();
subject.add(observer);
subject.notify(); // I have been informed
Copy the code

52. Publish/Subscribe (EventBus/EventEmitter)

EventEmitter is a typical publish/subscribe model that implements an event scheduling center. Publish and subscribe mode includes three roles: publisher, event scheduling center and subscriber. Publishers and subscribers are loosely coupled and don’t care if the other exists, they care about the event itself. Publishers borrow the EMIT method provided by the event dispatch center to publish events, while subscribers subscribe via ON.

Advantages:

  • In publish subscribe mode, for publishersPublisherAnd the subscriberSubscriberWithout special constraints, they act as if they are anonymous, publishing and subscribing to events through an interface provided by the event scheduling center without knowing who the other person is.
  • Loosely coupled, flexible, commonly used as an event bus
  • Easy to understand and can be compared withDOMIn the eventdispatchEventandaddEventListener.

Disadvantages:

  • When the number of event types increases, it is difficult to maintain. Therefore, event naming conventions should be considered and data flow chaos should be prevented.

The “crew” of Event Bus (Vue, Flutter, etc.) and Event Emitter (Node, etc.) are different, but they all correspond to a common role — global Event Bus.

The Event Bus is used in Vue to communicate between components

Event Bus/Event Emitter, as a global Event Bus, functions as a communication bridge. We can think of it as an event center, where subscriptions/publishing of all our events cannot be “privately communicated” between subscribers and publishers, and must be delegated to this event center for us.

In Vue, sometimes components A and B are far apart, seemingly unrelated, but we want them to communicate. In this case, in addition to resorting to Vuex, we can also realize our requirements through Event Bus. There are no specific publishers or subscribers (like PrdPublisher and DeveloperObserver above) throughout the call, and bus is the only thing making a mad dash for presence. This is the nature of the global event bus – all publish/subscribe operations for events must go through the event center, with no “sweetheart deals”!

class EventEmitter {
	constructor() {
		// Store event listeners and callback functions
		this.listeners = {};
	}
	
	/** * The register event listener on method is used to install the event listener, which takes the target event name and the callback function as arguments *@param {String} EventName Event type *@param {Function} Cb callback function */
    on(eventName, cb) {
    	// Check whether the target event name has a listener queue
    	if (!this.listeners[eventName]) {
    		// If not, initialize a listener queue first
    		this.listeners[eventName] = [];
    	}
    	
    	// Push the callback into the listener queue of the target event
    	this.listeners[eventName].push(cb);
    }
    
    /** * The emit method is used to fire the target event, which takes the event name and listener function input arguments *@param {String} EventName Event type *@param  {... any} Args argument list, which assigns the arguments passed by emit to the callback function */
    emit(eventName, ... args) {
    	// Check whether the target event has a listener queue
    	if (this.listeners[eventName]) {
    		// If so, call each of the queue's callback functions one by one
    		this.listeners[eventName].forEach((cb) = >{ cb(... args); }}})/** * off removes a listener for an event, and removes the specified callback function * from the event callback queue@param {String} EventName Event type *@param {Function} Cb callback function */
    off(eventName, cb) {
    	if (this.listeners[eventName]) {
    		const callbacks = this.listeners[eventName];
            const index = callbacks.indexOf(cb);
            if(index ! = = -1) callbacks.splice(index, 1);
            if (this.listeners[eventName].length === 0) delete this.listeners[eventName]; }}/** * offAll Removes all listeners of an event *@param {String} EventName Event type */
    offAll(eventName) {
    	if (this.listeners[eventName]) {
    		delete this.listeners[eventName]; }}/** * The once method registers a single listener * for the event@param {String} EventName Event type *@param {Function} Cb callback function */
    once(eventName, cb) {
    	// Wrap the callback function so that it is automatically removed after execution
    	const wrapper = (. args) = > {
    		// Use the arrow function to point this to an EventEmitter instance
    		cb.apply(this, args);
    		// Remove the callback function
    		this.off(eventName, cb);
    	}
    	this.on(eventName, wrapper); }}/ / test
// Create an event manager instance
const ee = new EventEmitter();
// Register a chifan event listener
ee.on('chifan'.function() { console.log('Dinner, let's go! ')});// Publish the event chifan
ee.emit('chifan');
// Emit can also pass arguments
ee.on('chifan'.function(address, food) { console.log('Dinner, let's go${address}eat${food}! `)}); ee.emit('chifan'.'Three Canteen'.'Iron plate rice'); // Two messages are printed because two listeners for chifan events were registered

// Test removing event listening
const toBeRemovedListener = function() { console.log('I'm a listener that can be removed.')}; ee.on('testoff', toBeRemovedListener);
ee.emit('testoff');
ee.off('testoff', toBeRemovedListener);
ee.emit('testoff'); // Now the event listener has been removed and no more console.log will be printed

// Test to remove all event listening for chifan
ee.offAll('chifan');
console.log(ee); // Notice that the ee.listeners have become empty objects, and the listeners will not respond to sending chifan events
Copy the code

53. Handwritten event proxy

Event proxy, perhaps the most common use of the proxy model, is also a solid, frequent interview question. It has a scene of multiple child elements under a parent element, like this:

Event proxy, perhaps the most common use of the proxy model, is also a solid, frequent interview question. It has a scene of multiple child elements under a parent element, like this:

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>The event agent</title>
</head>
<body>
  <div id="father">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
    <a href="#">Link to no. 3,</a>
    <a href="#">Link 4,</a>
    <a href="#">Links to 5,</a>
    <a href="#">Links to 6</a>
  </div>
</body>
</html>
Copy the code

What we need now is that when clicking each A label with the mouse, a prompt like “I am XXX” will pop up. Click on the first A TAB, for example, and a prompt like “I’m link number 1” pops up. This means that we need to install at least six listeners for six different elements (we usually use loops, as shown below), and if we increase the number of A tags, the performance cost will be even higher.

// If the proxy mode is not used, we will install the listener function in a loop
// All a tag nodes
const aNodes = document.getElementById('father').getElementsByTagName('a');

// Loop to install listener functions
for (let i = 0; i < aNodes.length; i++) {
	aNodes[i].addEventListener('click'.funtion(e) {
		// Block event default behavior
		e.preventDefault();
		alert(I was `${aNodes[i].innerText}`); })}Copy the code

Given the bubbling nature of the event itself, when we click on the A element, the click event “bubbles” onto the parent div and is listened on. This way, the listener for the click event only needs to be bound once on the div element instead of N times on the child element — this practice is called event proxy, which can greatly improve the performance of our code.

Implementation of the event broker

Using proxy mode to implement event listening for multiple child elements, the code is much simpler:

// Get the parent element
const father = document.getElementId('father');

// Install a listener for the parent element
father.addEventListener('click'.function(e) {
	// Identify if it is a target child
	if (e.target.tagName === 'A') {
		// This is the body of the listener function
		// Block event default behavior
		e.preventDefault();
		alert(I was `${e.target.innerText}`); }})Copy the code

In this way, our click does not directly touch the target child element, but rather the parent element processes and distributes the event, acting indirectly on the child element, thus classifying the action as a proxy pattern.

55. Write a Promise

Why Promise? In traditional asynchronous programming, if asynchronous dependencies between, we need to meet this dependence, through layers of nested callbacks if nested layers is overmuch, have become very poor readability and maintainability, produce the so-called “callback hell”, and Promise will change for chain embedded callback invocation, readability and maintainability.

Romise is essentially a state machine that can be in the following three states: This is a big pity. The change of the state is one-way and can only be realized from Pending -> depressing or Pending -> Rejected. The state change is irreversible. A Promise is executed as soon as it is created, and its state is Pending.

The then method takes two optional arguments, resolve and Reject, corresponding to the callback that is triggered when the state changes. The resolve function changes the state of the Promise object from ‘Pending’ to ‘Resolved’ and passes the result as an argument when the asynchronous operation succeeds. The Reject function changes the state of the Promise object from ‘unfinished’ to failed (from Pending to Rejected), calls it when an asynchronous operation fails, and passes the error of the asynchronous operation as an argument.

Basic version

Complete basic functions

Then method

Add the THEN method to the base version

The finally method

Regardless of whether the current Promise succeeds or fails, a call to finally executes the function passed in finally and passes the value down unchanged.

Promise. Resolve and Promise. Reject

The promise.resolve (value) method returns a Promise object resolved with the given value. If the value is promise, return the promise; If the value is thenable (that is, with the “then” method), the returned promise “follows “the thenable object and adopts its final state; Otherwise the returned promise will be fulfilled with this value. This function flattens out the multiple layers of nesting of promise-like objects.

The promise.reject () method returns a Promise object with a reason for the rejection.

Promise.all

Promise.all() takes an array of Promise objects as arguments and returns a new Promise object.

When all the objects in the array are resolved, the state of the new object will be fulfilled. The value of the resolve of all objects will be successively added to form a new array, and the new array will be used as the value of the new object resolve. When there is a Reject object in the array, the state of the new object changes to Reject, and the reason of the current reject object is used as the reason of the new object.

Promise.race

Promise.race() also takes an array of Promise objects as arguments and returns a new Promise object.

Unlike promise.all (), it changes its state when there is an object in the array (resolve or reject is the first to change state) and performs a callback in response.

Promise.allSettled

The accepted result is one by one corresponding to the promise instance when the parameter is fulfilled, and each item of the result is an object, which tells you the result and value. There is an attribute called “Status” in the object, which is used to clearly know the state of the corresponding Promise instance (depressing or Rejected). The object has a value attribute and a Reason attribute, which correspond to the return values of the two states.

The important point is that it returns all promise results regardless of the state of the promise itself it accepts as an input, but promise.all does not. If you need to know the results of all asynchronous operations on inputs, or if they are all finished, Promise.allsettled () should be used instead.

Promise.any

Promise.any() is a new feature in ES2021 that accepts a Promise iterable (such as an array),

If one of the promises succeeds, return the promise that has already succeeded. If none of the iterables succeeds (i.e. all promises fail/reject), Returns a failed promise and an instance of type AggregateError, which is a subclass of Error used to group single errors together

class MyPromise {
	// The constructor receives a callback
	constructor(fn) {
		// Promise has three states
		this.status = 'pending';
		// This is a big pity
    	this.value = undefined;
    	// Define the state of the rejected state
    	this.reason = undefined;
    	
    	// Success queue, which stores the successful callback. Resolve is triggered
    	this.onResolvedCallbacks = [];
    	// Failure queue, which stores failed callback, reject
    	this.onRejectedCallbacks = [];
    	
    	// Since resolve/reject is called inside fn, we need to use the arrow function to fix this pointing to the Promise instance
    	let resolve = (value) = > {
    		// The state can only be changed from pending to pity or rejected.
    		if (this.status === 'pending') {
    			this.value = value;
    			// Change the state
    			this.status = 'fulfilled';
    			this.onResolvedCallbacks.forEach(callback= >callback(value)); }}// Implement the same as resolve
    	let reject = (reason) = > {
    		// The state can only be changed from pending to pity or rejected.
    		if (this.status === 'pending') {
    			this.reason = reason;
    			// Change the state
    			this.status = 'rejected';
    			this.onRejectedCallbacks.forEach(callback= >callback(reason)); }}// An exception may occur during execution
        try {
            // New Promise() immediately executes fn, passing resolve and reject
            fn(resolve, reject);
        } catch(e) { reject(e); }}The then method receives a successful callback and a failed callback
	then(onFullfilled, onRejected) {
		// Prevent value penetration
		if (typeofonFullfilled ! = ='function') onFullfilled = v= > v;
		if (typeofonRejected ! = ='function') onRejected = v= > v;
		Return a new promise
		return new MyPromise((resolve, reject) = > {
			// Rewrap onFullfilled and push it into the resolve execution queue to get the return value of the callback for sorting. Use the arrow function to make this point to the instance
			const fulfilledFn = value= > {
                try {
                    // Execute the success callback for the first (current)Promise and get the return value
                    let x = onFullfilled(value);
                    // The class discusses the return value, if it is a Promise, then wait for the Promise state to change, otherwise resolve
                    x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
                } catch(error) { reject(error); }}// Repackage onRejected with the arrow function so that this points to the instance
           	const rejectedFn = value= > {
                try {
                    // Executes the failure callback for the first (current)Promise and gets the return value
                    let x = onRejected(value);
                    // The class discusses the return value, if it is a Promise, then wait for the Promise state to change, otherwise resolve
                    x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
                } catch(error) { reject(error); }}// According to the state
           	switch(this.status) {
           		// When the status is pending, push the then callback into the resolve/reject queue for execution
           		case 'pending':
           			this.onResolvedCallbacks.push(fulfilledFn);
           			this.onRejectedCallbacks.push(rejectedFn);
           			break;
               	// Execute the then callback when the state has changed to resolve/reject
               	case 'fulfilled':
           			fulfilledFn(this.value);
           			break;
               	case 'rejected':
           			rejectedFn(this.reason);
           			break; }})}// The catch method receives a failed callback
	catch(onRejected) {
        return this.then(undefined, onRejected);
    }
    
    / / finally
    finally(cb) {
    	return this.then(
    		// Perform a callback and return value is passed to the following THEN
    		value= > MyPromise.resolve(cb()).then(() = > value),
    		/ / reject again
    		reason= > MyPromise.resolve(cb()).then(() = > {throw reason})
      	)
    }
    
    // Static resolve method
    static resolve(value) {
    	// According to the specification, if the argument is a Promise instance, return the instance directly
    	if (value instanceof MyPromise) return value;
    	return new MyPromise(resolve= > resolve(value));
    }
    
    Static reject method
    static reject(reason) {
    	return new MyPromise((resolve, reject) = > reject(reason));
    }
    
    // Static all method
    static all(promises) {
    	// count the number of times a promise has been executed
    	let index = 0;
    	// Store the result of a promise execution
    	let res = [];
    	Return a MyPromise object
    	return new MyPromise(function (resolve, reject) {
    		for (let i = 0; i < promises.length; i++) {
    			MyPromise.resolve(promises[i]).then(
    				function(value) {
    					index++;
    					// The resulting array is printed in the same order as the original array
    					res[i] = value;
    					// All MyPromise resolve
    					if (index === promises.length) resolve(res);
    				},function(reason) {
    					// The first reject MyPromisereject(reason); })})}// Static race methods succeed as long as one promise succeeds. If the first one fails, it fails
    static race(promises) {
    	Return a MyPromise object
    	return MyPromise(function(resolve, reject) {
    		// Execute the Promise, and if one of the Promise states changes, change the new MyPromise state
    		for (let promise of promises) {
    			Resolve (Promise) is used to handle cases where the incoming value is not a Promise
    			MyPromise.resolve(promise).then(function(value) {
    				// Resolve = new MyPromise
    				resolve(value);
    			}, function(error) { reject(error); })}})}// Static allSettled method
    static allSettled(promises) {
    	// count the number of times a promise has been executed
    	let index = 0;
    	// Store the result of a promise execution
    	let res = [];
    	Return a MyPromise object
    	return new MyPromise(function (resolve, reject) {
    		for (let i = 0; i < promises.length; i++) {
    			MyPromise.resolve(promises[i]).then(
    				function(value) {
    					index++;
    					// The resulting array is printed in the same order as the original array
    					res[i] = { status: 'fulfilled'.value: value };
    					// All MyPromise resolve
    					if (index === promises.length) resolve(res);
    				},function(reason) {
    					// Similar to the first one, but pay attention to the state
    					index++;
    					// The resulting array is printed in the same order as the original array
    					res[i] = { status: 'rejected'.reason: reason };
    					// All MyPromise resolve
    					if(index === promises.length) resolve(res); })})}// Static any method
	static any(promises) {
		// count the number of times a promise has been executed
    	let index = 0;
    	// Store the result of a promise execution
    	let reasons = [];
    	Return a MyPromise object
    	return new MyPromise(function (resolve, reject) {
    		for (let i = 0; i < promises.length; i++) {
    			MyPromise.resolve(promises[i]).then(
    				function(value) {
    					// The first resolve MyPromise
    					resolve(value);
    				}, function(reason) {
    					// The resulting array is printed in the same order as the original array
    					index++;
						reasons.push(reason);
						// All MyPromise resolve
						if (index === promises.length) reject(new AggregateError('All promises were rejected', reasons)); })})}}Copy the code

At last all Reject failed.

56. Handwritten Ajax encapsulation

Native Ajax encapsulation

steps

  • createXMLHttpRequestThe instance
  • Making an HTTP request
  • The server returns a string in XML format
  • JS parses the XML and updates local pages
  • But over the course of history, XML has been phased out in favor of JSON.

Once you know the properties and methods, follow the AJAX steps and write the simplest GET request.

function ajax(url, method = 'get', param = {}) {
	// Create an XMLHttpRequest object
	let xhr = new XMLHttpRequest();
	// Three parameters that specify the type of request, the URL, and whether the request is processed asynchronously.
	xhr.open(method, url, true);
	// Set the request header, send the message to the server content encoding type, can not write
	xhr.setRequestHeader('Content-type'.'application/x-www-form-urlencoded');
	// This function is called whenever the readyState property changes.
	xhr.onreadystatechange = function() {
		// The current state of the XMLHttpRequest agent.
		if (xhr.readyState === 4) {
			// 200-300 The request succeeded
			if ((xhr.status >= 200 && xhr.status < 300) || xhr === 304) {
				The // json.parse () method parses JSON strings to construct JavaScript values or objects described by the strings
				success(JSON.parse(xhr.responseText));
			} else{ fail && fail(); }}}// Send a request, which is used to actually make an HTTP request. GET request without parameters
	xhr.send(null);
}
Copy the code

Promise to encapsulate the ajax

  • Return a new Promise instance
  • Create an HMLHttpRequest asynchronous object
  • Call the open method, open the URL, establish a link with the server (some processing before sending)
  • Listen for Ajax state information
  • ifxhr.readyState == 4(Indicates that the server response is complete and you are ready to retrieve the response using the server)
    • xhr.status == 200, returns the resolve state
    • xhr.status == 404Reject is returned
  • xhr.readyState ! = = 4, sends the request body information to the server based on SEND
function ajax(url, method = 'get', param = {}) {
    return new Promise((resolve, reject) = > {
        // Create an XMLHttpRequest object
        let xhr = new XHLHttpRequest();
        // Three parameters that specify the type of request, the URL, and whether the request is processed asynchronously.
        xhr.open(method, url, true);
        // Set the request header, send the message to the server content encoding type, can not write
        xhr.setRequestHeader('Content-type'.'application/x-www-form-urlencoded');
        // This function is called whenever the readyState property changes.
        xhr.onreadystatechange() = function() {
            // The current state of the XMLHttpRequest agent.
            if (xhr.readyState === 4) {
                // 200-300 The request succeeded
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
                    resolve(JSON.parse(xhr.responseText));
                } else {
                    reject('Request error'); }}}})}Copy the code

57. Write sleep by hand

The use of Promise

function sleep(time) {
	return new Promise(function(resolve) {
		setTimeout(resolve, time); })}/ / use
sleep(1000).then(() = > {
	console.log(1);
})
Copy the code

Use Generator Generator

function* sleepGenerator(time){
	yield new Promise(function(resolve, reject) {
		setTimeout(resolve, time); })}/ / use
sleepGenerator(1000).next().value.then(() = > {
	console.log(1);
})
Copy the code

Use the async/await

function sleep(time) {
	return new Promise(function(resolve) {
		setTimeout(resolve, time); })}async function output(time) {
	let out = await sleep(time);
	console.log(1);
	return out;
}

output(1000);
Copy the code

ES5

function sleep(callback, time) {
	if (typeof(callback) === 'function') {
		setTimeout(callback, time); }}function output() {
	console.log(1);
}
sleep(output, 1000);
Copy the code

SetTimeout is wrapped as a function of sleep

Handwritten f function

setTimeout(() = > console.log('hi'), 500);
const sleep = f(setTimeout);
sleep(500).then(() = > console.log('hi'));
Copy the code

That is, implement higher-order functions

function f(fn) {
    return function(. args) {
        return new Promise(function(resolve) { args.unshift(resolve); fn(... args); }}})const sleep = f(setTimeout);
sleep(500).then(() = > console.log('hi'));
Copy the code