202303--同事分享的前端面试题收录

6,837 阅读23分钟

react hooks为什么不能放在if和for里?

React Hooks 是 React 16.8 版本新增的特性,它可以让函数组件具有类组件的状态和生命周期管理能力。再使用 React Hooks 时,需要遵循一些规则,其中一个重要的规则是不能将 Hooks 放到条件判断语句、循环语句等代码块中。

这是因为 React 需要依赖 Hook 调用的顺序来确定每个 Hook 对应的状态,如果 Hook 调用的顺序发生改变,可能会导致程序出现错误或不符合预期的行为。

而在 if 和 for 中,由于它们的执行次数是动态的,可能会导致 Hook 的调用顺序发生改变,从而引发问题。例如,在某个循环中使用 useState Hook,由于循环次数是不定的,Hook 的调用顺序也就无法确定,会导致状态更新混乱。

因此,为了确保 Hook 的正确性,React 需要在编译期间对 Hook 的调用顺序进行静态分析,从而确保每个 Hook 对应的状态都是稳定的。因此,不能将 Hook 放置在条件语句和循环语句中,只能在函数组件的顶层作用域中使用 Hook。

防抖和节流的应用场景?

防抖和节流都是为了优化页面性能而设计的技术,它们有不同的应用场景。

防抖的应用场景:

  1. 表单提交:当用户在表单中输入内容时,通常会触发onChange事件,如果每次输入都立即向服务器发送请求,会对服务器造成较大的负担。使用防抖技术可以避免这种情况,只有当用户停止输入一段时间后才会发送请求。

  2. 搜索框自动补全:当用户在搜索框中输入关键词时,实时查询后台数据并展示自动补全列表,如果每次输入都直接向服务器发送请求,会对服务器造成很大的压力。使用防抖技术可以让列表展示变得更加平滑,减少不必要的请求。

  3. 窗口大小改变:当窗口大小改变时,需要重新计算页面布局,如果不控制频率,每次调整大小都会触发计算,导致性能问题。使用防抖技术可以确保只有在停止调整大小一段时间后才会触发计算。

节流的应用场景:

  1. 页面滚动:当用户滚动页面时,如果不控制频率,每次滚动都会触发一些昂贵的计算或网络请求,导致页面卡顿。使用节流技术可以确保不会触发过多的计算或请求,保证用户体验。

  2. 频繁点击按钮:当用户频繁点击按钮时,每次点击都会触发相关事件,导致页面响应缓慢。使用节流技术可以确保只有在一定时间内才能触发一次事件,避免了因用户快速重复操作而导致的问题。

  3. 实时网络传输:例如在线游戏中的实时通信,需要确保客户端和服务器之间的通信是稳定可靠的,同时不能对网络带宽造成过大的负担。使用节流技术可以控制发送数据的频率,以确保通信的稳定性和通信质量。

https加密证书怎么做的?

HTTPS加密证书是由数字证书颁发机构(CA)签发的一种安全证书,用于对网站进行加密通信,保护用户数据不被窃取或篡改。生成HTTPS加密证书的一般步骤是:

  1. 选择一个可靠的证书颁发机构(CA),例如GlobalSign、Symantec等。

  2. 生成公钥和私钥。可以使用SSL/TLS协议来生成这两个密钥。

  3. 向证书颁发机构提交申请,通常需要提供域名、公司信息和联系人信息等。

  4. 证书颁发机构会对申请人进行身份验证,以确保其合法性。

  5. 签发证书并将其发送给申请人。证书包含了公钥和申请人的信息,并由证书颁发机构的私钥进行签名。

  6. 安装证书到Web服务器上,并配置将HTTP请求自动重定向到HTTPS。

  7. 在浏览器中访问网站时,浏览器会收到证书并验证其有效性。如果证书有效,则建立安全连接,否则会显示错误提示信息。

需要注意的是,HTTPS加密证书需要定期更新,以保证证书的有效性。

什么是 Nuxt 水合作用?

看图说话, 服务端脱水的时候,仅用动态数据渲染出静态页面,注水之后(服务器端没有Dom环境,只能放在客户端做),页面才能响应交互。 UI 直接通过后端的 response 返回的 html + css 构建,但是页面的Dom元素事件需要等待 React/Vue 运行,然后通过 hydration(水合作用) 构建出虚拟 DOM 之后才能够响应各种Dom事件。 image.png 水合概念的重点来了:

  • React应用
    前端的 React 应用按照正常的客户端渲染流程需要调用 ReacDOM.render,而对于 SSR 来说,HTML 内容因为提前创建好了,不需要再由 ReactDOM 去创建,此时只需要“激活” React 应用即可,对应的 API 是 ReactDOM.hydrate,激活的操作是把 HTML 的结构与 React 应用建立起映射关系,方便后续的视图更新,同时绑定好事件。
 ReactDOM.hydrate(
      entryApp,
      document.querySelector(rootId),
      callback
    );
  • Vue应用
    而vue,如果是服务器渲染的输出结果,会在应用程序的根元素上添加一个data-server-rendered 特殊属性,让客户端 Vue 知道 这部分 HTML 是由 Vue 在服务端渲染的,应该以激活模式进行挂载。
<div data-server-rendered="true">

在开发模式下,Vue 将推断客户端生成的虚拟 DOM 树 (virtual DOM tree),是否与从服务器渲染的 DOM 结构 (DOM structure) 匹配。如果无法匹配,它将退出混合模式,丢弃现有的 DOM 并从头开始渲染。在生产模式下,此检测会被跳过,以避免性能损耗。

React, Vue各自的优缺点?

React和Vue共同的优点:

  1. 操作dom效率高:两个框架都使用了虚拟Dom+Diff对比差异算法(React采用的是snabbdom算法, Vue2的核心Diff算法采用了双端比较算法,Vue3.x借鉴了ivi算法和inferno算法), 整体上比用原生方法操作Dom效率高。
  2. 模块化:以组件的形式组合页面及应用,使组件可以复用和解耦。

React:

  • 优点:
  1. 兼容性好:React框架帮开发者解决了浏览器兼容问题,提供了标准化的API,在IE8中上也能运行。
  2. 跨平台:React提供了开发移动端应用解决方案React Native,可通过相似的React语法开发iOS和Android应用。Weex也能开发移动端应用,可是使用者不如React Native多。
  3. 相对而言,React在国际上更流行一些。
  • 缺点:
  1. 学习曲线较为陡峭

Vue:

  • 优点:
  1. 易于使用: 与经典的网页开发模式 结构+展示+行为 写法可以无缝衔接过渡,不需要学习jsx这种不如原生html标签那么好理解的语法。
  2. 文档质量高:vue的官方文档排版整齐, 语法点介绍全面详尽,易于学习和理解。
  3. 有许多好用的指令如v-if/v-for等,比在react的jsx中用三目表达式和map实现同样的功能更优雅
  4. 获取表单数据方便:数据双向绑定,设置与获取表单数据比较方便。
  • 缺点:
  1. 不兼容IE8

如何判断一个单向链表是否有环?

首先看看js中的链表应该是什么样子?

const linkedList = {
    head: {
        value: 1
        next: {
            value: 2                                             
            next: {
                value: 3
                next: {
                    value: 4
                    next: null    
                    }
                }
            }
        }
    }
};

链表的优点

相对于数组,可以比较容易地从链表中添加或删除节点,而无需重组整个数据结构。

链表的缺点

  • 相对于数组, 链表的搜索操作很慢,不允许随机访问数据元素,必须从第一个节点开始按顺序访问节点。
  • 相较于数组, 由于需要储存指针,需要更多内存。

解法有三

  1. 快慢指针 -- 时间复杂度:O(n) 空间复杂度:O(1)
    设置快慢两个指针,遍历单链表,快指针一次走两步,慢指针一次走一步,如果单链表中存在环,则快慢指针终会指向同一个节点,否则直到快指针指向 null 时,快慢指针都不会相遇。
const hasCycle = function(head) {
    if(!head || !head.next) {
        return false;
    }
    
    let fast = head.next.next, slow = head;
    
    while(fast !== slow) {
        if(!fast || !fast.next) {
            return false;
        }
        fast = fast.next.next;
        slow = slow.next;
    }
    return true
};

  1. 标记法 时间复杂度:O(n) 空间复杂度:O(n)
const hasCycle = function(head) {
    while(head) {
        if(head.flag) {
            return true;
        }
        head.flag = true;
        head = head.next;
    }
    return false;
};
  1. 利用 JSON.stringify() 序列化含有循环引用的结构会报错来判断
const hasCycle = function(head) {
    try{
        JSON.stringify(head);
        return false;
    }
    catch(err){
        return true;
    }
};

低代码实现原理?

传统的网页开发主要依靠手写代码,低代码开发平台主要是通过配置可视化图形界面创建网页应用,因为写的代码少了,所以叫低代码。 实现低代码的流程是:先在可视化编辑界面配置页面和交互行为,再根据配置数据,把页面元素和交互还原出来。配置页面的模块构成是:组件列表+拖拽区域+选中组件的属性和接口配置区域+预览,最终要生成DSL(特定领域编程)数据,保存到后端。还原页面的时候,从后端读取DSL数据,解析DSL数据,解析成浏览器可以识别渲染的数据,渲染页面。

image.png

  • 组件列表 组件就是低代码平台的物料,如同AntDesign、ElementUI等UI的组件一样。

  • 预览 就是把页面上的配置,根据用户操作实时生成DSL,并且实时解析DSL,渲染页面配置结果,保证用户所见则所得。

  • 组件配置 用户添加的每个组件,组件都有许多属性需要用户编辑。比如说外观展示属性和Dom事件属性

  • 接口配置 每个接口也被当做一个组件,需要配置使用哪个接口,以及接口入参绑定的界面值

  • 拖拽系统 在画布上通过拖动组件,确定每个组件的位置。画布的粘附定位,对提高操作体验影响较大。

  • 表达式系统 利用表达式实现计算属性的功能,比如组件的条件渲染、字段联动。

  • 渲染系统 根据DSL渲染成UI,并解析其中的表达式实现字段联动。和预览最大的不同是,预览中的组件点击后,是组件的编辑。而实际运行渲染结果,是组件的点击行为。

如何让 a== 1 && a==5 返回true?

有三种思路:

1. 隐式转换思路

先温习一下,使用 == 比较中的5条规则:

  • 规则 1:NaN和其他任何类型比较永远返回false(包括自身)
  • 规则 2:Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
  • 规则 3:StringNumber比较,先将String转换为Number类型。
  • 规则 4:null == undefined比较结果是true,除此之外,nullundefined分别与'',false,0比较值都为false
console.log(null == undefined); // true
console.log(null == ''); // false
console.log(null == 0); // false
console.log(null == false); // false
console.log(undefined == ''); // false
console.log(undefined == 0); // false
console.log(undefined == false); // false
  • 规则 5:原始类型引用类型做比较时,引用类型会依照ToPrimitive规则转换为原始类型。 ToPrimitive规则如下:
  1. 如果定义了Symbol.toPrimitive()方法,优先调用并返回,如果返回的不是原始类型,会报错
  2. 如果未定义Symbol.toPrimitive()方法,先调用valueOf(),如果转换为原始类型,则返回,否则继续调用toString方法
  3. 继续调用toString(),如果转换为原始类型,则返回,返回就会报错
const a = {
  value: 0,
  // 优先级最高
  [Symbol.toPrimitive]() {
    return 1;
  },
  // 优先级次之
  valueOf() {
    return 2;
  },
  // 优先级最低
  toString() {
    return 3;
  },
}
console.log(a+1); // 输出2

了解了上面的原理,这道题的三种解法就来了:

const getValue=(obj)=>{

  if(obj.value == 0){
    obj.value=1;
  }else if(obj.value == 1){
     obj.value=5;
  }

  return obj;

}

const a = {
  value: 0,
  [Symbol.toPrimitive]() {
    return getValue(this).value;
  },
  valueOf() {
    return getValue(this).value;
  },
  toString() {
    return getValue(this).value;
  },
}
console.log(a == 1 && a == 5); 

2.隐式转换+数组特性

前面说过原始类型和引用类型做比较时的隐式转换逻辑,Array对象的toString方法,会返回一个字符串,该字符串由数组的每个元素的toString返回值经调用join()方法连接组成。这里把join方法改为shift方法,每次返回第一个元素,可以实现非连续的数字比较返回true的逻辑。

const a=[1,5];  
a.join=a.shift;  
console.log(a == 1 && a == 5); 

3. 劫持思路

  // 方式一
   Object.defineProperty(window, 'a', {
    get: (function() {
      const obj={value : 0};
      return function(){
        return getValue(obj).value;
      }
    })()
  });
 
// 方法二  
const a = new Proxy({ value: 0 }, {
      get(target) {
          return () => getValue(target).value;
      }
  })
  
 console.log(a == 1 && a == 5); 

4.零宽字符障眼法

所谓零宽字符,就是不可见的非打印字符,通过视觉无法看出字符串中是否有零宽字符,但是通过代码遍历,是可以获取到该字符的。有些软件也可以将零宽字符打印出来。

零宽字符有几种:零宽空格和零宽连接符、零宽非连接符。

  • 零宽空格U+200B
  • 零宽非连接符 U+200C
  • 零宽连接符 U+200D,常见的复杂Emoji表情即用到了该字符,用于表示多字符关系从而合成复杂新字符
  • 左至右符U+200E
  • 右至左符U+200F
  • 零宽非断空格符 U+FEFF
const a = 1; // 字符a
const a‍ = 5; // 字符a·
console.log(a === 1 && a‍ === 5); // true

用正则替换的方式,可以让零宽字符现出原形:

console.log("const a = 1;".replace(/[\u200b-\u200f|\ufeff]/g, "show"));
// const a = 1;
console.log("const a‍ = 5;".replace(/[\u200b-\u200d|\ufeff]/g, "show"));
// const ashow = 5;

浏览器和node的事件循环有什么区别?

先说一下异步任务中的宏任务与微任务概念

异步任务被分为两类:宏任务(macrotask)与微任务(microtask),两者的执行优先级也有所区别。
宏任务主要包含:script(整体代码)setTimeoutsetIntervalsetImmediate(Node独有),I/ODOM EventsrequestAnimationFrame
微任务主要包含:Promise.thenPromise.catchPromise.finallyMutationObserver,queueMicrotask,process.nextTick(Node独有)等。

浏览器事件循环,执行顺序如下:

  1. 一开始执行栈空,micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)
  2. 执行一个宏任务script 脚本(执行栈中没有就从任务队列中获取)。
  3. 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中。
  4. 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务,每个微任务是依次执行的。
  5. 当前宏任务执行完毕,开始检查渲染,然后渲染线程接管进行渲染。
  6. 渲染完毕后,JavaScript 线程继续接管,开始下一个循环。

image.png

Node的事件循环,执行顺序如下:

外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段(周而复始运行)...

(1) poll:检索新的 I/O 事件;执行与 I/O 相关的回调,除了关闭的回调函数socket.on('close', callback),setTimeout,setInterval和 setImmediate() 回调函数之外,其余情况 node 将在适当的时候阻塞等待。 poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情
i> 进入该阶段时如果没有设定 timer 的话,会发生以下两件事情

  • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
  • 如果 poll 队列为空时,会有两件事发生
    • 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调
    • 如,果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去

ii> 当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。
(2) check:setImmediate() 回调函数在这里执行。
(3) close callbacks:一些关闭的回调函数,如:socket.on('close', ...)。
(4) timers:timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。 同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。
(5) pending callbacks:执行延迟到下一个循环迭代的 I/O 回调。
(6) idle、prepare:仅系统内部使用。

浏览器和node事件循环的区别:
浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。而在Node中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务

下面用一个例子,说明浏览器和node事件循环的区别:

console.log('script开始');
setTimeout(() => {
    console.log('宏任务1');
    Promise.resolve().then(function () {
        console.log('微任务2')
    })
},0);

setTimeout(() => {
    console.log('宏任务2');
    Promise.resolve().then(function() {
        console.log('微任务3')
    })
},0)

Promise.resolve().then(function () {
    console.log('微任务1');
})

console.log('script结束');

在浏览器中的执行顺序为:

script开始 
script结束 
微任务1 
宏任务1 
微任务2 
宏任务2 
微任务3

宏任务与微任务的执行顺序在 Node v10前后版本中表现也有所不同。用上面的例子来分析:

  • 在 Node v11+ 一旦执行一个阶段里的一个宏任务(setTimeout,setInterval 和 setImmediate),会立刻执行微任务队列,所以输出顺序为
script开始 
script结束 
微任务1 
宏任务1 
微任务2 
宏任务2 
微任务3
  • 在 Node v10 及以下版本,要看第一个定时器执行完成时,第二个定时器是否在完成队列中。

如果第二个定时器还未在完成队列中,输出顺序为

script开始 
script结束 
微任务1 
宏任务1 
微任务2 
宏任务2 
微任务3

如果是第二个定时器已经在完成队列中,输出顺序为

script开始 
script结束 
微任务1 
宏任务1 
宏任务2 
微任务2 
微任务3

什么是AST?有什么用?

抽象语法树AST是Abstract Syntax Tree的缩写,它以抽象的树状形式表现编程语言的语法结构。

先说两个概念:

词法分析:一个一个字母的来读取字符,然后与定义好的 JavaScript 关键字符做比较,生成对应的Token。Token 是一个不可分割的最小单元。每个关键字,标识符,操作符,标点符号都是一个 Token。词法分析器会过滤掉源程序中的注释和空白字符(如换行符、空格、制表符等)。最终,整个代码被分割成一个个tokens,形成一个一维的token数组。

语法分析:将词法分析出来的 Token 转化成有语法含义的抽象语法树结构。同时,验证语法,语法如果有错的话,抛出语法错误。常见的 JS Parser(语法解析器)有 acorn、esprima、traceur、shift 等。

抽象语法树的用途:
(1)编辑器的错误提示、代码格式化、代码高亮、代码自动补全;
(2)对代码错误进行检查或风格,比如eslintpretiier ;
(3)将高版本的JS语法转换成兼容低版本浏览器的语法。如  babel

JS执行的过程是读出JS文件中的字符流,接着通过词法分析生成 token,之后通过语法分析( Parser )生成 AST,最后生成机器码执行。

webpack5是怎么做缓存的?

一图胜千言, 搞清楚缓存是什么时候生成和读取的,这个问题差不多就讲清楚了。先说一下缓存是怎么生成的。缓存主要产生在两个阶段,第一个阶段是模块解析,第二个阶段是模块编译完之后。缓存并不是直接写入硬盘的, 先写入到内存中的缓存队列, 等编译完成之后,才从内存缓存队列写入到硬盘。

写入的是什么内容呢?是一个map类型,key是identifier(缓存资源的唯一标识),value是模块或文件的解析数据(resolveData)+快照(snapshot)。解析数据很好理解。快照的生成规则得说一下,每个文件的快照是依据resolveTimefileDependenciescontextDependenciesmissingDependencies 以及在 webpack.config 的 snapshotOptions配置来生成快照的内容。

image.png

何时读缓存? 项目二次构建解析和编译文件或模块时,会去读缓存。从硬盘读取到内存,再加以利用。读取的时候要判断缓存是否还能使用,若是强制构建,设置了不进行缓存,没有可以检查的快照,缓存失效的话,该解析编译还得重新解析编译。 image.png

webpack缓存设计的核心思路是复用已经编译的模块,省去重新执行编译的流程与时间。babel-loadereslint-loader 自身内置的缓存功能,DLL,cache-loader 都是遵循这种思路。顺便说一下webpack4和webpack5缓存的区别。

webpack4缓存方案的不足:
(1)cache-loader的能力圈仅是经由 loader 处理后的文件内容,缓存内容的范围比较有限;
(2)cache-loader 缓存数据的过程也有一些性能开销,会影响整个项目编译构建速度,一般多用于编译耗时较长的 loader 上。
(3)cache-loader 是通过对比文件 metadata 的 timestamps,这种缓存失效策略不是非常的安全。

webpack5与webpack4相比:
(1) webpack5 不仅在module的解析和编译阶段,而且在代码生成、sourceMap 阶段都使用到了持久化缓存;
(2)内置了更加安全的缓存对比策略(timestamp + content hash);
(3)compile 流程和持久化缓存解耦,在compile阶段持久化缓存数据的动作不会阻碍整个流程,而是先放置到一个缓存队列中,当 compile 结束后才会从内存中写入到硬盘中。

webpack5的cache属性添加了很多配置选项:

属性说明
cache.type缓存类型,支持 'memory' | 'filesystem',需要设置为 filesystem 才能开启持久缓存。
cache.cacheDirectory缓存文件路径,默认为 node_modules/.cache/webpack。
cache.buildDependencies额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建。
cache.managedPaths受控目录,Webpack 构建时会跳过新旧代码哈希值与时间戳的对比,直接使用缓存副本,默认值为 ['./node_modules']。
cache.profile是否输出缓存处理过程的详细日志,默认为 false。
cache.maxAge缓存失效时间,默认值为 5184000000秒 。
module.exports = {
    cache: {
        type: 'filesystem', // 可选值 memory | filesystem
        cacheDirectory: './.cache/webpack', // 缓存文件生成的地址
        buildDependencies: { // 那些文件发现改变就让缓存失效,一般为 webpack 的配置文件
            config: [
                './webpack.config.js'
            ]
        },
        managedPaths: ['./node_modules', './libs'], // 受控目录,指的就是那些目录文件会生成缓存
        profile: true, // 是否输出缓存处理过程的详细日志,默认为 false
        maxAge: 1000 * 60 * 60 * 24, // 缓存失效时间,默认值为 5184000000
    }
}

async/await的实现原理?

实现思路:

  1. generator + promise, generator的用法与async/await比较形似,直观的让人想到, async应该是generator的语法糖。async的返回值是Promise,所以最终也要返回一个Promise。
  2. generator与async的差别是async里面的await 函数可以自动串行执行,所以要写一个递归函数,让generator自动执行
  3. generator的value和done状态是迭代器协议的返回值。value是yield的返回值, done是false时,继续执行迭代器, done为true时,resolve结果。
/**
 * async模拟实现
 * @param {*} genFn - 生成器函数
 */
function asyncFn(genFn) {
  const g = genFn();
  return new Promise((resolve, reject) => {
    function autoRunNext(g, nextVal) {
      const { value, done } = g.next(nextVal);
      // 迭代器未执行完
      if (!done) {
        value.then((res) => {
          autoRunNext(g, res);
        })
      } else {
        // 迭代器执行完
        resolve(value);
      }
    }
    // 第一次执行autoRunNext是用来启动遍历器,不用传参数
    autoRunNext(g);
  })
}

// 测试

const getData = (i) => new Promise((resolve) => setTimeout(() => resolve(`data${i}`), 500))

function* testG() {
  const data1 = yield getData(1);
  console.log('data1: ', data1);
  const data2 = yield getData(2);
  console.log('data2: ', data2);
  return 'success';
}

asyncFn(testG).then((res) => { console.log(res)})

上传大文件中断了怎么恢复?

大文件一般都是采用分片上传,将大文件转换成二进制文件流,利用文件流可以切割的属性,将整个文件切分成多个chunk,每个chunk都要有chunkIndex,chunkHash, 此外还要传chunkTotal,fileHash给服务器,以便服务端校验文件的正确性和完整性。以并行或串行的方式传输,服务器接收分片并存储,收到合并请求后使用流将切片合并成最终文件。

在分片上传的过程中,如果因为由于意外因素(如网络中断或系统崩溃等异常因素)导致上传中断,网络恢复后,为了避免重新开始从头上传, 需要在上传切片的时候记录上传的进度。再次上传时,可以继续从上次中断的地方进行继续上传。可以在客户端记录,服务端也可以提供已上传分片查询接口,让客户端查询已上传的分片数据,下一次从未上传的分片数据开始继续上传。

用css画一个扇形?

画扇形的方法比较多, 大多方法使用多个div,需要设置多个层级,相对复杂。有一种简单的画法,就是使用css裁剪clip-path的多边形polygon属性,思路是:

  1. 画一个圆,对这个圆进行裁剪。
  2. 设置裁剪的第一个点是圆心x=50%, y=50%; 第二个点是x=0%, y=0%;
  3. 第三个裁剪点是x=100%, y=0%
  4. 连点成面, 后面若还有裁剪点,裁剪都是基于前面点连出的轮廓。
  5. 逆时针翻转45度,与我们的直觉就会趋于一致。
<div class="sector"></div>
<style>
.sector{
  width: 100px;
  height: 100px;
  border-radius: 100%;
  background-color: green;
  // 第三个点x3在0-100%的变化,对应着0-90deg之间的扇形
  clip-path: polygon(50% 50%, 0% 0%, 100% 0);
  // 在其它点都不变化的情况下,第四个点y4在0-100%之间变化,对应着90-180deg之间的扇形
  // clip-path: polygon(50% 50%, 0% 0%, 100% 0,100% 10%);
  // 在其它点都不变化的情况下,第五个点x5在100-0%之间变化,对应着180-270deg之间的扇形
  // clip-path: polygon(50% 50%, 0% 0%, 100% 0,100% 100%,10% 100%);
  // 在其它点都不变化的情况下,第六个点y6在100-0%之间变化,对应着270-360deg之间的扇形
  // clip-path: polygon(50% 50%, 0% 0%, 100% 0,100% 100%,0% 100%, 0 10%);
  transform: rotate(-45deg);
}
</style>

image.png

参考链接

本文正在参加「金石计划」