Have you mastered these common handwritten questions?

Have you mastered these common handwritten questions?

Preface

Handwritten code can test the interviewer's coding ability, so this kind of questions is often favored by interviewers. If you don't prepare in advance, there are often cases of missing 10,000. Now let's summarize the handwritten questions that are often asked.

1. Implement the instanceof operator

The instanceof operator is used to detect whether the prototype property of the constructor appears on the prototype chain of an instance object. The left side of the operator is the instance object, and the right side is the constructor.

const iInstanceof = function ( left, right ) { //If it is the original value, always return false if (left === null || typeof left !== 'object' ) return false ; let proto = Object .getPrototypeOf(left ); while ( true ) { if (proto === null ) return false ; if (proto === right.prototype) return true ; proto = Object .getPrototypeOf(proto); } }; Copy code

This is a common implementation, we can also use isPrototypeOf to achieve

const iInstanceof = function ( left, right ) { return right.prototype.isPrototypeOf(left) }; Copy code

2. Implement the new operator

The execution process of new is as follows:

  1. Create a new object;

  2. The [[prototype]] property of the new object points to the prototype property of the constructor;

  3. This inside the constructor points to the new object;

  4. Execute the constructor;

  5. If the constructor returns a non-empty object, return that object; otherwise, return the new object just created;

const iNew = function ( fn, ...rest ) { let instance = Object .create(fn.prototype); let res = fn.apply(instance, rest); return res !== null && ( typeof res === 'object' || typeof res === 'function' )? res: instance; }; Copy code

3. Implement the Object.assign method

The shallow copy method only copies the enumerable attributes of the source object itself (including the attributes with Symbol as the key) to the target object

const iAssign = function ( target, ...source ) { if (target === null || target === undefined ) { throw new TypeError ( 'Cannot convert undefined or null to object' ); } let res = Object (target); for ( let i = 0 ; i <source.length; i++) { let src = source[i]; let keys = [...Object.keys(src), ...Object .getOwnPropertySymbols(src)]; for ( const k of keys) { if (src.propertyIsEnumerable(k)) { res[k] = src[k]; } } } return res; }; //Keep the data attributes of assign consistent Object .defineProperty( Object , 'iAssign' , { value : iAssign, configurable : true , enumerable : false , writable : true }); Copy code

4. The bind method

Change the value of this in the function and pass parameters to return a function

const iBind = function ( thisArg, ...args ) { const originFunc = this ; const boundFunc = function ( ...args1 ) { //solve the problem of returning function new after bind return originFunc.apply( new .target? this : thisArg, args.concat(args1)); }; if (originFunc.prototype) { boundFunc.prototype = originFunc.prototype; } //Solve the problem of length and name properties const desc = Object .getOwnPropertyDescriptors(originFunc); Object .defineProperties(boundFunc, { length : Object .assign(desc.length, { value : desc.length.value <args.length? 0 : (desc.length.value-args.length) }), name : Object .assign(desc.name, { value : `bound ${desc.name.value} ` }) }); return boundFunc; }; //Keep the data properties of bind consistent Object .defineProperty( Function .prototype, 'iBind' , { value : iBind, enumerable : false , configurable : true , writable : true }); Copy code

5. call method

Call the function with the specified this value and parameters

const iCall = function ( thisArg, ...args ) { thisArg = (thisArg === undefined || thisArg === null )? window : Object (thisArg); let fn = Symbol ( 'fn' ); thisArg[fn] = this ; let res = thisArg[fn](...args); delete thisArg[fn]; return res; }; //Keep the data properties of call consistent Object .defineProperty( Function .prototype, 'iCall' , { value : iCall, configurable : true , enumerable : false , writable : true }); Copy code

6. Function Currying

Convert a multi-parameter function into multiple nested single-parameter functions.

const curry = function ( targetFn ) { return function fn ( ...rest ) { if (targetFn.length === rest.length) { return targetFn.apply( null , rest); } else { return fn.bind( null , ...rest); } }; }; //Usage function add ( a, b, c, d ) { return a + b + c + d; } Console .log ( 'curried:' , Curry (the Add) ( . 1 ) ( 2 ) ( . 3 ) ( . 4 )); //currying: 10 copy the code

7. Function debounce method

const debounce = function ( func, wait = 0 , options = { leading: true , context: null } ) { let timer; let res; const _debounce = function ( ...args ) { options.context || (options.context = this ); if (timer) { clearTimeout (timer); } if (options.leading && !timer) { timer = setTimeout ( () => { timer = null ; }, wait); res = func.apply(options.context, args); } else { timer = setTimeout ( () => { res = func.apply(options.context, args); timer = null ; }, wait); } return res; }; _debounce.cancel = function () { clearTimeout (timer); timer = null ; }; return _debounce; }; Copy code

Leading indicates whether to execute immediately when entering. If the event is triggered within the wait time, the previous timer will be cleared and a timer for the wait time will be reset.

8. Function throttling method

const throttle = function ( func, wait = 0 , options = { leading: true , trailing: false , context: null } ) { let timer; let res; let previous = 0 ; const _throttle = function ( ...args ) { options.context || (options.context = this ); let now = Date .now(); if (!previous && !options.leading) previous = now; if (now-previous >= wait) { if (timer) { clearTimeout (timer); timer = null ; } res = func.apply(options.context, args); previous = now; } else if (!timer && options.trailing) { timer = setTimeout ( () => { res = func.apply(options.context, args); previous = 0 ; timer = null ; }, wait); } return res; }; _throttle.cancel = function () { previous = 0 ; clearTimeout (timer); timer = null ; }; return _throttle; }; Copy code

Function throttling is like dripping water from a faucet. It will be triggered once at the interval of wait time. Compared with the function anti-shake, a trailing option has been added to indicate whether to trigger an additional time at the end.

9. Event publish and subscribe (EventBus event bus)

class EventBus { constructor () { Object .defineProperty( this , 'handles' , { value : {} }); } on (eventName, listener) { if ( typeof listener !== 'function' ) { console .error( 'Please pass in the correct callback function' ); return ; } if (! this .handles[eventName]) { this .handles[eventName] = []; } this .handles[eventName].push(listener); } emit (eventName, ...args) { let listeners = this .handles[eventName]; if (!listeners) { console .warn( ` ${eventName} event does not exist` ); return ; } for ( const listener of listeners) { listener(...args); } } off (eventName, listener) { if (!listener) { delete this .handles[eventName]; return ; } let listeners = this .handles[eventName]; if (listeners && listeners.length) { let index = listeners.findIndex( item => item === listener); if (~index) { listeners.splice(index, 1 ); } } } once (eventName, listener) { if ( typeof listener !== 'function' ) { console .error( 'Please pass in the correct callback function' ); return ; } const onceListener = ( ...args ) => { listener(...args); this .off(eventName, onceListener); }; this .on(eventName, onceListener); } } Copy code

Used when custom events, pay attention to some boundary checks

10. Deep copy

const deepClone = function ( source ) { if (source === null || typeof source !== 'object' ) { return source; } let res = Array .isArray(source)? []: {}; for ( const key in source) { if ( Object .prototype.hasOwnProperty.call(source, key)) { res[key] = deepClone(source[key]); } } return res; }; Copy code

This is a very basic version of deep copy. There are some problems in it, such as circular references, such as recursive stack explosion. I will write an article to discuss it later.

11. Implement ES6 Class

Use the constructor to simulate, the class can only be created with new and cannot be called directly. Also pay attention to the attribute descriptor

const checkNew = function ( instance, con ) { if (!(instance instanceof con)) { throw new TypeError ( `Class constructor ${con.name} cannot be invoked without'new'` ); } }; const defineProperties = function ( target, obj ) { for ( const key in obj) { Object .defineProperty(target, key, { configurable : true , enumerable : false , value : obj[key], writable : true }); } }; const createClass = function ( con, proto, staticAttr ) { proto && defineProperties(con.prototype, proto); staticAttr && defineProperties(con, staticAttr); return con; }; //Usage function Person ( name ) { checkNew( this , Person); this .name = name; } var PersonClass = createClass(Person, { getName : function () { return this .name; } }, { getAge : function () {} }); Copy code

12. Implement ES6 inheritance

ES6 internally uses parasitic combined inheritance. 1. Object.create is used to inherit the prototype, and the second parameter is passed to point the parent class constructor to itself, and at the same time set the data attribute descriptor.

Then use Object.setPrototypeOf to inherit static properties and static methods.

const inherit = function ( subType, superType ) { //Type judgment on superType if ( typeof superType !== "function" && superType !== null ) { throw new TypeError ( "Super expression must either be null or a function" ); } subType.prototype = Object .create(superType && superType.prototype, { constructor : { configurable : true , enumerable : false , value : subType, writable : true } }); //Inherit the static method superType && Object .setPrototypeOf(subType, superType); }; //Usage function superType ( name ) { this .name = name; } superType.staticFn = function () { console .log( 'staticFn' ); } superType.prototype.getName = function () { console .log( 'name: ' + this .name); } function subType ( name, age ) { superType.call( this , name); this .age = age; } inherit(subType, superType); //Prototype method must be added to subType after inheritance, otherwise it will be overwritten subType.prototype.getAge = function () { console .log( 'age: ' + this .age); } let subTypeInstance = new subType( 'Twittytop' , 29 ); subType.staticFn(); subTypeInstance.getName(); subTypeInstance.getAge(); Copy code

13. Image lazy loading

//Get window height function getWindowHeight () { return window .innerHeight || document .documentElement.clientHeight || document .body.clientHeight; } function getTop ( e ) { let t = e.offsetTop; while (e = e.offsetParent) { t += e.offsetTop; } return t; } const delta = 30 ; let count = 0 ; function lazyLoad ( imgs ) { const winH = getWindowHeight(); const s = document .documentElement.scrollTop || document .body.scrollTop; for ( let i = 0 , l = imgs. length; i <l; i++) { if (winH + s + delta> getTop(imgs[i]) && getTop(imgs[i]) + imgs[i].offsetHeight + delta> s) { if (!imgs[ i].src) { imgs[i].src = imgs[i].getAttribute( 'data-src' ); count++; } if (count === l) { window .removeEventListener( 'scroll' , handler); window .removeEventListener( 'load' , handler); } } } } const imgs = document .querySelectorAll( 'img' ); const handler = function () { lazyLoad(imgs); }; window .addEventListener ( 'Scroll' , Handler); window .addEventListener ( 'Load' , Handler); duplicated code

Of course you can also use the getBoundingClientRect method:

//Get window height function getWindowHeight () { return window .innerHeight || document .documentElement.clientHeight || document .body.clientHeight; } const delta = 30 ; let count = 0 ; function lazyLoad ( imgs ) { const winH = getWindowHeight(); for ( let i = 0 , l = imgs.length; i <l; i++) { const rect = imgs[i] .getBoundingClientRect(); if (winH + delta> rect.top && rect.bottom> -delta) { if (!imgs[i].src) { imgs[i].src = imgs[i].getAttribute( 'data-src' ); count++; } if (count === l) { window .removeEventListener( 'scroll' , handler); window .removeEventListener( 'load' , handler); } } } } const imgs = document .querySelectorAll( 'img' ); const handler = function () { lazyLoad(imgs); }; window .addEventListener ( 'Scroll' , Handler); window .addEventListener ( 'Load' , Handler); duplicated code

Of course you can also use the IntersectionObserver method:

function lazyLoad ( imgs ) { let options = { rootMargin : '30px' }; let count = 0 ; let observer = new IntersectionObserver( entries => { entries.forEach( entry => { if (entry.intersectionRatio> 0 ) { entry.target.src = entry.target.getAttribute( 'data-src' ); count++; observer.unobserve(entry.target); if (count === imgs.length) { window .removeEventListener( 'load' , handler); } } }); }, options); for ( let i = 0 ; i <imgs.length; i++) { observer.observe(imgs[i]); } } const imgs = document .querySelectorAll( 'img' ); const handler = function () { lazyLoad(imgs); }; window .addEventListener ( 'Load' , Handler); duplicated code

14. Implement Object.is method

The difference between Object.is() and === is that Object.is(0, -0) returns false and Object.is(NaN, NaN) returns true.

const iIs = function ( x, y ) { if (x === y) { return x !== 0 || 1/x === 1/y; } else { return x !== x && y !== y; } } //Keep the data properties of is consistent Object .defineProperty( Function .prototype, 'iIs' , { value : iIs, configurable : true , enumerable : false , writable : true }); Copy code

15. Time slice

Cut a long task into multiple small tasks. The usage scenario is to prevent a task from executing too long and blocking the thread.

function ts ( gen ) { if ( typeof gen === 'function' ) gen = gen(); if (!gen || typeof gen.next !== 'function' ) return ; ( function next () { const start = performance.now(); let res = null ; do { res = gen.next(); } while (!res.done && performance.now()-start < 25 ) if (res.done) return ; setTimeout (next); })(); } //Usage ts( function * () { const start = performance.now(); while (performance.now()-start < 1000 ) { yield ; } console .log( 'done!' ); }); Copy code

16. CO (Coroutine) Implementation

function co ( gen ) { return new Promise ( function ( resolve, reject ) { if ( typeof gen === 'function' ) gen = gen(); if (!gen || typeof gen.next !== 'function' ) return resolve(gen); onFulfilled(); function onFulfilled ( res ) { let ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } function onRejected ( err ) { let ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next ( ret ) { if (ret.done) return resolve(ret.value); let val = Promise .resolve(ret.value); return val.then(onFulfilled, onRejected); } }); } //usage co( function * () { let res1 = yield Promise .resolve( 1 ); console .log(res1); let res2 = yield Promise .resolve( 2 ); console .log(res2); let res3 = yield Promise .resolve( 3 ); console .log(res3); return res1 + res2 + res3; }).then( value => { console .log( 'add: ' + value); }, function ( err ) { console .error(err.stack); }); Copy code

co accepts a generator function, suspends execution when it encounters yield, and hands over control. When other programs are executed, it returns the result and continues execution from where it was interrupted, and so on, until all tasks are executed. , And finally return a Promise and use the return value of the generator function as the resolve value.

When we replace * with async and yield with await, it is the same as the async/await we often use, so async/await is syntactic sugar for generator functions.

17. Singleton mode

const getSingleton = function ( fn ) { let instance; return function () { return instance || (instance = new (fn.bind( this , ...arguments))); }; }; //Usage function Person ( name ) { this .name = name; } the let Singleton = getSingleton (the Person); the let instance1 = new new Singleton ( 'Twittop1' ); the let instance2 = new new Singleton ( 'Twittop2' ); Console .log (instance1 === instance2); //to true copy the code

Of course, you can also use ES6 Proxy to achieve:

const getSingleton = function ( fn ) { let instance; const handler = { construct (target, argumentsList) { return instance || (instance = Reflect .construct(target, argumentsList)); } } return new Proxy (fn, handler); }; //Usage function Person ( name ) { this .name = name; } the let Singleton = getSingleton (the Person); the let instance1 = new new Singleton ( 'Twittop1' ); the let instance2 = new new Singleton ( 'Twittop2' ); Console .log (instance1 === instance2); //to true copy the code

18. Promise

function isFunction ( obj ) { return typeof obj === 'function' ; } function isObject ( obj ) { return !!(obj && typeof obj === 'object' ); } function isPromise ( obj ) { return obj instanceof Promise ; } function isThenable ( obj ) { return (isFunction(obj) || isObject(obj)) && 'then' in obj; } function transition ( promise, state, result ) { //Once it becomes a non-pending state, it is irreversible if (promise.state !== 'pending' ) return ; promise.state = state; promise.result = result; setTimeout ( () => promise.callbacks.forEach( callback => handleCallback(callback, state, result))); } function resolvePromise ( promise, result, resolve, reject ) { if (promise === result) { return reject( new TypeError ( 'Chaining cycle detected for promise' )); } if (isPromise(result)) { return result.then(resolve, reject); } if (isThenable(result)) { try { let then = result.then; if (isFunction(then)) { return new Promise (then.bind(result)).then(resolve, reject); } } catch (error) { return reject(error); } } resolve(result); } function handleCallback ( callback, state, result ) { let {onFulfilled, onRejected, resolve, reject} = callback; try { if (state === 'fulfilled' ) { isFunction(onFulfilled)? resolve(onFulfilled(result)): resolve(result); } else if (state === 'rejected' ) { isFunction(onRejected)? resolve(onRejected(result)): reject(result); } } catch (e) { reject(e); } } class Promise { constructor ( executor ) { this .state = 'pending' ; this .result = undefined ; this .callbacks = []; let onFulfilled = value => transition( this , 'fulfilled' , value); let onRejected = reason => transition( this , 'rejected' , reason); //ensure that resolve or reject is only called once let flag = false; let resolve = value => { if (flag) return ; flag = true ; resolvePromise( this , value, onFulfilled, onRejected); }; let reject = reason => { if (flag) return ; flag = true ; onRejected(reason); }; try { executor(resolve, reject); } catch (e) { reject(e); } } then (onFulfilled, onRejected) { return new Promise ( ( resolve, reject ) => { let callback = {onFulfilled, onRejected, resolve, reject }; if ( this .state === 'pending' ) { this .callbacks.push(callback); } else { setTimeout ( () => { handleCallback(callback, this .state, this .result); }); } }); } catch (onRejected) { this .then( undefined , onRejected); } //Whether it succeeds or fails, it will be executed. Generally, the status of the previous promise will be passed. Only when onFinally throws an error (show throw or reject) will it return a rejected promise finally (onFinally) { return this .then( val => Promise .resolve(onFinally()).then( () => val), rea => Promise .resolve(onFinally()).then( () => { throw rea; }) ); } static resolve (value) { if (isPromise(value)) return value; return new Promise ( ( resolve, reject ) => resolve(value)); } static reject (reason) { return new Promise ( ( resolve, reject ) => reject(reason)); } //When all promises return fulfilled, it will return a fulfilled promise, which contains an array of corresponding results, otherwise as long as a promise returns rejected, it will return a rejected promise, which contains the first rejected The error message thrown by the promise static all (iterable) { return new Promise ( ( resolve, reject ) => { let count = 0 ; let arr = []; for ( let i = 0 , l = iterable.length; i <l; i ++) { iterable[i].then( val => { count++; arr[i] = val; if (count === l) { reresolve(arr); } }, reject); } }); } //As long as a promise returns fulfilled or rejected, it will return a fulfilled or rejected promise static race (iterable) { return new Promise ( ( resolve, reject ) => { for ( const p of iterable) { p.then(resolve, reject); } }); } //When all promises are fulfilled or rejected, return an array containing the corresponding results static allSettled (iterable) { return new Promise ( ( resolve, reject ) => { let count = 0 ; let arr = []; function handle ( state, index, result ) { arr[index] = { status : state, [state === 'fulfilled' ? 'value' : 'reason' ]: result }; count++; if (count === iterable.length) { resolve(arr); } } for ( let i = 0 , l = iterable.length; i <l; i ++) { iterable[i].then( val => handle ( 'fulfilled' , i, val), rea => handle ( 'rejected' , i, rea)); } }); } //As long as a promise succeeds, it will return a successful promise, otherwise it will return a failure of an AggregateError type instance. promise static any (iterable) { return new Promise ( ( resolve, reject ) => { let count = 0 ; let arr = []; for ( let i = 0 , l = iterable.length; i <l; i ++) { iterable[i].then(resolve, rea => { count++; arr[i] = rea; if (count === l) { reject( new AggregateError(arr)); } }); } }); } } Copy code

Promise has three states: pending, fulfilled, and rejected. Pending is the initial state. Once it is settled into fulfilled or rejected state, it is irreversible. And once the resolve or reject is executed, the subsequent resolve or reject will not take effect. The callback function passed in by then may be delayed in execution, so it needs to be placed in the callbacks array and taken out and executed when the state changes.

At last

Some code may need to be digested to understand thoroughly (except for the big guys). The author also spent several weeks, referencing a lot of information, and verifying the code to complete this article. If it can help you a little, it will It is my greatest relief. If you think you can learn something, please move your cute little finger to let more people see it. If there are errors or questions, please exchange and discuss.

Reference

juejin.cn/post/684490...

github.com/berwin/time...

CO module

100 lines of code to implement the Promises/A+ specification