容器
.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
属性,允许单个项目在交叉轴上有不一样的对齐方式。
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" })
);
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()
能够获取所有属性。类似数组,但所有value唯一。
常用
size
add
has
delete
clear
:清空。values
:获得Set迭代对象。与数组互转
Array.from()
、[...set]
new Set(arr)
和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);
类似对象,但所有key唯一,且key可以是任意值(对象key本质是字符串)。
常用
set
,key同样时,即为更改。new Map([[key, value], [], ...])
delete
has
、get
entries
,可以解构一下[key, value]keys
values
for...of
forEach
与数组互转
[...map]
new Map([[key, value], [], ...])
与map差不多,但键key必须是引用数据类型,且WeakMap是弱引用类型。
变化:size
、keys
等方法都用不了,因为是弱引用,不加入垃圾回收机制。
**
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、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)
不再用到的内存,没有及时释放,叫做内存泄漏。
标记清除法
根对象开始寻找可引用的对象,未被引用的对象移出。
引用计数法
新引用+1,移出引用-1,引用为0的对象回收。
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 (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
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)
注意
arr.length
。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(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() |
---|---|---|
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 |
总结:
类型 | 值 | 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 |
总结:
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
Number(date)
date.valueOf()
date.getTime
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__
Fn.prototype.constructor === Fn
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
,从此每一个属性都将被收集到计算属性的依赖。
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
attribute 即可。
永远不要把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
父组件
// 法一:有缺陷,初始值传不过去
// 法二:刨根问底
// or
子组件
Vue.component('Son', {
props: ['message'],
template: `
`
})
小知识: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
并作为本地数据使用。
props: ['initCount'],
data() {
return {
count: this.initCount
}
}
带有默认值的对象
对象或数组默认值必须从一个工厂函数获取
props: {
propA: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
}
}
a.vue
App.vue
header
footer
main
子组件
// propName是子组件内部数据
插槽内容
// allProp可以使用所有插槽的所有属性,通过打点区分。
name
的
默认名称为“default”。v-slot
只能添加在
上。this.$root
this.$parent
this.$children
ref
父组件使用实例新选项provide
provide() {
return {
getMap: this.getMap
}
}
子组件使用实例新选项inject
inject: ['getMap']
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.directive('focus', {
inserted: function(el) {
el.focus()
}
})
实例新选项directives
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
使用:v-focus
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
常在双花括号插值、v-bind
表达式中用于文本格式化,需被添加在JavaScript
表达式尾部。
如:
{{ message | capitalize }}
局部
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
常用于:
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+)v-enter
类名在节点插入 DOM 后不会立即删除,而是在 animationend
事件触发时删除。可使用v-if/v-else
。
transition
新props
:mode
mode常用值:
out-in
:当前元素过渡完,新元素再来。in-out
:新元素先过渡,当前元素再走。key
。可使用动态组件。
常用props
:
tag
:默认span
标签。
为真实元素,默认span
。
使
或
成为根组件。
所有属性都是动态可绑定的。
流程
定位模板
寻找根节点$el
mount
,下一步。寻找模板template
,render函数编译。
解析器:生成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()
setup
组合式API特有,在created之前
onBeforeMount
onMounted
onBeforeUpdate
props
发生变更时,会调用此方法。
onUpdated
onBeforeUnmount
onUnmounted
app.config.errorHandler = (err) => {
/* 处理错误 */
console.log(err);
};
Proxy
reactive
:只能用引用数据类型ref
:all。
value
属性的ref对象。.value
。computed(Function)
方法:const unwatch = watch(ele, (newVal, oldVal) => {}, options)
参数:
ele
可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。watchEffect()、watchPostEffect()
允许我们自动跟踪回调的响应式依赖unwatch()
注意:
ref支持访问模板、v-for、函数、组件
访问子组件:const refName = ref(null)
注意:
的组件是默认私有的。父组件无法访问子组件,需要子组件在其中通过
defineExpose
宏显式暴露。
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
b
})
</script>
defineExpose
,暴露子组件属性。defineProps
,定义子组件props
defineEmits
通过 ,导入的组件都在模板中直接可用。
v-model
在组件上都是使用 modelValue
作为 prop,并以 update:modelValue
作为对应的事件。
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));
官方文档:https://www.tsdev.cn/type-inference.html
any、number、string、boolean、数组、元组、enum、void、null、undefined、never
or value as type
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'
}
枚举值:
const num = [1,'2',false,4]
可放任意类型的数组。
类型即可是type1,也可是type2.
const num:type1 | type2;
子类只能继承一个类,但可以多重继承。
注意:
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方法
只读属性必须在声明时或构造函数里被初始化。
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();
TypeScript 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。TypeScript 支持 3 种不同的访问权限。
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 ...
工作区:写好的代码,以文件形式保存的。
暂存区:add
之后的。
本地仓库:commit
之后的。
查看提交记录
复制指定commit
的版本id
。
git log
版本回退
git reset
git reset --soft
暂存区、工作区保持不变,本地仓库回滚到指定版本commit
完成后的那一刻。
git reset --mixed
工作区保持不变,本地仓和暂存区 回滚到指定版本。
git reset --hard
本地仓、暂存区、工作区都回滚到指定版本。
shuffle
clone
cloneDeep
debounce
区分简单请求和复杂请求:
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
:浏览器指定时间内无需再次发送预检请求。用户对View操作后,View将处理的权限移交给Controller。Controller进行应用逻辑(调Model哪个接口、对View数据预处理…),具体的业务逻辑交给Model。当Model变更时通过观察者模式告诉View,View收到消息后向Model请求最新的数据,更新界面。
大致与MVC相同,唯一不同的是Controller变成了Presenter。
用户对View操作后,View将处理的权限移交给Presenter。Presenter进行应用逻辑(调Model哪个接口、对View数据预处理…),具体的业务逻辑交给Model。
不同的是,当Model变更时通过观察者模式告诉Presenter,Presenter通过View提供的接口告诉View。
大致与MVP相同,唯一不同的是将Presenter中的同步逻辑分给了“Binder”。Binder主要是负责View和Model双向绑定。
用户对View操作后,View将处理的权限移交给View-Model。View-Model进行应用逻辑(调Model哪个接口、对View数据预处理…),具体的业务逻辑交给Model。
不同的是,当Model变更时,通过Binder自动把数据更新到View上。当用户对View操作时,也会自动更新到Model。
思路:
确定状态
f[i][j]
。转移方程
初始条件和边界状态
边界状态即数组不可越界。初始状态一般类似于f(0) = 0。
计算顺序
一维从小到大,二维从左到右、从上至下。
当端口号、域名、协议三者有一不同,即为跨域。
服务端设置cors白名单
Nginx代理
前端想要访问的内容,本质是存在跨域问题的。但在客户端和服务端之间增加了一层,这一层与服务端同源,同时又允许客户端访问。客户端访问的就是中间这一层,而中间这一层再请求真实的服务端,返回数据。
api
接口,存在多个地址的问题,它们可能来自多个ip。而跑前端项目时,请求的baseIP固定,而真实的接口IP又不一致,所以请求不到接口。
解决方案
工程项目,不外部使用。npm i
时,devDependencies
和dependencies
的依赖都会下载。
工具库。外部使用时,npm i
只下载dependencies
的依赖。
三种情况:
工程项目和工具库都要用到的依赖。
工程项目设置dependencies
公用依赖,工具库设置peerDependencies
。
dependencies
,将忽略工具库的peerDependencies
;dependencies
,将使用工具库的peerDependencies
;工程项目不会用的依赖,工具库用到的依赖。
工具库设置
dependencies
devDependencies
安装
$ 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