const index = this.subs.indexOf(sub)
if(index > -1) return this.subs.splice(index, 1)
}
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
class Watcher {
constructor(name) {
this.name = name
}
update() {
console.log(‘更新’)
}
}
手写发布订阅模式
与观察者模式相似,区别在于发布者和订阅者是解耦的,由中间的调度中心去与发布者和订阅者通信。Vue响应式原理个人更倾向于发布订阅模式。其中 Observer 是发布者,Watcher 是订阅者,Dep 是调度中心。
class EventEmitter {
constructor() {
this.events = {}
}
on(type, cb) {
if(!this.events[type]) this.events[type] = []
this.events[type].push(cb)
}
emit(type, …args) {
if(this.events[type]) {
this.events[type].forEach(cb => {
cb(…args)
})
}
}
off(type, cb) {
if(this.events[type]) {
const index = this.events[type].indexOf(cb)
if(index > -1) this.events[type].splice(index, 1)
}
}
}
组件中的data为什么是一个函数
数据以函数返回值形式定义,当每复用一次组件,就会返回一份新的data。即给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。对象在栈中存储的都是地址,函数的作用就是属性私有化,保证组件修改自身属性时不会影响其他复用组件。
生命周期
| 生命周期 | 描述 |
| — | — |
| beforeCreate | vue实例初始化后,数据观测(data observer)和事件配置之前。data、computed、watch、methods都无法访问。 |
| created | vue实例创建完成后立即调用 ,可访问 data、computed、watch、methods。未挂载 DOM,不能访问 、ref。 |
| beforeMount | 在 DOM 挂载开始之前调用。 |
| mounted | vue实例被挂载到 DOM。 |
| beforeUpdate | 数据更新之前调用,发生在虚拟 DOM 打补丁之前。 |
| updated | 数据更新之后调用。 |
| beforeDestroy | 实例销毁前调用。 |
| destroyed | 实例销毁后调用 。 |
调用异步请求可在created
、beforeMount
、mounted
生命周期中调用,因为相关数据都已创建。在不涉及到DOM操作时,最好的选择是在created
中调用。
具体细节参考文章
父组件与子组件生命周期钩子执行顺序
加载渲染过程
速记:父先创建,才能有子;子创建完成,父才完整。
顺序:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
组件更新过程
顺序:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
销毁过程
顺序:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
父组件监听子组件生命周期钩子的方式
以mounted为例
1、$emit
// 父组件
<Child @mounted=“doSomething”/>
//子组件
2、@hook
// 父组件
<Child @hook:mounted=“doSomething”/>
//子组件
组件通信方式
父子组件
1、props、$emit
2、 p a r e n t 、 parent、 parent、children
跨级组件
1、 a t t r s 、 attrs、 attrs、listeners
2、provide、inject
父子、跨级、兄弟
1、EventBus(事件总线)
2、Vuex(状态管理)
扩展
1、缓存
2、路由
具体细节参考文章
v-on监听多个方法
鼠标进来1
常用修饰符
表单修饰符
1、lazy::失去焦点后同步信息
2、trim:自动过滤首尾空格
3、number:输入值转为数值类型
事件修饰符
1、stop:阻止冒泡(js中stopPropagation)
2、prevent:阻止默认行为(js中preventDefault)
3、self:仅绑定元素自身触发
4、once:只触发一次
class、style的动态绑定方式
对象方式
数组方式
v-show与v-if的区别
相同点
都是控制页面元素的显示与隐藏
不同点
1、v-show 控制的是元素的CSS(v-show="false"相当于display:none);v-if 是控制元素本身的添加或删除(即是否出现在DOM结构中)
2、v-show 由 false 变为 true 的时候不会触发组件的生命周期。v-if 由 false 变为 true 则会触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由 true 变为 false 会触发组件的beforeDestory
、destoryed
方法
3、频繁切换时,使用v-show;不频繁切换、加快页面渲染、需要销毁元素使用v-if
为什么v-if不能和v-for一起使用
性能浪费,每次渲染都要先循环再进行条件判断,考虑用计算属性替代。
Vue2.x中v-for
比v-if
更高的优先级。
Vue3.x中v-if
比 v-for
更高的优先级。
computed与watch的区别与使用场景
computed
计算属性,依赖其他属性值,且值具备缓存的特性。只有它依赖的属性值发生改变,下一次获取的值才会重新计算。
适用于数值计算,并且依赖于其他属性时。因为可以利用缓存特性,避免每次获取值,都需要重新计算。
watch
观察属性,监听属性值变动。每当属性值发生变化,都会执行相应的回调。
适用于数据变化时执行异步或开销比较大的操作。
具体细节参考文章
slot插槽的理解与使用
slot 插槽,可以理解为slot
在组件模板中提前占据了位置。当复用组件时,使用相关的slot标签时,标签里的内容就会自动替换组件模板中对应slot标签的位置,作为承载分发内容的出口。
具体细节参考文章
Vue.$delete和delete的区别
Vue.$delete 是直接删除了元素,改变了数组的长度;delete 是将被删除的元素变成内 undefined
,其他元素键值不变。
Vue.$set如何解决对象新增属性不能响应的问题
Vue.$set的出现是由于Object.defineProperty
的局限性:无法检测对象属性的新增或删除。
源码位置:vue/src/core/observer/index.js
export function set(target, key, val) {
// 数组
if(Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组长度,避免索引大于数组长度导致splice错误
target.length = Math.max(target.length, key)
// 利用数组splice触发响应
target.splice(key, 1, val)
return val
}
// key 已经存在,直接修改属性值
if(key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = target.ob
// target 不是响应式数据,直接赋值
if(!ob) {
target[key] = val
return val
}
// 响应式处理属性
defineReactive(ob.value, key, val)
// 派发更新
ob.dep.notify()
return val
}
具体原理
1、若是数组,直接使用数组的 splice 方法触发响应式
2、若是对象,判断属性是否存在,对象是否是响应式
3、以上都不满足,最后通过 defineReactive 对属性进行响应式处理
具体细节参考文章
{{firstName}} {{lastName}} say {{alias}}
’,… https://blog.csdn.net/qq_41809113/article/details/121581605?spm=1001.2014.3001.5502Vue.$nextTick的原理
nextTick:在下次 DOM 更新循环结束之后执行延迟回调。常用于修改数据后获取更新后的DOM。
源码位置:vue/src/core/util/next-tick.js
import { noop } from ‘shared/util’
import { handleError } from ‘./error’
import { isIE, isIOS, isNative } from ‘./env’
// 是否使用微任务标识
export let isUsingMicroTask = false
// 回调函数队列
const callbacks = []
// 异步锁
let pending = false
function flushCallbacks () {
// 表示下一个 flushCallbacks 可以进入浏览器的任务队列了
pending = false
// 防止 nextTick 中包含 nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
const copies = callbacks.slice(0)
// 清空 callbacks 数组
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copiesi
}
}
let timerFunc
// 浏览器能力检测
// 使用宏任务或微任务的目的是宏任务和微任务必在同步代码结束之后执行,这时能保证是最终渲染好的DOM。
// 宏任务耗费时间是大于微任务,在浏览器支持的情况下,优先使用微任务。
// 宏任务中效率也有差距,最低的就是 setTimeout
if (typeof Promise !== ‘undefined’ && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== ‘undefined’ && (
isNative(MutationObserver) ||
MutationObserver.toString() === ‘[object MutationObserverConstructor]’
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== ‘undefined’ && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 将 nextTick 的回调函数用 try catch 包裹一层,用于异常捕获
// 将包裹后的函数放到 callback 中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, ‘nextTick’)
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pengding 为 false, 执行 timerFunc
if (!pending) {
// 关上锁
pending = true
timerFunc()
}
if (!cb && typeof Promise !== ‘undefined’) {
return new Promise(resolve => {
_resolve = resolve
})
}
}
总结:
1、运用异步锁的概念,保证同一时刻任务队列中只有一个 flushCallbacks。当 pengding 为 false 的时候,表示浏览器任务队列中没有 flushCallbacks 函数;当 pengding 为 true 的时候,表示浏览器任务队列中已经放入 flushCallbacks;待执行 flushCallback 函数时,pengding 会被再次置为 false,表示下一个 flushCallbacks 可进入任务队列
2、环境能力检测,选择可选中效率最高的(宏任务/微任务)进行包装执行,保证是在同步代码都执行完成后再去执行修改 DOM 等操作
3、flushCallbacks 先拷贝再清空,为了防止nextTick嵌套nextTick导致循环不结束
具体细节参考如上文章
虚拟DOM的理解
虚拟 DOM 的出现解决了浏览器的性能问题。虚拟 DOM 是一个用 JS 模拟的 DOM 结构对象(Vnode),用于频繁更改 DOM 操作后不立即更新 DOM,而是对比新老 Vnode,更新获取最新的Vnode,最后再一次性映射成真实的 DOM。这样做的原因是操作内存中操作 JS 对象速度比操作 DOM 快很多。
举个例子
real dom
- item 1
- item 2
- item 3
用 JS 来模拟以上 DOM 节点实现虚拟 DOM
function Element(tagName, props, children) {
this.tageName = tagName
this.props = props || {}
this.children = children || []
this.key = props.key
let count = 0
this.children.forEach(child => {
if(child instanceof Element) count += child.count
count++
})
this.count = count
}
const tree = Element(‘div’, { id: container }, [
Element(‘p’, {}, [‘real dom’])
Element(‘ul’, {}, [
Element(‘li’, { class: ‘item’ }, [‘item1’]),
Element(‘li’, { class: ‘item’ }, [‘item2’]),
Element(‘li’, { class: ‘item’ }, [‘item3’])
])
])
虚拟 DOM 转为真实的节点
Element.prototype.render = function() {
let el = document.createElement(this.tagName)
let props = this.props
for(let key in props) {
el.setAttribute(key, props[key])
}
let children = this.children || []
children.forEach(child => {
let child = (child instanceof Element) ? child.render() : document.createTextNode(child)
el.appendChild(child)
})
return el
}
Diff算法原理
具体细节参考文章
key的作用
key 是 Vue 中 vnode 的唯一标记,我们的 diff 的算法中 sameVnode 和 updateChildren 中就使用到了 key。
sameVnode 用来判断是否为同一节点。常见的业务场景是一个列表,若 key 值是列表索引,在新增或删除的情况下会存在就地复用的问题。(简单说,复用了上一个在当前位置元素的状态)所以 key 值的唯一,确保 diff 更准确。
updateChildren 中当其中四种假设都未匹配,就需要依赖老节点的 key 和 索引创建关系映射表,再用新节点的 key 去关系映射表去寻找索引进行更新,这保证 diff 算法更加快速。
简单来说就是:根据key判断是否复用,实现高效渲染
动态组件 和 异步组件
动态组件通过
is
特性实现。适用于根据数据、动态渲染的场景,即组件类型不确定。具体细节参考官网
Vue.directive 有写过么,有哪些应用场景
Vue.directive 可以注册全局指令和局部指令。
指令定义函数提供如下钩子函数
1、bind:指令第一次绑定到元素时调用(只调用一次)
2、inserted: 被绑定元素插入父节点时使用(父节点存在即可调用)
3、update:被绑定元素所在模板更新时调用,不论绑定值是否变化。通过比较更新前后的绑定值
4、 componentUpdated: 被绑定元素所在模板完成一次更新周期时调用
5、unbind: 只调用一次,指令与元素解绑时调用
具体细节参考官网
Vue 过滤器
Vue 过滤器可用在两个地方:双花括号插值和 v-bind 表达式。
Vue3 中已经废弃这个特点。
过滤器分为 全局过滤器 和 局部过滤器。
局部过滤器
{{ name | formatName }}全局过滤器
Vue.filter(‘formatName’, function(value) {
// 可基于源值做一些处理
return value
})
过滤器可串联,执行顺序从左到右,第二个过滤器输入值是第一个过滤器的输出值
{{ name | formatName1 | formatName2 }}关于mixin的理解,有什么应用场景
mixin 混入分全局混入和局部混入,本质是 JS 对象,如 data、components、computed、methods 等。
全局混入不推荐使用,会影响后续每个Vue实例的创建。局部混入可提取组件间相同的代码,进行逻辑复用。
适用场景:如多个页面具备
相同
的悬浮定位浮窗,可尝试用 mixin 封装。具体细节参考文章
介绍一下keep-alive
具体细节参考文章
Vue-Router 配置 404 页面
* 代表通配符,若放在任意路由前,会被先匹配,导致跳转到 404 页面,所以需将如下配置置于最后。
{
path: ‘*’,
name: ‘404’
component: () => import(‘./404.vue’)
}
Vue-Router 有哪几种导航守卫
全局路由守卫
在路由跳转前触发,可在执行 next 方法前做一些身份登录验证的逻辑
const router = new VueRouter({
// 相关配置
})
// 全局路由守卫(跳转每个路由页面前统一处理)
router.beforeEach((to, from, next) => {
…
// 必须执行 next 方法来触发路由跳转
next()
})
全局后置钩子
和守卫不同的是,这些钩子不会接受
next
函数也不会改变导航本身// 全局后置钩子(跳转每个路由页面后统一处理)
router.afterEach((to, from) => {
// …
})
路由独享守卫
可在路由配置上直接定义 beforeEnter
const router = new VueRouter({
routes: [
{
path: ‘/home’,
component: Home,
// 只有该路由独享
beforeEnter: (to, from, next) => {
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
常用的JavaScript设计模式
-
单体模式
-
工厂模式
-
例模式
函数
-
函数的定义
-
局部变量和全局变量
-
返回值
-
匿名函数
-
自运行函数
-
闭包
ent: Home,
// 只有该路由独享
beforeEnter: (to, from, next) => {
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-5epLAYIk-1711967234361)]
[外链图片转存中…(img-Khao72ON-1711967234361)]
[外链图片转存中…(img-FPSvswNV-1711967234362)]
[外链图片转存中…(img-kajB1E7t-1711967234362)]
[外链图片转存中…(img-chkI11bF-1711967234362)]
[外链图片转存中…(img-R79y4dEt-1711967234363)]既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-JFpBJX9v-1711967234363)]常用的JavaScript设计模式
-
单体模式
-
工厂模式
-
例模式
函数
-
函数的定义
-
局部变量和全局变量
-
返回值
-
匿名函数
-
自运行函数
-
闭包
-