Skip to content

基础题目

1. 函数节流

javascript
function throttle(fn, delay) {
    let flag = true,
        timer = null
    return function(...args) {
        let context = this
        if(!flag) return
        
        flag = false
        clearTimeout(timer)
        timer = setTimeout(function() {
            fn.apply(context,args)
            flag = true
        },delay)
    }
}

2. 函数防抖

javascript
function debounce(fn, delay) {
    let timer = null
    return function(...args) {
        let context = this
        if(timer) clearTimeout(timer)
        timer = setTimeout(function(){
            fn.apply(context,args)
        },delay)
    }
}

3. 手写call

javascript
Function.prototype.myCall = function(context) {
  if (typeof context === undefined || typeof context === null) {
    context = window
  }
  const symbol = Symbol()
  context[symbol] = this
  const args = [...arguments].slice(1)
  const result = context[symbol](...args)
  delete context[symbol]
  return result
}

4. 手写apply

javascript
Function.prototype.myApply = function(context) {
  if (typeof context === undefined || typeof context === null) {
    context = window
  }
  const symbol = Symbol()
  context[symbol] = this
  let result
  // 处理参数和 call 有区别
  if (arguments[1]) {
    result = context[symbol](...arguments[1])
  } else {
    result = context[symbol]()
  }
  delete context[symbol]
  return result
}

5. 手写bind

javascript
Function.prototype.myBind = function (context) {
  if (typeof context === undefined || typeof context === null) {
    context = window
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    // 这边的 apply 严谨点可以自己实现
    return _this.apply(context, args.concat(...arguments))
  }
}

6. 实现instanceof

原型链的向上找,找到原型的最顶端,也就是Object.prototype

javascript
function my_instance_of(leftVaule, rightVaule) {
    if(typeof leftVaule !== 'object' || leftVaule === null) return false;
    let rightProto = rightVaule.prototype,
        leftProto = leftVaule.__proto__;
    while (true) {
        if (leftProto === null) {
            return false;
        }
        if (leftProto === rightProto) {
            return true;
        }
        leftProto = leftProto.__proto__
    }
}

7. 实现new操作

要点

  • 创建一个新对象,这个对象的__proto__要指向构造函数的原型对象
  • 执行构造函数
  • 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
javascript
function _new() {
    let obj = {};
    let [constructor, ...args] = [...arguments];
    obj.__proto__ = constructor.prototype;
    let result = constructor.apply(obj, args);
    if (result && typeof result === 'function' || typeof result === 'object') {
        return result;
    }
    return obj;
}

8. 实现sleep

某个时间后就去执行某个函数,使用Promise封装

javascript
function sleep(fn, time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(fn);
        }, time);
    });
}
let saySomething = (name) => console.log(`hello,${name}`)
async function autoPlay() {
    let demo = await sleep(saySomething('xxx'),1000)
    let demo2 = await sleep(saySomething('xxxx'),1000)
    let demo3 = await sleep(saySomething('xxxxx'),1000)
}
autoPlay()

9. 深拷贝

  • 判断类型,正则和日期直接返回新对象
  • 空或者非对象类型,直接返回原值
  • 考虑循环引用,判断如果hash中含有直接返回hash中的值
  • 新建一个相应的new obj.constructor加入hash
  • 遍历对象递归(普通key和key是symbol情况)
javascript
function deepClone(obj,hash = new WeakMap()){
    if(obj instanceof RegExp) return new RegExp(obj);
    if(obj instanceof Date) return new Date(obj);
    if(obj === null || typeof obj !== 'object') return obj;
    //循环引用的情况
    if(hash.has(obj)){
        return hash.get(obj)
    }
    //new 一个相应的对象
    //obj为Array,相当于new Array()
    //obj为Object,相当于new Object()
    let constr = new obj.constructor();
    hash.set(obj,constr);
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            constr[key] = deepClone(obj[key],hash)
        }
    }
    //考虑symbol的情况
    let symbolObj = Object.getOwnPropertySymbols(obj)
    for(let i=0;i<symbolObj.length;i++){
        if(obj.hasOwnProperty(symbolObj[i])){
            constr[symbolObj[i]] = deepClone(obj[symbolObj[i]],hash)
        }
    }
    return constr
}

10. 函数柯里化

javascript
function curry(fn,...args){
  let fnLen = fn.length,
      argsLen = args.length;
  //对比函数的参数和当前传入参数
  //若参数不够就继续递归返回curry
  //若参数够就调用函数返回相应的值
  if(fnLen > argsLen){
    return function(...arg2s){
      return curry(fn,...args,...arg2s)
    }
  }else{
    return fn(...args)
  }
}

function sumFn(a,b,c){return a+ b + c};
let sum = curry(sumFn);
sum(2)(3)(5)//10
sum(2,3)(5)//10

11. 数组扁平化

javascript
let arr = [1,2,[3,4,[5,[6]]]]
console.log(arr.flat(Infinity))//flat参数为指定要提取嵌套数组的结构深度,默认值为 1
javascript
//用reduce实现
function fn(arr){
   return arr.reduce((prev,cur)=>{
      return prev.concat(Array.isArray(cur)?fn(cur):cur)
   },[])
}

12. 生成随机数

javascript
function getRandom(min, max) {
  return Math.floor(Math.random() * (max - min)) + min   
}

实现数组的随机排序

javascript
let arr = [2,3,454,34,324,32]
arr.sort(randomSort)
function randomSort(a, b) {
  return Math.random() > 0.5 ? -1 : 1;
}

13. 数字转字符串千分位

  1. \d 表示匹配一个数字字符(0-9)。

  2. (?=...) 表示这是一个正向先行断言(positive lookahead),它断言在当前位置后面必须跟着某些特定的字符,但这些字符不会包含在匹配结果中。

  3. (?:\d{3})+ 表示这是一个非捕获组(non-capturing group),它匹配一个或多个连续的三位数字。具体来说:

    • \d{3} 表示匹配三个连续的数字。
    • (?: ... )+ 表示匹配一个或多个非捕获组。
  4. (?:.\d+|$) 表示这是另一个非捕获组,它匹配小数点后跟一个或多个数字,或者字符串的结尾。具体来说:

    • .\d+ 表示匹配一个小数点后跟一个或多个数字。
    • | 表示表示逻辑或。
    • $ 表示匹配字符串的结尾。
javascript
// 输入仅支持整数
function thousandthInteger(num) {
  return num.toString().replace(/\d(?=(?:\d{3})+$)/g, '$&,');
}

// 输入同时支持整数和小数
function thousandth(num) {
  return num.toString().replace(/\d(?=(?:\d{3})+(?:\.\d+|$))/g, '$&,');
}

// 正则表达式 /\d(?=(?:\d{3})+(?:\.\d+|$))/g 中,非捕获组 (?:\d{3})+ 和 (?:\.\d+|$) 用于对匹配进行分组,但不捕获这些组的内容,从而简化了正则表达式的结构,并提高了匹配性能。

关于(?:...) 非捕获组 在正则表达式中,(?:...) 表示一个非捕获组(non-capturing group)。非捕获组的主要作用是对正则表达式的匹配进行分组,但不捕获匹配的内容。这与普通的捕获组 (...) 不同,普通的捕获组会捕获匹配的内容,并将其存储在一个编号的捕获组中,可以在后续的正则表达式操作中引用。

使用非捕获组的好处包括:

  • 性能优化:非捕获组不会存储匹配的内容,因此在某些情况下可以提高正则表达式的匹配性能。
  • 简化引用:在某些复杂的正则表达式中,使用非捕获组可以减少捕获组的数量,从而简化对捕获组的引用。
javascript
// 使用捕获组
let regex1 = /(\d{3})-(\d{2})-(\d{4})/;
let match1 = regex1.exec("123-45-6789");
console.log(match1); // 输出 ["123-45-6789", "123", "45", "6789"]

// 在第一个示例中,正则表达式使用了捕获组 (...),因此匹配的结果包含了三个捕获组的内容:"123"、"45" 和 "6789"。

// 使用非捕获组
let regex2 = /(?:\d{3})-(?:\d{2})-(?:\d{4})/;
let match2 = regex2.exec("123-45-6789");
console.log(match2); // 输出 ["123-45-6789"]



// 在第二个示例中,正则表达式使用了非捕获组 (?:...),因此匹配的结果只包含了整个匹配的内容:"123-45-6789",而没有捕获组的内容。

其他方法

javascript
function numberWithCommas(x) {
    // 转为字符串,按照.拆分
    let arr = (x + '').split(".");
    // 整数部分再拆分
    let int = arr[0].split('');
    // 保存小数部分
    const fraction = arr[1] || '';
    // 记录返回的结果
    let r = '';
    let len = int.length;
    
    // 倒序遍历
    int.reverse().forEach((v, i) => {
        // 非第一位且位值是3的倍数,添加','
        if(i !== 0 && i % 3 === 0) {
            r = v + ',' + r;
        } else {
            r = v + r;
        }
    })
    
    // 返回整数部分 + 小数部分
    return r + (!!fraction ? '.' + fraction : '');
}

// 使用示例
var number = 1234567.89;
var formattedNumber = numberWithCommas(number);
console.log(formattedNumber); // 输出: 1,234,567.89
javascript
console.log((1234567.8911111).toLocaleString('en-US'));  
// 1,234,567.891

console.log((1234567.8911111).toLocaleString('en-US', {
    maximumFractionDigits:10
})); 
// 1,234,567.8911111

14. 用正则实现 trim()

javascript
function trim(string){
    return string.replace(/^\s+|\s+$/g, '')
}

15. 手写一下AJAX

javascript
var request = new XMLHttpRequest()
 request.open('GET', 'index/a/b/c?name=xxx', true);
 request.onreadystatechange = function () {
   if(request.readyState === 4 && request.status === 200) {
     console.log(request.responseText);
   }};
 request.send();

16. 手写寄生组合继承

javascript
function inheritPrototype(Child, Parent) {
    // 创建对象,创建父类原型的一个副本
    var prototype = Object.create(Parent.prototype); 
    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    prototype.constructor = Child;
    // 指定对象,将新创建的对象赋值给子类的原型
    Child.prototype = prototype; 
}

测试用例

javascript
// 父类初始化实例属性和原型属性
function Father(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
Father.prototype.sayName = function () {
    alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function Son(name, age) {
    Father.call(this, name);
    this.age = age;
}

// 将父类原型指向子类
inheritPrototype(Son, Father);

// 新增子类原型属性
Son.prototype.sayAge = function () {
    alert(this.age);
}

var demo1 = new Son("son1", 21);
var demo2 = new Son("son2", 20);

demo1.colors.push("2"); // ["red", "blue", "green", "2"]
demo2.colors.push("3"); // ["red", "blue", "green", "3"]

17. 10进制转换成[2~16]进制区间数

javascript
function Convert(number, base = 2) {
  let rem, res = '', digits = '0123456789ABCDEF', stack = [];

  while (number) {
    rem = number % base;
    stack.push(rem);

    number = Math.floor(number / base);
  }

  while (stack.length) {
    res += digits[stack.pop()].toString();
  }
  
  return res;
}

18. 判断对象类型

javascript
let isType = (type) => (obj) => Object.prototype.toString.call(obj) === `[object ${type}]`

// let isArray = isType('Array')
// let isFunction = isType('Function')
// console.log(isArray([1,2,3]),isFunction(Map))
javascript
function getType(obj) {
  // 使用 Object.prototype.toString.call 获取对象类型
  const fullType = Object.prototype.toString.call(obj);
  // 提取类型名称,slice(8, -1):从索引 8 开始(即跳过 [object 部分),到倒数第一个字符(即 ] 之前)结束
  const type = fullType.slice(8, -1);
  return type;
}

// 示例
console.log(getType({})); // Object
console.log(getType([])); // Array
console.log(getType(new Date())); // Date
console.log(getType(null)); // Null
console.log(getType(undefined)); // Undefined
console.log(getType(123)); // Number
console.log(getType('abc')); // String
console.log(getType(true)); // Boolean
console.log(getType(function() {})); // Function

19. 实现数组去重

javascript
let unique = arr => [...new Set(arr)];

20. 实现reduce方法

javascript
Array.prototype.myReduce = function(fn, initVal) {
    let result = initVal,
        i = 0;
    if(typeof initVal  === 'undefined'){
        result = this[i]
        i++;
    }
    while( i < this.length ){
        result = fn(result, this[i])
    }
    return result
}

21. 实现一个同时允许任务数量最大为n的函数

javascript
// tasks数组的每一项是一个Promise对象
function limitRunTask(tasks, n) {
  return new Promise((resolve, reject) => {
    let index = 0, finish = 0, start = 0, res = [];
    function run() {
      if (finish == tasks.length) {
        resolve(res);
        return;
      }
      while (start < n && index < tasks.length) {
        // 每一阶段的任务数量++
        start++;
        let cur = index;
        tasks[index++]().then(v => {
          start--;
          finish++;
          res[cur] = v;
          run();
        });
      }
    }
    run();
  })
}

22. 前端竞态问题

两个信号试着彼此竞争,来影响谁先输出

例如有一个分页列表,快速地切换第二页,第三页; 先后请求 data2 与 data3,分页器显示当前在第三页,并且进入 loading; 但由于网络的不确定性,先发出的请求不一定先响应,所以有可能 data3 比 data2 先返回; 在 data2 最终返回后,分页器指示当前在第三页,但展示的是第二页的数据。

在前端开发中,常见于搜索,分页,选项卡等切换的场景。

当发出新的请求时,取消掉上次请求即可

javascript
function cancelablePromise(promiseArg) {
  let resolve = null
  let reject = null

  const wrappedPromise = new Promise((_resolve, _reject) => {
    resolve = _resolve
    reject = _reject
  })

  promiseArg && promiseArg.then(
    val => {
      resolve && resolve(val)
    },
    error => {
      reject && reject(error)
    }
  )

  return {
    promise: wrappedPromise,
    resolve: (value) => {
      resolve && resolve(value)
    },
    reject: (reason) => {
      reject && reject(reason)
    },
    cancel: () => {
      resolve = null
      reject = null
    }
  }
}

function onlyResolvesLast(fn) {
  // 保存上一个请求的 cancel 方法
  let cancelPrevious = null; 

  const wrappedFn = (...args) => {
    // 当前请求执行前,先 cancel 上一个请求
    cancelPrevious && cancelPrevious();
    // 执行当前请求
    const result = fn.apply(this, args); 
    
    // 创建指令式的 promise,暴露 cancel 方法并保存
    const { promise, cancel } = cancelablePromise(result);
    cancelPrevious = cancel;
    
    return promise;
  };

  return wrappedFn;
}

const fn = (duration) => 
  new Promise(r => {    
    setTimeout(r, duration);  
  });

const wrappedFn = onlyResolvesLast(fn);

wrappedFn(500).then(() => console.log(1));
wrappedFn(1000).then(() => console.log(2));
wrappedFn(100).then(() => console.log(3));

// 输出 3

23. 控制并发数量(p-limit)

javascript
// 简洁版pLimit
// 异步逻辑并行执行,并控制并行数量
// 一个队列来保存任务,有两个时机考虑触发任务执行:
// 1. 开始的时候一次性执行最大并发数的任务 
// 2. 然后每执行完一个启动一个新的
const shortPLimit = (concurrency) => {
  // 传入并发数量
  if (!((Number.isInteger(concurrency) || concurrency === Infinity) && concurrency > 0)) {
    throw new TypeError('Expected `concurrency` to be a number from 1 and up');
  }
  
  // 添加的并发任务要进行排队,所以我们准备一个 queue
  const queue = [];
  // 记录当前在进行中的异步任务
  let activeCount = 0;

  // 下一步处理自然就是把活跃任务数量减一,然后再跑一个任务
  const next = () => {
    activeCount--;

    if (queue.length > 0) {
      // 如果队列中还有任务,就再跑一个
      queue.shift()();
    }
  };

  // 计数,运行这个函数,改变最后返回的那个 promise 的状态
  const run = async (fn, resolve, ...args) => {
    activeCount++;
    // 运行传入的异步函数
    const result = (async () => fn(...args))();

    resolve(result);

    try {
      // 等待异步函数执行完
      await result;
    } catch {}

    // 执行完之后进行下一步处理
    next();
  };

  // 把一个异步任务添加到 queue 中,并且只要没达到并发上限就再执行一批任务
  const enqueue = (fn, resolve, ...args) => {
    queue.push(run.bind(null, fn, resolve, ...args));

    // 为了保证并发数量能控制准确,要等全部的微任务执行完再拿 activeCount 和 queue.length 来判断
    (async () => {
      await Promise.resolve();

      if (activeCount < concurrency && queue.length > 0) {
        // 如果活跃任务数量小于并发数量,就再跑一个
        queue.shift()();
      }
    })();
  };

  // 返回一个添加并发任务的函数,我们把它叫做 generator。
  // 依旧希望返回返回任务函数的promise的结果
  const generator = (fn, ...args) =>
    new Promise((resolve) => {
      enqueue(fn, resolve, ...args);
    });

  
  return generator;
};
javascript
// 完整版pLimit
// 异步逻辑并行执行,并控制并行数量
// 一个队列来保存任务,有两个时机考虑触发任务执行:
// 1. 开始的时候一次性执行最大并发数的任务 
// 2. 然后每执行完一个启动一个新的
const pLimit = (concurrency) => {
    // 传入并发数量
    if (!((Number.isInteger(concurrency) || concurrency === Infinity) && concurrency > 0)) {
      throw new TypeError('Expected `concurrency` to be a number from 1 and up');
    }
    
    // 添加的并发任务要进行排队,所以我们准备一个 queue
    const queue = [];
    // 记录当前在进行中的异步任务
    let activeCount = 0;
  
    // 下一步处理自然就是把活跃任务数量减一,然后再跑一个任务
    const next = () => {
      activeCount--;
  
      if (queue.length > 0) {
        // 如果队列中还有任务,就再跑一个
        queue.shift()();
      }
    };
  
    // 计数,运行这个函数,改变最后返回的那个 promise 的状态
    const run = async (fn, resolve, ...args) => {
      activeCount++;
      // 运行传入的异步函数
      const result = (async () => fn(...args))();

      resolve(result);
  
      try {
        // 等待异步函数执行完
        await result;
      } catch {}

      // 执行完之后进行下一步处理
      next();
    };
  
    // 把一个异步任务添加到 queue 中,并且只要没达到并发上限就再执行一批任务
    const enqueue = (fn, resolve, ...args) => {
      queue.push(run.bind(null, fn, resolve, ...args));
  
      // 为了保证并发数量能控制准确,要等全部的微任务执行完再拿 activeCount 和 queue.length 来判断
      (async () => {
        await Promise.resolve();
  
        if (activeCount < concurrency && queue.length > 0) {
          // 如果活跃任务数量小于并发数量,就再跑一个
          queue.shift()();
        }
      })();
    };
  
    // 返回一个添加并发任务的函数,我们把它叫做 generator
    const generator = (fn, ...args) =>
      new Promise((resolve) => {
        enqueue(fn, resolve, ...args);
      });

    // 为 generator 添加一些属性,方便外部获取当前的状态
    Object.defineProperties(generator, {
      activeCount: {
        get: () => activeCount
      },
      pendingCount: {
        get: () => queue.length
      },
      // 提供了一个清空任务队列的函数
      clearQueue: {
        value: () => {
          queue.length = 0;
        }
      }
    });
    
    return generator;
  };


// 测试代码
const limit = pLimit(2);
  
function asyncFun(value, delay) {
    return new Promise((resolve) => {
        console.log('start ' + value);
        setTimeout(() => resolve(value), delay);
    });
}

(async function () {
    const arr = [
        limit(() => asyncFun('aaa', 2000)),
        limit(() => asyncFun('bbb', 3000)),
        limit(() => asyncFun('ccc', 1000)),
        limit(() => asyncFun('ccc', 1000)),
        limit(() => asyncFun('ccc', 1000))
    ];
  
    const result = await Promise.all(arr);
    console.log(result);
})();

24. 手写Promise

Promise/A+规范:

三种状态 pending| fulfilled(resolved) | rejected 当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态 当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。

必须有一个支持链式调用的异步 then 方法,then接受两个参数(onFulfilled 用来接收promise成功的值, onRejected 用来接收promise失败的原因)且必须返回一个promise

javascript
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
    let that = this; // 缓存当前promise实例对象
    that.status = PENDING; // 初始状态
    that.value = undefined; // fulfilled状态时 返回的信息
    that.reason = undefined; // rejected状态时 拒绝的原因
    that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
    that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

    function resolve(value) { // value成功态时接收的终值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }
        // 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
        setTimeout(() => {
            // 调用resolve 回调对应onFulfilled函数
            if (that.status === PENDING) {
                // 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)
                that.status = FULFILLED;
                that.value = value;
                that.onFulfilledCallbacks.forEach(cb => cb(that.value));
            }
        });
    }
    function reject(reason) { // reason失败态时接收的拒因
        setTimeout(() => {
            // 调用reject 回调对应onRejected函数
            if (that.status === PENDING) {
                // 只能由pending状态 => rejected状态 (避免调用多次resolve reject)
                that.status = REJECTED;
                that.reason = reason;
                that.onRejectedCallbacks.forEach(cb => cb(that.reason));
            }
        });
    }

    // 捕获在excutor执行器中抛出的异常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    const that = this;
    let newPromise;
    // 处理参数默认值 保证参数后续能够继续执行
    onFulfilled =
        typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected =
        typeof onRejected === "function" ? onRejected : reason => {
            throw reason;
        };
    if (that.status === FULFILLED) { // 成功态
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value);
                    resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
                } catch(e) {
                    reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
                }
            });
        })
    }

    if (that.status === REJECTED) { // 失败态
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (that.status === PENDING) { // 等待态
        // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
        return newPromise = new Promise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};

实现 Promise.resolve

Promise.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。

javascript
Promise.resolve(value) {
  if (value && value instanceof Promise) {
    return value;
  } else if (value && typeof value === 'object' && typeof value.then === 'function') {
    let then = value.then;
    return new Promise(resolve => {
      then(resolve);
    });
  } else if (value) {
    return new Promise(resolve => resolve(value));
  } else {
    return new Promise(resolve => resolve());
  }
}

实现Promise.reject

和 Promise.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。

javascript
Promise.reject = function(reason) {
    return new Promise((resolve, reject) => reject(reason))
}

实现 Promise.all

  • 传入的所有 Promise 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
  • 只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promise,且它的值是第一个 rejected 的 Promise 的值;
  • 只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
javascript
Promise.all = function(promiseArr) {
    let index = 0, result = []
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                index++
                result[i] = val
                if (index === promiseArr.length) {
                    resolve(result)
                }
            }, err => {
                reject(err)
            })
        })
    })
}

实现 Promise.race

Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。

javascript
Promise.race = function(promiseArr) {
    return new Promise((resolve, reject) => {
        promiseArr.forEach(p => {
            Promise.resolve(p).then(val => {
                resolve(val)
            }, err => {
                reject(err)
            })
        })
    })
}

25. 排序算法

冒泡排序

冒泡排序的原理如下,从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到 length - 2 的位置。

javascript
function bubble(array) {
  checkArray(array);
  for (let i = array.length - 1; i > 0; i--) {
    // 从 0 到 `length - 1` 遍历
    for (let j = 0; j < i; j++) {
      if (array[j] > array[j + 1]) swap(array, j, j + 1)
    }
  }
  return array;
}

快速排序

快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作。

javascript
function sort(array) {
  if (!checkArray(array)) return
  quickSort(array, 0, array.length - 1);
  return array;
}

function quickSort(array, left, right) {
  if (left < right) {
    swap(array, , right)
    // 随机取值,然后和末尾交换,这样做比固定取一个位置的复杂度略低
    let indexs = part(array, parseInt(Math.random() * (right - left + 1)) + left, right);
    quickSort(array, left, indexs[0]);
    quickSort(array, indexs[1] + 1, right);
  }
}
function part(array, left, right) {
  let less = left - 1;
  let more = right;
  while (left < more) {
    if (array[left] < array[right]) {
      // 当前值比基准值小,`less` 和 `left` 都加一
      ++less;
      ++left;
    } else if (array[left] > array[right]) {
      // 当前值比基准值大,将当前值和右边的值交换
      // 并且不改变 `left`,因为当前换过来的值还没有判断过大小
      swap(array, --more, left);
    } else {
      // 和基准值相同,只移动下标
      left++;
    }
  }
  // 将基准值和比基准值大的第一个值交换位置
  // 这样数组就变成 `[比基准值小, 基准值, 比基准值大]`
  swap(array, right, more);
  return [less, more];
}

阮一峰版(非原地算法、返回非原始引用)

javascript
function quickSort (arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

const arr = [85, 24, 63, 45, 17, 31, 96, 50]
console.log(quickSort(arr)) // [17, 24, 31, 45, 50, 63, 85, 96]