首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
13,895 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
10,595 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
6,247 阅读
4
Mac下右键使用VSCode打开项目
3,944 阅读
5
BrowserHistory刷新页面404问题
3,430 阅读
码上世界
内容分享
生活印记
其他
登录
Search
小小孩
累计撰写
95
篇文章
累计收到
181
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
95
篇与
小小孩
的结果
2023-05-26
清除 npx 缓存
为何需要清除 npx 缓存在新建模板时,比如 npm init vue@3 , 会下载 create-vue 包在 npx 缓存中,并且有概率不会根据create-vue包的小版本更新而更新,导致运行 npm init vue@3 都是旧的模板代码。如果发现这种问题就需要删除 npx 缓存,重新获取 create-vue 包。如何删除 npx 缓存查看本地 npm 缓存文件的路径:npm config get cache然后打开上述命令返回的文件夹路径,删除_npx 文件夹即可清除;其它方式还可以借助 clear-npx-cache 实现:npx clear-npx-cache
2023年05月26日
388 阅读
0 评论
1 点赞
2023-02-28
扩展 vis-network 右键菜单
之前一直使用 antv/g6 作为拓扑图展示插件,但发现 antv/g6 在力导向布局拖动下展示效果很差,节点一直在抖,而且在大数据下,直接卡死。所以转换为 vis-network ,相比之下 vis-network 效果很流畅,展示效果也不错。 唯一不好就是 没有 G6 那么多插件,就自己根据 G6 的插件api封装形式,自己封装了一份 vis 的右键菜单插件插件代码function modifyCSS(dom, css) { if (dom) { for (let key in css) { dom.style[key] = css[key]; } } return dom; } export default class VisMenu { constructor(network, config = {}) { this.network = network; const defaultConfig = { offsetX: 6, offsetY: 6, handleMenuClick: undefined, getContent: (e) => { return ` <div class='aa'> <div>菜单项1</div> <div>菜单项2</div> </div> `; }, onHide() { return true; }, itemTypes: ["node", "edge", "canvas"], }; this.config = { ...defaultConfig, ...config }; const _this = this; // 初始化menuDom this._createMenu(); this.network.on("oncontext", function (params) { params.event.preventDefault(); _this._showMenu(params); }); } _createMenu() { const _this = this; const containerDom = _this.network.canvas.frame; modifyCSS(containerDom, { position: "relative" }); const menuDom = document.createElement("div"); menuDom.className = _this.config.className || "vis-contextmenu"; containerDom.appendChild(menuDom); modifyCSS(menuDom, { top: "0px", position: "absolute", visibility: "hidden", }); _this.menuDom = menuDom; _this.containerDom = containerDom; } _showMenu(params) { const _this = this; const { pointer } = params; const menuResParams = {}; const atNode = _this.network.getNodeAt({ x: pointer.DOM.x, y: pointer.DOM.y, }); const atEdge = _this.network.getEdgeAt({ x: pointer.DOM.x, y: pointer.DOM.y, }); if (atNode) { menuResParams.type = "node"; menuResParams.id = atNode; menuResParams.orgItemData = _this.network.body.data.nodes.get(atNode); } else if (atEdge) { menuResParams.type = "edge"; menuResParams.id = atEdge; const orgItemData = _this.network.body.data.edges.get(atEdge); const fromId = orgItemData.from; const toId = orgItemData.to; menuResParams.orgItemData = orgItemData; menuResParams.orgFromData = _this.network.body.data.nodes.get(fromId); menuResParams.orgToData = _this.network.body.data.nodes.get(toId); } else { menuResParams.type = "canvas"; } // 判断是否显示菜单 if (!_this.config.itemTypes.includes(menuResParams.type)) { this._hideMenu(); return false; } // 塞入menuDom const menuStr = _this.config.getContent(menuResParams); _this.menuDom.innerHTML = menuStr; const graphWidth = _this.containerDom.offsetWidth; const graphHeight = _this.containerDom.offsetHeight; // Vis 默认在父元素添加 position:relative 所以不需要计算 offsetTop,offsetLeft // const graphTop = _this.containerDom.offsetTop; // const graphLeft = _this.containerDom.offsetLeft; // const bbox = _this.menuDom.getBoundingClientRect(); // let x = pointer.DOM.x + graphLeft + _this.config.offsetX; // let y = pointer.DOM.y + graphTop + _this.config.offsetY; // // when the menu is (part of) out of the DOM // if (x + bbox.width > graphWidth) { // x = pointer.DOM.x - bbox.width - _this.config.offsetX + graphLeft; // } // if (y + bbox.height > graphHeight) { // y = pointer.DOM.y - bbox.height - _this.config.offsetY + graphTop; // } const bbox = _this.menuDom.getBoundingClientRect(); let x = pointer.DOM.x + _this.config.offsetX; let y = pointer.DOM.y + _this.config.offsetY; if (x + bbox.width > graphWidth) { x = pointer.DOM.x - bbox.width - _this.config.offsetX; } if (y + bbox.height > graphHeight) { y = pointer.DOM.y - bbox.height - _this.config.offsetY; } modifyCSS(_this.menuDom, { top: `${y}px`, left: `${x}px`, visibility: "visible", }); const handler = () => { _this._hideMenu(); }; _this.handler = handler; // 如果在页面中其他任意地方进行click, 隐去菜单 document.body.addEventListener("click", handler); if (_this.config.handleMenuClick) { const handleMenuClickWrapper = (evt) => { _this.config.handleMenuClick(evt.target, menuResParams, _this.network); }; _this.menuDom.addEventListener("click", handleMenuClickWrapper); _this.handleMenuClickWrapper = handleMenuClickWrapper; } } _removeMenuEventListener() { const _this = this; if (_this.handler) { document.body.removeEventListener("click", _this.handler); } if (_this.handleMenuClickWrapper) { _this.menuDom.removeEventListener("click", _this.handleMenuClickWrapper); } } _hideMenu() { if (this.menuDom) { modifyCSS(this.menuDom, { visibility: "hidden" }); } // 隐藏菜单后需要移除事件监听 this._removeMenuEventListener(); } destroy() { this._removeMenuEventListener(); if (this.menuDom) { this.containerDom.removeChild(this.menuDom); } } } 如何使用import { Network } from "vis-network"; import VisMenu from './visMenu' const myNetwork = new Network(DOM, data, {/*...options*/}); new VisMenu(myNetwork, { className: "my-network-menu", getContent(e) { return `<ul> <li>${e.type}</li> <li>菜单一</li> <li>菜单二</li> <li>菜单三</li> <li>菜单四</li> <ul>`; }, handleMenuClick(target, item, network) { console.log(target.innerText); }, itemTypes: ["node", "edge"], });
2023年02月28日
288 阅读
1 评论
0 点赞
2022-12-26
利用 MessageChannel 来实现深克隆
前言一般 JS 深度克隆有两种方法 ,一种是 JSON 序列与反序列,另一种是 lodash 提供的 deepClone,其中利用 JSON 克隆 有以下缺点属性值为函数和 undefined 的属性会丢失属性值为正则表达式的会变成{}属性值为时间对象的会变成时间字符串其实另一种方法来实现克隆,利用MessageChannel消息通道的数据传输也可以实现对象深拷贝。MessageChannel利用 MessageChannel() 构造函数返回一个新的 MessageChannel 对象,其中返回的对象中包含两个 MessagePort 对象。这就是两个通道,其中可以数据传输。具体实现如下:function deepClone(obj) { return new Promise((resolve, reject) => { const { port1, port2 } = new MessageChannel(); port1.postMessage(obj); port2.onmessage = (msg) => { resolve(msg.data); }; port2.onerror = (msg) => { reject(msg.data); }; }); }使用(async () => { function deepClone(obj) { return new Promise((resolve, reject) => { const { port1, port2 } = new MessageChannel(); port1.postMessage(obj); port2.onmessage = (msg) => { resolve(msg.data); }; port2.onerror = (msg) => { reject(msg.data); }; }); } const obj = { a: 1, b: /\d/, c: new Date(), d: undefined, }; const cloneObj = await deepClone(obj); console.log(cloneObj, cloneObj === obj); })();利用 MessageChannel 来实现克隆 可以解决 大部分 JSON 克隆的缺点,但还是有个缺点是函数不能拷贝。 如果你所需拷贝的对象含有内置类型并且不包含函数,就可以利用 MessageChannel 来实现克隆。如果函数也要拷贝, 还是使用lodash 通过递归处理的 deepClone 方法。
2022年12月26日
286 阅读
0 评论
0 点赞
2022-12-26
自建 prerender 服务来对 SPA 项目做 SEO 优化
背景单页面应用使用浏览器动态解析 JS,呈现出最终的页面,用户体验比较好,网站性能也提高不少。但网络爬虫并不会动态解析 Js (谷歌比较先进可以解析 JS),所以访问所有未处理的单页面应用 URL 得到的只会是项目入口(index.html)文件中的代码,不能得到具体的内容,对 Seo 的优化极其的不友好。为了优化项目 Seo,目前主要有两个方案:服务端渲染(Server Side Rendering)、预渲染(Prerending)。使用服务端渲染的话 需要对项目进行改造,也违背了开始做 SPA 的初衷,这里就不做深入了,这里提供了一种专门针对搜索引擎爬虫的渲染 SEO 优化预渲染使用 Prerender.io 做网站预渲染,可以将网站页面渲染之后再返回给网络爬虫,间接完成网页的解析。 Prerender 相较于其他的解决方案,配置相对要简单一些,不用修改项目源码,代码零侵入,是一个不错的解决方案。可以使用访问 htttps:/prerender.io 就可以按照步骤接入,但是这事商业化的方案,使用量较多时就需要付费了,免费的计划 适合小型项目,看个人需求。另一种是可以自建 prerender 服务 可以为多个项目进行 预渲染自建 Prerender 服务选用的是 Prerender.io 开源渲染方案 再加上自己的优化处理。这里就主要介绍搭建基于 Centos 7 和 Nginx 环境的 Prerender 渲染服务,完成项目中网页的预渲染安装 Prerender 服务在服务器上安装 Node 环境在服务器上安装 Chrome在 Node 部署 Perender 服务安装 Node 环境第一步安装 Node 环境 我就不多赘述了 网上教程一大堆安装 ChromeCentos 系统下安装配置 yum 源 因为国内无法访问 Google,所以需要自己配置 yum 源,在目录 /etc/yum.repos.d/ 下新建google-chrome.repo文件cd /ect/yum.repos.d/ touch google-chrome.repo写入内容 google-chrome.repo[google-chrome] name=google-chrome baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub安装运行# 国内推荐 yum -y install google-chrome-stable --nogpgcheck检查安装安装成功后,Chrome的安装路径应该是 /opt/google/chrome .默认情况下,root用户不能直接运行chrome,所以可以 使用 adduser www新建另一个用户来运行,这里新建了 新的 www 用户。cd /opt/google/chrome su www ./chromedebian 系统下安装需要通过apt在终端中运行以下命令来确保您的系统是最新的sudo apt update sudo apt upgrade从官方存储库安装Google Chrome,需要添加Google的GPG密钥:wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -将Google Chrome存储库添加到您的Debian系统sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'使用以下命令安装Google Chrome浏览器64位稳定版sudo apt update sudo apt install google-chrome-stable其实不一定非要安装 Chrome ,开源的 chromium 也行安装sudo apt update sudo apt install chromium找出位置 一般在 /usr/bin/chromiumwhich chromium配置项目的chrome 地址把得到的地址填入 chromeLocationconst server = prerender({ chromeLocation: '/usr/bin/chromium' }); 部署 Perender 服务本地新建项目└─node_modules └─index.js └─package.json先安装 prerender 木块npm i prerender编写 index.jsindex.jsconst prerender = require('prerender'); const server = prerender({ port:26666 }); server.start();后台运行nohup node ./server.js &如此就在 本地 26666 端口 运行了。安装中间件,这里使用 nginx 作为处理中间件server { listen 80; server_name example.com; root /path/to/your/root; index index.html; location / { try_files $uri @prerender; } location @prerender { # 如果使用 商业的 prerender.io 需要 将 YOUR_TOKEN替换为你的个人token proxy_set_header X-Prerender-Token YOUR_TOKEN; set $prerender 0; # 这里判断哪些爬虫需要走预渲染 if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider") { set $prerender 1; } # 测试用 if ($args ~ "_escaped_fragment_") { set $prerender 1; } if ($http_user_agent ~ "Prerender") { set $prerender 0; } # 静态文件不需要预渲染 if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") { set $prerender 0; } #resolve using Google's DNS server to force DNS resolution and prevent caching of IPs resolver 8.8.8.8; if ($prerender = 1) { # 后续自建将 http://service.prerender.io 替换为自己的prerender服务,如 http://127.0.0.1:26666 set $prerenderServer "http://service.prerender.io"; rewrite .* /$scheme://$host$request_uri? break; proxy_pass $prerenderServer; } if ($prerender = 0) { rewrite .* /index.html break; } } }nginx 配置如上,看注释 自及修改自己的配置,检测nginx配置,并重启nginx。至此,Prerender 服务已经安装并启动成功。测试通过curl命令测试curl http://www.example.com/?_escaped_fragment_= 一般情况下就可看到 实际运行渲染的 html 结构了也可以 在网上找一个 在线模拟百度爬虫的网站 对网站进行模拟访问一下,看看 html 结构其他优化缓存优化同一个页面 每次 进来 都要进过chrome 来进行渲染,这很影响性能,这时可以通过缓存在优化,在有效时间内,如果有缓存直接取缓存数据返回需要编写缓存处理在 node 项目中安装 cache-manager-fs 模块npm i cache-manager-fs添加 插件 文件夹 及文件└─node_modules └─plugins │ └─fs-cache.js └─index.js └─package.jsonfs-cache.jsvar cacheManager = require('cache-manager'); var fsStore = require('cache-manager-fs'); module.exports = { init: function () { this.cache = cacheManager.caching({ store: fsStore, maxsize: process.env.CACHE_MAXSIZE || 500 * 1000 * 1000 * 1000, ttl: process.env.CACHE_TTL || 24*60*60, /*seconds*/ path: 'cache', preventfill: true }); }, requestReceived: function (req, res, next) { this.cache.get(req.prerender.url, function (err, result) { if (!err && result) { req.prerender.cacheHit = true; console.log('cacheHit 缓存命中', req.prerender.url) res.send(200, result); } else { next(); } }); }, beforeSend: function (req, res, next) { if (!req.prerender.cacheHit && req.prerender.statusCode == 200) { this.cache.set(req.prerender.url, req.prerender.content); } next(); } };其中 maxsize ttl path 可以通过 环境变量设置也可以 使用默认设置相应的 index.js 也需要小小修改const prerender = require('prerender'); const fsCache = require('./plugins/fs-cache.js'); const server = prerender({ port:26666 }); server.use(fsCache) server.start(); 重启 服务就可以实现了接口保护如果你的服务需要收到保护,防止别人恶意使用 可以像 prerender.io 添加接口 token 保护修改 index.jsconst prerender = require('prerender'); const fsCache = require('./plugins/fs-cache.js'); const server = prerender({ port:26666 }); server.use({ requestReceived: (req, res, next) => { const canPassToken = [] //这里放 可以通过的token const token = req.headers['x-prerender-token']; // 这里使用与 prerender.io 一样的头部字段名 if (!canPassToken.includes(token)) { // 如果 token 不在 数据里 则返回403 res.send(403) } else { next() } }, }) server.use(fsCache) server.start();
2022年12月26日
632 阅读
2 评论
0 点赞
2022-07-11
如何将有上下级联系的扁平数据结构转成树形数据结构
有一套考察算法的小题目。将一个扁平的数据结构转成树。题目扁平的数据结构如下let arr = [ {id: 1, name: '部门1', pid: 0}, {id: 2, name: '部门2', pid: 1}, {id: 3, name: '部门3', pid: 1}, {id: 4, name: '部门4', pid: 3}, {id: 5, name: '部门5', pid: 4}, ] 输出结果[ { "id": 1, "name": "部门1", "pid": 0, "children": [ { "id": 2, "name": "部门2", "pid": 1, "children": [] }, { "id": 3, "name": "部门3", "pid": 1, "children": [ // 结果 ,,, ] } ] } ] 不考虑性能实现,递归遍历查找主要思路是提供一个递getChildren的方法,该方法递归去查找子集。/** * 递归查找,获取children */ const getChildren = (data, result, pid) => { for (const item of data) { if (item.pid === pid) { const newItem = {...item, children: []}; result.push(newItem); getChildren(data, newItem.children, item.id); } } } /** * 转换方法 */ const arrayToTree = (data, pid) => { const result = []; getChildren(data, result, pid) return result; } 该实现的时间复杂度为O(2^n)。不用递归主要思路是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储function arrayToTree(items) { const result = []; // 存放结果集 const itemMap = {}; // // 先转成map存储 for (const item of items) { itemMap[item.id] = {...item, children: []} } for (const item of items) { const id = item.id; const pid = item.pid; const treeItem = itemMap[id]; if (pid === 0) { result.push(treeItem); } else { if (!itemMap[pid]) { itemMap[pid] = { children: [], } } itemMap[pid].children.push(treeItem) } } return result; }有两次循环,该实现的时间复杂度为O(2n),需要一个Map把数据存储起来,空间复杂度O(n)最优性能主要思路也是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储。不同点在遍历的时候即做Map存储,有找对应关系。性能会更好。function arrayToTree(items) { const result = []; // 存放结果集 const itemMap = {}; // for (const item of items) { const id = item.id; const pid = item.pid; if (!itemMap[id]) { itemMap[id] = { children: [], } } itemMap[id] = { ...item, children: itemMap[id]['children'] } const treeItem = itemMap[id]; if (pid === 0) { result.push(treeItem); } else { if (!itemMap[pid]) { itemMap[pid] = { children: [], } } itemMap[pid].children.push(treeItem) } } return result; }一次循环就搞定了,该实现的时间复杂度为O(n),需要一个Map把数据存储起来,空间复杂度O(n)
2022年07月11日
809 阅读
3 评论
1 点赞
2022-07-11
如何判断一个算法的好坏
我们常常用时间复杂度和空间复杂度来判断一个算法的好坏。通俗的讲就是从执行时间和占用空间来看,执行时间越短,占用的内存空间越小,那么它就是好的算法。时间复杂度代表的就是执行时间,空间复杂度就是代表占用的内存空间。什么是时间复杂度?时间复杂度并不是计算程序具体运行的时间,它计算的是算法执行语句的次数。随着执行次数的不断增大,时间复杂度不断增大,算法花费时间越多。常见的时间复杂度有常数阶 O(1)对数阶 O(log2 n)线性阶 O(n)线性对数阶 O(n log2 n)平方阶 O(n^2)立方阶 O(n^3)k 次方阶 O(n^K)指数阶 O(2^n)计算方法选取相对增长最高的项最高项系数是都化为 1若是常数的话用 O(1)表示;通常呢,我们计算时间复杂度都是计算最坏情况。 举个例子:如 f(n)=3\*n^4+3n+300 则 O(n)=n^4`计算时间复杂度的要注意的几个点如果算法的执行时间不随 n 的增加而增长,假如算法中有上千条语句,执行时间也不过是一个较大的常数。此类算法的时间复杂度是 O(1)。 举例如下:代码执行 100 次,是一个常数,复杂度也是 O(1)。let x = 1 while (x < 100) { x++ }有多个循环语句时候,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的方法决定的。举例如下:在下面 for 循环当中,外层循环每执行一次,内层循环要执行 n 次,执行次数是根据 n 所决定的,时间复杂度是 O(n^2)。 for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { // ...code } }循环不仅与 n 有关,还与执行循环判断条件有关。举例如下:在代码中,如果arr[i]不等于 1 的话,时间复杂度是O(n)。如果arr[i]等于 1 的话,循环不执行,时间复杂度是O(0)。for (var i = 0; i < n && arr[i] != 1; i++) { // ...code }什么是空间复杂度?空间复杂度是对一个算法在运行过程中临时占用存储空间的大小。计算方法:忽略常数,用 O(1)表示递归算法的空间复杂度=(递归深度 n)*(每次递归所要的辅助空间)计算空间复杂度的简单几点仅仅只复制单个变量,空间复杂度为 O(1)。举例如下:空间复杂度为 O(n) = O(1)。let a = 1 let b = 2 let c = 3 console.log('输出a,b,c', a, b, c)递归实现,调用 fun 函数,每次都创建 1 个变量 k。调用 n 次,空间复杂度O(n*1) = O(n)。function fun(n) { let k = 10 if (n == k) { return n } else { return fun(++n) } }
2022年07月11日
189 阅读
0 评论
0 点赞
2022-03-11
JS 判断带 emoji 的字符串长度的究极方法
历史的坑JS 的字符串长度是个奇怪的设定,很多编程语言,获取字符串的长度是得到 字节长度,比如一个正常的汉字是两个字节,但在 js 中,'汉'.length 是 1 。看上去很方便,殊不知,这个特性埋下的坑。比如:😀 : '😀'.length 得到的是 2𠮷 : '𠮷''.length 得到的也是 2JS 内部字符以 UTF-16 的格式储存 (一种兼容 utf-8,但可储存编码比utf-8多8位),简单点说,就是最多存 16 位的编码,但中华文化博大精深,字太多了,有些字排到后面,都不只 16 位编码了,于是,在 js 中只能用 2 个 utf-16 来储存,于是,这个𠮷这就被 js 当成长度为 2 了。生辟字很少见的,但 emoji 不少见啊,还很流行,关键的是,大多都是 编码超过 16 位,也就是说 js 中长度不止为 1 可能有的 emoji 长度为 2 都不止。比如这个 👨👩👧👧 ,运行 '👨👩👧👧'.length 在 js 中 答案为 11 , 这里不去深究 Emoji 的历史了,有兴趣可以去了解下。解决办法那在需求中,比如评论输入框中 限定 20 的字符, 很明显 直接用 length 判断长度 ,用户两个 emoji 就不够了,显然不能满足需求其他一些 for ... of 和 codePointAt 方法在一些情况下是可以解决长度问题,但在 组合 emoji 中就无能为力了弊端的实现function length(s) { let len = 0; for (let i = 0; i < s.length; i++) { len++; // 0xffff 是16进制,一个f相当4位二进制,4个f就是16位 if (s.codePointAt(i) > 0xffff) i++; } return len; }length("😀"); //1 length("𠮷"); //1这个方法一些情况可以获取真实长度,但是在 组合 emoji 比如 👨👩👧👧 就会有问题。length("👨👩👧👧"); // 7 很明显不对究极解决这里需要引入 lodash 的 toArray 方法 转成数组 再获取数组的长度有兴趣的可以去 https://github.com/lodash/lodash/blob/master/toArray.js 源码了解它的实现 这里先不去深究了function trueLength(str) { return _.toArray(str).length; }不想引入loadsh 我这边根据源码重新写了一个const countStrLen = (string) => { const reUnicode = /\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]?|[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?)*/g; return (string.match(reUnicode) || []).length } countStrLen('👨👩👧👧') //1或者直接挂载在原型链上String.prototype.countLengtn = function() { const reUnicode = /\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]?|[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?)*/g; return (this.toString().match(reUnicode) || []).length } "𠮷👨👩👧👧👨👩👧👧😀𠮷".countLengtn() // 5
2022年03月11日
1,291 阅读
1 评论
4 点赞
2022-03-08
JS 循环里如何使用多个异步
循环里多个异步串行运行方法for 循环const items = [3000, 2000, 1000, 4000]; const testPromise = (param) => { return new Promise((resolve) => { setTimeout(() => { console.log( `时间(s):${Math.floor(Date.now() / 1000)},结果:${param} ` ); resolve(); }, param); }); }; (async () => { console.log(`开始时间(s):${Math.floor(Date.now() / 1000)}`); for (let item of items) { await testPromise(item); } console.log(`结束时间(s):${Math.floor(Date.now() / 1000)}`); })();reduce 方法const items = [3000, 2000, 1000, 4000]; const testPromise = (param) => { return new Promise((resolve) => { setTimeout(() => { console.log( `时间(s):${Math.floor(Date.now() / 1000)},结果:${param} ` ); resolve(); }, param); }); }; (async () => { console.log(`开始时间(s):${Math.floor(Date.now() / 1000)}`); const result = items.reduce((resPromise, next) => { return resPromise.then(() => { return testPromise(next); }); }, Promise.resolve()); result.then(() => { console.log(`结束时间(s):${Math.floor(Date.now() / 1000)}`); }); })();这里异步生成器的方法,但是实现不是很简约,就不展示了!多个异步并行运行方法这里使用 promise.all()const items = [3000, 2000, 1000, 4000]; const testPromise = (param) => { return new Promise((resolve) => { setTimeout(() => { console.log( `时间(s):${Math.floor(Date.now() / 1000)},结果:${param} ` ); resolve(); }, param); }); }; (async () => { const promises = items.map((item) => { return testPromise(item); }); await Promise.all(promises); })();
2022年03月08日
262 阅读
0 评论
0 点赞
2022-01-24
国内 jsDelivr 已死,网站资源该何去何从?
jsDelivr 备案吊销后,网站资源该何去何从?最近发现许多网站(包括本站点)加载时间已经超出平常。之前是 Jsdelivr CDN 的 SSL 证书过期了,导致服务断了很长时间。然而不知何种原因,jsDelivr 的域名备案在 2021 年 12 月 20 日被吊销,导致国内的 CDN 提供商网宿移除了 jsDelivr。现在 jsDelivr 的国内线路为 Fastly 提供, 所以现在的速度相比之前慢了许多。国内部分替代加速平台https://cdn.baomitu.comhttp://staticfile.orghttps://www.bootcdn.cnhttp://cdn.bytedance.com
2022年01月24日
1,387 阅读
6 评论
0 点赞
2021-12-06
解决网页无法复制和无右键菜单问题
解决 如 CSDN 网站无法复制的问题将下面链接鼠标拖入到浏览器书签栏,然后再不能复制的网页,点击该书签即可!🔓解除网页限制测试:该文字不可复制,点击运行书签即可复制
2021年12月06日
821 阅读
1 评论
5 点赞
1
2
3
...
10