实现迭代器生成函数

咱们说迭代器对象全凭迭代器生成函数帮咱们生成。在ES6中,实现一个迭代器生成函数并不是什么难事儿,因为ES6早帮咱们思考好了全套的解决方案,内置了贴心的 生成器Generator)供咱们应用:

// 编写一个迭代器生成函数function *iteratorGenerator() {    yield '1号选手'    yield '2号选手'    yield '3号选手'}const iterator = iteratorGenerator()iterator.next()iterator.next()iterator.next()

丢进控制台,不负众望:

写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背地的实现逻辑更感兴趣。上面咱们要做的,不仅仅是写一个迭代器对象,而是用ES5去写一个可能生成迭代器对象的迭代器生成函数(解析在正文里):

// 定义生成器函数,入参是任意汇合function iteratorGenerator(list) {    // idx记录以后拜访的索引    var idx = 0    // len记录传入汇合的长度    var len = list.length    return {        // 自定义next办法        next: function() {            // 如果索引还没有超出汇合长度,done为false            var done = idx >= len            // 如果done为false,则能够持续取值            var value = !done ? list[idx++] : undefined            // 将以后值与遍历是否结束(done)返回            return {                done: done,                value: value            }        }    }}var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])iterator.next()iterator.next()iterator.next()

此处为了记录每次遍历的地位,咱们实现了一个闭包,借助自在变量来做咱们的迭代过程中的“游标”。

运行一下咱们自定义的迭代器,后果合乎预期:

实现一个call

call做了什么:

  • 将函数设为对象的属性
  • 执行&删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);//实现一个call办法:Function.prototype.myCall = function(context) {  //此处没有思考context非object状况  context.fn = this;  let args = [];  for (let i = 1, len = arguments.length; i < len; i++) {    args.push(arguments[i]);  }  context.fn(...args);  let result = context.fn(...args);  delete context.fn;  return result;};

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) : resolve(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 < len; i++) {        // Promise.resolve()解决,确保每一个都是promise实例        Promise.resolve(promiseArr[i]).then(          val => {            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),        )      })    })  }}

查找文章中呈现频率最高的单词

function findMostWord(article) {  // 合法性判断  if (!article) return;  // 参数解决  article = article.trim().toLowerCase();  let wordList = article.match(/[a-z]+/g),    visited = [],    maxNum = 0,    maxWord = "";  article = " " + wordList.join("  ") + " ";  // 遍历判断单词呈现次数  wordList.forEach(function(item) {    if (visited.indexOf(item) < 0) {      // 退出 visited       visited.push(item);      let word = new RegExp(" " + item + " ", "g"),        num = article.match(word).length;      if (num > maxNum) {        maxNum = num;        maxWord = item;      }    }  });  return maxWord + "  " + maxNum;}

实现apply办法

apply原理与call很类似,不多赘述

// 模仿 applyFunction.prototype.myapply = function(context, arr) {  var context = Object(context) || window;  context.fn = this;  var result;  if (!arr) {    result = context.fn();  } else {    var args = [];    for (var i = 0, len = arr.length; i < len; i++) {      args.push("arr[" + i + "]");    }    result = eval("context.fn(" + args + ")");  }  delete context.fn;  return result;};

手写 Object.create

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

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

参考 前端进阶面试题具体解答

手写 bind 函数

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 保留以后函数的援用,获取其余传入参数值。
  3. 创立一个函数返回
  4. 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
// bind 函数实现Function.prototype.myBind = function(context) {  // 判断调用对象是否为函数  if (typeof this !== "function") {    throw new TypeError("Error");  }  // 获取参数  var args = [...arguments].slice(1),      fn = this;  return function Fn() {    // 依据调用形式,传入不同绑定值    return fn.apply(      this instanceof Fn ? this : context,      args.concat(...arguments)    );  };};

深拷贝

递归的残缺版本(思考到了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;}

字符串解析问题

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 < str.length; i++) {        if (str[i] === '{') {            flag = true;            start = i + 1;            continue;        }        if (!flag) res += str[i];        else {            if (str[i] === '}') {                flag = false;                res += match(str.slice(start, i), obj);            }        }    }    return res;}// 对象匹配操作const match = (str, obj) => {    const keys = str.split('.').slice(1);    let index = 0;    let o = obj;    while (index < keys.length) {        const key = keys[index];        if (!o[key]) {            return `{${str}}`;        } else {            o = o[key];        }        index++;    }    return o;}

解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 后果{ user: 'anonymous',  id: [ 123, 456 ], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型  city: '北京', // 中文需解码  enabled: true, // 未指定值得 key 约定为 true}*/
function parseParam(url) {  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来  const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中  let paramsObj = {};  // 将 params 存到对象中  paramsArr.forEach(param => {    if (/=/.test(param)) { // 解决有 value 的参数      let [key, val] = param.split('='); // 宰割 key 和 value      val = decodeURIComponent(val); // 解码      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值        paramsObj[key] = [].concat(paramsObj[key], val);      } else { // 如果对象没有这个 key,创立 key 并设置值        paramsObj[key] = val;      }    } else { // 解决没有 value 的参数      paramsObj[param] = true;    }  })  return paramsObj;}

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {  if (this == undefined) {    throw new TypeError('this is null or not defined');  }  if (typeof callback !== 'function') {    throw new TypeError(callback + ' is not a function');  }  const res = [];  // 同理  const O = Object(this);  const len = O.length >>> 0;  for (let i = 0; i < len; i++) {    if (i in O) {      // 调用回调函数并传入新数组      res[i] = callback.call(thisArg, O[i], i, this);    }  }  return res;}

递归反转链表

// node节点class Node {  constructor(element,next) {    this.element = element    this.next = next  } }class LinkedList { constructor() {   this.head = null // 默认应该指向第一个节点   this.size = 0 // 通过这个长度能够遍历这个链表 } // 减少O(n) add(index,element) {   if(arguments.length === 1) {     // 向开端增加     element = index // 以后元素等于传递的第一项     index = this.size // 索引指向最初一个元素   }  if(index < 0 || index > this.size) {    throw new Error('增加的索引不失常')  }  if(index === 0) {    // 间接找到头部 把头部改掉 性能更好    let head = this.head    this.head = new Node(element,head)  } else {    // 获取以后头指针    let current = this.head    // 不停遍历 直到找到最初一项 增加的索引是1就找到第0个的next赋值    for (let i = 0; i < index-1; i++) { // 找到它的前一个      current = current.next    }    // 让创立的元素指向上一个元素的下一个    // 看图了解next层级 ![](http://img-repo.poetries.top/images/20210522115056.png)    current.next = new Node(element,current.next) // 让以后元素指向下一个元素的next  }  this.size++; } // 删除O(n) remove(index) {  if(index < 0 || index >= this.size) {    throw new Error('删除的索引不失常')  }  this.size--  if(index === 0) {    let head = this.head    this.head = this.head.next // 挪动指针地位    return head // 返回删除的元素  }else {    let current = this.head    for (let i = 0; i < index-1; i++) { // index-1找到它的前一个      current = current.next    }    let returnVal = current.next // 返回删除的元素    // 找到待删除的指针的上一个 current.next.next     // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可    current.next = current.next.next     return returnVal  } } // 查找O(n) get(index) {  if(index < 0 || index >= this.size) {    throw new Error('查找的索引不失常')  }  let current = this.head  for (let i = 0; i < index; i++) {    current = current.next  }  return current } reverse() {  const reverse = head=>{    if(head == null || head.next == null) {      return head    }    let newHead = reverse(head.next)    // 从这个链表的最初一个开始反转,让以后下一个元素的next指向本人,本人指向null    // ![](http://img-repo.poetries.top/images/20210522161710.png)    // 刚开始反转的是最初两个    head.next.next = head    head.next = null    return newHead  }  return reverse(this.head) }}let ll = new LinkedList()ll.add(1)ll.add(2)ll.add(3)ll.add(4)// console.dir(ll,{depth: 1000})console.log(ll.reverse())

实现数组的map办法

Array.prototype._map = function(fn) {   if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        res.push(fn(this[i]));    }    return res;}

实现一个 sleep 函数,比方 sleep(1000) 意味着期待1000毫秒

// 应用 promise来实现 sleepconst sleep = (time) => {  return new Promise(resolve => setTimeout(resolve, time))}sleep(1000).then(() => {  // 这里写你的骚操作})

判断是否是电话号码

function isPhone(tel) {    var regx = /^1[34578]\d{9}$/;    return regx.test(tel);}

图片懒加载

// <img src="default.png" data-src="https://xxxx/real.png">function isVisible(el) {  const position = el.getBoundingClientRect()  const windowHeight = document.documentElement.clientHeight  // 顶部边缘可见  const topVisible = position.top > 0 && position.top < windowHeight;  // 底部边缘可见  const bottomVisible = position.bottom < windowHeight && position.bottom > 0;  return topVisible || bottomVisible;}function imageLazyLoad() {  const images = document.querySelectorAll('img')  for (let img of images) {    const realSrc = img.dataset.src    if (!realSrc) continue    if (isVisible(img)) {      img.src = realSrc      img.dataset.src = ''    }  }}// 测试window.addEventListener('load', imageLazyLoad)window.addEventListener('scroll', imageLazyLoad)// orwindow.addEventListener('scroll', throttle(imageLazyLoad, 1000))

模仿Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。

// 模仿 Object.createfunction create(proto) {  function F() {}  F.prototype = proto;  return new F();}

Promise并行限度

就是实现有并行限度的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; i++) {      this.request();    }  }  request() {    if (!this.queue || !this.queue.length || this.runCounts >= 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

实现防抖函数(debounce)

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则从新计时。

那么与节流函数的区别间接看这个动画实现即可。

手写简化版:

// 防抖函数const debounce = (fn, delay) => {  let timer = null;  return (...args) => {    clearTimeout(timer);    timer = setTimeout(() => {      fn.apply(this, args);    }, delay);  };};

实用场景:

  • 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
  • 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似

生存环境请用lodash.debounce

Object.is

Object.is解决的次要是这两个问题:

+0 === -0  // trueNaN === NaN // false
const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}