This collection aims to consolidate the core basic skills of front-end engineers, starting from the high frequency handwriting problems of various sizes of factories, summarizing the common simulation implementation, stick to learning, there will be Easter.

More detailed and more source code on github project, a problem containing a variety of solutions, interested in learning can download, long-term update and maintenance.

PS: If the article is wrong, please do not hesitate to correct. Your “praise” is the motivation of the author’s creation.

Part II: “Middle and advanced front-end interview” handwritten code collection (2)

Let’s start with the mind map:

1.instanceof

Instanceof determines whether the left prototype exists in the right prototype chain.

If the final prototype is null, it proves that it does not exist in the prototype chain, otherwise it does exist.

function myInstanceof(left, right) {
    if (typeofleft ! = ='object' || left === null) return false // The base type is always false
    let proto = Object.getPrototypeOf(left) // Get the prototype of the object
    while(true) {
    	if (proto === null) return false
        if (proto === right.prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}
Copy the code

2.Object.create

const myCreate = function(obj{ 
    function F({}
    F.prototype = obj  
    return new F() // Create a pure object that inherits the obj stereotype properties
}
Copy the code

3.new

When new is called it does three things:

  1. Give instance objects access to private properties
  2. Give instance objects access to properties on the constructor. Prototype chain
  3. Consider the case where the constructor has a return value
function myNew(ctor, ... args) {
    let fn = Array.prototype.shift.call(arguments) // Take the first argument ctor
    if (typeoffn ! = ='function') throw `${fn} is not a constructor`
    let obj = Object.create(fn.prototype)
    let res = fn.apply(obj, args) // If the constructor returns a value, execute it directly
    let isObject = typeof res === 'object'&& res ! = =null
    let isFunction = typeof res === 'function'
    return isObject || isFunction ? res : obj
}
Copy the code

4.call & apply

Implementation idea: use who call a function, function this points to who this feature to achieve.

Function.prototype.myCall = function() { / / myApply gm
    if (typeof this! = ='function') throw 'caller must be a function'
    let self = arguments[0] | |window
    self._fn = this
    let args = [...arguments].flat().slice(1) // Expand to get the parameter list
    letres = self._fn(... args)// The function's this refers to whoever calls the function
    Reflect.deleteProperty(self, '_fn') // Delete the _fn attribute
    return res
}
Copy the code

5.bind

Bind is used to change the this pointer and return a function.

Function.prototype.myBind = function() {
    if (typeof this! = ='function') throw 'caller must be a function'
    let self = this
    let context = arguments[0]
    let args = Array.prototype.slice.call(arguments.1)
    let fn = function() {
    	let fnArgs = Array.prototype.slice.call(arguments)
        // Arguments to bind + arguments to delay
    	self.apply(this instanceof self ? this : context, args.concat(fnArgs)
      )
    }
    fn.prototype = Object.create(self.prototype) // Maintain the prototype
    return fn
}
Copy the code

6. Mr Currie

Corey:

  • Definition: Returns a new function after binding a function to a subset of its arguments.
  • Benefits: Reduces code redundancy, increases readability, and is a concise way to implement function delegation.

Take a simple 🌰 :

function multiFn(x, y, z) {
    return x * y * z
}
function curry() {... }// Suppose we have a curry function that can be currified
let multi = curry(multiFn)
multi(2.3.4)
multi(2) (3) (4)
multi(2.3) (4)
multi(2) (3.4)   // All of the above results are equal to 3.
let seniorMulti = multi(2) // seniorMulti can be used multiple times
seniorMulti(3) (4) // We can do this if we feel that passing argument 2 repeatedly is always redundant.
Copy the code

Code implementation:

function curry(fn, args=[]) {
    return function() {
        let newArgs = args.concat(Array.prototype.slice.call(arguments))
        if (newArgs.length < fn.length) { // If: the number of arguments < the number of parameters
            return curry.call(this, fn, newArgs)
        } else {
            return fn.apply(this, newArgs)
        }
    }
}

// ES6 high appearance level writing
const curry = fn= >
    judge = (. args) = >args.length === fn.length ? fn(... args) :(arg2) = >judge(... args, arg2)Copy the code

Parasitic combinatorial inheritance

function Parent() {
	this.favorite = 'money'
}
function Child() {
	Parent.call(this) // Inherits the attributes of the parent class
	this.age = 18
}
Child.prototype = Object.create(Parent.prototype) // Inherits the stereotype attributes of the parent class
Object.setPrototypeOf(Child, Parent) // Inherits a static method from the parent class
Child.prototype.constructor = Child // constructor redirect to Child
Copy the code

8. How the Class private property in TypeScript works

Private attributes generally satisfy:

  1. Can be accessed by different methods inside the class, but not outside the class
  2. Subclasses cannot inherit private attributes from their parent class

ES6 already provides a # for us to use, but requires a Babel translation, so let’s implement one ourselves.

const MyClass = (function() { // Utilize closures and WeakMap
    const _x = new WeakMap(a)class InnerClass {
    	constructor(x) {
        	_x.set(this, x)
        }
        getX() {
        	return x.get(this)}}return InnerClass
})()

let myClass = new MyClass(5)
console.log(myClass.getX()) / / 5
console.log(myClass.x) // undefined
Copy the code

9. Array sort

The bubbling

function bubbleSort(arr) {
    let len = arr.length
    for (let i=len; i>=2; i--) { // The first row is automatically the smallest
    	for (let j=0; j<i-1; j++) { // Gradually narrow down the scope
            if (arr[j] > arr[j+1])
            	[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
        }
    }
    return arr
}
Copy the code

Selection sort

Implementation idea: iterate over the element after itself, the smallest element with their position.

function selectSort(arr) {
    let len = arr.length
    for (let i=0; i<len-1; i++) {
    	for (let j=i; j<len; j++) {
        	if (arr[j] < arr[i]) 
            	[arr[i], arr[j]] = [arr[j], arr[i]]
        }
    }
    return arr
}
Copy the code

Insertion sort

Insert elements into a sorted array.

function insertSort(arr) {
    for (let i=1; i<arr.length; i++) { // arr[0] defaults to sorted arrays
    	for (let j=i; j>0; j--) {
            if (arr[j] < arr[j-1]) {
            	[arr[j],arr[j-1]] = [arr[j-1],arr[j]]
            } else { break}}}return arr
}
Copy the code

Quick sort

Implementation idea: select the base value mid, loop the original array, less than the base value put the left array, greater than the right array, and then concat combination, finally rely on recursion to complete sorting.

function quickSort(arr) {
    if (arr.length <= 1) return arr
    let left = [], right = [], mid = arr.splice(0.1)
    for (let i=0; i<arr.length; i++) {
    	if (arr[i] < mid) {
      	    left.push(arr[i])
      	} else {
      	    right.push(arr[i])
    	}
    }
    return quickSort(left).concat(mid, quickSort(right)) // Don't forget mid
}
Copy the code

sort

Here is just the idea of sort implementation, the SORT method in V8 engine is a relatively advanced algorithm compared to other methods.

Here’s a chart:

Sorting algorithm Average time complexity Spatial complexity Best case scenario! The stability of
Bubble sort O(n^2) O(1) O(n) stable
Selection sort O(n^2) O(1) O(n^2) unstable
Insertion sort O(n^2) O(1) O(n) stable
Quick sort O(nlogn) O(logn) O(nlogn) unstable

Stability definition: if the relative positions of two equal numbers before and after sorting remain unchanged, the algorithm is stable. The advantage is that the results of the first key sort can be used for the second key sort.

Let the number of elements to be sorted be n:

  • whenn <= 10, insert sort is used(When n is small enough, insert sort is faster than quicksort, see table).
  • whenn > 10, three-way quicksort is used.

Where 10 < n <= 1000, the median is used as the sentry element; N > 1000, pick one element every 200 to 215, put it in a new array, and sort it to find the middle number as the median.

10. Array deduplication

Double loop

function unique(arr) {
    for (let i=0; i<arr.length; i++) { // Note that the length of arr varies
    	for (let j=i+1; j<arr.length; j++) {
            if (arr[i] === arr[j]) {
            	arr.splice(j, 1)
                j--
            }
        }
    }
    return arr
}
Copy the code

indexOf

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

// Alternatively, use filter + indexOf
function unique(arr) {
    let res = arr.filter(function(item, index, array){
        return arr.indexOf(item) === index;
    })
    return res;
}
Copy the code

Sort and de-weight

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

// Alternatively, use sort + filter
function unique(arr) {
    return arr.concat().sort().filter(function(item, index, array){
        return! index || item ! == arr[index -1]})}Copy the code

ES6 Set or Map

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

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

11.map

According to the MAP specification to simulate the implementation:

Array.prototype.map = function(callbackFn, thisArg) {
    if (this= = =null || this= = =undefined) 
    	throw new TypeError(`Cannot read property 'map' of The ${this}`)
    // Handle callback type exceptions
    if (Object.prototype.toString.call(callbackFn) ! = ='[object Function]')
    	throw new TypeError(`${callbackFn} is not a function`)
        
    let O = Object(this), // specify that this be converted to an object first
    	T = thisArg,
        len = O.length >>> 0.// Ensure len is a number and an integer
        A = new Array(len)
        
    for (let k=0; k<len; k++) {
        if (k in O) { // The prototype chain looks for attributes
            let mappedValue = callbackFn.call(T, O[k], k, O)
            A[k] = mappedValue
        }
    }
    return A
}
Copy the code

12.reduce

Simulation implementation according to reduce specification:

Array.prototype.reduce = function(callbackFn, initialValue) {
    if (this= = =null || this= = =undefined) 
    	throw new TypeError(`Cannot read property 'reduce' of The ${this}`)
    // Handle callback type exceptions
    if (Object.prototype.toString.call(callbackFn) ! = ='[object Function]')
    	throw new TypeError(`${callbackFn} is not a function`)
        
    let O = Object(this), // specify that this be converted to an object first
    	k = 0,
        len = O.length >>> 0.// Ensure len is a number and an integer
        accumulator = initialValue
        
    if (accumulator === undefined) {
    	for (; k<len; k++) {
            if (k in O) {
            	accumulator = O[k]
                k++
                break}}}if (k === len && accumulator === undefined) // Indicates that the array is empty
    	throw new Error('Each element of the array is empty')
        
    for(; k<len; k++) {
    	if (k in O) {
      	    accumulator = callbackfn.call(undefined, accumulator, O[k], k, O); }}return accumulator
}
Copy the code

13.filter

According to the Filter specification to simulate the implementation:

Array.prototype.filter = function(callbackFn, thisArg) {
    if (this= = =null || this= = =undefined) 
    	throw new TypeError(`Cannot read property 'filter' of The ${this}`)
    // Handle callback type exceptions
    if (Object.prototype.toString.call(callbackFn) ! = ='[object Function]')
    	throw new TypeError(`${callbackFn} is not a function`)
        
    let O = Object(this), // specify that this be converted to an object first
        resLen = 0,
        len = O.length >>> 0.// Ensure len is a number and an integer
        res = []
        
    for (let i=0; i<len; i++) {
        if (i in O) { // The prototype chain looks for attributes
            let element = O[i];
            if (callbackfn.call(thisArg, O[i], i, O)) res[resLen++] = element
        }
    }
    return res
}
Copy the code

14. Random strings

function generateRandomString(len) {
    let randomStr = ' '
    for (; randomStr.length<len; randomStr+=Math.random().toString(36).substr(2)) {}
    return randomStr.substr(0, len)
}
Copy the code

15. Fibonacci numbers

Are you hungry after all this studying? Congratulations on finding…

Take a look at the Fibonacci sequence created by the creator of up, and, boy, tears are trickled out of his mouth…

// The time complexity is O(2^n)
function fibSequence(n) {
    if (n === 1 || n === 2) return n - 1;
    return fib(n - 1) + fib(n - 2)}// Or use iteration, O(n), recommended!
function fibSequence(n) {
  let a = 0, b = 1, c = a + b
  for (let i=3; i<n; i++) {
    a = b
    b = c
    c = a + b
  }
  return c
}
Copy the code

16. Shallow copy

Shallow copy:

  • Only one layer of objects can be copied. If there are nested objects, shallow copy cannot be used.
  • Potential problem: If the copied property is a reference type, the copied memory address will affect each other.
const shallowClone = (target) = > {
  if (typeof target === 'object'&& target ! = =null) {
    const cloneTarget = Array.isArray(target) ? [] : {}for (let prop in target) {
      if (target.hasOwnProperty(prop)) { // Whether it is its own (non-inherited) attribute
          cloneTarget[prop] = target[prop] // Consider only one layer of objects}}return cloneTarget
  } else {
    return target // Base type returns directly}}// Or you can
console.log(Object.assign(array, ... sources))console.log(array.concat())
console.log(array.slice())
console.log([...array])
Copy the code

17. A deep copy

A deep copy creates a copy of an object with different reference addresses. Deep copy is a good choice when you want to use an object but don’t want to modify the original object.

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

The beggar version above already covers most application scenarios, but forget it when interviewing! It has several problems:

  1. Unable to resolveA circular reference.
  2. Unable to copy special objects such asFunction, RegExp, Date, Set, MapAnd so on.

If we encounter complex objects in our work, we can use a library of tools, such as Lodash’s cloneDeep method. Don’t abuse it!

Start with a simple comb:

Now, let’s implement a deep copy that covers most scenarios (you can skip this and see the next version for easier understanding) :

// Map is strongly referenced, and attributes need to be cleared manually to free memory.
// WeakMap weak reference, may be garbage collection at any time, so that memory timely release, is the only choice to solve circular reference.
function cloneDeep(obj, map = new WeakMap(a)) {
  if (obj === null || obj === undefined) return obj // Do not copy
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)
  // Base types do not require deep copies
  if (typeofobj ! = ='object' && typeofobj ! = ='function') return obj
  // Handle normal functions and arrow functions
  if (typeof obj === 'function') return handleFunc(obj)
  // Make a deep copy of an object
  if (map.get(obj)) return map.get(obj)
  let cloneObj = new obj.constructor()
  // Find the constructor from the parent class stereotype, which points to the current class itself.
  map.set(obj, cloneObj)
  
  if (getType(obj) === '[object Map]') {
    obj.forEach((item, key) = >{ cloneObj.set(cloneDeep(key, map), cloneDeep(item, map)); })}if (getType(obj) === '[object Set]') {
    obj.forEach(item= >{ cloneObj.add(cloneDeep(item, map)); })}for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], map)
    }
  }
  return cloneObj
}

// Get a more detailed data type
function getType(obj) {
    return Object.prototype.toString.call(obj)
}
// Handle normal functions and arrow functions
function handleFunc(func) {
  if(! func.prototype)return func // The arrow function returns itself directly
  const bodyReg = / (? <={)(.|\n)+(? =})/m
  const paramReg = / (? < = \ () + (? =\)\s+{)/
  const funcString = func.toString()
  // Match function parameters and function body respectively
  const param = paramReg.exec(funcString)
  const body = bodyReg.exec(funcString)
  if(! body)return null
  if (param) {
    const paramArr = param[0].split(', ')
    return new Function(... paramArr, body[0])}else {
    return new Function(body[0])}}Copy the code

The following version I changed:

function cloneDeep(obj, map = new WeakMap(a)) {
  if(! (objinstanceof Object)) return obj; // Basic data
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
  
  if (map.get(obj)) return map.get(obj); // Resolve circular references
  
  if (obj instanceof Function) { // Solve the function
    return function () {
      return obj.apply(this, [...arguments]);
    };
  }
  
  const res = new obj.constructor(); // The following is the array/ordinary object /Set/Map processing
  
  obj instanceof Object && map.set(obj, res);
  if (obj instanceof Map) {
    obj.forEach((item, index) = > {
      res.set(cloneDeep(index, map), cloneDeep(item, map));
    });
  }
  if (obj instanceof Set) {
    obj.forEach((item) = > {
      res.add(cloneDeep(item, map));
    });
  }
  Object.keys(obj).forEach((key) = > {
    if (obj[key] instanceof Object) {
      res[key] = cloneDeep(obj[key], map);
    } else{ res[key] = obj[key]; }});return res;
}

const map = new Map(a); map.set({a: 1 }, "1");
const source = {
  name: "Jack".meta: {
    age: 12.birth: new Date("1997-10-10"),
    ary: [1.2, { a: 1}].say() {
      console.log("Hello");
    },
    map
  },
};
source.source = source;
const newObj = cloneDeep(source);
console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
console.log(newObj.meta.birth === source.meta.birth); // false
console.log(newObj);
Copy the code

18. Parse the URL

What does the full URL look like first?

🌰 : https://keith:[email protected]:80/file? Test =3& Miao =4#heading-0

Simple implementation with re:

function parseUrl(url) {
    / / scheme: / / user: passwd @ part
    let schemeStr = '(? : [[# ^ /?] +))? / / (? (*) [^ :] (? : :? (. *)) @)? '.// host:port path? Part of the query
        urlStr = '(? : [[# ^ /? :] *) :? ([0-9] +)? ? ([^? #] *) (\ \? (? : [^ #] *))? './ / # fragments
    	fragmentStr = '(# (? : *)) '
        
    let pattern = RegExp(` ^${schemeStr}${urlStr}${fragmentStr}? `)
    let matched = url.match(pattern) || []
    return {
    	protocol: matched[1]./ / agreement
    	username: matched[2]./ / user name
    	password: matched[3]./ / password
    	hostname: matched[4]./ / host
    	port: matched[5]./ / port
    	pathname: matched[6]./ / path
    	search: matched[7].// queryString queryString
    	hash: matched[8]./ / the anchor}}// Or you can
function parseUrl(url) {
    const urlObj = new URL(url)
    return {
    	protocol: urlObj.protocol,
        username: urlObj.username,
        password: urlObj.password,
        hostname: urlObj.hostname,
        port: urlObj.port,
        pathname: urlObj.pathname,
        search: urlObj.search,
        hash: urlObj.hash
    }
}
Copy the code

Parse the queryString queryString separately:

function parseQueryString(query) {
    if(! query)return {}
    query = query.replace(/ ^ \? /.' ')
    const queryArr = query.split('&')
    const result = {}
    queryArr.forEach(query= > {
    	let [key, value] = query.split('=')
        try {
            key = decodeURLComponent(key || ' ').replace(/\+/g.' ')
            value = decodeURLComponent(value || ' ').replace(/\+/g.' ')}catch(e) {
            return console.log(e) // Invalid characters are not processed
        }
        const type = getQueryType(key)
        switch(type) {
            case 'ARRAY':
            	key = key.replace($/ / \ [\].' ') // Parse an array of the form 'list[]'
                if(! result[key]) { result[key] = [value] }else {
                    result[key].push(value)
                }
                break;
            case 'JSON':
            	key = key.replace(/ \ {\} $/.' ') // Objects of the form obj{} are resolved as objects
                value = JSON.parse(value)
                result.json = value
                break;
            default:
                result[key] = value
        }
    })
    return result
}
function getQueryType (key) {
    if (key.endsWith('[]')) return 'ARRAY'
    if (key.endsWith('{}')) return 'JSON'
    return 'DEFAULT'
}

// Or you can, if you are prepared to be hit by an interviewer...
/ / simple version
function getUrlQuery(search) {
    let searchObj = {};
    for (let [key, value] of new URLSearchParams(search)) {
        searchObj[key] = value
    }
    return searchObj
}
Copy the code

Of course, this is not rigorous enough to take into account the following issues:

  • How to handle the same field
  • There is no replacement+% 20
  • onlykey/ onlyvalue
  • .

In our work, we recommend two open source libraries: JS-URL and Query-String. The boundaries are taken into account, and of course they are more complex than our implementation.

And don’t forget nodeJS’s two module methods: url.parse and queryString.parse are already pretty handy.

19.JSONP

JSONP: common cross-domain means, using the

const jsonp = ({url, params, callbackName}) = > {
    const generateURL = () = > { // Generate an address based on the URL format
    	let dataStr = ' '
    	for (let key in params) {
      	    dataStr += `${key}=${params[key]}& `
    	}
    	dataStr += `callback=${callbackName}`
    	return `${url}?${dataStr}`
    }
    return new Promise((resolve, reject) = > {
    	callbackName = callbackName || Math.random().toString()
    	let scriptEle = document.createElement('script')
    	scriptEle.src = generateURL()
    	document.body.appendChild(scriptEle)
        // The server returns the string '${callbackName}(${server data})', which the browser can parse.
    	window[callbackName] = (data) = > {
      	    resolve(data)
      	    document.body.removeChild(scriptEle) // Don't forget to clear the DOM}})}Copy the code

20. Anti-shake/throttling

Image stabilization

  • QA: Why did I press this Button and send two requests in a row?
  • FE: Because you pressed it twice in a row.
  • QA: You set limits.

Ok, so the purpose of this is to avoid unnecessary actions, such as this button where QA wants to perform only one last click for a short period of time.

My usual way of memory also has a kind: the mage reads, the last one will plan the casting of the previous one…

const debounce = (fn, delay) = > {
    let timer = null
    return function(. args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() = > { // Here is the arrow function, this refers to the outer non-arrow function this
            fn.apply(this, args)
        }, delay)
    }
}
Copy the code

The throttle

Throttling is a fixed frequency trigger, and is most appropriate for scroll events.

function throttle(fn, interval) {
    let flag = true
    return function(. args) {
    	if(! flag)return;
    	flag = false
    	setTimeout(() = > {
      	    fn.apply(this, args)
      	    flag = true
    	}, interval)
    }
}

// Or you can choose what you like.
function throttle(fn, interval) {
    let last = 0 // Execute directly for the first time
    return function (. args) {
    	let now = +new Date(a)if(now - last < interval) return;
    	last = now // Update last as time runs out
    	fn.apply(this, args)
    }
}
Copy the code

Enhanced throttling

Now the button is using the anti-shock version above…

  • QA pressed the button twice in a row, only the last time the request was sent, the test passed, and the OK gesture was made.
  • FE was relieved.
  • QA came on again and began to point and point at Button without getting tired.
  • QA: Why is it never triggered?
  • FE:…

Enhanced version of unlimited anti – shake throttling, fixed frequency trigger.

const throttle = (fn, delay) = > {
    let timer = null, last = 0
    return function(. args) {
    	let now = +new Date(a)if (now - last < delay && timer) {
      	    clearTimeout(timer)
      	    timer = setTimeout(() = > {
            	fn.apply(this, args)
      	    }, delay)
        } else { // When the delay is reached, the delay is triggered.
      	    last = now
      	    fn.apply(this, args)
    	}
    }
}
Copy the code

21. Lazy loading of images

Sometimes you go through a lot of hard work with several performance optimizations, and the result is not as good and cost-effective as you can get with compressed, lazy loading images. So performance optimization, not mindless optimization.

There are three ways to realize lazy image loading:

  • ClientHeight, scrollTop, and offsetTop
  • getBoundingClientRect
  • IntersectionObserver

First, give all images a placeholder resource:

<img src="default.jpg" data-src="https://www.xxx.com/target-1.jpg" />
<img src="default.jpg" data-src="https://www.xxx.com/target-2.jpg" />.<img src="default.jpg" data-src="https://www.xxx.com/target-39.jpg" />
Copy the code

① clientHeight, scrollTop and offsetTop

First above:

You can see that the image’s offsetTop is less than the purple line (scrollHeight + clientHeight) will be displayed in the window.

let imgs = document.getElementsByTagName("img"), count = 0
// First load
lazyLoad()
// Check whether the image reaches the viewport by listening for scroll events
window.addEventListener('scroll', throttle(lazyLoad, 160))
function lazyLoad() {
    let viewHeight = document.documentElement.clientHeight // Viewport height
    // The height of the scroll bar
    let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
    for(let i=count; i<imgs.length; i++) {
    	// The element now appears in the viewport
    	if(imgs[i].offsetTop < scrollTop + viewHeight) {
      	    if(imgs[i].getAttribute("src")! = ="default.jpg") continue;
      	    imgs[i].src = imgs[i].getAttribute("data-src")
      	    count ++
    	}
    }
}
Copy the code

(2) getBoundingClientRect

The dom element’s getBoundingClientRect().top attribute directly determines whether the image is present in the current viewport.

// Just modify the lazyLoad function
function lazyLoad() {
    for(let i=count; i<imgs.length; i++) {
        if(imgs[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      	    if(imgs[i].getAttribute("src")! = ="default.jpg") continue;
      	    imgs[i].src = imgs[i].getAttribute("data-src")
            count ++
    	}
    }
}
Copy the code

(3) IntersectionObserver

The built-in API of IntersectionObserver browser can monitor scroll events of window, judge whether it is in viewport, and perform throttling. This API needs to be polyfilled.

let imgs = document.getElementsByTagName("img")
const observer = new IntersectionObserver(changes= > {
    for(let i=0, len=imgs.length; i<len; i++) {
        let img = imgs[i]
        // Use this property to determine whether it is in the viewport. Return Boolean
        if(img.isIntersecting) {
            const imgElement = img.target
            imgElement.src = imgElement.getAttribute("data-src")
            observer.unobserve(imgElement) // Remove the observation}}})Array.from(imgs).forEach(item= > observer.observe(item)) / / call
Copy the code

22. Asynchronous callback to the Promise series

Promise

Promise to achieve combing:

  • Chain calls
  • Error capture (bubbling)
const PENDING = 'PENDING';      / /
const FULFILLED = 'FULFILLED';  / / has been successful
const REJECTED = 'REJECTED';    / / has failed

class Promise {
  constructor(exector) {
    // Initialization state
    this.status = PENDING;
    // Place success and failure results on this for easy access to then and catch
    this.value = undefined;
    this.reason = undefined;
    // Success callback function queue
    this.onFulfilledCallbacks = [];
    // Failed state callback function queue
    this.onRejectedCallbacks = [];

    const resolve = value= > {
      // You can change a state only if it is in progress
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // The success state functions are executed in sequence
        this.onFulfilledCallbacks.forEach(fn= > fn(this.value)); }}const reject = reason= > {
      // You can change a state only if it is in progress
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // The failed state functions are executed in sequence
        this.onRejectedCallbacks.forEach(fn= > fn(this.reason))
      }
    }
    try {
      Execute executor immediately
      // Pass internal resolve and reject to the executor. The user can call resolve and reject
      exector(resolve, reject);
    } catch(e) {
      // The executor executes an error, throwing out the error content rejectreject(e); }}then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    onRejected = typeof onRejected === 'function'? onRejected:
      reason= > { throw new Error(reason instanceof Error ? reason.message:reason) }
    / / save this
    const self = this;
    return new Promise((resolve, reject) = > {
      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() = > {
          // try to catch an error
          try {
            // Simulate microtasks
            setTimeout(() = > {
              const result = onFulfilled(self.value);
              // There are two kinds of cases:
              // 1. The return value of the callback is Promise
              // 2. If it is not a Promise, call the new Promise's resolve function
              result instanceof Promise ? result.then(resolve, reject) :
              resolve(result)
            })
          } catch(e) { reject(e); }}); self.onRejectedCallbacks.push(() = > {
          // Do the following
          try {
            setTimeout(() = > {
              const result = onRejected(self.reason);
              // Reject: reject
              result instanceof Promise ? result.then(resolve, reject) : 
              reject(result)
            })
          } catch(e) { reject(e); }})}else if (self.status === FULFILLED) {
        try {
          setTimeout(() = > {
            const result = onFulfilled(self.value)
            result instanceof Promise ? result.then(resolve, reject) : resolve(result)
          });
        } catch(e) { reject(e); }}else if (self.status === REJECTED){
        try {
          setTimeout(() = > {
            const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : reject(result)
          })
        } catch(e) {
          reject(e)
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  static resolve(value) {
    if (value instanceof Promise) {
      // If it is a Promise instance, return it directly
      return value;
    } else {
      // If it is not a Promise instance, return a new Promise object, which is FULFILLED
      return new Promise((resolve, reject) = >resolve(value)); }}static reject(reason) {
    return new Promise((resolve, reject) = >{ reject(reason); }}})Promise.prototype.finally = function(callback) {
  this.then(value= > {
    return Promise.resolve(callback()).then(() = > {
      return value
    })
  }, error= > {
    return Promise.resolve(callback()).then(() = > {
      throw error
    })
  })
}
Copy the code

Promise.resolve

Resolve static method:

  1. Pass the parameter as a Promise and return it directly.
  2. The parameter is passed as a Thenable object, and the returned Promise follows the object, taking its final state as its own.
  3. Otherwise, a Promise object with that value as a success status is returned directly.
Promise.resolve = (param) = > {
  if(param instanceof Promise) return param / / in 1
  return new Promise((resolve, reject) = > {
    if (param && param.then && typeof param.then === 'function') { / / in 2
      // A successful param state calls resolve, which changes the state of the new Promise to successful and vice versa
      param.then(resolve, reject)
    } else { / / in accordance with 3
      resolve(param)
    }
  })
}
Copy the code

Promise.reject

// bubble capture
Promise.reject = function (reason) {
    return new Promise((resolve, reject) = > {
        reject(reason)
    })
}
Copy the code

Promise.all

Promise. All

  1. Resolve is passed as an empty iterable.
  2. If one of the arguments fails, the promise object returned by promise.all fails.
  3. In any case, promise.all returns an array as the result of the completion state of the Promise.
Promise.all = function(promises) {
  return new Promise((resolve, reject) = > {
    let result = [],
        index = 0,
        len = promises.length
    if(len === 0) {
      resolve(result)
      return;
    }
   
    for(let i=0; i<len; i++) {
      // Why not just promise[I]. Then, consider that promise[I] might not be a promise object
      Promise.resolve(promise[i]).then(data= > {
        result[i] = data
        index++
        if(index === len) resolve(result)
      }).catch(err= > {
        reject(err)
      })
    }
  })
}
Copy the code

Promise.race

Promise. Race Resolve and stop execution once a Promise is completed.

Promise.race = function(promises) {
  return new Promise((resolve, reject) = > {
    let len = promises.length
    if(len === 0) return;
    for(let i=0; i<len; i++) {
      // Promises [I] are not promises
      Promise.resolve(promises[i]).then(data= > {
        resolve(data)
        return;
      }).catch(err= > {
        reject(err)
        return; })}})}Copy the code

❤️ look here (* ̄)  ̄)

If you find this post inspiring, please like it and make it available to more people. This is really important to me.

Previous selections:

“Intermediate and Advanced Front-end Interview” Handwritten code Collection (2)

Byte interview -TLS handshake process details

reference

The secret to JavaScript handwritten code

JavaScript standard built-in objects

Original JS soul of the question series

How to write a deep copy that will impress your interviewer

When to encode space to plus (+) or %20