4 月记
简介
这是属于我 4 月份收集的一些记录的知识
base64 为什么比 png 大?
一般情况下,base64 编码后的字符串长度会比原始图片的大小大约为 4/3 左右。这是因为 base64 编码将 3 个字节转换为 4 个字符,同时可能会添加一些填充字符。 此外,图片在编码为 base64 格式时,通常会进行二进制转换,将每个像素的颜色信息转换为二进制数据,然后再将这些二进制数据编码为 base64 字符串。由于 base64 编码使用的是文本形式表示二进制数据,因此在编码过程中可能会引入一些额外的开销和填充,从而导致编码后的字符串比原始图片的大小更大。
自动生成 CHANGELOG ⭐️⭐️⭐️
CHANGELOG 会记录所有的 commit 信息并归类版本,可以快速跳转到该条 commit 记录,它能让你方便知道项目里哪个版本做了哪些功能有哪些 bug 等信息。也方便排查 bug,对于提交记录一目了然,不用一个一个去翻去查。 我们使用 standard-version 来实现自动生成 CHANGELOG
yarn add standard-version -D
package.json 增加命令
{
"scripts": {
"release": "standard-version"
}
}
当执行以下命令时会自动生成 CHANGELOG
yarn release
当然 standard-version 提供自定义配置不同类型对应显示文案,在根目录新建 .versionrc 文件
// .versionrc
{
"header": "# 更新日志 \n\n",
"types": [
{
"type": "feat",
"section": "✨ Features | 新功能",
"hidden": false
},
{
"type": "fix",
"section": "🐛 Bug Fixes | Bug 修复",
"hidden": false
},
{
"type": "init",
"section": "🎉 Init | 初始化",
"hidden": true
},
{
"type": "docs",
"section": "✏️ Documentation | 文档",
"hidden": false
},
{
"type": "style",
"section": "💄 Styles | 风格",
"hidden": true
},
{
"type": "refactor",
"section": "♻️ Code Refactoring | 代码重构",
"hidden": true
},
{
"type": "perf",
"section": "⚡ Performance Improvements | 性能优化",
"hidden": true
},
{
"type": "test",
"section": "✅ Tests | 测试",
"hidden": true
},
{
"type": "revert",
"section": "⏪ Revert | 回退",
"hidden": true
},
{
"type": "build",
"section": "📦 Build System | 打包构建",
"hidden": true
},
{
"type": "chore",
"section": "🚀 Chore | 构建/工程依赖/工具",
"hidden": true
},
{
"type": "ci",
"section": "👷 Continuous Integration | CI 配置",
"hidden": true
}
]
}
css 文字多行省略
.box {
width: 100px;
height: 100px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
/* 块容器中的内容限制为指定的行数 */
/* display 属性设置成 -webkit-box 或者 -webkit-inline-box 并且 box-orient 属性设置成 vertical时才有效果。 */
-webkit-line-clamp: 2;
}
this.$toast 这种方式是如何实现的?
vue2
- 首先创建一个 toast.vue 组件,然后定义好需要的属性,比如:type、content 等
- 然后使用 vue.extend 创建一个子类构造器,然后通过 new 创建一个实例
- 有了实例之后调用 $mount 方法,将组件挂载到页面上
- 然后可以将 toast 实例元素挂载通过 appendChild 添加到 document.body 上
- 将上面的过程封装成一个方法,然后将这个方法挂载到原型上
vue3
vue3 中,我们可以使用 createApp 创建一个应用实例,通过$mount 方法,将组件挂载到页面上 然后通过 app.config.globalProperties.xxx 添加一个全局属性,然后就可以直接通过 this.xxx 调用了
设备像素(dp)、css 像素(css px)、设备像素比(dpr) ⭐️⭐️⭐️
设备像素(dp)
设备像素(dp)是设备屏幕上显示的物理像素,它与设备屏幕的分辨率是一一对应的。
css 像素(css px)
css 像素是 Web 编程的概念,指的是 CSS 样式代码中使用的逻辑像素
设备像素比(dpr)
设备像素比(dpr)是设备屏幕上物理像素与 CSS 像素的比值,它表示设备屏幕的分辨率与 CSS 样式代码中设置的分辨率的比值。
在浏览器上 iPhone6/7/8 375px ,通过 window.devicePixelRatio 可以获取到 iPhone6/7/8 设备像素比为 2 所以一般开发都是使用二倍图来开发
异常捕获
try{
// 可能出错的代码
}catch(e){
// 处理try代码块中抛出的异常
// e 错误信息
throw // 抛出自定义异常
}finally{
// 无论什么情况都会执行代码块
}
判断可迭代对象
typeof obj[Symbol.iterator] === "function";
// 如果为true, 则是可迭代对象
// 如果为false, 则不是可迭代对象
Symbol 的元编程能力(改写语法本身)
let obj = {
a: "1",
[Symbol.toStringTag]: "vity",
};
console.log(Object.prototype.toString.call(obj)); // [object vity]
0.1 + 0.2 != 0.3 问题
0.1 + 0.2 != 0.3 的原因: 我们小数在转换成二进制的时候,结果是无限循环的,但是我们计算机存储的时候,存储的位数是有限,那么就必须做一些近似值的取舍,这样就导致了小数精度丢失了
解决方法:
先将小数乘以 10 的 n 次方倍,装换为整数,再将计算后的结果除以 10 的 n 次方倍
let a = 0.1;
let b = 0.2(a * 10 + b * 10) / 10 === 0.3; // true
String.prototype.replace()
replace 第二个参数是一个函数的问题
function render(template, data) {
// \为转义
let str = template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
console.log(match);
console.log(key);
/*
*match =>{{name}} 字符串中匹配到的子串
*key =>name 代表括号匹配的字符串 (重点:括号 -> \w+)
*/
return data[key];
});
return str;
}
let template = "我是{{name}},年龄{{age}},性别{{sex}}";
let data = {
name: "小明",
age: 18,
};
render(template, data); // 我是小明,年龄18,性别undefined
这里可以知道
- match 会匹配到字符串的子串
- key 会匹配括号里的内容
es5 实现 const
Object.defineProperty(window, "name", {
value: "vvv",
writable: false, // 控制可写属性
});
// 这样子相当于 const name = 'vvv'
window.name = "123";
window.name; // 'vvv'
有 1000 个 dom,需要更新其中的 100 个,如何操作才能减少 dom 的操作?
添加多个 dom 元素,可以把元素先 append 进 DocumentFragment 中去,最后再统一将 DocumentFragment 添加到页面中(文档碎片流
)
提醒: 不会进行多次重排和重绘, 只进行一次
const ul = document.createElement("ul");
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.innerHTML = `index: ${i}`;
// 正序插入
// fragment.appendChild(li)
// 倒序插入
fragment.insertBefore(li, fragment.childNodes[0]);
}
ul.appendChild(fragment);
webpack 中的 source map 是什么,生产环境怎么用?
source map 构建了处理前的代码和处理后的代码之间的桥梁,在打包之后也可以看到打包之前的代码,可以快速定位 bug]
隐式转换 ⭐️⭐️⭐️
特例
- null == undefined 为 true;
- NaN 不与任何值相等,包括它自己;
- null 和 undefined 不会进行数据转换;
数值和非数值类型比较
数值和字符串
jsconsole.log("" == 0); // true console.log("1" == 1); // true console.log("000" == 0); // true console.log("sy" == 0); // false
当一个数据类型是数值,另一个是字符串,会将字符串转换为数值,再与之比较。比如其中
'sy'
会转换为数字NaN
,所以返回false
。数值和布尔值
jsconsole.log(true == 1); // true console.log(true == 2); // false console.log(false == 0); // true console.log(false == 1); // false
很容易就能看出,这样的比较,会将
true
转化成数值1
,false
转化为0
。数值和数组
jsconsole.log([] == 1); // false console.log([] == 0); // true console.log([1, 2] == 2); // false
在这里,数组会先通过调用
toString()
转换为字符串后再转化为数值。比如['Ywis']
先转化为字符串'Ywis'
后再转化为数值NaN
,所以返回false
。拓展:数组、对象和函数在与其他基本数据类型进行比较时都会先转换为字符串,然后再转换为相应的数据类型。
布尔值和非布尔值类型比较
console.log(true == 1); // true
console.log(true == "00001"); // true
console.log(false == []); // true
console.log(true == ["123"]); // false
当布尔值和非布尔类型比较,会将true
转换为 1,false
转换为 0。将非布尔值类型统统转化成数值,这里的类型转换规则和上面提到的相同。
对象和原始值的比较
console.log({} == 0); // false
console.log([] == 1); // false
当对象与原始值进行比较时,JS 会先尝试通过调用对象的valueOf()
方法来获取原始值。如果valueOf()
方法返回的是一个原始值,JS 会将其转换成与待比较的原始值相同的类型,然后进行比较。如果valueOf()
方法返回的还是一个对象,则会继续调用对象的toString()
方法,将其返回值转换为原始值,然后再进行比较。
比如{} == 0
,对于空对象{}
它的valueOf()
方法返回的是对象本身,因此 JS 会继续调用 toString()
方法,返回得到一个字符串 "[object Object]"
,再转换为数值NaN
,所以返回false
。\
小测试
问: foo 为何值时能输出 'check in' ?
var foo = ?
if( foo == 1 && foo == 2) {
console.log('check in')
}
先给出答案
var foo = {
a: 0,
valueOf: function () {
return ++this.a;
},
};
if (foo == 1 && foo == 2) {
console.log("check in");
}
或者
var foo = {
a: 1,
valueOf: function () {
return this.a++;
},
};
if (foo == 1 && foo == 2) {
console.log("check in");
}
解释
当对象跟数字比较的时候,会调用对象的 valueOf 方法,返回一个数字, 所以我们可以依据这个属性进行运算。
为什么 Vue3 中使用 ref 定义的变量要用 .value 呢?
源码中 使用 了 get value 函数和 set value 函数, 在使用 getter 和 setter 的时候为实例创建一个伪属性(value)
大概源码展示:
class RefImpl {
private _value // 私有的值
private _rawValue // 私有的原始值
public readonly __v_isRef = true
constructor(value, public readonly __v_isShallow: boolean) {
this._rawValue = value
this._value = __v_isShallow ? value : toReactive(value)
}
// 代理
get value() {
// 收集依赖
track(this, trackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if(hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
// 触发依赖
trigger(this, TriggerOrTypes.SET, 'value', newVal)
}
}
}
在页面中添加水印可以通过控制台移除,那么怎么样才能避免用户移除水印呢?
可以通过用 MutationObserver 监听,重新添加水印
水印封装代码:
function watermark(options = {}) {
const {
container = document.body, // 容器
width = "240", // canvas元素宽
height = "100", // canvas元素高
textAlign = "left", // 文字对齐
textBaseline = "bottom", // 基准线
font = "16px Microsoft Yahei", // 字体大小及样式
fillStyle = "#000", // 自定义水印的颜色
content = "进制外传", // 水印内容
globalAlpha = 0.3, // 设置图形和图像透明度的值
rotate = 16, // 文字旋转角度
zIndex = 1000, // 元素堆叠顺序
} = options;
const canvas = document.createElement("canvas");
canvas.setAttribute("width", width);
canvas.setAttribute("height", height);
const ctx = canvas.getContext("2d"); // 获取 canvas2d 上下文
ctx.globalAlpha = globalAlpha;
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.rotate((Math.PI * rotate) / 180);
// ctx.rotate(-10 * Math.PI / 140);
ctx.fillText(content, 50, 50);
const base64Url = canvas.toDataURL(); // 返回一个包含图片展示的 data URI
const __wm = document.querySelector(".__wm"); // 选择器
const watermarkDiv = __wm || document.createElement("div");
const waterMarkStyle = `
position:absolute;
top:0px;
left:0px;
width:100%;
height:100%;
z-index:${zIndex};
pointer-events:none;
background-repeat:repeat;
background-image:url('${base64Url}')`;
watermarkDiv.setAttribute("style", waterMarkStyle);
watermarkDiv.classList.add("__wm"); // 为元素添加“__wm”类名
container.style.position = "relative";
container.appendChild(watermarkDiv); // 添加元素
// 监听删除 防止用户去手动删除,如果手动删除 ,在重新添加
const MutationObserver =
window.MutationObserver || window.WebKitMutationObserver;
// 检查浏览器是否支持这个API
if (MutationObserver) {
const args = arguments[0];
const __wm = document.querySelector(".__wm");
let mo = new MutationObserver(function () {
// 只在__wm元素变动才重新调用
if (
(__wm && __wm.getAttribute("style") !== waterMarkStyle) ||
!__wm ||
document.body.style.position !== "relative"
) {
// 避免一直触发
mo.disconnect();
mo = null;
watermark(args);
}
});
mo.observe(__wm, {
// 视情况开启
attributes: true, // 观察目标节点的属性节点
subtree: false, // 观察目标节点的所有后代节点
childList: true, // 观察目标节点的子节点
});
}
}
export default watermark;
vue3 常用方法
ref、reactive、computed、watch、defineProps、defineExpose、defineEmits、withDefaults
- ref 用于定义基础类型变量
- reactive 用于定义引用类型变量
- computed 用于定义计算属性
- watch 用于监听数据变化
- defineProps 用于定义组件属性
- defineExpose 用于暴露组件内部数据
- defineEmits 用于定义组件内部事件
- withDefaults 用于定义组件默认属性值
基本用法
什么情况下会产生跨域?
跨域主要是由于浏览器的同源策略引用的,同源策略是浏览器的安全机制,当协议,域名,端口三者有一个不同,浏览器就禁止访问资源
扩展
① 服务端请求服务端会跨域吗?
不会,跨域主要针对对象是浏览器
② webpack 或者 vite 中可以使用 proxy 解决跨域,它解决跨域的原理是什么?
webpack 或者 vite 中可以使用 proxy 解决跨域, 其实现是提供一个中间服务器进行代理。 webpack 提供服务器的工具是 webpack-dev-server,只适用于开发阶段。
③ 为什么跨域的时候有时候请求会发两次?
当发起跨域请求时,浏览器会先发送一个预请求(通常是 OPTIONS 请求),用于询问服务器是否允许该跨域请求。如果服务器返回允许,则再发送正式的请求。如果预请求失败,则不会发送正式的请求
④ 那为什么非要有这个预检请求呢?
检查跨域请求的安全性: 预检请求用于验证是否允许发起跨域请求,以保障跨域请求的安全性。这有助于防止潜在的安全风险,如跨站请求伪造(CSRF)攻击。
检查跨域请求的支持: 预检请求允许服务器检查客户端的请求头(Request Headers)和方法(HTTP Methods),以确定是否支持跨域请求。这有助于服务器根据实际情况决定是否允许客户端访问资源。
提供更好的错误信息: 如果服务器不支持跨域请求,预检请求可以提供更详细的错误信息,以便客户端开发者了解问题所在。
减少潜在的冲突: 预检请求还可以用于避免一些潜在的冲突,如浏览器缓存问题,从而确保请求的顺利进行。
cookie 的有效时间设置为 0 会怎么样?
Cookie
过期时间设置为 0,表示跟随系统默认,其销毁与 Session 销毁时间相同,会在浏览器关闭后删除
for...in 和 for...of 的区别
for…of 是 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,和 ES3 中的 for…in 的区别如下
- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
- 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;
样式穿透的写法总结
- css 可以使用 >>>,/deep/,::v-deep
- less 和 node-sass 可以使用/deep/,::v-deep
- dart-sass 可以使用::v-deep
- vue2.7 以上版本以及包括 vue3,应该使用:deep()
总结:
for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
⭐️⭐️⭐️ 好啦!!!本文章到这里就结束啦。⭐️⭐️⭐️
✿✿ ヽ(°▽°)ノ ✿
撒花 🌸🌸🌸🌸🌸🌸