技术清单
- js正则删除行内块之间的空格
- 本地缓存
- 页面直接编辑
- 实现一套撤销重恢复做功能
- 双向数据绑定
- 节流防抖
- 导出表格
- window.requestAnimationFrame & window.requestIdleCallback
- MutationObserver
- ResizeObserver
- 页面离开检测
- IntersectionObserver 是否可见
- Es6模块导入
- Tay Catch
- await 执行顺序
- 控制进度 (任务编排)
- 睡觉函数
- 弱网检测
- 获取 某个 Attr 属性上的 dom
- userAgent
- import和require区别
- 检测数据类型
- HTML页面加载完毕后运行JS
- 解释 script 标签的加载和执行顺序
- JS 动态加载 JS
- Symbol
- 小东西
- Think 循环
- js文件压缩原因和压缩原理
- window.performance API
- 读取注释
- 参看文档
# js正则删除行内块之间的空格
let rep = function (match, item1, item2, item3) {
// item2 是空格部分
return item1 + item3
}
let source = template.content.replace(/(>)(\s*)(<)/g, rep);
2
3
4
5
// 去除两边空格 or 全部空格
const strTrims = (str: string, isGlobal: string) => {
let result = str.replace(/(^\s+)|(\s+$)/g, '');
if (isGlobal === 'g') {
result = result.replace(/\s/g, '');
}
return result;
};
2
3
4
5
6
7
8
# 本地缓存
localStorage的库
yarn add store
JS检测本地储存localStorage的变化
window.addEventListener('storage', function(event){
console.log(event)
})
2
3
IndexedDB的库
yarn add hello-indexeddb
# 页面直接编辑
document.body.contentEditable = true
// 一些API的简单使用
<input type=button value=剪切 οnclick=document.execCommand('Cut')>
<input type=button value=拷贝 οnclick=document.execCommand('Copy')>
<input type=button value=粘贴 οnclick=document.execCommand('Paste')>
<input type=button value=撤消 οnclick=document.execCommand('Undo')>
<input type=button value=重做 οnclick=document.execCommand('Redo')>
<input type=button value=删除 οnclick=document.execCommand('Delete')>
<input type=button value=黑体 οnclick=document.execCommand('Bold')>
<input type=button value=斜体 οnclick=document.execCommand('Italic')>
<input type=button value=下划线 οnclick=document.execCommand('Underline')>
<input type=button value=停止 οnclick=document.execCommand('stop')>
<input type=button value=保存 οnclick=document.execCommand('SaveAs')>
<input type=button value=另存为 οnclick=document.execCommand('Saveas',false,'c:\\test.htm')>
<input type=button value=字体 οnclick=document.execCommand('FontName',false,fn)>
<input type=button value=字体大小 οnclick=document.execCommand('FontSize',false,fs)>
<input type=button value=刷新 οnclick=document.execCommand('refresh',false,0)>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
*第二个参数最好不要设置为TRUE,否则可能会执行不了
*/
//加粗
function jiacu() {
document.execCommand("Bold",false,null);
}
//斜体
function xieti() {
document.execCommand("Italic",false,null);
}
//下划线
function xiahua() {
document.execCommand("Underline",false,null);
}
//删除线
function shanchu() {
document.execCommand("StrikeThrough",false,null);
}
//设置字体
function setFontName(param) {
document.execCommand("FontName",false,param);
document.getElementById("fontUl").style.display="none";
}
//设置字体大小
function setFontSize(param) {
document.execCommand("FontSize",false,param);
document.getElementById("sizeUl").style.display="none";
}
//设置字体颜色
function setFontColor(param) {
document.execCommand("ForeColor",false,param)
document.getElementById("fontColor1").style.display="none";
}
//设置背景颜色
function setBackColor(param)
{
document.execCommand("BackColor",false,param)
document.getElementById("bgColor1").style.display="none";
}
//删除文字格式
function removeFormat() {
document.execCommand("RemoveFormat",false,null);
}
//对齐方式
function duiqiway(param) {
document.execCommand(param,false,null);
document.getElementById("duiqiUl").style.display="none";
}
//插入列表
function insertList(param) {
document.execCommand(param,false,null);
alert("暂时未实现");
document.getElementById("liebiaoUl").style.display="none";
}
//改变缩进
function changeIndent(param) {
document.execCommand(param,false,null);
alert("暂时未实现");
}
//链接 //不能实现,取消链接的命令只用于用createLink命令创建的链接
function setLink(param) {
document.execCommand(param,false,"http://blog.csdn.net/u011043843"); //第三个参数为URL
alert("暂时未实现");
}
//表情
function insertBQ(param) {
document.execCommand("InsertImage",false,param); //param为图片的url
document.getElementById("bqDiv").style.display="none";
}
//段落
function parag(param) {
document.execCommand("formatBlock",false,param);
document.getElementById("paraUl").style.display="none";
}
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 实现一套撤销重恢复做功能
上面的方案仅仅针对文字类需求有效,而且兼容性比较低,一些设备都不能用。
如果是画布的话,只能自己实现一套
我们需要两个栈来实现这个操作:分别是撤销栈和回退栈。
整体流程: 1.每当我们开始拖拽画布元素,或者开始缩放之前,我们要保存此次操作时刻的屏幕快照,在本系统中,组件都以json数组的形式来渲染。所以我们需要保存的就是当前画布的组件json数组。.将当前画布的json数组作为一个整体push进撤销栈,表示记录本次操作。
2.我们拖动元素这个动作结束了,当前画布的json数组必然发生了更新。现在我们想撤销这次拖动操作。点击撤销后,先将当前画布的json数组push到回退栈中,用撤销栈中最后一个json数组来setstate重新渲染画布元素(第一步push的那个json数组)。再使用数组pop方法,删除撤销栈中最后一个json数组。
3.回退时先将当前画布的json数组push到撤销栈中,用回退栈中最后一个json数组来setstate重新渲染画布元素(第二步push的那个json数组)。再使用数组pop方法,删除回退栈中最后一个json数组。
看下控制台输出:
部分实现逻辑
import { cloneDeep } from 'lodash';
import { useModel } from 'umi';
// 最大回退操作步骤
const maxStep = 60;
const undoQueue: any = [];
let redoQueue: any = [];
// 实现撤销回退队列的管理功能
const QueueManager = () => {
const { components, setComponents } = useModel('visualPage');
const saveSnap = () => {
const snap = cloneDeep(components);
if (undoQueue.length >= maxStep) {
undoQueue.shift();
}
undoQueue.push(snap);
// console.log('queue', undoQueue, redoQueue);
// 注意!!每次执行命令时清空重做栈
redoQueue = [];
};
const undo = () => {
const snap = cloneDeep(components);
const c = cloneDeep(undoQueue[undoQueue.length - 1]);
setComponents(c);
redoQueue.push(snap);
undoQueue.pop();
// console.log('undo', undoQueue, redoQueue);
};
const redo = () => {
const snap = cloneDeep(components);
const c = cloneDeep(redoQueue[redoQueue.length - 1]);
setComponents(c);
undoQueue.push(snap);
redoQueue.pop();
// console.log('redo', undoQueue, redoQueue);
};
return {
saveSnap,
undo,
redo,
undoQueue,
redoQueue,
};
};
/* bug手中过,片叶不沾身! */
export default QueueManager;
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
52
53
# 双向数据绑定
defineProperty
版本
<input type="text" id="input">
<span id="text"/>
const data = {};
Object.defineProperty(data, 'text', {
set(value) {
this.value = value;
input.value = value;
text.innerText = value;
return value
},
get(val) {
console.log('获取值', this); //this就是当前的对象
return val
}
});
input.oninput = function (e) {
console.log(data.text = e.target.value);
console.log(data.text);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
proxy
版本
<input type="text" id="input">
<span id="text"/>
const data = {};
const handler = {
set(target, key, value) { //当前对象 key 当前值
target[key] = value;
input.value = value;
text.innerText = value;
return value
},
get(obj, value, target) {//{key:当前值} 当前值 当前对象
console.log('获取值')
return value
}
};
const proxy = new Proxy(data, handler);
input.oninput = function (e) {
proxy.text1 = e.target.value
console.log(proxy.text2 = e.target.value);
console.log(proxy.text1);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 节流防抖
// 防抖函数
export const debounce = function (func, delay) {
let timer;
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
};
// 节流函数
export const throttle = function (func, delay) {
let now = Date.now();
return function (...args) {
const current = Date.now();
if (current - now >= delay) {
func.apply(this, args);
now = current
}
}
};
// 使用时候切记不是每次都要调用这个,利用闭包原理,
// vue2下使用方式,或者生命周期里面提前赋值
methods:{
hangleChange:debounce(function(e){
console.log(this.value)
})
}
created(){
this.fnScroll = this.throttle(fn, 1000)
},
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
# 导出表格
const url = window.URL.createObjectURL(
new Blob([res], { type: "application/vnd.ms-excel" })
);
const fileName = `操作日志${dayjs().format("YYYY-MM-DD")}.xls`;
let link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
//释放URL对象所占资源
window.URL.revokeObjectURL(url);
//用完即删
document.body.removeChild(link);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# window.requestAnimationFrame & window.requestIdleCallback
软知识:
屏幕刷新频率: 屏幕每秒出现图像的次数,普通笔记本为60Hz.
动画原理: 计算机每16.7ms刷新一次(1000/60),由于人眼的视觉停留,所以看起来是流畅的移动。
setTimeout: 通过设定
间隔时间
来不断改变图像位置,达到动画效果。但是容易出现卡顿、抖动的现象;原因是:
1、settimeout任务被放入
异步队列
,只有当主线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚;
2、settimeout的固定时间间隔不一定与屏幕刷新时间相同
,会引起丢帧。
- requestAnimationFrame: 优势:
1.由
系统决定回调函数的执行时机
,60Hz的刷新频率,那么每次刷新的间隔中会执行一次回调函数,不会引起丢帧,不会卡顿
2.CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时
,setTimeout 仍然在后台执行动画任务
,由于此时页面处于不可见或不可用状态, 刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停, 因此跟着系统步伐走的requestAnimationFrame也会停止渲染
,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
3.函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行, 使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。
# requestAnimationFrame
//回到顶部
const goTop=()=> {
let product_content = document.getElementById('product_content');
this.progress = product_content.scrollTop;
if (this.progress >= 0) {
window.requestAnimationFrame(this.step);
}
}
export const step= ()=> {
let product_content = document.getElementById('product_content');
if (this.progress > 0) {
this.progress -= 250; //这里可以根据高度成比例减少,不会突兀
product_content.scrollTop = this.progress;
window.requestAnimationFrame(this.step);
}else{
window.cancelAnimationFrame(this.step);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# requestIdleCallback
浏览器每一帧都需要完成哪些工作?
通过上图可看到,一帧内需要完成如下六个步骤的任务:
处理用户的交互
JS 解析执行
帧开始。窗口尺寸变更,页面滚去等的处理
requestAnimationFrame(rAF)
布局
绘制
上面六个步骤完成后没超过16.7ms,说明时间有富余,此时就会执行requestIdleCallback里注册的任务
(
requestIdleCallback的时长并不是16ms,他是一个肉眼觉察不到的时间)
,如果没rAF这样的循环处理,浏览器一直处于空闲状态的话,deadline.timeRemaining
可以得到的最长时间
在空闲时段
这种情况下,用户代理可能没有即将完成的任务,可以限制空闲周期的结束。为了避免在不可预测的任务(例如用户输入的处理)中引起用户可察觉的延迟,这些空闲周期的长度应限制为最大值50ms。
最大期限为50毫秒,是根据研究[ RESPONSETIME ] 得出的,该研究表明,对用户输入的100毫秒以内的响应通常被认为对人类是瞬时的。将闲置截止期限设置为50ms意味着即使在闲置任务开始后立即发生用户输入,用户代理仍然有剩余的50ms可以在其中响应用户输入而不会产生用户可察觉的滞后。
API
var handle = window.requestIdleCallback(callback[, options])
- callback:回调,即空闲时需要执行的任务,该回调函数接收一个IdleDeadline对象作为入参。其中IdleDeadline对象包含:
- didTimeout,布尔值,表示任务是否超时,结合 timeRemaining 使用。
- timeRemaining(),表示当前帧剩余的时间,也可理解为留给任务的时间还有多少。
- options:目前 options 只有一个参数
- timeout。表示超过这个时间后,如果任务还没执行,则强制执行,不必等待空闲
requestIdleCallback(myNonEssentialWork, { timeout: 2000 });
// 任务队列
const tasks = [
() => {
console.log("第一个任务");
},
() => {
console.log("第二个任务");
},
() => {
console.log("第三个任务");
},
];
function myNonEssentialWork (deadline) {
// 如果帧内有富余的时间,或者超时
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
work();
}
if (tasks.length > 0)
requestIdleCallback(myNonEssentialWork);
}
function work () {
tasks.shift()();
console.log('执行任务');
}
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
超时的情况,其实就是浏览器很忙,没有空闲时间,此时会等待指定的 timeout 那么久再执行
,通过入参 dealine 拿到的 didTmieout 会为 true,同时 timeRemaining () 返回的也是 0
。
超时的情况下如果选择继续执行的话,肯定会出现卡顿的,因为必然会将一帧的时间拉长
cancelIdleCallback
与 setTimeout 类似,返回一个唯一 id,可通过 cancelIdleCallback 来取消任务。
# MutationObserver
MutationObserver 是一个可以监听DOM结构变化的接口。
异步的 (微任务)
- childList:如果突变目标的子代被观察,则设置为 true。
- attributes:如果要观察目标属性的突变,则设置为 true。 如果指定了 attributeOldValue 和(或)attributeFilter,则可以省略。
- characterData:如果要观察到目标数据的突变,则设置为 true。 如果指定了characterDataOldValue,则可以省略。
- subtree:如果突变不仅仅是目标对象,而且包括目标的后代(descendants),则设置为 true。
- attributeOldValue:如果前面的 attributes 属性设置为 true 或省略,并且目标的属性值在突变前要做记录,则设置为 true。
- characterDataOldValue:如果前面的 characterData 属性设置为 true 或省略,并且目标的数据在突变前要做记录,则设置为 true。
- attributeFilter:设置为属性本地名称列表(没有命名空间),如果不是所有属性突变都需要观察,属性为 true 或省略。例如:['class','src']
new MutationObserver(() => {
let dom = document.body.getAttribute('data-random')
console.log(dom) // 如果用户修改body的属性,就会触发这个方法
}).observe(document.body, {
attributes: true
})
document.body.setAttribute('data-random', Math.random())
document.body.setAttribute('data-random', Math.random())
document.body.setAttribute('data-random', Math.random())
// 只会输出一次 ovserver
2
3
4
5
6
7
8
9
10
11
12
# ResizeObserver
ResizeObserver 创建一个新的ResizeObserver对象监听元素大小变化
这个接口可以监听到元素的变化,以前我们只能通过window.resize来监听页面变化,现在有了这个API任何元素都可以监听
<!--html-->
<textarea id="main"></textarea>
// javascript
let mainEl = document.querySelector('#main')
const resizeObserver = new ResizeObserver(entries => {
console.log(entries)
})
//拉动textarea的大小,即可看到输出
// 监听元素大小改变
resizeObserver.observe(mainEl) //ResizeObserverEntry [{target: textarea#main, contentRect: {x: 2, y: 2, width: 143, height: 43, top: 2, …}}]
// 取消某个元素监听
//resizeObserver.unobserve(mainEl)
// 取消全部元素监听
//resizeObserver.disconnect()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 页面离开检测
visibilitychange
const loadedAdCount = () => {
// 页面可见
if (document.visibilityState === 'visible') {
}
// 页面不可见
if (document.visibilityState === 'hidden') {
}
// 页面不可见 返回true/false
if document.hidden) {
}
};
// 最好是加上 load 事件,否则在页面加载(刷新)的时候,会触发一次
window.addEventListener('load', () => {
// 只能挂载在 document 上
document.addEventListener('visibilitychange', loadedAdCount);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pagehide 少用、移动端很少兼容
pageshow 类似,一样
能感知页面从当前上下文被移除,而不仅仅是隐藏(例如,关闭页面或刷新页面都会触发)。 可以处理页面关闭前的清理逻辑(但不能确保一定成功,取决于浏览器支持)。
window.addEventListener('pagehide', (event) => {
if (event.persisted) {
console.log('页面进入缓存(bfcache)');
} else {
console.log('页面被隐藏或刷新');
}
});
2
3
4
5
6
7
# 区别总结
事件 | 触发时机 | 适用场景 | 支持的属性 |
---|---|---|---|
visibilitychange | 页面可见性发生变化(如切换标签页、最小化窗口等) | 暂停/恢复操作,节省资源 | document.visibilityState |
pagehide | 页面被隐藏(包括关闭、刷新、导航离开或进入缓存) | 清理资源,保存数据,处理页面即将离开时的逻辑 | event.persisted |
pageshow | 页面被显示(包括首次加载或从缓存恢复) | 初始化页面逻辑,处理缓存恢复的逻辑 | event.persisted |
# IntersectionObserver 是否可见
语法: var observer = new IntersectionObserver(callback[, options]);
callback
当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数:
entries : 一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。
observer : 被调用的IntersectionObserver实例。
options 可选
一个可以用来配置 observer 实例的对象。如果options未指定,observer 实例默认使用文档视口作为 root,并且没有 margin,阈值为 0%(意味着即使一像素的改变都会触发回调函数)。你可以指定以下配置:
root
监听元素的祖先元素Element对象,其边界盒将被视作视口。目标在根的可见区域的的任何不可见部分都会被视为不可见。
rootMargin
一个在计算交叉值时添加至根的边界盒 (bounding_box (en-US)) 中的一组偏移量,类型为字符串 (string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和 CSS 中的margin 属性等同; 可以参考 intersection root 和 root margin 来深入了解 margin 的工作原理及其语法。默认值是"0px 0px 0px 0px"。
threshold
规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见。若指定值为 1.0,则意味着整个元素都在可见范围内时才算可见。可以参考阈值来深入了解阈值是如何使用的。阈值的默认值为 0.0。
实践: vue3 实现一个图片懒加载
import { DirectiveBinding } from 'vue';
const loadingImg = 'xxx';
const defaultImg = 'xxx';
// 异步加载图片
const imageAsync = (url: string) => {
return new Promise<void>((resolve, reject) => {
const img = new Image();
img.src = url;
img.onload = () => {
resolve();
};
img.onerror = (err) => {
reject(err);
};
});
};
export default {
// @ts-ignore
mounted(el, binding: DirectiveBinding) {
el.src = loadingImg;
// https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver/IntersectionObserver#threshold
const io = new IntersectionObserver(
(entries) => {
entries.forEach((item) => {
if (item.isIntersecting) {
// 使用异步加载图片
imageAsync(binding.value)
.then(() => {
el.src = binding.value; // 成功后再替换 img 标签的 src
})
.catch((error) => {
console.log(error);
el.src = defaultImg;
});
io.unobserve(item.target);
}
});
},
{
rootMargin: '0px 0px 10px 0px',
threshold: 0,
}
);
io.observe(el);
},
};
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
# Es6模块导入
import * as xxx from ‘xxx’: // 会将若干export导出的内容组合成一个对象返回;
import {a,b,c,...} from ‘xxx’
import xxx from ‘xxx’:(export default xxx)// 只会导出这个默认的对象作为一个对象
export {default as docs} from "./xxx" // 可以作为入口 统一导出
2
3
4
5
6
# Tay Catch
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} finally {
console.log('失败与否必须执行')
}
2
3
4
5
6
7
# await 执行顺序
await 后面是 Promise 的时候,会阻断后面的执行,变成同步执行,不然的话此时添加 await 没有效果
const copyTask = async () => {
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1111);
resolve(3333);
}, 2000);
})
await copyParentTask(params); // 里面打印 4444
await getProjectTasksList(); // 里面打印 5555
console.log(2222);
return true;
};
// 执行顺序 1111、3333、4444、5555、2222
const copyTask = async () => {
await setTimeout(() => {
console.log(1111);
}, 2000);
await copyParentTask(params); // 里面打印 4444
await getProjectTasksList(); // 里面打印 5555
console.log(2222);
return true;
};
// 执行顺序 4444、5555、2222 、1111
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
# 控制进度 (任务编排)
// 控制加载
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
let frameworkStartedDefer = new Deferred()
async function ff() {
await frameworkStartedDefer.promise;
console.log(121212)
}
ff();
frameworkStartedDefer.resolve()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 睡觉函数
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function ff() {
await sleep(3000);
console.log(121212)
}
ff();
2
3
4
5
6
7
8
9
# 弱网检测
const isSlowNetwork = navigator.connection
? navigator.connection.saveData ||
(navigator.connection.type !== 'wifi' &&
navigator.connection.type !== 'ethernet' &&
/(2|3)g/.test(navigator.connection.effectiveType))
: false;
if (!navigator.onLine || isSlowNetwork) {
return;
}
2
3
4
5
6
7
8
9
# 获取 某个 Attr 属性上的 dom
<div class="report-position" data-log-expo="{"event":"get_sharebooks_content"}"></div>
获取 dom
document.querySelectorAll(`[data-log-expo]`)
# userAgent
// 判断浏览器内核、手机系统等,使用 browser.userAgent.mobile
var browser = {
userAgent: function () {
var ua = navigator.userAgent;
var ualower = navigator.userAgent.toLocaleLowerCase();
return {
trident: ua.indexOf('Trident') > -1, // IE内核
presto: ua.indexOf('Presto') > -1, // opera内核
webKit: ua.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') == -1, // 火狐内核
mobile: !!ua.match(/AppleWebKit.*Mobile.*/) || !!ua.match(/AppleWebKit/), // 是否为移动终端
ios: !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // IOS终端
android: ua.indexOf('Android') > -1, // 安卓终端
iPhone: ua.indexOf('iPhone') > -1, // 是否为iphone或QQHD浏览器
iPad: ua.indexOf('iPad') > -1, // 是否为iPad
webApp: ua.indexOf('Safari') == -1, // 是否web应用程序,没有头部与底部
QQbrw: ua.indexOf('MQQBrowser') > -1, // QQ浏览器(手机上的)
weiXin: ua.indexOf('MicroMessenger') > -1, // 微信
QQ: ualower.match(/\sQQ/i) == " qq", // QQ App内置浏览器(需要配合使用)
weiBo: ualower.match(/WeiBo/i) == "weibo", // 微博
ucLowEnd: ua.indexOf('UCWEB7.') > -1, //
ucSpecial: ua.indexOf('rv:1.2.3.4') > -1,
webview: !(ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/)) && ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/),
ucweb: function () {
try {
return parseFloat(ua.match(/ucweb\d+\.\d+/gi).toString().match(/\d+\.\d+/).toString()) >= 8.2
} catch (e) {
if (ua.indexOf('UC') > -1) {
return true;
}
return false;
}
}(),
Symbian: ua.indexOf('Symbian') > -1,
ucSB: ua.indexOf('Firofox/1.') > -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
# import和require区别
//index.tsx
console.log(1)
import {sum} from "./sum"
console.log(sum(1,2))
//sum.tsx
console.log(2)
export const sum=(a,b)=>a+b;
//result: 2,1,3
//import命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行
//如果是require()结果会依次打印出来,1,2,3
2
3
4
5
6
7
8
9
10
11
12
13
# 检测数据类型
# typeof
//typeof用以获取一个变量或者表达式的类型,typeof一般只能返回如下几个结果:
number,boolean,string,function(函数),symbol,object(NULL,数组,对象),undefined。
2
# instanceof
[1, 2, 3] instanceof Array; //true
//可以看到[1, 2, 3]是类型Array的实例
[1, 2, 3] instanceof Object; //true
//封装
function myInstanceof(val, type) {
let rightProto = type.prototype; // 取右边 prototype的值
let leftPrevProto = val.__proto__; // 取左边__proto__值
while (true) {
if (leftPrevProto === null) { //如果左边的__proto__值为null,返回false
return false;
}
if (leftPrevProto === rightProto) {
return true;
}
leftPrevProto = leftPrevProto.__proto__ ; //以上都不满足,取上一层原型继续循环,直到没取到为null
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# constructor
1:null 和 undefined 无constructor,这种方法判断不了。
2:还有,如果自定义对象,开发者重写prototype之后,原有的constructor会丢失,因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。
''.constructor===String
new Number(111).constructor===Number
(11).constructor===Number
false.constructor===Boolean
new Date().constructor===Date
new Function().constructor===Function
[].constructor===Array
class Chameleon {
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
Chameleon.constructor===Function
new Chameleon().constructor.constructor===Function
Object.prototype.toString.call(new Chameleon().constructor) //"[object Function]"
new ew Chameleon().constructor===Function //false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Object.prototype.toString.call()
function is(type, obj) {
var clas = Object.prototype.toString.call(Object(obj)).slice(8, -1); //Object(null)
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true
2
3
4
5
6
7
# HTML页面加载完毕后运行JS
window.onload=function(){} // 可以判断js时候加载完毕
$(function(){});
$(document).ready(function(){})
// 当dom加载完就可以执行(比window.onload更早)
<body onload="fn()"></body>
// 最晚执行
2
3
4
5
6
7
8
两者的主要区别
当一个文档完全下载到浏览器中时,才会触发window.onload
事件
$(document).ready{ }是在DOM完全就绪并可以使用时调用,此时可能图片等还可能没有下载完成
# 解释 script 标签的加载和执行顺序
在没有使用 defer 或 async 属性的情况下,<script> 标签是按它们在 HTML 文档中出现的顺序从上到下依次加载和执行的。这个过程是同步的,即在一个脚本加载和执行完成之前,浏览器不会继续解析和渲染后续的 HTML 内容。
默认行为(不带 defer 或 async)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="script1.js"></script>
<script src="script2.js"></script>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
在这个示例中:
浏览器加载 script1.js,并执行其中的代码。
在 script1.js 加载和执行完成之前,浏览器不会加载或执行 script2.js,也不会继续解析 HTML。
加载并执行完 script1.js 后,浏览器才会加载和执行 script2.js。
完成所有脚本的加载和执行后,浏览器继续解析剩余的 HTML。
# 使用 defer
带有 defer 属性的 <script> 标签会按顺序异步加载,但在文档解析完成后才会按顺序执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
在这个示例中:
script1.js 和 script2.js 会同时异步加载,不会阻塞 HTML 的解析。
HTML 文档解析完成后,脚本会按照它们在文档中的顺序执行。
# 使用 async
带有 async 属性的 <script> 标签会异步加载和执行,不保证执行顺序。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="script1.js" async></script>
<script src="script2.js" async></script>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
在这个示例中:
script1.js 和 script2.js 会同时异步加载,不会阻塞 HTML 的解析。
每个脚本在加载完成后立即执行,执行顺序不一定与它们在文档中的顺序一致。
# 总结
默认(无 defer 或 async):按顺序加载和执行,阻塞 HTML 解析。
defer:按顺序异步加载,文档解析完成后按顺序执行。
async:异步加载,加载完成后立即执行,不保证执行顺序
# JS 动态加载 JS
// script 加载方式
function reloadJSFn(id, newJS) {
let oldjs = document.getElementById(id);
if (document.getElementById(id)) oldjs.parentNode.removeChild(oldjs);
let scriptObj = document.createElement('script');
scriptObj.src = newJS;
scriptObj.type = 'text/javascript';
scriptObj.id = id;
document.getElementsByTagName('head')[0].appendChild(scriptObj);
}
// fetch 直接请求
fetch('https://gcore.jsdelivr.net/gh/hzfvictory/cdn@master/water-mark/index.js').then(e => e.text()).then((res) => {
(0, eval)(res);
waterMark('hezhenfeng',null,'app')
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Symbol
//Symbol 是基本数据类型
const Age=Symbol();
typeof Age === 'symbol'
2
3
4
5
//属性名 为不可枚举属性
let obj = {
[Symbol('name')]: '一斤代码',
age: 18,
title: 'Engineer'
}
Object.keys(obj) // ['age', 'title']
for (let p in obj) {
console.log(p) // 分别会输出:'age' 和 'title'
}
Object.getOwnPropertyNames(obj) // ['age', 'title']
JSON.stringify(obj) // {"age":18,"title":"Engineer"}
//一些专门针对Symbol的API
// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]
// 使用新增的反射API
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//使用Symbol代替REDUX的常量
// const TYPE_AUDIO = 'AUDIO'
// const TYPE_VIDEO = 'VIDEO'
// const TYPE_IMAGE = 'IMAGE'
const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()
2
3
4
5
6
7
8
# 小东西
- 显示版本号
const pkg = require('../package.json');
window.mmPlayer = window.mmplayer = `欢迎使用
当前版本为:V${pkg.version}
作者:${pkg.auter}`
console.info(`%c${window.mmPlayer}`, `color:blue`);
2
3
4
5
- 耗时监控
console.time(111)
Array(1000000).keys()
console.timeEnd(111) //111: 8.5390625ms
2
3
4
- table
console.table()
清空控制台历史记录
- 在控制台
右键
,或者按下Ctrl 并单击鼠标
,选择 Clear Console。 - 在脚本窗口输入
clear()
执行。 - 使用快捷键
command + K
- 在控制台
切换主题
Chrome 提供了 亮&暗 两种主题,当你视觉疲劳的时候,可以 switch 哦, 快捷键 command+shift+p
,打开 Command Menu,输入 theme
,即可选择切换
# Think 循环
<if condition="$Think.get.edit == 1 OR $Think.get.judge == 1">disabled</if>
<?php if($_GET['id'] == $parter['id']){ echo "selected";}?>
<?php dump ($vo)?>
<volist name="areas" id="vo"></volist>
2
3
4
5
6
# js文件压缩原因和压缩原理
压缩: 删除 Javascript 代码中所有注释、跳格符号、换行符号及无用的空格,从而压缩 JS 文件大小。
混淆: 经过编码将变量和函数原命名改为毫无意义的命名,以防止他人窥视和窃取 Javascript 源代码。
javascript文件压缩的原理
第一个当然就是去掉注释了。
另外就是跟CSS压缩相同的去掉换行符,空格什么的。
JAVASCRIPT中有几种变量形式,如变量,函数名,函数的参数等,通常我们在手写JS代码的时候,为了便于理解,我们都会给这些变量名以直观易懂的字符串,如:var title=”javascript”;这个习惯是值得推崇的。
但是,这些变量对于用户理解有帮助,对于计算机却没什么影响,如果我们把前面的句子变成:var a=”javascript”;对电脑来讲是一样的。
通常深度压缩JS都必须要做的一步就是尽量地缩短变量名,因为一份体积巨大的JS代码,其中的变量名会占去不少空间。
26个单字母,几乎就可以把一个函数中所有的参数都写完,所以我们经常在压缩版的JS代码中发现a,b,c,d之类的连续变量。
另外,Javascript有个特性就是不同作用域的变量名可以任意重复,所以此函数中有a,b,c,d,其他函数也可以有。这样短又大量重复的变量可以让人索云里雾里不知所云,也变相的起到了加密JS代码的作用.
注意
- 压缩前的代码格式要标准。因为去掉换行与空格时,所有语句就变成一行了,如果你的代码有瑕疵(比如某行少了个分号),那就会导致整个文件报错。当然,现在有的压缩工具已经比较智能了。
- 备份原文件
- 压缩很可能不会一次成功,一般要多试,多改
# window.performance API
允许网页访问某些函数来测量网页和Web应用程序的性能,包括 Navigation Timing API和高分辨率时间数据
function getPerformanceTiming () {
var performance = window.performance;
if (!performance) {
// 当前浏览器不支持
console.log('你的浏览器不支持 performance 接口');
return;
}
var t = performance.timing;
var times = {};
//【重要】页面加载完成的时间
//【原因】这几乎代表了用户等待页面可用的时间
times.loadPage = t.loadEventEnd - t.navigationStart;
//【重要】解析 DOM 树结构的时间
//【原因】反省下你的 DOM 树嵌套是不是太多了!
times.domReady = t.domComplete - t.responseEnd;
//【重要】重定向的时间
//【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
times.redirect = t.redirectEnd - t.redirectStart;
//【重要】DNS 查询时间
//【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
// 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
//【重要】读取页面第一个字节的时间
//【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
// TTFB 即 Time To First Byte 的意思
// 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
times.ttfb = t.responseStart - t.navigationStart;
//【重要】内容加载完成的时间
//【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
times.request = t.responseEnd - t.requestStart;
//【重要】执行 onload 回调函数的时间
//【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
times.loadEvent = t.loadEventEnd - t.loadEventStart;
// DNS 缓存时间
times.appcache = t.domainLookupStart - t.fetchStart;
// 卸载页面的时间
times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;
// TCP 建立连接完成握手的时间
times.connect = t.connectEnd - t.connectStart;
return times;
}
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
52
53
54
计算性能指标
可以使用Navigation.timing 统计到的时间数据来计算一些页面性能指标,比如DNS查询耗时、白屏时间、domready等等。如下:
DNS查询耗时 = domainLookupEnd - domainLookupStart
TCP链接耗时 = connectEnd - connectStart
request请求耗时 = responseEnd - responseStart
解析dom树耗时 = domComplete - domInteractive
白屏时间 = domloadng - fetchStart
domready时间 = domContentLoadedEventEnd - fetchStart
onload时间 = loadEventEnd - fetchStart
# 读取注释
function eachComment(ele) {
let child = ele.childNodes[0];
if (child.nodeType === 8) {
console.log(child.nodeValue);
}
}
let bodyElement = document.getElementsByTagName("body")[0];
eachComment(bodyElement);
2
3
4
5
6
7
8
9