写点什么

字节前端高频手写面试题(持续更新中)

  • 2023-01-03
    浙江
  • 本文字数:12914 字

    阅读完需:约 42 分钟

Promise

// 模拟实现Promise// Promise利用三大手段解决回调地狱:// 1. 回调函数延迟绑定// 2. 返回值穿透// 3. 错误冒泡
// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失败
class Promise { constructor(exector) { // 初始化状态 this.status = PENDING; // 将成功、失败结果放在this上,便于then、catch访问 this.value = undefined; this.reason = undefined; // 成功态回调函数队列 this.onFulfilledCallbacks = []; // 失败态回调函数队列 this.onRejectedCallbacks = [];
const resolve = value => { // 只有进行中状态才能更改状态 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 成功态函数依次执行 this.onFulfilledCallbacks.forEach(fn => fn(this.value)); } } const reject = reason => { // 只有进行中状态才能更改状态 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 失败态函数依次执行 this.onRejectedCallbacks.forEach(fn => fn(this.reason)) } } try { // 立即执行executor // 把内部的resolve和reject传入executor,用户可调用resolve和reject exector(resolve, reject); } catch(e) { // executor执行出错,将错误内容reject抛出去 reject(e); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function'? onRejected : reason => { throw new Error(reason instanceof Error ? reason.message : reason) } // 保存this const self = this; return new Promise((resolve, reject) => { if (self.status === PENDING) { self.onFulfilledCallbacks.push(() => { // try捕获错误 try { // 模拟微任务 setTimeout(() => { const result = onFulfilled(self.value); // 分两种情况: // 1. 回调函数返回值是Promise,执行then操作 // 2. 如果不是Promise,调用新Promise的resolve函数 result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }); self.onRejectedCallbacks.push(() => { // 以下同理 try { setTimeout(() => { const result = onRejected(self.reason); // 不同点:此时是reject result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }) } else if (self.status === FULFILLED) { try { setTimeout(() => { const result = onFulfilled(self.value); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }); } catch(e) { reject(e); } } else if (self.status === REJECTED) { try { setTimeout(() => { const result = onRejected(self.reason); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } } }); } catch(onRejected) { return this.then(null, onRejected); } static resolve(value) { if (value instanceof Promise) { // 如果是Promise实例,直接返回 return value; } else { // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED return new Promise((resolve, reject) => resolve(value)); } } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } static all(promiseArr) { const len = promiseArr.length; const values = new Array(len); // 记录已经成功执行的promise个数 let count = 0; return new Promise((resolve, reject) => { for (let i = 0; i < len; i++) { // Promise.resolve()处理,确保每一个都是promise实例 Promise.resolve(promiseArr[i]).then( val => { values[i] = val; count++; // 如果全部执行完,返回promise的状态就可以改变了 if (count === len) resolve(values); }, err => reject(err), ); } }) } static race(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p).then( val => resolve(val), err => reject(err), ) }) }) }}
复制代码

请实现一个 add 函数,满足以下功能

add(1);             // 1add(1)(2);      // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(1, 2, 3); // 6
复制代码


function add(...args) {  // 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值  let fn = function(...newArgs) {   return add.apply(null, args.concat(newArgs))  }
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 fn.toString = function() { return args.reduce((total,curr)=> total + curr) }
return fn}
复制代码


考点:


  • 使用闭包, 同时要对 JavaScript 的作用域链(原型链)有深入的理解

  • 重写函数的 toSting()方法


// 测试,调用toString方法触发求值
add(1).toString(); // 1add(1)(2).toString(); // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(1, 2, 3).toString(); // 6
复制代码

实现 Array.of 方法

Array.of()方法用于将一组值,转换为数组


  • 这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

  • Array.of()基本上可以用来替代Array()new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一


Array.of(3, 11, 8) // [3,11,8]Array.of(3) // [3]Array.of(3).length // 1
复制代码


实现


function ArrayOf(){  return [].slice.call(arguments);}
复制代码

实现 async/await

分析


// generator生成器  生成迭代器iterator
// 默认这样写的类数组是不能被迭代的,缺少迭代方法let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}
// // 使用迭代器使得可以展开数组// // Symbol有很多元编程方法,可以改js本身功能// likeArray[Symbol.iterator] = function () {// // 迭代器是一个对象 对象中有next方法 每次调用next 都需要返回一个对象 {value,done}// let index = 0// return {// next: ()=>{// // 会自动调用这个方法// console.log('index',index)// return {// // this 指向likeArray// value: this[index],// done: index++ === this.length// }// }// }// }// let arr = [...likeArray]
// console.log('arr', arr)
// 使用生成器返回迭代器// likeArray[Symbol.iterator] = function *() {// let index = 0// while (index != this.length) {// yield this[index++]// }// }// let arr = [...likeArray]
// console.log('arr', arr)

// 生成器 碰到yield就会暂停// function *read(params) {// yield 1;// yield 2;// }// 生成器返回的是迭代器// let it = read()// console.log(it.next())// console.log(it.next())// console.log(it.next())
// 通过generator来优化promise(promise的缺点是不停的链式调用)const fs = require('fs')const path = require('path')// const co = require('co') // 帮我们执行generator
const promisify = fn=>{ return (...args)=>{ return new Promise((resolve,reject)=>{ fn(...args, (err,data)=>{ if(err) { reject(err) } resolve(data) }) }) }}
// promise化let asyncReadFile = promisify(fs.readFile)
function * read() { let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8') let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8') return content2}
// 这样写太繁琐 需要借助co来实现// let re = read()// let {value,done} = re.next()// value.then(data=>{// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 // let {value,done} = re.next(data) // value.then(d=>{// let {value,done} = re.next(d)// console.log(value,done)// })// }).catch(err=>{// re.throw(err) // 手动抛出错误 可以被try catch捕获// })


// 实现co原理function co(it) {// it 迭代器 return new Promise((resolve,reject)=>{ // 异步迭代 需要根据函数来实现 function next(data) { // 递归得有中止条件 let {value,done} = it.next(data) if(done) { resolve(value) // 直接让promise变成成功 用当前返回的结果 } else { // Promise.resolve(value).then(data=>{ // next(data) // }).catch(err=>{ // reject(err) // }) // 简写 Promise.resolve(value).then(next,reject) } } // 首次调用 next() })}
co(read()).then(d=>{ console.log(d)}).catch(err=>{ console.log(err,'--')})
复制代码


整体看一下结构


function asyncToGenerator(generatorFunc) {    return function() {      const gen = generatorFunc.apply(this, arguments)      return new Promise((resolve, reject) => {        function step(key, arg) {          let generatorResult          try {            generatorResult = gen[key](arg)          } catch (error) {            return reject(error)          }          const { value, done } = generatorResult          if (done) {            return resolve(value)          } else {            return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))          }        }        step("next")      })    }}
复制代码


分析


function asyncToGenerator(generatorFunc) {  // 返回的是一个新的函数  return function() {
// 先调用generator函数 生成迭代器 // 对应 var gen = testG() const gen = generatorFunc.apply(this, arguments)
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的 // var test = asyncToGenerator(testG) // test().then(res => console.log(res)) return new Promise((resolve, reject) => {
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍 // key有next和throw两种取值,分别对应了gen的next和throw方法 // arg参数则是用来把promise resolve出来的值交给下一个yield function step(key, arg) { let generatorResult
// 这个方法需要包裹在try catch中 // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误 try { generatorResult = gen[key](arg) } catch (error) { return reject(error) }
// gen.next() 得到的结果是一个 { value, done } 的结构 const { value, done } = generatorResult
if (done) { // 如果已经完成了 就直接resolve这个promise // 这个done是在最后一次调用next后才会为true // 以本文的例子来说 此时的结果是 { done: true, value: 'success' } // 这个value也就是generator函数最后的返回值 return resolve(value) } else { // 除了最后结束的时候外,每次调用gen.next() // 其实是返回 { value: Promise, done: false } 的结构, // 这里要注意的是Promise.resolve可以接受一个promise为参数 // 并且这个promise参数被resolve的时候,这个then才会被调用 return Promise.resolve( // 这个value对应的是yield后面的promise value ).then( // value这个promise被resove的时候,就会执行next // 并且只要done不是true的时候 就会递归的往下解开promise // 对应gen.next().value.then(value => { // gen.next(value).value.then(value2 => { // gen.next() // // // 此时done为true了 整个promise被resolve了 // // 最外部的test().then(res => console.log(res))的then就开始执行了 // }) // }) function onResolve(val) { step("next", val) }, // 如果promise被reject了 就再次进入step函数 // 不同的是,这次的try catch中调用的是gen.throw(err) // 那么自然就被catch到 然后把promise给reject掉啦 function onReject(err) { step("throw", err) }, ) } } step("next") }) }}
复制代码

基于 Generator 函数实现 async/await 原理

核心:传递给我一个Generator函数,把函数中的内容基于Iterator迭代器的特点一步步的执行


function readFile(file) {    return new Promise(resolve => {        setTimeout(() => {            resolve(file);    }, 1000);    })};
function asyncFunc(generator) { const iterator = generator(); // 接下来要执行next // data为第一次执行之后的返回结果,用于传给第二次执行 const next = (data) => { let { value, done } = iterator.next(data); // 第二次执行,并接收第一次的请求结果 data
if (done) return; // 执行完毕(到第三次)直接返回 // 第一次执行next时,yield返回的 promise实例 赋值给了 value value.then(data => { next(data); // 当第一次value 执行完毕且成功时,执行下一步(并把第一次的结果传递下一步) }); } next();};
asyncFunc(function* () { // 生成器函数:控制代码一步步执行 let data = yield readFile('a.js'); // 等这一步骤执行执行成功之后,再往下走,没执行完的时候,直接返回 data = yield readFile(data + 'b.js'); return data;})
复制代码

实现 Vue reactive 响应式

// Dep moduleclass Dep {  static stack = []  static target = null  deps = null
constructor() { this.deps = new Set() }
depend() { if (Dep.target) { this.deps.add(Dep.target) } }
notify() { this.deps.forEach(w => w.update()) }
static pushTarget(t) { if (this.target) { this.stack.push(this.target) } this.target = t }
static popTarget() { this.target = this.stack.pop() }}
// reactivefunction reactive(o) { if (o && typeof o === 'object') { Object.keys(o).forEach(k => { defineReactive(o, k, o[k]) }) } return o}
function defineReactive(obj, k, val) { let dep = new Dep() Object.defineProperty(obj, k, { get() { dep.depend() return val }, set(newVal) { val = newVal dep.notify() } }) if (val && typeof val === 'object') { reactive(val) }}
// watcherclass Watcher { constructor(effect) { this.effect = effect this.update() }
update() { Dep.pushTarget(this) this.value = this.effect() Dep.popTarget() return this.value }}
// 测试代码const data = reactive({ msg: 'aaa'})
new Watcher(() => { console.log('===> effect', data.msg);})
setTimeout(() => { data.msg = 'hello'}, 1000)
复制代码


参考 前端进阶面试题详细解答

实现观察者模式

观察者模式(基于发布订阅模式) 有观察者,也有被观察者


观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者


class Subject { // 被观察者 学生  constructor(name) {    this.state = 'happy'    this.observers = []; // 存储所有的观察者  }  // 收集所有的观察者  attach(o){ // Subject. prototype. attch    this.observers.push(o)  }  // 更新被观察者 状态的方法  setState(newState) {    this.state = newState; // 更新状态    // this 指被观察者 学生    this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态  }}
class Observer{ // 观察者 父母和老师 constructor(name) { this.name = name } update(student) { console.log('当前' + this.name + '被通知了', '当前学生的状态是' + student.state) }}
let student = new Subject('学生');
let parent = new Observer('父母'); let teacher = new Observer('老师');
// 被观察者存储观察者的前提,需要先接纳观察者student. attach(parent); student. attach(teacher); student. setState('被欺负了');
复制代码

实现一个双向绑定

defineProperty 版本


// 数据const data = {  text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持Object.defineProperty(data, 'text', {  // 数据变化 --> 修改视图  set(newVal) {    input.value = newVal;    span.innerHTML = newVal;  }});// 视图更改 --> 数据变化input.addEventListener('keyup', function(e) {  data.text = e.target.value;});
复制代码


proxy 版本


// 数据const data = {  text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持const handler = {  set(target, key, value) {    target[key] = value;    // 数据变化 --> 修改视图    input.value = value;    span.innerHTML = value;    return value;  }};const proxy = new Proxy(data, handler);
// 视图更改 --> 数据变化input.addEventListener('keyup', function(e) { proxy.text = e.target.value;});
复制代码

手写防抖函数

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。


// 函数防抖的实现function debounce(fn, wait) {  let timer = null;
return function() { let context = this, args = arguments;
// 如果此时存在定时器的话,则取消之前的定时器重新记时 if (timer) { clearTimeout(timer); timer = null; }
// 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); };}
复制代码

实现深拷贝

  • 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用  Object.assign 和展开运算符来实现。

  • 深拷贝: 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败

(1)JSON.stringify()

  • JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringifyjs对象序列化(JSON 字符串),再使用JSON.parse来反序列化(还原)js对象。

  • 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。


let obj1 = {  a: 0,              b: {                 c: 0                 }            };let obj2 = JSON.parse(JSON.stringify(obj1));obj1.a = 1;obj1.b.c = 1;console.log(obj1); // {a: 1, b: {c: 1}}console.log(obj2); // {a: 0, b: {c: 0}}
复制代码

(2)函数库 lodash 的_.cloneDeep 方法

该函数库也有提供_.cloneDeep 用来做 Deep Copy


var _ = require('lodash');var obj1 = {    a: 1,    b: { f: { g: 1 } },    c: [1, 2, 3]};var obj2 = _.cloneDeep(obj1);console.log(obj1.b.f === obj2.b.f);// false
复制代码

(3)手写实现深拷贝函数

// 深拷贝的实现function deepCopy(object) {  if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]; } }
return newObject;}
复制代码

数组去重方法汇总

首先:我知道多少种去重方式


1. 双层 for 循环


function distinct(arr) {    for (let i=0, len=arr.length; i<len; i++) {        for (let j=i+1; j<len; j++) {            if (arr[i] == arr[j]) {                arr.splice(j, 1);                // splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一                len--;                j--;            }        }    }    return arr;}
复制代码


思想: 双重 for 循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2),如果数组长度很大,效率会很低


2. Array.filter() 加 indexOf/includes


function distinct(a, b) {    let arr = a.concat(b);    return arr.filter((item, index)=> {        //return arr.indexOf(item) === index        return arr.includes(item)    })}
复制代码


思想: 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素


3. ES6 中的 Set 去重


function distinct(array) {   return Array.from(new Set(array));}
复制代码


思想: ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。


4. reduce 实现对象数组去重复


var resources = [    { name: "张三", age: "18" },    { name: "张三", age: "19" },    { name: "张三", age: "20" },    { name: "李四", age: "19" },    { name: "王五", age: "20" },    { name: "赵六", age: "21" }]var temp = {};resources = resources.reduce((prev, curv) => { // 如果临时对象中有这个名字,什么都不做 if (temp[curv.name]) {
}else { // 如果临时对象没有就把这个名字加进去,同时把当前的这个对象加入到prev中 temp[curv.name] = true; prev.push(curv); } return prev}, []);console.log("结果", resources);
复制代码


这种方法是利用高阶函数 reduce 进行去重, 这里只需要注意initialValue得放一个空数组[],不然没法push

实现 apply 方法

apply 原理与 call 很相似,不多赘述


// 模拟 applyFunction.prototype.myapply = function(context, arr) {  var context = Object(context) || window;  context.fn = this;
var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); }
delete context.fn; return result;};
复制代码

原生实现

function ajax() {  let xhr = new XMLHttpRequest() //实例化,以调用方法  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步  xhr.onreadystatechange = () => {  //每当 readyState 属性改变时,就会调用该函数。    if (xhr.readyState === 4) {  //XMLHttpRequest 代理当前所处状态。      if (xhr.status >= 200 && xhr.status < 300) {  //200-300请求成功        let string = request.responseText        //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象        let object = JSON.parse(string)      }    }  }  request.send() //用于实际发出 HTTP 请求。不带参数为GET请求}
复制代码

将 js 对象转化为树形结构

// 转换前:source = [{            id: 1,            pid: 0,            name: 'body'          }, {            id: 2,            pid: 1,            name: 'title'          }, {            id: 3,            pid: 2,            name: 'div'          }]// 转换为: tree = [{          id: 1,          pid: 0,          name: 'body',          children: [{            id: 2,            pid: 1,            name: 'title',            children: [{              id: 3,              pid: 1,              name: 'div'            }]          }        }]
复制代码


代码实现:


function jsonToTree(data) {  // 初始化结果数组,并判断输入数据的格式  let result = []  if(!Array.isArray(data)) {    return result  }  // 使用map,将当前对象的id与当前对象对应存储起来  let map = {};  data.forEach(item => {    map[item.id] = item;  });  //   data.forEach(item => {    let parent = map[item.pid];    if(parent) {      (parent.children || (parent.children = [])).push(item);    } else {      result.push(item);    }  });  return result;}
复制代码

原型继承

这里只写寄生组合继承了,中间还有几个演变过来的继承但都有一些缺陷


function Parent() {  this.name = 'parent';}function Child() {  Parent.call(this);  this.type = 'children';}Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;
复制代码

实现 forEach 方法

Array.prototype.myForEach = function(callback, context=window) {  // this=>arr  let self = this,        i = 0,      len = self.length;
for(;i<len;i++) { typeof callback == 'function' && callback.call(context,self[i], i) }}
复制代码

实现迭代器生成函数

我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6中,实现一个迭代器生成函数并不是什么难事儿,因为 ES6 早帮我们考虑好了全套的解决方案,内置了贴心的 生成器Generator)供我们使用:


// 编写一个迭代器生成函数function *iteratorGenerator() {    yield '1号选手'    yield '2号选手'    yield '3号选手'}
const iterator = iteratorGenerator()
iterator.next()iterator.next()iterator.next()
复制代码


丢进控制台,不负众望:



写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背后的实现逻辑更感兴趣。下面我们要做的,不仅仅是写一个迭代器对象,而是用ES5去写一个能够生成迭代器对象的迭代器生成函数(解析在注释里):


// 定义生成器函数,入参是任意集合function iteratorGenerator(list) {    // idx记录当前访问的索引    var idx = 0    // len记录传入集合的长度    var len = list.length    return {        // 自定义next方法        next: function() {            // 如果索引还没有超出集合长度,done为false            var done = idx >= len            // 如果done为false,则可以继续取值            var value = !done ? list[idx++] : undefined
// 将当前值与遍历是否完毕(done)返回 return { done: done, value: value } } }}
var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])iterator.next()iterator.next()iterator.next()
复制代码


此处为了记录每次遍历的位置,我们实现了一个闭包,借助自由变量来做我们的迭代过程中的“游标”。


运行一下我们自定义的迭代器,结果符合预期:


实现斐波那契数列

// 递归function fn (n){    if(n==0) return 0    if(n==1) return 1    return fn(n-2)+fn(n-1)}// 优化function fibonacci2(n) {    const arr = [1, 1, 2];    const arrLen = arr.length;
if (n <= arrLen) { return arr[n]; }
for (let i = arrLen; i < n; i++) { arr.push(arr[i - 1] + arr[ i - 2]); }
return arr[arr.length - 1];}// 非递归function fn(n) { let pre1 = 1; let pre2 = 1; let current = 2;
if (n <= 2) { return current; }
for (let i = 2; i < n; i++) { pre1 = pre2; pre2 = current; current = pre1 + pre2; }
return current;}
复制代码

Promise.race

Promise.race = function(promiseArr) {  return new Promise((resolve, reject) => {    promiseArr.forEach(p => {      // 如果不是Promise实例需要转化为Promise实例      Promise.resolve(p).then(        val => resolve(val),        err => reject(err),      )    })  })}
复制代码

滚动加载

原理就是监听页面滚动事件,分析 clientHeightscrollTopscrollHeight 三者的属性关系。


window.addEventListener('scroll', function() {  const clientHeight = document.documentElement.clientHeight;  const scrollTop = document.documentElement.scrollTop;  const scrollHeight = document.documentElement.scrollHeight;  if (clientHeight + scrollTop >= scrollHeight) {    // 检测到滚动至页面底部,进行后续操作    // ...  }}, false);
复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
字节前端高频手写面试题(持续更新中)_JavaScript_helloworld1024fd_InfoQ写作社区