[intermediate and advanced front end] necessary, 30 + high-frequency handwritten questions and detailed answers (10000 words long text). See how "you" can embarrass me

Keywords: Javascript html5 Interview

preface

Write a quick row. Can you write a Promise A deep copy... I believe you have encountered such problems in the interview or daily business more than once. You feel familiar when writing code on site, but even if you can't write it, the expected offer is far away from us o(╥﹏╥) o. Come on, guys, roll up. The daily plan is not enough, and the old plan is more than enough. Let's learn one every day and see how those interviewers can beat us!!! Hum

catalogue

preface

one   Three ways to implement instanceOf

Recursive implementation (mode 1)

Traversal implementation (mode 2)

Traversal implementation (mode 3)

2. Implement json.stringify (super detailed)

three   Implement a Promise

four   Three ways to flatten multidimensional arrays

Recursive implementation (mode 1)

Traversal implementation (mode 2)

Teaser Version (mode 3)

5. Realize deep copy

6. Implement the new operator

7. Implement publish and subscribe (EventEmitter)

8. Implement Promise with parallel restrictions

9. Handwritten LRU algorithm (ant financial service has encountered it)

Array & & object implementation

Map implementation method

10. call

11. apply

12. Two ways to implement trim method

Space removal method (mode 1)

Character extraction method (mode 2)

13. Implement Promise.all

14. Implement Promise.race

15. Object.create

16. Quick sort

17. Bubble sorting

18. Select sort

19. Insert sort

20. setTimeout simulates setInterval

21. setInterval simulates setTimeout

22. Four methods of array de duplication

Using Set (mode 1)

indexOf de duplication (mode 2)

indexOf de duplication (mode 3)

Array.from de duplication

23. Mobile phone number 3-3-4 Division

24. Thousands of formatted numbers

25. Binary search

26. Two methods of version comparison

27. Parse url parameters

28. Implement the general function to obtain js data type

29. Convert string to hump

30. Implement reduce

code implementation

Test one

Handwritten implementation questions.jpg

one   Three ways to implement instanceOf

instanceof   The operator is used to detect the of the constructor   prototype   Property appears on the prototype chain of an instance object. On MDN

"Key points:"   The prototype of the constructor Fn, the prototype chain of the instance object.

Therefore, as long as you traverse the prototype chain of the instance Object, look up one by one to see if there is a prototype equal to Fn's prototype. Until the topmost Object is not found, false is returned.

Recursive implementation (mode 1)

/**
 * 
 * @param {*} obj Instance object
 * @param {*} func Constructor
 * @returns true false
 */
const instanceOf1 = (obj, func) => {
  if (obj === null || typeof obj !== 'object') {
    return false
  }

  let proto = Object.getPrototypeOf(obj)

  if (proto === func.prototype) {
    return true
  } else if (proto === null) {
    return false
  } else {
    return instanceOf1(proto, func)
  }
}

//  test
let Fn = function () { }
let p1 = new Fn()

console.log(instanceOf1({}, Object)) // true
console.log(instanceOf1(p1, Fn)) // true
console.log(instanceOf1({}, Fn)) // false
console.log(instanceOf1(null, Fn)) // false
console.log(instanceOf1(1, Fn)) // false

Traversal implementation (mode 2)

/**
 * 
 * @param {*} obj Instance object
 * @param {*} func Constructor
 * @returns true false
 */
const instanceOf2 = (obj, func) => {
  if (obj === null || typeof obj !== 'object') {
    return false
  }

  let proto = obj

  while (proto = Object.getPrototypeOf(proto)) {
    if (proto === null) {
      return false
    } else if (proto === func.prototype) {
      return true
    }
  }

  return false
}

//  test
let Fn = function () { }
let p1 = new Fn()

console.log(instanceOf2({}, Object)) // true
console.log(instanceOf2(p1, Fn)) // true
console.log(instanceOf2({}, Fn)) // false
console.log(instanceOf2(null, Fn)) // false
console.log(instanceOf2(1, Fn)) // false

Traversal implementation (mode 3)

/**
 * 
 * @param {*} obj Instance object
 * @param {*} func Constructor
 * @returns true false
 */
const instanceOf3 = (obj, func) => {
  if (obj === null || typeof obj !== 'object') {
    return false
  }

  let proto = obj
  //  Because there must be an end (the topmost Object), it will not be an endless loop
  while (true) {
    if (proto === null) {
      return false
    } else if (proto === func.prototype) {
      return true
    } else {
      proto = Object.getPrototypeOf(proto)
    }
  }
}

//  test
let Fn = function () { }
let p1 = new Fn()

console.log(instanceOf3({}, Object)) // true
console.log(instanceOf3(p1, Fn)) // true
console.log(instanceOf3({}, Fn)) // false
console.log(instanceOf3(null, Fn)) // false
console.log(instanceOf3(1, Fn)) // false


2. Implement json.stringify (super detailed)

Before looking at the code implementation, you can first look at a sad story written a few days ago. My year-end bonus was almost wasted because of JSON.stringify

JSON.stringify()   Method converts a JavaScript object or value into a JSON string. If a replacer function is specified, the value can be optionally replaced, or if the specified replacer is an array, it can optionally contain only the attributes specified by the array. MDN

❞ ❝

JSON.stringify itself has many conversion rules and features (see MDN for details), and it is troublesome to implement it completely (in order to implement this function, fat fish will not move o(╥﹏╥) o)

const jsonstringify = (data) => {
  //  Confirm whether there is a circular reference to an object
  const isCyclic = (obj) => {
  //  Use the Set data type to store the detected objects
  let stackSet = new Set()
  let detected = false

  const detect = (obj) => {
    //  If it is not an object type, you can skip it directly
    if (obj && typeof obj != 'object') {
      return
    }
    //  When the object to be checked already exists in the stackSet, it indicates that there is a circular reference
    if (stackSet.has(obj)) {
      return detected = true
    }
    //  Save the current obj as stackSet
    stackSet.add(obj)

    for (let key in obj) {
      //  Check the attributes under obj one by one
      if (obj.hasOwnProperty(key)) {
        detect(obj[key])
      }
    }
    //  After the level detection is completed, the current object is deleted to prevent misjudgment
    /*
      For example, an object's attribute points to the same reference. If it is not deleted, it will be regarded as a circular reference
      let tempObj = {
        name: ''fat head fish'
      }
      let obj4 = {
        obj1: tempObj,
        obj2: tempObj
      }
    */
    stackSet.delete(obj)
  }

  detect(obj)

  return detected
}

  //  Feature 7:
  //  Executing this method on objects that contain circular references (objects refer to each other to form an infinite loop) will throw an error.
  if (isCyclic(data)) {
    throw new TypeError('Converting circular structure to JSON')
  }

  //  Feature 9:
  //  When trying to convert   BigInt   The value of type throws an error
  if (typeof data === 'bigint') {
    throw new TypeError('Do not know how to serialize a BigInt')
  }

  const type = typeof data
  const commonKeys1 = ['undefined', 'function', 'symbol']
  const getType = (s) => {
    return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
  }

  //  Non object
  if (type !== 'object' || data === null) {
    let result = data
    //  Feature 4:
    // Values in NaN and Infinity formats and null will be treated as null.
    if ([NaN, Infinity, null].includes(data)) {
      result = 'null'
      //  Feature 1:
      // ` When undefined, any function and symbol value are converted separately, they will be returned   undefined
    } else if (commonKeys1.includes(type)) {
      //  Get undefined directly, not a string 'undefined'
      return undefined
    } else if (type === 'string') {
      result = '"' + data + '"'
    }

    return String(result)
  } else if (type === 'object') {
    //  Feature 5:
    //  Conversion value, if any   toJSON()   Method that defines what values will be serialized
    //  Feature 6:
    // Date date called toJSON()   Convert it to a string string (the same as date. Toisstring()), so it will be treated as a string.
    if (typeof data.toJSON === 'function') {
      return jsonstringify(data.toJSON())
    } else if (Array.isArray(data)) {
      let result = data.map((it) => {
        //  Feature 1:
        // ` undefined ',' arbitrary function 'and' symbol value 'will be converted to  ` null`
        return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
      })

      return `[${result}]`.replace(/'/g, '"')
    } else {
      //  Feature 2:
      //  The wrapper objects of Boolean value, number and string will be automatically converted to the corresponding original value during serialization.
      if (['boolean', 'number'].includes(getType(data))) {
        return String(data)
      } else if (getType(data) === 'string') {
        return '"' + data + '"'
      } else {
        let result = []
        //  Characteristic 8
        //  Other types of objects, including   Map/Set/WeakMap/WeakSet, only enumerable properties will be serialized
        Object.keys(data).forEach((key) => {
          //  Feature 3:
          //  All attributes with symbol as the attribute key are completely ignored, even if they are forcibly specified in the replace parameter.
          if (typeof key !== 'symbol') {
            const value = data[key]
            //  Characteristic I
            // ` undefined ',' arbitrary function 'and' symbol value 'will be ignored in the serialization process when they appear in the attribute value of' non array object '
            if (!commonKeys1.includes(typeof value)) {
              result.push(`"${key}":${jsonstringify(value)}`)
            }
          }
        })

        return `{${result}}`.replace(/'/, '"')
      }
    }
  }
}

//  Various tests

//  one   Test the basic output
console.log(jsonstringify(undefined)) // undefined 
console.log(jsonstringify(() => { })) // undefined
console.log(jsonstringify(Symbol('Front fat head fish'))) // undefined
console.log(jsonstringify((NaN))) // null
console.log(jsonstringify((Infinity))) // null
console.log(jsonstringify((null))) // null
console.log(jsonstringify({
  name: 'Front fat head fish',
  toJSON() {
    return {
      name: 'Front fat head fish 2',
      sex: 'boy'
    }
  }
}))
//  {"name": "front end fat head fish 2","sex":"boy"}

//  two   Compare with the native JSON.stringify transformation
console.log(jsonstringify(null) === JSON.stringify(null));
// true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
// true
console.log(jsonstringify(false) === JSON.stringify(false));
// true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
// true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
// true
let str = "Front fat head fish";
console.log(jsonstringify(str) === JSON.stringify(str));
// true
let reg = new RegExp("\w");
console.log(jsonstringify(reg) === JSON.stringify(reg));
// true
let date = new Date();
console.log(jsonstringify(date) === JSON.stringify(date));
// true
let sym = Symbol('Front fat head fish');
console.log(jsonstringify(sym) === JSON.stringify(sym));
// true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
// true
let obj = {
  name: 'Front fat head fish',
  age: 18,
  attr: ['coding', 123],
  date: new Date(),
  uni: Symbol(2),
  sayHi: function () {
    console.log("hello world")
  },
  info: {
    age: 16,
    intro: {
      money: undefined,
      job: null
    }
  },
  pakingObj: {
    boolean: new Boolean(false),
    string: new String('Front fat head fish'),
    number: new Number(1),
  }
}
console.log(jsonstringify(obj) === JSON.stringify(obj)) 
// true
console.log((jsonstringify(obj)))
//  {"name": "front-end fat head fish", "age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string": "front-end fat head fish", "number":1}}
console.log(JSON.stringify(obj))
//  {"name": "front-end fat head fish", "age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string": "front-end fat head fish", "number":1}}

//  three   Test traversable objects
let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: 'Front fat head fish',
    enumerable: true
  },
  sex: {
    value: 'boy',
    enumerable: false
  },
})

console.log(jsonstringify(enumerableObj))
//  {"name": "front end fat head fish"}

//  four   Test circular references and Bigint

let obj1 = { a: 'aa' }
let obj2 = { name: 'Front fat head fish', a: obj1, b: obj1 }
obj2.obj = obj2

console.log(jsonstringify(obj2))
// TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
// TypeError: Do not know how to serialize a BigInt

three   Implement a Promise

For space reasons, I will not introduce Promise A + specification and other detailed implementations other than then function here. I generally use the following version in interviews and basically pass it directly.

class MyPromise {
  constructor (exe) {
    //  Last value, Promise  . The value received by then or. catch
    this.value = undefined
    //  Status: three statuspending success failure
    this.status = 'pending'
    //  Successful function queue
    this.successQueue = []
    //  Failed function queue
    this.failureQueue = []
    const resolve = () => {
      const doResolve = (value) => {
        //  Execute the cached function queue one by one, and set the status and value
        if (this.status === 'pending') {
          this.status = 'success'
          this.value = value
  
          while (this.successQueue.length) {
            const cb = this.successQueue.shift()
  
            cb && cb(this.value)
          }
        }
      }

      setTimeout(doResolve, 0)
    }

    const reject = () => {
      //  Basically the same as resolve
      const doReject = (value) => {
        if (this.status === 'pending') {
          this.status = 'failure'
          this.value = value
  
          while (this.failureQueue.length) {
            const cb = this.failureQueue.shift()
  
            cb && cb(this.value)
          }
        }
      }

      setTimeout(doReject, 0)
    }

    exe(resolve, reject)
  }
  
  then (success = (value) => value, failure = (value) => value) {
    // . then returns a new Promise
    return new MyPromise((resolve, reject) => {
      //  Wrap back to function
      const successFn = (value) => {
        try {
          const result = success(value)
          //  If the result value is a Promise, you need to pass the Promise value down, otherwise you can resolve it directly
          result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
        } catch (err) {
          reject(err)
        }
      }
      //  Encapsulation of basic callback function
      const failureFn = (value) => {
        try {
          const result = failure(value)
          
          result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
        } catch (err) {
          reject(err)
        }
      }
      //  If the Promise status is not over, the successful and failed functions are cached in the queue
      if (this.status === 'pending') {
        this.successQueue.push(successFn)
        this.failureQueue.push(failureFn)
        //  If it has ended successfully, execute the callback directly  
      } else if (this.status === 'success') {
        success(this.value)
      } else {
        //  If it has failed, execute the failure callback directly
        failure(this.value)
      }
    })
  }
  //  Other functions are not implemented one by one
  catch () {

  }
} 
//  The following is an example to verify the results of the above implementation
const pro = new MyPromise((resolve, reject) => {
  setTimeout(resolve, 1000)
  setTimeout(reject, 2000)
})

pro
  .then(() => {
    console.log('2_1')
    const newPro = new MyPromise((resolve, reject) => {
      console.log('2_2')
      setTimeout(reject, 2000)
    })
    console.log('2_3')
    return newPro
  })
  .then(
    () => {
      console.log('2_4')
    },
    () => {
      console.log('2_5')
    }
  )
  
pro
  .then(
    data => {
      console.log('3_1')
      throw new Error()
    },
    data => {
      console.log('3_2')
    }
  )
  .then(
    () => {
      console.log('3_3')
    },
    e => {
      console.log('3_4')
    }
  )
// 2_1
// 2_2
// 2_3
// 3_1
// 3_4
// 2_5

four   Three ways to flatten multidimensional arrays

It is often encountered in business and interview. Flattening multidimensional arrays is a necessary skill

Recursive implementation (mode 1)

/**
 * 
 * @param {*} array Deeply nested data
 * @returns array New array
 */
const flat1 = (array) => {
  return array.reduce((result, it) => {
    return result.concat(Array.isArray(it) ? flat1(it) : it)
  }, [])
}

//  test
let arr1 = [
  1,
  [ 2, 3, 4 ],
  [ 5, [ 6, [ 7, [ 8 ] ] ] ]
]
console.log(flat1(arr1)) // [1, 2, 3, 4, 5, 6, 7, 8]

Traversal implementation (mode 2)

/**
 * 
 * @param {*} array Deeply nested data
 * @returns array New array
 */
 
const flat2 = (array) => {
  const result = []
  //  Expand one layer
  const stack = [ ...array ]
  
  while (stack.length !== 0) {
    //  Take out the last element
    const val = stack.pop()
    
    if (Array.isArray(val)) {
     //  If it is an array, push it behind the stack
      stack.push(...val)
    } else {
      //  Push in front of array
      result.unshift(val)
    }
  }

  return result
}
//  test
let arr2 = [
  1,
  [ 2, 3, 4 ],
  [ 5, [ 6, [ 7, [ 8 ] ] ] ]
]

console.log(flat2(arr2)) // [1, 2, 3, 4, 5, 6, 7, 8]

Teaser Version (mode 3)

With the help of the native flat function, the layer to be expanded is designated as Infinity, that is, the infinite layer, which can be leveled. It's an idea, but the interviewer probably thinks we're funny 😄, I don't know whether to write such code.

/**
 * 
 * @param {*} array Deeply nested data
 * @returns New array
 */
const flat3 = (array) => {
  return array.flat(Infinity)
}
//  test
let arr3 = [
  1,
  [ 2, 3, 4 ],
  [ 5, [ 6, [ 7, [ 8 ] ] ] ]
]

console.log(flat3(arr3)) // [1, 2, 3, 4, 5, 6, 7, 8]

5. Realize deep copy

const deepClone = (target, cache = new Map()) => {
  const isObject = (obj) => typeof obj === 'object' && obj !== null

  if (isObject(target)) {
    //  Resolve circular references
    const cacheTarget = cache.get(target)
    //  Direct return already exists, no need to parse again
    if (cacheTarget) {
      return cacheTarget
    }

    let cloneTarget = Array.isArray(target) ? [] : {}

    cache.set(target, cloneTarget)

    for (const key in target) {
      if (target.hasOwnProperty(key)) {
        const value = target[ key ] 
        cloneTarget[ key ] = isObject(value) ? deepClone(value, cache) : value
      }
    }

    return cloneTarget
  } else {
    return target
  }
}

const target = {
  field1: 1,
  field2: undefined,
  field3: {
      child: 'child'
  },
  field4: [2, 4, 8],
  f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } } },
};

target.target = target;

const result1 = deepClone(target);
console.log(result1)

image.png

6. Implement the new operator

"Idea:"   Before implementing new, let's take a look at the execution process of new

「new」   Keyword will perform the following operations:

  1. Create an empty simple JavaScript object (i.e. {});

  2. Add attributes to the newly created object in step 1   「「proto」」  , Link this property to the prototype object of the constructor

  3. Take the newly created object in step 1 as the context of this and execute the function;

  4. If the function does not return an object, this is returned.

const _new = function (func, ...args) {

  //  Steps 1 and 2
  let obj = Object.create(func.prototype)
  //  It can also be simulated through the following code
  /**
  let Ctor = function () {}

  Ctor.prototype = func.prototype
  Ctor.prototype.constructor = func

  let obj = new Ctor()
 */
  //  Step 3
  let result = func.apply(obj, args)
  //  Step 4
  if (typeof result === 'object' && result !== null || typeof result === 'function') {
    return result
  } else {
    return obj
  }
}

//  test
let Person = function (name, sex) {
  this.name = name
  this.sex = sex
}

Person.prototype.showInfo = function () {
  console.log(this.name, this.sex)
}

let p1 = _new(Person, 'qianlongo', 'sex')

console.log(p1)

//  Person   {   name:  ' Front fat head fish ',   sex:  ' sex'  }

7. Implement publish and subscribe (EventEmitter)

I believe you will not be unfamiliar with publishing and subscribing, and you will often encounter it in practical work, such as Vue's EventBus,  $ on,  $ Let's try it out

class EventEmitter {
  constructor () {
    this.events = {}
  }
  //  event listeners 
  on (evt, callback, ctx) {
    if (!this.events[ evt ]) {
      this.events[ evt ] = []
    }
    
    this.events[ evt ].push(callback)

    return this
  }
  //  Publish event
  emit (evt, ...payload) {
    const callbacks = this.events[ evt ]

    if (callbacks) {
      callbacks.forEach((cb) => cb.apply(this, payload))
    }

    return this
  } 
  //  Delete subscription
  off (evt, callback) {

    //  Nothing was posted. All events were cancelled
    if (typeof evt === 'undefined') {
      delete this.events
    } else if (typeof evt === 'string') {
      //  Deletes the callback for the specified event  
      if (typeof callback === 'function') {
        this.events[ evt ] = this.events[ evt ].filter((cb) => cb !== callback)
      } else {
        //  Delete entire event
        delete this.events[ evt ]
      }
    }

    return this
  }
  //  One time event subscription
  once (evt, callback, ctx) {
    const proxyCallback = (...payload) => {
      callback.apply(ctx, payload)
      //  The event subscription is deleted after the callback function is executed
      this.off(evt, proxyCallback)
    }

    this.on(evt, proxyCallback, ctx)
  }
}

//  test
const e1 = new EventEmitter()

const e1Callback1 = (name, sex) => {
  console.log(name, sex, 'evt1---callback1')
}
const e1Callback2 = (name, sex) => {
  console.log(name, sex, 'evt1---callback2')
}
const e1Callback3 = (name, sex) => {
  console.log(name, sex, 'evt1---callback3')
}

e1.on('evt1', e1Callback1)
e1.on('evt1', e1Callback2)
//  Execute callback only once
e1.once('evt1', e1Callback3)

e1.emit('evt1', 'Front fat head fish', 'boy')
//  Front fat head fish   boy   evt1---callback1
//  Front fat head fish   boy   evt1---callback2
//  Front fat head fish   boy   evt1---callback3
console.log('------Attempt to delete e1Callback1------')
//  Remove e1Callback1
e1.off('evt1', e1Callback1)
e1.emit('evt1', 'Front fat head fish', 'boy')
//  Front fat head fish   boy   evt1---callback2

8. Implement Promise with parallel restrictions

This is a real problem encountered by the majority of netizens. Let's take a look at the meaning of the problem first

/*
JS Implement an asynchronous Scheduler with concurrency constraints to ensure that there are at most two tasks running at the same time.
Improve the Scheduler class of the following code to enable the following programs to output normally:
class Scheduler {
  add(promiseCreator) { ... }
  // ...
}
   
const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  }
})
  
const scheduler = new Scheduler()
  
const addTask = (time,order) => {
  scheduler.add(() => timeout(time).then(()=>console.log(order)))
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')

// output: 2 3 1 4
 Complete execution process of the whole:

Start 1 and 2 tasks
500ms When, task 2 is completed, output 2, and task 3 starts to execute
800ms When, task 3 is completed, output 3, and task 4 begins to execute
1000ms When 1 task is executed, 1 is output, and only 4 tasks are left to execute
1200ms When the task is completed, output 4
*/


"Resolution"

After reading the title, these problems will probably exist

  1. How can I ensure that only two tasks are being executed at the same time?

  2. When a task is finished, how do you know which task to perform next?

"Question 1": you only need to use one counter to control. Each task counter starts with + 1, and after the end, the counter is - 1 to ensure that the counter must be < = 2.

"Question 2": according to the requirements of the topic, the tasks are executed in order, but the end time of the task is uncertain, so the next task must be executed in this order

Task 1 = > Task 2 = > Task 3 = > task 4

Using the nature of array queue, push the tasks into the queue one by one. After the execution of the previous tasks, take out the tasks at the head of the queue for execution.

class Scheduler {
  constructor () {
    this.queue = []
    this.maxCount = 2
    this.runCount = 0
  }
  //  The Promise creator returns a Promise after execution
  add(promiseCreator) {
    //  Less than or equal to 2, direct execution
    this.queue.push(promiseCreator)
    //  Every time you add, you will try to perform the task
    this.runQueue()
  }

  runQueue () {
    //  There are still tasks in the queue to be executed
    if (this.queue.length && this.runCount < this.maxCount) {
      //  Execute the function that joins the queue first
      const promiseCreator = this.queue.shift()
      //  Start task   Count + 1     
      this.runCount += 1
      //  Assuming that all tasks are executed successfully, of course, you can also do catch
      promiseCreator().then(() => {
        //  Task execution completed, count - 1
        this.runCount -= 1
        //  Try the next task
        this.runQueue()
      })
    }
  }
}
   
const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}
  
const scheduler = new Scheduler()
  
const addTask = (time,order) => {
  scheduler.add(() => timeout(time).then(()=>console.log(order)))
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')

// 2
// 3
// 1
// 4

9. Handwritten LRU algorithm (ant financial service has encountered it)

I remember I met this algorithm problem in the interview of ant gold suit before. You may also encounter it.

"General meaning"

leetCode

Use the data structure you master to design and implement a   LRU (least recently used) caching mechanism. Implement LRUCache class:

  1. LRUCache(int capacity) takes a positive integer as the capacity   Capacity initialize LRU cache

  2. int get(int key)   If the keyword key exists in the cache, the value of the keyword is returned; otherwise, - 1 is returned.

  3. void put(int key, int value)   If the keyword already exists, change its data value; if the keyword does not exist, insert the group of "keyword value". When the cache capacity reaches the maximum, it should delete the longest unused data value before writing new data, so as to make room for the new data value.

The requirements of 1 and 2 are relatively simple, mainly condition 3. When the cache capacity reaches the upper limit, it should delete the longest unused data value before writing new data. The capacity corresponds to condition 1. The key is how to understand "longest unused"?

  1. Both reading and writing are using data

  2. If we put the corresponding key value at the end of the array, whether reading or writing, does that mean that the head of the array is the longest unused?

Array & & object implementation

var LRUCache = function (capacity) {
  //  Use an array to record the order of reading and writing
  this.keys = []
  //  Save key with object   Value value
  this.cache = {}
  //  capacity
  this.capacity = capacity
}

LRUCache.prototype.get = function (key) {
  //  If present
  if (this.cache[key]) {
    //  Delete the original location first
    remove(this.keys, key)
    //  Move to the last one to keep access up to date
    this.keys.push(key)
    //  Return value
    return this.cache[key]
  }
  return -1
}

LRUCache.prototype.put = function (key, value) {
  if (this.cache[key]) {
    //  Update the value when it exists
    this.cache[key] = value
    //  Update the location to the last one
    remove(this.keys, key)

    this.keys.push(key)
  } else {
    //  Join when it doesn't exist
    this.keys.push(key)
    this.cache[key] = value
    //  If the capacity exceeds the maximum value, the longest unused (that is, the first key in the array) will be deleted
    if (this.keys.length > this.capacity) {
      removeCache(this.cache, this.keys, this.keys[0])
    }
  }
}

//  Remove key from array
function remove(arr, key) {
  if (arr.length) {
    const index = arr.indexOf(key)

    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

//  Remove from cache   key
function removeCache(cache, keys, key) {
  cache[key] = null
  remove(keys, key)
}

const lRUCache = new LRUCache(2)

console.log(lRUCache.put(1, 1)) //  Cache is   {1=1}
console.log(lRUCache.put(2, 2)) //  Cache is   {1=1,   2=2}
console.log(lRUCache.get(1))    //  return   one
console.log(lRUCache.put(3, 3)) //  This operation causes the keyword   two   Void, cache is   {1=1,   3=3}
console.log(lRUCache.get(2))    //  return  - one   (not found)
console.log(lRUCache.put(4, 4)) //  This operation causes the keyword   one   Void, cache is   {4=4,   3=3}
console.log(lRUCache.get(1) )   //  return  - one   (not found)
console.log(lRUCache.get(3))    //  return   three
console.log(lRUCache.get(4) )   //  return   four

Map implementation method

In the first implementation method, we use arrays to store the order in which each key is accessed (get, set). This implementation is more troublesome. Is there any other scheme that makes it more convenient and does not need to maintain the array? With the help of Map, the order can be maintained when setting the value, and it will be very convenient to process LRU algorithm

/**
 * @param {number} capacity
 */
var LRUCache = function (capacity) {
  this.cache = new Map()
  this.capacity = capacity
};

/** 
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function (key) {
  if (this.cache.has(key)) {
    const value = this.cache.get(key)
    //  Update location
    this.cache.delete(key)
    this.cache.set(key, value)

    return value
  }

  return -1
};

/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function (key, value) {
  //  If it already exists, update its location to "latest"
  //  Delete before insert
  if (this.cache.has(key)) {
    this.cache.delete(key)
  } else {
    //  Before inserting data, judge whether the size meets the capacity
    //  It has > = capacity. You need to delete the data inserted at the beginning
    //  The keys() method gets a traversable object, and executes next() to get a shape such as{   value:  ' xxx',   done:   false  } Object of
    if (this.cache.size >= this.capacity) {
      this.cache.delete(this.cache.keys().next().value)
    }
  }

  this.cache.set(key, value)
};

const lRUCache = new LRUCache(2)

console.log(lRUCache.put(1, 1)) //  Cache is   {1=1}
console.log(lRUCache.put(2, 2)) //  Cache is   {1=1,   2=2}
console.log(lRUCache.get(1))    //  return   one
console.log(lRUCache.put(3, 3)) //  This operation causes the keyword   two   Void, cache is   {1=1,   3=3}
console.log(lRUCache.get(2))    //  return  - one   (not found)
console.log(lRUCache.put(4, 4)) //  This operation causes the keyword   one   Void, cache is   {4=4,   3=3}
console.log(lRUCache.get(1) )   //  return  - one   (not found)
console.log(lRUCache.get(3))    //  return   three
console.log(lRUCache.get(4) )   //  return   four


10. call

mdn call is described in this way,   call   Method uses a specified   this   Value and one or more parameters given separately to call a function. Therefore, the key point is to specify this and one or more parameters. As long as you understand the basic usage of this, it will be much easier to implement.

/**
 * 
 * @param {*} ctx Function execution context this
 * @param  {...any} args parameter list
 * @returns Result of function execution
 */
 
Function.prototype.myCall = function (ctx, ...args) {
  //  Simply handle scenarios where ctx context is not transmitted, or where null and undefined are transmitted
  if (!ctx) {
    ctx = typeof window !== 'undefined' ? window : global
  }
  //  Violence handling   ctx may be passed to non objects
  ctx = Object(ctx)
  //  Generate unique key with Symbol
  const fnName = Symbol()
  //  this here is the function to be called
  ctx[ fnName ] = this
  //  Expand args and call the fnName function. At this time, this inside the fnName function is ctx
  const result = ctx[ fnName ](...args)
  //  When finished, remove fnName from the context ctx
  delete ctx[ fnName ]

  return result
}

//  test
let fn = function (name, sex) {
  console.log(this, name, sex)
}

fn.myCall('', 'Front fat head fish')
//  window   Front fat head fish   boy
fn.myCall({ name: 'Front fat head fish', sex: 'boy' }, 'Front fat head fish')
//  {   name:  ' Front fat head fish ',   sex:  ' boy'  }  Front fat head fish   boy

11. apply

The syntax and function of this method are similar to   call   The method is similar, with only one difference, that is   call   Method accepts "a parameter list", and   apply   Method accepts an array containing multiple parameters.

/**
 * 
 * @param {*} ctx Function execution context this
 * @param {*} args  parameter list
 * @returns Result of function execution
 */
//  The only difference here is that you don't need... args to become an array  
Function.prototype.myApply = function (ctx, args) {
  if (!ctx) {
    ctx = typeof window !== 'undefined' ? window : global
  }

  ctx = Object(ctx)

  const fnName = Symbol()

  ctx[ fnName ] = this
  //  Expand the args parameter array into multiple parameters for function calls
  const result = ctx[ fnName ](...args)

  delete ctx[ fnName ]

  return result
}

//  test
let fn = function (name, sex) {
  console.log(this, name, sex)
}

fn.myApply('', ['Front fat head fish', 'boy'])
//  window   Front fat head fish   boy
fn.myApply({ name: 'Front fat head fish', sex: 'boy' }, ['Front fat head fish', 'boy'])
//  {   name:  ' Front fat head fish ',   sex:  ' boy'  }  Front fat head fish   boy

12. Two ways to implement trim method

「trim」   Method removes white space characters from both ends of a string. White space characters in this context are all white space characters   (space, tab, no break space, etc.) and all line terminator characters (such as LF, CR, etc.)

"Idea:" at first glance, the method that flashed through our mind is to delete the blank part and retain the non blank part, but we can also change our idea or extract the non blank part, regardless of the blank part. Next, let's write about the implementation of two trim methods

Space removal method (mode 1)

const trim = (str) => { 
  return str.replace(/^\s*|\s*$/g, '')
}

console.log(trim(' Front fat head fish')) //  Front fat head fish  
console.log(trim('Front fat head fish ')) //  Front fat head fish  
console.log(trim(' Front fat head fish ')) //  Front fat head fish  
console.log(trim(' front end variegated carp ')) //  front end   variegated carp

Character extraction method (mode 2)

const trim = (str) => { 
  return str.replace(/^\s*(.*?)\s*$/g, '$1') 
} 

console.log(trim(' Front fat head fish')) //  Front fat head fish  
console.log(trim('Front fat head fish ')) //  Front fat head fish  
console.log(trim(' Front fat head fish ')) //  Front fat head fish  
console.log(trim(' front end variegated carp ')) //  front end   variegated carp

13. Implement Promise.all

Promise.all() method receives the input of a promise's iterable type (Note: Array, Map and Set all belong to the iterable type of ES6), and returns only one promise instance,   The result of the resolve callbacks of all the input promises is an Array. The resolve callback of this promise is executed when the resolve callbacks of all the input promises end or there is no promise in the input iterable. Its reject callback execution is that as long as the reject callback of any input promise is executed or the illegal promise is input, it will be thrown immediately An error occurs, and reject is the first error message thrown.

The above is the description of Promise.all on MDN. At first glance, it's a little confused. Let's summarize the key points

  1. Promise.all receives an array, which can be promise instances or not

  2. Promise.all   Wait for all to complete (or the first to fail)

  3. The result of Promise.all execution is also a Promise

Promise.myAll = (promises) => {
  //  If condition 3 is met, a Promise is returned
  return new Promise((rs, rj) => {
    let count = 0
    let result = []
    const len = promises.length

    promises.forEach((p, i) => {
      //  If condition 1 is met, wrap the items in the array through Promise.resolve
      Promise.resolve(p).then((res) => {
        count += 1
        result[ i ] = res
        //  Meet condition 2   Wait until all are finished
        if (count === len) {
          rs(result)
        }
        //  Meet condition 2    Every failure is a failure
      }).catch(rj)
    })
  })
}

let p1 = Promise.resolve(1)
let p2 = 2
let p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 3)
})

let p4 = Promise.reject('Error ')

Promise.myAll([p1, p2, p3]).then((res) => {
  console.log(res); // [ 1, 2, 3 ]
});


Promise.myAll([ p1, p2, 3 ]).then((res) => {
  console.log(res) // [ 1, 2, 3 ]
}).catch((err) => {
  console.log('err', err)
})


Promise.myAll([ p1, p2, p4 ]).then((res) => {
  console.log(res)
}).catch((err) => {
  console.log('err', err) //  err   Error 
})

14. Implement Promise.race

「Promise.race(iterable)」   Method returns a promise. Once a promise in the iterator is resolved or rejected, the returned promise will be resolved or rejected.

Promise.myRace = (promises) => {
  //  Returns a new Promise
  return new Promise((rs, rj) => {
    promises.forEach((p) => {
      //  Wrap the items in promises to prevent non promises  . then error
      //  As long as any one is completed or rejected, the race is over
      Promise.resolve(p).then(rs).catch(rj)
    })
  })
}

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 1);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 2);
});

Promise.myRace([promise1, promise2]).then((value) => {
  //  Because promise2 is faster, print out 2
  console.log(value) // 2
});

Promise.myRace([promise1, promise2, 3]).then((value) => {
  //  3 is faster than the other two
  console.log(value) // 3
});


15. Object.create

「Object.create()」   Method to create a new object and use the existing object to provide the _proto_of the newly created object.

"Let's see how to use it first"

  1. Regular use

//  Object.create use
const person = {
  showName () {
    console.log(this.name)
  }
}
const me = Object.create(person)

me.name = 'Front fat head fish' 
me.showName() //  Front fat head fish

You can see that person exists as the prototype of the me instance with the showName method on it

image.png

  1. Create an object whose prototype is null

const emptyObj = Object.create(null)

console.log(emptyObj)

image.png

  1. The second propertiesObject parameter

Optional. You need to pass in an object whose property type refers to the second parameter of Object.defineProperties(). If the parameter is specified and not null   undefined, the self enumerable attribute of the incoming object (that is, the attribute defined by itself, rather than the enumeration attribute on its prototype chain) will add the specified attribute value and corresponding attribute descriptor to the newly created object.

let o = Object.create(Object.prototype, {
  //  foo becomes the data attribute of the created object
  foo: {
    writable:true, //  Can be modified
    configurable:true, //  Can configure
    enumerable: true, //  Can traverse
    value: "hello"
  },
  //  bar becomes the accessor property of the created object
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
})
//  Unable to modify
o.bar = 'Front fat head fish'

console.log(o.foo) // hello
console.log(o.bar) // 10
//  Traversal test
for (let key in o) {
  console.log(key, o[key]) // foo hello
}

"Code implementation"

const create = (prop, props) => {
  if (![ 'object', 'function' ].includes(typeof prop)) {
    throw new TypeError(`Object prototype may only be an Object or null: ${prop}`)
  }
  //  Create constructor
  const Ctor = function () {}
  //  Assignment prototype
  Ctor.prototype = prop
  //  Create instance
  const obj = new Ctor()
  //  The second parameter is supported
  if (props) {
    Object.defineProperties(obj, props)
  }
  //  Support empty prototypes
  if (prop === null) {
    obj.__proto__ = null
  }

  return obj
}

//  Test with the previous example
const person = {
  showName () {
    console.log(this.name)
  }
}
const me2 = create(person)

me2.name = 'Front fat head fish'
me2.showName() //  Front fat head fish

const emptyObj2 = create(null)
console.log(emptyObj2)

const props = {
  //  foo becomes the data attribute of the created object
  foo: {
    writable:true,
    configurable:true,
    value: "hello"
  },
  //  bar becomes the accessor property of the created object
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
}
let o2 = create(Object.prototype, props) //  Please see the screenshot below
//  Cannot modify
o2.bar = 'Front fat head fish'

console.log(o2.foo) // hello
console.log(o2.bar) // 10

image.png

16. Quick sort

const quickSort = (array) => {
  const length = array.length

  if (length <= 1) {
    return array
  }

  const midIndex = Math.floor(length / 2)
  const midValue = array.splice(midIndex, 1)[ 0 ]
  let leftArray = []
  let rightArray = []
  let index = 0

  while (index < length - 1) {
    const curValue = array[ index ]

    if (curValue <= midValue) {
      leftArray.push(curValue)
    } else {
      rightArray.push(curValue)
    }

    index++
  }

  return quickSort(leftArray).concat([ midValue ], quickSort(rightArray))
}

const arr = [ -10, 10, 1, 34, 5, 1 ]

console.log(quickSort(arr)) // [-10, 1, 1, 5, 10, 34]

17. Bubble sorting

/**
 * 1. Starting from the first element, compare two adjacent elements. The former will exchange positions
 * 2. At the end of each traversal, a maximum value can be found
 * 3. If there are unsorted elements, continue with 1
 * 
 */
const swap = (array, a, b) => [ array[ b ], array[ a ] ] = [ array[ a ], array[ b ] ]
const bubbleSort = (array) => {
  const length = array.length
  for (let i = 0; i < length - 1; i++) {
    for (let j = 0; j < length - 1 - i; j++) {
      if (array[ j ] > array[ j + 1 ]) {
        swap(array, j, j + 1)
      }
    }
  }

  return array
}

console.log(bubbleSort([ -1, 10, 10, 2 ])) //  [-1, 2, 10, 10]

18. Select sort

/**
 * 1. Take out the first element that is not sorted, traverse the part after the element and compare it. The first time is to take the first element
 * 2. If there is a smaller one, switch positions
 */

const swap = (array, a, b) => [ array[ b ], array[ a ] ] = [ array[ a ], array[ b ] ]
const selectSort = (array) => {
  const length = array.length

  for (let i = 0; i < length; i++) {
    let minIndex = i

    for (let j = i + 1; j < length; j++) {
      if (array[ j ] < array[ minIndex ]) {
        minIndex = j
      }
    }

    if (minIndex !== i) {
      swap(array, i, minIndex)
    }
  }

  return array
}

console.log(selectSort([ -1, 10, 10, 2 ]))  // [-1, 2, 10, 10]


19. Insert sort

//  Insert sort
/**
 * Remember how you play cards and you'll know how to implement insertion sorting
 * 1. First, there is an ordered sequence. It can be considered that the first element is an ordered sequence
 * 2. Take an element from the unordered sequence and find a suitable position in the ordered sequence. If the position is larger than the element, move it later,   Otherwise, keep looking forward
 */

const insertSort = (array) => {
  for (let i = 1, length = array.length; i < length; i++) {
    let j = i - 1
    const curValue = array[ i ]

    while (j >= 0 && array[ j ] > curValue) {
      array[ j + 1 ] = array[ j ]
      j--
    }

    array[ j + 1 ] = curValue
  }

  return array
}

console.log(insertSort([ -1, 10, 10, 2 ])) // [-1, 2, 10, 10]

20. setTimeout simulates setInterval

Description:   Use setTimeout to simulate the function of setInterval

"Idea:"   Of course, this is not a complete implementation. For example, after setInterval is executed, a digital id is obtained, which we will not simulate. The method of closing the timer is carried out by returning a function

const simulateSetInterval = (func, timeout) => {
  let timer = null
  const interval = () => {
    timer = setTimeout(() => {
      //  The real func function is executed after the timeout time
      func()
      //  At the same time, call interval itself again. Does it feel like setInterval
      interval()
    }, timeout)
  }
  //  Start execution  
  interval()
  //  Returns the function used to turn off the timer  
  return () => clearTimeout(timer)
}

const cancel = simulateSetInterval(() => {
  console.log(1)
}, 300)

setTimeout(() => {
  cancel()
  console.log('Turn off the timer after one second')
}, 1000)


You can see that 1 is printed three times, the timer is turned off at the 1000th millisecond, and 1 does not continue printing.

image.png

21. setInterval simulates setTimeout

Description:   Use setInterval simulation to realize the function of setTimeout

"Idea:"   The feature of setTimeout is to execute only once within the specified time. We just need to turn off the timer after executing callback within setInterval

const simulateSetTimeout = (fn, timeout) => {
  let timer = null

  timer = setInterval(() => {
    //  Turn off the timer and ensure that it is executed only once fn, which achieves the effect of setTimeout
    clearInterval(timer)
    fn()
  }, timeout)
  //  Returns the method used to turn off the timer
  return () => clearInterval(timer)
}

const cancel = simulateSetTimeout(() => {
  console.log(1)
}, 1000)

//  Print out 1 in a second

22. Four methods of array de duplication

It is often encountered in business and interview. De duplication of arrays is a necessary basic skill

Using Set (mode 1)

const uniqueArray1 = (array) => {
  return [ ...new Set(array) ]
}

//  test
let testArray = [ 1, 2, 3, 1, 2, 3, 4 ]
console.log(uniqueArray1(testArray)) // [1, 2, 3, 4]

indexOf de duplication (mode 2)

const uniqueArray2 = (array) => {
  let result = []

  array.forEach((it, i) => {
    if (result.indexOf(it) === -1) {
      result.push(it)
    }
  })

  return result
}

//  test
console.log(uniqueArray2(testArray)) // [1, 2, 3, 4]

indexOf de duplication (mode 3)

const uniqueArray3 = (array) => {
  return array.filter((it, i) => array.indexOf(it) === i)
}

//  test
console.log(uniqueArray3(testArray)) // [1, 2, 3, 4]

Array.from de duplication

const uniqueArray4 = (array) => {
  return Array.from(new Set(array))
}

//  test
console.log(uniqueArray4(testArray)) // [1, 2, 3, 4]

23. Mobile phone number 3-3-4 Division

The mobile phone number is segmented according to, for example, 183-7980-2267

//  Suitable for pure 11 bit mobile phones
const splitMobile = (mobile, format = '-') => {
  return String(mobile).replace(/(?=(\d{4})+$)/g, format)
}
//  Suitable for segmentation within 11 bits
const splitMobile2 = (mobile, format = '-') => {
  return String(mobile).replace(/(?<=(\d{3}))/, format).replace(/(?<=([\d\-]{8}))/, format)
}

console.log(splitMobile(18379802267)) // 183-7980-2267
console.log(splitMobile2(18379876545)) // 183-7987-6545


24. Thousands of formatted numbers

Change 123456789 to 123456789 and support decimals

//  Amount to thousands
const formatPrice = (number) => {
  number = '' + number

  const [ integer, decimal = '' ] = number.split('.')

  return integer.replace(/\B(?=(\d{3})+$)/g, ',') + (decimal ? '.' + decimal : '')
}

console.log(formatPrice(123456789.3343)) // 123,456,789.3343

25. Binary search

//  seven hundred and four   Binary search
/**
 * 
Given an n-element ordered (ascending) integer array nums and a target value target  , Write a function to search the target in nums. If the target value exists, return the subscript. Otherwise, return the subscript  - 1. 


Example   1:

Input:   nums  =  [-1,0,3,5,9,12],   target  =  nine
 Output:   four
 Explanation:   nine   Appear in   nums   Middle and subscript   four
 Example   2:

Input:   nums  =  [-1,0,3,5,9,12],   target  =  two
 Output:  - one
 Explanation:   two   non-existent   nums   So return  - one
 

Tips:

You can assume that all elements in nums are not repeated.
n Will be in   Between [1, 10000].
nums Each element of will be   [- 9999, 9999].
 */


const search = (nums, target) => {
  let i = 0
  let j = nums.length - 1
  let midIndex = 0

  while (i <= j) {
    midIndex = Math.floor((i + j) / 2)
    const midValue = nums[ midIndex ]

    if (midValue === target) {
      return midIndex
    } else if (midValue < target) {
      i = midIndex + 1
    } else {
      j = midIndex - 1
    }
  }

  return -1
}

console.log(search([-1,0,3,5,9,12], 9)) // 4

26. Two methods of version comparison

It is estimated that the client will encounter more cases of comparing version numbers, but fat head fish has also encountered this requirement in the business

"Detailed rules"

I'll give you two version numbers version1 and version2 ,Please compare them.

The version number consists of one or more revision numbers, and each revision number consists of one '.' connect. Each revision number is Multi digit number Composition, may contain Leading zero . Each version number contains at least one character. The revision number is numbered from left to right, and the subscript is from 0 Initially, the leftmost revision number subscript is 0 ,The next revision number subscript is 1 ,and so on. For example, 2.5.33 and 0.1 Are valid version numbers.

When comparing version numbers, compare their revision numbers from left to right. When comparing revision numbers, just compare Ignore any integer values after leading zeros . That is, the revision number 1 And revision number 001 equal . If the revision number at a subscript is not specified in the version number, the revision number is regarded as 0 . For example, version 1.0 Less than version 1.1 ,Because they are subscript 0 The revision number of is the same, and the subscript is 1 The revision numbers are 0 and 1 ,0 < 1 . 

The return rules are as follows:

If version1 > version2 return 1,
If version1 < version2 return -1,
In addition, return 0. 


"Source code implementation"

//  Compare version number

const compareVersion = function(version1, version2) {
  version1 = version1.split('.')
  version2 = version2.split('.')

  const len1 = version1.length
  const len2 = version2.length
  let maxLen = len1
  const fillZero = (array, len) => {
    while (len--) {
      array.push(0)
    }
  }

  if (len1 < len2) {
    fillZero(version1, len2 - len1)
    maxLen = len2
  } else if (len1 > len2) {
    fillZero(version2, len1 - len2)
    maxLen = len1
  }

  for (let i = 0; i < maxLen; i++) {
    const a = parseInt(version1[i])
    const b = parseInt(version2[i])
    if ( a === b) {
      // i++
    } else if (a > b) {
      return 1
    } else {
      return -1
    }
  }

  return 0
}

//  You can also not fill in zero
const compareVersion = function(version1, version2) {
  version1 = version1.split('.')
  version2 = version2.split('.')

  const maxLen = Math.max(version1.length, version2.length)

  for (let i = 0; i < maxLen; i++) {
    const a = parseInt(version1[i]??0)
    const b = parseInt(version2[i]??0)
    if ( a === b) {
      // i++
    } else if (a > b) {
      return 1
    } else {
      return -1
    }
  }

  return 0
}

console.log(compareVersion('1.0', '1.0.0'))

//  Extension 1 compares and sorts multiple version numbers

const compareMoreVersion = (versions) => {
  return versions.sort((a, b) => compareVersion(a, b))
}

console.log(compareMoreVersion(['1.0', '3.1', '1.01']))


27. Parse url parameters

Get the value of the search parameter on the url according to name

const getQueryByName = (name) => {
  const queryNameRegex = new RegExp(`[?&]${name}=([^&]*)(&|$)`)
  const queryNameMatch = window.location.search.match(queryNameRegex)
  //  Generally, it will be decoded through the decodeURIComponent
  return queryNameMatch ? decodeURIComponent(queryNameMatch[1]) : ''
}

// https://www.baidu.com/?name=%E5%89%8D%E7%AB%AF%E8%83%96%E5%A4%B4%E9%B1%BC&sex=boy

console.log(getQueryByName('name'), getQueryByName('sex')) //  Front fat head fish   boy


28. Implement the general function to obtain js data type

Implement a general function to judge the data type

const getType = (s) => {
  const r = Object.prototype.toString.call(s)

  return r.replace(/\[object (.*?)\]/, '$1').toLowerCase()
}

//  test
console.log(getType()) // undefined
console.log(getType(null)) // null
console.log(getType(1)) // number
console.log(getType('Front fat head fish')) // string
console.log(getType(true)) // boolean
console.log(getType(Symbol('Front fat head fish'))) // symbol
console.log(getType({})) // object
console.log(getType([])) // array

29. Convert string to hump

According to the following rules, the corresponding string is changed into hump writing

1. foo Bar => fooBar

2. foo-bar---- => fooBar

3. foo_bar__ => fooBar

const camelCase = (string) => {
  const camelCaseRegex = /[-_\s]+(.)?/g

  return string.replace(camelCaseRegex, (match, char) => {
    return char ? char.toUpperCase() : ''
  })
}

//  test
console.log(camelCase('foo Bar')) // fooBar 
console.log(camelCase('foo-bar--')) // fooBar 
console.log(camelCase('foo_bar__')) // fooBar

30. Implement reduce

reduce   Method executes a "reducer" function (executed in ascending order) provided by you for each element in the array, and summarizes the results into a single return value mdn[1]

This function is a little more complicated. Let's use an example to see how it is used.

const sum = [1, 2, 3, 4].reduce((prev, cur) => {
  return prev + cur;
})

console.log(sum) // 10

//  Initial settings
prev = initialValue = 1, cur = 2

//  First iteration
prev = (1 + 2) =  3, cur = 3

//  Second iteration
prev = (3 + 3) =  6, cur = 4

//  Third iteration
prev = (6 + 4) =  10, cur = undefined (sign out)

code implementation

Click to view the source code implementation [2]

Array.prototype.reduce2 = function (callback, initValue) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  let pre = initValue
  let i = 0
  const length = this.length
  //  When no initial value is passed, take the first as the initial value   
  if (typeof pre === 'undefined') {
    pre = this[0]
    i = 1
  }

  while (i < length) {
    if (i in this) {
      pre = callback(pre, this[ i ], i, this)
    }
    i++
  }

  return pre
}

Copy code

Test one

const sum = [1, 2, 3, 4].reduce2((prev, cur) => {
  return prev + cur;
})

console.log(sum) // 10

Posted by radstorm on Thu, 14 Oct 2021 14:48:38 -0700