# 编程实战

# 实现一个new操作符

function new(Con, ...args) {
  // 创建一个空的对象
  let obj = {};

  // 链接到原型
  Object.setPrototypeOf(obj, Con.prototype); // obj.__proto__ = Con.prototype;

  // 绑定 this,执行构造函数
  let result = Con.apply(obj, ...args);

  // 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用obj
  return typeof result === 'object' ? result : obj
}

# 实现一个Object.create

思路:将传入的对象作为原型

function create(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}

# 实现一个JSON.stringify

# 实现一个JSON.parse

var jsonStr = '{ "age": 20, "name": "jack" }';

var json = new Function("return " + jsonStr)();

原理:

ƒ anonymous() {
return { "age": 20, "name": "jack" }
}

# 实现一个call

方法1

Function.prototype._call1 = function (obj) {
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  // 默认上下文是window
  let context = obj || window;

  // 前面讲的关键,将函数本身作为对象context的属性调用,自动绑定this
  context._fn = this;
  const args = [...arguments].slice(1);
  const result = context._fn(...args);

  // 删除 fn
  delete context._fn;

  return result;
};


const obj = {
  age: "88",
};

function People() {
  return this.age;
}

const r = People._call(obj);
console.log(r);

方法2

Function.prototype._call2 = function (context, ...args) {
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  let fn = this;
  return fn.apply(context, ...args);
};

# 实现一个apply

Function.prototype._apply = function (context, ...args) {
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  const obj = context || window;

  obj._fn = this;

  let result;
  if (args === undefined || args === null) {
    //undefined 或者 是 null 不是 Iterator 对象,不能被 ...
    result = obj._fn(args);
  } else if (typeof args === "object") {
    result = obj._fn(...args);
  }

  delete obj._fn;
  return result;
};

# 实现一个bind

Function.prototype._bind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  var _this = this;
  var args = [...arguments].slice(1);
  
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments);
    }
    return _this.apply(context, args.concat(...arguments));
  }
}

# 实现Object.assign

if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      
      if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true,
    enumerable: false
  });
}

参考 (opens new window)

more: Object.assign 方法肯定不会拷贝原型链上的属性,所以模拟实现时需要用 hasOwnProperty(..) 判断处理下,但是直接使用 myObject.hasOwnProperty(..) 是有问题的,因为有的对象可能没有连接到 Object.prototype 上(比如通过 Object.create(null) 来创建),这种情况下,使用 myObject.hasOwnProperty(..) 就会失败

# 实现一个继承

寄生组合继承

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue']
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
};

function SubType(name, age) {
  //继承属性  //第二次调用
  SuperType.call(this, name);
  this.age = age;
}

//继承方法
SubType.prototype = Object.create(SuperType.prototype);  //第一次调用
SubType.prototype.constructor = SubType;  //如果不添加,原型(父类的实例)上是没有这个属性的
SubType.prototype.sayAge = function() {
  console.log(this.age)
};

var ins3 = new SubType('yixing', 24);
ins3.colors.push('green');
console.log(ins3.colors);  //'red,blue,green'
ins3.sayName();  //'yixing'
ins3.sayAge();  //24

var ins4 = new SubType('xiaoya', 23);
ins4.colors.push('yellow');
ins4.sayName();  //'xiaoya'
ins4.sayAge();  //23

# 实现一个JS函数柯里化

实现柯里化

function curry(func, args) {
  var length = func.length; //获取函数的参数值
  var args = args || [];
  
  return function () {
    var _args = [].slice.apply(arguments);
    args.push(..._args);
    
    if (args.length < length) {
      return createCurry.call(this, func, args);
    }
    
    return func.apply(this, args);
  }
}

# 实现 add 返回结果是1,2,3之和

方法1

function curry(fn) {
  let arr = [];

  return function f() {
    if (arguments.length !== 0) {
      arr.push(...Array.from(arguments));
      return f;
    } else {
      console.log("arr", arr);
      return fn(...arr);
    }
  };
}

function add(...args) {
  //求和
  return args.reduce((a, b) => a + b);
}

let sumFn = curry(add);
console.log(sumFn(1)(2)(3)(4)()); //10

方法2

function add() {
  const args = [].slice.call(arguments);

  let fn = function () {
    const arg_fn = [].slice.call(arguments);
    return add.apply(null, args.concat(arg_fn));
  };

  fn.toString = function () {
    return args.reduce((a, b) => a + b);
  };

  return fn;
}

console.log(+add(1, 2, 3)); //6
console.log(+add(1)(2, 4)(3)); //10

方法3

function add(...args) {
  function fn(...rest) {
    args = args.concat(rest);
    return fn;
  }

  console.log("args", args);

  fn.toString = function () {
    return args.reduce((a, b) => {
      return a + b;
    }, 0);
  };

  return fn;
}

console.log(+add(1)(2)(3)(4));
console.log(+add(1, 2, 3)(4, 5)(5));

# 实现Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保存初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach((callback) => {
          callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach((callback) => {
          callback(value);
        });
      }
    }, 0);
  }

  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function (value) {
          return value;
        };

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function (error) {
          throw error;
        };

  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态已经凝固,则直接执行对应状态的函数

  if (this.state === RESOLVED) {
    onResolved(this.value);
  }

  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};

function timeout(time) {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

async function run() {
  console.log("time start");
  try {
    await timeout(3000);
    console.log("time end");
  } catch (error) {
    console.log("error", error);
  }
}

run();

const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(123);
  }, 3000);
});

console.log(p);

p.then((value) => {
  console.log("value", value);
});

# 实现Promise all

_promise.prototype.all = function (promises) {
  let arr = [],
    count = 0;

  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item)
        .then((res) => {
          arr[i] = res;
          count += 1;
          if (count === promises.length) {
            resolve(arr);
          }
        })
        .catch(reject);
    });
  });
};

# 实现Promise finally

_promise.prototype.finally = function (callback) {
  return this.then(
    (value) => {
      return Promise.resolve(callback()).then(() => {
        return value;
      });
    },
    (err) => {
      return Promise.resolve(callback()).then(() => {
        throw err;
      });
    }
  );
};

# 实现Promise race

_promise.prototype.race = function (promises) {
  return new Promise((resolve, reject) => {
    // 这里不需要使用索引,只要能循环出每一项就行

    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];
      promise.then(resolve, reject);
    }
  });
};

# 实现Promise any

_promise.prototype.any = function (promises) {
  let arr = [],
    count = 0;

  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(resolve, (err) => {
        arr[i] = { status: "rejected", val: err };
        count += 1;
        if (count === promises.length) {
          reject(new Error("没有promise成功"));
        }
      });
    });
  });
};

# 实现Promise allSettled

等所有异步操作结束了在继续执行下一步操作

_promise.prototype.allSettled = function (promises) {
  let arr = [],
    count = 0;
  return new Promise((resolve, reject) => {
    const processResult = (res, index, status) => {
      arr[index] = { status: status, val: res };
      count += 1;
      if (count === promises.length) {
        resolve(arr);
      }
    };

    promises.forEach((item, i) => {
      Promise.resolve(item).then(
        (res) => {
          processResult(res, i, "fulfilled");
        },
        (err) => {
          processResult(err, i, "rejected");
        }
      );
    });
  });
};

# 实现防抖(Debounce)

思路:不空则清

function debounce(fn, wait) {
  let timer = null;

  return function () {
    const context = this;
    const args = [].slice.call(arguments);

    if (timer !== null) {
      clearTimeout(timer);
    }

    timer = setTimeout(function () {
      fn.apply(context, args);
    }, wait);
  };
}

const test = debounce(fn, 3000);

立即执行:

function debounceNow(func, wait) {
  let timeout = null;

  return function () {
    if (timeout) clearTimeout(timeout);

    let callNow = !timeout;
    timeout = setTimeout(function () {
      timeout = null;
    }, wait);

    if (callNow) {
      func();
    }
  };
}

# 实现节流(Throttle)

两种实现方法,计时器和时间

计时器:没有则定义

function throttle(fn, duration) {
  var timer = null;

  return function () {
    const context = this;
    const args = [].slice.call(arguments);

    if (!timer) {
      timer = setTimeout(function () {
        fn.apply(context, ...args);
        timer = null;
      }, duration);
    }
  };
}

时间版本

function throttle(func, duration) {
  var oldTime = new Date().getTime();

  return function () {
    const context = this;
    const args = [].slice.call(arguments);

    var newTime = new Date().getTime();

    if (newTime - oldTime > duration) {
      func.apply(context, args);
      oldTime = newTime;
    }
  };
}

# 实现一个JS浅拷贝

function shallowCopy(src) {
  var dst = {};
  for (var prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}

obj.hasOwnProperty(..) 只会检查属性是否在 obj 对象中,不会检查 [[Prototype]] 原型链。

# 实现一个JS深拷贝

  • JSON.parse(JSON.stringify(obj)) 【弊端:继承的属性会丢失】因为在序列化JavaScript对象时,所有函数和原型成员会被有意忽略
  • 递归实现
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1);
}

function clone(target) {
  //判断拷贝的数据类型
  //初始化变量result 成为最终克隆的数据

  // P.S  此处如果是使用Array.isArray()来判断那么必须要放在 typeof obj === 'object'之前
  let result, targetType = checkedType(target);
  if (targetType === 'Object') {
    result = {};
  } else if (targetType === 'Array') {
    result = [];
  } else {
    return target;
  }

  //遍历目标数据
  for (let i in target) {
    //获取遍历数据结构的每一项值。
    let value = target[i];
    //判断目标结构里的每一值是否存在对象/数组
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
      //对象/数组里嵌套了对象/数组
      //继续遍历获取到value值
      result[i] = clone(value);
    } else {
      //获取到value值是基本的数据类型或者是函数。
      result[i] = value;
    }
  }
  return result;
}

# 实现一个instanceOf

instanceof 运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

function instanceOf(left, right) {
    // 获得类型的原型
    let prototype = right.prototype
    // 获得对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
    	if (left === null)
    		return false
    	if (prototype === left)
    		return true
    	left = left.__proto__
    }
}

# 实现一个flat方法

方法1:

function findType(data) {
  return Object.prototype.toString.call(data).slice(8, -1);
}

function flatMap(arr) {
  let newArray = [];
  for (let i = 0; i < arr.length; i++) {
    const item = arr[index];

    if (Array.isArray(item)) {
      newArray = newArray.concat(method(item));
    } else {
      newArray = newArray.concat(item);
    }
  }
  return newArray;
}

方法2

function flatMap(arr) {
  //利用ES6提供的flat方法
  return arr.flat(Infinity);
}

# 实现一个 obj={a:0} 每次取属性a 值+1

const obj = {
  a: 0
}

let sum = 0

Object.defineProperty(obj, 'a', {
  get() {
    return sum++
  }
})

console.log(obj.a);
console.log(obj.a);
console.log(obj.a);

参考 (opens new window)

# 实现原生Ajax

// 原生XMLHttpRequest对象
let req = new XMLHttpRequest();

req.onreadystatechange = function () {
  if (req.readyState === 4) {
    // do something
    // console.log(req.getAllResponseHeaders());
    console.log(req.responseText);
  }
}

req.open('GET', 'https://www.baidu.com', true);
req.setRequestHeader("Content-Type", "application/javascript;charset=UTF-8");
// req.send(null)  //GET 请求发送空Body
req.send({
  name: 12
})

# 实现二叉树广度优先遍历

function bfs(tree) {
  const queue = [];
  const value = [];
  queue.push(tree);

  while (queue.length !== 0) {
    const item = queue.shift();

    if (item.left) {
      queue.push(item.left);
    }

    if (item.right) {
      queue.push(item.right);
    }

    value.push(item.value);
  }

  return value;
}

带树的深度

function bfs(tree) {
  const queue = [];
  const value = [];
  queue.push(tree);
  let index = 0;

  while (queue.length !== 0) {
    const parents = queue.splice(0);

    // 遍历本层的节点
    for (let i = 0, l = parents.length; i < l; i++) {
      let item = parents[i];
      value.push(item.value);

      if (item.left) {
        queue.push(item.left);
      }

      if (item.right) {
        queue.push(item.right);
      }
    }

    index++;
  }

  console.log("index", index);
  return value;
}

console.log(bfs(tree));

# 实现二叉树深度优先遍历

  • 前序遍历 根左右
  • 中序遍历 左根右
  • 后序遍历 左右根
function dfs(tree) {
  const value = [];
  function travel(node) {
    value.push(node.value); // 前序遍历 根左右

    if (node.left) {
      travel(node.left);
    }

    // value.push(node.value); 中序遍历 左根右

    if (node.right) {
      travel(node.right);
    }

    // value.push(node.value); 后序遍历 左右根
  }

  travel(tree);
  return value;
}

console.log(dfs(tree));

# 实现cookie的 读 写 删除

cookie 读

function getCookie(cname) {
  let name = cname + "=";
  let ca = document.cookie.split(";");
  for (let i = 0; i < ca.length; i++) {
    let c = ca[i].trim();
    if (c.indexOf(name) === 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}

cookie 写

function setCookie(name, value) {
  let Days = 30;
  let exp = new Date();
  exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
  document.cookie =
    name + "=" + escape(value) + ";expires=" + exp.toGMTString();
}

cookie 删除

function deleteCookie(name) {
  let exp = new Date();
  exp.setTime(exp.getTime() - 1);
  let cval = getCookie(name);
  if (cval != null)
    document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
}

# 实现数字金额千分位

function toThousands(number) {
  let string = number.toString();
  let result = '';

  while(string.length > 3) {
    result = ',' + string.slice(-3) + result;
    string = string.slice(0, string.length - 3);
  }

  if(string) {
    result = string + result;
  }

  return result
}

# 实现 getUrlParams(url,key) 获取url的某个参数的问题


# 实现 lastPromise 最后一个promise的结果

思路:和debounce一样做延迟计算

let count = 1;

let promiseFunction = () =>
  new Promise((rs) =>
    window.setTimeout(() => {
      rs(count++);
    })
  );

function lastPromise(fn) {
  let timer = null;
  return async (...args) => {
    const response = await fn(...args);
    clearTimeout(timer);

    return new Promise((resolve) => {
      timer = setTimeout(() => {
        resolve(response);
      }, 0);
    });
  };
}

let lastFn = lastPromise(promiseFunction);
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 3

# 实现并发控制(任务队列) 无缓存

思路:构建task任务对象和任务队列

function SuperTask(maxTaskNumber = 2) {
  this.taskQueue = [];
  this.runTaskNumber = 0;
  this.maxTaskNumber = maxTaskNumber;
}

SuperTask.prototype.add = function (fn) {
  return new Promise((resolve, reject) => {
    const task = () => {
      return fn().then(() => {
        resolve();
      });
    };

    this.taskQueue.push(task);
    this.run();
  });
};

SuperTask.prototype.run = function () {
  console.log(this.taskQueue);

  while (this.runTaskNumber < this.maxTaskNumber && this.taskQueue.length) {
    this.runTaskNumber++;
    const task = this.taskQueue.shift();
    task().then(() => {
      this.runTaskNumber--;
      this.run();
    });
  }
};

function timeout(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

const superTask = new SuperTask();

function addTask(time, name) {
  superTask
    .add(() => timeout(time))
    .then(() => {
      console.log(`任务${name}完成`);
    });
}

addTask(10000, 1); // 10000ms后输出 任务1完成
addTask(5000, 2); // 5000ms后输出 任务2完成
addTask(3000, 3); // 8000ms后输出 任务3完成
addTask(4000, 4); // 12000ms后输出 任务4完成
addTask(5000, 5); // 15000ms后输出 任务5完成

# 实现 前端并发10个相同的请求,怎么控制为只发一个请求

思路:采取队列的方式,异步任务进入队列

function cacheAsync(fn, symbol) {
  const cache = new Map();
  const never = Symbol();

  return async function (params) {
    return new Promise((resolve, reject) => {
      symbol = params || symbol;
      let cacheCfg = cache.get(symbol);

      if (!cacheCfg) {
        cacheCfg = {
          hit: never,
          executor: [{ resolve, reject }],
        };
        cache.set(symbol, cacheCfg);
      } else {
        // 命中缓存
        if (cacheCfg.hit !== never) {
          return resolve(cacheCfg.hit);
        }
        cacheCfg.executor.push({ resolve, reject });
      }

      const { executor } = cacheCfg;

      // 处理并发,在请求还处于pending过程中就发起了相同的请求
      // 拿第一个请求
      if (executor.length === 1) {
        const next = async () => {
          try {
            if (!executor.length) return;
            const response = await fn(params);
            // 如果成功了,那么直接resolve掉剩余同样的请求
            while (executor.length) {
              // 清空
              executor.shift().resolve(response);
            }
            // 缓存结果
            cacheCfg.hit = response;
          } catch (error) {
            // 如果失败了 那么这个promise的则为reject
            const { reject } = executor.shift();
            reject(error);
            next(); // 失败重试,降级为串行
          }
        };
        next();
      }
    });
  };
}

var fetch2 = cacheAsync(fetchData, "test2");

fetch2(2); // 编号 1
fetch2(2); // 2
fetch2(2); // 3
fetch2(2); // 4
fetch2(2); // 4
fetch2(2); // 5

# 实现一个批量请求函数 multiRequest(urls, maxNum)

要求实现:

  • 要求最大并发数 maxNum
  • 每当有一个请求返回,就留下一个空位,可以增加新的请求
  • 所有请求完成后,结果按照 urls 里面的顺序依次打出

思路:和实现并发控制(任务队列)的思路类似,采用任务队列

class Task {
  constructor(max) {
    this.maxNums = max;
    this.list = [];
    this.runNums = 0;
  }

  add(fn, index) {
    return new Promise((resolve, reject) => {
      const task = () => {
        return fn()
          .then((res) => res.json())
          .then((res) => {
            const data = res;
            resolve({
              index: index,
              data: data,
            });
          });
      };

      this.list.push(task);
      this.run();
    });
  }

  run() {
    while (this.runNums < this.maxNums && this.list.length !== 0) {
      this.runNums++;
      const task = this.list.shift();
      task().then(() => {
        this.runNums--;
        this.run();
      });
    }
  }
}

function multiRequest(urls = [], maxNum) {
  return new Promise((resolve, reject) => {
    let count = 0;
    const task = new Task(maxNum);
    let result = [];
    for (let i = 0; i < urls.length; i++) {
      const url = urls[i];
      task
        .add(() => fetch(url), i)
        .then((res) => {
          count++;
          result[i] = res;

          if (count === urls.length) {
            resolve(result);
          }
        });
    }
  });
}

const urls = [
  "http://127.0.0.1:3000/test/1",
  "http://127.0.0.1:3000/test/2",
  "http://127.0.0.1:3000/test/3",
  "http://127.0.0.1:3000/test/4",
  "http://127.0.0.1:3000/test/5",
  "http://127.0.0.1:3000/test/6",
];

async function run() {
  const r = await multiRequest(urls, 3);
  console.log("r", r);
}

run();

# 实现 10 个 Ajax 同时发起请求,全部返回展示结果,并且至多允许三次失败,说出设计思路

const send = async (list) => {
  let success = 0;
  let fail = 0;

  const requestList = []

  for (let index = 0; index < list.length; index++) {
    const url = list[index];

    const p = new Promise((resolve, reject) => {
      const Ajax = new XMLHttpRequest();
      Ajax.open('get', url)
      Ajax.onreadystatechange = (() => {
        if (Ajax.readyState === 4) {
          // console.log(Ajax.response);
          // console.log(Ajax.status);
          if (Ajax.status === 200) {
            success++;
            resolve({
              success: true,
              data: 1
            })
          } else {
            fail++;
            resolve({    //<== 此处不要使用reject
              success: false,
              data: 'error'
            })
          }
        }
      })
      Ajax.send(null)
    })

    requestList.push(p)
  }

  const r = await Promise.all(
    requestList
  ).catch((error) => {
    console.log('promise error', error)
    return '不会运行到此处'
  })

  return {
    data: r,
    success,
    fail
  };
}

# 实现LRU

let cacheMap = new Map();
const cacheLength = 5;

function lru(key, value) {

  if (cacheMap.has(key)) {
    cacheMap.delete(key);
  }

  cacheMap.set(key, value);

  if (cacheMap.size > cacheLength) {
    const oldKey = Array.from(cacheMap.keys()).shift().toString();
    cacheMap.delete(oldKey);
  }

  console.log('cacheMap', cacheMap);
}

lru('1', '1');
lru('2', '2');
lru('3', '3');
lru('4', '4');
lru('3', '3');
lru('2', '2');
lru('6', '6');

console.log(cacheMap);

# 实现数据的双向绑定

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property);
      return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
      setBind(value, property);
      return Reflect.set(target, property, value);
    },
  };
  return new Proxy(obj, handler);
};

let obj = { a: 1 };
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`);
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`);
  }
);

p.a = 2; // 监听到属性a改变
p.a; // 'a' = 2

# 实现一个懒加载

两种方式

  • 方法1: 计算高度 element.offsetTop < window.innerHeight + document.body.scrollTop
  • 方法2: 计算高度 使用getBoundingClientRect().top < window.innerHeight
  • 方法3: Intersection Observer API

# 实现a,b的值的交换

两种方式

c = a
a = b
b = c

方法2

a = a + b
b = a - b
a = a - b

# 实现数组去重

const arr1 = [1, 6, 6, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
const arr2 = [];

function uniqueArray1(array) {
  const newArray = [];
  for (let i = 0; i < array.length; i++) {
    if (array.indexOf(array[i]) === i) {
      newArray.push(array[i]);
    }
  }
  return newArray;
}

function uniqueArray2(array) {
  return [...new Set(array)];
}

function uniqueArray3(array) {
  let map = {};
  let res = [];

  for (var i = 0; i < array.length; i++) {
    if (!map[array[i]]) {
      map[array[i]] = true;
      res.push(array[i]);
    }
  }
  return res;
}

console.log(uniqueArray1(arr1));
console.log(uniqueArray2(arr1));
console.log(uniqueArray3(arr1));

# 实现数组元素求和

const arr = [1, 2, 3, [[4, 5], 6], 7, 8, 9];

function sum(array) {
  let num = 0;
  for (let index = 0; index < array.length; index++) {
    const element = array[index];

    if (typeof element === "number") {
      num = element + num;
    } else {
      num += sum(element);
    }
  }
  return num;
}

console.log(sum(arr));

# 实现一个 EventEmitter 类

const EventEmitter = {
  events: {},
  on(event, listener) {
    if (typeof this.events[event] !== "object") {
      this.events[event] = [];
    }
    this.events[event].push(listener);
    return () => this.removeListener(event, listener);
  },
  removeListener(event, listener) {
    if (typeof this.events[event] === "object") {
      const idx = this.events[event].indexOf(listener);
      if (idx > -1) {
        this.events[event].splice(idx, 1);
      }
    }
  },
  emit(event, ...args) {
    if (typeof this.events[event] === "object") {
      this.events[event].forEach((listener) => listener.apply(this, args));
    }
  },
  once(event, listener) {
    const remove = this.on(event, (...args) => {
      remove();
      listener.apply(this, args);
    });
  },
};

export default EventEmitter;

# 实现遍历DOM树

function traversal(node) {
  //对node的处理
  if (node && node.nodeType === 1) {
    console.log(node.tagName);
  }

  let childNodes = node.childNodes;
  let item;

  for (let index = 0; index < childNodes.length; index++) {
    item = childNodes[index];
    if (item.nodeType === 1) {
      //递归先序遍历子节点
      traversal(item);
    }
  }
}

Node.ELEMENT_NODE 1 元素节点

# 实现 compose 函数

function compose(...func) {
  if(func.length === 0) {
    return args => args;
  }

  if(func.length === 1) {
    return func[0];
  }

  //return funcs.reduce((a, b) => (...args) => a(b(...args)));
  return func.reduce((a, b) => {
    return (...args) => {
      return a(b(...args));
    };
  });
}

# 实现 async 函数

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里

核心

step(function () {
  return gen.next();
});
function async(genF) {
  return new Promise(function (resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch (e) {
        return reject(e);
      }
      if (next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(
        function (v) {
          step(function () {
            return gen.next(v);
          });
        },
        function (e) {
          step(function () {
            return gen.throw(e);
          });
        }
      );
    }
    step(function () {
      return gen.next(undefined);
    });
  });
}

# 实现 co 函数

//co的实现
function run(fn, ...args) {
  return new Promise((resolve, reject) => {
    let it;
    // fn必须是一个函数
    if (typeof fn === "function") {
      it = fn(...args);
    }
    // fn返回的对象必须是一个迭代器
    if (!it || typeof it.next !== "function") {
      return resolve(it);
    }

    onResolved();

    function onResolved(value) {
      let res;

      try {
        res = it.next(value);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function onRejected(reason) {
      let res;

      try {
        res = it.throw(reason);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // 将结果转换为Promise,然后进行统一处理
      Promise.resolve(res.value).then(onResolved, onRejected);
    }
  });
}

# 实现一个memorize函数

参考lodash的实现:

/**
 * @param {Function} func The function to have its output memoized.
 * @param {Function} [resolver] The function to resolve the cache key.
 * @returns {Function} Returns the new memoized function.
 */

function memoize(func, resolver) {
  // 异常过滤
  if (typeof func !== "function" || (resolver != null && typeof resolver !== "function")) {
    throw new TypeError("Expected a function")
  }

  // 返回函数 memoized
  const memoized = function (...args) {
    // 获取到 key:resolver存在则取函数执行结果,否则取参数第一位
    const key = resolver ? resolver.apply(this, args) : args[0]

    // 缓存判断
    // 该写法是直接把缓存对象挂载到函数对象属性cache上
    const cache = memoized.cache
    if (cache.has(key)) {
      return cache.get(key)
    }

    // 执行函数得到 result
    const result = func.apply(this, args)

    // 保存到缓存对象 cache 上
    memoized.cache = cache.set(key, result) || cache
    return result
  }

  // 指定 cache实例,默认为 Map
  memoized.cache = new (memoize.Cache || Map)()
  return memoized
}

// 可指定 缓存对象实例
// 例如:替换 memoize.Cache = WeakMap;
memoize.Cache = Map

export default memoize

# 实现一个asyncTry函数

方法一:

function fn() {
  return new Promise((resolve, reject) => {
    const r = Math.random();

    if (r < 0.3) {
      resolve("success");
    } else {
      reject("error");
    }
  });
}

function asyncRetry(fn, time) {
  return new Promise((resolve, reject) => {
    let count = 0;

    function run() {
      fn()
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          count++;
          if (count >= time) {
            reject(err);
          } else {
            run();
          }
        });
    }

    run();
  });
}

asyncRetry(fn, 3).then(console.log, console.log);

方法二:利用async/await能够阻塞for循环

async function asyncRetry(fn, time) {
  for (let i = 0; i < time; i++) {
    try {
      const res = await fn();
      return res;
    } catch (error) {
      if (i === time - 1) {
        throw error;
      }
    }
  }
}

asyncRetry(fn, 3).then(console.log, console.log);

# 实现Tom函数

class Tom {
  constructor() {
    this.list = [];
    setTimeout(() => {
      this.run();
    }, 0);
  }

  eat(string) {
    const fn = () => {
      console.log(string);
      this.run();
    };

    this.list.push(fn);
    return this;
  }

  sleep(time) {
    const fn = () => {
      setTimeout(() => {
        console.log("run xx");
        this.run();
      }, time * 1000);
    };
    this.list.push(fn);
    return this;
  }

  run() {
    const task = this.list.shift();
    if (task) {
      task();
    }
  }
}

// 实现Tom函数 满足以下的调用
const tom = new Tom();
tom.eat("123").sleep(3).eat("456").eat("789");

陕ICP备20004732号-3