记录几段常用js代码的实现

8,195次阅读
没有评论

共计 7241 个字符,预计需要花费 19 分钟才能阅读完成。

前言

记录几段常用的 js 代码实现,掌握了实现原理,就对这些用法会有更加深入的理解。

一、深拷贝

深拷贝,深比较,之前有文章写过,请看:https://www.haorooms.com/post/js_copy_sq

今天的深 copy 考虑了 Symbol 属性,代码如下:

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数处理
  if (typeof target !== 'object' || target === null) {return target;}
  // 哈希表中存在直接返回
  if (hash.has(target)) return hash.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  // 针对 Symbol 属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);
      } else {cloneTarget[symKey] = target[symKey];
      }
    })
  }

  for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }
  return cloneTarget;
}

二、Promise 实现

// 模拟实现 Promise
// Promise 利用三大手段解决回调地狱:// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在 this 上,便于 then、catch 访问
    this.value = undefined;
    this.reason = undefined;
    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立即执行 executor
      // 把内部的 resolve 和 reject 传入 executor,用户可调用 resolve 和 reject
      exector(resolve, reject);
    } catch(e) {
      // executor 执行出错,将错误内容 reject 抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function'? onRejected :
      reason => {throw new Error(reason instanceof Error ? reason.message : reason) }
    // 保存 this
    const self = this;
    return new Promise((resolve, reject) => {if (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {
          // try 捕获错误
          try {
            // 模拟微任务
            setTimeout(() => {const result = onFulfilled(self.value);
              // 分两种情况:// 1. 回调函数返回值是 Promise,执行 then 操作
              // 2. 如果不是 Promise,调用新 Promise 的 resolve 函数
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {setTimeout(() => {const result = onRejected(self.reason);
              // 不同点:此时是 reject
              result instanceof Promise ? result.then(resolve, reject) : reject(result);
            })
          } catch(e) {reject(e);
          }
        })
      } else if (self.status === FULFILLED) {
        try {setTimeout(() => {const result = onFulfilled(self.value);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          });
        } catch(e) {reject(e);
        }
      } else if (self.status === REJECTED) {
        try {setTimeout(() => {const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {reject(e);
        }
      }
    });
  }
  catch(onRejected) {return this.then(null, onRejected);
  }
  static resolve(value) {if (value instanceof Promise) {
      // 如果是 Promise 实例,直接返回
      return value;
    } else {
      // 如果不是 Promise 实例,返回一个新的 Promise 对象,状态为 FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {return new Promise((resolve, reject) => {reject(reason);
    })
  }
  static all(promiseArr) {
    const len = promiseArr.length;
    const values = new Array(len);
    // 记录已经成功执行的 promise 个数
    let count = 0;
    return new Promise((resolve, reject) => {
      for (let i = 0; i  {values[i] = val;
            count++;
            // 如果全部执行完,返回 promise 的状态就可以改变了
            if (count === len) resolve(values);
          },
          err => reject(err),
        );
      }
    })
  }
  static race(promiseArr) {return new Promise((resolve, reject) => {promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),
          err => reject(err),
        )
      })
    })
  }
}

三、Promise 并行限制

class Scheduler {constructor() {this.queue = [];
    this.maxCount = 2;
    this.runCounts = 0;
  }
  add(promiseCreator) {this.queue.push(promiseCreator);
  }
  taskStart() {for (let i = 0; i = this.maxCount) {return;}
    this.runCounts++;

    this.queue.shift()().then(() => {
      this.runCounts--;
      this.request();});
  }
}

const timeout = time => 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');
scheduler.taskStart()
// 2
// 3
// 1
// 4

四、JSONP 的实现

const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
    let dataSrc = '';
    for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;
      }
    }
    dataSrc += `callback=${callbackName}`;
    return `${url}?${dataSrc}`;
  }
  return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
    scriptEle.src = generateUrl();
    document.body.appendChild(scriptEle);
    window[callbackName] = data => {resolve(data);
      document.removeChild(scriptEle);
    }
  })
}

五、event 模块实现

实现 node 中回调函数的机制,node 中回调函数其实是内部使用了观察者模式。

function EventEmitter() {this.events = new Map();
}

// 需要实现的一些方法:// addListener、removeListener、once、removeAllListeners、emit

// 模拟实现 addlistener 方法
const wrapCallback = (fn, once = false) => ({callback: fn, once});
EventEmitter.prototype.addListener = function(type, fn, once = false) {const hanlder = this.events.get(type);
  if (!hanlder) {
    // 没有 type 绑定事件
    this.events.set(type, wrapCallback(fn, once));
  } else if (hanlder && typeof hanlder.callback === 'function') {
    // 目前 type 事件只有一个回调
    this.events.set(type, [hanlder, wrapCallback(fn, once)]);
  } else {// 目前 type 事件数>=2
    hanlder.push(wrapCallback(fn, once));
  }
}
// 模拟实现 removeListener
EventEmitter.prototype.removeListener = function(type, listener) {const hanlder = this.events.get(type);
  if (!hanlder) return;
  if (!Array.isArray(this.events)) {if (hanlder.callback === listener.callback) this.events.delete(type);
    else return;
  }
  for (let i = 0; i  {item.callback.apply(this, args);
      if (item.once) {this.removeListener(type, item);
      }
    })
  } else {hanlder.callback.apply(this, args);
    if (hanlder.once) {this.events.delete(type);
    }
  }
  return true;
}
EventEmitter.prototype.removeAllListeners = function(type) {const hanlder = this.events.get(type);
  if (!hanlder) return;
  this.events.delete(type);
}

六、一次性渲染几万条数据数据,页面不算很卡的方法

几万条数据,假如不是滚动加载,一次性渲染到页面中,单单是节点,都会很慢。下面方法可以渲染出来。但是节点很多,也会比较慢,但是是一次性渲染几万条数据比较好的方式了。

setTimeout(() => {
  // 插入十万条数据
  const total = 100000;
  // 一次插入的数据
  const once = 20;
  // 插入数据需要的次数
  const loopCount = Math.ceil(total / once);
  let countOfRender = 0;
  const ul = document.querySelector('ul');
  // 添加数据的方法
  function add() {const fragment = document.createDocumentFragment();
    for(let i = 0; i 

这个方法使用 createDocumentFragment 和 requestAnimationFrame,将操作切分为一小段一小段执行。

七、将 VirtualDom 转化为真实 DOM 结构

// vnode 结构:// {
//   tag,
//   attrs,
//   children,
// }

//Virtual DOM => DOM
function render(vnode, container) {container.appendChild(_render(vnode));
}
function _render(vnode) {
  // 如果是数字类型转化为字符串
  if (typeof vnode === 'number') {vnode = String(vnode);
  }
  // 字符串类型直接就是文本节点
  if (typeof vnode === 'string') {return document.createTextNode(vnode);
  }
  // 普通 DOM
  const dom = document.createElement(vnode.tag);
  if (vnode.attrs) {
    // 遍历属性
    Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];
      dom.setAttribute(key, value);
    })
  }
  // 子数组进行递归操作
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

八、字符串解析问题

var a = {
    b: 123,
    c: '456',
    e: '789',
}
var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;
// => 'a123aa456aa {a.d}aaaa'

实现函数使得将 str 字符串中的 {} 内的变量替换,如果属性不存在保持原样(比如{a.d})
类似于模版字符串,但有一点出入,实际上原理大差不差

const fn1 = (str, obj) => {
    let res = '';
    // 标志位,标志前面是否有{
    let flag = false;
    let start;
    for (let i = 0; i  {const keys = str.split('.').slice(1);
    let index = 0;
    let o = obj;
    while (index 

九、数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]

方法一:使用 flat()

const res1 = arr.flat(Infinity);

方法二:正则

const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/[|]/g, '') +']');

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计7241字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)