1、带有多的条件的if的语句 逻辑 || 的简写
if (x == 'true' || x == '2523' || x == '小明') {}
// 简化操作
if (['true', '2523', '小明'].includes(x)) {}
2、查找两个数组的交集
var numOne = [0, 2, 4, 6, 8, 8];
var numTwo = [1, 2, 3, 4, 5, 6];
var cross= [...new Set(numOne)].filter(item => numTwo.includes(item));
console.log(cross); // returns [2, 4, 6]
题外,
并集,
const union=Array.from(new Set([...arr1,...arr2]))
Array.from不出现重复项
差集,
const diff=Array.from(new Set(union.filter(item => !cross.includes(item))))
3、快速转类型
- 转Number(+变量,或者以下)
["1", "2"].map(Number)
4、短路运算
短路&&: 只要碰到了假值(false),就会短路,并返回该假值, 只要短路,不会继续执行后面的表达式。
短路||: 只要碰到了真值(true),就会短路,并返回该真值, 只要短路,不会继续执行后面的表达式。(一般用作默认值)
&&
//判断this里是否有这个的值,存在为true,执行后面的
let { phone } = this;
phone && (await this.$store.dispatch("getCode", phone));
||则是前面值假,执行后面的
5、定时器立即执行
原本是在setInterval函数前写一遍要执行的js代码
getData();
timer=setInterval(getData,1000); // 启动定时器,1s一次
但可以
getData(){
return getData;
};
timer=setInterval(getData(),1000); // 定时器中,函数先执行一次
总结
setInterval(fn(),1000) 加括号立即执行一次
setInterval(fn,1000) 不加括号,1秒后执行,且循环执行
6、多分支(if…else…)优化
条件一样,分支不一样
条件不一样,分支也不一样
使用元组
在两中心项目里应用了一下,感觉非常厉害
7、监听元素重叠度
const ob = new IntersectionObserver((entries) => {
console.log(entries)
}, {
root: null, // 要观察的元素与谁交叉, 默认值null视口
rootMargin: '10px', // 拓宽交叉范围 默认值 0
threshold: 0, // 交叉阈值 取值 0-1,进入多少
})
打印 entries 得到一个数组,
数组里的每一项中的 isIntersecting: true 表示该元素与设置的目标元素有交叉
target 是监听的 dom 卸载时可使用 observer.unobserve(entry.target)
8、initial(不能继承的设置默认值)、inherit(继承)、unset(该继承继承,没有继承设置默认值,就是initial和inherit结合体)和revert(恢复默认样式)
清除样式只需要
9、v-model
语法糖,v-model等价于 给一个input框提供了 :value属性以及 @input事件
@后面的事件可以根据父传子自定义,思维定式了
10、高阶函数
函数作为参数,返回一个参数就是高阶函数,比如map就是高阶函数
应用,防抖,并发队列,参数归一化,惰性函数
11、并发队列/并发请求
11.1、并发队列
function paralleTask(tasks, parallelCount = 2) {
//tasks:异步请求函数队列;parallelCount:最多同时运行几个任务
return new Promise((resolve, reject) => {
// 表示没有任务情况
if (tasks.length === 0) {
resolve();
return;
}
let nextIndex = 0;
let finishCount = 0;
function _run() {
// 运行下一次任务
const task = tasks[nextIndex]; //获取当前异步函数
nextIndex++;
task().then(() => {
finishCount++;
// 运行下一个
if (nextIndex < tasks.length) {
_run();
} else if (finishCount === tasks.length) {
//如果当前请求完成数量和异步请求队列数量一致,则表示所有请求完毕
// 任务完成
resolve();
}
});
}
for (let i = 0; i < parallelCount && i < tasks.length; i++) {
// 执行几次任务
_run();
}
});
}
export default paralleTask;
import paralleTask from "./paralleTask";
function mockTasks(num) {
//模拟请求多个异步任务(可替换成请求api)
let asyncTasks = [];
for (let i = 0; i < num; i++) {
let task = async () => {
// 在这里编写异步任务的逻辑
return i + 1;
};
asyncTasks.push(task);
}
return asyncTasks;
}
const tasks = mockTasks(100);
paralleTask(tasks, 4).then(() => { //tasks异步请求队列 4:表示并发请求数量
console.log("完成全部的并发异步请求");
});
11.2、并发请求
https://blog.csdn.net/weixin_46533577/article/details/133855741
/**
* 并发请求
* @param {string[]} url 待请求的 url 数组
* @param {number} maxNum 最大并发数
*/
function concurRequest(urls, maxNum) {
return new Promise((resolve, reject) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求下标
let count = 0; // 请求完成数
// 发送请求
async function request() {
// 当index等于数组的长度表示完成
if (index === urls.length) return;
const i = index;
const url = urls[index];
index++;
try {
const response = (await fetch(url)).json();
results[i] = await response;
} catch (error) {
console.log(error.message);
} finally {
count++;
// 判断所有请求是否都完成
if (count === urls.length) {
resolve(results);
}
request();
}
console.log(results);
}
const times = Math.min(maxNum, urls.length);
for (let i = 0; i < times; i++) {
request();
}
});
}
const urls = [];
for (let i = 1; i <= 30; i++) {
const url = `https://jsonplaceholder.typicode.com/todos/${i}`;
urls.push(url);
}
concurRequest(urls, 6).then((response) => {
console.log(response);
});
12、watchEffect中的异步问题
https://www.douyin.com/user/self?modal_id=7327589377025084712
document.getElementById("myVideo").playbackRate = 0.5;
watchEffect会把watchEffect里面的函数调用一次,监听A的执行期间用到的响应式数据,假如响应式数据变化,则会重新触发函数,可以收集依赖
图中代码是异步函数,在等待,只运行了fetchVideoURL()同步函数,运行完结束,并不会真正等待。watchEffect没有收集到playbackRate 这一行的依赖。
两行代码反着写也可以,或者fetchVideoURL()这行外面提出去立即执行函数
13、拼音
<ruby>
汉
<rp>(</rp>
<rt>hàn</rt>
<rp>)</rp>
</ruby>
不支持html5的会汉(hàn)
利用pinyin这个第三方库
拼音(‘中’)
存在文件大的情况,懒加载↓或者node服务器去加载
14、vmin和vmax
视口最短边
15、console importer
https://crxdl.com/#google_vignette
https://github.com/pd4d10/console-importer?tab=readme-ov-file
$i('axios');
在知乎页面上加载了一下,报错了,不知道前端怎样做到的
16、原生弹窗
<dialog open id="dog"></dialog>
// 打开弹窗
dog.show()
// 打开关闭
dog.close()
模态窗口
dog.showModal()
对话框独立渲染,是系统级别的
dialog 的打开和关闭方法很迷惑,分别是show() / close()
dialog中表单元素在添加method=dialog属性之后,只要触发表单提交就会自动关闭弹窗,无需额外 js
通过showModal()可以打开模态弹窗,并且后打开的弹窗永远比先打开的弹窗层级要高,无需手动计算层级
打开弹窗会自动聚焦到弹窗内的第一个可聚焦元素,方便快速输入 模态弹窗的焦点不会聚焦到弹窗外部,这和inert特性比较类似
模态弹窗其实是渲染到了html文档流之外,一个叫#top-layer的层级上,因此不会受到原父容器的影响
17、监控页面卡顿
数据埋点
首先计算FPS,requestAnimationFrame()假如两帧间隔超过16.6ms,但是用户那边的卡顿不一定代表FPS,比方说有事件延迟(点了之后没反应),FID(首次输入延迟),布局偏移(其他东西加载出来,把它挤下去)
考虑新的观察器
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);//可以根据duration判断卡顿
}
});
observer.observe({ entryTypes: ['paint', 'measure','longtask'] });
//longtask长任务
18、clipboardAPI
https://blog.csdn.net/yuanhongkun/article/details/130487724
1、图片剪切到页面上,
<div class="editor"></div>
const editor = document.querySelector('.editor')
document.addEventListener('paste', (e) => {
if (e.clipboardData.files.length > 0) {
event.preventDefault();
// 获取剪贴板中的项
const clipboardItems = e.clipboardData.items;
// 遍历剪贴板中的项
for (let i = 0; i < clipboardItems.length; i++) {
const item = clipboardItems[i];
// 检查项是否为图片类型
if (item.type.indexOf('image') !== -1) {
const blob = item.getAsFile();
// 处理图片数据,例如显示或上传
// 你可以使用 FileReader 来读取图片数据,然后在页面中显示它
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement("img");
// 在页面中显示图片或执行其他操作
img.src = e.target.result
console.log(e.target.result, '?')
editor.appendChild(img)
};
reader.readAsDataURL(blob);
} else if (item.type === 'text/plain') {
// 处理文本项目
item.getAsString(function (text) {
// ...
});
} else {
// 处理其他类型的项目 // ...
}
}
}
});
2、复制文字复制成别的文字,
<div class="editor"></div>
navigator.clipboard.readText().then((text)=>{
document.querySelector('.editor').innerHTML=text
})
3、抖音、淘宝文字分享链接读取
剪切板是操作系统级的内存空间,提供两个方法,读写
20、width: fit-content
宽度适应内容
21、放弃nvm使用volta
它可以A项目运行14,b项目运行16,同时可以运行。nvm切换版本是全局切换的,一次只能跑一个版本的项目。
22、WeakSet
阮一峰 https://es6.ruanyifeng.com/#docs/set-map#WeakSet
https://juejin.cn/post/7041574685363945479
23、使用defer优化白屏时间
官网首页头部轮播的图片过大, 导致页面加载过慢, 或是先加载出中部和底部部分, 页面出现次序十分不雅观, 故可自定义一个方法, 使页面组件逐帧加载
在untils.ts文件中新建方法:
import { ref,onUnmounted } from 'vue
// 给 maxCount一个最大值, 防止frameCount.value无限递增
export const useDefer = (maxCount = 4) => {
const frameCount = ref(0); // 此处使用ref, 使 frameCount 为响应式值
let rafId;
function updateFrameCount() {
rafId = requestAnimationFrame(() => {
frameCount.value++;
if (frameCount.value >= maxCount) {
return;
}
updateFrameCount();
});
}
updateFrameCount();
//销毁
onUnmounted(() => {
cancelAnimationFrame(rafId);
});
return function defer(n: number) {
return frameCount.value >= n;
};
};
import { useDefer } from '@/utils/utils';
const defer = useDefer();
//可定义在第几帧加载
<Banner v-if="defer(2)" />
24、浏览器防截屏录屏
Encrypted Media Extensions
25、行盒截断样式
box-decoration-break:clone;//默认是slice
26、字典顺序和编码顺序
names.sort((a,b)=>a.localeCompare(b))
27、如何实现高度自动过渡
max-height不行,有一个局限性,高度差异越大,过渡效果越糟糕,假设元素真实高度只有 100px,如果 max-height为800px,那只有前1/8有动画
grid-template-rows有Safari兼容性问题,
.grid {
position: absolute;
margin-top: 10px;
max-width: 250px;
display: grid;
grid-template-rows: 0fr;
transition: 0.3s;
overflow: hidden;
background-color: rgba(0, 0, 0, 0.65);
color: #fff;
}
.grid>* {
min-height: 0;
padding: 0 10px;
}
.wrap:hover .grid {
grid-template-rows: 1fr;
}
</style>
</head>
<body>
<div class="wrap">
<button class="trigger">鼠标放上来试试</button>
<div class="grid">
<div>
<p>
高度自动过渡,这里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧,比如这篇文章,如何让
</p>
</div>
</div>
</div>
最优解是js
28、链式调用和延迟执行
实现 arrange 函数
arrange('LBJhui').wait(5).do('commit').waitFirst(3).execute()
// 等待 3 秒
// > LBJhui is notified
// 等待 5 秒
// > Start to commit
function arrange(name) {
const tasks = []
tasks.push(() => {
console.log(`${name} is notified`)
})
function wait(time) {
tasks.push(
() =>
new Promise((resolve) => {
setTimeout(resolve, time * 1000)
})
)
return this//就是 {wait,do: doSomething,waitFirst,execute,}
}
function doSomething(taskName) {
tasks.push(() => {
console.log(`Start to ${taskName}`)
})
return this
}
function waitFirst(time) {
tasks.unshift(
() =>
new Promise((resolve) => {
setTimeout(resolve, time * 1000)
})
)
return this
}
async function execute() {
for (const t of tasks) {
await t()
}
return this
}
return {
wait,
do: doSomething,
waitFirst,
execute,
}
}
29、标签页间通信
29.1原生自带功能
29.2标签间通信
30、bfc
31、pc端字体突破12px限制
transform:scale(0.66);//对应比例,只对块盒和行块盒有效
transform-origin:left center;
32、码点和码元
const string = "😭🦐"
console.log(string.length); // 4
每一个字符在内存空间里占16位,就是两字节,就是码元,0-2^16 这个范围代表字符,但是一些表情、文字可能不够用,所以就会用到两个码元,所以就有2^32,这个范围,叫做码点
length读的是码元的数量
String.prototype.pointLength = function () {
let len = 0;
for (let i = 0; i < this.length; ) {
len++;
const t = this.codePointAt(i);//'a'.codePointAt(0)<65535//0xffff
i += t > 0xffff ? 2 : 1;
}
return len;
};
在字符串原型上添加pointAt方法,用于获取字符串中指定码点索引的字符。
String.prototype.pointAt = function (index) {
let currentIndex = 0;
for (let i = 0; i < this.length; ) {
if (currentIndex === index) {
const code = this.codePointAt(i);
return String.fromCodePoint(code);//转数字
}
currentIndex++;
const point = this.codePointAt(i);
i += point > 0xffff ? 2 : 1;
}
};
在字符串原型上添加pointSlice方法,用于截取指定码点索引的字符。
String.prototype.pointSlice = function (start, end) {
let result = "";
for (let i = start; i < this.pointLength() && i < end; i++) {
result += this.pointAt(i);
}
return result;
};
33、函数的length属性
不包括剩余参数,计算默认赋值前的个数
34、动态执行JS
1、eval同步执行,当前运行的作用域
2、setTimeout异步执行,全局作用域
3、创建script,同步,全局作用域,缺陷会创建元素
4、Function,同步,全局作用域
35、自定义指令v-loading
Vue.directive()
第一个参数是指令的名字(不需要写上v-前缀)
第二个参数可以是对象数据,也可以是一个指令函数
自定义指令有两种方法全局自定义指令与局部自定义指令
- 全局自定义指令 新建src/directives/Focus.js文件
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
- 在main.js中全局引入
import Focus from '@/directive/Focus.js'
Vue.use(Focus)
- 局部指令的定义方法 在组件内部
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
- 你在模板中可以这样应用
<input v-focus>
钩子函数,前三个常用
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。数据变化时改变
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用
所有钩子函数都有以下参数
- el:指令所绑定的元素,可以用来直接操作 DOM
- binding:一个对象,包含以下 property:
`name`:指令名,不包括 v- 前缀。
`value`:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
`oldValue`:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
`expression`:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
`arg`:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
`modifiers`:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
`vnode`:Vue 编译生成的虚拟节点
`oldVnode`:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
配置简化
防抖例子
// 1.设置v-throttle自定义指令
Vue.directive('throttle', {
bind: (el, binding) => {
let setTime = binding.value; // 防抖时间
if (!setTime) { // 用户若不设置防抖时间,则默认2s
setTime = 2000;
}
let cbFun;
el.addEventListener('click', event => {
if (!cbFun) { // 第一次执行
cbFun = setTimeout(() => {
cbFun = null;
}, throttleTime);
} else {
event && event.stopImmediatePropagation();
}
}, true);
},
});
// 2.为button标签设置v-throttle自定义指令
<button @click="sayHello" v-throttle>提交</button>
loading例子
36、Reflect的本质
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
直接调用对象的基本方法,比如[[set]]
const obj={}
obj.a=1//[set]]
//等同于
Reflect.set(obj,'a',a)
obj.a//[[get]]
什么区别呢?
还有额外步骤,比方说读obj.a的时候,有可能经过一个函数,
下面receiver是啥,this就是啥,首先obj会被定义为this,再去调get方法,
输出7
接下来我们再扩展一下,
第一个例子
直接把原始对象c返回,this指向obj,读target里面的ab收不到任何通知,
应该这么改
第二个例子
const obj={
a:1,
b:2
}
const keys=Object.keys(obj)//会调用getOwnProperty
37、跨标签页通信
- BroadCast Channel
- Service Worker
- LocalStorage window.onstorage 监听
- Shared Worker 定时器轮询( setInterval )
- IndexedDB 定时器轮询( setInterval )
- cookie 定时器轮询( setInterval )
- window.open、window.postMessage
- Websocket
应用,播放音乐,可以用window.open第二个参数写名字,但是会刷新页面
37.1、BroadCast Channel
BroadCast Channel 可以帮我们创建一个用于广播的通信频道。当所有页面都监听同一频道的消息时,其中某一个页面通过它发送的消息就会被其他所有页面收到。但是前提是同源页面。
index.html
<body>
<input type="text" name="" id="content">
<button id="btn">发送数据</button>
<script>
const content = document.querySelector("#content");
const btn = document.querySelector("#btn");
// 创建一个名字是 load1 的 BroadcastChannel 对象
var BroadcastChanne1 = new BroadcastChannel('load1');
btn.onclick = function () {
BroadcastChanne1.postMessage({
value: content.value
});
}
</script>
</body>
index2.html
<body>
<script>
var BroadcastChanne1 = new BroadcastChannel('load1');//要接收到数据,BroadcastChannel对象的名字必须相同
BroadcastChanne1.onmessage = function (e) {
console.log(e.data);//发送的数据
};
</script>
</body>
在上面的代码中,我们在页面一注册了一个名为 load1 的 BroadcastChannel 对象,之后所有的页面也创建同名的 BroadcastChannel 对象,然后就可以通过 postMessage 和 onmessage 方法进行相互通信了。
37.2、Service Worker
Service Worker 实际上是浏览器和服务器之间的代理服务器,它最大的特点是在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。
Service Worker 的目的在于离线缓存,转发请求和网络代理。
index.html
<body>
<h1>页面一</h1>
<button>发送</button>
<script>
navigator.serviceWorker.register('sw.js')
.then(() => {
console.log("service worker 注册成功");
});
document.querySelector("button").onclick = function () {
navigator.serviceWorker.controller.postMessage('hello');
}
</script>
</body>
index2.html
<body>
<h1>页面二</h1>
<script>
navigator.serviceWorker.register('sw.js')
.then(() => {
console.log("service worker 注册成功");
});
navigator.serviceWorker.onmessage = function ({ data }) {
console.log(data);
}
</script>
</body>
sw.js
self.addEventListener("message",async event=>{
const clients = await self.clients.matchAll();
clients.forEach(function(client){
client.postMessage(event.data)
});
});
37.3、LocalStorage window.onstorage 监听
在 Web Storage 中,每次将一个值存储到本地存储时,就会触发一个 storage 事件。
由事件监听器发送给回调函数的事件对象有几个自动填充的属性如下:
-
key:告诉我们被修改的条目的键。
-
newValue:告诉我们被修改后的新值。
-
oldValue:告诉我们修改前的值。
-
storageArea:指向事件监听对应的 Storage 对象。
-
url:原始触发 storage 事件的那个网页的地址。
注意:这个事件只在同一域下的任何窗口或者标签上触发,并且只在被存储的条目改变时触发。
示例如下:这里我们需要打开服务器进行演示,本地文件无法触发 storage 事件
index.html
<body>
<script>
localStorage.name = "谢杰";
localStorage.age = 20;
console.log("信息已经设置!");
</script>
</body>
在上面的代码中,我们在该页面下设置了两个 localStorage 本地数据。
index2.html
<body>
<script>
let name = localStorage.getItem("name");
let age = localStorage.age;
console.log(`姓名为${name},年龄为${age}`);
window.addEventListener("storage", (e) => {
console.log(`${e.key}从${e.oldValue}修改为${e.newValue}`);
console.log(e.storageArea);
console.log(`被改变的url为${e.url}`);
}, true);
</script>
</body>
在该页面中我们安装了一个 storage 的事件监听器,安装之后只要是同一域下面的其他 storage 值发生改变,该页面下面的 storage 事件就会被触发。
37.4、Shared Worker 定时器轮询( setInterval )
下面是 MDN 关于 SharedWorker 的说明:
SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域,如果要使 SharedWorker 连接到多个不同的页面,这些页面必须是同源的(相同的协议、host 以及端口)。
index.html
<body>
<input type="text" name="" id="content" placeholder="请输入要发送的信息">
<button id="btn">发送</button>
<script>
const content = document.querySelector("#content");
const btn = document.querySelector("#btn");
const worker = new SharedWorker('worker.js')
btn.onclick = function () {
worker.port.postMessage(content.value);
}
</script>
</body>
index2.html
<body>
<script>
const btn = document.querySelector("#btn");
var worker = new SharedWorker('worker.js');
worker.port.start()
worker.port.addEventListener('message', (e) => {
if(e.data){
console.log('来自worker的数据:', e.data)
}
}, false);
setInterval(function(){
// 获取和发送消息都是调用 postMessage 方法,我这里约定的是传递'get'表示获取数据。
worker.port.postMessage('get')
},1000);
</script>
</body>
worker.js
var data = '';
onconnect = function (e) {
var port = e.ports[0]
port.onmessage = function (e) {
// 如果是 get 则返回数据给客户端
if (e.data === 'get') {
port.postMessage(data);
data = "";
} else {
// 否则把数据保存
data = e.data
}
}
}
37.5、IndexedDB 定时器轮询( setInterval )
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。
通过对 IndexedDB 进行定时器轮询的方式,我们也能够实现跨标签页的通信。
index.html
<body>
<h1>新增学生</h1>
<div>
<span>学生学号:</span>
<input type="text" name="stuId" id="stuId">
</div>
<div>
<span>学生姓名:</span>
<input type="text" name="stuName" id="stuName">
</div>
<div>
<span>学生年龄:</span>
<input type="text" name="stuAge" id="stuAge">
</div>
<button id="addBtn">新增学生</button>
<script src="./db.js"></script>
<script>
openDB('stuDB', 1)
.then((db) => {
document.getElementById("addBtn").onclick = function () {
addData(db, "stu", { "stuId": stuId.value, "stuName": stuName.value, "stuAge": stuAge.value });
stuId.value = stuName.value = stuAge.value = "";
}
})
</script>
</body>
index2.html
<body>
<h1>学生表</h1>
<table id="tab"></table>
<script src="./db.js"></script>
<script>
function render(arr) {
let tab = document.querySelector("#tab");
tab.innerHTML = `
<tr>
<td>学号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
`;
var str = arr.map((item) => {
return `
<tr>
<td>${item.stuId}</td>
<td>${item.stuName}</td>
<td>${item.stuAge}</td>
</tr>
`
}).join("");
tab.innerHTML += str;
}
async function renderTable() {
let db = await openDB('stuDB', 1);
let stuInfo = await getDataByKey(db, "stu");
render(stuInfo);
setInterval(async function () {
let stuInfo2 = await getDataByKey(db, "stu");
if (stuInfo2.length !== stuInfo.length) {
stuInfo = stuInfo2;
render(stuInfo);
}
}, 1000);
}
renderTable();
</script>
</body>
db.js
/**
* 打开数据库
* @param {object} dbName 数据库的名字
* @param {string} storeName 仓库名称
* @param {string} version 数据库的版本
* @return {object} 该函数会返回一个数据库实例
*/
function openDB(dbName, version = 1) {
return new Promise((resolve, reject) => {
var db; // 存储创建的数据库
// 打开数据库,若没有则会创建
const request = indexedDB.open(dbName, version);
// 数据库打开成功回调
request.onsuccess = function (event) {
db = event.target.result; // 存储数据库对象
console.log("数据库打开成功");
resolve(db);
};
// 数据库打开失败的回调
request.onerror = function (event) {
console.log("数据库打开报错");
};
// 数据库有更新时候的回调
request.onupgradeneeded = function (event) {
// 数据库创建或升级的时候会触发
console.log("onupgradeneeded");
db = event.target.result; // 存储数据库对象
var objectStore;
// 创建存储库
objectStore = db.createObjectStore("stu", {
keyPath: "stuId", // 这是主键
autoIncrement: true // 实现自增
});
// 创建索引,在后面查询数据的时候可以根据索引查
objectStore.createIndex("stuId", "stuId", { unique: true });
objectStore.createIndex("stuName", "stuName", { unique: false });
objectStore.createIndex("stuAge", "stuAge", { unique: false });
};
});
}
/**
* 新增数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} data 数据
*/
function addData(db, storeName, data) {
var request = db
.transaction([storeName], "readwrite") // 事务对象 指定表格名称和操作模式("只读"或"读写")
.objectStore(storeName) // 仓库对象
.add(data);
request.onsuccess = function (event) {
console.log("数据写入成功");
};
request.onerror = function (event) {
console.log("数据写入失败");
};
}
/**
* 通过主键读取数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} key 主键值
*/
function getDataByKey(db, storeName, key) {
return new Promise((resolve, reject) => {
var transaction = db.transaction([storeName]); // 事务
var objectStore = transaction.objectStore(storeName); // 仓库对象
var request = objectStore.getAll(); // 通过主键获取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
// console.log("主键查询结果: ", request.result);
resolve(request.result);
};
});
}
37.6、cookie 定时器轮询( setInterval )
我们同样可以通过定时器轮询的方式来监听 Cookie 的变化,从而达到一个多标签页通信的目的。
index.html
<body>
<script>
// 设置 cookie
document.cookie = "name=zhangsan";
console.log("cookie 已经设置");
</script>
</body>
index2.html
<body>
<script>
// 获取当前的 cookie
var cookie = document.cookie;
console.log(`当前的 cookie 值为 ${document.cookie}`);
setInterval(function(){
if(cookie !== document.cookie){
console.log(`cookie 信息已经改变,最新的 cookie 值为${document.cookie}`);
cookie = document.cookie;
console.log("最新的 cookie 值已经保存");
}
},1000);
</script>
</body>
在上面的代码中,我们为 index2.html 设置了一个定时器,之后每过一秒钟都会重新去读取本地的 Cookie 信息,并比较和之前获取到的 Cookie 信息有没有变化,如果有变化就进行更新操作。
37.7、window.open、window.postMessage
MDN 上是这样介绍 window.postMessage 的:
window.postMessage( ) 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage( ) 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
从广义上讲,一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage( ) 方法分发一个 MessageEvent 消息。接收消息的窗口可以根据需要自由处理此事件 (en-US)。传递给 window.postMessage( ) 的参数(比如 message )将通过消息事件对象暴露给接收消息的窗口。
index.html
<body>
<button id="popBtn">弹出新的窗口</button>
<input type="text" name="" id="content">
<button id="btn">发送数据</button>
<script>
const popBtn = document.querySelector('#popBtn');
const content = document.querySelector("#content");
const btn = document.querySelector("#btn");
let opener = null; // 保存打开窗口的引用
popBtn.onclick = function () {
opener = window.open("index2.html", "123", "height=400,width=400,top=10,resizable=yes");
}
btn.onclick = function () {
let data = {
value: content.value
}
// data 代表的是发送是数据,origin 用来限制访问来源,也可以用 * 代替
opener.postMessage(data, "*");
}
</script>
</body>
index2.html
<body>
<p>这是弹出页面</p>
<script>
window.addEventListener('message', function (e) {
console.log(e.data);
}, false);//事件监听
</script>
</body>
在上面的代码中,我们在页面一通过 open 方法打开页面二,然后通过 postMessage 的方式向页面二传递信息。页面二通过监听 message 事件来接收信息。
37.8、Websocket
WebSocket 协议在 2008 年诞生,2011 年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
server.js
// 初始化一个 node 项目 npm init -y
// 安装依赖 npm i -save ws
// 获得 WebSocketServer 类型
var WebSocketServer = require('ws').Server;
// 创建 WebSocketServer 对象实例,监听指定端口
var wss = new WebSocketServer({
port: 8080
});
// 创建保存所有已连接到服务器的客户端对象的数组
var clients = [];
// 为服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中
wss.on('connection', function (client) {
// 如果是首次连接
if (clients.indexOf(client) === -1) {
// 就将当前连接保存到数组备用
clients.push(client)
console.log("有" + clients.length + "客户端在线");
// 为每个 client 对象绑定 message 事件,当某个客户端发来消息时,自动触发
client.on('message', function (msg) {
console.log(msg, typeof msg);
console.log('收到消息' + msg)
// 遍历 clients 数组中每个其他客户端对象,并发送消息给其他客户端
for (var c of clients) {
// 排除自己这个客户端连接
if (c !== client) {
// 把消息发给别人
c.send(msg.toString());
}
}
});
// 当客户端断开连接时触发该事件
client.onclose = function () {
var index = clients.indexOf(this);
clients.splice(index, 1);
console.log("有" + clients.length + "客户端在线")
}
}
});
console.log("服务器已启动...");
在上面的代码中,我们创建了一个 Websocket 服务器,监听 8080 端口。每一个连接到该服务器的客户端,都会触发服务器的 connection 事件,并且会将此客户端连接实例作为回调函数的参数传入。
我们将所有的客户端连接实例保存到一个数组里面。为该实例绑定了 message 和 close 事件,当某个客户端发来消息时,自动触发 message 事件,然后遍历 clients 数组中每个其他客户端对象,并发送消息给其他客户端。
close 事件在客户端断开连接时会触发,我们要做的事情就是从数组中删除该连接。
index.html
<body>
<!-- 这个页面是用来发送信息的 -->
<input type="text" id="msg">
<button id="send">发送</button>
<script>
// 建立到服务端 webSoket 连接
var ws = new WebSocket("ws://localhost:8080");
send.onclick = function () {
// 如果 msg 输入框内容不是空的
if (msg.value.trim() != '') {
// 将 msg 输入框中的内容发送给服务器
ws.send(msg.value.trim())
}
}
// 断开 websoket 连接
window.onbeforeunload = function () {
ws.close()
}
</script>
</body>
index2.html
<body>
<script>
//建立到服务端webSoket连接
var ws = new WebSocket("ws://localhost:8080");
var count = 1;
ws.onopen = function (event) {
// 当有消息发过来时,就将消息放到显示元素上
ws.onmessage = function (event) {
var oP = document.createElement("p");
oP.innerHTML = `第${count}次接收到的消息:${event.data}`;
document.body.appendChild(oP);
count++;
}
}
// 断开 websoket 连接
window.onbeforeunload = function () {
ws.close()
}
</script>
</body>
38、动画的暂停与恢复问题
两种,鼠标放上去停止,和鼠标放上去运行
39、全局导入和局部导入的区别
不分包情况下,没有区别
分包情况下,全局导入,假如mainjs里面依赖某两个组件,会使得分包变大,导致白屏;局部导入,则相反
40、toFiexd是在toPrecision的基础上四舍五入的
41、网络状态监控
effectiveType值改变是第三个监听
42、自动检测更新
1、webSocket
2、轮询
npm i auto-update-html
import 'auto-update-html';
/*
* @Author: Jackie
* @Date: 2023-06-06 13:36:13
* @LastEditTime: 2023-06-06 13:46:54
* @LastEditors: Jackie
* @Description: 自动检测前端页面更新,提示用户刷新前端页面
* @FilePath: /auto-html/auto-update.js
* @version:
*/
let lastSrcs; //上一次获取script地址
const scriptReg = /\<script.*src=["'](?<src>[^"']+)/gm;
/**
* 获取最新页面中script链接
*/
async function extractNewScripts() {
const html = await fetch('/?_timestamp=' + Date.now()).then((resp) => resp.text());
console.log(html);
scriptReg.lastIndex = 0;
let result = [];
let match;
while ((match = scriptReg.exec(html))) {
result.push(match.groups.src);
}
return result;
}
async function needUpdate() {
const newScripts = await extractNewScripts();
console.log(newScripts);
if (!lastSrcs) {
lastSrcs = newScripts;
return false;
}
let result = false;
if (lastSrcs.length !== newScripts.length) {
return true;
}
for (let i = 0; i < lastSrcs.length; i++) {
if (lastSrcs[i] !== newScripts[i]) {
result = true;
break;
}
}
lastSrcs = newScripts;
return result;
}
const DURATION = 2000;
function autoRefresh() {
setTimeout(async () => {
const willUpdate = await needUpdate();
console.log(willUpdate);
if (willUpdate) {
const result = confirm("页面有更新,点击确定刷新页面");
if (result) {
location.reload();
}
}
autoRefresh();
}, DURATION);
}
autoRefresh();
43、自动播放
- 静音自动播放总是允许的。
- 在下列情况下允许使用声音自动播放:
a. 用户已经与此页面进行了交互(点击,tap,按下键盘等)。
b. 在该站点,用户的媒体参与指数阈值(MEI)达到可以自动播放音频的标准,这意味着用户以前播放带有声音的视频。
c.在移动设备上,用户将该网站添加到主屏幕(在浏览器中访问页面时将页面添加到主屏幕)或安装了PWA到主页。
d.顶部框架可以将自动播放权限授予其iframe以允许自动播放声音。
3.自动播放权限策略被应用于 iframe 或者其文档上,从而获得了自动播放的权限
bilibili自动播放是第二点的b
媒体参与指数(Media Engagement Index)(MEI)
MEI衡量个人在网站上消费媒体的倾向。
Chrome 目前的方法是访问每个来源的重要媒体播放事件的比率:
媒体消耗(音频/视频)必须大于7秒。
音频必须存在并取消静音。
视频选项卡处于活动状态。
视频大小(以像素为单位)必须大于200x140。
因此,Chrome会计算媒体参与度分数,该分数在定期播放媒体的网站上最高。足够高时,媒体播放只允许在桌面上自动播放。MEI是谷歌自动播放策略的一部分。它是一个算法,参考了媒体内容的持续时间、浏览器标签页是否活动、活动标签页视频的大小这一系列元素。不过也正因此,开发者难以在所有的网页上都测试这一算法的效果。
我的MEI位于chrome://media-engagement/内部页面
Uncaught: (in promise)DOMExcepion: play() failed because the user didn't interact with the document first.
1、
2、bilibili自动播放是方案2
ctx.state表示能否自动播放
44、data url的问题
上传图片返回url地址,再发送获取URL请求,再回显图片,这一段时间等待太长了,直接获取
45、插槽的本质
实际上传递过去的是对象,对象有三个属性
46、多媒体文本化
https://blog.csdn.net/Dream_Weave/article/details/138152491
47、数组的forEach方法
forEach里面push(2)为什么没有死循环?
forEach里面删除splice(i,1)为什么正常输出数组
- forEach因为在最开始调用时就已经确定了len,所以最大执行次数不会超过初始length
- forEach因为会在每次循环中先判断是否存在索引键,因此会在循环函数中进行当前数组删除操作后,导致某个循环中索引建不存在,因此不会执行某个回调,从而导致减少回调触发次数
- 尽量不要在forEach中进行原数组的操作,否则不如自己去用循环实现
48、Vue方法中属性丢失
输出false
vue的生命周期,在beforeCreate和Created之间包含了响应式处理(defineProperty)、提取(遍历组件配置VueComponent)
option.methods.m.bind(实例)
改为以下解决问题
49、用正则前瞻检查密码强度
50、使用CSS实现滚动吸附效果
父元素滚动方向x,吸附方式mandatory
子元素,吸附对齐方式start,always表示不跳过中间元素,不然会从第一个元素直接到最后元素