前端知识整理

一、CSS

1. 说一下CSS的盒模型。

在HTML页面中所有的元素都可以看成是一个盒子。
盒子的组成:内容content、内边距padding、边框border、外边距margin。
盒模型的类型:

  • 标准盒模型:width = content
  • IE盒模型(怪异盒模型):width = content + padding + border
    控制盒模型的模式:border-sizing: content-box(默认值,标准盒模型)、border-box(IE盒模型)。

2. CSS选择器的优先级

CSS的特性:继承性、层叠性、优先级
标签、类/伪类/属性、全局选择器、行内样式、id、!important
优先级: !important > 行内样式 > id > 类/伪类/属性 > 标签 > 全局选择器

3. 隐藏元素的方法有哪些

  • display: none; 元素在页面上消失,不会占据空间。
  • opacity: 0; 设置元素的透明度为0,元素不可见,占据空间位置。
  • visibility: hidden; 元素在页面上消失,占据空间位置,一种不可见的状态。
  • position: absolute; 设置定位,移出可见区域。
  • clip-path

4. px、rem、em的区别是什么?

  • px是像素,显示器上给我们呈现画面的像素,每个像素的大小是一样的,绝对单位长度。
  • rem,相对单位,相对于html根节点的font-size的值,直接给html节点的font-size: 62.5%; 1rem = 10px; (16px * 62.5% = 10px)
  • em,相对单位,相对于父元素的font-size的值。

5. 重排和重绘有什么区别?

重排必定会引发重绘

  • 重排(回流):布局引擎会根据所有的样式计算出盒模型在页面上的位置和大小。
  • 重绘:计算好盒模型的位置、大小和其他一些属性后,浏览器会根据每个盒模型的的特性进行绘制。
    浏览器的渲染机制:
  • 对DOM元素的大小、位置进行修改后,浏览器需要重新计算元素的几何信息,会触发重排。
  • 只对DOM的样式进行修改,比如color和background-color,浏览器不需要重新计算DOM元素的集合信息,直接绘制了该元素的新样式。这时会触发重绘。

6. 元素水平垂直居中的方式有哪些?

  1. flex
  2. absolute + margin
  3. absolute + transform
  4. gird
  5. table

7. CSS属性中有那些可以被继承,那些不可以被继承?

  • 能够继承的属性:
  1. 字体系列的属性:font-size、font-family、font-weight、font-style;
  2. 文本系列的属性:
  1. 内联元素:color、line-height、word-spacing、letter-spacing、text-transform;
  2. 块级元素:text-align、text-indent、
  1. 元素可见性:visibility
  • 不能被继承的属性:
  1. 盒子模型的属性:width、height、margin、padding、border;
  2. 背景属性:background、background-color、background-image
  3. 定位属性:float、clear、position、top、left、right、bottom、overflow、min-width、max-width、min-height、max-height;

8. CSS预处理器?

LESS、SASS
CSS 预处理器用一种专门的编程语言,进行 Web 页面样式设计,然后再编译成正常的 CSS 文件,以供项目使用。CSS 预处理器为 CSS 增加一些编程的特性,无需考虑浏览器的兼容性问题。
优点:提高代码复用率和可维护性
缺点:需要引入编译过程 有学习成本

9. grid布局基础知识

  1. grid布局的基本概念
    grid容器:采用grid布局的父元素。
    grid项目:grid布局中每个格子内部放置的元素。
    grid内容:显示项目的区域。
    行:横向
    列:纵向
    网格线:网格布局中横向和纵向的线条。
    单元格:横纵线交汇的区域被称为单元格。
    间距:网格与网格之间的距离被称为间距。
  2. 容器属性
  • 容器划分行列
    • 取值为数值
      grid-template-rows: 100px 100px 100px;
      grid-template-columns: 100px 100px 100px;
    • 取值百分比
      grid-template-rows: 20% 30% 50%;
      grid-template-columns: 20% 30% 50%;
    • 重复函数 repeat
      grid-template-rows: repeat(3, 20%);
      grid-template-columns: repeat(3, 100px);
    • 自动填充
      grid-template-rows: repeat(auto-fill, 15%);
      grid-template-columns: repeat(auto-fill, 15%);
    • auto自动
      grid-template-rows: 100px auto 100px;
      grid-template-columns: 100px auto 100px;
    • fr片段划分
      grid-template-rows: 1fr 2fr 3fr;
      grid-template-columns: 1fr 2fr 3fr;
    • minmax()
      grid-template-rows: 200px 200px minmax(100px, 200px);
      grid-template-columns: 200px 200px minmax(100px, 200px);
  • 调整间距
    grid-row-gap: 20px;
    grid-column-gap: 20px;
    grid-gap: 20px 10px;
    gap: 30px 10px;
  • 容器内内容的对齐方式
    justify-content: center;
    align-content: center;
    place-content: center;
    place-content: space-between space-evenly;
    取值范围:start、end、center、space-around(间距环绕)、space-between(两端对齐)、space-evenly(间距平分)。
  • 网格内项目的对齐方式
    justify-items: center;
    align-items: center;
    place-items: center;
  1. 项目属性
  • 合并单元格属性
    grid-row-start: 1;
    grid-row-end: 2;
    grid-column-start: 4;
    grid-column-end: 6;
    grid-row: 1/2;
    grid-column: 4/6;
  • 单个项目位置
```
  justify-self 和 align-self
  `justify-self` 属性设置单元格内容的水平位置(左中右),跟 `justify-items` 属性的用法完全一致,但只作用于单个项目。
  `align-self` 属性设置单元格内容的垂直位置(上中下),跟 `align-items` 属性的用法完全一致,也是只作用于单个项目。
  .item {
    justify-self: start | end | center | stretch;
    align-self: start | end | center | stretch;
    /* 简写 place-self: <align-self> <justify-self>; */
  }
```

注意点:复合属性的书写都是先上下再左右

二、JS基础

1. JS由那三部分组成?

  1. ECMASript
  2. 文档对象模型(DOM)
  3. 浏览器对象模型(BOM)

2. 操作数组的方法有那些?

  1. 高阶函数:map、filter、forEach、reduce、find、findIndex、every、some、
  2. push、unshift、shift、pop、splice、slice、join、concat、fill、indexOf、includes、reduceRight、reverse、sort、
    会改变原数组的方法:push、unshift、shift、pop、splice、join、reverse、sort

3. JS对数据类型的检测方法有哪些?

  1. typeof:只能判断出基本数据类型,不能区分引用类型。
  2. instanceof:只能判断出引用类型,不能区分基本类型。
    原理:判断实例对象的__proto__属性是否和构造函数的prototype属性指向同一个原型对象。如果没找到,则会在原型链上继续查找。
  3. Object.prototype.toString:最佳的方法。

4. 说一下闭包,以及特点。

  1. 如何产生闭包?
    -函数嵌套,内部函数引用了外部函数的(变量或函数)时,就产生了闭包
  2. 闭包的作用?
    使用闭包函数执行完成后,内部函数引用外部函数的数据(变量或函数)在存在内存中
    使函数外部可以操作到函数内部的数据
  3. 闭包的生命周期
    1. 产生: 在内部嵌套函数定义执行(不是真实执行)完成时就产生了(不是在调用)
    2. 死亡: 在内部嵌套函数成为垃圾对象时
function fn1() {
// 程序执行到此处时, 先函数提升然后变量提升(内部函数对象创建) 闭包出现 
// 因为有 var a = undefined
  var a = 2
  function fun() {
    console.log(++a)
  }
  return fun
}
var f = fn1()
f() // 3
f() // 4
// 此时没有死亡
f = null // 此时内部函数无引用指向 死亡
  1. 闭包的应用-定义js模块
    - 具有特定功能的js模块
    - 将所有的数据和方法都封装到一个函数的内部(私有的)
    - 只向外部暴露一个包含n个方法的对象或函数
    - 模块的使用者,只需要通过模块导出的对象调用方法来实现对应的功能。
/*方法一*/
function my_module() {
  var msg = 'songRuiXue'
  function doSomething() {
    console.log('doSomething' + msg.toUpperCase()) // msg: 全部大写
  }
  function doOtherthing() {
    console.log('doOtherthing' + msg.toLowerCase()) // msg: 全部小写
  }
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}
/*方法二*/
(function(window) {
  var msg = 'songRuiXue'
  function doSomething() {
    console.log('doSomething' + msg.toUpperCase()) // msg: 全部大写
  }
  function doOtherthing() {
    console.log('doOtherthing' + msg.toLowerCase()) // msg: 全部小写
  }
  window.obj = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})(window)

调用
/*方法一*/
var obj = my_module()
obj.doSomething()
obj.doOtherthing()

/*方法二*/
obj.doSomething()
obj.doOtherthing()

5. 事件冒泡、事件捕获及事件代理(事件委托)?

事件流(event flow)过程:事件捕获 -> 目标阶段 -> 事件冒泡
阻止冒泡:event.stopPropagation() (停止传播)
阻止默认行为:event.preventDefault() return false
return false 不仅阻止了事件往上冒泡,而且阻止了事件本身(默认事件)。event.stopPropagation()则只阻止事件往上冒泡,不阻止事件本身。
事件冒泡、事件捕获及事件代理

6. 说一下for…in 和 for…of的区别?

for...of遍历获取的是对象的键值,for...in遍历获取的是对象的键名
for...of只遍历当前对象不会去遍历原型链,for...in会遍历对象的整个原型链,性能差。
对于数组的遍历,for...of只返回数组下标对应的属性值。for...in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性)。
总结:for...in主要用来遍历对象,不适合遍历数组。 for....of循环可以用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象

7. 给 a b c 三个请求,希望 c 在 a b 获取结果之后再请求。

1. Promise.all

2. 使用数组实现

const fs = require('fs')

const arr = []
function fn (data) {
  arr.push(data)
  if (arr.length === 2) {
    console.log(arr)
    // 在此时可以执行 c 的逻辑
  }
}
fs.readFile('./a.text', 'utf-8', (err, data) => {
  fn(data)
})
fs.readFile('./b.text', 'utf-8', (err, data) => {
  fn(data)
})

三、JS手写题

1. 提取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))

2. 数组扁平化

// 方法一
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))

3. 生成随机数组

// 方法一
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)

4. 两数之和

// 两数之和
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))

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

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));

2. 打印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))

6. 数组去重

// 数组去重
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));

7. 计算斐波那契数列

// 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));

8. 计算阶乘

// 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));

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))

10. 深拷贝、浅拷贝、赋值

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();    

11. 把类数组转换为数组

//通过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)

12. 括号匹配

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))

13. 翻转字符串

// 方法一
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))

14. 生成随机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());

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

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))

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);

17. 实现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)
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值