基础相关
html
-
css
盒模型
margin
、border
、padding
、content
box-sizing
:content-box(默认)
、border-box
inline
:<a>、<span>、<br>、<i>、<em>、<strong>、<label>、<q>、<var>、<cite>、<code>
block
:<div>、<p>、<h1>-<h6>、<ol>、<ul>、<dl>、<table>、<address>、<blockquote> 、<form>
inline-block
:image
、input
居中
水平:
内联:
text-align: center
块元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// transform
.son {
position: absolute;
left: 50%;
margin-left: - 1/2 width; // 或者
transform: translateY(-50%);
}
// flex
.father {
display: flex;
justify-content: center;
}
// 常规
.son {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto
}
垂直
内联:
line-height: height
块元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// transform
.son {
position: absolute;
top: 50%;
margin-top: - 1/2 height; // 或者
transform: translateY(-50%)
}
// flex
.father {
display: flex;
align-item: center;
}
// 常规,同上
.son {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto
}
- 完全:
flex
- flex-direction: row, row-reserve, column, column-reserve
- flex-wrap: wrap, no-wrap, wrap-reserve
- flex-flow: flex-direction + flex-wrap 组合
- justify-content: flex-start, flex-end, center, space-between, space-around
- align-content: stretch, flex-start, flex-end, center, space-between, space-around
- align-items: stretch, flex-start, flex-end, center, baseline
- align-self: auto, stretch, flex-start, flex-end, center, baseline
grid
- display: grid | inline-grid
- grid-template-column, grid-template-row, grid-auto-column, grid-auto-row
- repeat, auto-fill / auto-fit, fr, minmax(), auto, 网格线的名字
- auto-fill: 完全填充时一样。不完全填充时,保留 原宽度不变
- auto-fit: 完全填充时一样。不完全填充时,会 拉伸原宽度至填充满
- grid-template-area: 区域,后面定位用
- grid-row-gap, grid-column-gap, grid-gap(row | column)
- grid-auto-flow: row, row dense, column, column dense
- 位置
- 容器,整体位置:
- justify-content, align-content, place-content(align | justify)
- start, end, center, stretch, space-around, space-between, space-evenly(等间距)
- 容器,每个项目内元素位置
- justify-items, align-items, place-items, place-items(align | justify)
- start, end, center, stretch
- 项目,每个项目内元素位置
- justify-self, align-self, place-self(align | justify)
- start, end, center, stretch
- 容器,整体位置:
合并
- grid-column-start, grid-column-end, grid-row-start, grid-row-end
1
2
3
4
5
6
7
8
9
10
11
12
13.item-1 {
grid-column-start: 1;
grid-column-end: 2;
}
// 等效于
.item-2 {
grid-column-start: span 2;
}
// 或者
.item-3 {
grid-column-end: span 2;
}grid-column: grid-column-start / grid-column-end
grid-row: grid-row-start / grid-row-end
- 对于
xx-end
也可用span
表示跨越 /
后可省略,默认跨越1格1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.item-1 {
grid-column: 1 / 3;
// grid-column: 1 e("/") 3;
// calc(~'50% - @{bg} - 10px')
}
// 等效于
.item-1 {
grid-column-start: 1;
grid-column-end: 3;
}
// 或者
.item-1 {
grid-column: 1 / span 2;
}
- 对于
grid-area: 区域定位
1
2
3
4
5
6
7
8.item-1 {
grid-area: e;
}
// 等效于
.item-2 {
grid-area: 2 / 2 / 3 / 3
}使用
grid-template-area
的定位- 或者等效于
grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end
伪元素
- 链接相关:link, visited, hover, active
- 常用:
- first-child
- nth-child, nth-of-type, nth-col(odd: 奇数, even: 偶数)
- focus, not, has, root
- before, after: 创建一个已选中元素的第x个子元素
穿透
- 穿透父级/高优先级样式
- /deep/
- >>>
优先级
- 7大优先级,
- 内联
style
、id选择器、属性选择器、类选择器、伪类选择器、元素选择器、通用选择器(*) - 权重计算:
- 1000、100、10*3、1*2
- 不能跨层级
background
- color:
- image: url(‘xxx’)
- position: top, right bottom left center
- repeat: repeat, repeat-x, repeat-y, no-repeat
- attachment:
- local: 相对 元素内容 固定,会在子窗口中随滚动条滚动
- scroll: 相对 元素 固定,不会在子窗口中随滚动条滚动
- size: 固定值,百分比,cover, contain
- 合并写法:url position / size repeat attachment color
- 高级用法: linear-gradient: 渐变
position
- static, fixed, relative, absolute,
- sticky: 粘性定位,相对定位和固定定位的混合。在跨越特定阈值前为相对定位,之后为固定定位。需指定
top
、right
、bottom
、left
4个值其中1个。
js
基础类型
- 值类型:
String
、Number
、Null
、Undefined
、Boolean
- 保存在
栈
中
- 保存在
- 引用类型:
Object
、Array
、Date
、Function
- 变量名保存在
栈
中,变量值保存在堆
中
- 变量名保存在
原型/原型链
箭头函数不能用来继承,因为没有
prototype
属性原型
- 原型,又叫原型对象,指构造函数的
prototype
, 比如Father.prototype
- 原型的作用就是共享方法,
Father.prototype.method
上的方法,可以被共享 - 原型中的
this
指向实例
- 原型,又叫原型对象,指构造函数的
原型链
原型与原型之间相链接的过程即为 原型链
实例的
__proto__
指向的是构造函数的protoType
原型对象:console(obj.__proto__ === Star.prototype) // true
原型查找方式
- 查找obj实例上是否有
dance
方式:this.dance = function(){}
- 查找Star构造函数 原型对象
prototype
上是否有dance
方法:Star.prototype.dance = xxx
- 查找Object原型对象
prototype
上是否有dance
方法:Object.prototype.dance = xxx
- 还没找到,就报错
- 查找obj实例上是否有
原型构造器
原型的构造器指向构造函数
1
2console.log(Star.prototype.constructor === Star) // true
console.log(obj.__proto__.constructor === Star) // true
Star.prototype = {}
会丢失构造器,所以一般用Star.prototype.xxx = function() {}
继承
call
只可以继承 属性,要继承 函数 的话,需要使用 原型链继承方法一:利用
Son.prototype = Father.prototype
改变原型指向,但子类增加原型方法,也会影响到父类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Father(name) {
this.name = name
}
Father.prototype.dance = function () {..}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = Father.prototype
// 为子类添加方法
Son.prototype.sing = function () {...}
let son = new Son('xiaohong', 18)
// 此时父类也被影响了
console.log(Father.prototype) // {dance: f, sing: f, constructor: f}
console.log(Father) // f Father(name) {}方法二:子类的原型指向父类的实例
Son.prototype = new Father()
, 这样就可以顺着原型链继承父类的方法了。并且子类添加原型方法的时候,不会影响父类1
2
3
4
5
6
7
8
9
10
11
12
13
14function Father(name) {
this.name = name
}
Father.prototype.dance = function () {...}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = new Father()
Son.prototype.sing = function() {...}
let son = new Son('xiaoming', 20)
// 此时父类不受影响
console.log(Father.prototype) // {dance: f, constructor: f}
console.log(Son.prototype) // Father实例 {name: undefined, sing: f, __proto__: { dance: f, constructor: f }}继承的写法
ES5写法:上面的原型链写法
ES6写法: 类写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Father {
constructor(name) {
this.name = name
}
dance() {...}
}
class Son extends Father {
constructor(name, age) {
super(name)
this.age = age
}
sing() {...}
}
let obj = new Son('xiaohong', 18)
console.log(Father.prototype) // {dance: f, constructor: f}
常用函数
- String: slice, toUpperCase, toLowerCase, indexOf, startsWith, endsWith,padStart, padEnd
- Array: find, filter, map, includes, flat, reduce, slice, splice, concat
- reduce: (reducer, initialValue)
- reducer: (accumulator, currentValue, index, array)
- 非变异方法(不改变现有数组): filter, slice, concat
- Object: keys, hasOwnproperty, values
- Map, Set: has, set, get, delete, entites
正则表达式
^$
*+?
{n, m}
[^xyz]
非贪婪匹配(?): (.*)(&arr=(.))
举例: 匹配url字符串
方法一:
1
2
3
4
5
6
7
8
9
10function getValue (key) {
// 规则 ?|&key=value|&|$, 也就是 /(\?|&)${key}=([^&]*)(&|$)/
// (\?|&): 以?或& 开头
// ([^&]*): 匹配非&字符,贪婪匹配
// (&|$): 以&或最后字符结尾
let url = decodeURI(window.location.href)
let reg = new RegExp(`(\\?|&)${key}=([^&]*)(&|$)`, 'i')
let res = url.match(reg)
return (res && res.length > 2) ? res[2] : ''
}方法二:
1
2
3
4
5
6
7
8import qs from 'qs'
function getValue2 (key) {
let search = window.location.href.split('?')[1]
let params = qs.parse(search)
console.log('params', params, params[key])
return params[key]
},
this
- 普通函数
- 普通函数调用: 全局windows
- 作为对象的函数调用:上级对象
- 作为构造函数调用:new 出来的对象
- call、apply: 传入的上下文
- 箭头函数
- 没有绑定this,它的this取决于该函数外部非箭头函数的this值
setTimeout, setInterval
- 普通函数:指向windows
- 箭头函数:指向外层对象obj
bind,call,apply
都是改变执行的上下文,也就是
this
的指向call, apply: 立即执行
- call: pa1, pa2, pa3, …
- apply: [pa1, pa2, pa3, …]
bind: 只是生成一个新函数,在调用时才执行
1
2
3
4
5
6
7
8
9Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call('123'); // "[object String]"
Object.prototype.toString.call(false); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
// 优化
Object.prototype.toString.call([]).slice(8, -1); // Array
promise、async、await
- promise:
- new Promise, Promise.resolve, Promise.reject
- then, catch
- all: 全成功或1个失败,then/catch, 并行
- race: 1个成功或失败,then/catch, 并行
- allSettled: 全部成功或失败,then, [{status: ‘fulfilled’, value: ‘’}, {status: ‘rejected’, reason: ‘’}]
- async:
- 返回 promise
- await:
- async 内部才能使用
- await 默认只能链式成功的promise,除非加
.catch
for
、for of
内会按顺序执行,其他有回调循环的循环(forEach
、filter
、map
、reduce
)不会按顺序
axios
1
2
3
4
5
6axios.carete({
baseURL: '',
timeout: 30000,
token: '',
headers: {}
})1
2
3axios.interceptors.request.use(request => {
})1
2
3axios.interceptors.response.use(response => {
// response.data, code
})添加请求参数
get
: axios.get(url, { params: {aa: 1, bb: 2} })post
: axios.post(url, { aa: 1, bb: 2 })
防止多次请求
使用axiom.cancelToken = new Axios.CancelToken(function executor(cancel){ // 执行cancel })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56// request interceptor
service.interceptors.request.use(
(config) => {
removePending(config, "请求前触发");
addPending(config);
return config;
},
(error) => {
return Promise.reject(error);
}
);
const pendings = {}
/**
* 添加请求
*/
export let addPending = (config) => {
const { method, url, params, data } = config;
const id = [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
config.cancelToken = new Axios.CancelToken(function executor(cancel) {
if (!pendings[id]) {
pendings[id] = cancel
}
})
return config;
}
/**
* 移除请求
*/
export let removePending = (config) => {
if (!Object.keys(pendings).length) {
return
}
// console.log(who, pendings, '取消前')
let { method, url, params, data } = config;
try {
data = JSON.parse(data)
} catch (error) {
// data
}
const id = [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
const cancel = pendings[id];
if (cancel && typeof cancel === 'function') {
cancel();
delete pendings[id]
}
// console.log(who, pendings, '取消后')
}
/**
* 清空所有pending请求
*/
export let clearPending = () => {
Object.keys(pendings).forEach(c => pendings[c]());
}
防抖和节流
防抖:执行高频函数n秒后才执行x函数,如果期间执行n,重新计时并结束后再执行x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/*
* 1. 函数防抖
* 执行高频函数x秒后才执行n函数
* 场景:dom更新,onresize,input下拉框
* */
function debounce(fn) {
let timeout = null
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.call(this, arguments)
}, 300)
}
}
function sayHi() {
console.log('say hi')
}
let inp = document.getElementById('inp')
inp.addEventListener('input', debounce(sayHi))节流:n秒内只执行一次x函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/*
* 2. 函数节流
* x秒内只执行一次n函数
* 场景:api请求
* */
function throttle(fn) {
let canRun = true
return function () {
if (!canRun) {
return
}
canRun = false
setTimeout(() => {
fn.call(this, arguments)
canRun = true
}, 300)
}
}
function sayH(e) {
console.log(e.target.innerWidth, e.target.innerHeight)
}
window.addEventListener('resize', throttle(sayH))
defer、async
- defer: 异步下载,最后执行
- async: 异步下载,下载完执行
捕获/冒泡
事件有3个阶段:
事件捕获阶段
、事件目标阶段
、事件冒泡阶段
事件捕获阶段:父 –> 子
事件目标阶段:按js添加顺序执行
事件冒泡阶段:子 –> 父
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
var btn = document.getElementById("btn1");
div1.addEventListener("click",function ( event ) {
console.log("div1,false");
},false)
div2.addEventListener("click",function ( event ) {
console.log("div2,false");
},false)
btn.addEventListener("click",function ( event ) {
console.log("btn,false");
},false)
div1.addEventListener("click",function ( event ) {
console.log("div1,true");
},true)
div2.addEventListener("click",function ( event ) {
console.log("div2,true");
},true)
btn.addEventListener("click",function ( event ) {
console.log("btn,true");
},true)
// 结果
// div1,true
// div2,true
// btn,false 这里
// btn,true
// div2,false
// div1,false
ES6+
进阶1(技能方向)
git
基础命令
- git add, git commit, git branch, git checkout, git checkout -b, git pull, git push, git clone
git flows
- master, env-release, env-test, env-dev, feat-xx, hot-fix
- feat: feat -> dev -> test -> master
- hotfix: hotfix -> dev, hotfix -> test, hotfix -> master
变基(rebase)与合并
- 变基都要在
push
前 - 交互式变基:优化log提交记录。合并、修改log、调整顺序
- 普通变基:
- 把A合并到B,代码上等效于,把B变基到A
- 代码层面相同,log不一样
遴选(cherry-pick)
- 把某一条提交记录,合并到分支A
贮藏(stash)
- 把当前修改临时存储起来,需要的时候应用
- 修改文件直接贮藏,新增文件要
git add
后在贮藏
patch
- 打补丁,类似遴选
vue 2 全家桶(重点)
原理
遍历
data
对象,通过Object.defineProperty
添加getter
和setter
方法。- 并
发布订阅和通知
getter
: 添加订阅addSub
setter
: 触发通知notify
- 并
实例化订阅
Watcher
,实例化时触发getter
订阅。修改触发
setter
时,通知DOM
修改模板1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// getter 与 setter
function observe (data) {
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive (obj, key, value) {
// 递归子属性
observe(value)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
console.log('get: ', value)
// 添加订阅: Dep.target的this指向的是dp实例
if (Dep.target) {
dp.addSub(Dep.target)
}
return value
},
set: function reactiveSetter (newValue) {
console.log('set: ', newValue)
value = newValue
// 执行通知
dp.notify()
}
})
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class Dep {
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
notify () {
this.subs.forEach(sub => {
sub.update()
})
}
}
Dep.target = null // 全局属性
function updateDiv (value) {
document.getElementById('div').innerText = value
}
class Watcher {
constructor (obj, key, cb) {
// 手动触发getter,添加「订阅」
Dep.target = this
this.obj = obj
this.key = key
this.value = obj[key]
this.cb = cb
Dep.target = null
}
update () {
// 获取新值
this.value = this.obj[this.key]
// 更新DOM
this.cb(this.value)
}
}
// ---------------------------- 调 用 ----------------------------
let data = { name: 'yy' }
observe(data)
// 模拟解析到 `{{name}}` 触发的操作; 手动触发「订阅」
new Watcher(data, 'name', updateDiv)
// 更新DOM
data.name = 'zz'
new Vue 过程
1 | Vue.prototype._init = function(options) { |
Vue 虚拟DOM
操作 DOM
耗费性能太大,改用虚拟 DOM
- js 创建虚拟
dom对象
- 判断差异
diff
算法- 树的递归,广度优先
- 判断列表差异
- 判断属性的更改
- 遍历子元素打标识
- 渲染差异
vue 基础
生命周期
- beforeCreate:
vue
实例的挂载元素$el
和数据对象data
都是undefined
,还未初始化 - created: 完成了
data
的初始化,$el
还未初始化 - beforeMount:
vue
实例的$el
和data
都初始化了,相关的render
函数首次被调用。实例已完成以下配置:编译模板,把data
里的数据和模板生成html
。 注意此时html
还没有挂载到页面上 - mounted: 在
el
被新创建出来的vm.$el
替换,并挂载到实例上去之后调用。实例已完成以下配置:用编译好的html
内容替换el
属性指向的DOM对象
。完成模板中的html
渲染到html
页面中,此过程中进行ajax
交互 - beforeUpdate: 在数据更新之前调用,发生在虚拟
DOM
重新渲染和打补丁之前调用。可以在该钩子中进一步地更改状态,不会触发附加的渲染状态 - updated: 在由于数据更改导致的虚拟
DOM
重新渲染和打补丁之后调用。调用时,组件DOM
已经更新,所以可以依赖与DOM
的操作。然而在大多数情况下应避免在此期间更改状态,因为这可能会导致无限循环,该钩子在服务器端渲染期间不可用 - activated:
keep-alive
激活时触发 - deactivated:
keep-alive
缓存时触发 - beforeDestroy: 在实例销毁之前,实例仍完全可用
- destroyed: 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子函数在服务器端渲染期间被调用
父子组件渲染顺序
先父后子的原则
多个子组件 单线程 渲染
场景:父组件A,子组件B、C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 进入页面
A.beforeCreate
A.Created
A.beforeMount
B.beforeCreate
B.Created
B.beforeMount
C.beforeCreate
C.Created
C.beforeMount
B.Mounted
C.Mounted
A.Mounted1
2
3
4
5
6
7
8
9
10// 离开页面
A.beforeDestroy
B.beforeDestroy
B.destroyed
C.beforeDestroy
C.destroyed
A.destroyed
双向绑定
- 响应化:
Vue.observable(object)
- 对象和数组要初始化
- 单独响应化
- 对象
Vue.$set(object, key, value)
orvm.$set(object, key, value)
orthis.$set(object, key, value)
Object.assign({}, object, {a: 1, b:2})
- 数组
- 不能响应:用下标修改数组值和修改数组长度
- 修改值:
this.$set(array, index, value)
array.splice(index, 1, value)
- 修改长度:
- array.splice(newIndex)
- 对象
传值(6种)
props
、$emit
中央事件总线
vue bus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// xxx.js
let eventBus = new Vue()
export eventBus
// 创建
import eventBus from '../xxx.js'
eventBus.$emit('name', params)
// 监听
mounted () {
eventBus.$on('name', fn)
},
beforeDestroy () {
eventBus.$off('name')
},
methods: {
fn (params) {
console.log('params', params)
}
}
provide
、inject
多层父子组件,传变量(函数),非响应式
若要变为响应式,两种方法
传递父组件实例过去,即传递
this
1
2
3
4
5
6
7
8
9
10provide () {
return {
xxx: this
}
}
inject: ['xxx']
inject: {
'xxx': () => {}
}通过
Vue.observable()
传递响应式变量1
2
3
4
5
6
7
8provide () {
this.theme = Vue.observable({
color: 'red'
})
return {
theme: this.theme
}
}
vuex
$parent
、$children
、$ref
$parent
: 父组件实例$children
: 子组件实例,不保证顺序$ref
:- 当前组件:
DOM
元素 - 子组件:子组件实例
- 当前组件:
$attrs
、$listeners
$attrs
: 子组件内使用,包含所有 父组件传递了但子组件 props 里未定义 的值,对象结构,值为 {key1: value1, key2: value2}$listeners
: 子组件内使用,包含所有 父组件的非.native 方法。通过下面的操作,可以在多层父子组件传递
1
v-bind="$attrs"
1
v-on="$listeners"
v-on事件修饰符
- https://cn.vuejs.org/v2/api/#vm-listeners
.stop
: 阻止冒泡<button @click.stop="xxx"></button>
.prevent
: 阻止默认行为.capture
: 添加事件监听器时使用capture
模式(捕获).self
: 只当事件是从监听器绑定的元素本身触发时才触发回调.native
: 监听组件根元素的原生事件
prop单向数据流传递
- 子组件的prop调用的父组件数据,当子组件修改时不想修改父组件
- 方法一:
- prop作为
data
初始值,之后使用data
的新值
- prop作为
- 方法二:
- 使用计算属性转换
- 注意: 数组和对象是以引用的方式传递的,所以修改子组件的值时,也会修改父组件的值。这种情况下,需使用
深拷贝
生成新值,然后对新值修改。
computed写法
1
2
3
4
5
6
7
8
9
10
11
12
13computed: {
aa () {
return this.xxx + '11'
},
bb () {
get: function () {
return this.bb + '22'
},
set: function (value) {
this.bb = value + 'xx'
}
}
}
watch写法
aa () {}
aa.bb () {}
1
2
3
4
5
6
7watch: {
cc: {
handler: 'methods',
deep: true,
immediate: true
}
}
computed,watch,methods区别
- watch:监听回调,当依赖的值有改变时,触发回调执行一些逻辑
- computed:计算属性,根据依赖的值动态显示最新的结果,会缓存(
getter
后)。能监听到obj深层key - methods:方法,执行函数
- methods和computed:methods每次渲染时都会计算,而computed会从缓存取值
mixin
混入文件写法和单页面文件一样
调用混入以数组方式引入
mixins: [myMixin]
合并规则
- data: 递归合并,同名时以
组件
优先 - 钩子函数:都会调用,
混入文件
优先调用 - 其他对象(
methods
,components
,directives
): 合并为一个对象,同名时以组件
为准
- data: 递归合并,同名时以
全局混入
1
2
3
4
5
6
7
8Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
keep-alive
缓存未激活的组件
1
2
3<keep-alive>
<component :is="view"></component>
</keep-alive>
include: 字符串或正则,匹配的才会缓存
exclude: 字符串或正则,匹配的都不会缓存
max: 数字,最多缓存多少实例
slot
定义插槽:
<slot name="xxx"></slot>
使用插槽:
<template v-slot:"xxx"></template>
,v-slot
可以缩写为#
当插槽所在的父作用域 要 使用子作用域时,可以把子作用域作为变量传递给父
1
2
3
4
5
6
7<!-- 子 -->
<span>
<slot v-bind:user="user">
{{user.firstName}}
</slot>
<slot name="other" v-bind:dou="dou" v-bind:ruai="ruai" v-bind:mi="mi"></slot>
</span>1
2
3
4
5<!-- 父 -->
<current-user>
<template v-slot:default="{ user }"></template>
<template v-slot:other="{ dou, ruai, mi }"></template>
</current-user>v-slot:aa
、#aa
: 使用插槽v-slot:bb={}
: 使用插槽传值v-slot={}
: 使用插槽传值,默认名,等效于v-slot:default={}
过度、动画效果
- 触发时机
v-if
、v-show
、动态组件
、组件根节点
- 单组件触发
<transition name="t"></transition>
- 使用
t-xx
定义状态样式,如未定义name
, 默认为v-xx
x-enter
、x-enter-active
、x-enter-to
、x-leave
、x-leave-active
、x-leave-to
,一般使用x-enter
、x-enter-active
、x-leave-active
、x-leave-to
4个状态x-enter-active
、x-leave-active
: 激活时状态(稳定)
- 多组件
li
触发<transition-group name="x"></transition-group>
- 样式定义同上
x-move
:位置移动时平滑移动transition: transform 1s
常用API
指令
Vue.directive('name', { ... })
,<button v-name="xxx"></button>
,官网钩子函数:
- bind: 只调用一次,第一次绑定到元素时调用。
- inserted: 被绑定元素插入到父节点时调用。
- update: 组件所在
VNode
更新时调用。 - componentUpdated: 组件所在
VNode
及其子VNode
全部更新后调用。 - unbind: 只调用一次,解绑时调用。
钩子函数参数:所有钩子参数一样,除了
el
其他参数都只读- el: 绑定元素DOM,可直接操作
- binding: 一个对象,包含下面值
- name: 指令名,不包含
v-
前缀 - value: 指令的绑定值。
v-dire="1 + 1"
中,绑定值为2
- oldValue: 指令绑定的前一个值,仅在
update
和componentUpdated
钩子中可用 - expression: 字符串形式的表达式。
v-dire="1 + 1"
中,表达式为1 + 1
- arg: 传给指令的参数,可选。
v-dire:foo
中,参数为foo
- modifiers: 一个包含修饰符的对象。
v-dire.foo.bar
中,修饰符对象为{ foo: true, bar: true }
- name: 指令名,不包含
- vnode: 虚拟节点
- oldVnode: 上一个虚拟节点
实例
v-longpress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59// directive.js
import Vue from 'vue'
Vue.directive('longpress'', {
bind: function (el, binding, vNode) {
// 确保提供的表达式是函数
if (typeof binding.value !== 'function') {
// 获取组件名称
const compName = vNode.context.name
// 将警告传递给控制台
let warn = `[longpress:] provided expression '${binding.expression}' is not a function, but has to be `
if (compName) { warn += `Found in component '${compName}' ` }
console.warn(warn)
}
// 定义变量
let pressTimer = null
// 定义函数处理程序
// 创建计时器( 2秒后执行函数 )
// 不是鼠标左键返回
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 执行函数
handler()
}, 2000)
}
}
// 取消计时器
let cancel = () => {
// 检查计时器是否有值
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// 运行函数
const handler = (e) => {
// 执行传递给指令的方法
binding.value(e)
}
// 添加事件监听器
el.addEventListener('mousedown', start)
el.addEventListener('touchstart', start)
// 取消计时器
el.addEventListener('click', cancel)
el.addEventListener('mouseout', cancel)
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
}
})
vue cli
vue loader
vue router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
hash、history模式
- 默认
hash
模式,即url
上带#
的模式。hashchange
事件 history模式
:url
像正常url一样,需要后端支持。popstate
、pushstate
事件
router、route
router
: 路由实例router.beforeEach
、router.beforeResolve
、router.afterEach
、router.push
、router.replace
、router.go
、router.back
、router.forward
等
this.$route
: 当前路由对象,当前激活的路由对象信息- path:
- params:
- query:
- hash:
- fullPath:
- matched: 一个数组,包含当前路由的所有嵌套路径片段的路由记录
- name:
- redirectedFrom: 如果存在重定向,即为重定向来源的路由的名字
传值
params
:- 不能和
path
一起使用,一起使用时params
会失效 - 传递:
this.$router.push({name: 'xxx', params: {...}})
- 获取:
let a = this.$route.params
- 不能和
query
:- 任何场合
- 传递:
this.$router.push({path: '/setup', query: {}})
导航守卫
- 组件内的守卫
beforeRouteLeave
beforeRouteUpdate
beforeRouteEnter
- 全局导航守卫
beforeEach
afterEach
- 全局解析守卫
beforeResolve
:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
- 路由独享的守卫
beforeEnter
路由导航顺序
- 导航被触发
- 在失活的组件里调用
beforeRouteLeave
守卫 - 调用全局的
beforeEach
守卫 - 在重用的组件内调用
beforeRouteUpdate
守卫 - 在路由配置里调用
beforeEnter
守卫 - 解析异步路由组件
- 在被激活的组件里调用
beforeRouteEnter
守卫 - 调用全局的
beforeResolve
守卫 - 导航被确认
- 调用全局的
afterEach
钩子 - 触发
DOM
更新 - 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的实例会作为回调函数的参数传入
vuex
- 专门为
vue
设计的 状态管理模式
State
状态:
store.state.xxx
或this.$store.state.xxx
store.state.moduleName.xxx
或this.$store.state.moduleName.xxx
1
2
3
4
5
6
7
8
9
10import { mapState } from 'vuex'
computed: {
...mapState({
count1: state => state.count1,
countAlias: 'count'
}),
// 或者
...mapState(['count', 'count1'])
}
Getters
state
的计算属性,但如果有变量传入时,则每次都会计算(不会缓存)this.$store.getters.xxx
或this.$store.getters.modulesName.xxx
通过属性访问
1
2
3
4
5getters: {
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
通过方法访问
1
2
3
4
5
6// 每次访问都会调用,而不会缓存结果
getters: {
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
通过辅助函数
mapGetters
访问1
2
3
4
5
6
7
8
9
10
11
12import { mapGetters } from 'vuex'
computed: {
...mapGetters([
'doneTodosCount',
'otherGetters'
]),
// 或者
...mapGetters({
aliasName: 'doneTodosCount'
})
}
Mutations
修改
state
的唯一方式只能是同步
逻辑只能是修改
state
建议 大写、常量 命名
定义
1
2
3
4
5mutations: {
UPDATE_USER (state, payload) {
state.age += payload.age
}
}
调用
1
2
3
4
5
6this.$store.commit('name', payload)
// or
this.$store.commit({
type: name,
...payload
})1
2
3
4
5
6
7
8
9
10
11
12import { mapMutations } from 'vuex'
methods: {
...mapMutations([
'mA',
'mB'
]),
// 或者,可以重命名
...mapMutations({
aliasName: 'mA'
})
}
Actions
异步,返回
promise
可以写复杂逻辑,一般用于调用
mutations
用法,参数基本和
mutations
一致定义
1
2
3
4
5
6actions: {
increment ({ state, getters, commit, dispatch }, payload) {
// 可以写复杂逻辑
commit('INCREMENT', payload)
}
}
调用
1
2
3
4
5
6this.$store.dispatch('increment', payload)
// 或者
this.$store.dispatch({
type: 'increment',
...payload
})1
2
3
4
5
6
7
8
9
10
11
12import { mapActions } from 'vuex'
methods: {
...mapActions([
'aA',
'aB'
]),
// 或者
...mapActions({
aliasName: 'aA'
})
}
namespace、module
按模块区分,添加
namespaced: true
即可调用需加上
moduleName
this.$store.state.moduleName.xx
、this.$store.getters.moduleName.xx
、this.$store.commit('moduleName/xxx')
、this.$store.dispatch('moduleName/xxx')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19computed: {
...mapState('moduleName', ['xxx', 'yyy']),
...mapGetters('moduleName', ['xxx', 'yyy'])
},
methods: {
...mapMutations('moduleName', ['xxx', 'yyy']),
...mapActions('moduleName', ['xxx', 'yyy'])
}
// 或者使用 createNamespacedHelpers 创建基于命名空间的辅助函数
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
computed: {
...mapState(['xxx', 'yyy']),
...mapGetters(['xxx', 'yyy'])
},
methods: {
...mapMutations(['xxx', 'yyy']),
...mapActions(['xxx', 'yyy'])
}
在带命名空间的模块内调用全局内容
state
和getters
:rootState
和rootGetters
作为第三、第四个参数传给getters
,也会作为context
传给action
mutations
和actions
: 若要在全局命名空间内分发action
或 提交mutation
, 将{ root: true }
作为第三个参数传递给dispatch
或commit
即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
modules
定义全局action
: 添加root: true
, 并将这个action
的定义放在函数handler
中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
动态导入 modules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
export default new Vuex.Store({
modules,
getters
})
动态导入组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// global.js文件
import Vue from 'vue'
function changeStr (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
const requireComponent = require.context('./', false, /\.vue$/)
// 查找同级目录下以vue结尾的组件
const install = () => {
requireComponent.keys().forEach(fileName => {
let config = requireComponent(fileName)
console.log(config) // ./child1.vue 然后用正则拿到child1
let componentName = changeStr(
//fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
fileName.replace(/^\.\/(.*)\.vue$/, '$1')
)
Vue.component(componentName, config.default || config)
})
}
export default {
install // 对外暴露install方法
}
ssr
ui框架
- ui框架类似,以
iview
举例
iview / iview Design
安装
- 全局引入:
vue.use(iview) css
- 按需引入:
css
+babelrc, babel-plugin-import
- 全局引入:
国际化
- i18n
全局配置
Vue.use(ViewUI, {xxx})
validator
prop和key一致
required: true, message: 'xx', trigger: 'blur'
type: 'string', pattern: '/^(0|[1-9][0-9]*)$/', message: 'xx', trigger: 'change'
validator: this.xxx', trigger: 'blur'
1
2
3
4
5
6
7const validateQualityValueMile = (rule, value, callback) => {
if (this.isGasControl && !value && !this.carForm.qualityValueDay) {
console.log('vMile ', value)
return callback(new Error('质量保证期必须填写'))
}
return callback()
}
vant
element-ui
ant-design
iconfont
- 添加图标,命名
- 下载后,添加6个文件(除了demo的css,js),引入
iconfont.css
<span class="iconfont xxx">
vue 3(重点)
option API
composition API
区别(响应速度、原理等)
区别(vue 2)
语法?
ts
基础
- 基本类型:string, number, boolean, null, undefined, Symbol, BigInt
|
: 联合类型,同时只能是多个类型中其中一个&
: 交叉类型,同时是所有类型的所有
进阶
type
: 类型别名,=
赋值interface
: 接口,直接赋值type
与interface
的区别:基本一致。赋值方式不一样;接口可以合并,类型别名需用&
生成 交叉类型
class
:类constructor
: 构造函数extends
: 继承(类-类,类-接口,接口-接口,接口-类)super
: 调用父类的构造函数- 修饰符
public
: 公开,默认private
: 私有的,只能当前类使用protected
: 和private
类似,区别是子类也可以使用
implements
: 类实现接口- 在
TS
里,接口是可以继承类的
abstract
: 抽象类,抽象方法- 抽象类不能被实例化
- 抽象方法必须在子类实现
自带函数
- typeof: 获取实例的类型, type x = typeof xxInstance
- keyof: 获取
type
或interface
的key
的联合类型 - partial: 所有key变为可选
- required: 所有key变为必填
- readonly: 所有key变为只读
- record: 所有key变为指定T类型
- pick: 选择部分key
- extract: 遍历T.keys,返回U中存在的keys的类型,参数和结果都是联合类型
- exclude: 遍历T.keys, 返回U中 不存在的keys类型,参数和结果都是联合类型
- omit: pick+exclude组合,返回 对象中,除几个key外的其他所有类型。和pick对应
webpack
进阶2(管理方向)
脚手架搭建
基础封装
axios
es lint
git-hook
prettier
jenkins
进阶3(面试题)
原型链与继承
js -> 原型/原型链
输入url之后
异步:宏任务与微任务
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7
顺序:同步 -> 宏任务队列1 -> 微任务队列1 -> 宏任务队列2 -> 微任务队列2
宏任务:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
微任务:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
Tips:
promise(A).then(B)
内的A函数为立即执行,B函数为微任务队列B
里如果return promise
,会添加到微任务队列最后,如果return xx(number | string)
,则立即执行
await A; B()
的A函数为立即执行,B函数为微任务队列微任务里创建的微任务(无论多少层),都在当前微任务队列内执行(添加到当前微任务列表最后)
1
2
3
4
5
6
7
8
9promise.resolve().then(() => {
console.log(1)
promise.resolve().then(() => {
console.log(2)
})
}).then(() => {
console.log(3)
})
// => 返回1,2,3
script
执行完后,会直接执行 微任务,所以默认顺序为:同步 -> 微任务1 -> 宏任务2 -> 微任务2 -> …
Promise原理
注册 then
回调函数,push 到 callbacks
里。当 resolve
时,调用 callbacks
里所有函数。
PWA
渐进式应用程序
web worker: 单独开线程执行任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// main.js
const myWorker = new Worker('worker.js')
// 传递
myWorker.postMessage('hello')
// 接收
myWorker.onmessage = function(e) {
console.log(e.data)
}
// worker.js, 回立即执行
self.onmessage = function(e) {
console.log(e.data)
// 向主文件发送信息
self.postMessage('lala')
}
service worker: 可以拦截网络请求,决定走网络还是返回缓存数据,所以可以实现「后退」功能
脚手架发布
package.json.main: "bin/mbs.js"
js取整
- 简书
- parseInt
~~20.25
20.25^0
20.25 << 0
Math
Math.floor
Math.ceil
Math.round
Math.trunc
: 直接取整
js动效
js动效中:
setTimeout
、setInterval
并不是间隔xx
开始渲染页面,而是间隔xx
添加到 任务队列 中,如果队列里已经有任务的话…..requestAnimationFrame(callback)
: 在下一次重绘前,执行动效。重绘是浏览器决定1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const element = document.getElementById('ele')
const start = null
// timestamp: 当前时间戳
function step(timestamp) {
if (start === undefined) {
start = timestamp
}
const elapsed = timestamp - start
element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px )'
// 2s 后停止动画
if (elapsed < 2000) {
window.requestAnimationFrame(step)
}
}
window.requestAnimationFrame(step)
实际面试
this.$nextTick 原理
- 创建异步延迟函数
timerFunc
,在异步延迟结束后,调用传入的回调函数 timerFunc
,延迟调用优先级:Promise.resolve().then()
>MutationObserver
>Setimmediate
>setTimeout
:2微任务,2宏任务nextTick(cb?, ctx)
,如果未传入cb
,会自动返回Promise.resolve()
,可用then
调用后续函数。
vuex缺点
- 内存占用大
vue 渲染原理
- 虚拟DOM啥的
vue 多页面是什么
- 多个html页面,除了主页内是vue-router跳转,其他页面都是a标签跳转到其他页面
- 主页面加载快,多页面SEO好
- 多页面之间跳转慢,因为要加载css和js
js为啥会阻塞页面渲染
- js可能会修改DOM结构,比如
document.write
事件委托/事件代理
- 利用事件冒泡,只为父元素添加事件,进而达到为多个子元素添加事件的效果
- 减少DOM查询,提升性能,
- 可扩展,新增的子元素也可直接代理
- 不冒泡的事件不支持;冒泡过多可能被阻止掉;别人使用时误判导致调用2次
- 掘金
事件循环
Web 性能优化
网络、资源
- 同域名下减少DNS解析
- 开启http2,多路复用
- 压缩图片,懒加载(layzsizes),雪碧图,iconfont图标
- gzip压缩代码
- 静态包,包含下载好的首页等
css
- 修改类名而不是样式
- 减少DOM访问:事件代理
- 减少重排重绘:
- 不使用table
- 开启硬件加速:animations、transforms、transitions不会自动开启,当检测到DOM元素开启某些规则时会开启,比如3D变化
transform: translate3d(250px, 250px, 250px) rotate3d(250px, 250px, 250px, -120deg) scale3d(0.5, 0.5, 0.5)
(左手,右下前)- 有时候不想3d的转换,可以使用小技巧欺骗
transform: translateZ(0)
- 但此时会屏幕闪烁,可以这样解决
backface-visibility: hidden; perspective: 1000
或者transform: translate3d(0, 0, 0)
参考资料 - 元素背面朝向观察者时是否可见;观察者距离z=0平面的距离。
js
- 减少js体积,webpack chunk打包
- web worker 开线程
- 优化代码,减少循环
- 函数节流
commonJs, ES6 module
- commonJs: 动态编译,使用时才编译;可使用变量;值拷贝,不影响
- es6 module: 静态编译,先一次性编译好,后面使用;所以编译的时候就能够分析代码是否被使用,进而使用
tree shaking
;值应用,共享数据