部分概念
- DOM 操作本身应该是同步的(当然,我说的是单纯的 DOM 操作,不考虑 ajax 请求后渲染等)。
- DOM 操作之后导致的渲染等是异步的(在 DOM 操作简单的情况下,是难以察觉的)。
CSS阻塞渲染:由于CSSOM负责存储渲染信息,浏览器就必须保证在合成渲染树之前,CSSOM是完备的,这种完备是指所有的CSS(内联、内部和外部)都已经下载完,并解析完,只有CSSOM和DOM的解析完全结束,浏览器才会进入下一步的渲染。CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染。
JS阻塞页面:JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器在解析HTML时,一旦碰到script,就会立即停止HTML的解析,也阻塞了其后的CSS解析,整个解析进程必须等待JS的执行完成才能够继续。从性能角度上讲,将script放在页面底部,也就合情合理了
重排(Reflow):DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow
触发重排(reflow):
- 1、当增加、删除、修改DOM节点时,会导致reflow或repaint
- 2、当移动DOM的位置,或是插入动画的时候
- 3、当修改CSS样式的时候
- 4、当Resize窗口的时候,或是滚动的时候
- 5、当修改网页的默认字体时
重绘(Repaint):当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。
触发重绘(Repaint):
- 1、DOM改动
- 2、CSS改动
最小化重绘和重排 1.一次性修改样式:减少内联样式使用 样式合并写法 2.批量修改DOM:使用文档片段创建一个子树,然后再拷贝到文档中(document.fragment) 3.缓存布局信息
//每次需要查询div.offsetLeft 浪费性能 div.style.left = 1 + div.offsetLeft + 'px'; div.style.top = 1 + div.offsetTop + 'px'; //将这个值保存下来,避免重复取值 性能优化 current = div.offsetLeft; div.style.left = 1 + ++current + 'px'; div.style.top = 1 + ++current + 'px';
浮动:可能是最全面最易懂的解析前端浮动的文章 - 掘金
为啥有浮动这个东西,业务驱动吧,需要文字环绕图片的功能。position: absolute|fixed
向左或向右边缘碰到父容器边缘或浮动元素
脱离文档流,影响后面的文档流(内部和后面的外部)不懂看前面的链接
处理方案:
(1)伪元素 ::befor ::afer 加载父元素上
(2)BFC
(3)clear:both
(4) <br clear="all" />
BFC-块级格式化上下文
独立的渲染区和 一个隔离独立容器(1)浮动元素,float 除 none 以外的值;脱离文档流
(2)定位元素,position(absolute,fixed);绝对定位和固定定位
(3)display 为以下其中之一的值 inline-block, flex, table-cell,table-caption;后两个这个去细看,不常用
(4)overflow 除了 visible 以外的值(hidden,auto,scroll),他能单独滚动特点:
(1)独立容器不会影响外面
(2).垂直方向上的距离由margin 决定;(解决外边距重叠问题)(3)bfc 的区域不会与 float 的元素区域重叠;(防止浮动文字环绕)
(4)计算 bfc 的高度时,浮动元素也参与计算;(清除浮动)(5)
BFC
就是一个块级元素,块级元素会在垂直方向一个接一个的排列
<div class="container"> <div class="box"> <span>1</span> <span>2</span> </div> <div class="box1"></div> <div class="box2"></div> </div> .container { background: red; /* display: inline-block; */ } .box { display: inline-block; float: left; width: 200px; height: 200px; text-align: left; border: 10px solid black; padding: 15px; box-sizing: content-box; } .box1 { display: inline-block; width: 200px; height: 200px; text-align: left; border: 10px solid black; padding: 15px; box-sizing: border-box; } .box2 { float: right; display: inline-block; width: 100px; height: 100px; text-align: left; border: 10px solid black; padding: 15px; box-sizing: border-box; }
background: red;
/* display: inline-block; */
(1)塌陷(2)Margin边距重叠
A: margin:10
B: margin:10
A距离B 还是margin10
(3)
box-sizing: content-box(标准模式) | border-box(怪异模式)
vue 2.4
$attrs 参数传递
A ->B->CA :name='anme' :age="age"
B v-bind="$attrs" inheritAttrs: true // 继承所有父组件的内容
C :name='anme' :age="age" inheritAttrs: false
vue 2.4
$listeners 事件传递
A ->B->CA @click="handleClick"
B v-on="$listeners"C @click="handleClick"
$listeners 和 $attrs 都是避免组件多层调用的参数和方法多层传递的问题
事件总线
// busVue.js
import Vue from 'vue'
export default new Vue()
Vue的实列
a组件
import Bus from "@/store/modules/busVue.js";
methods: {
handleSelect (item) {
Bus.$emit('changeSelect', item)
}
}
b组件
import Bus from "@/store/modules/busVue.js";
mounted(){
Bus.$on('changeSelect', (val)=>{
console.log(val, '7777777777') })
},}
destroyed() {
EventBus.$off('changeEvent')
main.jsVue.prototype.$EventBus = new Vue();
A组件
this.$EventBus.$emit("videoPause");
B组件this.$EventBus.$on("videoPause", () => {
this.pause();
});
$on $emit $off
$emit
子组件 抛出事件 this.$emit("changeName","法外狂徒张三")
父组件@changeName='changeName'
$on生命周期 create mounted
this.$on("transmit",res=>{ console.log(res)//打印接收到的参数 })
可以自己监听自己
$off 防止$on多次触发,需要$off进行销毁。
destroyed() {this.$off('changeEvent');
}
provide
injectprovide(){
return{
closeCurrent:this.closeCurrent,
reload: this.routeReload
}
},
inject: ["closeCurrent","reload"],
内置指令
v-cloak
使用{{}}展示或更新页面数据时:当网速比较慢时,会出现一个不好的过度现象,会让用户先看到我们的表达式(上面页面中的{{msg}}),然后才看到data中的值(欢迎Vue!)------->即所谓的闪烁问题!
[v-cloak]{
display: none !important;
}
<div id="app" v-cloak>{{msg}}</div>
v-pre
加v-pre指令跳过这个元素和它的子元素的编译过程
vue源码 中 dep
当你data中数据没被使用是不在__ob__中的dep.subs添加监控
添加一个watcher
<div key="1">{{obj.name}}</div>
<div key="2">{{obj.a}}</div>
<div key="3">{{obj.ji}}</div>
watch 再添加一个
computed 中调用一个次,且再页面上调用,就会添加一个watcher,如果有3个只有两个被使用使用就算2个写个demo看看
大概顺序是这么个回事,编译html模板时,发现需要特殊处理的变量,比如v-model=“name”,这个name被发现以后,就准备为其创建watcher,在创建watcher的时候,先把这个watcher挂载到Dep.target这个全局静态变量上,然后触发一次get事件,这样就触发了get函数中的Dep.target && dep.addSub(Dep.target);,等get到了变量以后,也已经添加到subs队列里了,这时候在令Dep.target = null。
而且,在变量改变,触发set函数时,要调用watcher.update()函数,这里也要触发一次get函数。因此可能会重复往subs队列添加watcher,因此还要设置一个可以自增的depid来进行唯一性控制。这也太抽象了,我人晕了。
obj已被使用
arr数组没被使用
vue中渲染过程
数据劫持-->模板解析-->模板渲染-->数据变化视图自动更新
vue中为什么无法监测数组
__ob__
Object.defineProperty 设置不可枚举
所有对象会有这个方法 对象数组
数据初始化的时候添加
其实也可以对数组下标监听,但是数组量大,会影响性能,所以vue监听的时候判断了是否为数组,如果是对象将会进行递归,如果是数组重写__proto__原型链上的方法,如果是多层数组也会使用递归,为对数组下标的拦截太浪费性能,Observer 构造函数传入的数据参数增加了数组的判断// src/obserber/index.js class Observer { // 观测值 constructor(value) { Object.defineProperty(value, "__ob__", { // 值指代的就是Observer的实例 value: this, // 不可枚举 enumerable: false, writable: true, configurable: true, }); } }
单项数据流
父组件可以向值组件传递数据,并且改变值组件的值,而子组件不能改变父组件传递给它的 prop 属性,官方推荐的做法是它抛出事件,通知父组件自行改变绑定的值。
数据向下,事件向上。
v-model
- v-bind绑定value属性的值;
- v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
- <input :value = "name" @input = "name=$event.target.value"/>
v-model lazy修饰符是什么作用呢?
默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步;
如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发
原型
命名方式:_private 开发不建议修改属性 __private__ 类似格式系统属性 不建议更改
显示原型 prototype function的属性
隐式原型 __proto__ 实例的原型
new 的时候在构造函数内部 Son(){
this.__proto__ = Son.prototype
}constructor一般指类的
prototype.constructor
。prototype.constructor
是prototype上的一个保留属性,这个属性就指向类函数本身,用于指示当前类的构造函数。圣杯模式 var inherit = function(){ var F = function(){}; return function(target,origin){ F.prototype = origin.prototype; target.prototype = new F(); target.prototype.constructor = target; target.prototype.uber = origin.prototype; }
- new运算符原理
1、创建一个空对象
2、让空对象的__proto__(IE没有该属性)成员指向了构造函数的prototype成员对象3、使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
4、如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象
function _new(func) { // 第一步 创建新对象 let obj= {}; // 第二步 空对象的_proto_指向了构造函数的prototype成员对象 obj.__proto__ = func.prototype;// // 一二步合并就相当于 let obj=Object.create(func.prototype) // 第三步 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中 let result = func.apply(obj); if (result && (typeof (result) == "object" || typeof (result) == "function")) { // 如果构造函数执行的结果返回的是一个对象,那么返回这个对象 return result; } // 如果构造函数返回的不是一个对象,返回创建的新对象 return obj; }
$ router是用来操作路由的,$ route是用来获取路由信息的。
$router是VueRouter的一个实例,他包含了所有的路由,包括路由的跳转方法,钩子函数等,也包含一些子对象(例如history)
常用的跳转连接的方法:
//常规方法
this.$router.push("/login"); //使用对象的形式 不带参数this.$router.push({ path:"/login" }); //使用对象的形式,参数为地址栏上的参数 this.$router.push({ path:"/login",query:{username:"jack"} }); 使用对象的形式 ,参数为params 不会显示在地址栏
this.$router.push({ name:'user' , params: {id:123} });
Vue路由守卫有哪些
一、全局路由守卫 一个是全局前置守卫,一个是全局后置守卫router.beforeEach((to, from, next) => { console.log('🚀🚀~ to:', to); console.log('🚀🚀~ from:', from); next(); })
- to: Route: 即将要进入的目标路由对象
- from: Route: 当前导航正要离开的路由对象
- next: Function: 一定要调用该方法不然会阻塞路由。
next
参数可以不添加,但是一旦添加,则必须调用一次,否则路由跳转等会停止。
next()
方法的几种情况
- next(): 进行管道中的下一个钩子。
- next(false): 中断当前的导航。回到
from
路由对应的地址。- next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址,可传递的参数与
router.push
中选项一致。- next(error): 导航终止,且该错误会被传递给
router.onError()
注册过的回调。二、组件路由守卫
// 跟methods: {}等同级别书写,组件路由守卫是写在每个单独的vue文件里面的路由守卫
beforeRouteEnter (to, from, next) { //
注意,在路由进入之前,组件实例还未渲染,所以无法获取this实例,只能通过vm来访问组件实例 next(vm => {}) }
beforeRouteUpdate (to, from, next) { // 同一页面,刷新不同数据时调用, }
beforeRouteLeave (to, from, next) { // 离开当前路由页面时调用 }
三、路由独享守卫
路由参数方式
动态路径参数const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: "/user/:id", component: User }, ], });
解决路由不生效的问题 通过
(1)通过 watch 监听路由参数再发请求
watch: { //通过watch来监听路由变化 "$route": function(){ this.getData(this.$route.params.xxx); } }
(2)用 :key 来阻止“复用”
<router-view :key="$route.fullPath" />
vue的diff过程
双指针
patch判断两个vnode的标签和key是否相同 如果相同 就可以认为是同一节点就地复用
// 只有当新老儿子的双指标的起始位置不大于结束位置的时候 才能循环 一方停止了就需要结束循环 while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex)1.使用双指针移动来进行新老节点的对比
2.用 isSameVnode 来判断新老子节点的头头 尾尾 头尾 尾头 是否是同一节点 如果满足就进行相应的移动指针(头头 尾尾)或者移动 dom 节点(头尾 尾头)操作
3.如果全都不相等 进行暴力对比 如果找到了利用 key 和 index 的映射表来移动老的子节点到前面去 如果找不到就直接插入
4.对老的子节点进行递归 patch 处理
5.最后老的子节点有多的就删掉 新的子节点有多的就添加到相应的位置
vue异步更新原理
$nextTick
优雅降级-版本兼容
setTimeout(() => { vm.a = 1; vm.a = 2; vm.a = 3; }, 1000);watcher.update
import { queueWatcher } from "./scheduler"; export default class Watcher { update() { // 每次watcher进行更新的时候 是否可以让他们先缓存起来 之后再一起调用 // 异步队列机制 queueWatcher(this); } run() { // 真正的触发更新 this.get(); } }
每次我们改变数据的时候都会触发相应的 watcher 进行更新 如果是渲染 watcher 那是不是意味着 数据变动一次 就会重新渲染一次 这样其实是很浪费性能的 我们有没有更好的方法 让数据变动完毕后统一去更新视图呢Promise——》MutationObserver——》setImmediate——》setTimeout
MutationObserver
setImmediate 异步方法,将事件放在事件列表尾部
主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
vue 在渲染组件时,如果数据结构太深。再动态修改数据时,组件并不会更新。使用this.$forceUpdate()
new Vue({ data:{ obj: { id: 1 } }, template: '<div>{{ obj.message }}</div>' }) this.obj.message = 'hello' // 不是响应式的 delete this.obj.id // 不是响应式的
// 动态添加 - Vue.set
Vue.set(this.obj, 'id', 002)
// 动态添加 - this.$set
this.$set(this.obj, 'id', 002)
// 动态添加多个
// 代替 Object.assign(this.obj, { a: 1, b: 2 })
this.obj = Object.assign({}, this.obj, { a: 1, b: 2 })
// 动态移除 - Vue.delete
Vue.delete(this.obj, 'name')
// 动态移除 - this.$delete
this.$delete(this.obj, 'name')
thi.$set
this.set因为响应式数据 我们给对象和数组本身都增加了__ob__属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪 然后会触发对象__ob__的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组
export function set(target: Array | Object, key: any, val: any): any { // 如果是数组 调用我们重写的splice方法 (这样可以更新视图) if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val; } // 如果是对象本身的属性,则直接添加即可 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } const ob = (target: any).__ob__; // 如果不是响应式的也不需要将其定义成响应式属性 if (!ob) { target[key] = val; return val; } // 将属性定义成响应式的 defineReactive(ob.value, key, val); // 通知视图更新 ob.dep.notify(); return val; }
sdvue中权限管理怎么用
页面和按钮参数和后台达成一致
(1)页面动态路由------获取页面的权限和路由进行匹配,生成左边的菜单
(2)按钮级别
1、获取角色权限-页面中用(v-show ----不建议使用,懂点前端的就可以修改显示状态)v-if做判断
2、v-promission 指令方式 判断当前判断
举例
directive
index.js
permission.js
main.js
import directive from "@/directive"
Vue.use(directive)
permissions.jsconst permissionsList = ['news_add', "news_import", "news_export"] const permissions = { inserted(el, binding, vnode, oldVnode) { let { value } = binding console.log(value) if (Array.isArray(value) ) { if(!value.length){ throw "v-permission 对应数组值不能为空"; } console.log(isArrayEqual(permissionsList, value)) if (!isArrayEqual(permissionsList, value)) { el & el.remove() } }else if(typeof(value)=='string'){ if(!permissionsList.includes(value)){ el & el.remove() } }else{ throw "v-permission 类型只能为数据或者字符串"; } // if (value !== "read") { // el & el.remove() // } } } export default permissions function isArrayEqual(arr1, arr2) { return arr2.some(item=>{ return arr1.includes(item) }) }
index.js
import permissions from "./permissions" // import fontRed from "./font-red"; // import style from "./style"; const install = function (Vue) { Vue.directive('permissions', permissions) // Vue.directive('font-red', fontRed) // Vue.directive('style', style) } export default install
<el-button type="primary" size="mini" @click="showModal('add')" v-permissions="'news_add'"> <svg-icon icon-class="new-add" class="new-add" /> 新增 </el-button>
你知道
this.$data Vue 原型上的属性---指向的_data
this.具体属性 Vue上的属性-代理过去的
this._data是怎么实现的吗? Vue上的属性--代理过去的
数据代理
在vue initState initData中
// src/state.js import { observe } from "./observer/index.js"; // 初始化状态 注意这里的顺序 比如我经常面试会问到 是否能在data里面直接使用prop的值 为什么? // 这里初始化的顺序依次是 prop>methods>data>computed>watch export function initState(vm) { // 获取传入的数据对象 const opts = vm.$options; if (opts.props) { initProps(vm); } if (opts.methods) { initMethod(vm); } if (opts.data) { // 初始化data initData(vm); } if (opts.computed) { initComputed(vm); } if (opts.watch) { initWatch(vm); } } // 初始化data数据 function initData(vm) { let data = vm.$options.data; // 实例的_data属性就是传入的data // vue组件data推荐使用函数 防止数据在组件之间共享 data = vm._data = typeof data === "function" ? data.call(vm) : data || {}; // 把data数据代理到vm 也就是Vue实例上面 我们可以使用this.a来访问this._data.a for (let key in data) { proxy(vm, `_data`, key); } // 对数据进行观测 --响应式数据核心 observe(data); } // 数据代理 function proxy(object, sourceKey, key) { Object.defineProperty(object, key, { get() { return object[sourceKey][key]; }, set(newValue) { object[sourceKey][key] = newValue; }, }); }
stateMixin.js