1_获取 URL 参数并转换成对象
1 | // 获取URL参数并转换成对象 |
2_封装 storage 的存取
还可以参考下这里实现:https://mp.weixin.qq.com/s/oD3qDWkYSjBkJpsoLARavw
1 | // 封装storage的存取(加入命名空间和过期时间控制) |
3_实现洗牌函数
多提一嘴,该方式可以实现较少数量下的结果尽可能满足正态分布(与之相对的是随机数需要在大数据的情况才能满足正态分布),但是在游戏概率模拟上,要人为干预防止出现欧皇和脸黑的情况,这时候可以通过 RPD 算法实现。
1 | // 获取一个在[min, max]的整数 |
4_格式化 UTC 时间
1 | Date.prototype.format = function (fmt) { |
5_移动端拖拽
1 | var block = document.querySelector('#element') |
参考:https://www.cnblogs.com/lvmingyin/p/5372678.html
touchstart→touchmove→touchend 或者 touchstart→touchend→click。
6_PC 端拖拽
1 | var running_mouse = document.getElementById('running-mouse') |
7_ 移动到元素上出现滚动条,移出滚动条消失
1 | <!-- 移动到元素上出现滚动条,移出滚动条消失 --> |
8_ 全局异常处理
https://juejin.cn/post/6844903751271055374
9_ 同步阻塞法实现 sleep 函数
1 | const sleep = (delay) => { |
10_ 利用 new URL 解析 URL
1 | const parseURL = (url = '') => { |
11_ 一行代码实现星级评分
1 | const getRate = (rate = 0) => '★★★★★☆☆☆☆☆'.slice(5 - rate, 10 - rate) |
12_用位运算提升效率
1 | // | 取整 |
13_判断是否是千分符字符
1 | const numberIsThousand = (str) => /^-?\d{1,3}(,\d{3})*(\.\d+)?$/.test(str) |
14_复制文本到剪切板
1 | function copyToClipboard(text) { |
下面是以前记录的示例,这里保留下,主要有2个问题没有明白,不感兴趣可以不看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 const copyToClipboard = (content) => {
const clipboardData = window.clipboardData
if (clipboardData) {
// IE
clipboardData.clearData()
clipboardData.setData('Text', content)
return true
} else if (document.execCommand) {
// Firefox、Chrome、Edge,只能用户点击有效(https://caniuse.com/?search=execCommand)
const el = document.createElement('textarea')
el.value = content
el.setAttribute('readonly', '')
el.style.position = 'absolute'
el.style.left = '-9999px'
document.body.appendChild(el)
el.select()
setTimeout((_) => document.body.removeChild(el), 300)
return document.execCommand('copy') // 复制失败返回false
}
return false // 复制失败,需要引导用户手动复制
}
const isCopyed = copyToClipboard('hello world')
// 会失败
// const tryAgain = !isCopyed ? window.confirm('自动复制失败,是否重试') : false
// tryAgain && copyToClipboard('hello world')
!isCopyed &&
this.$confirm('自动复制失败,是否重试', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
const isCopyed2 = copyToClipboard('hello world')
return isCopyed2
? this.$message.success('复制成功')
: Promise.reject(new Error('faild'))
})
.catch(() => {
this.$message.error('复制失败,请到手动复制')
})这里发现一个奇怪的现象,Chrome 浏览器异步时间不是太久就可以复制成功,如下:
1
2
3
4 setTimeout(_ => {console.log(copyToClipboard('123456'))}, 5000)
VM39945:1 false
setTimeout(_ => {console.log(copyToClipboard('123456'))}, 1000)
VM39959:1 true这点不应该滥用,因为不同浏览器似乎支持不一样,见https://segmentfault.com/q/1010000005783830?utm_source=sf-similar-question
15_一行代码生成指定长度的数组
1 | const List = (len) => [...new Array(len).keys()] |
16_判断数据类型
1 | const type = (data) => { |
17_正则判断字符重复次数不超过两次
1 | const strIsRepeatThan2 = (str = '') => |
18_正则匹配可以只有 0 但开头不能是 0 的数字
1 | const getCorrectNumber = (str = '') => /^(\d|[1-9]\d*)$/.test(str) |
19_获取显示器分辨率
1 | const radio = window.devicePixelRatio |
关于设备分辨率和物理分辨率可以参考:https://mp.weixin.qq.com/s/Sb4qj4m5WGiWiNEP98RC0A
20_目录树的选中和取消选中
1 | const { forEach, padStart, filter, every, assign } = require('lodash') |
21_JS 加载脚本
1 | // 不阻塞加载,不能保证加载完成的顺序,只能保证加载时间的顺序 |
22_判断元素在当前窗口是否可见
1 | /** |
23_页面显示状态变化
1 | document.addEventListener('visibilitychange', function () { |
24_使用 ES6 删除对象中某些属性
1 | const obj = { |
ES6 从一个对象取部分属性给另一个对象
1
2
3
4
5
6
7
8
9 var obj = {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5,
}
var { a, d, e } = obj
var obj2 = { a, d, e }
25_连续点击多次事件
1 | // 连续点击多次触发 |
26_原生 javasript 实现 ajax 请求
1 | var $xhr = { |
202:Accepted 服务器已接受请求,但尚未处理。(202 状态码适合异步任务或者说须要处理时间比较长的请求,避免 HTTP 链接一直占用,超时这些状况。常见的就是使用 MQ 异步处理批任务,客户端定时轮训结果。缓存)
304:Not Modified 缓存可用
0:未定义的错误,可能是在联系服务器之前发生错误的结果(https://www.cnblogs.com/ranyonsue/p/9187799.html)
更多见:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
27_取消滚动的默认行为
一些参考资料:
https://zh.javascript.info/default-browser-action
https://blog.csdn.net/crystal6918/article/details/77507414
1 | // 通过阻止动作来达到阻止默认行为(即不能通过ui事件去阻止,ui事件触发了的时候,行为已经产生了) |
UI 事件包括:
- load、unload
- abort
- error
- select
- resize
- scroll
28_创建隔离的 css 样式区域
CSS 一旦生效,就会应用于全局,所以很容易出现冲突。有时候,我们一些内容是用户自己编辑或者公司的运营人员编辑展示到页面上的,如果用户编辑的内容使用了全局 css 类名,就会导致意想不到的混乱。下面讲讲我对这种情况的处理方式:
Web 组件封装(如 vue 中的 scoped,适用于 vue-cli 等编译应用的情况)
shadowDOM(兼容性不好)
1
2
3
4
5
6const html = `<h1>我是一个标题</h1>`‘’
const container = document.getElementById('contentContainer')
const shadow = container.attachShadow({mode: 'closed'})
const div = document.createElement('div')
div.innerHTML = mailContent
shadow.appendChild(div)https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attachShadow
iframe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30const html = `<h1>我是一个标题</h1>`
const container = document.getElementById('contentContainer')
const myIframe = document.createElement('iframe')
const attrDict = {
frameborder: 0,
scrolling: 'no',
seamless: true,
allowtransparency: true,
referrerpolicy: 'no-referrer',
// sandbox: '', // 安全限制
src: 'about:blank',
// srcdoc: html // 填充html
}
const keys = Object.keys(attrDict)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = attrDict[key]
myIframe.setAttribute(key, value)
}
myIframe.onload = function () {
const body = myIframe.contentDocument.querySelector('body')
body.innerHTML = mailContent
setTimeout(function () {
// 先渲染DOM,再执行样式设置
myIframe.setAttribute('width', body.scrollWidth)
myIframe.setAttribute('height', body.scrollHeight)
body.style.overflow = 'hidden'
}, 0)
}
container.appendChild(myIframe)缺点:1. iframe 有许多安全性的问题,允许用户在你的网站嵌入内容,那么最好通过 sandbox 属性进行隔离,同时对用户的内容进行预解析处理,比如阻止用户嵌入 link,script,iframe 等容易导致安全性的标签。更多见:https://www.xf1433.com/7986.html
- iframe 里面嵌入的链接打开是在 iframe 里面打开的,这个和正常的页面链接表现不一致,所以需要把所有的链接都改成
target="_blank"
新页面打开,不能允许当前页面打开
- iframe 里面嵌入的链接打开是在 iframe 里面打开的,这个和正常的页面链接表现不一致,所以需要把所有的链接都改成
js 仿 CSS Modules
① juice 样式行内化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 即通过JavaScript,解析css rule,然后对rule进行作用域限制
function limitStyleScope(html, scopeStr) {
const replaceFun = function (matched, item) {
return matched.replace(
item,
item
.split(',')
.map((item) => scopeStr + '' + item.trim())
.join(',')
)
}
return html.replace(
/<style>([\s\S]*?)<\/style>/gi,
function (matched, item) {
item = item
.replace(/ /g, ' ')
.replace(/\n/g, '')
.replace(/\s+/g, ' ')
.replace(/([\s\S]*?){/, replaceFun)
.replace(/}([\s\S]*?){/g, replaceFun)
return '<style>' + item + '</style>'
}
)
}
// 该方式的最佳实践是使用juice("juice": "^4.2.0"),上面仅仅是简单处理
const juice = require('juice')
// 转成行内样式(防止局部样式污染全局样式)
const htmlContent = ~htmlContent.indexOf('</style>')
? juice(htmlContent)
: htmlContent
// 去除原来DOM上的类(防止全局样式污染局部样式)
htmlContent = htmlContent.replace(/class=['"].+?['"]/, '')
// 解决 导致的单词内换行失效问题(word-beak: break-word;)
htmlContent = htmlContent.replace(
/( )+/g,
function (matched, item, index) {
const preWord = htmlContent[index - 1]
const nextWord = htmlContent[index + matched.length]
if (preWord === ' ' && nextWord === ' ') return matched
if (preWord === ' ') return matched.slice(0, -item.length) + ' '
if (nextWord === ' ') return ' ' + matched.slice(item.length)
// 前后都不为空
if (blankLength > 2)
return ' ' + matched.slice(item.length, -item.length) + ' '
if (blankLength === 2) return ' ' + item
return ' '
}
)② 样式表添加父级作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46function addParentScopesToStyles(htmlString, parentScopeName) {
// Create a new DOMParser
const parser = new DOMParser()
// Parse the HTML string
const doc = parser.parseFromString(htmlString, 'text/html')
// Get all style elements
const styleElements = doc.querySelectorAll('style')
// Loop through each style element
styleElements.forEach((styleElement) => {
const styleSheet = styleElement.sheet
// Loop through each CSS rule in the stylesheet
for (let i = 0; i < styleSheet.cssRules.length; i++) {
const rule = styleSheet.cssRules[i]
// If it's a CSSStyleRule (not @import, @font-face, etc.)
if (rule instanceof CSSStyleRule) {
// Add parent scope to the CSSText
const modifiedCssText = parentScopeName + ' ' + rule.cssText
// Update the CSSText(`rule.cssText` cannot be overwritten)
rule.cssNewText = modifiedCssText
}
}
// Reconstruct the style element with modified CSS rules
styleElement.textContent = Array.from(styleSheet.cssRules)
.map((rule) => rule.cssNewText)
.join('\n')
})
// Return the modified HTML string
return doc.documentElement.outerHTML
}
// Example usage
const htmlString =
'<html><head><style>p {color: red;padding: 0}a{display: block;}</style></head><body><p>Hello, world!</p></body></html>'
const modifiedHTML = addParentScopesToStyles(
htmlString,
'#content-' + new Date().valueOf()
)
console.log(modifiedHTML)
综上所述,iframe 和 shadow dom 这两种方式比较符合,但是对于移动端传统应用无法使用上面 2 种方式也可以使用 juice。
对于不太复杂的场景,如文章的发布建议使用 web 组件的 css 作用域隔离的方式。
如果是邮件等高危场景推荐使用 iframe 方便对内容更好做限制
如果是普通文本且不需要支持低版本浏览器,则可以使用 shadowDOM
29_js 对象如何优雅的取出/设置一个深度的值?
我这里是对 lodash 的简单实现,不考虑性能以及过多的安全因素
1 | const data = { |
还可以使用lodash/get
1
2 # 实现见:https://github.com/lodash/lodash/blob/master/get.js
_.get(obj,'school.class1.student', undefined)
类似微信小程序的 setData
1 | const data = { |
30_自定义事件的使用
1 | // 推荐使用发布订阅者模式去实现,这里还涉及兼容性问题,不太推荐使用,了解即可 |
31_冻结对象
1 | // 1. 使用Object.freeze实现 |
实现深度冻结
1 | function deepFreeze(obj) { |
32_原生 js 实现移动端的一些常用事件
更多手势事件见:https://www.runoob.com/ionic/ionic-gesture-event.html
这里目前只实现了部分
1 | export default (function () { |
33_回调 API 改成支持 Promise
如微信小程序现在大多数的 API 还是使用回调的方式,不支持 promise。可以使用该方式进行封装。
PS: nodejs 中有 util.promisify 也是把 异步 API 包装成 promise
1 | const promisify = function (func) { |
34_node 传参
在 package.json 里面脚本经常使用 node build.js
的命令打包,可以使用下面的方式传参。
1 | const [nodePath, filePath, ...arguments] = process.argv |
也可以使用第三方插件,如
const argv = require('yargs').argv
, 注意用法稍有不同(node build.js --arg1 1 --arg2 2 --arg3 3
, 即 webpack-dev-server 使用的读取参数模块, webpack-dev-server –config ./webpack.conf.js)PS: 多扩展一点,在 webpack-dev-server 启动服务器时,仅支持指定的参数,如–config,–inline 等,要传入自定义的一些参数可以通过下面方式
1
2 webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env.platform app
# argv.env.platform 即可拿到自定义参数
35_英文单词自动换行
本来这个是 css 问题,可以通过 css 解决,但是发现在富文本编辑器中存在用户复制粘贴导致的单词内空格通过 HTML 字符实体(
)连接,css 失去效用,参考 outlook 和 qq 邮箱,采用加上 js 过滤的方式处理
1 | // 假设这里contentHTML是富文本编辑的html数据 |
拓展:这里 css 用下面的代码实现
1
2
3
4 word-wrap: break-word;
overflow-wrap: anywhere; /* overflow-wrap 是 CSS3 里的属性,用来取代 word-wrap,为了兼容2个都写上 */
/* ===== */
word-break: break-word; /* deprecated,目前依旧可用,且兼容性更好,作用相当于上面的2个 */
word-wrap: break-word;
和overflow-wrap: break-word;
只适合在宽度固定的场景下,如果对于自适应宽度会失效,如 flex 布局右侧自适应。可以用 word-wrap: break-word; + overflow-wrap: anywhere; 处理,或者 word-break: break-word;目前来说,推荐使用 word-break: break-word;,兼容性更好(有点讽刺)。https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-wrap
36_cookie 操作封装
1 | var $cookies = { |
37_微信内置浏览器环境判断
1 | var getRunEnv = function () { |
参考:https://www.jianshu.com/p/93ab17a7d8b7
普通微信中,普通文件可以直接下载,apk 会被屏蔽,部分其他文件格式也会被屏蔽(解决:
https://blog.csdn.net/weixin_45132005/article/details/90580806,好像最近类似的平台都无法访问了)apk 要在微信中下载:https://segmentfault.com/q/1010000002553117
企业微信 PC 有接口可以调起浏览器打开:
wx.invoke('openDefaultBrowser', {})
, 如果文件下载只为预览,企业微信移动端可以用wx.previewFile
接口
38_location.replace 不生效处理方法
在钉钉和微信的内置浏览器存在 location.relace 会产生历史问题,可以用下面方法解决。
1 | function locationReplace(url) { |
39_常用的正则表达式
1 | // 匹配URL的Origin |
40_生成随机数
1 | function randomString(len) { |
41_文件大小格式化
1 | // c 参数:表示要被转化的容量大小,以字节为单 |
42_Promise.allSettled 实现
原生支持情况:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
因为浏览器支持不好,自己实现一下
1 | const allSettled = function (promiseList) { |
43_判断是否为整数类型
1 | function isInteger(obj) { |
44_防抖节流
1 | // 防抖(常见场景:键盘输入关键词联想) |
45_删除 DOM 上的所有属性
1 | function cleanDOM(innerHTMLStr, excludeAttrs = []) { |
这里只为记录一个疑惑,要完全清除需要执行多遍,不清楚为什么?猜测和浏览器重排优化有关
46_懒加载
对于邮件或者博文,可能存在 html 过长导致的页面显示不了或者等待过长时间,可以使用 DOM 的懒加载处理(特别是 IOS 移动端,低内存下如果来个 10 多 M 的 html 字符串则容易卡死或者自动刷新)
1 |
|
上面还存在一个问题就是对于异步加载的元素,直接替换后在内容加载出来前该位置还是保持空白。因此如果场景更小一些,比如单纯只有图片的场景,可以通过类似的方式实现(如下)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 function hideDOM(mailHTML) {
const div = document.createElement('div')
div.innerHTML = mailHTML
const nodes = div.querySelectorAll('img')
for (let i = nodes.length - 1; i >= 0; i--) {
nodes[i].setAttribute('data-src', nodes[i].getAttribute('src'))
nodes[i].setAttribute(
'src',
'https://blog.cdn.ionluo.cn/blog/loading.gif'
)
}
return div.innerHTML
}
function showDOM(lazyNode) {
lazyNode[i].setAttribute('src', nodes[i].getAttribute('data-src'))
}
另外图片的懒加载还有一个专门的属性,可惜兼容性不好。https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement/loading
47_组合手势的一点思考
移动端开发中,有的时候希望像 PC 端一样可以通过组合键来切换不同功能,但是移动端键盘无法输入组合键,则可以考虑用组合手势来代替(题外话:另外一个考虑就是画一个简单的符号,但是符号涉及轨迹识别,准确性不好验证)
组合手势难点在于要控制手势的时间衔接,想到支付宝的鲤鱼跃龙门的小游戏,通过快速点击让小鲤鱼越冲越高,如果不点击则慢慢下降,设计实现了下面的代码(只实现了简单的点击+长按,如果要改成滑动就判断距离,和长按同理)
1 | // 进入页面 ${limitTime} 秒内,长按 ${longpressTime} 秒后快速点击 ${clickTime} 次,等待 ${waitTime} 秒开启调试模式 |
48_支持解析带注释的 JSON
有的时候应用需要设置一些配置,通常可以使用 JSON 文件来描述,但是直接 JSON.parse 无法解析带注释的 JSON,这时候可以通过自定义解析器来支持注释
1 | const CJSON = {} |
当然最直接的方式就是使用开源的库,如comment-json
49_函数拷贝
1 | function func(a, b, c) { |
50_递归优化
1 | // 普通递归 |
// 蹦床函数实现尾递归优化(了解即可)
function trampoline (f) {
while (f && f instanceof Function) {
f = f()
}
return f
}
function tailFibonacci2 (n, ac1 = 1, ac2 = 1) {
if (n <= 1) return ac2return tailFibonacci2.bind(null, n - 1, ac2, ac1 + ac2)
}
trampoline(tailFibonacci2(1000))
51_求时间段交集
1 | // 求时间段交集(time1, time2) |
53_等待回调执行
对于一些 sdk 内嵌的时候,需要等待 sdk 接口注入完成执行方法封装
1 | function NativeJs(timeout) { |
54_不定长度的 promise 依次执行
1 | function loadScript (url) { |
55_虚拟滚动
1 | <template> |
56_PT2PX
1 | px = 72pt / 96 = 0.75 * pt |
57_公交卡规则更改后花费
1 | const MONTH_DAY = 22 // 一个月的天数 |
58_原型链继承
1 | function __extends(/* 派生类 */ d, /* 基类 */ b) { |
还是 class 方便啊,可惜不支持 ES5
1
2
3
4
5
6
7
8
9
10 class Point3D extends Point {
constructor(x, y, z) {
super(x, y)
this.z = z
}
add(point) {
var point2d = super.add(point)
return new Point3D(point2d.x, point2d.y, this.z + point.z)
}
}
59_克隆 DOM 时也复制事件
参考:https://github.com/shulandmimi/blog/issues/5
1 | ;(function () { |
自实现绑定方法
1 | function cache() { |
60_ html2text and text2html
1 | function encode(str) { |
61_Promise.abort 实现
1 | function abortWrapper(p1) { |
62_处理前端竞态请求
分为 2 种情况,取消请求和忽略请求,见:https://mp.weixin.qq.com/s/H1nbIeryxdaPhiOmkI_oRA
1 | <!DOCTYPE html> |
63_导出 table 数据
1 | function exportToCSV(data = [], filename = 'demo.csv') { |
这里存在以 0 开始的字符串,用 excel 打开后,我们看到的可能是经过科学计算转换过后的,或者是去掉了前面 0 的数字,为了展现方便,我们可以在生成 csv 文件的时候,用=”01023232”将这样的字段来表示即可。详见:https://www.cnblogs.com/christmasliu/articles/4260039.html
64_千分位分割数字
1 | const num = 1234567890 |
65_分时函数封装
1 |
|
66_记忆化函数
对于一些高频率访问但是内容并不经常更新的接口,我们通常会缓存其值来减少向服务器通信的开销。
1 | function memorize(f, limit = 50) { |
67_根据目录树结构数据生成表格
1 |
|
68_合并表格最后一列的相同内容
1 |
|
69_精准加法
1 | // 精准加法 |
还有一个想法就是当做字符串处理,如
+'0.92'.padEnd(5, '0').replace('.', '')
来转换成整数运算就不用担心精度问题了
70_递归高级函数
1 | // 递归 map |
上面只是简单举例,其他高级函数如 reduce 等类似
71_插值函数
1 | /* 插值函数 |
72_树操作
1 | // 根据查询条件获取指定节点 |
推荐阅读
本文链接: http://www.ionluo.cn/blog/posts/229e087d.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!