面向大厂编程,100道你必须掌握的js手写题(二)
11、检测JavaScript中的数据类型
编写一个函数来检测任意数据的类型,除了基本的类型,还需要处理常用的复合数据类型包括 Array、ArrayBuffer、Map、Set、Date和Function。
需要注意的是,返回的类型应该小写
实现:
/**
* @param {any} data
* @return {string}
*/
function detectType(data) {
if(typeof data == 'object'){
if(data == null) return 'null'
if(data instanceof FileReader) return 'object'
return data.constructor.name.toLowerCase();
}
return typeof data
}
也可以用Object.prototype.toString.call()获取对象具体的数据类型
12、实现 JSON.stringify()
JSON.stringify()方法可以将JavaScript对象或值转换成JSON字符串,如果指定了替换器函数,则可以选择替换值,如果指定了替换器数组,则可以选择包含指定的属性
JSON.stringify(value)中,如果value中有toJSON方法,那么它负责定义将序列化的数据
实现一个最简单的JSON.stringify:
function stringify(data) {
if ([NaN, null, undefined, Infinity].includes(data)) {
return 'null'
}
const type = typeof data
switch (type) {
case 'function': return undefined;
case 'bigint': throw Error('bigints are not supported');
case 'string': return `"${data}"`;
case 'object': {
if (Array.isArray(data)) {
return `[${data.map(item => {
return (typeof item) == 'symbol' ? 'null' : stringify(item)
}).join()}]`
}
if (data instanceof Date) {
return `"${data.toISOString()}"`
}
return '{' + Object.keys(data).filter(key => data[key] !== undefined).map(key => `"${key}":${stringify(data[key])}`).join() + '}'
}
default: return String(data);
}
}
13、实现JSON.parse
实现:
/**
* @param {string} str
* @return {object | Array | string | number | boolean | null}
*/
function parse(str) {
if (str === '') {
throw Error();
}
if (str[0] === "'") {
throw Error();
}
if (str === 'null') {
return null;
}
if (str === '{}') {
return {};
}
if (str === '[]') {
return [];
}
if (str === 'true') {
return true;
}
if (str === 'false') {
return false;
}
if (str[0] === '"') {
return str.slice(1, -1);
}
if (+str === +str) {
return Number(str);
}
if (str[0] === '{') {
return str.slice(1, -1).split(',').reduce((acc, item) => {
const index = item.indexOf(':');
const key = item.slice(0, index)
const value = item.slice(index + 1);
acc[parse(key)] = parse(value);
return acc;
}, {});
}
if (str[0] === '[') {
return str.slice(1, -1).split(',').map((value) => parse(value));
}
}
14、通过Stack实现一个Queue
在JavaScript中,我们可以使用数组作为堆栈或队列
作为堆栈:
cosnt arr = [1, 2, 3, 4]
arr.push(5)
arr.pop()
作为队列:
const arr = [1, 2, 3, 4]
arr.push(5)
arr.shift()
现在假设有一个Stack,它只有以下接口:
class Stack {
push(element) { /* add element to stack */ }
peek() { /* get the top element */ }
pop() { /* remove the top element */ }
size() { /* count of elements */ }
}
要求只使用上面的Stack来实现队列,必须具备以下接口
class Queue {
enqueue(element) { /* add element to queue */ }
peek() { /* get the head element */ }
dequeue() { /* remove the head head */ }
size() { /* count of elements */ }
}
实现:
class Queue {
constructor() {
this.stack = new Stack()
}
enqueue(element) {
return this.stack.push(element)
}
peek() {
// get the head element
return this._reserveStack(this.stack).peek()
}
size() {
// return count of element
return this.stack.size()
}
dequeue() {
// remove the head element
this.stack = this._reserveStack(this.stack)
return this.stack.pop()
}
_reserveStack(stack) {
const tempStack = new Stack();
while (this.stack.size() > 0) {
tempStack.push(this.stack.pop())
}
return tempStack
}
}
15、为DOM element创建一个简单的store
ES6中的Map的键值可以是任何数据,比如是DOM element
const map = new Map()
map.set(domNode, data)
要求实现一个Node Store,在ES5等环境下支持DOM element作为key
class NodeStore {
set(node, value){}
get(node) {}
has(node) {}
}
实现:
class NodeStore {
constructor() {
this.map = {}
this.nodeKey = Symbol('nodeKey')
this.counter = 0
}
/**
* @param {Node} node
* @param {any} value
*/
set(node, value) {
if(!node.dataset[this.nodeKey]) {
node.dataset[this.nodeKey] = ++this.counter
this.map[this.counter] = value
}else {
this.map[node.dataset[this.nodeKey]] = value
}
}
/**
* @param {Node} node
* @return {any}
*/
get(node) {
return this.map[node.dataset[this.nodeKey]]
}
/**
* @param {Node} node
* @return {Boolean}
*/
has(node) {
return this.map.hasOwnProperty(node.dataset[this.nodeKey])
}
}
16、实现Object.assign()
Object.assign方法将所有可枚举的自身属性从一个或多个源对象复制到目标对象,并返回目标对象。
Object Spread运算符实际上在内部与Object.assign()相同
let result = { ...sourceObj }
// 等同于
let result = Object.assin({}, sourceObj)
实现:
/**
* @param {any} target
* @param {any[]} sources
* @return {object}
*/
function objectAssign(target, ...sources) {
if (target == null || target == undefined) throw new Error('invalid target')
let result = target
// 支持以number、string、boolean类型的值为target
if (['number', 'string', 'boolean'].includes(typeof target)) {
result = Object(target)
}
// 判断对象是否可以写入
function isOwnPropertyWritable(obj, prop) {
const des = Object.getOwnPropertyDescriptor(obj, prop);
return des == null || des.writable || !!des.set;
}
sources.map(item => {
if ([NaN, null, undefined, Infinity].includes(item)) { return }
// 支持以Symbol为键值
let keyArr = [...Object.keys(item), ...Object.getOwnPropertySymbols(item)]
keyArr.map(key => {
if (isOwnPropertyWritable(target, key)) {
target[key] = item[key]
} else {
throw new Error('cannot assign to read-only properties')
}
})
})
return result
}
也可以通过Reflect.set给对象设置属性,判断是否更新成功
17、实现 _.once 方法
_once(func)用于强制一个函数只调用一次,后面的调用只返回第一次调用的结果。
要求只能执行一次func
举例:
function func(num){
return num
}
const onced = once(func)
onced(1) // 1
onced(2) // 1
function once(func) {
let result
let called = false
return function (...args) {
if (!called) {
result = func.apply(this, args)
called = true
}
return result
}
}
18、实现一个async helper方法 sequence
要求实现一个异步函数helper,像是pipe方法那样可以将异步函数连接起来
举例:
假设所有异步函数都有以下interface
type Callback = (error: Error, data: any) => void
type AsyncFunc = (
callback: Callback,
data: any
) => void
sequence()可以接受AsyncFunc数组,并通过回调中的数据作为新数据传给下一个AsyncFunc
const asyncTimes = (callback, num) => {
setTiout(() => callback(null, num * 2), 100)
}
const asyncTimesArr = sequence(
[
asyncTimes,
asyncTimes
]
)
asyncTimesArr((err, data) =>{
console.log(data) // 4
}, 1)
使用promise实现:
/**
* @param {Function} func
* @return {Function}
*/
function sequence(funcs) {
const promiseFuncs = funcs.map(promisify)
return function (callback, input) {
let promise = Promise.resolve(input)
promiseFuncs.forEach((promiseFunc) => {
promise = promise.then(promiseFunc)
});
promise.then((data) => {
callback(undefined, data)
}).catch(callback)
}
}
function promisify(callback) {
return function (input) {
return new Promise((resolve, reject) => {
callback((err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
}, input)
})
}
}
不使用promise实现:
/**
* @param {Function} func
* @return {Function}
*/
function sequence(funcs) {
const funcsQueue = [...funcs];
return function run(finalCB, data) {
if (funcsQueue.length === 0) {
finalCB(undefined, data)
return
}
const currentFunc = funcsQueue.shift()
const cb = (error, num) => {
if(error) {
finalCB(error, num)
return
}
run(finalCB, num)
}
currentFunc(cb, data)
}
}
19、实现一个async helper方法 parallel
parallel()类似Promise.all(),和前面的sequence()不同,异步函数的执行没有先后顺序,在parallel()中是同时触发。
注意:paralle需要返回一个function,这个function将在所有的的异步函数完成或者error发生的时候被触发。
只有第一个error需要被传递到callback,其他的error和data都被忽略
举例:
const async1 = (callback) => {
setTimeout(() => callback(undefined, 1))
}
const async2 = (callback) => {
setTimeout(() => callback(undefined, 2))
}
const async3 = (callback) => {
setTimeout(() => callback(undefined, 3))
}
const all = parallel(
[
async1,
async2,
async3
]
)
all((error, data) => {
console.log(data) // [1, 2, 3]
})
使用promise实现:
function parallel(funcs) {
const promiseFuncs = funcs.map(promisify)
return function (callback, input) {
Promise
.all(promiseFuncs.map(func => func(input)))
.then(res => callback(undefined, res))
.catch(err => callback(err, undefined))
}
}
function promisify(callback) {
return function (input) {
return new Promise((resolve, reject) => {
callback((err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
}, input)
})
}
}
不用promise实现:
function parallel(funcs) {
return function (callback) {
let isError = false
const res = []
funcs.map((func, index) => {
func((error, data) => {
if (!isError) {
if (error) {
isError = true
callback(error, undefined)
}
res.push(data)
if (res.length === funcs.length) {
callback(undefined, res)
}
}
})
})
}
}
20、实现一个async helper方法 race
race类似Promise.race()。parallel会等待所有的function执行结束,race()会在任何一个function结束或产生error的时候调用最终的callback。
注意:race()在任何一个function结束或产生error的时候调用最终的 callback
举例:
const async1 = (callback) => {
setTimeout(() => callback(undefined, 1), 300)
}
const async2 = (callback) => {
setTimeout(() => callback(undefined, 2), 100)
}
const async3 = (callback) => {
setTimeout(() => callback(undefined, 3), 200)
}
const first = race(
[
async1,
async2,
async3
]
)
first((err, data) => {
// 2
console.log(data)
}, 1)
实现:
function race(funcs) {
return function (callback) {
let handled = false
funcs.map((func) => {
func((err, data) => {
if (!handled) {
handled = true
callback(err, data)
}
})
})
}
}
往期文章推荐
带你彻底了解微信小程序的createIntersectionObserver,实现长列表性能优化、图片懒加载、吸顶等等操作