css
c3选择器
1.相邻兄弟选择器
匹配E元素后紧邻的F元素(有且仅有一个)
eg: .box+h1
{color:blue;}
匹配.box后紧邻的h1元素
2.E~F
通用兄弟选择器
匹配E元素后所有的F元素(有可能匹配到多个)
eg: .box~h1{color:blue;}
匹配.box后所有的h1元素
3.:first-child
匹配某个父元素下第一个子元素
eg: ul li:first-child{color:red;}
匹配ul下第一个子元素li
4.:last-child
匹配某个父元素下最后一个子元素
eg: ul li:last-child{color:blue;}
匹配ul下最后一个子元素li
5.:nth-child(n)
匹配父元素下第n个子元素
eg: ul li:nth-child(n+5)
{color:pink;}
匹配ul下第5个以后的子元素li
eg: ul li:nth-child(-n+5)
{color:pink;}
匹配ul下前5个子元素li
6.:first-of-type
匹配某个父元素下指定类型的第一个子元素
eg: ul li:first-of-type{color:red;}
匹配ul下li这种类型的子元素中的第一个
7.:nth-of-type(n)
匹配父元素下指定类型的第n个子元素
eg: ul li:nth-of-type(3){color:pink;}
匹配ul下li这种类型的子元素中的第3个
8.:only-child
匹配父元素下唯一一个子元素
9.:only-of-type
匹配父元素下指定类型的唯一一个子元素
10.:empty
匹配内容为空的元素(空格,回车换行符都不能有)
11.:root
匹配根元素html
12.E[attr] 匹配含有attr属性的E元素
eg: a[title]
{text-decoration:none;}
匹配含有title属性的a元素
13.E[attr=val] 匹配含有attr属性并且值为val的E元素
eg: a[title=“123”]
{color:red;}
匹配含有title属性并且值为123的a元素
14.E[attr^=val] 匹配含有attr属性并且值以val开头的E元素
eg: a[href^=“http”]
{color:gray;}
匹配含有href属性,并且值以http开头的a元素
15.E[attr$=val]
匹配含有attr属性并且值以val结尾的E元素
eg: a[href$=“com”]
{background:orange;}
匹配含有href属性,并且值以com结尾的a元素
16.E[attr|=val] 匹配含有attr属性并且值以val-开头或值为val的E元素
eg: a[class|=“link”]
{color:blue;}
匹配含有class属性,并且值以link-开头,或值为link的a元素
17.E[attr*=val] 匹配含有attr属性,并且属性值中含有val的E元素
eg: a[title*=“教育”]
{color:red;}
匹配含有title属性,并且属性值中含有教育两个字的a元素
18.E[attr~=val] 匹配含有attr属性,并且属性值中含有val这个词的E元素
eg: a[title~=“教育”]
{color:green;}
匹配含有title属性,并且属性值中含有教育这个词的a元素
flex
flex三个属性(flex-grow flex-shrink flex-basis|auto|initial|inherit;
):
flex-grow 属性用于设置或检索弹性盒子的扩展比率。属性值number 一个数字,规定项目将相对于其他灵活的项目进行扩展的量。默认值是 0。
flex-shrink 属性指定了 flex 元素的收缩规则。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。
flex-basis 属性用于设置或检索弹性盒伸缩基准值。属性值number 一个长度单位或者一个百分比,规定灵活项目的初始长度。
.box1{
flex: 1 2 1000px;
}
.box2{
flex: 2 1 500px;
}
.box3{
flex: 1 1 250px;
}
上面3个box,当他们的父元素宽度大于1750px时,他们增加宽度的比例为1:2:1;当他们宽度小于1750px时,他们减少的宽度比例为2x1000:1x500:1x250(8:2:1)。
JS
闭包
由于在js中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的能够读取该函数内部变量的函数
。
原型链
每个实例对象都有一个__proto__属性,指向它的原型对象,和它构造函数的prototype指向的是同一对象。当该对象查找某一个属性时,自身没有就沿着__proto__去它的原型上找,再找不到就去原型的原型上找,直到找到Object.prototype.proto=null为止,这就形成了原型链。
this
- 作为纯粹的函数调用 this 指向全局对象
- 作为对象的方法调用 this 指向调用对象
- 作为构造函数被调用 this 指向新的对象(new 会改变 this 的指向)
- apply 调用 this 指向 apply 方法的第一个参数
JS的数据类型有哪些?
7种:String
、Number
、boolean
、null
、undefined
、Object
、Symbol(es6)
基本类型(单类型): String、Number、boolean、null、undefined、Symbol(es6)
引用类型:Object。里面包含的 function、Array、Date
JS判断数据类型
1.使用Object.prototype.toString.call
console.log(Object.prototype.toString.call(bool));//[object Boolean]
console.log(Object.prototype.toString.call(num));//[object Number]
console.log(Object.prototype.toString.call(str));//[object String]
console.log(Object.prototype.toString.call(und));//[object Undefined]
console.log(Object.prototype.toString.call(nul));//[object Null]
console.log(Object.prototype.toString.call(arr));//[object Array]
console.log(Object.prototype.toString.call(obj));//[object Object]
console.log(Object.prototype.toString.call(fun));//[object Function]
function Person(){}
function Student(){}
Student.prototype = new Person()
var haoxl = new Student()
console.log(Object.prototype.toString.call(haoxl));//[object Object]
2.使用typeof
console.log(typeof bool); //boolean
console.log(typeof num);//number
console.log(typeof str);//string
console.log(typeof und);//undefined
console.log(typeof nul);//object
console.log(typeof arr);//object
console.log(typeof obj);//object
console.log(typeof fun);//function
由结果可知typeof
可以测试出number
、string
、boolean
、undefined
及function
,而对于null
及数组
、对象
,typeof均检测出为object
,不能进一步判断它们的类型。
3.使用instanceof
console.log(bool instanceof Boolean);// false
console.log(num instanceof Number);// false
console.log(str instanceof String);// false
console.log(und instanceof Object);// false
console.log(arr instanceof Array);// true
console.log(nul instanceof Object);// false
console.log(obj instanceof Object);// true
console.log(fun instanceof Function);// true
var bool2 = new Boolean()
console.log(bool2 instanceof Boolean);// true
var num2 = new Number()
console.log(num2 instanceof Number);// true
var str2 = new String()
console.log(str2 instanceof String);// true
function Person(){}
var per = new Person()
console.log(per instanceof Person);// true
function Student(){}
Student.prototype = new Person()
var haoxl = new Student()
console.log(haoxl instanceof Student);// true
console.log(haoxl instanceof Person);// true
instanceof不能区别undefined
和null
,而且对于基本类型如果不是用new声明的则也测试不出来,对于是使用new声明的类型,它还可以检测出多层继承关系。
4.使用constructor
undefined和null没有contructor属性
console.log(bool.constructor === Boolean);// true
console.log(num.constructor === Number);// true
console.log(str.constructor === String);// true
console.log(arr.constructor === Array);// true
console.log(obj.constructor === Object);// true
console.log(fun.constructor === Function);// true
console.log(haoxl.constructor === Student);// false
console.log(haoxl.constructor === Person);// true
constructor
不能判断undefined
和null
,并且使用它是不安全的,因为contructor
的指向是可以改变的
0、false、null、undefined、""的相等关系
互不全等
0==""==false
null==undefined
es6部分
let和const
两者都有块级作用域,let声明的是变量,const声明的是常量
模板字符串
var a = 'abc'
var b = '123'
var c = `abc${b}` // 'abc123'
vue
组件通信
1.父子通信: 父向子传递数据是通过 props
,子向父是通过 $on、$emit
;通过 $parent
/ $children
通信;$ref
也可以访问组件实例;provide
/ inject
;$attrs
/ $listeners
;
2.兄弟通信: EventBus
;Vuex
;
3.跨级通信: EventBus
;Vuex
;provide
/ inject
;$attrs
/ $listeners
;
vuex
什么是MVVM?
MVVM是Model-View-ViewModel的缩写。数据模型和UI组件之间通过ViewModel连接起来,两者之间是双向交互的,其中一个发生变化另一个也会随之自动变化,无需人为干涉。
v-for 为什么使用 key?
因为 vue 在更新渲染 dom 的时候是根据新旧 dom 树进行对比的,使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
vue 中双向数据绑定的原理是什么?
observer用来实现对每个vue中的data中定义的属性循环用Object.defineProperty()实现数据劫持,以便利用其中的setter和getter,然后通知订阅者,订阅者会触发它的update方法,对视图进行更新。
在vue中v-model,v-bind,{{}}等都可以对数据进行显示,也就是说假如一个属性都通过这三个指令了,那么每当这个属性改变的时候,相应的这个三个指令的html视图也必须改变,于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者,其订阅者只是更新自己的指令对应的数据,也就是v-model='name’和{{name}}有两个对应的订阅者,各自管理自己的地方。每当属性的set方法触发,就循环更新Dep中的订阅者。
vue 路由钩子函数
1.全局导航钩子
全局前置守卫:router.beforeEach
全局解析守卫:router.beforeResolve
全局后置钩子:router.afterEach
2.路由独享的守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
3.组件内的守卫
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
beforeRouteLeave
-beforeEach
-beforeRouteUpdate
-beforeEnter
-beforeRouteEnter
-beforeResolve
-afterEach
-beforeRouteEnter
Vue模版编译原理
简单说,Vue的编译过程就是将template
转化为render
函数的过程。会经历以下阶段:
- 生成AST树(抽象语法树)
- 优化
- codegen
首先解析模版,生成AST语法树
(一种用JavaScript对象的形式来描述整个模板)。
使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点
)我们就可以跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST树转换为可执行的代码
。
Vue2.x和Vue3.x渲染器的diff算法分别说一下
Vue2.x大致的流程为:
我们可以理解为有旧的Vnode数组和新的Vnode数组这两个数组
然后有四个变量充当指针分别指到两个数组的头尾
重复下面的对比过程,直到两个数组中任一数组的头指针超过尾指针,循环结束
1.对比两个数组的头部,如果找到,把新节点patch到旧节点,头指针后移
2.对比两个数组的尾部,如果找到,把新节点patch到旧节点,尾指针前移
3.然后互相交叉对比,旧尾新头,如果找到,把新节点patch到旧节点,并插入到正确位置,旧尾指针前移,新头指针后移
4.继续互相交叉对比,旧头新尾,如果找到,把新节点patch到旧节点,并插入到正确位置,新尾指针前移,旧头指针后移
5.都没有,开始用新指针对应节点的key去旧数组中直接找
i.如果没有key,创建新的节点
ii.如果有key并且是相同的节点,把新节点patch到旧节点,并插入到正确位置
iii.如果有key但是不是相同的节点,创建新节点
循环结束后,
1.先对比旧数组的头尾指针,如果旧数组遍历完了(可能新数组没遍历完,有漏添加的问题),添加新数组中漏掉的节点
2.再对比新数组的头尾指针,如果新数组遍历完了(可能旧数组没遍历完,有漏删除的问题),删除旧数组中漏掉的节点
总结:旧头新头,旧尾新尾,旧尾新头,旧头新尾,以上都没有就循环查找。最后该新增的新增,该删除的删除。
Vue3.x大致的流程为:
在Vue3.0中,在模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。「也就是在生成VNode的时候,同时打上标记,在这个基础上再进行核心的diff算法」并且 PatchFlag 会标识动态的属性类型有哪些,比如这里 的TEXT 表示只有节点中的文字是动态的。而patchFlag的类型也很多。这里直接引用一张图片。
总结:Vue3.0对于不参与更新的元素,做静态标记并提示,只会被创建一次,在渲染时直接复用。
再说一下虚拟Dom以及key属性的作用
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。
Vue2的Virtual DOM借鉴了开源库snabbdom的实现。
Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。
(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。)
VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。
key的作用是尽可能的复用 DOM 元素
。
新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。
keep-alive
keep-alive
可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性include/exclude
,允许组件有条件的进行缓存。
两个生命周期activated/deactivated
,用来得知当前组件是否处于活跃状态。
keep-alive
包裹的页面第一次进入,钩子的触发顺序created
-> mounted
-> activated
,退出时触发deactivated
。当再次进入(前进或者后退)时,只触发activated
,created、mounted、deactivated
均不触发。
如何实现a->b->c均刷新页面,c->b走缓存(b页面不变)
方法1:
动态增减include
数组里的值。在b页面的beforeRouteLeave
中判断,如果to
是c页面就在include
中添加b,如果是a页面就在include
中删除b。
方法2:
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
{
path: '/1',
name: 'components1',
component: Components1,
meta: {
keepAlive: true //判断是否缓存
}
},
动态修改meta中keepAlive的值,修改时机同方法1。
Vue中组件生命周期调用顺序说一下
组件的调用顺序都是先父后子
,渲染完成的顺序是先子后父
。
组件的销毁操作是先父后子
,销毁完成的顺序是先子后父
。
都是父组件先发起操作,子组件先完成操作。
加载渲染过程
父beforeCreate
->父created
->父beforeMount
->子beforeCreate
->子created
->子beforeMount
- >子mounted
->父mounted
子组件更新过程
父beforeUpdate
->子beforeUpdate
->子updated
->父updated
父组件更新过程
父 beforeUpdate
-> 父 updated
vue组件化的意义 vue的渲染粒度
销毁过程
父beforeDestroy
->子beforeDestroy
->子destroyed
->父destroyed
SSR(服务端渲染)
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。
SSR有着更好的SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate
和created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。还有就是服务器会有更大的负载需求。
Vue的性能优化
长列表优化
编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理
- SPA 页面采用keep-alive缓存组件
- 在更多的情况下,使用v-if替代v-show
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖(将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。)、节流(使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。)
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
SEO优化
- 预渲染
- 服务端渲染SSR
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
用户体验
- 骨架屏
- PWA