Event Loop 相关
1,为什么 js 是单线程?
js 作为主要运行在浏览器的脚本语言,js 主要用途之一是操作 DOM。
在 js 高程中举过一个栗子,如果 js 同时有两个线程,同时对同一个 dom 进行操作,这时浏览器应该听哪个线程的,如何判断优先级?为了避免这种问题,js 必须是一门单线程语言,并且在未来这个特点也不会改变。
执行栈
1,执行栈是一个类似于函数调用栈的运行容器,是一个具有 LIFO(后进先出)结构的堆栈,用于存储在代码执行期间创建的所有执行上下文。当执行栈为空时,JS 引擎便检查事件队列,如果事件队列不为空的话,事件队列便将第一个任务压入执行栈中运行。
事件队列
1,事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行(先进先出),排在队头的任务将会率先执行,而排在队尾的任务会最后执行。
2,事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。
主线程
1,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。
2,主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。
3,当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。
4,当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。
js 异步执行的运行机制
1,所有任务都在主线程上执行,形成一个执行栈。
2,主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
3,一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
4,主线程不断重复上面的第三步。
宏任务与微任务
1,异步任务分为 宏任务(macrotask)与微任务 (microtask),不同的 API 注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。
2,宏任务(macrotask)包括:script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)。
3,微任务(microtask)包括:Promise、 MutaionObserver、process.nextTick(Node.js 环境)。
Event Loop(事件循环)
1,Event Loop(事件循环)中,每一次循环称为 tick, 每一次 tick 的任务如下:
执行栈选择最先进入队列的宏任务(通常是 script 整体代码),如果有则执行。
检查是否存在宏任务,如果存在则不停的执行,直至清空微任务队列。
更新 render(每一次事件循环,浏览器都可能会去更新渲染)。
重复以上步骤。
2,事件循环执行机制为:宏任务 => 所有微任务 => 宏任务,如下图所示:
从上图我们可以看出:
- 将所有任务看成两个队列:执行队列与事件队列。
- 执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列之后,事件队列之前。
- 当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务。
3,完整的 Event Loop 如下图:
4,简单实践题:
1 | setTimeout(function () { |
解题思路为:
- 先执行 script 同步代码:先执行 new Promise 中的 console.log(2),then 后面的不执行属于微任务,然后执行 console.log(4)。
- 执行完 script 宏任务后,执行微任务,console.log(3),没有其他微任务了。
- 执行另一个宏任务,定时器,console.log(1)。
JS API
outerHTML
1,使用 outerHTML 获取指定元素中的所有子元素及文本元素。
1 | <div id="content"> |
2,使用 outerHTML 整体替换指定元素。
1 | <div id="content"> |
上述代码中,将会使用 span 标签整体替换原来的 div 标签。
encodeURIComponent()
1,该方法可以把字符串作为 URI 组件进行编码。
2,该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( )
。其他字符(比如:; / ? : @ & = + $ , #
这些用于分隔 URI 组件的标点符号),都会被一个或多个十六进制的转义序列替换的。
3,具体使用:
1 | const url = "http://www.baidu.com"; |
文件流相关
文件切片
1、使用 file 的 slice 方法将文件进行切片。
2、切片的规则可以根据规定的文件大小进行切分、也可以根据规定的份数进行切分、也可以两者结合,具体操作如下:
1 | // 已规定的最大切片大小进行切片,算出需要切出的份数,如果超过了规定切分的最大份数10,那么就将文件切成10份,以10份为标准,算出每份的size |
构造函数相关
构造函数 this
1,构造函数中,this 总是指向新创建出来的实例对象。
2,如果在严格模式下,this 则指向 undefined。
new 关键字
1,构造函数于普通函数的区别就是,构造函数需要使用 new 关键字进行调用。
2,new 关键字调用构造函数时,会经历四个过程,分别为:
- 创建一个 Object 实例对象。相当于:
1 | const obj = new Object(); |
- 将构造函数中的 this 指向新创建的这个实例对象。
1 | this.name = "dnhyxc"; |
自上而下执行构造函数中的代码。
返回 new 构建出的实例对象。
注意:如果被调用的这个构造函数没有显示的 return
表达式(仅限于返回对象类型数据的情况)时,则会隐式的返 this
对象,也就是 new 创建出来的实例对象。
说明:如果使用 return 表达式返回 undefined、null、boolean、number、string 等基本数据类型的时候,则不会替换 new 关键字调用的默认行为,也就是说此时会隐式的返回 new 创建出来的实例对象。而如果用 return 显示的返回 {}、[]、RegExp、Data、Function 时,return 返回的值则会替换 new 调用的默认返回值 this,也就是说会替换 new 新创建出来的实例对象。
相关测试代码
1 | function Test(name) { |
JS 计算行数
计算元素是否达到三行
1,使用元素的高度
/ 元素的行高
即为文本行数:
1 | const box = document.getElementById("box"); |
Array.map() 实现页面展示数组数据
上传文件与列表已有文件同时展示
1,处理文件上传文件与原有文件需要同时展示时,需要使用两个互不相干的数组进行展示,即上传文件分为一个数组,已有文件分为一个数组,将这两个数组同时展示在页面上,避免在操作增删改操作时相互影响。
2,伪代码如下:
1 | <div className={classNames(styles.contentWrap, styles.faceWrap)}> |
数组转对象的方式
方式一
1,对象结构
:
1 | const arr = ["arr1", "arr2", "arr3"]; |
方式二
1,for...in 循环
:
1 | const arr = ["arr1", "arr2", "arr3"]; |
方式三
1,Object.assign()
:
1 | const arr = ["arr1", "arr2", "arr3"]; |
方式四
1,Array.reduce()
:
1 | arr.reduce((obj, arr, index) => { |
js 类型判断
类型判断函数封装
1 | const getType = function (obj) { |
js 类型比较特殊点
1,当声明的函数,其参数都为数字型
字符串时,JS 会比较两个字符串 ASCII 码的大小,而不是比较数值的大小,具体如下:
1 | function numCompare(first, second) { |
随机打乱数组顺序
sort 与 Math.random
1,具体方式如下:
1 | let list = [1, 2, "dnhyxc", 1, 34, 3, 12]; |
判断数组中的值是否满足要求
数组里的值是否有一个或一个以上满足要求
1,当前方法只要数组里面有一个值符合需求,就返回 true,否则 false。
1 | let list = [6, 8, 8, 9, 8, 88]; |
数组里的值是否都满足要求
1,我们之前使用 for 遍历在判断当前数组里的值是否全符合要求,还要声明一个变量来进行累计,直接使用 every 当全部满足需求时返回 true,否则返回 false。
1 | let list = [6, 8, 8, 88, 8, 68]; |
找出数组 a 中不存在于数组 b 中的每一项
1 | const arr1 = [ |
运算符相关
?? 运算符
1,??
运算符只有前面的值是 undefined or null 才会执行,工作中有的情况下使用,我们来看一下。
1 | let status = undefined; |
?. 运算符
1,?.
运算符这在有时候处理对象时非常有用,看下面案例,person.name 返回 undefined 然后在调用 toString 这时肯定会报错,这时使用?.
运算符就不会产生错误,?.
运算符是只有在当值不是 undefined 时才会调用 toString 方法。
1 | let person = {}; |
~~ 双非运算符
1,~~
双非运算符可以用于向下取整。
1 | console.log(~~9.2); // 9 |
代替 if else 的方式
定义特定的对象替代 if else
1,在处理判断时,都会使用 if else, 但当业务越来越庞大时有好几种状态时,这样代码太冗余了,我们做一下简化。
1 | if(xxx = 1) { |
坐标定位
使用坐标计算目标在元素中的实际位置
1,说明:利用坐标在指定宽高的元素中按照实际比例框中指定的位置。
1 | const getVirtualRect = (options: any, meta: any) => { |
浏览器下载操作
下载图片
1,当后端返回的 url 是预览的形式(点击下载图片会直接在浏览器新标签页打开)时,可在 url 后面拼接上 filename=xxx.png 的形式完成下载。
1 | const download = (url, index) => { |
处理批量下载
1,如果后端返回的就是下载的 url,即可直接用当前 url 进行下载,无需拼接 filename。
1 | export const download = (url: string) => { |
下载 office 文档
1,适用于下载 word,ppt 等浏览器不会默认执行预览的文档,也可以用于下载后端接口返回的流数据。
1 | function download(link, name) { |
在浏览器中自定义下载一些内容
1,下载一些 DOM 内容,或者下载一个 JSON 文件。
1 | /** |
提供一个图片链接,点击下载
1,图片、pdf 等文件,浏览器会默认执行预览,不能调用 download 方法进行下载,需要先把图片、pdf 等文件转成 blob,再调用 download 方法进行下载,转换的方式是使用 axios 请求对应的链接。
1 | //可以用来下载浏览器会默认预览的文件类型,例如mp4,jpg等 |
window.Image()
new Image() 的用途
1、图片预加载:使图片能更快打开,原理:
- 创建 image 对象,将 image 对象的 src 分别指向需加载的图片地址,图片被请求,因为 Image 对象没有显示在页面上,所以不会对页面布局产生影响。
1 | const arr = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"]; |
2、向服务器发送统计请求:为了做点击量或页面统计(访问量、页面错误量、页面加载时间等)时,向服务器发送请求。原理:
- 创建 image 对象,image 对象的 src 为请求服务器的地址,当 image 对象请求图片资源时,服务发送成功。为了避免浏览器缓存导致的不发送请求,可在请求地址后加时间戳。
1 | window.logInfo = {}; //统计页面加载时间 |
3、创建 image 对象:可在图片很大时,在图片还未加载出来时显示 loading 效果:
1 |
|
DOM
事件委托
1,如果有 100 个 li 元素,都要绑定一个 onclick 事件,这样性能不怎么好,我们可以通过事件委托实现。
1 | ul.on("click", "li", (e) => { |
dom 滚动条相关
1 |
|
CSS
flex 换行后双双对齐
1,给开启 flex 的父元素添加 ::after
伪元素,并设置宽度与需要对齐的元素一致。不要设置高度
。
1 |
|
使网页整体变灰色
1 |
|
双击不选中文字
1 | .clickNoSelectText { |
多行文字垂直对齐
1 |
|
不设置宽度文本超出隐藏
1 |
|
需要设置超出隐藏的元素必须是块级元素,否则设置将不生效。
图片层叠显示效果实现
1 |
|
- 效果如下:
less
定义不带参数的属性集合
1,可用于隐藏这些属性集合,不让它暴露到 CSS 中。
1 | // 是网页整体变成灰色 |
定义带参数的属性集合
1,不给参数设置默认值。
1 | .border-radius (@radius) { |
2,给参数设置默认值。
1 | .border-radius (@radius : 5px) { |
3,arguments 包含了所有的传递进来的参数,不用单独处理每一个参数。
1 | .box-shadow (@x: 0, @y: 0, @blur: 1px, @color: #000) { |
以上样式编译结果如下:
1 | .box-shadow { |
匹配模式
1,可以通过参数与实参的名称进行匹配,也可以通过参数的个数进行匹配。
1 | //让.mixin根据不同的@switch值而表现各异 |
编译结果如下:
1 | .class { |
移动端滑动事件
判断手指滑动方向
1 | let startx, starty; |
H5 相关
H5 判断系统类型
1 | function getSystemType() { |
Fetch API
text()
1,该方法用于读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 USVString 格式的 Promise 对象。
1 | async function getData() { |
上述代码描述的是通过 url 从 oss 上获取富文本内容(返回的是一个 html 字符串),此时获取到的结果在 body 中,如果不使用
text()
方法,那么获取到的数据就是不可读的。因此 text() 可以读取 response 对象,同时将其设置为已读。最后返回一被解析的 html 字符串。
esc-ui 业务组件库
esc-ui 之 Http 请求库
1、Http 基本配置:
- api/urlMap.js:
1 | export default { |
要传递 query 参数,api 后面需要加
?:id
。
- api/index.js:
1 | import { Http } from "esc-ui"; |
2、使用 get 发送 Array 参数:
- 要发送 query 数组参数,需要借助 qs 第三方模块 处理数组参数:
1 | npm i qs -S |
- 参数处理:
1 | const params = qs.stringify({ ids: [111, 222, 333] }, { indices: false }); // 'ids=111&ids=222&ids=333' |
- get 传递 query 参数的基本使用方式:
1 | import http from "@/api"; |
3、post 请求使用方式:
1 | import http from "@/api"; |
momment
momment 获取当天时间戳
1 | // 返回今天的起止时间戳 |
momment 获取昨天时间戳
1 | // 返回昨天的起止时间戳 |
momment 获取近七天时间戳
1 | // 返回近七天的起止时间戳 |
GBK/GB2312 编码解码
iconv-lite
使用 iconv-lite 第三方库将文本转为 gbk 格式,具体使用方式如下:
- 安装 iconv-lite:
1 | npm i iconv-lite -S |
- 使用示例:
1 | import iconv from "iconv-lite"; |
如果需要兼容 IE11,那么 iconv-lite 将是一个很好的选择。
Dva & React & Vue
创建 react + ts 项目
1 | npx create-react-app micro-react-sub --template typescript |
react 配置 antd 按需加载
1、安装 antd:
1 | npm install antd --save |
2、安装 babel-plugin-import、react-app-rewired、customize-cra:
1 | npm install babel-plugin-import react-app-rewired customize-cra --save |
3、在项目根目录下创建 config-overrides.js
文件,配置如下:
1 | const { override, fixBabelImports } = require("customize-cra"); |
4、修改 package.json 文件中 scripts 的配置:
1 | "scripts": { |
Dva 中实现请求轮循
1,利用 yield 关键字可以阻塞代码运行的特性,将异步变为同步的特性来实现轮询,通过设置一个延时函数,延时时间为 300ms,当此次的数据请求完成之后通过延时函数延时 300ms 之后再进行下一次请求执行。
2,具体代码如下:
1 | * getLatestList(action, { call, put, select }) { |
Dva 配置二级路由无法显示问题
1,Dva 配置二级路由('/app/about')
时,路由无法显示的原因是因为在入口 index.html 中加载 js 资源时没有使用绝对路径,如下:
1 |
|
以上配置,在设置二级路由时将不显示二级路由的内容。
2,如果要使配置的二级路由,生效,需要将 script 标签中引入的 src 资源改为绝对路径,如下:
1 |
|
以上配置可正常显示二级路由的内容。
3,如果使用的入口 html 为 index.ejs,则需要在加载样式及 js 资源前使用使用 base 标签将路径改为绝对路径,具体操作如下:
1 |
|
以上配置可正常显示二级路由的内容。
React 路由切换报错
1,报错详情为:Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack。
2,解决方式为:在 Link/NavLink 标签中添加 replace 属性即可。
1 | <Link to="/seal/register/info" replace> |
React 实现列表多选
1,具体实现方式如下:
1 | import React, { useState } from "react"; |
上述代码中选中逻辑解析如下:
1,当 activeMult 为 true 时,说明当前点击的这一项已经存在于 activeMultIds 中,是处于选中状态的,需要将其从 activeMultIds(选中列表)中移除。
2,当 activeMult 为 false 时,说明当前点击的这一项不在 activeMultIds 中,即还没有被选中,需要将其加入到 activeMultIds(选中列表)中。
React 获取 query 参数
1 | export const getQueryParams = (url) => { |
Vue 获取 query 参数
1 | const { formId, formGroupId, active } = this.$route.query; |
Vue H5 页面回退刷新页面
1、问题描述:当从 B 应用页面返回 A 应用页面时,使 A 页面能够实时刷新。
2、处理方式:使用 visibilitychange 事件。
- 在 mounted 中监听该方法。
1 | document.addEventListener("visibilitychange", function () { |
react 路由任意匹配路径
1、使用场景:当多个路由需要同时引用同一个路由组件时,比如 /home/detail
、/about/detail
,这个时候如果不使用任意匹配,就需要同时为 home 及 about 加一个二级路由,如果不设置二级路由,直接将 detail 设置为一级路由,当跳转到 detail 页面时,如果页面左侧存在 menu 菜单,那么菜单将失去选中状态,因为此时路由路径是 detail
而不是 /home/detail
,因此任意匹配就是用来解决这个问题的。
2、将详情页设置为任意匹配路径,即使用 (.*)?/detail
的形式,具体配置方式如下:
- router/config.js 配置文件:
1 | const config = [ |
- 跳转到详情的方式:
1 | history.push("/home/detail"); // 从home跳转到详情 |
实时监听 url 中路由路径的变化
1、通过 hashchange 监听:
- 适用于 hash 路由模式。
1 | window.onhashchange = (event) => { |
2、通过 popstate 监听:
- 注意:popstate 只能监听到 history.back()、history.forward()、history.go()。
1 | window.addEventListener("popstate", function (event) { |
3、实现 replaceState 和 pushState 的监听:
1 | const listenPath = function (type) { |
实现复制功能
react-copy-to-clipboard
1,react-copy-to-clipboard 第三方库可实现复制功能。
2,具体使用如下:
- 安装 react-copy-to-clipboard:
1 | npm install --save react-copy-to-clipboard |
- 使用示例:
1 | import React from "react"; |
架构原理相关
双向数据绑定原理
使用 Object.defineProperty 实现
1,具体实现代码如下:
1 |
|
2,使用 Object.defineProperty 实现的缺点:
需要对原始数据进行克隆。
需要分别对对象中的每一个属性设置监听,这就会导致一上来有的属性监听不上。
- 如:当定义了一个对象,而这个对象是一个空对象,其中没有任何属性,此时后期设置的属性就会监听不上。
使用 ES6 的 Proxy 实现
1,具体实现代码如下:
1 |
|
2,Proxy 实现的优点:
不需要对原始数据进行克隆,代码简洁方便。
不需要对每个属性进行监听,不会出现初使对象为空的时,监听不上属性的情况。
npm 相关
npm 版本更改
1,升级版本:
1 | npm install -g npm |
2,降级版本:
1 | npm i npm@版本号 -g |
npm 发布私有包
1,创建 npm 账号。
2,账号创建成功,即可在需要发布的项目终端中使用 npm login 命令进行登录。
3,在需要发布的项目终端中输入 npm publish 进行发布。
4,使用 npm unpublish 要删除的包名 –force 强制删除发布的私有包。
5,使用 npm unpublish 要删除的包名@版本号 删除指定的版本。
npm 私有包版本迭代
1,npm 采用语义化版本,共三位,以’.’隔开,从左至右依次代表:主版本(major)、次要版本(minor)、补丁版本(patch)。
2,变更版本号的命令:npm version [major | minor | patch]。
3,版本号更改完毕即可使用 npm publish 进行发布更新了。
发布时间: 2021-02-14
最后更新: 2022-05-04
本文标题: informal
本文链接: https://dnhyxc.gitee.io/informal/index.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!