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
one Three ways to implement instanceOf
Recursive implementation (mode 1)
Traversal implementation (mode 2)
Traversal implementation (mode 3)
2. Implement json.stringify (super detailed)
four Three ways to flatten multidimensional arrays
Recursive implementation (mode 1)
Traversal implementation (mode 2)
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
12. Two ways to implement trim method
Character extraction method (mode 2)
20. setTimeout simulates setInterval
21. setInterval simulates setTimeout
22. Four methods of array de duplication
indexOf de duplication (mode 2)
indexOf de duplication (mode 3)
23. Mobile phone number 3-3-4 Division
24. Thousands of formatted numbers
26. Two methods of version comparison
28. Implement the general function to obtain js data type
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:
-
Create an empty simple JavaScript object (i.e. {});
-
Add attributes to the newly created object in step 1 「「proto」」 , Link this property to the prototype object of the constructor
-
Take the newly created object in step 1 as the context of this and execute the function;
-
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
-
How can I ensure that only two tasks are being executed at the same time?
-
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:
-
LRUCache(int capacity) takes a positive integer as the capacity Capacity initialize LRU cache
-
int get(int key) If the keyword key exists in the cache, the value of the keyword is returned; otherwise, - 1 is returned.
-
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"?
-
Both reading and writing are using data
-
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
-
Promise.all receives an array, which can be promise instances or not
-
Promise.all Wait for all to complete (or the first to fail)
-
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"
-
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
-
Create an object whose prototype is null
const emptyObj = Object.create(null) console.log(emptyObj)
image.png
-
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