HTML CSS
Flex布局
使用flex布局
-
容器
.box { display: flex; }
-
行内元素
.box { display: inline-flex; }
flex布局后,float
、vertical-align
、clear
失效。
容器属性
-
flex-direction
:主轴方向属性值
row
:子元素起点在左,左到右。row-reverse
:起点在右,右到左。column
:起点在上,上到下。column-reverse
-
flex-wrap
:换行nowarp
:不换行,缩小各子元素宽度。warp
:换行。warp-reverse
:换行,但从下至上,原第一排在最下方。
-
flex-flow
:flex-direction
和flex-wrap
缩写。flex-flow: column nowarp;
-
justify-content
:项目在主轴上对齐方式flex-start
:左对齐,默认值。flex-end
:右对齐。center
:居中。space-between
:两端对齐,项目间间隔相等。space-around
:项目间间隔相等。
-
align-items
:项目在交叉轴的对齐方式flex-start
flex-end
center
base-line
:和项目第一行文字基线对齐。strench
:默认值,没有高度将撑满容器。
-
align-content
:多根轴线的对齐方式flex-start
:左对齐,默认值。flex-end
:右对齐。center
:居中。space-between
:两端对齐,项目间间隔相等。space-around
:项目间间隔相等。stretch
项目属性
-
order
:项目排列顺序,默认0,越小越靠前。 -
flex-grow
:项目放大比例,默认0。如果所有项目的
flex-grow
属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow
属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。 -
flex-shrink
:项目缩小比例,默认1。所有项目的
flex-shrink
属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink
属性为0,其他项目都为1,则空间不足时,前者不缩小。 -
flex-basis
:项目占据的主轴空间,默认为auto即项目原本大小。 -
flex
:flex-grow
、flex-shrink
、flex-basis
三者合一。flex:1
代表flex-grow:1
、flex-shrink:1
、flex-basis:0%
,项目将自动平分剩余空间。 -
align-self
:覆盖align-items
属性,允许单个项目在交叉轴上有不一样的对齐方式。
Canvas
线条
moveTo(x,y)
:起点lineTo(x,y)
:终点stroke()
:绘制
矩形
fillRect(*x,y,width,height)
参数:
x、y
:左上角坐标width、height
:宽高
圆
-
beginPath()
开始一条路径,或重置当前的路径。
-
arc(x, y, r, startAngle, endAngle)
参数:
x、y
:圆中心坐标r
:圆半径startAngle、endAngle
:起始角、结束角。
-
stroke()
文本
-
font
:设置属性ctx.font = '30px Arail'
-
fillText(str, x, y)
:绘制实心文本。 -
strokeText(str, x, y)
:绘制空心文本。
图像
drawImage(image,x,y*)
注意需图片加载出来onload
后再调用。
渐变
-
线性渐变
createLinearGradient(x,y,x1,y1)
-
圆/径向渐变
createRadialGradient(x,y,r,x1,y1,r1)
-
颜色停止
addColorStop(location, color)
const grd = ctx.createLinearGradient(0, 0, 200, 0);
grd.addColorStop(0, "red");
grd.addColorStop(1, "white");
ctx.fillStyle = grd;
ctx.fillRect(50, 125, 150, 75);
水印实战
原理:生成单个canvas
水印,放到大div
中,此大div
设置absolute
定位、高z-index
、background-img:url()
,依靠背景图片默认repeat
。
css解读
position: absolute
:将元素从文档流拖出来,对其最接近的一个具有定位属性的父定位包含框进行绝对定位。如果不存在这样的定位包含框,则相对于浏览器窗口。pointer-events: none
:该元素永远不会成为鼠标事件的 target。当其后代元素的pointer-events
属性指定其他值时,鼠标事件可以指向后代元素。
/*
* @Author: cbw
* @Date: 2022-09-23 16:21:05
* @LastEditors: cbw
* @LastEditTime: 2022-09-26 11:22:07
* @Description:
*/
class WaterMark {
#canvasOptions; // canvas默认配置
#canvasIndividualOptions; //canvas个性化配置
#waterMarkStyle; // 水印默认配置
#waterMarkIndividualStyle; // 水印个性化配置
#wm; // 水印DOM
#Uuid; // 唯一id
#waterMarkStyleStr; // style字符串
constructor(canvasOptions = {}, waterMarkStyle = {}) {
// canvas默认配置
this.#canvasOptions = {
width: 400, // canvas宽
height: 400, // canvas高
font: "normal 16px 思源黑体_ Regular",
fillStyle: "rgba(10, 100, 80, .2)", // 文本颜色
textAlign: "center",
fillTextArr: ["Boen", "3150987521986"], // 文本
rotate: -20, // 旋转角度
fillTextX: 100, // 文本横坐标
fillTextY: 100, // 文本纵坐标
};
// 水印默认配置
this.#waterMarkStyle = {
position: "absolute",
left: 0,
top: 0,
right: 0,
bottom: 0,
"z-index": "99999",
"pointer-events": "none", // 永远不成文鼠标事件的 target
container: document.body, // 水印创建位置
};
// canvas个性化配置
this.#canvasIndividualOptions = canvasOptions;
// 水印个性化配置
this.#waterMarkIndividualStyle = waterMarkStyle;
// 初始化
this.#init();
}
/**
* 创建canvas
* @param {Object} options 选项
* @returns canvasUrl
*/
createCanvasUrl(options = {}) {
const canvas = document.createElement("canvas"); // 创建canvas
// 设置属性
canvas.setAttribute("width", options?.width ?? this.#canvasOptions.width);
canvas.setAttribute(
"height",
options?.height ?? this.#canvasOptions.height
);
const ctx = canvas.getContext("2d");
ctx.font = options?.font ?? this.#canvasOptions.font;
ctx.fillStyle = options?.fillStyle ?? this.#canvasOptions.fillStyle;
ctx.textAlign = options?.textAlign ?? this.#canvasOptions.textAlign;
ctx.rotate(
(Math.PI / 180) * (options?.rotate ?? this.#canvasOptions.rotate)
);
const fillTextArr = options?.fillTextArr || this.#canvasOptions.fillTextArr;
for (let i = 0; i < fillTextArr.length; i++) {
const fillText = fillTextArr[i];
// 防止多文本重叠
ctx.fillText(
fillText,
options?.fillTextX ?? this.#canvasOptions.fillTextX,
(options?.fillTextY ?? this.#canvasOptions.fillTextY) + 20 * i
);
}
const canvasUrl = canvas.toDataURL(); // 获取base64图片URL
return canvasUrl;
}
/**
* 创建水印
* @param {String} bgcImgUrl
* @param {Object} options
*/
createWaterMark(bgcImgUrl, options = {}) {
this.#Uuid = this.getUuid();
const waterMark = document.createElement("div");
waterMark.setAttribute("id", this.#Uuid);
this.#waterMarkStyleStr = "";
// 拼接成style字符串
for (let key in this.#waterMarkStyle) {
this.#waterMarkStyleStr +=
key + `:${options?.[key] ?? this.#waterMarkStyle[key]};`;
}
this.#waterMarkStyleStr += `background-image:url(${bgcImgUrl});`;
waterMark.setAttribute("style", this.#waterMarkStyleStr); // 设置style属性
options?.container?.appendChild(waterMark) ??
this.#waterMarkStyle.container.appendChild(waterMark);
return waterMark;
}
/**
* 生成uuid
* @returns
*/
getUuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
}
/**
* 初始化
*/
#init() {
const base64Url = this.createCanvasUrl(this.#canvasIndividualOptions); // base64图片
this.#wm = this.createWaterMark(base64Url, this.#waterMarkIndividualStyle); // 创建水印
this.#observer();
}
/**
* 移除水印
*/
#remove() {
const wmDiv = document.getElementById(this.#Uuid);
// 防止预移出节点不存在
if (!wmDiv) {
this.#waterMarkIndividualStyle?.container?.removeChild(this.#wm) ??
this.#waterMarkStyle.container.removeChild(this.#wm);
}
}
/**
* 防止控制台删除水印
*/
#observer() {
const targetNode =
this.#waterMarkIndividualStyle?.container ??
this.#waterMarkStyle.container; // 监听节点
// 监听配置
const observerConfig = {
subtree: true,
childList: true,
attributes: true,
};
// 创建observer对象
const observer = new MutationObserver(() => {
const wmDiv = document.getElementById(this.#Uuid);
// wmDiv不存在
if (!wmDiv) {
this.init();
return;
}
// css样式被修改
if (wmDiv.getAttribute("style") !== this.#waterMarkStyleStr) {
wmDiv.setAttribute("style", this.#waterMarkStyleStr);
}
});
// 开始监听
observer.observe(targetNode, observerConfig);
}
}
const wm = new WaterMark();
const wm2 = new WaterMark(
{},
(waterMarkStyle = { left: "150px", top: "150px" })
);
JavaScript
ES6
Symbol-新数据类型
Symbol是JavaScript的第七种数据类型。常用于表示独一无二的字符串,例如函数名等。
定义
-
Symbol()
局部,相同的串并不代表是同一个Symbol。
-
Symbol.for()
全局,开辟内存空间,相同的串代表是同一个Symbol。
描述
symbol.description
:通用。Symbol.keyfor()
:由Symbol.for()
建立的对象独享。
用途
-
唯一的key。
-
对象私有属性。
遍历时,将无法取到以Symbol对象为key的属性。
注意事项
for...in
取不到以Symbol对象为key的属性。Object.getOwnPropertySymbols()
可以拿到以Symbol对象为key的属性。Reflect.ownKeys()
能够获取所有属性。
数据结构
Set
类似数组,但所有value唯一。
常用
- 属性:
size
- 方法:
- 增:
add
- 查:
has
- 删:
delete
clear
:清空。values
:获得Set迭代对象。
- 增:
与数组互转
- 转数组:
Array.from()
、[...set]
- 转Set:
new Set(arr)
WeakSet
和Set
差不多,但是WeakSet
有几个不同点:
-
只能存放引用数据类型。
-
WeakSet
的对象是弱引用。WeakSet
对对象的引用不会被考虑进垃圾回收机制,只要没有引用该对象,该对象就会被回收,无论是否在WeakSet
中被引用。因此容易被弱引用造成影响的方法都不被提供,如values
、size
、for of
等。const objs = new WeakSet(); let obj = { qq: "123" }; objs.add(obj); objs.add({ a: "123", b: { c: "hello", }, }); console.log(objs); setTimeout(() => { console.log(objs); }, 5000);
Map
类似对象,但所有key唯一,且key可以是任意值(对象key本质是字符串)。
常用
- 属性
- 方法
- 增:
set
,key同样时,即为更改。new Map([[key, value], [], ...])
- 删:
delete
- 查:
has
、get
- 增:
- 遍历
entries
,可以解构一下[key, value]keys
values
for...of
forEach
与数组互转
- 转数组:
[...map]
- 转map:
new Map([[key, value], [], ...])
WeakMap
与map差不多,但键key必须是引用数据类型,且WeakMap是弱引用类型。
变化:size
、keys
等方法都用不了,因为是弱引用,不加入垃圾回收机制。
运算符的扩展(ES6)
指数运算符**
num1 ** num2 = num1^num2
// 右结合
num1 ** num2 * num3 = num1^(num2^num3)
// 新赋值运算符
num1 **= 3 // num1 ^3
链判断运算符?.
三种用法:
-
对象属性
obj
是否存在?obj.obj2
是否存在?obj.obj2.data
是否存在?顺序着来,不存在直接返回undefined
。const data = obj?.obj2?.data || {}
-
对象方法
obj?.func()
-
函数调用
func?.()
Null判断运算符??
通常赋默认值是以||
方式提供,但 如null、undefined、NaN、0、""、false
也会出现会被囊括其中。Null判断运算符??
只有运算符左侧的值为undefined
、null
时才会返回右侧的值。
const num = 0 ?? 1 // num = 0
const num = obj?.num ?? 1 // num = 1
文字翻转
split('').reverse().join('')
类型判断
typeof
type of 1 // number
type of '1' // string
type of {} // object
type of [1, 2, 3] // object
type of function run() {} // function
type of hdcms // hdcms未定义情况下:undefined
type of hdcms // hdcms已声明情况下:undefined,因为初始值为undefined
instanceof
[] instanceof Array // true
{} instanceof Object // true
同源、跨域页面间通信
同源页面
同源:协议、url、端口号一致。
-
A页面
window.addEventListener('storage', (e)=>{ console.log(e) })
-
B页面
localStorage.setItem(key, value)
跨域页面
跨域:协议、url、端口号至少一个不一样。
-
A页面
window.addEventListener('message', (e) => { console.log(e) // e.origin 查看源地址 })
-
B页面
const targetWindow = window.open('http://localhost:10001/user'); setTimeout(()=>{ targetWindow.postMessage('来自10002的消息', 'http://localhost:10001') }, 3000)
内存泄漏
不再用到的内存,没有及时释放,叫做内存泄漏。
引起的原因
- 全局变量
- 定时器
- 闭包使用不当
- 网络回调函数
- DOM元素(js、DOM树都清理)
Garbage Collection(GC)
-
标记清除法
根对象开始寻找可引用的对象,未被引用的对象移出。
-
引用计数法
新引用+1,移出引用-1,引用为0的对象回收。
Websocket
基本用法
方法
close()
:主动关闭连接。send()
:客户端想服务端发送数据。
事件
close
:连接断开触发。error
:连接错误触发。message
:收到服务端发送的数据。open
:打开连接时触发。
例子
const ws = new WebSocket(url)
// 建立连接
ws.addEventListener('open', () => {
ws.send('hello server') // 向服务端发送数据
})
// 接受消息
ws.addEventListener('message', (e) => {
// e.data
})
// 断开连接
ws.addEventListener('close', () => {
})
// 断开连接
ws.addEventListener('error', () => {
})
对于后端,每创建一个新的连接,都会有一个conn
对象。
try…catch
语法
try {
}catch (err) {
throw new Error()
}finally {
// 总会做的事情
}
注意事项
function func() {
try {
return 1;
} catch (err) {
/* ... */
} finally {
alert( 'finally' );
}
}
这个例子,先alert
、后return
。
全局变量污染
-
立即执行函数
(function (window) { function show() { console.log(`js2.js ---`); } window.js2 = { show, }; })(window);
-
块作用域
{ let show = function () { console.log(`js1.js ---`); }; window.js1 = { show, }; }
Web Workers
基本使用
-
检测浏览器是否支持
Web Workers
if(window.Worker) {}
-
创造一个
worker
const worker1 = new Worker(aURL, options)
此
worker
指定一个脚本来执行线程。脚本里(main.js)可以写一个事件处理函数作为响应
onmessage = function (e) { // e.data postMessage(data); }
-
向
worker
发送数据worker1.postMessage(data)
-
监听
worker
返回的消息worker1.onmessage = function (e) {}
-
杀掉
worker
worker1.terminate()
字符串常用方法
查找
-
indexOf
、lastIndexOf(char, loc)
-
includes
-
startsWith(str)
是否以str开头。
截取
substr(start, num)
slice(start, end)
subString(start, end)
:start和end为非负的整数。
替换
replace(word, replaceWord)
重复
str.repeat(num)
数组常用方法
检测
Array.isArray()
数组与字符串互转
转为字符串
-
join()
-
String()
、toString()
默认间隔英文逗号“,”。
转为数组
-
split()
-
Array.from(obj, map)
-
[...arr] = str
原理解构赋值+rest操作符。str本身是有 length属性的字符串,所以每个字符都放到了变量arr里。
元素操作
添加
-
push
-
unshift
添加到数组头部。
-
splice(start, delNum, ele1,...)
删除
-
pop
-
shift
删除数组尾部元素。
-
splice(start, delNum)
移动
-
splice
function itemMove(arr, from, to) { arr.splice(to, 0, ...arr.splice(from, 1)); return arr; }
替换
以obj替换已有数组[start, end)所有元素。
arr.fill(obj, start, end)
清空数组
arr = []
- 推荐:
arr.length = 0
arr.splice(0, arr.length)
创建数组
-
Array.of(params,...)
const arr = Array.of(1, 2, 3, true, "123");
-
Array.from()
通过拥有 length 属性的对象或可迭代的对象来返回一个数组。
Array.from(obj, mapFunc, this)
可实现浅拷贝,如
const arr = Array.from([{a: '1'}, 'b'])
-
new Array()
仅传一个参数num,即创建num个为undefined的元素。多个参数与
Array.of
一致。
数组截取
-
slice
截取数组[start, end)部分,返回一个新数组。不对原数组操作。
arr.slice(start, end)
注意
- 只传一个参数时,end代表
arr.length
。 - slice(0)是浅拷贝!!!
- 只传一个参数时,end代表
-
splice
此函数会修改数组本身,返回被修改的内容。
arr.splice(start)
查找
以下查找,针对数组里的对象都是“址”查找,不是一样的地址,不会匹配。
-
indexOf
查找在数组中某一指定元素(必须
===
)的第一次出现的位置。返回index、-1。arr.indexOf(obj)
-
includes
判断一个数组是否包含一个指定的值,返回true、false。
arr.includes(ele)
-
find
查找通过测试的第一个值,返回查到的值、undefined。
arr.find(item => item > 18) // 手撕 Array.prototype.find(callback) { for(let item of this) { if(callback(item)) return item; } return undefined; }
-
findIndex
查找通过测试的第一个值,返回查到的值的索引、-1。
arr.findIndex(item => item > 18)
-
some
查找是否有通过测试的值,返回true、false。
arr.some((item, index) => item > 18)
-
every
类some,查找是否都有通过测试,返回true、false。
-
filter
过滤不符合条件的元素,返回一个新数组(浅拷贝)。
arr2 = arr.filter(item => item > 18)
原理:
Array.prototype.filter = function (callback) { const arr = []; for (const iterator of this) { callback(iterator) && arr.push(iterator); } return arr; };
合并
-
arr.concat(array2,...)
返回一个新数组,将arr、arr2,…连接。传入的参数如果是数组,将被展开一层。如果是非数组,将直接作为元素添加。
-
[...arr1, ...arr2]
-
Object.assign()
排序
arr.sort((a,b) => a-b) // 小于0升序,大于0降序
原理:
Array.prototype.swap = function (index_a, index_b) {
const box = this[index_a];
this[index_a] = this[index_b];
this[index_b] = box;
};
Array.prototype.sort = function (callback) {
for (let i = 0; i < this.length; i++) {
for (let j = i + 1; j < this.length; j++) {
if (callback(this[i], this[j]) > 0) {
this.swap(i, j);
}
}
}
};
翻转
arr.reverse()
集合运算
去重
[...new Set(arr)]
交集
function show(arr1, arr2) {
const arr = [];
const set1 = new Set(arr1);
const set2 = new Set(arr2);
return [...set1].filter((item) => set2.has(item));
}
并集-交集
function show(arr1, arr2) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
const fn = (s1, s2) => {
const arr = [];
for (let item of s1) {
if (!s2.has(item)) {
arr.push(item);
}
}
return arr;
};
const res1 = fn(set1, set2);
const res2 = fn(set2, set1);
return [...res1, ...res2];
}
差集
arr1.filter((item) => !new Set(arr2).has(item))
高阶
-
累计:
array.reduce(function(pre, currentValue, currentIndex, arr), initialValue)
参数:
pre
是上次的返回值,第一次为initialValue ?? array[0]
。原理
Array.prototype.reduce = function (callback, initValue) { // 检查数组是否为null或undefined if (this == undefined) throw new TypeError("this is null or undefined"); // 检查callback是否是函数 if (typeof callback !== "function") throw new TypeError(`${callback} is not a function`); const arr = Object(this); // 确保arr为对象 const arrLength = arr.length >>> 0; // 确保length为正数 let index = 0; // 第一个有效值索引 if (initValue === undefined) { // 寻找第一个有效值 while (index < arrLength && !(index in arr)) index++; // index超出数组范围,证明是空数组 if (index >= arrLength) { throw new TypeError("empty array"); } // 设置初始值 initValue = initValue ? initValue : arr[index++]; } let res = initValue; // 初始化结果 // 计算结果 for (let i = index; i < arrLength; i++) { if (i in arr) res = callback(res, this[i], i, this); } return res; };
-
映射:
map
原理
Array.prototype.map = function (callback = (item) => item) { const newArr = []; this.forEach((element) => { newArr.push(callback(element)); }); return newArr; };
对象
属性
属性特征
-
设置属性特征
Object.defineProperty(obj, property, { writable: false, // 不可写 enumerable: false, // 不可遍历 configurable: false, // 不可配置、删除 value }); Object.definePropertys(obj,{property1, property2,...})
-
读取属性特征
Object.getOwnPropertyDescriptor(obj, propertyStr) Object.getOwnPropertyDescriptors(obj)
删除属性
delete obj.property
禁止添加属性
Object.preventExtensions(obj)
检测是否禁止:Object.isExtensible()
封闭对象
禁止添加属性,且对象不可配置
Object.seal(obj)
检测是否被封禁:Object.isSealed()
冻结对象
对象不能被修改,不可增删属性、不可配置、不可写,原型不可修改。
Object.freeze(obj)
检测某属性是否存在
hasOwnProperty
:不查原型链
in
:查原型链
访问器
const data = {
set property(value) {
},
get property() {
}
}
注意,set
和打点访问赋值同时针对一个属性,set
优先。
遍历
for in
拷贝
浅拷贝
Object.assign({}, obj1, obj2)
{...obj1, ...obj2}
深拷贝
-
老版
function cloneDeep(obj) { if (Array.isArray(obj)) { const arr = []; for (const ele of obj) { arr.push(cloneDeep(ele)); } return arr; } else if (obj instanceof Object) { const copy_obj = {}; for (const key in obj) { // for in会遍历所有可枚举属性 if (obj.hasOwnProperty(key)) { copy_obj[key] = cloneDeep(obj[key]); } } return copy_obj; } else { return obj; } }
-
新版
function cloneDeep(obj) { if (obj instanceof Object) { const res = Array.isArray(obj) ? [] : {}; for (const [key, value] of Object.entries(obj)) { res[key] = cloneDeep(value); } return res; } else { return obj; } }
创建对象
Object.create(prototype, properties)
if()表达式和==原理
if()
if(1) // true ---> if(Boolean(1))
if(undefined) // fasle
if({}) // true
if([]) // true ---> if(Boolean([]))
if()
里,其实就是执行Boolan()方法。
==
1 == true // true
2 == true // false ---> Number(2) == Number(true) ---> 2 == 1
[1] == true // true
==
,本质是执行Number()
方法。
Boolean()
类型 | 值 | Boolean() |
---|---|---|
undefined | undefined | false |
null | null | false |
string | ‘’ | false |
string | ‘0’ | true |
number | 0 | false |
number | 1 | true |
boolean | false | false |
boolean | true | true |
object | {} | true |
object | {num:0} | true |
object | [] | true |
object | [0] | true |
总结:
- undefined、null都为false。
- 字符串只有’'为false。
- 数值类型只有0为false。
- 引用数据类型都为true。
- 布尔类型看本身。
Number()
类型 | 值 | Number() |
---|---|---|
undefined | undefined | NaN |
null | null | 0 |
string | ‘’ | 0 |
string | ‘0’ | 0 |
string | ‘1’ | 1 |
string | ‘1a’ | NaN |
number | 0 | 0 |
number | 1 | 1 |
boolean | false | 0 |
boolean | true | 1 |
object | {} | NaN |
object | {num:0} | NaN |
object | [] | 0 |
object | [0] | 0 |
object | [0,1] | NaN |
总结:
- undefined为NaN。
- null为0。
- 字符类型长度为0必为0。长度不为0看value是否包含非数字,不包含就是去掉引号后的值。否则NaN。
- 数值类型保持原值。
- 布尔类型true为1,false为0。
- 引用数据类型。
- 对象{}为NaN。
- 数组长度为0即为0。长度为1则转那一个数值,长度大于1则NaN。
Math
-
max
、min
可以使用spread、rest或apply、call来改变传入参数是列表还是数组。
-
ceil
、floor
ceil:天花板,floor:地板。
-
round
四舍五入。
-
random
取[0, num]的整数:
Math.floor(Math.random() * (num + 1))
。取[num1, num2]的整数:本质还是[0 ,num]
function getRandom(num1, num2) { return ( Math.min(num1, num2) + Math.floor(Math.random() * (num2 - num1 + 1)) ); }
Date
获取时间戳
+date
Number(date)
date.valueOf()
date.getTime
时间戳转ISO时间
new Date(timeStamp)
格式化
好库:moment.js
- 年:
getFullYear
- 月:
getMonth
,从0开始。 - 日:
getDate
- 小时:
getHours
- 分钟:
getMinutes
- 秒:
getSeconds
const d = new Date("1999-11-10 03:03:12");
function formatDate(date, format) {
const config = {
YY: date.getFullYear(),
MM: date.getMonth(),
DD: date.getDate(),
HH: date.getHours(),
mm: date.getMinutes(),
SS: date.getSeconds(),
};
for (let item in config) {
format = format.replace(item, config[item]);
}
return format;
}
console.log(formatDate(d, "YY年MM月"));
原型
含义
- 显式原型:
prototype
- 隐式原型:
__proto__
查找
Object.getPrototypeOf()
设置原型
Object.setPropertyOf(o. proto)
__proto__
constructor
Fn.prototype.constructor === Fn
Vue2
创建全局组件
Vue.component(name, {
template: '', // html代码
props: ,
})
需要在创建实例之前使用该方法。
计算属性
计算属性是基于响应式依赖进行缓存的,相比方法,只有在相关响应式依赖发生变化时才重新求值。
原理
src/core/instance/state.js
,参考链接。
-
initState
函数中,initComputed
初始化computed
。 -
initComputed
中- 遍历
computed
,获取(key, value)
,即属性名和计算的方法。 - 对每一个计算属性
- 创建
Watcher
实例 - 初始化
getter
、deps
(依赖哪些属性)、dep
(发布者,以备未来有被订阅) defineComputed
— 定义set
、get(createComputedGetter)
计算方法,然后通过Object.defineProperty()
设置vue实例的属性的set
、get
方法。
- 创建
- 遍历
-
针对
createComputedGetter
-
watcher.evaluate()
如果已创建的
Watcher
实例,是计算方法,执行evaluate()
获取值。evaluate
本质是调用之前定义的Watcher
实例的getter
方法。如果要求是depp
深度监听,将重新收集所有依赖。 -
watcher.depend()
:如果存在Dep.target
,收集依赖。
当对一个对象使用
getters
时,同样会调用其子属性的getters
。这样每一个属性对应的watcher
都会被推入Dep
类的静态属性target
,从此每一个属性都将被收集到计算属性的依赖。 -
key
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
attribute 即可。
v-for
-
永远不要把
v-if
和v-for
同时用在同一个元素上。v-for
的优先级比v-if
的优先级更高,因此每次重渲染时,都会遍历整个列表,不论活跃用户是否发生了变化,因此浪费性能。解决方法:
- 先使用计算属性筛选一次。
- 将
v-if
转移到容器元素上。
-
key
不能使用index
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值。index
代表当前项的索引(0,1,2,3,4,…),当发生删除、增加等操作时,其后面的元素的index
都会发生变化,此时diff
算法就认为后面的key-index
映射全部发生了变化,将全部重新渲染,严重影响性能。因此推荐key
使用唯一值,如时间戳+new Date()
、身份证号、学号等…
同步组件和异步组件
同步组件
import componentA from './componentA.vue'
异步组件
只在组件需要渲染的时候才进行加载渲染并进行缓存,以备下次访问。
componentA: () => import('./componentA.vue')
调用异步组件的方法-延时
setTimeout(() => {
this.$nextTick(() => {
console.log(this.$refs.com);
});
}, 100);
优点:提升首页渲染速度。
区别
-
nextTick
父组件获取子组件时:
同步组件:nextTick可以获取组件。
异步组件:第一次nextTick之后无法获取组件。
-
打包
打包成单独的js文件存储在static/js文件夹里面
-
生命周期顺序
异步组件:父组件
beforeCreate
、created
、beforeMount
、mounted
—>挨个子组件beforeCreate
、created
、beforeMount
、mounted
同步组件:父组件
beforeCreate
、created
、beforeMount
—>挨个子组件beforeCreate
、created
、beforeMount
—>挨个子组件mounted
—> 父组件mounted
组件
-
当一个组件被定义,
data
必须声明为返回一个初始数据对象的函数,因为组件可能被用于创建多个实例。否则,会出现多个组件使用一个数据对象的情况。
-
使用事件抛出值
-
子组件
<button @click="$emit('put', 999)"><button />
-
父组件
num
接收来自子组件传来的值num
<Father @put="num = $event"/>
或者
<Father @put="change"/> methods:{ change(num) { // change方法的第一个形参即子组件传来的值 } }
-
-
组件使用
v-model
-
父组件
// 法一:有缺陷,初始值传不过去 <Father v-model="message" /> // 法二:刨根问底 <Father v-bind:message="message" v-on:input="message = $event"> // or <Father :message="message" @input="message = $event">
-
子组件
Vue.component('Son', { props: ['message'], template: ` <input v-bind:value="message" v-on:input="$emit('input', $event.target.value)" > ` })
小知识:
update:myPropName
模式可以用.sync
修饰符使父子组件通信的prop
进行双向绑定。-
子组件
数据发生变化时
this.$emit('update:title', newtitle)
-
父组件
-
刨根问底
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" ></text-document>
-
.sync
修饰符<text-document v-bind:title.sync="doc.title"></text-document>
-
-
prop
-
子组件接收
prop
并作为本地数据使用。props: ['initCount'], data() { return { count: this.initCount } }
-
带有默认值的对象
对象或数组默认值必须从一个工厂函数获取
props: { propA: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } } }
插槽
使用
-
a.vue
<template> <div> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template>
-
App.vue
<A> <template v-slot:header> <h1>header</h1> </template> <template v-slot:footer> <h1>footer</h1> </template> <h1>main</h1> </A>
插槽内容使用子组件数据
子组件
<slot v-bind:propName="propName"></slot>
// propName是子组件内部数据
插槽内容
<template v-slot="allProp">
// allProp可以使用所有插槽的所有属性,通过打点区分。
</template>
注意事项
- 不带
name
的<slot>
默认名称为“default”。 v-slot
只能添加在<template>
上。
访问组件或元素
访问根实例
this.$root
访问父组件
this.$parent
访问子组件
this.$children
ref
依赖注入
父组件使用实例新选项provide
provide() {
return {
getMap: this.getMap
}
}
子组件使用实例新选项inject
inject: ['getMap']
vue生命周期
-
new Vue()
初始化事件和生命周期。
-
breforeCreate()
创建实例前,可以访问实例选项
$options
,但无法访问$el
、data
等。 -
created()
创建实例后,可以访问
data
,无法访问$el
。created
~breforeMount
-
查询是否有
$el
选项,没有则暂停生命周期,需手动挂载$mount
,反之进行下一步。 -
查询是否有模板
template
,没有则使用外部HTML
作为模板编译,反之则render
函数编译模板。优先级:
render
>template
>outHTML
-
-
beforeMount()
挂载前,无法访问
$el
。 -
mounted()
挂载后,可以访问
$el
。 -
breforeUpdate()
视图层
view
数据并未更新,$el
、data
均已更新,但真实DOM
还未更新。 -
updated()
视图层
view
数据更新,真实DOM
更新。 -
beforeDestory()
仍可使用
this
、 -
destoryed()
Vue实例销毁,解绑事件监听、子实例、
watcher
。
vue自定义指令
全局注册
Vue.directive('focus', {
inserted: function(el) {
el.focus()
}
})
局部注册
实例新选项directives
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
使用:v-focus
forceUpdate
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
过滤器
常在双花括号插值、v-bind
表达式中用于文本格式化,需被添加在JavaScript
表达式尾部。
如:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
创建过滤器
-
局部
filters: { function a(){} }
-
全局
Vue.filter(funcName, function (value) {})
使用
{{ message | filterA | filterB }}
中message
用于函数filterA
的第一个参数,filterA
的结果被传入filterB
。
混入
-
创建混入对象
const myMixin = { created() { this.show() }, methods:{ show() { console.log('混入!') } }, }
-
使用混入对象
const vm = new Vue({ mixins: [myMixin] }) // 全局引入: Vue.mixin(myMixin)
注意事项:
- 选项合并发生冲突时,以组件数据为准。未冲突,将合并。
- 钩子函数合并,混入先被调用,其次调用组件的。
插件
开发插件
插件暴露install(Vue, options)
方法,在此方法里do something。
const obj = {
install(_Vue) {
// 如果较多时,可以forEach批量注册components
_Vue.component("my", {
render(createElement) {
return createElement("h" + this.level, this.$slots.default);
},
props: {
level: {
type: Number,
require: true,
},
},
});
},
};
使用插件
Vue.use(obj)
过渡
单元素/组件过渡
Vue提供了封装组件transition
<transition name=""></transition>
常用于:
v-if
v-show
- 动态组件
- 组件根节点
过渡类名
默认无名transition
:v-enter
、v-enter-active
、v-enter-to
、v-leave
、v-leave-active
、v-leave-to
。
有name
:name
取代v
。
自定义类名
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)
注意事项
- CSS 过渡在动画中
v-enter
类名在节点插入 DOM 后不会立即删除,而是在animationend
事件触发时删除。
多元素过渡
可使用v-if/v-else
。
过渡模式
transition
新props
:mode
mode常用值:
out-in
:当前元素过渡完,新元素再来。in-out
:新元素先过渡,当前元素再走。
注意事项
- 同名标签切换,记得设置
key
。
多组件过渡
可使用动态组件。
列表过渡
<transition-group>
常用props
:
-
tag
:默认span
标签。<transition-group>
为真实元素,默认span
。
可复用过渡组件
使<transition>
或<transition-group>
成为根组件。
动态过渡
所有属性都是动态可绑定的。
模板template编译原理
流程
-
定位模板
寻找根节点
$el
- 不存在,手动
mount
,下一步。 - 存在,下一步。
寻找模板
template
,render函数编译。- 不存在,使用外部HTML。
- 存在,使用。
- 不存在,手动
-
解析器:生成AST语法树
一段一段生成,开始标签、文本、注释、结束标签。确定层级关系使用栈,开始标签推入栈,结束标签弹出栈。
-
优化器:标记静态节点
方便重新渲染,不需再为静态节点创建新虚拟树。
标记:
- 所有静态子树。
- 静态根树。
-
代码生成器:生成render函数
首先,得到渲染函数字符串。
with (this) { return _c( 'div', { attrs: {"id": "app"} }, [ _c( 'div', { staticClass: "class-name", attrs: { "title": `title`} }, [ _v(" "+_s(name)+" ") ] ), _v(" "), _c( 'div', [_v("tetttt")] ) ] ) }
再通过new Function得到渲染函数,以便得到该模板的虚拟DOM。
注意:
_c
:createElement,处理元素节点为虚拟DOM节点。_v
:createTextVNode,处理文本节点为虚拟DOM节点。_e
:createEmptyVnode,处理注释节点为虚拟DOM节点。
数组变化监听
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Vue3
生命周期(组合式API)
-
setup
组合式API特有,在created之前
-
onBeforeMount
-
onMounted
-
onBeforeUpdate
props
发生变更时,会调用此方法。 -
onUpdated
-
onBeforeUnmount
-
onUnmounted
错误拦截
app.config.errorHandler = (err) => {
/* 处理错误 */
console.log(err);
};
响应式原理
Proxy
格式
- prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式
响应式
reactive
:只能用引用数据类型ref
:all。- 包装成带
value
属性的ref对象。 - 当 ref 在模板中作为顶层属性被访问时。它们会被自动“解包”,所以不需要使用
.value
。
- 包装成带
计算属性
computed(Function)
watch监听
方法:const unwatch = watch(ele, (newVal, oldVal) => {}, options)
参数:
ele
可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。watchEffect()、watchPostEffect()
允许我们自动跟踪回调的响应式依赖- options包括deep、immediate、flush
- 手动关闭
unwatch()
注意:
- watch不可以监听响应式对象的属性。解决方法:getter函数
- 用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
ref
ref支持访问模板、v-for、函数、组件
访问子组件:const refName = ref(null)
注意:
-
<script setup>
的组件是默认私有的。父组件无法访问子组件,需要子组件在其中通过defineExpose
宏显式暴露。<script setup> import { ref } from 'vue' const a = 1 const b = ref(2) // 像 defineExpose 这样的编译器宏不需要导入 defineExpose({ a, b }) </script>
编译宏命令
defineExpose
,暴露子组件属性。defineProps
,定义子组件props
defineEmits
组件
通过 <script setup>
,导入的组件都在模板中直接可用。
v-model
v-model
在组件上都是使用 modelValue
作为 prop,并以 update:modelValue
作为对应的事件。
禁用 Attributes 继承
inheritAttrs: false
。
透传
class
、style
和 id
。
-
显式绑定
Attributes
继承:v-bind="$attrs"
-
访问透传 Attributes
import { useAttrs } from 'vue' const attrs = useAttrs()
依赖注入
父组件
provide(propName, value)
子组件
const prop = inject(propName, default)
传输的数据
-
可操作
// fath provide(propName,{value, chanegMethod}) // son const {value, changeMethod} = inject(propName)
-
不可操作
provide("read-only-state", readonly(state));
TS
官方文档:https://www.tsdev.cn/type-inference.html
数据类型
any、number、string、boolean、数组、元组、enum、void、null、undefined、never
手动指定类型
<type> value
or value as type
类型断言
- as 关键字
var as string
<>var
!
非空断言操作符
接口
interface inter {
num?: number, // 可选
readonly str: string, // 只读
[propName: string]: any, // 额外属性
(name: string):void, // 函数
[index: number]: string, // 索引类型
}
变量声明
var [变量名] : [类型] = 值;
函数声明
type C = { a: string };
function show({ a }: C) {
console.log(a);
}
函数
基本语法
function fnName(ele:type, ele?:type):returnType {
return ...
}
函数重载
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
function fn(num:number):void;
function fn(str:string,num:number):void;
推断类型
interface Show {
(name: string): void;
}
const show: Show = (name) => {
console.log(name);
};
参数
-
默认参数
const fn2 = (val: string = "defalut value") => {};
默认参数,本质也转为了可选参数。
-
可选参数
const fn1 = (val?: string) => {};
-
剩余参数
const ff = (...args: any[]) => {};
泛型
简介
相当于一个类型变量,指定一个函数的未知类型。
interface Kan<T> {
(num: T): void;
}
function kankan<Y>(num: Y): void {
console.log(num);
}
const kan: Kan<number> = kankan;
泛型约束
就是将泛型变量继承一个接口,实现约束。
interface B {
length: number;
}
function kankan<T extends B>(num: T): void {
console.log(num.length);
}
枚举
enum Resposne {
Success,
Failure = 'Failure'
}
枚举值:
- 默认第一位为0,递增1。
- 可给值常数枚举表达式。
元组
const num = [1,'2',false,4]
可放任意类型的数组。
联合类型
类型即可是type1,也可是type2.
const num:type1 | type2;
类
继承extends
子类只能继承一个类,但可以多重继承。
注意:
-
super
关键字用于访问父类的属性、方法
- 派生类包含了一个构造函数,它必须调用
super()
,它会执行基类的构造函数。 - 在构造函数里访问
this
的属性之前,一定要调用super()
- 派生类包含了一个构造函数,它必须调用
抽象类
与接口类似,用于定义抽象方法和属性。但不同于接口。主要区别在于抽象类里可以有成员实现的细节。
abstract class Person {
abstract show(): void;
}
class XiaoMing extends Person {
show() {
console.log("1111111");
}
kan() {
console.log(222222222);
}
}
const xiao: Person = new XiaoMing();
xiao.show()
xiao.kan(); // error: 因为xiao定义了Person类型,里面只能有show方法
readonly修饰符
只读属性必须在声明时或构造函数里被初始化。
class Person {
readonly code: string = "pidan";
}
class XiaoMing extends Person {
name: string;
constructor(name: string) {
super();
this.name = name;
}
show() {
console.log(`${this.name} - ${this.code}`);
}
}
const xiao = new XiaoMing("123");
xiao.show();
静态static
类型判断instanceof
访问控制修饰符
TypeScript 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。TypeScript 支持 3 种不同的访问权限。
- public(默认) : 公有,可以在任何地方被访问。
- protected : 受保护,可以被其自身以及其子类访问。
- private : 私有,只能被其定义所在的类访问。
多态
interface Water {
weight: number;
}
class Ice implements Water {
weight: number;
name: number;
constructor(weight: number, name: number) {
this.name = name;
this.weight = weight;
}
}
class Liquid implements Water {
weight: number;
age: number;
constructor(weight: number, age: number) {
this.weight = weight;
this.age = age;
}
}
function showWeight(water: Water) {
console.log(water.weight);
}
const liquid = new Liquid(999, 111);
showWeight(liquid);
命名空间
在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。
namespace Boen {
export class Water {
name: string;
constructor(name: string) {
this.name = name;
}
}
export interface Ice {
type: string;
}
}
// 使用
Boen.Water ...
杂
Git
工作区:写好的代码,以文件形式保存的。
暂存区:add
之后的。
本地仓库:commit
之后的。
Git版本回退
-
查看提交记录
复制指定
commit
的版本id
。git log
-
版本回退
git reset
-
git reset --soft
暂存区、工作区保持不变,本地仓库回滚到指定版本
commit
完成后的那一刻。 -
git reset --mixed
工作区保持不变,本地仓和暂存区 回滚到指定版本。
-
git reset --hard
本地仓、暂存区、工作区都回滚到指定版本。
-
Lodash常用
shuffle
- 拷贝
- 浅拷贝
clone
- 深拷贝
cloneDeep
- 浅拷贝
- 防抖
debounce
跨域
options预检请求
区分简单请求和复杂请求:
- 使用方法
put/delete/patch/post
; - 发送
json
格式的数据(content-type: application/json)
- 请求中带有自定义头部;
在跨域复杂请求的时候才会preflight request
,因为怕对服务器数据造成影响。
请求报文:
Access-Control-Request-Method
:告知服务器实际请求所使用的HTTP方法;Access-Control-Request-Headers
:告知服务器实际请求所携带的自定义首部字段。
响应报文:
Access-Control-Request-Method
:告知客户端被允许使用的HTTP方法;Access-Control-Request-Headers
:告知客户端被允许携带的自定义首部字段。Access-Control-Max-Age
:浏览器指定时间内无需再次发送预检请求。
MV*模式
MVC
Smalltalk-80 MVC解释
- View:管理用户界面的层次。
- Model:提供操作数据的接口,执行业务逻辑。
- Controller:View和Model之间的协作。
用户对View操作后,View将处理的权限移交给Controller。Controller进行应用逻辑(调Model哪个接口、对View数据预处理…),具体的业务逻辑交给Model。当Model变更时通过观察者模式告诉View,View收到消息后向Model请求最新的数据,更新界面。
优缺点
- 优点:逻辑分离(应用逻辑、业务逻辑、展示逻辑)、多视图更新(观察者模式)。
- 缺点:单元测试困难,View无法组件化(高度依赖Model)。
前端思考:
- 前端由于HTTP是单工协议,服务端无法向前端发送消息(websocket除外),只能前端发送请求到后端。
- MVC模式有点类似于Vuex。在Vuex中,更改state状态的唯一方法就是提交mutation,即业务逻辑。进行应用逻辑的类似于action,action中提交的是mutation。展示逻辑就类似于Getters,负责将数据归纳整理,提供给页面想要的数据。
MVP
解释
大致与MVC相同,唯一不同的是Controller变成了Presenter。
用户对View操作后,View将处理的权限移交给Presenter。Presenter进行应用逻辑(调Model哪个接口、对View数据预处理…),具体的业务逻辑交给Model。
不同的是,当Model变更时通过观察者模式告诉Presenter,Presenter通过View提供的接口告诉View。
优缺点
- 优点:便于对Presenter单元测试、View可组件化。
- 缺点:Presenter变得臃肿,不仅有应用逻辑,还有同步逻辑。
MVVM
解释
大致与MVP相同,唯一不同的是将Presenter中的同步逻辑分给了“Binder”。Binder主要是负责View和Model双向绑定。
用户对View操作后,View将处理的权限移交给View-Model。View-Model进行应用逻辑(调Model哪个接口、对View数据预处理…),具体的业务逻辑交给Model。
不同的是,当Model变更时,通过Binder自动把数据更新到View上。当用户对View操作时,也会自动更新到Model。
优缺点
- 优点:解决了大量同步问题,提高代码可维护性。便于测试,由于Binder的存在,Model正确即View正确,减少View同步更新测试。
- 缺点:建立大量View-Model、数据绑定以指令写在View中不便测试。
动态规划
思路:
-
确定状态
f[i][j]
。- 最后一步:最优策略的最后一步。
- 子问题:通过最后一步,把问题规模缩小。
-
转移方程
-
初始条件和边界状态
边界状态即数组不可越界。初始状态一般类似于f(0) = 0。
-
计算顺序
一维从小到大,二维从左到右、从上至下。
前端跨域
什么是跨域?
当端口号、域名、协议三者有一不同,即为跨域。
实战解决方案
-
服务端设置cors白名单
-
Nginx代理
前端想要访问的内容,本质是存在跨域问题的。但在客户端和服务端之间增加了一层,这一层与服务端同源,同时又允许客户端访问。客户端访问的就是中间这一层,而中间这一层再请求真实的服务端,返回数据。
前端代理
api
接口,存在多个地址的问题,它们可能来自多个ip。而跑前端项目时,请求的baseIP固定,而真实的接口IP又不一致,所以请求不到接口。
解决方案
- 前端本地做代理,请求接口时,更改baseIP。
- Nginx代理。
webpack
devDependencies 和 dependencies 的区别
- devDependencies:开发时依赖。
- dependencies:运行时依赖。
工程项目,不外部使用。npm i
时,devDependencies
和dependencies
的依赖都会下载。
工具库。外部使用时,npm i
只下载dependencies
的依赖。
三种情况:
-
工程项目和工具库都要用到的依赖。
工程项目设置
dependencies
公用依赖,工具库设置peerDependencies
。- 当工程项目显示设置
dependencies
,将忽略工具库的peerDependencies
; - 当工程项目未显示设置
dependencies
,将使用工具库的peerDependencies
; - 当用户依赖的版本、各插件依赖的版本之间不相互兼容,会报错让用户自行修复;
- 当工程项目显示设置
-
工程项目不会用的依赖,工具库用到的依赖。
工具库设置
- 业务依赖:
dependencies
- 开发时依赖:
devDependencies
- 业务依赖:
ESLint
开始
-
安装
$ npm install eslint --save-dev
-
生成配置文件
npx eslint --init
Tips:
npx
会自动查找当前依赖包中的可执行文件,如果找不到,就会去PATH
里找。如果依然找不到,就会帮你安装! -
校验
// 文件 npx eslint file_path // 目录 npx eslint path
-
自动修复
npx eslint {file_path or path} --fix