目录
- HTML CSS
- Flex布局
- 使用flex布局
- 容器属性
- 项目属性
- Canvas
- 线条
- 矩形
- 圆
- 文本
- 图像
- 渐变
- 水印实战
- JavaScript
- ES6
- Symbol-新数据类型
- 定义
- 描述
- 用途
- 注意事项
- 数据结构
- Set
- WeakSet
- Map
- WeakMap
- 运算符的扩展(ES6)
- 指数运算符`**`
- 链判断运算符`?.`
- Null判断运算符`??`
- 文字翻转
- 类型判断
- `typeof`
- `instanceof`
- 同源、跨域页面间通信
- 同源页面
- 跨域页面
- 内存泄漏
- 引起的原因
- Garbage Collection(GC)
- Websocket
- 基本用法
- 方法
- 事件
- try...catch
- 语法
- 注意事项
- 全局变量污染
- Web Workers
- 基本使用
- 字符串常用方法
- 查找
- 截取
- 替换
- 重复
- 数组常用方法
- 检测
- 数组与字符串互转
- 转为字符串
- 转为数组
- 元素操作
- 添加
- 删除
- 移动
- 替换
- 清空数组
- 创建数组
- 数组截取
- 查找
- 合并
- 排序
- 翻转
- 集合运算
- 去重
- 交集
- 并集-交集
- 差集
- 高阶
- 对象
- 属性
- 属性特征
- 删除属性
- 禁止添加属性
- 封闭对象
- 冻结对象
- 检测某属性是否存在
- 访问器
- 遍历
- 拷贝
- 创建对象
- if()表达式和==原理
- if()
- ==
- Boolean()
- Number()
- Math
- Date
- 获取时间戳
- 时间戳转ISO时间
- 格式化
- 原型
- 含义
- 查找
- 设置原型
- constructor
- Vue2
- 创建全局组件
- 计算属性
- 原理
- key
- v-for
- 同步组件和异步组件
- 同步组件
- 异步组件
- 区别
- 组件
- prop
- 插槽
- 使用
- 插槽内容使用子组件数据
- 注意事项
- 访问组件或元素
- 访问根实例
- 访问父组件
- 访问子组件
- 依赖注入
- vue生命周期
- vue自定义指令
- 全局注册
- 局部注册
- forceUpdate
- 过滤器
- 创建过滤器
- 使用
- 混入
- 插件
- 开发插件
- 使用插件
- 过渡
- 单元素/组件过渡
- 过渡类名
- 自定义类名
- 注意事项
- 多元素过渡
- 过渡模式
- 注意事项
- 多组件过渡
- 列表过渡
- 可复用过渡组件
- 动态过渡
- 模板template编译原理
- 数组变化监听
- Vue3
- 生命周期(组合式API)
- 错误拦截
- 响应式原理
- 格式
- 响应式
- 计算属性
- watch监听
- ref
- 编译宏命令
- 组件
- v-model
- 禁用 Attributes 继承
- 透传
- 依赖注入
- 父组件
- 子组件
- 传输的数据
- TS
- 数据类型
- 手动指定类型
- 类型断言
- 接口
- 变量声明
- 函数声明
- 函数
- 基本语法
- 函数重载
- 推断类型
- 参数
- 泛型
- 简介
- 泛型约束
- 枚举
- 元组
- 联合类型
- 类
- 继承extends
- 抽象类
- readonly修饰符
- 静态static
- 类型判断instanceof
- 访问控制修饰符
- 多态
- 命名空间
- 杂
- Git
- Git版本回退
- Lodash常用
- 跨域
- options预检请求
- MV*模式
- MVC
- Smalltalk-80 MVC解释
- 优缺点
- 前端思考:
- MVP
- 解释
- 优缺点
- MVVM
- 解释
- 优缺点
- 动态规划
- 前端跨域
- 什么是跨域?
- 实战解决方案
- 前端代理
- webpack
- devDependencies 和 dependencies 的区别
- ESLint
- 开始
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", // 永远不成文鼠标事件的 targetcontainer: 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图片URLreturn 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.datapostMessage(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或undefinedif (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: `<inputv-bind:value="message"v-on:input="$emit('input', $event.target.value)">` })
小知识:
update:myPropName
模式可以用.sync
修饰符使父子组件通信的prop
进行双向绑定。-
子组件
数据发生变化时
this.$emit('update:title', newtitle)
-
父组件
-
刨根问底
<text-documentv-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