# 实现 Object.assign () 和 completeAssign ()

# Object.assign()

Object.assign() 方法用于将所有源对象的可枚举属性,复制到目标对象。它将返回目标对象。

# 语法

Object.assign(target, ...sources)

# 示例

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target); // { a: 1, b: 4, c: 5 }

# 手写实现

使用 for...of 循环遍历 sources 数组,然后使用 Object.keys()Object.getOwnPropertySymbols() 获取 source 对象的可枚举属性和 symbol 类型的属性。

function objectAssign(target, ...sources) {
  if(target === null || target === undefined) {
    throw Error()
  }
  // your code here
   target = Object(target)
   console.log(target);
  for(let source of sources) {
    console.log(source, source == null)
    if(source == null) continue;
    merge(Object.keys(source), source);
    merge(Object.getOwnPropertySymbols(source), source); // 获取 symbol 的 key
  }
  function merge(keys = [], source) {
    console.log(keys.length, 111)
    for(let key of keys) {
      target[key] = source[key];
      if(target[key] !== source[key]) throw Error();
    }
  }
  return target;
}

# completeAssign()

completeAssign() 处理的是可枚举属性和 symbol 类型的属性, getters 不会被复制,不可枚举属性被忽略。

# 示例

const source = Object.create(
  {
    a: 3 // prototype
  },
  {
    b: {
      value: 4,
      enumerable: true // 可枚举 data descriptor
    },
    c: {
      value: 5, // 不可枚举 data descriptor
    },
    d: { // 不可枚举 accessor descriptor 
      get: function() {
        return this._d;
      },
      set: function(value) {
        this._d = value
      }
    },
    e: { // 可枚举 accessor descriptor 
      get: function() {
        return this._e;
      },
      set: function(value) {
        this._e = value
      },
      enumerable: true
    }
  }
)
console.log(completeAssign({}, source)) // {b: 4, c: 5}

# 手写实现

使用 Object.getOwnPropertyDescriptors() 获取 source 对象的所有属性描述符。再使用 Object.defineProperties() 设置 target 对象的属性描述符。最后遍历 source 对象的 symbol 类型的属性,设置 target 对象的属性。

function completeAssign(target, ...sources) {
  // your code here
  if(target == null) throw Error('11');
  target = Object(target);
  for(let source of sources) {
    if(source == null) continue;
    source = Object(source);
    Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); // 获取到特有的
    for(let key of Object.getOwnPropertySymbols(source)) { // Symbol 属性
      target[key] = source[key];
    }
  }
  return target;
}

# 实现 clearAllTimeout ()

clearAllTimeout() 方法用于清除所有的 setTimeout() 定时器。

# 代码实现

开多个定时器,然后依次清除。总能清除所有定时器。

function clearAllTimeout() {
  // your code here
  let id = setTimeout(() => {}, 0);
  while(id) {
    clearTimeout(id);
    id --;
  }
}

# 实现 async helper - sequence() & parallel()

# parallel

sequence() 方法用于将异步函数串行化,依次执行异步函数,并返回一个函数,该函数接受一个回调函数作为参数。

# 代码实现

  1. 定义 promisify() 方法,将异步函数转换为 Promise 对象。
  2. 使用 Promise.resolve() 将输入转换为 Promise 对象。
  3. 然后开始链式调用,每次返回的值作为下一个异步函数的输入。
  4. 最后解决时调用回调函数。
/*
type Callback = (error: Error, data: any) => void
type AsyncFunc = (
   callback: Callback,
   data: any
) => void
*/
/**
 * @param {AsyncFunc[]} funcs
 * @return {(callback: Callback) => void}
 */
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(fun) {
  return (input) => {
    return new Promise((resolve, reject) => {
      fun((err, newData) => {
        if(err) {
          reject(err);
        } else {
          resolve(newData)
        }
      }, input)
    })
  }
}

# parallel()

parallel() 方法用于将异步函数并行化,同时执行异步函数,并返回一个函数,该函数接受一个回调函数作为参数。

# 代码实现

  1. 定义 promisify() 方法,将异步函数转换为 Promise 对象。
  2. 使用 Promise.all() 将异步函数并行执行,并返回一个 Promise 对象。
  3. 最后解决时调用回调函数。
/*
type Callback = (error: Error, data: any) => void
type AsyncFunc = (
   callback: Callback,
   data: any
) => void
*/
/**
 * @param {AsyncFunc[]} funcs
 * @return {(callback: Callback) => void}
 */
function parallel(funcs){
  // your code here
  // const promisiFuncs = funcs.map(promisify);
  return function(callback, input) {
    Promise.all(funcs.map(fn => promisify(fn)(input)))
    .then(data => callback(undefined, data))
    .catch(err => callback(err, undefined))
  }
}
function promisify(fun) {
  return (input) => {
    return new Promise((resolve, reject) => {
      fun((err, newData) => {
        if(err) {
          reject(err);
        } else {
          resolve(newData)
        }
      }, input)
    })
  }
}

# 实现 async helper - race()

# race()

race() 方法会在任何一个 function 结束或者产生 error 的时候调用最终的 callback。

# 代码实现

  1. 为每个 function 都添加一个 flag,如果 flag 为 true,则不再执行后续的 function。
  2. 使用 forEach() 遍历每个 function,并调用其函数。
/*
type Callback = (error: Error, data: any) => void
type AsyncFunc = (
   callback: Callback,
   data: any
) => void
*/
/**
 * @param {AsyncFunc[]} funcs
 * @return {(callback: Callback) => void}
 */
function race(funcs){
  // your code here
  return (callback, input) => {
    let flag = false;
    const fun = (...arg) => {
      if(flag === true) return ;
      callback(...arg);
      flag = true;
    }
    for(let func of funcs) {
      func(fun);
    }
  }
}

# 实现 Promise.all()

# Promise.all()

Promise.all() 方法用于将多个 Promise 对象组合为一个新的 Promise 对象,并返回一个 Promise 对象。该 Promise 对象在传入的所有 Promise 对象都成功时才会成功,一旦有一个 Promise 对象失败,则立即返回一个失败的 Promise 对象。

# 代码实现

  1. 通过一个数组存储每个 Promise 完成后的结果
  2. 调用 Promise.resolve() 将输入转换为 Promise 对象。
  3. 使用 .then(onfulfilled, onrejected) 当 Promise 成功时调用 onfulfilled,当 Promise 失败时调用 onrejected。也就直接 reject 出去了
/**
 * @param {Array<any>} promises - notice input might have non-Promises
 * @return {Promise<any[]>}
 */
function all(promises) {
  // your code here
  return new Promise((resolve, reject) => {
    let arr = [];
    if(promises.length === 0) resolve([]);
    for(let i = 0; i < promises.length; i ++) {
      const promise = promises[i];
      Promise.resolve(promise).then((value) => {
        arr[i] = value;
        if(i === promises.length - 1) resolve(arr);
      }, reject);
    }
  })
}

# 实现 Promise.allSettled()

# Promise.allSettled()

Promise.allSettled() 方法返回一个在所有给定的 promise 都已经 fulfilledrejected 后的 promise ,并带有一个对象数组,每个对象表示对应的 promise 结果。

# 示例

Promise.allSettled([
  Promise.resolve(33),
  new Promise((resolve) => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));
// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
//   {status: 'rejected', reason: Error: 一个错误}
// ]

# 代码实现

  1. 通过一个数组存储每个 Promise 完成后的结果
  2. 注意数组中存储的是 resolvereject 后的结果,所以需要分别处理。
  3. 使用 .finally() 方法,当 Promise 完成时调用,并返回一个 Promise 对象。
/**
 * @param {Array<any>} promises - notice that input might contains non-promises
 * @return {Promise<Array<{status: 'fulfilled', value: any} | {status: 'rejected', reason: any}>>}
 */
function allSettled(promises) {
  
  // your code here
  let results = [];
  let idx = 0;
  return new Promise((resolve, reject) => {
    if(promises.length === 0) {
      resolve([]);
    }
    for(let i = 0; i < promises.length; i ++) {
      const promise = promises[i];
      Promise.resolve(promise)
        .then(value => {
          results[i] = { status: 'fulfilled', value }
        })
        .catch((reason) => {
          results[i] = { status: 'rejected', reason }
        })
        .finally(() => {
          idx ++;
          if(idx === promises.length) resolve(results);
        })
    }
  })
}

# 实现 Promise.any()

# Promise.any()

Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败 / 拒绝),就返回一个失败的 promiseAggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和 Promise.all() 是相反的。

# 代码实现

  1. 数组存储每个 promise 失败的原因。
  2. 使用 .catch() 方法,当 Promise 全部失败时调用,并返回一个 Promise 对象包含 AggregateError
  3. 当有一个 promise 成功时直接 resolve
/**
 * @param {Array<Promise>} promises
 * @return {Promise}
 */
function any(promises) {
  // your code here
  return new Promise((resolve, reject) => {
    let results = [];
    let idx = 0;
    for(let i = 0; i < promises.length;i ++) {
      const promise = promises[i];
      promise
        .then((value) => resolve(value))
        .catch((err) => {
          results[i] = err;
          idx ++;
          if(idx === promises.length) reject(new AggregateError('error', results));
        })
    }
  })
}

# 手写 _.cloneDeep()

实现深拷贝支持

  • 基础数据类型及其 wrapper object
  • 简单 Object(可枚举属性)
  • 数组 Array
  • 注意循环引用

# 代码实现

什么是 WeakMap
WeakMap 是一个键值对集合,键必须是对象,值可以是任意类型。 WeakMap 对键的引用是弱引用
通过 WeakMap 记录已经拷贝的对象,避免循环引用

let visited = new WeakMap();
function cloneDeep(data) {
  // your code here
  if(data === null || data === undefined || typeof data === "number" || typeof data === 'string' || typeof data === 'boolean' || typeof data === 'symbol' || typeof data === 'bigint') {
    return data;
  }
  if(visited.has(data)) {
    return visited.get(data);
  }
  let res = Array.isArray(data) ? [] : {};
  visited.set(data, res);
  if(Array.isArray(data)) {
    for(let i = 0; i < data.length; i ++) {
      res[i] = cloneDeep(data[i]);
    }
    return res;
  } else if(typeof data === 'object') {
    for(key of Reflect.ownKeys(data)) {
      res[key] = cloneDeep(data[key]);
    }
    return res;
  }
  return data;
}

# Promise reject 的时候自动 retry

实现一个 fetchWithAutoRetry (fetcher, count),当出错的时候会自动重试,直到最大的重试次数。

# 代码实现

  1. Promise 链的链接
    fetcher 返回一个 Promise
    如果 fetcher 成功后,就会返回成功结果
    如果 fetcher 失败后, .catch() 被触发
    .catch() 中,如果次数用完了直接返回失败结果,否则将继续执行 fetcher
  2. 递归工作
    每次递归减少次数
    次数为 0 时停止,抛出错误
    每次递归调用都会返回一个新的 Promise,这个 Promise 会被上一级的.catch () 链接起来。
/**
 * @param {() => Promise<any>} fetcher
 * @param {number} maximumRetryCount
 * @return {Promise<any>}
 */
function fetchWithAutoRetry(fetcher, maximumRetryCount) {
  // your code here
  return fetcher().catch((err) => {
    if(maximumRetryCount === 0) {
      throw err;
    } else {
      return fetchWithAutoRetry(fetcher, maximumRetryCount - 1);
    }
  })
}

# 实现负索引

# 实现思路

通过 Proxy 代理实现,设置 get 方法通过 Reflect.get 获取属性值,并返回结果。设置 set 方法通过 Reflect.set 设置属性值,并返回结果。

# 代码实现

注意判断索引还是方法

function wrap(arr) {
  // your code here
  const isNumber = (prop) => typeof prop !== 'symbol' && !isNaN(prop);
  const normalizeProp = (prop, arrLength) => prop >= 0 ? prop : prop + arrLength;
  return new Proxy(arr, {
    get(target, prop, reciever) {
      console.log(2);
      if(isNumber(prop)) {
        prop = normalizeProp(+prop, target.length); 
      }
      return Reflect.get(target, prop, reciever);
    },
    set(target, prop, value, reciever) {
      console.log(1);
      if(isNumber(prop)) {
        prop = normalizeProp(+prop, target.length);
      }
      if(prop < 0) throw new Error('error');
      return Reflect.set(target, prop, value, reciever);
    }
  })
}

# 实现 DOM 的序列化与反序列化

将 DOM 序列化为对象,对象反序列化为 DOM。

# 实现思路

  1. 获取 DOM 节点,通过 tagName 可以得到节点类型,通过 attributes 可以得到节点的属性,再通过 childNodes 去遍历子节点。递归添加到对象中。
  2. 对于每个节点,通过 createElement 创建节点,直接设置 element[key] = value 可以将属性设置到节点上。递归渲染子节点。

# 代码实现

/**
 * @param {HTMLElement} 
 * @return {object} object literal presentation
 */
function virtualize(element) {
  // your code here
  const result = {
    type: element.tagName.toLowerCase(), // 标签名
    props: {}
  }
  for(let attr of element.attributes) { // 获取属性
    const name = attr.name === 'class' ? 'className' : attr.name;
    result.props[name] = attr.value;
  }
  const children = [];
  for(let node of element.childNodes) {
    if(node.nodeType === 3) { // 文本节点
      children.push(node.textContent);
    } else { // 其他节点
      children.push(virtualize(node));
    }
  }
  result.props.children = children.length === 1 ? children[0] : children;
  return result;
}
/**
 * @param {object} valid object literal presentation
 * @return {HTMLElement} 
 */
function render(json) {
  // create the top level emlement
  // recursively append the children
  // textnode
  if(typeof json === 'string') { // 文本节点
    return document.createTextNode(json);
  }
  const {type, props: {children, ...attrs}} = json;
  const element = document.createElement(type);
  for(let [key, value] of Object.entries(attrs)) {
    element[key] = value; // 设置属性
  }
  const childrenArr = Array.isArray(children) ? children : [children];
  for(let child of childrenArr) {
    element.append(render(child)); // 递归渲染子节点
  }
  return element;
}

# 手写 Promise.prototype.finally()

可以再 promisesettle 的时候执行一个 callback ,无论是否被 fulfill 或者 reject ,都会执行 callback

# 实现思路

返回一个 Promise 当前 promisesettle 的时候执行 callback ,并返回 promise 。注意回调函数可能返回一个 promise ,如果返回的是一个 promise ,则返回的 promise 的状态和 callback 的返回值一致。

# 代码实现

设置无论当 promisefulfill 或者 reject 的时候执行 onFinally ,如果 onFinally 返回的是一个 promise ,则返回的 promise 的状态和 onFinally 的返回值一致。当 await 一个被 rejectpromise 相当于抛出异常

/**
 * @param {Promise<any>} promise
 * @param {() => void} onFinally
 * @returns {Promise<any>}
 */
function myFinally(promise, onFinally) {
  // your code here
  return promise.then(async (data) => {
    // console.log(1);
    await onFinally();
    return Promise.resolve(data);
  }, async (reason) => {
    await onFinally();
    return Promise.reject(reason);
  })
}
const res = []
const promise = Promise.reject(100)
myFinally(promise, () => {
  res.push(1)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      res.push(2)
      reject(4)
    }, 100)
  })
}).then((value) => {
  res.push(3)
}).catch((reason) => {
  console.log(reason);
}) // 4

# 创建 LazyMan

# 实现思路

  1. 创建一个操作对象
  2. 在对象中修改函数中的属性,并返回对象本身,实现链式调用
  3. 通过 setTimeout 来将任务加入延时队列,只有当这次任务执行完毕,才会执行下一次任务,也就是这次链式调用时, setTimeout 任务处理函数并不会执行,等到链式调用结束,才会执行 setTimeout 任务处理函数。

# 代码实现

// interface Laziness {
//   sleep: (time: number) => Laziness
//   sleepFirst: (time: number) => Laziness
//   eat: (food: string) => Laziness
// }
/**
 * @param {string} name
 * @param {(log: string) => void} logFn
 * @returns {Laziness}
 */
function LazyMan(name, logFn) {
  // your code here
  let queue = [() => {logFn(`Hi, I'm ${name}.`)}];
  const Man = {
    eat: function(name) {
      queue.push(() => {logFn(`Eat ${name}.`)});
      return this;
    },
    sleep: function(delay) {
      queue.push(() => new Promise((resolve, reject) => {setTimeout(() => {logFn(`Wake up after ${delay} ${delay > 1 ? 'seconds' : 'second'}.`); resolve()}, delay * 1000)}))
      return this;
    },
    sleepFirst: function(delay) {
      queue.unshift(() => new Promise((resolve, reject) => {setTimeout(() => {logFn(`Wake up after ${delay} ${delay > 1 ? 'seconds' : 'second'}.`); resolve()}, delay * 1000)}))
      return this;
    }
  }
  setTimeout(async() => {
    for (task of queue) {
      console.log(task);
      await task();
    }
  }, 0);
  return Man;
}
LazyMan('Jack', console.log)
  .eat('banana')
  .eat('apple')
  .sleepFirst(2)
// Wake up after 2 seconds.
// Hi, I'm Jack.
// Eat banana.
// Eat apple.

# 数组扁平化的多种写法

# 目前六种

# 代码

let a = [[1, 2, 3], [[4]], [5, 6]];
// 1. Array.prototype.flat()
console.log(a.flat(Infinity))
// 2. 手写递归 flat
function myFlat(arrs) {
    let res = [];
    for (const arr of arrs) {
        if (Array.isArray(arr)) {
            res = res.concat(myFlat(arr));
        } else {
            res.push(arr);
        }
    }
    return res;
}
console.log(myFlat(a));
// 3. reduce
function myFlat2(arrs) {
    return arrs.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? myFlat2(cur) : cur);
    }, []);
}
console.log(myFlat2(a));
// 4. 扩展运算符
function myFlat3(arrs) {
    while (arrs.some(item => Array.isArray(item))) {
        arrs = [].concat(...arrs);
    }
    return arrs;
}
console.log(myFlat3(a));
// 5. toString + split (仅限于数字数组)
function myFlat4(arrs) {
    return arrs.toString().split(',').map(item => Number(item));
}
console.log(myFlat4(a));
// 6. JSON.stringify + parse
function myFlat5(arrs) {
    let str = JSON.stringify(arrs);
    str = str.replace(/(\[|\])/g, '')
    str = '[' + str + ']';
    return JSON.parse(str);
}
console.log(myFlat5(a));

# 带标签的模板

带标签的模板是模板字面量的一种更高级的形式,它允许你使用函数解析模板字面量。标签函数的第一个参数包含一个字符串数组,其余的参数与表达式相关。你可以用标签函数对这些参数执行任何操作,并返回被操作过的字符串(或者,也可返回完全不同的内容,见下面的示例)。用作标签的函数名没有限制。

const person = "Mike";
const age = 28;
function myTag(strings, personExp, ageExp) {
  const str0 = strings[0]; // "That "
  const str1 = strings[1]; // " is a "
  const str2 = strings[2]; // "."
  const ageStr = ageExp > 99 ? "centenarian" : "youngster";
  // 我们甚至可以返回使用模板字面量构建的字符串
  return `${str0}${personExp}${str1}${ageStr}${str2}`;
}
const output = myTag`That ${person} is a ${age}.`;
console.log(output);
// That Mike is a youngster.
function p(strings, ...values) {
    console.log(strings);
    console.log(values);
}
p`h${1}, h${2}, h${3}`;
// => [ 'h', ', h', ', h' ]
// => [ 1, 2, 3 ]
更新于 阅读次数