前言

我们好,我是 程序员Better,努力! 奋斗! 爱折腾的程序员, 不会没关系, 最重要的是我们一同学习! 我们可以重视我的大众号:程序员Betrer让我们一同学习呀~

常见的 js手写我们常常会在作业面试中遇到, 这次也总结了一些,发现有很多js的文章, 我也总结了下自己的常常的用到的,方便自己学习,今日也给我们共享出来, 欢迎我们一同学习交流, 有更好的办法欢迎谈论区指出, 后序我也将持续整理总结,也欢迎补充其他的js手写~

数据绑架

vue2数据绑架

function defineReactive(data) {
  if (!data || Object.prototype.toString.call(data) !== '[object Object]')
    return;
  for (let key in data) {
    let val = data[key];
    Object.defineProperty(data, key, {
      enumerable: true, //可枚举
      configurable: true, //可配置
      get: function() {
        track(data, key);
        return val;
      },
      set: function() {
        trigger(val, key);
      },
    });
    if (typeof val === "object") {
      defineReactive(val);
    }
  }
}
function trigger(val, key) {
  console.log("sue set", val, key);
}
function track(val, key) {
  console.log("sue set", val, key);
}
const data = {
  name:'better',
  firends:['1','2']
}
defineReactive(data)
console.log(data.name)
console.log(data.firends[1])
console.log(data.firends[0])
console.log(Object.prototype.toString.call(data))

vue3 proxy

function reactive(obj) {
  const handler = {
    get(target, prop, receiver) {
      track(target, prop);
      const value =  Reflect.get(...arguments);
      if(typeof value === 'Object') {
        reactive(value)
      }else {
        return value
      }
    },
    set(target,key, value, receiver) {
      trigger(target,key, value);
      return Reflect.set(...arguments);
    },
  };
  return new Proxy(obj,handler)
}
function track(data, key) {
  console.log("sue set", data, key);
}
function trigger(data, key,value) {
  console.log("sue set", key,':',value);
}
const dinner = {
  name:'haochi1'
}
const proxy  =reactive(dinner)
proxy.name
proxy.list = []
proxy.list.push(1)

防抖节省

依稀记得当年孤身面字节的时分

防抖

运用场景: 输入框输入搜索,拖拽( mousemove )

作用: 不是每次操作后履行函数.在频繁操作的最终一次操作完毕后在设置的时刻内没有触发操作时才履行回调

两种思路

  1. 当即履行: 在榜首次触发事情的时分当即履行当时操作的回调,后边的操作在最终一次操作完毕后在设置的时刻内没有触发操作时才履行回调
  2. 无当即履行: 按最终一次操作完毕后的规则时刻履行
function debounce(fn, delay, immediate) {
  let timer; //利用闭包保存同一个timer
  return function () {
    let self = this;
    let arg = arguments;
    clearTimeout(timer);
    if (immediate) {
      const callNow = !timer;
      timer = setTimeOut(() => {
        timer = null;
      }, delay);
      if (callNow) fn.apply(self.arg);
    } else {
      timer = setTimeout(() => {
        fn.apply(self, arg);
      }, delay);
    }
  };
}

节省

运用场景:翻滚条翻滚,频繁点击恳求接口

作用:预订一个函数只要在大于等于履行周期时才履行

两种思路:

  1. 时刻戳,先会当即履行,达到时刻周期再履行
function throttle(fn, delay) {
  let t;
  return function () {
    let self = this;
    let arg = arguments;
    if (!t || Date.now() - t >= delay) {
      fn.apply(self, arg);
      t = new Date();
    }
  };
}
  1. 定时器,定时必定时刻周期之后去履行,可是在这时刻内中不停的调用,不让他的定时器清零重新计时,不会影响当时的成果,仍是那时刻持续等,等抵达时刻周期后触发(会呈现中止操作仍是会触发)
function throttle(fn,delay) {
  let timer
  retrun function () {
    let self = this
    let arg = arguments
    if(timer) return
    timer = setTimeOut(()=> {
      fn.apply(fn,arg)
      timer = null
    },delay)
  }
}

call apply bind

三者都是用来改动 this 指向的

  1. call
    运用:
 function.call(thisArg,arg1,grg2,...)
  • thisArg 可选参数,function 履行时内部的 this 指向thisArg
  • arg1,arg2,… 可选参数,传递给 function 的参数列表
  • 回来值:在指定的 this 值和所传递的参数下调用此函数的回来成果
    注意:
  1. function 函数将会当即履行
  2. 在履行时,会将函数内部的 this 指向 thisArg
  3. 出 thisArg 外的一切剩下参数将悉数传递给 function
  4. 回来 function 函数履行后的成果
Function.prototype.myCall = function (context, ...arr) {
  console.log('调用mycall中的this', this);
  if (context === null || context === undefined) {
    context = window;
  } else {
    context = Object(context);
  }
  const specialPrototype = Symbol('特别特色symbol');
  context[specialPrototype] = this; // this指向调用者
  // context[specialPrototype]履行函数调用时this指向context
  let result = context[specialPrototype](...arr);
  delete context[specialPrototype];
  return result;
};
// context :{
//   specialPrototype:this->调用者
// }
  1. apply

注意:

  • 运用 apply 只支持两个参数,榜首个为 thisArg,第二个是包括多个参数的数组
Function.prototype.myApply = function (context, arr) {
  console.log(this);
  if (context === null || context === undefined) {
    context = window;
  } else {
    context = Object(context);
  }
  const specialPrototype = Symbol('特别特色symbol');
  context[specialPrototype] = this;
  let result = context[specialPrototype](...arr);
  delete context[specialPrototype];
  return result;
};
  1. bind
    运用:
 function.bind(thisArg,arg1,grg2,...)
  • thisArg 可选参数,function 履行时会生成一个包裹着function(...)的邦迪函数,而且将function(...)的 this 指向 thisArg,假如运用 new 运算符调用这个生成的绑定函数,则疏忽thisArg
  • arg1,arg2,… 可选参数,传递给 function 的参数列表
  • 回来值:在指定的 this 值和所传递的参数下调用此函数的回来成果
    注意:
  1. bind 办法将创立并回来一个新的函数,新函数称为绑定函数,而且此绑定函数包裹着原始函数
  2. 履行时,会显示将原始函数内部的 this 指向了thisArg
  3. 除 thisArg 外一切剩下参数将悉数传递给 function
  4. 履行邦定函数时,假如传递了参数,这些参数将悉数传递给原始函数 function
  5. 假如运用 new 运算符调用生成的绑定函数,则疏忽 thisArg
Function.prototype.mybind = function () {
  //判别调用bind的 是不是函数,抛出异常
  if (typeof this !== 'function') {
    throw new Error(
      'function.prototype.bind - what is trying to be bound is not  callable',
    );
  }
  // 将类数组的参数转换成数组然后截取榜首个参数
  // const argsArr =Array.prototype.slice.call(arguments)
  const argsArr = [...arguments];
  const args = argsArr.shift();
  const self = this;
  const fToBind = function () {
    console.log('回来函数的参数', arguments);
    const isNew = this instanceof fToBind; // this是否是fToBind的实例 也便是回来的fToBind是否经过new调用
    const context = isNew ? this : Object(args); // new调用就绑定到this上,否则就绑定到传入的objThis上
    return self.apply(context, argsArr.concat([...arguments]));
  };
  if (self.prototype) {
    // 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比方箭头函数
    fToBind.prototype = Object.create(self.prototype);
  }
  return fToBind;
};

手写 new 关键字

作用
创立一个用户界说的目标类型的实例或者具有结构函数的内置目标的实例

特色
可以经过 new 一个结构函数的方法来创立一个目标实例,可是结构函数的差异导致创立的实例有必定的不同

结构函数的回来值不同

  1. 无回来值:生成一个实例化目标,结构函数中的 this 指向该目标
  2. 回来一个目标:return 之前的都被覆盖了,new 生成是 return 的目标
  3. 回来一个非目标:跟没有 return 是一样的成果
// new关键字可以创立目标的实例
// 产生一个新的目标
function myNew(fn, ...args) {
  const obj = new Object();
  obj._proto_ = fn.prototype;
  // Object.create()办法创立一个新目标,运用现有的目标来提供新创立目标的_proto_
  // const obj = Object.create(fn.prototype)
  // 履行fn并把fn中的this指向新创立的目标
  let res = fn.apply(obj, args);
  // 判别结构函数的回来值是不是object,是object则运用retrun的目标,不是的话就运用生成的目标
  return typeof ret === 'object' ? ret || obj : obj;
}

instanceof

function myInstanceof(A, B) {
  // 遍历链表
  let p = A;
  while (p) {
    p = p.__proto__;
    // B的 prototype 特色是否呈现在A实例目标的原型链上
    if (p === B.prototype) {
      return true;
    }
  }
  return false;
}
function Foo() {}
var f = new Foo();
console.log(myInstanceof(f, Foo)); // true
console.log(myInstanceof(f, Object)); // true
console.log(myInstanceof([1, 2], Array)); // true
console.log(myInstanceof({ a: 1 }, Array)); // false
console.log(myInstanceof(Array, Object)); // true
console.log(Array instanceof Object); // true

深浅克隆

function shallowClone(source) {
  const target = {};
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = source[key];
    }
  }
  return target;
}
// 深复制1.0
function deeClone1(source) {
  if (typeof source === 'object') {
    let target = Array.isArray(source) ? [] : {};
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        // 假如特色是目标类型则递归再次遍历赋值
        target[key] = deeClone1(source[key]);
      }
    }
    return target;
  } else {
    return source;
  }
}
const textObject = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
};
const deepResult = deeClone1(textObject);
const shallowResult = shallowClone(textObject);
console.log('深克隆', deepResult);
console.log('浅克隆', shallowResult);
deepResult.field4.push(1);
console.log('深克隆', deepResult, textObject);
// 深复制2.0 解决循环引用问题
const textObject2 = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
};
textObject2.textObject2 = textObject2;
// 运用1.0克隆克隆循环引用的值会呈现爆栈的现象
// const deepResult2 = deeClone1(textObject2);
// 深复制2.0 运用Map
// 查看map中有无克隆过的目标
// 有 - 直接回来
// 没有 - 将当时目标作为key,克隆目标作为value进行存储
// 持续克隆
function deeCloneMap(source, map = new Map()) {
  if (typeof source === 'object') {
    let target = Array.isArray(source) ? [] : {};
    // 查看map中有无克隆过的目标
    if (map.get(source)) {
      // 有 - 直接回来
      return map.get(source);
    }
    // 没有 - 将当时目标作为key,克隆目标作为value进行存储
    map.set(source, target);
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        // 假如特色是目标类型则递归再次遍历赋值
        target[key] = deeCloneMap(source[key], map);
      }
    }
    return target;
  } else {
    return source;
  }
}
const deepResult2 = deeCloneMap(textObject2);
console.log('mapClone', deepResult2);
// 深复制2.1 运用是WeakMap弱引用关系,当下一次垃圾回收机制履行时,这块内存就会被释放掉。
function deeCloneWeakMap(source, map = new WeakMap()) {
  if (typeof source === 'object') {
    let target = Array.isArray(source) ? [] : {};
    // 查看map中有无克隆过的目标
    if (map.get(source)) {
      // 有 - 直接回来
      return map.get(source);
    }
    // 没有 - 将当时目标作为key,克隆目标作为value进行存储
    map.set(source, target);
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        // 假如特色是目标类型则递归再次遍历赋值
        target[key] = deeCloneMap(source[key], map);
      }
    }
    return target;
  } else {
    return source;
  }
}
// while循环的功能高 运用while来完成一个通用的forEach遍历,iteratee是遍历的回掉函数,他可以接纳每次遍历的value和index两个参数:
function forEach(array, iteratee) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
    iteratee(array[index], index);
  }
  return array;
}
// 深复制3.0 运用是WeakMap弱引用关系,当下一次垃圾回收机制履行时,这块内存就会被释放掉。
function deeCloneWhile(source, map = new WeakMap()) {
  // 1.判别是否为null 或undefined
  if (typeof source == null) return source;
  // 2.判别是否为日期Date
  if (source instanceof Date) return new Date(osourcebj);
  // 3.判别是否为正则 typeof /d+/ === 'object'
  if (source instanceof RegExp) return new RegExp(source);
  if (typeof source === 'object') {
    const isArray = Array.isArray(source);
    let target = isArray ? [] : {};
    // 查看map中有无克隆过的目标
    if (map.get(source)) {
      // 有 - 直接回来
      return map.get(source);
    }
    // 没有 - 将当时目标作为key,克隆目标作为value进行存储
    const keys = isArray ? undefined : Object.keys(source);
    map.set(source, target);
    forEach(keys || source, (value, key) => {
      if (keys) {
        key = value;
      }
      target[key] = deeCloneWhile(source[key], map);
    });
    // for (let key in source) {
    //   if (source.hasOwnProperty(key)) {
    //     // 假如特色是目标类型则递归再次遍历赋值
    //     target[key] = deeCloneMap(source[key],map);
    //   }
    // }
    return target;
  } else {
    return source;
  }
}
const textObject3 = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
  f: {
    f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } },
  },
};
textObject3.textObject3 = textObject3;
const deepResult3 = deeCloneWhile(textObject3);
console.log('deeCloneWhile', deepResult3);

promise

Promise.allSettled

特色
接纳一个数组作为参数,数组的每一项都是一个Promise目标,回来一个新的Promise目标,只要比及参数数组的一切的Promise目标都产生状况改动,回来的Promise目标才会产生变更

Promise.allSettled = function (arr) {
  let result = [];
  return new Promise((resolve, reject) => {
    arr.forEach((item, index) => {
      Promise.resolve(item)
        .then((res) => {
          result.push({
            status: 'fulfilled',
            value: res,
          });
          result.length === arr.length && resolve(result);
        })
        .catch((error) => {
          result.push({
            status: 'rejected',
            value: error,
          });
          result.length === arr.length && resolve(result);
        });
    });
  });
};

Promise.all()

Promise.all()办法用于将多个 promise 实例包装成一个新的 Promise 实例

Promise.all = function (arr) {
  let index = 0,
    result = [];
  return new Promise((resolve, reject) => {
    arr.forEach((item, i) => {
      Promise.resolve(item)
        .then((res) => {
          index++;
          result[i] = res;
          if (index === arr.length) resolve(res);
        })
        .catch((error) => reject(error));
    });
  });
};

手写 fill

最近发现有很多地方让手写 fill 函数

Array.prototype.(fill())[developer.mozilla.org/zh-CN/docs/…] 办法用一个固定值填充一个数组中从起始索引到终止索引内的悉数元素, 不包括终止索引

(MDN)

语法

arr.fill(value[, start[, end]])

fill接纳三个参数 vlaue startend ,startend是可选的,其默认值分别为 0 和this目标的length特色值

假如 start 是个负数, 则开端索引会被主动核算成length+start, 其中lengththis目标的length特色值,假如 end 是个负数, 则完毕索引会被主动核算成 length + end

回来值
修改后的数组

示例

[1, 2, 3].fill(4); // [4, 4, 4]
[1, 2, 3].fill(4, 1); // [1, 4, 4]
[1, 2, 3].fill(4, 1, 2); // [1, 4, 3]
[1, 2, 3].fill(4, 1, 1); // [1, 2, 3]
[1, 2, 3].fill(4, 3, 3); // [1, 2, 3]
[1, 2, 3].fill(4, -3, -2); // [4, 2, 3]
[1, 2, 3].fill(4, NaN, NaN); // [1, 2, 3]
[1, 2, 3].fill(4, 3, 5); // [1, 2, 3]
Array(3).fill(4); // [4, 4, 4]
[].fill.call({ length: 3 }, 4); // {0: 4, 1: 4, 2: 4, length: 3}

参阅 MDN 中的手写代码

if (!Array.prototype.fill) {
  Object.defineProperty(Array.prototype, 'fill', {
    value: function(value) {
      // Steps 1-2.
      if (this == null) {
        throw new TypeError('this is null or not defined');
      }
      var O = Object(this);
      // Steps 3-5.
      var len = O.length &gt;&gt;&gt; 0;
      // Steps 6-7.
      var start = arguments[1];
      var relativeStart = start &gt;&gt; 0;
      // Step 8.
      var k = relativeStart &lt; 0 ?
        Math.max(len + relativeStart, 0) :
        Math.min(relativeStart, len);
      // Steps 9-10.
      var end = arguments[2];
      var relativeEnd = end === undefined ?
        len : end &gt;&gt; 0;
      // Step 11.
      var final = relativeEnd &lt; 0 ?
        Math.max(len + relativeEnd, 0) :
        Math.min(relativeEnd, len);
      // Step 12.
      while (k &lt; final) {
        O[k] = value;
        k++;
      }
      // Step 13.
      return O;
    }
  });
}

数组扁平化

关于数组的扁平化,我现已出了一个详细的文章剖析
面试 20 个人竟然没有一个写出数组扁平化?怎么手写 flat 函数

这儿也给出一个我比较喜爱的完成方法,其他的完成方法欢迎我们查看原文学习更多的完成方法

思路

经过 some 来判别数组中是否用数组,经过 while 不断循环履行判别, 假如是数组的话可以运用 拓宽运算符… … 每次只能展开最外层的数组,加上 contact 来减少嵌套层数,

function flatten(arr) {
  while (arr.some(item=> Array.isArray(item))) {
    console.log(...arr)
    arr = [].concat(...arr)
    console.log(arr)
  }
  return arr
}
console.log(flatten(arr));

获取 URL 的参数

前段时刻自己还在某次测验中遇到了这个题

获取浏览器参数都很熟悉,榜首反应是运用正则表达式去对浏览器的参数进行切割获取,但是浏览器现已提供了一个URLSearchParams这个接口给我们去操作 URL 的查询字符串

URLSearchParams是一个结构函数,可以经过get()办法来获取

1. 运用URLSearchParams结构函数

const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
console.log(params); // {id: '2', isShare: 'true'}
console.log(params.id); // 2

运用正则表达式获取 url

function getParams(url, params) {
  var res = new RegExp('(?:&|/?)' + params + '=([^&$]+)').exec(url);
  return res ? res[1] : '';
}
// url: better.com?id=2&isShare=true
const id = getParams(window.location.search, 'id');
console.log(id); // 2

图片懒加载

概念:
图片懒加载便是开端加载页面的时分把图片的 src 给替换,在图片呈现在页面的可视区域内的时分再加载图片的 src

思路

  1. 获取一切的 img 标签,获取并替换一切 src 数据,将 src 替换成 data-src
  2. 判别当时图片是否进入可视区域内,进入后进行展现

getBoundingClientRect 办法回来元素的巨细及其相关于视口的方位


let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
 const imgLazyLoad = (function() {
    let count = 0
   return function() {
        let deleteIndexList = []
        imgList.forEach((img, index) => {
            let rect = img.getBoundingClientRect()
            if (rect.top < window.innerHeight) {
                img.src = img.dataset.src
                deleteIndexList.push(index)
                count++
                // 优化图片悉数加载完成后移除事情监听
                if (count === length) {
                    document.removeEventListener('scroll', imgLazyLoad)
                }
            }
        })
        // 当img加载完图片后将他从imglist中移除
        imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
   }
 })()
document.addEventListener('scroll', imgLazyLoad)

函数柯里化

概念
将运用多个参数的函数转换成一系列运用一个参数

比方:


function add (a,b,c) {
  retrun a+b+c
}
add(1,2,3)
let curryAdd =curry(add)
curryAdd(1)(2)(3)

思路

function curry(fn) {
    let judge = (...args) => {
        if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

完成链表

//节点类
class Node{
    constructor(data){
        this.data = data
        this.next = null
    }
}
//链表类
class SinglyLinkedList{
    constructor(){
        this.head = new Node() //head指向头节点
    }
    //在链表组后添加节点
    add(data){
        let node = new Node(data)
        let current = this.head
        while(current.next){
            current = current.next
        }
        current.next = node
    }
    //添加到指定方位
    addAt(index, data){
        let node = new Node(data)
        let current = this.head
        let counter = 1
        while(current){
            if(counter === index){
                node.next = current.next
                current.next = node
            }
            current = current.next
            counter++
        }
    }
    //删去某个方位的节点
    removeAt(index){
        let current = this.head
        let counter = 1
        while(current.next){
            if(counter === index){
                current.next = current.next.next
            }
            current = current.next
            counter++
        }
    }
    //回转链表
    reverse(){
        let current = this.head.next
        let prev = this.head
        while(current){
            let next = current.next
            //回转:改动当时节点指针。若当时节点是榜首个(即头节点后边的)节点,
            //则此节点的next为null,否则next指向他的上一个节点
            current.next = prev===this.head ? null : prev
            prev = current
            current = next
        }
        this.head.next = prev
    }
}

二叉树遍历

前序

var Traversal = function(root) {
    const stack = [];
    while (root || stack.length){
      while(root){
        stack.push(root);
        root = root.left;
      }
      root = stack.pop();
      root = root.right;
    }
    return res;
};

中序

// 中序遍历(非递归,迭代)
const inorder2 = (root) => {
  if (!root) return;
  const res = []
  const track = []
  // let p = root
  while (track.length ||root) {
      // 把左子树全入栈
      while (root) {
          track.push(root)
          root = root.left
      }
      root = track.pop()
      res.push(root.val)
      // 遍历根节点左面的节点的右侧
      root = root.right
      console.log('root',root)
  }
  return res
};

后序

var postorderTraversal = function(root) {
  // 初始化数据
    const res =[];
    const stack = [];
    while (root || stack.length){
      while(root){
        stack.push(root);
        res.unshift(root.val);
        root = root.right;
      }
      root = stack.pop();
      root = root.left;
    }
    return res;
};

翻转二叉树

// 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并回来其根节点。
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
  var invertTree = function(root) {
    let now = [root]
    // 广度优先遍历,利用层次遍历
    // 设置当时存储节点的数组,初始化是根节点
    while (now.length) {
        const next = []
        now.forEach(item => {
            // 节点为null直接回来
            if (item === null) return
            // 界说第三变量交流左右子节点
            let n = item.left
            item.left = item.right
            item.right = n
            // 将左右子节点放进数组
            next.push(item.left)
            next.push(item.right)
        })
        now = next
    }
    return root
};

二分查找

标题:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,假如目标值存在回来下标,否则回来 -1。

思路:

  1. 界说头尾两个指针 left = 0 ,right = length 中间索引 mid = left + (right – left) / 2
  2. 当left <= right 假如mid上的值等于target, 回来mid, 假如小于targt, left = mid + 1(砍掉左半边)
    假如大于target , right = mid – 1(砍掉右半边)
  3. 假如while 循环完毕后都没有找到target , 回来-1

const search = (nums, target) => {
  let i = 0
  let j = nums.length - 1
  let midIndex = 0
  while (i <= j) {
    midIndex = Math.floor((i + j) / 2)
    const midValue = nums[ midIndex ]
    if (midValue === target) {
      return midIndex
    } else if (midValue < target) {
      i = midIndex + 1
    } else {
      j = midIndex - 1
    }
  }
  return -1
}
console.log(search([-1,0,3,5,9,12], 9)) // 4

快排

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
    //   const length = nums.length
    //   if (length <= 1) {
    //     return nums
    //   }
    //   const midIndex = Math.floor(length / 2)
    //   const midValue = nums.splice(midIndex, 1)[ 0 ]
    //   let leftArray = []
    //   let rightArray = []
    //   let index = 0
    //   while (index < length - 1) {
    //     const curValue = nums[ index ]
    //     if (curValue <= midValue) {
    //       leftArray.push(curValue)
    //     } else {
    //       rightArray.push(curValue)
    //     }
    //     index++
    //   }
    //   return sortArray(leftArray).concat([ midValue ], sortArray(rightArray))
    let left = 0,
        right = nums.length - 1;
    //console.time('QuickSort');
    main(nums, left, right);
    //console.timeEnd('QuickSort');
    return nums;
    function main(nums, left, right) {
        // 递归完毕的条件,直到数组只包含一个元素。
        if (nums.length === 1) {
            // 由所以直接修改arr,所以不必回来值。
            return;
        }
        // 获取left指针,预备下一轮分化。
        let index = partition(nums, left, right);
        if (left < index - 1) {
            // 持续分化左面数组。
            main(nums, left, index - 1);
        }
        if (index < right) {
            // 分化右边数组。
            main(nums, index, right);
        }
    }
    // 数组分化函数。
    function partition(nums, left, right) {
        // 选取中间项为参阅点。
        let pivot = nums[Math.floor((left + right) / 2)];
        // 循环直到left > right。
        while (left <= right) {
            // 持续右移左指针直到其值不小于pivot。
            while (nums[left] < pivot) {
                left++;
            }
            // 持续左移右指针直到其值不大于pivot。
            while (nums[right] > pivot) {
                right--;
            }
            // 此时左指针的值不小于pivot,右指针的值不大于pivot。
            // 假如left依然不大于right。
            if (left <= right) {
                // 交流两者的值,使得不大于pivot的值在其左侧,不小于pivot的值在其右侧。
                [nums[left], nums[right]] = [nums[right], nums[left]];
                // 左指针右移,右指针左移预备开端下一轮,避免arr[left]和arr[right]都等于pivot然后导致死循环。
                left++;
                right--;
            }
        }
        // 回来左指针作为下一轮分化的根据。
        return left;
    }
};

我正在参与技术社区创作者签约计划招募活动,点击链接报名投稿。