乐趣区

关于前端:滴滴前端一面常考手写面试题整理

类数组转化为数组

类数组是具备 length 属性,但不具备数组原型上的办法。常见的类数组有arguments、DOM 操作方法返回的后果。

办法一:Array.from
Array.from(document.querySelectorAll('div'))
办法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
办法三:扩大运算符
[...document.querySelectorAll('div')]
办法四:利用 concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));

解析 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;
}

滚动加载

原理就是监听页面滚动事件,剖析 clientHeightscrollTopscrollHeight三者的属性关系。

window.addEventListener('scroll', function() {
  const clientHeight = document.documentElement.clientHeight;
  const scrollTop = document.documentElement.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight;
  if (clientHeight + scrollTop >= scrollHeight) {
    // 检测到滚动至页面底部,进行后续操作
    // ...
  }
}, false);

Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {throw new TypeError(callbackfn + 'is not a function');
  }
  const O = Object(this);
  const len = this.length >>> 0;
  let accumulator = initialValue;
  let k = 0;
  // 如果第二个参数为 undefined 的状况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}
    // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
    if (k >= len) {throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = O[k++];
  }
  while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);
    }
    k++;
  }
  return accumulator;
}

实现简略路由

// hash 路由
class Route{constructor(){
    // 路由存储对象
    this.routes = {}
    // 以后 hash
    this.currentHash = ''
    // 绑定 this,防止监听时 this 指向扭转
    this.freshRoute = this.freshRoute.bind(this)
    // 监听
    window.addEventListener('load', this.freshRoute, false)
    window.addEventListener('hashchange', this.freshRoute, false)
  }
  // 存储
  storeRoute (path, cb) {this.routes[path] = cb || function () {}
  }
  // 更新
  freshRoute () {this.currentHash = location.hash.slice(1) || '/'
    this.routes[this.currentHash]()}
}

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

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

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 构造渲染如何解决?

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

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

应用 ES5 和 ES6 求函数参数的和

ES5:

function sum() {
    let sum = 0
    Array.prototype.forEach.call(arguments, function(item) {sum += item * 1})
    return sum
}

ES6:

function sum(...nums) {
    let sum = 0
    nums.forEach(function(item) {sum += item * 1})
    return sum
}

Promise 实现

基于 Promise 封装Ajax

  • 返回一个新的 Promise 实例
  • 创立 HMLHttpRequest 异步对象
  • 调用 open 办法,关上url,与服务器建设链接(发送前的一些解决)
  • 监听 Ajax 状态信息
  • 如果xhr.readyState == 4(示意服务器响应实现,能够获取应用服务器的响应了)

    • xhr.status == 200,返回 resolve 状态
    • xhr.status == 404,返回 reject 状态
  • xhr.readyState !== 4,把申请主体的信息基于 send 发送给服务器
function ajax(url) {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.onreadystatechange = () => {if (xhr.readyState == 4) {if (xhr.status >= 200 && xhr.status <= 300) {resolve(JSON.parse(xhr.responseText))
        } else {reject('申请出错')
        }
      }
    }
    xhr.send()  // 发送 hppt 申请})
}

let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))

实现 apply 办法

思路: 利用 this 的上下文个性。apply其实就是改一下参数的问题

Function.prototype.myApply = function(context = window, args) {
  // this-->func  context--> obj  args--> 传递过去的参数

  // 在 context 上加一个惟一值不影响 context 上的属性
  let key = Symbol('key')
  context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法
  // let args = [...arguments].slice(1)   // 第一个参数为 obj 所以删除, 伪数组转为数组

  let result = context[key](...args); // 这里和 call 传参不一样

  // 革除定义的 this 不删除会导致 context 属性越来越多
  delete context[key]; 

  // 返回后果
  return result;
}
// 应用
function f(a,b){console.log(a,b)
 console.log(this.name)
}
let obj={name:'张三'}
f.myApply(obj,[1,2])  //arguments[1]

setTimeout 与 setInterval 实现

setTimeout 模仿实现 setInterval

题目形容: setInterval 用来实现循环定时调用 可能会存在肯定的问题 能用 setTimeout 解决吗

实现代码如下:

function mySetInterval(fn, t) {
  let timerId = null;
  function interval() {fn();
    timerId = setTimeout(interval, t); // 递归调用
  }
  timerId = setTimeout(interval, t); // 首次调用
  return {
    // 利用闭包的个性 保留 timerId
    cancel:() => {clearTimeout(timerId)
    }
  }
}
// 测试
var a = mySetInterval(()=>{console.log(111);
},1000)
var b = mySetInterval(() => {console.log(222)
}, 1000)

// 终止定时器
a.cancel()
b.cancel()

为什么要用 setTimeout 模仿实现 setIntervalsetInterval 的缺点是什么?

setInterval(fn(), N);

下面这句代码的意思其实是 fn() 将会在 N 秒之后被推入工作队列。在 setInterval 被推入工作队列时,如果在它后面有很多工作或者某个工作等待时间较长比方网络申请等,那么这个定时器的执行工夫和咱们预约它执行的工夫可能并不统一

// 最常见的呈现的就是,当咱们须要应用 ajax 轮询服务器是否有新数据时,必定会有一些人会应用 setInterval,然而无论网络情况如何,它都会去一遍又一遍的发送申请,最初的间隔时间可能和原定的工夫有很大的出入

// 做一个网络轮询,每一秒查问一次数据。let startTime = new Date().getTime();
let count = 0;

setInterval(() => {
    let i = 0;
    while (i++ < 10000000); // 假如的网络提早
    count++;
    console.log(
        "与原设定的距离时差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
    );
}, 1000)

// 输入:// 与原设定的距离时差了:567 毫秒
// 与原设定的距离时差了:552 毫秒
// 与原设定的距离时差了:563 毫秒
// 与原设定的距离时差了:554 毫秒(2 次)
// 与原设定的距离时差了:564 毫秒
// 与原设定的距离时差了:602 毫秒
// 与原设定的距离时差了:573 毫秒
// 与原设定的距离时差了:633 毫秒

再次强调,定时器指定的工夫距离,示意的是何时将定时器的代码增加到音讯队列,而不是何时执行代码。所以真正何时执行代码的工夫是不能保障的,取决于何时被主线程的事件循环取到,并执行。

setInterval(function, N)
// 即:每隔 N 秒把 function 事件推到音讯队列中

上图可见,setInterval 每隔 100ms 往队列中增加一个事件;100ms 后,增加 T1 定时器代码至队列中,主线程中还有工作在执行,所以期待,some event 执行完结后执行 T1定时器代码;又过了 100msT2 定时器被增加到队列中,主线程还在执行 T1 代码,所以期待;又过了 100ms,实践上又要往队列里推一个定时器代码,但因为此时 T2 还在队列中,所以 T3 不会被增加(T3 被跳过),后果就是此时被跳过;这里咱们能够看到,T1 定时器执行完结后马上执行了 T2 代码,所以并没有达到定时器的成果

setInterval 有两个毛病

  • 应用 setInterval 时,某些距离会被跳过
  • 可能多个定时器会间断执行

能够这么了解 :每个setTimeout 产生的工作会间接 push 到工作队列中;而 setInterval 在每次把工作 push 到工作队列前,都要进行一下判断 (看上次的工作是否仍在队列中)。因此咱们个别用setTimeout 模仿setInterval,来躲避掉下面的毛病

setInterval 模仿实现 setTimeout

const mySetTimeout = (fn, t) => {const timer = setInterval(() => {clearInterval(timer);
    fn();}, t);
};
// 测试
// mySetTimeout(()=>{//   console.log(1);
// },1000)

实现 Object.create

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

// 模仿 Object.create

function create(proto) {function F() {}
  F.prototype = proto;

  return new F();}

实现一个迷你版的 vue

入口

// js/vue.js
class Vue {constructor (options) {
    // 1. 通过属性保留选项的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2. 把 data 中的成员转换成 getter 和 setter,注入到 vue 实例中
    this._proxyData(this.$data)
    // 3. 调用 observer 对象,监听数据的变动
    new Observer(this.$data)
    // 4. 调用 compiler 对象,解析指令和差值表达式
    new Compiler(this)
  }
  _proxyData (data) {
    // 遍历 data 中的所有属性
    Object.keys(data).forEach(key => {
      // 把 data 的属性注入到 vue 实例中
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get () {return data[key]
        },
        set (newValue) {if (newValue === data[key]) {return}
          data[key] = newValue
        }
      })
    })
  }
}

实现 Dep

class Dep {constructor () {
    // 存储所有的观察者
    this.subs = []}
  // 增加观察者
  addSub (sub) {if (sub && sub.update) {this.subs.push(sub)
    }
  }
  // 发送告诉
  notify () {
    this.subs.forEach(sub => {sub.update()
    })
  }
}

实现 watcher

class Watcher {constructor (vm, key, cb) {
    this.vm = vm
    // data 中的属性名称
    this.key = key
    // 回调函数负责更新视图
    this.cb = cb

    // 把 watcher 对象记录到 Dep 类的动态属性 target
    Dep.target = this
    // 触发 get 办法,在 get 办法中会调用 addSub
    this.oldValue = vm[key]
    Dep.target = null
  }
  // 当数据发生变化的时候更新视图
  update () {let newValue = this.vm[this.key]
    if (this.oldValue === newValue) {return}
    this.cb(newValue)
  }
}

实现 compiler

class Compiler {constructor (vm) {
    this.el = vm.$el
    this.vm = vm
    this.compile(this.el)
  }
  // 编译模板,解决文本节点和元素节点
  compile (el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 解决文本节点
      if (this.isTextNode(node)) {this.compileText(node)
      } else if (this.isElementNode(node)) {
        // 解决元素节点
        this.compileElement(node)
      }

      // 判断 node 节点,是否有子节点,如果有子节点,要递归调用 compile
      if (node.childNodes && node.childNodes.length) {this.compile(node)
      }
    })
  }
  // 编译元素节点,解决指令
  compileElement (node) {// console.log(node.attributes)
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // v-text --> text
        attrName = attrName.substr(2)
        let key = attr.value
        this.update(node, key, attrName)
      }
    })
  }

  update (node, key, attrName) {let updateFn = this[attrName + 'Updater']
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }

  // 解决 v-text 指令
  textUpdater (node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})
  }
  // v-model
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {node.value = newValue})
    // 双向绑定
    node.addEventListener('input', () => {this.vm[key] = node.value
    })
  }

  // 编译文本节点,解决差值表达式
  compileText (node) {// console.dir(node)
    // {{msg}}
    let reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if (reg.test(value)) {let key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])

      // 创立 watcher 对象,当数据扭转更新视图
      new Watcher(this.vm, key, (newValue) => {node.textContent = newValue})
    }
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode (node) {return node.nodeType === 3}
  // 判断节点是否是元素节点
  isElementNode (node) {return node.nodeType === 1}
}

实现 Observer

class Observer {constructor (data) {this.walk(data)
  }
  walk (data) {
    // 1. 判断 data 是否是对象
    if (!data || typeof data !== 'object') {return}
    // 2. 遍历 data 对象的所有属性
    Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key])
    })
  }
  defineReactive (obj, key, val) {
    let that = this
    // 负责收集依赖,并发送告诉
    let dep = new Dep()
    // 如果 val 是对象,把 val 外部的属性转换成响应式数据
    this.walk(val)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get () {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val
      },
      set (newValue) {if (newValue === val) {return}
        val = newValue
        that.walk(newValue)
        // 发送告诉
        dep.notify()}
    })
  }
}

应用

<!DOCTYPE html>
<html lang="cn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Mini Vue</title>
</head>
<body>
  <div id="app">
    <h1> 差值表达式 </h1>
    <h3>{{msg}}</h3>
    <h3>{{count}}</h3>
    <h1>v-text</h1>
    <div v-text="msg"></div>
    <h1>v-model</h1>
    <input type="text" v-model="msg">
    <input type="text" v-model="count">
  </div>
  <script src="./js/dep.js"></script>
  <script src="./js/watcher.js"></script>
  <script src="./js/compiler.js"></script>
  <script src="./js/observer.js"></script>
  <script src="./js/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue',
        count: 100,
        person: {name: 'zs'}
      }
    })
    console.log(vm.msg)
    // vm.msg = {test: 'Hello'}
    vm.test = 'abc'
  </script>
</body>
</html>

实现一个迭代器生成函数

ES6 对迭代器的实现

JS 原生的汇合类型数据结构,只有 Array(数组)和Object(对象);而ES6 中,又新增了 MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以 ES6 在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备 Symbol.iterator 属性(这个属性就是 Iterator 的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被 for...of... 循环和迭代器的 next 办法遍历。事实上,for...of...的背地正是对 next 办法的重复调用。

在 ES6 中,针对 ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of... 进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用 for...of... 遍历数组时:

const arr = [1, 2, 3]
const len = arr.length
for(item of arr) {console.log(` 以后元素是 ${item}`)
}

之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的 Symbol.iterator 生成了它对应的迭代器对象,通过重复调用迭代器对象的 next 办法拜访了数组成员,像这样:

const arr = [1, 2, 3]
// 通过调用 iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()

// 对迭代器对象执行 next,就能一一拜访汇合的成员
iterator.next()
iterator.next()
iterator.next()

丢进控制台,咱们能够看到 next 每次会按程序帮咱们拜访一个汇合成员:

for...of... 做的事件,根本等价于上面这通操作:

// 通过调用 iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()

// 初始化一个迭代后果
let now = {done: false}

// 循环往外迭代成员
while(!now.done) {now = iterator.next()
    if(!now.done) {console.log(` 当初遍历到了 ${now.value}`)
    }
}

能够看出,for...of...其实就是 iterator 循环调用换了种写法。在 ES6 中咱们之所以可能开心地用 for...of... 遍历各种各种的汇合,全靠迭代器模式在背地给力。

ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在 ES6 中的实现有更深的了解。

Promise.race

Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      // 如果不是 Promise 实例须要转化为 Promise 实例
      Promise.resolve(p).then(val => resolve(val),
        err => reject(err),
      )
    })
  })
}

实现 Array.isArray 办法

Array.myIsArray = function(o) {return Object.prototype.toString.call(Object(o)) === '[object Array]';
};

console.log(Array.myIsArray([])); // true

实现一个 compose 函数

组合多个函数,从右到左,比方:compose(f, g, h) 最终失去这个后果 (...args) => f(g(h(...args))).

题目形容: 实现一个 compose 函数

// 用法如下:
function fn1(x) {return x + 1;}
function fn2(x) {return x + 2;}
function fn3(x) {return x + 3;}
function fn4(x) {return x + 4;}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11

实现代码如下

function compose(...funcs) {if (!funcs.length) return (v) => v;

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

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

compose创立了一个从右向左执行的数据流。如果要实现从左到右的数据流,能够间接更改 compose 的局部代码即可实现

  • 更换 Api 接口:把 reduce 改为reduceRight
  • 交互包裹地位:把 a(b(...args)) 改为b(a(...args))

二分查找

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("指标元素不在数组中");
// }

实现一个链表构造

链表构造

看图了解 next 层级

// 链表 从头尾删除、减少 性能比拟好
// 分为很多类 罕用单向链表、双向链表

// js 模仿链表构造:增删改查

// 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 层级
    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
 }
}


var ll = new LinkedList()

ll.add(0,100) // Node {ellement: 100, next: null}
ll.add(0,200) // Node {element: 200, next: Node { element: 100, next: null} }
ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null} } }
ll.add(300)
ll.remove(0)

console.log(ll.get(2),'get')
console.log(ll.head)

module.exports = LinkedList

模仿 new 操作

3 个步骤:

  1. ctor.prototype 为原型创立一个对象。
  2. 执行构造函数并将 this 绑定到新创建的对象上。
  3. 判断构造函数执行返回的后果是否是援用数据类型,若是则返回构造函数执行的后果,否则返回创立的对象。
function newOperator(ctor, ...args) {if (typeof ctor !== 'function') {throw new TypeError('Type Error');
  }
  const obj = Object.create(ctor.prototype);
  const res = ctor.apply(obj, args);

  const isObject = typeof res === 'object' && res !== null;
  const isFunction = typeof res === 'function';
  return isObject || isFunction ? res : obj;
}

实现 lodash 的 chunk 办法 – 数组按指定长度拆分

题目

/**
 * @param input
 * @param size
 * @returns {Array}
 */
_.chunk(['a', 'b', 'c', 'd'], 2)
// => [['a', 'b'], ['c', 'd']]

_.chunk(['a', 'b', 'c', 'd'], 3)
// => [['a', 'b', 'c'], ['d']]

_.chunk(['a', 'b', 'c', 'd'], 5)
// => [['a', 'b', 'c', 'd']]

_.chunk(['a', 'b', 'c', 'd'], 0)
// => []

实现

function chunk(arr, length) {let newArr = [];
  for (let i = 0; i < arr.length; i += length) {newArr.push(arr.slice(i, i + length));
  }
  return newArr;
}
退出移动版