js手写题

126 阅读10分钟

提取url参数,并转化为对象形式。

const url = 'https://alibaba.com?a=1&b=2&c=3#hash'

function queryURLParams(URL) {
  const url = URL.split('?')[1]
  const urlSearchParams = new URLSearchParams(url)
  const params = Object.fromEntries(urlSearchParams.entries())
  // const arr = [
  //   ["0", "a"],
  //   ["1", "b"],
  //   ["2", "c"],
  // ];
  // const obj = Object.fromEntries(arr);
  // console.log(obj); // { 0: "a", 1: "b", 2: "c" }
  return params
}
console.log(queryURLParams(url))

数组扁平化

// 方法一
arr.flat(Infinity)

// 方法二
const flatten = function(arr) {
  return [].concat(...arr.map(v => Array.isArray(v) ? flatten(v) : v))
}

const arr = [1, 2, [3, 4, [5, 6, [7, 8]], 9, 10, [11, 12, [13, 14]]]]
console.log(flatten(arr))

生成随机数组

// 方法一
function randomArr(arr) {
  // 范围:[0, 1)
  return arr.sort(() => Math.random() - 0.5)
}

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(randomArr(arr))

// 方法二
function sort(arr) {
  for (let i = 0; i < arr.length; i++) {
    const randomIndex = parseInt(Math.random() * arr.length) // 范围:[0, arr.length)
    ;[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]
  }
  return arr
}

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
sort(arr)

两数之和

// 两数之和
function twoSum(nums, target) {
  for (let i = 0; i < nums.length; i++) {
    const num = nums[i]
    const targetIndex = nums.indexOf(target - num)
    if (targetIndex > -1 && targetIndex !== i) {
      return [i, targetIndex]
    }
  }
}

const nums = [2, 7, 11, 15]
console.log(twoSum(nums, 9))

质数(只能被1和自身整除的数为质数)

判断一个数是不是质数(只能被1和自身整除的数为质数)

在一般领域,对正整数n,如果用2到 根号n 之间的所有整数去除,均无法整除,则n为质数。 质数大于等于2 不能被它本身和1以外的数整除

// 判断一个数是不是质数(只能被1和自身整除的数为质数)
function isPrimeNumber(num) {
  if (num === 1) return false
  if (num === 2) return true
  for (let i = 2; i < Math.sqrt(num) + 1; i++) {
    if (num % i === 0) {
      // 在 [2, num) 中找到了可以被整除的数
      return false
    }
  }
  return true
}
console.log(isPrimeNumber(97));

打印100以内的所有质数

function printPrimeNum(num) {
  const isPrimeNumbers = []
  for (let i = 2; i <= num; i++) {
    let isPrime = true
    for (let j = 2; j < i; j++) {
      if (i % j === 0) {
        // 不是素数
        isPrime = false
        break
      }
    }
    if (isPrime) {
      isPrimeNumbers.push(i)
    }
  }
  return isPrimeNumbers
}

console.log(printPrimeNum(100))

数组去重

// 数组去重
const arr = [1, 2, 3, 5, 2, 4, 1, 3, 10, 6, 2]

// 方法一 new Set()
const distinct = arr => Array.from(new Set(arr))

// 方法二 filter + indexOf
const distinct = arr => arr.filter((item, index, arr) => arr.indexOf(item) === index)

console.log(distinct(arr));

计算斐波那契数列

// 1, 2, 3, 5, 8, 13, 21, ....
// fn(n) = fn(n - 1) + fn(n - 2),fn(1) = 1,fn(2) = 2
function fibonacci(n) {
  // if (n === 1) return 1
  // if (n === 2) return 2
  if (n <= 2) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
}

console.log(fibonacci(10));

计算阶乘

// n! = n * (n - 1)!
// n! = n * (n - 1) * ... * 3 * 2 * 1
// 1! = 1
function factorial(n) {
  // 返回的是n的阶乘
  if (n === 1) return 1
  return n * factorial(n - 1)
}

console.log(factorial(9));

数组排序

1. 快速排序

利用二分法 + 递归的原理

function quickSort(arr) {
  // 退出递归的条件:数组中只剩下一个元素时 返回数组
  if (arr.length <= 1) return arr

  const middleIndex = Math.floor(arr.length / 2)
  const middle = arr[middleIndex]

  const left = []
  const right = []
  const center = []

  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < middle) {
      left.push(arr[i])
    } else if (arr[i] > middle) {
      right.push(arr[i])
    } else {
      center.push(arr[i])
    }
  }

  return quickSort(left).concat(center, quickSort(right))
}

const arr = [3, 1, 4, 9, 10, 1, 3, 2, 5, 9, 6]
console.log(quickSort(arr))

2. 插入排序

以下是插入排序的基本思想步骤:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5,直到所有元素均排序完毕。
function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    let j = i - 1
    const current = arr[i] 
    // 必须使用变量将当前值保存起来,不然后面数组下标变化,导致出现问题。
    while (j >= 0 && arr[j] > current) {
      arr[j + 1] = arr[j]
      j--
    }
    arr[j + 1] = current
  }
  return arr
}

const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33]
console.log(insertSort(arr))

3. 冒泡排序

function bubbleSort(arr) {
  // 不易理解
  // for (let i = 0; i < arr.length - 1; i++) {
  //   for (let j = i + 1; j < arr.length; j++) {
  //     if (arr[i] > arr[j]) {
  //       [arr[i], arr[j]] = [arr[j], arr[i]]
  //     }
  //   }
  // }
  
  // 原数组:[4, 3, 2, 1]
  // [4, 3, 2, 1] i = 0, j = 0 --> [3, 4, 2, 1]
  //              i = 0, j = 1 --> [3, 2, 4, 1]
  //              i = 0, j = 2 --> [3, 2, 1, 4]
  // [3, 2, 1, 4] i = 1  j = 0, 1  --> [2, 3, 1, 4], [2, 1, 3, 4]
  // [2, 1, 3, 4] i = 2  j = 0  --> [1, 2, 3, 4]
  // [1, 2, 3, 4] i = 3         --> [1, 2, 3, 4]
  // 外层遍历控制行,内层遍历控制列
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
  }
  return arr
}

const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33, 8]
console.log(bubbleSort(arr))

4. 选择排序

// 思想:查找最小值,记录索引。将未排序列最小值与已排序列的后一位进行交换。
function selectionSort(arr) {
  let minIndex
  for (let i = 0; i < arr.length; i++) {
    minIndex = i
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
  }
  return arr
}

const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33, 8]
console.log(selectionSort(arr))

深拷贝、浅拷贝、赋值

1. 深拷贝

深拷贝

// 方法一
JSON.parse(JSON.stringify())

// 方法二(基础版本)
function deepClone(target, map = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeof target === 'object') {
    // 对于引用数据类型,创建一个对象将所有字段依次添加到对象上并返回
    const obj = Array.isArray(target) ? [] : {}

    // 检查map中有无克隆过的对象
    // 有 - 直接返回
    // 没有 - 将当前对象作为key,克隆对象作为value进行存储
    // 继续克隆
    if (map.get(target)) {
      return map.get(target)
    }
    map.set(target, obj)
    for (const key in target) {
      obj[key] = deepClone(target[key], map)
    }
    return obj
  } else {
    // 对于基本数据类型直接返回
    return target
  }
}

2. 浅拷贝

浅拷贝

// 方法一
const obj = Object.assign({}, target)

// 方法二
const a = { ...obj }

// 方法三
Array.prototype.concat()
const arr2 = arr.concat();    

// 方法三
Array.prototype.slice()
const arr3 = arr.slice();    

把类数组转换为数组

//通过Array.from方法来实现转换
Array.from(arrayLike)

// 通过扩展运算符
[...arrayLike]

//通过call调用数组的slice方法来实现转换
Array.prototype.slice.call(arrayLike)

//通过call调用数组的splice方法来实现转换
Array.prototype.splice.call(arrayLike,0)

//通过apply调用数组的concat方法来实现转换
Array.prototype.concat.apply([],arrayLike)

括号匹配

const str1 = '{[]}' // true
const str2 = '{[[]}' // false
const str3 = '{[]}[' // false
const str4 = '{[()]}' // true
function isValid(str) {
  if (str.length % 2 !== 0) return false
  const stack = []
  const map = {
    ')': '(',
    ']': '[',
    '}': '{'
  }
  for (const s of str) {
    if (s === '{' || s === '[' || s === '(') {
      // 只要是 {、[、( 就入栈
      stack.push(s)
    } else {
      // 遇到 }、]、) 就匹配出栈
      if (stack.pop() !== map[s]) return false
    }
  }
  if (stack.length === 0) return true
}

console.log(isValid(str1))
console.log(isValid(str2))
console.log(isValid(str3))
console.log(isValid(str4))

翻转字符串

// 方法一
function reverseString(str) {
  return str.split('').reverse().join('')
}

// 方法二
function reverseString(str) {
  let result = ''
  const len = str.length
  for (let i = len - 1; i >= 0; i--) {
    result += str[i]
  }
  return result
}

const str = 'hello world !'
console.log(reverseString(str))

生成随机16进制颜色

// 方法一 padStart, 补全字符串
function randomHexColor() {
  // [0, 256) 向下取整后为 [0, 255)
  const red = Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
  const green = Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
  const blue = Math.floor(Math.random() * 256).toString(16).padStart(2, '0')
  return `#${red}${green}${blue}`
}

// 方法二
function randomHexColor() {
  return `#${Math.random().toString(16).substring(2, 8)}`
}

// 方法三
function randomHexColor() {
  const red = Math.floor(Math.random() * 256)
  const green = Math.floor(Math.random() * 256)
  const blue = Math.floor(Math.random() * 256)
  const opacity = Math.random().toFixed(2)
  return `rgba(${red}, ${green}, ${blue}, ${opacity})`
}

console.log(randomHexColor());

获取指定范围内的随机整数

Math.floor // 向下取整
Math.ceil // 向上取整
Math.round // 四舍五入

function rangeRandomNum(min, max) {
  // [min, max]
  return Math.round(Math.random() * (max - min)) + min
  // [min, max)
  return Math.floor(Math.random() * (max - min)) + min
  // (min, max]
  return Math.ceil(Math.random() * (max - min)) + min
  // (min, max)
  return Math.round(Math.random() * (max - min - 2)) + min + 1
}

console.log(rangeRandomNum(3, 9))

实现 call、apply、bind

Function.prototype.myCall = function(context, ...args) {
  // 1. 判断调用myCall的是否为函数
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.myCall - 调用myCall的对象必须是函数!')
  }

  // 如果没有传入上下文对象,则默认为全局对象
  // ES11 引入了 globalThis,它是一个统一的全局对象 (window对象)
  context = context || globalThis

  // 用Symbol来创建唯一的一个function,防止命名冲突。
  const fn = Symbol()

  // this 是调用myCall的函数,将函数绑定到上下文对象的新属性上
  context[fn] = this

  // 传入myCall的多个参数
  const result = context[fn](...args)
  console.log(context);

  // 将增加在上下文对象上的fn方法删除
  delete context.fn

  return result
}
Function.prototype.myApply = function(context, args = []) {
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.myApply - 调用myApply的对象必须是函数!')
  }

  if (!Array.isArray(args)) {
    throw new TypeError('Function.prototype.myApply - 第二个参数必须为数组!')
  }

  context = context || globalThis

  const fn = Symbol()

  context[fn] = this

  const result = context[fn](...args)

  delete context.fn

  return result
}
Function.prototype.myBind = function(context, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.myBind - 调用myBind的对象必须是函数!')
  }

  // 将要被调用的函数用常量保存起来
  const _this = this

  // 返回一个匿名函数
  return function() {

    const fn = Symbol()

    context[fn] = _this

    // 整合参数并执行
    const result = context[fn](...args, ...arguments)

    delete context[fn]

    return result
  }
}

实现 new

function myNew(constructor, ...args) {
  // 创建一个空对象
  const obj = {}
  // 改变 obj 的原型
  obj.__proto__ = constructor.prototype
  // 使用 obj 来调用构造函数,并传入参数。
  const result = constructor.apply(obj, args)
  // 构造函数如果有返回值,返回值为引用数据类型,直接返回
  // 构造函数如果没有返回值,返回 obj
  return typeof result === 'object' ? result : obj
}

const p2 = myNew(Person, 'ps2', 16)

实现发布订阅模式(EventEmitter)、观察者模式(Observer)

// 参考文章
// https://juejin.cn/post/7052637219084828680
class Observer {
  constructor() {
    this.events = {}
  }
  on(type, handler) {
    if (!this.events[type]) {
      this.events[type] = []
    }
    this.events[type].push(handler)
  }
  once(type, handler) {
    const onceCallback = data => {
      handler(data) // 执行回调函数
      this.off(type, onceCallback)
    }
    this.on(type, onceCallback)
  }
  // 一、只有 type 删除整个type事件
  // 二、存在 handler 只删除里面有 handler 的
  off(type, handler) {
    if (!this.events[type]) return
    if (!handler) {
      delete this.events[type]
    } else {
      this.events[type] = this.events[type].filter(cb => cb !== handler)
    }
  }
  emit(type, params) {
    if (!this.events[type]) return
    this.events[type].forEach(handler => {
      handler(params)
    })
  }
}

const ob = new Observer()
function handlerA(data) {
  console.log('getList', data);
}
function handlerB(data) {
  console.log('getData', data);
}
ob.on('getListA', handlerA)
ob.on('getListA', handlerB)
ob.on('getData', handlerB)
// ob.off('getData')
// ob.off('getListA', handlerA)
ob.emit('getListA', '这是测试数据A')
ob.emit('getData', '这是测试数据B')
console.log(ob);

实现Promise

1. 基础版

class MyPromise {
  // 定义三种状态
  static PENGING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECT = 'reject';

  constructor(executor) {
    // 初始化状态
    this.status = MyPromise.PENGING;
    // 初始化值
    this.value = null;
    this.reason = null;
    // 成功的回调队列
    this.onFulfilledCallback = [];
    // 失败的回调队列
    this.onRejectedCallback = [];
    // 执行并绑定this
    executor(this.reslove.bind(this), this.reject.bind(this));
  }

  reslove(value) {
    // 状态一旦变更就不会再发生变化。
    if (this.status !== MyPromise.PENGING) return;
    this.value = value;
    this.status = MyPromise.FULFILLED;
    this.onFulfilledCallback.length && this.onFulfilledCallback.shift()();
  }

  reject(reason) {
    // 状态一旦变更就不会再发生变化。
    if (this.status !== MyPromise.PENGING) return;
    this.reason = reason;
    this.status = MyPromise.REJECT;
    this.onRejectedCallback.length && this.onRejectedCallback.shift()();
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled !== 'function' ? () => {} : onFulfilled;
    onRejected = typeof onRejected !== 'function' ? () => {} : onRejected;
    return new MyPromise(() => {
      if (this.status === MyPromise.PENGING) {
        this.onFulfilledCallback.push(() =>
          setTimeout(() => onFulfilled(this.value))
        );
        this.onRejectedCallback.push(() =>
          setTimeout(() => onRejected(this.reason))
        );
      } else if (this.status === MyPromise.FULFILLED) {
        onFulfilled(this.value);
      } else if (this.status === MyPromise.REJECT) {
        onRejected(this.reason);
      }
    });
  }
}

const p1 = new MyPromise((resolve, reject) => {
  resolve('p1成功');
  console.log('p1-console');
  setTimeout(() => {
    resolve('p1成功-setTimeout');
    reject('p1-失败-setTimeout');
    console.log('p1-console-setTimeout');
  }, 100);
});

p1.then(
  value => {
    console.log('value: ', value);
  },
  reason => {
    console.log('reason: ', reason);
  }
);

2. 实现all函数

成功的时候返回一个成功的数组;失败的时候则返回最先被reject失败状态的值。

static all(list) {
  return new MyPromise((resolve, reject) => {
    const result = []
    let count = 0
    for (let i = 0; i < list.length; i++) {
      const p = list[i]
      p.then(value => {
        result[i] = value
        count += 1
        if (count === list.length) {
          resolve(result)
        }
      }, reason => {
        reject(reason)
      })
    }
  })
}

3. 实现race函数

只要有一个完成,不管成功还是失败,都返回

static race(list) {
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < list.length; i++) {
      const p = list[i]
      p.then(value => {
        resolve(value)
      }, reason => {
        reject(reason)
      })
    }
  })
}

完整代码

class MyPromise {
  // 定义三种状态
  static PENGING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECT = 'reject';

  constructor(executor) {
    // 初始化状态
    this.status = MyPromise.PENGING;
    // 初始化值
    this.value = null;
    this.reason = null;
    // 成功的回调队列
    this.onFulfilledCallback = [];
    // 失败的回调队列
    this.onRejectedCallback = [];
    // 执行并绑定this
    executor(this.reslove.bind(this), this.reject.bind(this));
  }

  reslove(value) {
    // 状态一旦变更就不会再发生变化。
    if (this.status !== MyPromise.PENGING) return;
    this.value = value;
    this.status = MyPromise.FULFILLED;
    this.onFulfilledCallback.length && this.onFulfilledCallback.shift()();
  }

  reject(reason) {
    // 状态一旦变更就不会再发生变化。
    if (this.status !== MyPromise.PENGING) return;
    this.reason = reason;
    this.status = MyPromise.REJECT;
    this.onRejectedCallback.length && this.onRejectedCallback.shift()();
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled !== 'function' ? () => {} : onFulfilled;
    onRejected = typeof onRejected !== 'function' ? () => {} : onRejected;
    return new MyPromise(() => {
      if (this.status === MyPromise.PENGING) {
        this.onFulfilledCallback.push(() =>
          setTimeout(() => onFulfilled(this.value))
        );
        this.onRejectedCallback.push(() =>
          setTimeout(() => onRejected(this.reason))
        );
      } else if (this.status === MyPromise.FULFILLED) {
        onFulfilled(this.value);
      } else if (this.status === MyPromise.REJECT) {
        onRejected(this.reason);
      }
    });
  }

  // 成功的时候返回一个成功的数组;失败的时候则返回最先被reject失败状态的值。
  static all(list) {
    return new MyPromise((resolve, reject) => {
      const result = []
      let count = 0
      for (let i = 0; i < list.length; i++) {
        const p = list[i]
        p.then(value => {
          result[i] = value
          count += 1
          if (count === list.length) {
            resolve(result)
          }
        }, reason => {
          reject(reason)
        })
      }
    })
  }

  static race(list) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < list.length; i++) {
        const p = list[i]
        p.then(value => {
          resolve(value)
        }, reason => {
          reject(reason)
        })
      }
    })
  }
}

const p1 = new MyPromise((resolve, reject) => {
  // resolve('p1成功');
  // console.log('p1-console');
  setTimeout(() => {
    resolve('p1成功-setTimeout');
    reject('p1-失败-setTimeout');
    console.log('p1-console-setTimeout');
  }, 100);
});
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2成功');
    // reject('p2失败')
  }, 200);
});
const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3成功');
  }, 30);
});
// MyPromise.all([p1, p2, p3]).then(res => {
//   console.log('all-成功', res);
// }, reason => {
//   console.log('all-失败', reason)
// })
MyPromise.race([p1, p2, p3]).then(res => {
  console.log('race-成功', res);
}, reason => {
  console.log('race-失败', reason)
})