1. Implement the new method

During the process of new, the following four processes occur

    1. Create a new Object (or a new Object if the function is not built into JS)
    1. Point the __proto__ attribute of this empty object to the constructor’s prototype object (link to the prototype implementation inheritance)
    1. Bind this to execute the code in the constructor (add attributes to the new object)
    1. Returns a new object (this new object is automatically returned if the function returns no other object; If a function returns a non-object, the new object is automatically returned, overwriting the non-object.)
function _new() {
    // create a new object
    const obj = {}
    // 2. Bind the prototype
    let [constructor.args] = [...arguments]
    obj.__proto__ = constructor.prototype/ / 3. Bindingthis
    const result = constructor.apply(obj, args) // 4. Return a new objectreturn Object.prototype.toString.call(result) = = '[object Object]' ? result : obj;
}
/ / use
    function People(name,age) { 
       this.name = name
       this.age = age 
     }
   let peo = createNew(People,'Bob'.22) 
   console.log(peo.name) 
   console.log(peo.age)

Copy the code

Implement a call function

Bind this to an object by passing it a primitive type that would be converted to an object by the wrapper function – boxing.

// The last call is used first
function add(c,d) {
    return this.a + this.b + c + d
}
const obj = { a: 1.b: 2}

add.call(obj, 3.4) / / 10
add.apply(obj, [3.4]) / / 10

Copy the code

Call changes this to… What actually happens is something like this

    1. Add an add attribute to obj, and this points to obj
    1. Obj.add (3,4) gives the same result as add.call(obj, 3,4)
    1. Finally, delete the additional add attribute

Implement Call based on ES3

Function.prototype.es3Call = function(context) {
    // There is only one context parameter. If the parameter is empty, context is assigned the window value, otherwise the value is passed in
    context = context ||  window
    // Whoever calls this is this
    context.fn = this
    var args = []
    Below the argunments input parameter pseudo-array is the filter out of the first object, push into the other parameters
    for (var i= 1; len = argunments.lenght; i < len; i++) {
        args.push('arguments['+i+'] ')}// the eval() method parses the string as js code
    var result = eval('context.fn('+args+') ')
    // args will automatically call args.toString() because 'context.fn(' + args +')' is essentially a string concatenation and will automatically call toString()
    delete context.fn
    return result
}
Copy the code

Based on ES6 implementation, more convenient

Function.prototype.es6Call = function(context) {
    context = context || window
    context.fn = this
    let args = [...arguments].slice(1);
    // Return the result of the execution
    constresult = context.fn(... args)delete context.fn
    return result
}
Copy the code

Implement the Apply method

Apply does the same thing as Call, except that the extra arguments accepted by Apply must be arrays

    Function.prototype.apply = function(context,arr) {
        context = context ? Object(context) : window;
        context.fn = this

        var result;
        if(! arr) { result = context.fn(); }else {
        / / <! -- ES3 -->
            result = eval('context.fn('+arr+') ')
        / / <! -- ES6 -->result = context.fn(... arr) }delete context.fn
        return result
    }
Copy the code

Implement a bind function

Bind differs from call and apply in that call and apply return the result of an execution, whereas bind returns a function

Function.prototype.bind = function(context) {
    if (typeof this! = ='function') {
        throw new TypeError('Error')}let fn = this
    let arg = [...arguments].slice(1)
    return function F() {
        // Handle the case where the function uses new
        ifthis instanceof F) {
            return newfn(... arg, ... arguments) }else {
            returnfn.apply(context, arg.concat(... Arguments))}}} copy codeCopy the code

6. The difference and application of call, apply and bind

The basic grammar

fun.call(thisArg, param1, param2, ...) fun.apply(thisArg, [param1, param2, ...] ) fun.bind(thisArg, param1, param2, ...)Copy the code

The return value

Call () and apply() return the value that should be returned by the function, and bind() returns a new function that has been hardbound by apply or call(showing the binding)

perform

Call () and apply() execute immediately upon being called, while bind() simply completes the binding of this. Since the function does not execute immediately, it is appropriate to use bind() in the event binding function, both to complete the binding and to ensure that it will only execute when the event fires

Application scenarios

Call (), apply(), and bind() can all change this. When do I need to change this? Most of the time it’s actually about borrowing methods, that is, calling methods on objects that don’t have them themselves. Here’s an example

1. Determine the data type

Object. The prototype. ToString. Call () can accurately determine the data type

var a = "abc";
var b = [1.2.3];
Object.prototype.toString.call(a) == "[object String]" //true
Object.prototype.toString.call(b) == "[object Array]"  //true
Copy the code

The principle is that calling Object’s native toString() method on any value will generate a string of the form [Object NativeconstructorName], from which you can accurately determine the data type of any value. Since both Array and Function inherit this method from Object, why not call them directly? This is because toString() has been overwritten and is not a native method, so instead we call the method of Object and bind this to the corresponding value.

2. Simulate shallow copy

To simulate a shallow copy, we need to remove attributes from the prototype chain. Considering that the source Object may be created based on object.create () and such an Object does not have the hasOwnPrototype() method, we do not call this method directly on the source Object. But by the Object. The prototype. HasOwnPrototype. Call () method to call, because the Object must have this method, so we can borrow

if (Object.prototype.hasOwnPrototype.call(nextSource,nextKey)) {
    to[nextKey] = nextSource[nextKey]
}
Copy the code

3. The inheritance

One of the ways that JavaScript can inherit is by borrowing constructors and assuming that there are child constructors Son and parent constructors Paren. In the case of Son, the internal this refers to the object that will be instantiated later. To take advantage of this, we can augment the subclass instance by calling parent inside Son via call or apply and passing this

4. Class array borrow array method

For example, arguments is an array of classes and does not have the array’s forEach() method. We can call the array’s forEach() method and bind this to arguments

Array.prototype.forEach.call(arguments.item= > {
    console.log(item)
})
Copy the code

5. Maximize the array

The core is that apply can be used to expand an array, as we said earlier, to convert an array of parameters to a list of parameters. For example, we can find the maximum value of an array. Even though the Math object has a Max () method, it only accepts a list of parameters. Math.max.call(NULL, arr)

7. Shallow copy and deep copy implementation

Shallow copy

/ / method
let copy1 = { ... {x: 1}}

/ / method 2
let copy2 = Object.assign({}, {x:1})
Copy the code

Deep copy

// Method 1: disadvantages L copy objects that contain regular expressions, functions or undefined equivalents will fail
JSON.parse(JSON.strigify(obj))

// Method 2: recursive copy
function deepClone(obj) {
    let copy = Obj instanceif Array ? [] : {}
    for (let i in obj) {
        if (obj.hasOwnProperty(i)) {
            copy[i] = typeof Obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
        }
    }
    return copy
}
Copy the code

8. Throttling and shaking prevention

Fat podcast

preface

In the following scenarios, events are frequently triggered and heavy actions such as DOM operations and resource loading are frequently performed, resulting in UI pauses or even browser crashes

    1. Resize, Scroll events for the window object
    1. Mousemove event while dragging
    1. Mousedown, keyDown events in shooters
    1. Text such as, auto-complete keyup event

In fact, for window resize events, most of the actual requirements are to stop resize n milliseconds before performing subsequent processing; Most of the other events require that subsequent processing be performed at a certain frequency.

What is a debounce

Definition 1.

If you hold a spring down, it will not spring until you release it, that is, the action will not be performed until n milliseconds after the call. If the action is called within n milliseconds, the execution time will be recalculated (if it is not triggered a second time within the specified time, it will be executed) interface definition

/** ** Idle control, return function continuous calls, idle time must be "idle", action will be executed *@param Delay Idle time *@param Fn is the function that needs to be called in the actual application@return Returns the customer service call function */
Copy the code

Simple implementation

const debounce = (fn, delay) {
    // Use closures to save timers
    let timer
    return function() {
        // If triggered again within the specified time, the timer will be cleared and reset again
        clearTimeout(timer)
        timer = setTimeout(() = > {
            fn.apply(this.arguments)
        }, delay)
    }

}

function fn() {
    console.log('if you')
}
addEventListener('scroll', debounce(fn,1000))
Copy the code

What is throttling

Built-in trigger once at a specified time (and tighten the tap water, only drop a drop at a certain time)

function throttle(fn,delay) {
    // Use closures to save time
    let prev = Date.now()
    return function(){
        const now = Date.now()
        if (now - prev >= delay) {
            fn.apply(this.arguments)
            prev = Date.now()
        }
    }
}

function fn () { console.log('the throttling') }
addEventListener('scroll', throttle(fn, 1000))
Copy the code

9. How Instanceof works

The prototype of the right-hand variable is on the prototype chain of the left-hand variable

function instanceOf(left, right) {
    let leftValue = left.__proto__ // take implicit archetypes
    let rightValue = right.prototype // Take an explicit prototype
    white(true) {
        if (leftValue === null) {
            return false
        }
        // Returns true when the right explicit stereotype is strictly equal to the left implicit stereotype
        if (leftValue ==== rightValue) {
            return true
        }
        leftValue = leftValue.__proto__
    }
}
Copy the code

10. Implementation of Currization functions

Definition: Converting a multi-parameter function to a single-parameter form Principle: Using the closure principle, you can form a non-destructible scope during execution, then store the pre-processed content in this non-destructible scope, and return a function with the fewest parameters.

The first is to fix the passed parameters and execute them when enough parameters are passed

/** * Implementation note: When a function receives enough arguments, it executes the original function, so how do we determine when enough arguments are reached? * The curry function remembers the arguments you gave it, and defaults to an empty array if it didn't. * Each subsequent call checks to see if it was given enough arguments, executes fn if it was given enough, and returns a new curry function that stuffs the existing arguments into it */
// The function to be currified
let sum = (a, b, c, d) = > {
    return a + b + c + d
}

// The currified function returns a processed function
let curry = (fn, ... arr) = > {// arR records existing parameters
    console.log(arr)
    const fnParamsLen = fn.length
    return (. args) = > { // args receives new parameters
        if (fnParamsLen <= [...arr, ...args].length) { // If the parameters are sufficient, execution is triggered
            returnfn(... arr, ... args) }else { // Continue adding parameters
            return curry(fn, [...arr, ...args])
        }
    } 

}
var sumPlus = curry(sum)
sumPlus(1) (2) (3) (4)
sumPlus(1.2) (3) (4)
sumPlus(1.2.3) (4)
Copy the code

The second way: do not fix the incoming parameter, execute at any time

/** * The main effect of curritization is to delay execution. The trigger condition of execution is not necessarily equal to the number of arguments, but also other conditions, such as the number of arguments accepted at the end of 0, so we need to modify the above function slightly */
// The function to be currified
let sum = arr= > { 
    return arr.reduce((a, b) = > { return a + b }, []) 
}
let curry = (fn, ... arr) = > {
    return (. args) = > {
        if (args.length === 0) {
            returnfn(... arr, ... args) }else {
            returncurry(fn,... arr, ... args) } } }Copy the code

11. Basic implementation principle of Object.cteate

** Method description: **

  1. The object.create () method creates a new Object and takes the first argument of the method as the value of the __proto__ property of the new Object (the prototype Object that takes the first argument as the constructor of the new Object);
  2. Object. The create () method and the second optional parameter that is an Object, the Object of every attribute itself as the new Object attribute, Object attribute value to descriptor (Object. GetOwnPropertyDescriptor (obj, ‘key’)) in the form of Enumenrable is false by default

Define an empty constructor, then specify the prototype object of the constructor, create an empty object with the new operator, and if a second argument is passed, DefineProperties to set the key and value of the created Object and return the created Object. Source: original link

Object.myCreate = function(proto, propertyObject = undefined) {
    if (propertyObject === null) {
        // There is no check on whether the propertyObject is the original wrapper object
        throw 'TypeError'
        return
    }
    // Define an empty constructor
    function Fn() {}
    // Specify the original object of the constructor
    Fn.prototype = proto
    // Create an empty object with the new operator
    const obj = new Fn()
    // If the second argument is not null
    if(propertyObject ! = =undefined) {
        Object.defineProperties(obj, propertyObject)
    }
    if(proto = = =null) {// Create an Object with no prototype Object, object.create (null)
        obj.__proto__ = null
    }
    return obj
}
/ / sample
// When the second argument is null, TypeError is raised
// const throwErr = Object.myCreate({a: 'aa'}, null) // Uncaught TypeError
// Build a
const obj1 = Object.myCreate({a: 'aa'})
console.log(obj1)  // {}, the prototype object of obj1's constructor is {a: 'aa'}
const obj2 = Object.myCreate({a: 'aa'}, {
  b: {
    value: 'bb'.enumerable: true}})console.log(obj2)  // {b: 'bb'}, obj2's constructor prototype object is {a: 'aa'}

Copy the code

12. Implement a basic Event Bus

The purpose of EventBus is to act as middleware, a bridge between the two components

  • The sender passes the data and eventName to EventBus via EventBusName.$emit(‘eventName’, data)
  • The receiver processes the data via EventBusName.$ON (‘eventName’, methods)

Simple implementation

class EventBus {
    constructor() {
        // Store events
        this.events = this.events || new Map()}// Listen on events
    addListener(type, fn) {
        if (!this.events.get(type)) {
            this.events.set(type, fn)
        }
    }
    
    // Trigger the event
    emit(type) {
        let handle = this.events.get(type)
        handle.apply(this,[...arguments].slice(1))}}/ / test
let emitter = new EventBus()
// Listen on events
emitter.addListener('add'.age= > {console.log(age)})
// Trigger the event
emitter.emit('add'.18)
Copy the code

upgrade

What if you listen for the same event name repeatedly? After the first binding holds, there is no way to register the binding for subsequent listeners, because we need to put subsequent listeners into an array

Class EventBus {
    constructor() {
        // Store events
        this.events = this.events || new Map()}// Listen on events
    addListener(type, fn) {
        const handler = this.events.get(type)
        if(! handler) {this.events.set(type, fn)
        } else if(handler &&typeof handler ==== 'function') {// If handler is a function, there is only one listener before
            this._events.set(type, [handler, fn]); // We need to store multiple listeners in arrays
        } else {
            handler.push(fn); // There are already multiple listeners, so just add push to the array}}// Trigger the event
    emit(type,... args) {
        const handler = this.events.get(type)
        // If it is an array, it means that there are multiple listeners
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.lenght; i++) {
                handler[i].apply(this, args)
            }
        } else {
            // We can trigger a single function directly
            handler.apply(this, args); }}// Remove the listener using removeListener
    removeListener(type, fn) {
        const handler = this._events.get(type); // Get the function list corresponding to the event name
        // If it is a function, it is only listened once
         if (handler && typeof handler === 'function') {
           this._events.delete(type, fn);
         } else {
            let postion;
             // If handler is an array, it is necessary to find the corresponding function
            for (let i = 0; i < handler.length; i++) {
               if (handler[i] === fn) {
                 postion = i;
               } else {
                postion = -1; }}// If a matching function is found, it is cleared from the array
            if(postion ! = = -1) {
              // Find the location of the array and clear the callback directly
              handler.splice(postion, 1);
              // If only one function is cleared, cancel the array and save it as a function
              if (handler.length === 1) {
                this._events.set(type, handler[0]); }}else {
              return this;
            }
           }
         }
    }
}
Copy the code

13. Implement a two-way data binding

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// Data hijacking
Object.defineProperty(obj, 'text', {
    configurable: true.// can be operated
    enumerable: true.// enumerable
    get() {
        console.log('Got the data.')},set(newVal) {
        console.log('Data updated')
        input.value = newVal
        span.innerHTML = newVal
    }
})

// Input listener
input.addEventListener('keyup'.function(e) {
    obj.text = e.target.value
})
Copy the code

See this in more detail and this is probably the most detailed explanation of a responsive system

Implement a simple route

How it works: Using the hash form (which can also be handled using the History API) as an example, when the HASH of the URL changes, the callback registered with hashchange is triggered to perform different operations and display different contents in the callback.

class Route {
    constructor() {
        // Route storage object
        this.routes = {}
        / / the current hash
        this.currentHash = ' '
        FreshRoute = this.freshroute.bind (this)
        window.addEventListener('load'.this.refresh.bind(this), false)
        window.addEventListener('hashchange'.this.freshRoute.bind(this), false)}/ / update
     freshRoute () { 
         this.currentHash = location.hash.slice(1) | |'/' 
         this.routes[this.currentHash]() 
     }
     // Store or add
     storeRoute (path, cb) { 
         this.routes[path] = cb || function () {}}}Copy the code

15. Implement lazy loading

<ul>
    <li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
    <li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li> 
    <li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li> 
    <li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li> 
    <li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li> 
    <li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
    <li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li> 
    <li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
    <li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
    <li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
</ul>
Copy the code

The initial image SRC load will display the default image. The actual image is placed on the data attribute of the IMG tag. Principle: Monitor the screen scrolling, calculate the position of the image, and replace the SRC of the image with the actual resource to be displayed when the image is in the visible area of the screen. So the key is to know if the image is in the viewable area (traverse the image, figure out the critical point, just in and just out)

// Internet Explorer 8, 7, 6, 5:
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

// Get the nodes of all images
const imgs = document.querySelectorAll('img')
// Perform lazy load calculation while listening to screen scroll
window.addEventListener('scroll', lazyLoad)

/ / lazy loading
function lazyLoad () {
    // Roll out the height
    let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
    // Traverse the image node to determine the assignment SRC where the image appears in the visible area
    for (let i = 0; i < imgs.length; i++) {
        // Find the distance between the top corner of the current image and the outer element
        const curImgOffsetTop = imgs[i].offsetTop
        // Find the critical point, just to roll into, and just roll out completely (all the outer element vertices are reference points)
        // Just the point to scroll into
        const readyInOffestTop = scrollTop + clientHeight
        // Just out of the point (just out of the picture, the top left corner of the distance from the top)
        const justOutOffestTop = scrollTop - imgs[i].height
        // appear in viewable area
        if(curImgOffsetTop > readyInOffestTop && curImgOffsetTop < justOutOffestTop) {imgs[I].src = imgs[I].getAttribute('data')}}}Copy the code

Optimize to find the index of the first image under the screen, less than the update SRC of the index

16. Mobile ADAPTS rem

To understand:

  • Em is a unit of size relative to the font size of the parent element. When the parent element is 30px and the child element is 0.8em, the child element is 30 * 0.8 = 24px.
  • Rem is a unit of size relative to the HTML gen element, when
<html lang="zh-cmn-Hans" style="font-size: 50px;">
    <body style="font-size: 0">
        <div class="box1">This is the div box</div>
    <body>
</html>Box1 {font-size: 6rem} ' 'will render the actual box size will be' 50px * 6 = 300px '; So rem is 50px so the key point for mobile adaptation is to determine how much PX REM is; When we get the design drawing development, we need to pay attention to two, one is' different size of the phone ', the second is' the design will only give a standard design draft ', then we also hope that we can easily write REM by looking at the design draft. Now pretend that the fontSize of our root element is set to X (px), and I want to set the width of Box1 to the full screen width, say 750 and 1334; I hope I'll just focus on the design for the rest of my writing. Then I want the following to be true: HTML<div :style="width: `${designWidth}rem`;">This is the div box</div>
Copy the code
    const designWidth = 750
    // The width of the device is required
    const clientWidth = document.documentElement.clientWidth || document.body.clientWidth || window.innerWidth || window.screen.width
    designWidth + rem = designWidth * x = clientWidth
    // Solve the equation
    x = clientWidth / designWidth
   
Copy the code

So the correct position to set the fontSize of the HTML root element is:

document.documentElement.style.fontSize = ( clientWidth / designWidth) + 'px';
Copy the code

Then in the actual work, we will encounter landscape or portrait, and the designWidth will change; If the screen size can be changed on the PC side, listen for resize to change the fontSize of the root element

addEventListener("resize", setHtmlRootFontSize)
Copy the code