实现 add(1)(2)(3)

函数柯里化概念: 柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。

1)粗犷版

function add (a) {return function (b) {     return function (c) {      return a + b + c;     }}}console.log(add(1)(2)(3)); // 6

2)柯里化解决方案

  • 参数长度固定
var add = function (m) {  var temp = function (n) {    return add(m + n);  }  temp.toString = function () {    return m;  }  return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43

对于add(3)(4)(5),其执行过程如下:

  1. 先执行add(3),此时m=3,并且返回temp函数;
  2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
  3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
  4. 因为前面没有传入参数,等于返回的temp函数不被执行而是打印,理解JS的敌人都晓得对象的toString是批改对象转换字符串的办法,因而代码中temp函数的toString函数return m值,而m值是最初一步执行函数时的值m=12,所以返回值是12。
  5. 参数长度不固定
function add (...args) {    //求和    return args.reduce((a, b) => a + b)}function currying (fn) {    let args = []    return function temp (...newArgs) {        if (newArgs.length) {            args = [                ...args,                ...newArgs            ]            return temp        } else {            let val = fn.apply(this, args)            args = [] //保障再次调用时清空            return val        }    }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)())  //15console.log(addCurry(1)(2)(3, 4, 5)())  //15console.log(addCurry(1)(2, 3, 4, 5)())  //15

基于Generator函数实现async/await原理

外围:传递给我一个Generator函数,把函数中的内容基于Iterator迭代器的特点一步步的执行
function readFile(file) {    return new Promise(resolve => {        setTimeout(() => {            resolve(file);    }, 1000);    })};function asyncFunc(generator) {    const iterator = generator(); // 接下来要执行next  // data为第一次执行之后的返回后果,用于传给第二次执行  const next = (data) => {        let { value, done } = iterator.next(data); // 第二次执行,并接管第一次的申请后果 data    if (done) return; // 执行结束(到第三次)间接返回    // 第一次执行next时,yield返回的 promise实例 赋值给了 value    value.then(data => {      next(data); // 当第一次value 执行结束且胜利时,执行下一步(并把第一次的后果传递下一步)    });  }  next();};asyncFunc(function* () {    // 生成器函数:控制代码一步步执行   let data = yield readFile('a.js'); // 等这一步骤执行执行胜利之后,再往下走,没执行完的时候,间接返回  data = yield readFile(data + 'b.js');  return data;})

Function.prototype.apply()

第一个参数是绑定的this,默认为window,第二个参数是数组或类数组

Function.prototype.apply = function(context = window, args) {  if (typeof this !== 'function') {    throw new TypeError('Type Error');  }  const fn = Symbol('fn');  context[fn] = this;  const res = context[fn](...args);  delete context[fn];  return res;}

请实现一个 add 函数,满足以下性能

add(1);             // 1add(1)(2);      // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(1, 2, 3); // 6
function add(...args) {  // 在外部申明一个函数,利用闭包的个性保留并收集所有的参数值  let fn = function(...newArgs) {   return add.apply(null, args.concat(newArgs))  }  // 利用toString隐式转换的个性,当最初执行时隐式转换,并计算最终的值返回  fn.toString = function() {    return args.reduce((total,curr)=> total + curr)  }  return fn}

考点:

  • 应用闭包, 同时要对JavaScript 的作用域链(原型链)有深刻的了解
  • 重写函数的 toSting()办法
// 测试,调用toString办法触发求值add(1).toString();             // 1add(1)(2).toString();      // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(1, 2, 3).toString(); // 6

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

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

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

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

二叉树档次遍历

// 二叉树档次遍历class Node {  constructor(element, parent) {    this.parent = parent // 父节点     this.element = element // 以后存储内容    this.left = null // 左子树    this.right = null // 右子树  }}class BST {  constructor(compare) {    this.root = null // 树根    this.size = 0 // 树中的节点个数    this.compare = compare || this.compare  }  compare(a,b) {    return a - b  }  add(element) {    if(this.root === null) {      this.root = new Node(element, null)      this.size++      return    }    // 获取根节点 用以后增加的进行判断 放右边还是放左边    let currentNode = this.root     let compare    let parent = null     while (currentNode) {      compare = this.compare(element, currentNode.element)      parent = currentNode // 先将父亲保存起来      // currentNode要不停的变动      if(compare > 0) {        currentNode = currentNode.right      } else if(compare < 0) {        currentNode = currentNode.left      } else {        currentNode.element = element // 相等时 先笼罩后续解决      }    }    let newNode = new Node(element, parent)    if(compare > 0) {      parent.right = newNode    } else if(compare < 0) {      parent.left = newNode    }    this.size++  }  // 档次遍历 队列  levelOrderTraversal(visitor) {    if(this.root == null) {      return    }    let stack = [this.root]    let index = 0 // 指针 指向0    let currentNode     while (currentNode = stack[index++]) {      // 反转二叉树      let tmp = currentNode.left      currentNode.left = currentNode.right      currentNode.right = tmp      visitor.visit(currentNode.element)      if(currentNode.left) {        stack.push(currentNode.left)      }      if(currentNode.right) {        stack.push(currentNode.right)      }    }  }}
// 测试var bst = new BST((a,b)=>a.age-b.age) // 模仿sort办法// ![](http://img-repo.poetries.top/images/20210522203619.png)// ![](http://img-repo.poetries.top/images/20210522211809.png)bst.add({age: 10})bst.add({age: 8})bst.add({age:19})bst.add({age:6})bst.add({age: 15})bst.add({age: 22})bst.add({age: 20})// 应用访问者模式class Visitor {  constructor() {    this.visit = function (elem) {      elem.age = elem.age*2    }  }}// ![](http://img-repo.poetries.top/images/20210523095515.png)console.log(bst.levelOrderTraversal(new Visitor()))

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),        )      })    })  }}

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

let ul = document.getElementById("container");// 插入十万条数据let total = 100000;// 一次插入 20 条let once = 20;//总页数let page = total / once;//每条记录的索引let index = 0;//循环加载数据function loop(curTotal, curIndex) {  if (curTotal <= 0) {    return false;  }  //每页多少条  let pageCount = Math.min(curTotal, once);  window.requestAnimationFrame(function () {    for (let i = 0; i < pageCount; i++) {      let li = document.createElement("li");      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);      ul.appendChild(li);    }    loop(curTotal - pageCount, curIndex + pageCount);  });}loop(total, index);

扩大思考 :对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了,虚构列表和虚构表格在日常我的项目应用还是很多的

批改嵌套层级很深对象的 key

// 有一个嵌套档次很深的对象,key 都是 a_b 模式 ,须要改成 ab 的模式,留神不能用递归。const a = {  a_y: {    a_z: {      y_x: 6    },    b_c: 1  }}// {//   ay: {//     az: {//       yx: 6//     },//     bc: 1//   }// }

办法1:序列化 JSON.stringify + 正则匹配

const regularExpress = (obj) => {  try {    const str = JSON.stringify(obj).replace(/_/g, "");    return JSON.parse(str);  } catch (error) {    return obj;  }};;

办法2:递归

const recursion = (obj) => {  const keys = Object.keys(obj);  keys.forEach((key) => {    const newKey = key.replace(/_/g, "");    obj[newKey] = recursion(obj[key]);    delete obj[key];  });  return obj;};

实现一个治理本地缓存过期的函数

封装一个能够设置过期工夫的localStorage存储函数
class Storage{  constructor(name){      this.name = 'storage';  }  //设置缓存  setItem(params){      let obj = {          name:'', // 存入数据  属性          value:'',// 属性值          expires:"", // 过期工夫          startTime:new Date().getTime()//记录何时将值存入缓存,毫秒级      }      let options = {};      //将obj和传进来的params合并      Object.assign(options,obj,params);      if(options.expires){      //如果options.expires设置了的话      //以options.name为key,options为值放进去          localStorage.setItem(options.name,JSON.stringify(options));      }else{      //如果options.expires没有设置,就判断一下value的类型          let type = Object.prototype.toString.call(options.value);          //如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去          if(Object.prototype.toString.call(options.value) == '[object Object]'){              options.value = JSON.stringify(options.value);          }          if(Object.prototype.toString.call(options.value) == '[object Array]'){              options.value = JSON.stringify(options.value);          }          localStorage.setItem(options.name,options.value);      }  }  //拿到缓存  getItem(name){      let item = localStorage.getItem(name);      //先将拿到的试着进行json转为对象的模式      try{          item = JSON.parse(item);      }catch(error){      //如果不行就不是json的字符串,就间接返回          item = item;      }      //如果有startTime的值,阐明设置了生效工夫      if(item.startTime){          let date = new Date().getTime();          //何时将值取出减去刚存入的工夫,与item.expires比拟,如果大于就是过期了,如果小于或等于就还没过期          if(date - item.startTime > item.expires){          //缓存过期,革除缓存,返回false              localStorage.removeItem(name);              return false;          }else{          //缓存未过期,返回值              return item.value;          }      }else{      //如果没有设置生效工夫,间接返回值          return item;      }  }  //移出缓存  removeItem(name){      localStorage.removeItem(name);  }  //移出全副缓存  clear(){      localStorage.clear();  }}

用法

let storage = new Storage();storage.setItem({  name:"name",  value:"ppp"})

上面我把值取出来

let value = storage.getItem('name');console.log('我是value',value);
设置5秒过期
let storage = new Storage();storage.setItem({  name:"name",  value:"ppp",  expires: 5000})
// 过期后再取出来会变为 falselet value = storage.getItem('name');console.log('我是value',value);

判断括号字符串是否无效(小米)

题目形容

给定一个只包含 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否无效。无效字符串需满足:- 左括号必须用雷同类型的右括号闭合。- 左括号必须以正确的程序闭合。示例 1:输出:s = "()"输入:true示例 2:输出:s = "()[]{}"输入:true示例 3:输出:s = "(]"输入:false

答案

const isValid = function (s) {  if (s.length % 2 === 1) {    return false;  }  const regObj = {    "{": "}",    "(": ")",    "[": "]",  };  let stack = [];  for (let i = 0; i < s.length; i++) {    if (s[i] === "{" || s[i] === "(" || s[i] === "[") {      stack.push(s[i]);    } else {      const cur = stack.pop();      if (s[i] !== regObj[cur]) {        return false;      }    }  }  if (stack.length) {    return false;  }  return true;};

实现 (5).add(3).minus(2) 性能

例: 5 + 3 - 2,后果为 6
Number.prototype.add = function(n) {  return this.valueOf() + n;};Number.prototype.minus = function(n) {  return this.valueOf() - n;};

实现add(1)(2) =3

// 题意的答案const add = (num1) => (num2)=> num2 + num1;// 整了一个加强版 能够有限链式调用 add(1)(2)(3)(4)(5)....function add(x) {  // 存储和  let sum = x;  // 函数调用会相加,而后每次都会返回这个函数自身  let tmp = function (y) {    sum = sum + y;    return tmp;  };  // 对象的toString必须是一个办法 在办法中返回了这个和  tmp.toString = () => sum  return tmp;}alert(add(1)(2)(3)(4)(5))
有限链式调用实现的关键在于 对象的 toString 办法 : 每个对象都有一个 toString() 办法,当该对象被示意为一个文本值时,或者一个对象以预期的字符串形式援用时主动调用。

也就是我在调用很屡次后,他们的后果会存在add函数中的sum变量上,当我alert的时候 add会主动调用 toString办法 打印出 sum, 也就是最终的后果

AJAX

const getJSON = function(url) {  return new Promise((resolve, reject) => {    const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');    xhr.open('GET', url, false);    xhr.setRequestHeader('Accept', 'application/json');    xhr.onreadystatechange = function() {      if (xhr.readyState !== 4) return;      if (xhr.status === 200 || xhr.status === 304) {        resolve(xhr.responseText);      } else {        reject(new Error(xhr.responseText));      }    }    xhr.send();  })}

树形构造转成列表(解决菜单)

[    {        id: 1,        text: '节点1',        parentId: 0,        children: [            {                id:2,                text: '节点1_1',                parentId:1            }        ]    }]转成[    {        id: 1,        text: '节点1',        parentId: 0 //这里用0示意为顶级节点    },    {        id: 2,        text: '节点1_1',        parentId: 1 //通过这个字段来确定子父级    }    ...]

实现代码如下:

function treeToList(data) {  let res = [];  const dfs = (tree) => {    tree.forEach((item) => {      if (item.children) {        dfs(item.children);        delete item.children;      }      res.push(item);    });  };  dfs(data);  return res;}

异步串行 | 异步并行

// 字节面试题,实现一个异步加法function asyncAdd(a, b, callback) {  setTimeout(function () {    callback(null, a + b);  }, 500);}// 解决方案// 1. promisifyconst promiseAdd = (a, b) => new Promise((resolve, reject) => {  asyncAdd(a, b, (err, res) => {    if (err) {      reject(err)    } else {      resolve(res)    }  })})// 2. 串行解决async function serialSum(...args) {  return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))}// 3. 并行处理async function parallelSum(...args) {  if (args.length === 1) return args[0]  const tasks = []  for (let i = 0; i < args.length; i += 2) {    tasks.push(promiseAdd(args[i], args[i + 1] || 0))  }  const results = await Promise.all(tasks)  return parallelSum(...results)}// 测试(async () => {  console.log('Running...');  const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)  console.log(res1)  const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)  console.log(res2)  console.log('Done');})()

实现redux-thunk

redux-thunk 能够利用 redux 中间件让 redux 反对异步的 action
// 如果 action 是个函数,就调用这个函数// 如果 action 不是函数,就传给下一个中间件// 发现 action 是函数就调用const thunk = ({ dispatch, getState }) => (next) => (action) => {  if (typeof action === 'function') {    return action(dispatch, getState);  }  return next(action);};export default thunk

判断是否是电话号码

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

实现类数组转化为数组

类数组转换为数组的办法有这样几种:

  • 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 办法来实现转换
Array.from(arrayLike);

二分查找

function search(arr, target, start, end) {  let targetIndex = -1;  let mid = Math.floor((start + end) / 2);  if (arr[mid] === target) {    targetIndex = mid;    return targetIndex;  }  if (start >= end) {    return targetIndex;  }  if (arr[mid] < target) {    return search(arr, target, mid + 1, end);  } else {    return search(arr, target, start, mid - 1);  }}// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];// const position = search(dataArr, 6, 0, dataArr.length - 1);// if (position !== -1) {//   console.log(`指标元素在数组中的地位:${position}`);// } else {//   console.log("指标元素不在数组中");// }