preface
Written code is a great test of the interviewer’s coding skills, so this kind of question is often preferred by the interviewer. If you have not prepared in advance, you will often miss the whole thing. Now let’s summarize some of the handwritten questions that are often asked.
1. Implement the instanceof operator
The instanceof operator, to the left of the instance object and the right of the constructor, is used to check whether the constructor’s prototype property appears on the prototype chain of an instance object.
const iInstanceof = function (left, right) {
// Always return false if the value is raw
if (left === null || typeofleft ! = ='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 the code
This is a common implementation, but we can also use isPrototypeOf
const iInstanceof = function (left, right) {
return right.prototype.isPrototypeOf(left)
};
Copy the code
2. Implement the new operator
The new execution process is as follows:
-
Create a new object;
-
The [[prototype]] property of the new object points to the constructor’s Prototype property;
-
The this inside the constructor points to the new object;
-
Execute the constructor;
-
If the constructor returns a non-empty object, that object is returned; Otherwise, return the newly created object;
const iNew = function (fn, ... rest) {
let instance = Object.create(fn.prototype);
let res = fn.apply(instance, rest);
returnres ! = =null && (typeof res === 'object' || typeof res === 'function')? res : instance; };Copy the code
3. Implement the Object.assign method
The shallow copy method copies only the enumerable properties of the source object itself (including properties with Symbol keys) 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;
};
// Assign the same data attributes
Object.defineProperty(Object.'iAssign', {
value: iAssign,
configurable: true.enumerable: false.writable: true
});
Copy the code
4. The bind method
Return a function by changing the value of this in the function and passing the parameter
const iBind = function (thisArg, ... args) {
const originFunc = this;
const boundFunc = function (. args1) {
// Resolve the problem of bind returning new
return originFunc.apply(new.target ? this : thisArg, args.concat(args1));
};
if (originFunc.prototype) {
boundFunc.prototype = originFunc.prototype;
}
// Solve the length and name attributes problem
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 bind's data attributes consistent
Object.defineProperty(Function.prototype, 'iBind', {
value: iBind,
enumerable: false.configurable: true.writable: true
});
Copy the code
5. Call method
Calls the function with the specified this value and arguments
const iCall = function (thisArg, ... args) {
thisArg = (thisArg === undefined || thisArg === null)?window : Object(thisArg);
let fn = Symbol('fn');
thisArg[fn] = this;
letres = thisArg[fn](... args);delete thisArg[fn];
return res;
};
// Keep call data attributes consistent
Object.defineProperty(Function.prototype, 'iCall', {
value: iCall,
configurable: true.enumerable: false.writable: true
});
Copy the code
6. Coriolization of functions
Convert a multi-parameter function to 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('Currification:', curry(add)(1) (2) (3) (4));
// Currification: 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 the code
Leading specifies whether to execute immediately upon entering the system. If an event is triggered within the wait time, the system clears the previous timer and sets another wait time timer.
8. Function throttle 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 the code
Function throttling is like a tap drain. The trailing option is used to indicate whether an extra wait is triggered at the end of the function.
9. Event Publishing subscription (EventBus)
class EventBus {
constructor () {
Object.defineProperty(this.'handles', {
value: {}}); } on (eventName, listener) {if (typeoflistener ! = ='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}The event does not exist);
return;
}
for (const listener oflisteners) { 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 (typeoflistener ! = ='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 the code
For custom events, note some boundary checks
10. Deep copy
const deepClone = function (source) {
if (source === null || typeofsource ! = ='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 the code
This is a very basic version of deep copy, and it has some problems, such as circular references, such as recursive stack bursts, which I will discuss in a later article.
11. Implement ES6 classes
Using the constructor simulation, classes can only be created with new and cannot be called directly. Note also the property descriptors
const checkNew = function (instance, con) {
if(! (instanceinstanceof 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 the code
12. Implement ES6 inheritance
ES6 uses parasitic combinatorial inheritance internally, first inheriting the prototype with Object.create and passing a second argument to point the parent constructor to itself, while setting the data property descriptor.
It then inherits static properties and methods with Object.setProtoTypeof.
const inherit = function (subType, superType) {
// Determine the type of superType
if (typeofsuperType ! = ="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 static methods
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 methods must be added to subType after inheritance, otherwise they 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 the code
13. Lazy loading of images
// 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);
Copy the code
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);
Copy the code
Of course, you can also use IntersectionObserver:
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);
Copy the code
14. Implement object.is
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) {
returnx ! = =0 || 1 / x === 1 / y;
} else {
returnx ! == x && y ! == y; }}// Keep the data attributes of is consistent
Object.defineProperty(Function.prototype, 'iIs', {
value: iIs,
configurable: true.enumerable: false.writable: true
});
Copy the code
15. Time slice
To break a long task into smaller tasks, the scenario is to prevent one task from taking too long and blocking the thread
function ts (gen) {
if (typeof gen === 'function') gen = gen();
if(! gen ||typeofgen.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 the code
16. CO (coroutine) implementation
function co (gen) {
return new Promise(function (resolve, reject) {
if (typeof gen === 'function') gen = gen();
if(! gen ||typeofgen.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);
returnval.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 the code
Co takes a generator function, suspends execution at yield, surrendering control, returns the result when the rest of the program is finished, and continues execution from where it left off, and so on until all tasks have been completed. Finally, return a Promise with the return value of the generator function as the resolve value.
We replace * with async, and yield with await is the same as async/await we often use, so async/await is the syntactic sugar of 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;
}
let singleton = getSingleton(Person);
let instance1 = new singleton('Twittop1');
let instance2 = new singleton('Twittop2');
console.log(instance1 === instance2); // true
Copy the code
Of course, you can also use ES6 Proxy implementation:
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;
}
let singleton = getSingleton(Person);
let instance1 = new singleton('Twittop1');
let instance2 = new singleton('Twittop2');
console.log(instance1 === instance2); // 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 is in 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 called only 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);
}
// The state of the previous Promise is passed, and a Rejected Promise is returned only if onFinally throws an error (throw or reject)
finally (onFinally) {
return this.then(
val= > Promise.resolve(onFinally()).then(() = > val),
rea= > Promise.resolve(onFinally()).then(() = > { throwrea; })); }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));
}
// This is a big pity. Only when all the promises return fulfilled, it will return a fulfilled promise, which contains the array of the corresponding results. Otherwise, only one promise returns rejected, which is a pity. It returns a Rejected Promise containing the error message thrown by the first Rejected 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 there is a promise which will be fulfilled or rejected, it will return a fulfilled or rejected promise
static race (iterable) {
return new Promise ((resolve, reject) = > {
for (const p ofiterable) { p.then(resolve, reject); }}); }// After all promises are fulfilled or Rejected, an array containing the corresponding results will be returned
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)); }}); }// If either promise succeeds, a successful promise is returned, otherwise a failed promise of an instance of type AggregateError is returned
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(newAggregateError(arr)); }}); }}); }}Copy the code
There are three states of Promise: pending, fulfilled and Rejected. Pending is the initial state. Once it is fulfilled or rejected, it will be irreversible. Once resolve or reject is executed, the following resolve or reject does not take effect. Callbacks passed in by THEN can be executed late, so they need to be placed in the Callbacks array and retrieved when the state changes.
The last
Some of the code may require a lot of digesting to understand (except for the big guys), and it took me a few weeks, a lot of references, and a lot of code validation to write this article. If I can help you a little bit, that would be a great pleasure for me. If you think you can learn something, please use your cute little finger for more people to see. If there are any mistakes or questions, welcome to discuss them.
The resources
Juejin. Cn/post / 684490…
Github.com/berwin/time…
CO module
Promises/A+ 100 lines of code