看看这些常见的js手写题你会写多少个

前言

大家好,我是 程序员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;
    }

};

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

猜你喜欢

转载自juejin.im/post/7126539426397700133