一、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. 元素水平垂直居中的方式有哪些?
- flex
- absolute + margin
- absolute + transform
- gird
- table
7. CSS属性中有那些可以被继承,那些不可以被继承?
- 能够继承的属性:
- 字体系列的属性:font-size、font-family、font-weight、font-style;
- 文本系列的属性:
- 内联元素:color、line-height、word-spacing、letter-spacing、text-transform;
- 块级元素:text-align、text-indent、
- 元素可见性:visibility
- 不能被继承的属性:
- 盒子模型的属性:width、height、margin、padding、border;
- 背景属性:background、background-color、background-image
- 定位属性: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布局基础知识
- grid布局的基本概念
grid容器:采用grid布局的父元素。
grid项目:grid布局中每个格子内部放置的元素。
grid内容:显示项目的区域。
行:横向
列:纵向
网格线:网格布局中横向和纵向的线条。
单元格:横纵线交汇的区域被称为单元格。
间距:网格与网格之间的距离被称为间距。 - 容器属性
- 容器划分行列
- 取值为数值
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;
- 项目属性
- 合并单元格属性
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由那三部分组成?
- ECMASript
- 文档对象模型(DOM)
- 浏览器对象模型(BOM)
2. 操作数组的方法有那些?
- 高阶函数:map、filter、forEach、reduce、find、findIndex、every、some、
- push、unshift、shift、pop、splice、slice、join、concat、fill、indexOf、includes、reduceRight、reverse、sort、
会改变原数组的方法:push、unshift、shift、pop、splice、join、reverse、sort
3. JS对数据类型的检测方法有哪些?
- typeof:只能判断出基本数据类型,不能区分引用类型。
- instanceof:只能判断出引用类型,不能区分基本类型。
原理:判断实例对象的__proto__属性是否和构造函数的prototype属性指向同一个原型对象。如果没找到,则会在原型链上继续查找。 - Object.prototype.toString:最佳的方法。
4. 说一下闭包,以及特点。
- 如何产生闭包?
-函数嵌套,内部函数引用了外部函数的(变量或函数)时,就产生了闭包
- 闭包的作用?
使用闭包函数执行完成后,内部函数引用外部函数的数据(变量或函数)在存在内存中
使函数外部可以操作到函数内部的数据
- 闭包的生命周期
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 // 此时内部函数无引用指向 死亡
- 闭包的应用-定义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. 插入排序
以下是插入排序的基本思想步骤:
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤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)
})