- // vue3
- <script setup>
- import { onMounted } from 'vue'; // 使用前需引入生命周期钩子
-
- onMounted(() => {
- // ...
- });
-
- // 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖
- onMounted(() => {
- // ...
- });
- </script>
-
- // vue2
- <script>
- export default {
- mounted() { // 直接调用生命周期钩子
- // ...
- },
- }
- </script>
Vue 2 的响应式原理
在 Vue 2 中,响应式系统是基于
Object.defineProperty
实现的。当你把一个普通的 JavaScript 对象传给 Vue 实例的data
选项,Vue 将遍历此对象所有的属性,并使用Object.defineProperty
把这些属性全部转为 getter/setter,使数据变得“响应式”。
- getter:用于依赖收集。当组件渲染时,会访问到这些数据,此时 Vue 会记录这些组件作为依赖。
- setter:当数据改变时,setter 会被触发,然后通知所有依赖这个数据的组件重新渲染。
此外,Vue 2 提供了
watch
选项,用于观察和响应 Vue 实例上的数据变动。但watch
本身并不是响应式系统的核心部分,它只是 Vue 提供的一个工具,用于在数据变化时执行异步或开销较大的操作。Vue 3 的响应式原理
在 Vue 3 中,响应式系统得到了重构,基于 ES2015 的
Proxy
对象来实现。相比于Object.defineProperty
,Proxy
提供了更多的灵活性,可以监听对象属性的添加、删除、修改等操作。
- Proxy:Vue 3 使用
Proxy
来包装原始数据对象,创建一个代理对象。这个代理对象会拦截对原始数据的读取和修改操作,并在需要时触发相应的响应式逻辑。Vue 3 提供了
ref
和reactive
两个函数来创建响应式数据。
- ref:用于创建基本类型(如数字、字符串等)的响应式引用。
ref
返回的是一个对象,其value
属性包含实际的值。- reactive:用于创建复杂类型(如对象、数组等)的响应式引用。
reactive
返回的是一个代理对象,可以直接操作这个代理对象来修改原始数据。总结
- Vue 2 的响应式系统基于
Object.defineProperty
,通过 getter/setter 来实现数据的依赖收集和变化通知。- Vue 3 的响应式系统基于
Proxy
,提供了更强大的数据监听能力。它使用ref
和reactive
来创建响应式数据。
watch
在 Vue 2 和 Vue 3 中都是存在的,但它并不是响应式系统的核心部分,而是一个用于观察数据变化并触发相应逻辑的工具。
vue2 Object.defineProperty 和 vue 3 Proxy 区别:
Object.defineProperty:数据劫持
Proxy:数据代理 语法:Proxy构造函数:var proxy = new Proxy(target, handler);
Vue2和Vue3在监听功能上存在一些明显的区别,这些区别主要体现在监听机制、语法和性能优化等方面。以下是详细的比较:
- 监听机制:
- Vue2使用
Object.defineProperty
进行数据的劫持,实现双向绑定。但这种方式对于新添加的属性无法直接进行监听,需要通过Vue.set
或this.$set
方法手动添加响应式属性。
在Vue 2中,如果你需要在数据对象上添加新的属性,并且希望这个新属性也是响应式的(即当它的值改变时,视图也会更新),你需要使用Vue.set方法或实例的this.$set方法。这是因为Vue 2使用Object.defineProperty来劫持对象的属性,但这种方式只能对已经存在的属性进行劫持,对于后来添加的属性则无法直接进行劫持。 下面是一个使用Vue.set或this.$set添加响应式属性的例子: new Vue({ el: '#app', data: { // 初始数据对象 person: { name: 'John', age: 30 } }, mounted() { // 使用 Vue.set 添加响应式属性 Vue.set(this.person, 'address', '123 Street'); // 或者使用实例的 $set 方法添加响应式属性 // this.$set(this.person, 'city', 'New York'); } }); 在上面的例子中,person对象最初只有两个属性:name和age。在组件挂载后(mounted钩子中),我们使用Vue.set方法向person对象添加了一个新属性address。现在,address是一个响应式属性,当它的值改变时,任何依赖于它的视图都会更新。 如果你使用的是Vue实例内部,更常见的是使用this.$set方法,因为this指向了Vue实例本身: mounted() { this.$set(this.person, 'city', 'New York'); }- Vue3则采用了Proxy来代理对象,它可以直接监听对象上的所有属性,包括后续添加的属性,无需手动添加响应式属性。
在Vue 3中,由于使用了Proxy来创建响应式对象,你可以直接向数据对象添加新属性,而不需要使用Vue.set或this.$set。Proxy会拦截对对象属性的访问和修改,使得新添加的属性也能保持响应式。 以下是一个Vue 3的示例,展示如何在组件中直接添加新属性: <div> <p>Name: {{ person.name }}p> <p>Age: {{ person.age }}p> <p>New Property: {{ person.newProperty }}p> <button @click="addNewProperty">Add New Propertybutton> div> <script> import { ref, onMounted } from 'vue'; export default { setup() { const person = ref({ name: 'John', age: 30 }); // 添加新属性的方法 function addNewProperty() { // 直接添加新属性,不需要使用Vue.set或this.$set person.value.newProperty = 'This is a new property!'; } onMounted(() => { // 组件挂载后,你可以看到新属性已经被添加并且是响应式的 console.log(person.value.newProperty); // 初始为undefined,因为还没有添加 }); // 直接调用方法来添加新属性 addNewProperty(); return { person, addNewProperty }; } }; script> 在上面的示例中,我们定义了一个响应式对象person,并在addNewProperty方法中直接向其添加了一个新属性newProperty。当按钮被点击时,这个新属性就会被添加到person对象中,并且因为它是直接添加到响应式对象上的,所以它是响应式的——即当它的值改变时,视图也会自动更新。 请注意,虽然你不需要使用Vue.set或this.$set来添加新属性,但如果你想要确保在添加新属性时触发视图更新(即使新属性的值没有变化),你可能需要强制Vue重新评估相关的计算属性或方法。这通常不是必要的,因为当你直接修改响应式对象的属性时,Vue会自动触发必要的更新。
- 监听语法:
- Vue2中的监听主要通过
watch
选项或this.$watch
方法来实现,用于监听data中的属性变化,并在变化时执行相应的回调函数。- Vue3同样提供了
watch
和watchEffect
函数来监听数据变化,但它们的用法更加灵活和强大。watch
函数可以监听特定的数据源,并在其变化时执行回调函数;而watchEffect
则会自动收集其依赖,并在依赖变化时重新执行该函数。- 深度监听与立即执行:
- 在Vue2中,使用
watch
监听对象时,如果需要深度监听对象内部属性的变化,需要设置deep: true
选项。另外,如果需要监听器在创建时立即执行一次,可以设置immediate: true
选项。- Vue3中的
watch
函数也支持深度监听和立即执行,但语法和用法可能与Vue2有所不同。- 以下是Vue 2和Vue 3中
watch
的示例代码,以展示深度监听和立即执行的不同语法和用法
在Vue 2中,你可以使用watch选项来监听数据的变化,并可以通过设置deep: true来实现深度监听,以及设置immediate: true来让监听器在创建时立即执行一次。 new Vue({ el: '#app', data: { obj: { a: 1, b: 2 } }, watch: { obj: { handler(newVal, oldVal) { console.log('obj changed'); // 注意:这里不会深度监听obj内部属性的变化 }, deep: true, // 深度监听 immediate: true // 立即执行 } }, created() { // 由于设置了immediate: true,这里会打印'obj changed' } }); 但是,请注意,上面的watch只会告诉你obj对象本身是否发生了变化(比如,是否替换了整个对象),而不是它内部属性的变化。要监听obj内部属性的变化,你需要一个更具体的watch或者使用computed属性来配合。
在Vue 3中,watch函数的使用方式有所不同,并且更灵活。你可以通过watch函数来监听单个数据或计算属性,也可以监听多个数据源。 以下是Vue 3中深度监听和立即执行的示例: import { ref, watch, onMounted } from 'vue'; export default { setup() { const obj = ref({ a: 1, b: 2 }); // 使用watch函数来监听obj的变化,并设置深度监听和立即执行 watch( () => obj.value, // 监听obj.value的变化 (newVal, oldVal) => { console.log('obj changed', newVal, oldVal); }, { deep: true, // 深度监听obj内部属性的变化 immediate: true // 立即执行监听器 } ); // 注意:由于Vue 3的setup是同步执行的,immediate: true会在watch函数被调用时立即执行 onMounted(() => { // setup中的代码是同步执行的,所以这里不会看到因为immediate: true而打印的日志 }); return { obj }; } }; 在Vue 3中,watch函数接受三个参数: 第一个参数是监听的数据源,可以是一个getter函数,也可以直接是ref或reactive对象的属性。 第二个参数是当数据源变化时要执行的回调函数。 第三个参数是一个选项对象,其中可以包含deep和immediate等选项。 请注意,Vue 3的setup函数是同步执行的,所以immediate: true会在watch函数被调用时立即执行,而不是在组件挂载(onMounted)时。
- 性能优化:
- Vue2的监听机制在性能上可能存在一定的开销,尤其是在处理大量数据或复杂逻辑时。
- Vue3通过引入Proxy和Composition API等新技术,对性能进行了优化和提升,使得监听功能更加高效和灵活。
- 其他特性:
- Vue3中的监听功能还支持一些其他特性,如监听多个数据源、使用异步函数作为回调函数等。这些特性使得Vue3的监听功能更加强大和灵活。
综上所述,Vue2和Vue3在监听功能上存在明显的区别。Vue3通过引入Proxy和Composition API等新技术,对监听机制进行了改进和优化,使得其性能更加高效、语法更加灵活、功能更加强大。因此,在开发Vue项目时,建议根据项目需求和技术栈选择合适的Vue版本进行使用。
1.父传子
- v2和v3的父传子差不多
- // ParentComponent.vue
-
- <ChildComponent :message="parentMessage" /> 父组件定义的变量parentMessage用message来绑定
-
-
- <script>
- import ChildComponent from './ChildComponent.vue';
-
- export default {
- components: {
- ChildComponent
- },
- data() {
- return {
- parentMessage: 'Hello from Parent'
- };
- }
- };
- script>
-
- // ChildComponent.vue
- <template>
- <div>{{ message }}div>
- template>
-
- <script>
- export default {
- props: ['message'] 用props来接收父组件绑定的方法
- };
- script>
2.子传父
- 子传父
- // ChildComponent.vue
-
- <button @click="notifyParent">Notify Parentbutton>
-
-
- <script>
- export default {
- methods: {
- notifyParent() {
- this.$emit('child-event', 'Hello from Child'); //点击按钮传递一个叫child-event的,内容为Hello from Child
- }
- }
- };
- script>
-
- // ParentComponent.vue
- <template>
- <ChildComponent @child-event="handleChildEvent" /> //父组件绑定的方法和子组件传递过来的名字一样,父组件用handleChildEvent方法接收打印出来
- template>
-
- <script>
- import ChildComponent from './ChildComponent.vue';
-
- export default {
- components: {
- ChildComponent
- },
- methods: {
- handleChildEvent(message) {
- console.log(message); // "Hello from Child"
- }
- }
- };
- script>
3.vuex
- Vuex 示例(这通常涉及到更多的代码,这里只给出大致的概念):
- // store.js
- import Vue from 'vue';
- import Vuex from 'vuex';
-
- Vue.use(Vuex);
-
- export default new Vuex.Store({
- state: {
- message: 'Hello from Vuex'
- },
- mutations: {
- updateMessage(state, payload) {
- state.message = payload;
- }
- }
- });
-
- // 在组件中
- this.$store.commit('updateMessage', 'New message');
- // 在模板中
- {{ $store.state.message }}
4.Vue3组件通信之一_provide()与inject()依赖注入函数Provide / Inject (依赖注入)
Vue3组件通信之一_provide()与inject()依赖注入函数-CSDN博客(参考)
- // ParentComponent.vue (或更高层级的组件)
-
-
- // ChildComponent.vue (或任意深层的组件)
- <script>
- export default {
- inject: ['message'],
- mounted() {
- console.log(this.message); // "Hello from Provide/Inject"
- }
- };
- script>
Vue路由守卫是Vue Router提供的一种机制,允许开发者在路由跳转前后执行一些逻辑判断或操作,以确保应用程序的导航逻辑正确性和增强用户体验。以下是关于Vue路由守卫的详细解释:
一、路由守卫的概念
路由守卫(Route Guards)是Vue Router中的一个功能,允许我们在路由发生之前、之中、之后执行逻辑检查和操作。这些守卫可以用来实现权限验证、页面访问控制、数据预加载等逻辑,确保用户在应用中的导航流程符合业务需求。
二、路由守卫的分类
Vue路由守卫主要分为以下三种类型:
- 全局守卫
beforeEach(to, from, next)
:全局前置守卫,在路由进入之前调用。常用于权限验证,如果用户未登录,可以重定向到登录页面。每个守卫方法接收三个参数:to
(即将进入的目标路由对象)、from
(当前导航正要离开的路由对象)、next
(必须调用的函数来解析这个钩子)。beforeResolve(to, from, next)
:全局解析守卫,在路由解析之前调用,它在beforeEach
之后和afterEach
之前调用。afterEach(to, from)
:全局后置守卫,在路由跳转完成后调用,通常用于记录日志或执行一些全局操作。- 路由独享守卫
beforeEnter(to, from, next)
:在路由配置中直接定义,这些守卫只适用于特定的路由。- 组件内守卫
beforeRouteEnter(to, from, next)
:在渲染该组件的对应路由被确认前调用,不能访问组件实例 (this
)。beforeRouteUpdate(to, from, next)
:在当前路由改变,但是该组件被复用时调用(例如,对于带有动态参数的路由)。beforeRouteLeave(to, from, next)
:导航离开该组件的对应路由时调用。三、路由守卫的使用
全局守卫示例:
router.beforeEach((to, from, next) => { // 进行权限验证等操作 if (to.meta.requireAuth) { if (localStorage.getItem('token')) { next(); } else { next({ path: '/login', query: { redirect: to.fullPath } }); } } else { next(); } });路由独享守卫示例:
const router = new VueRouter({ routes: [ { path: '/admin', component: Admin, beforeEnter: (to, from, next) => { // 进行权限验证等操作 // ... } } ] });组件内守卫示例:
export default { beforeRouteEnter(to, from, next) { // 在渲染该组件的对应路由被确认前调用 // 注意:这里不能访问组件实例 (this) // ... next(); }, beforeRouteLeave(to, from, next) { // 导航离开该组件的对应路由时调用 // ... next(); } }四、总结
Vue路由守卫是Vue Router提供的一个强大功能,通过它,我们可以在路由跳转前后执行逻辑检查和操作,以确保应用程序的导航逻辑正确性和增强用户体验。开发者可以根据具体需求选择使用全局守卫、路由独享守卫或组件内守卫。
Vue2的打包方式
Vue2项目通常使用Webpack作为构建工具进行打包。Webpack是一个模块打包器,可以将项目中的多个模块打包成一个或多个bundle,供浏览器使用。在Vue2项目中,Webpack的配置通常位于
vue.config.js
文件中,或者通过其他方式进行配置。在Vue2项目中,打包过程可能包括以下几个步骤:
- 设置路由模式:根据项目需求选择hash模式或history模式。
- 性能分析和CDN应用:使用Vue CLI提供的性能分析工具对项目进行打包分析,并根据需要将一些大文件或库文件通过CDN引入,以减轻服务器负担和提高加载速度。
- Webpack配置:通过
vue.config.js
或其他方式配置Webpack,包括入口文件、输出目录、加载器(loader)和插件等。Vue3的打包方式
Vue3项目在打包方式上与Vue2类似,但也有一些新的特性和工具可以使用。
- 使用Vite:Vite是一个由原生ESM驱动的Web开发构建工具,它提供了更快的冷启动速度、即时的热模块替换(HMR)和真正的按需编译。目前,Vite主要用于Vue3项目,因为它针对Vue3做了优化。使用Vite可以更快地构建和开发Vue3项目。
- Webpack:虽然Vite是Vue3的推荐构建工具,但Webpack仍然可以用于Vue3项目。在Vue3项目中,Webpack的配置和使用方式与Vue2类似,但可能需要针对Vue3的一些新特性进行调整。
- cross-env:在Vue3项目中,可以使用cross-env进行分环境配置,根据不同的环境使用不同的接口和配置。这有助于更好地管理不同环境下的项目配置和依赖。
总结
- Vue2:主要使用Webpack作为构建工具进行打包,配置方式相对传统和灵活。
- Vue3:推荐使用Vite作为构建工具,因为它提供了更快的构建速度和更好的开发体验。但Webpack仍然可以用于Vue3项目,只是可能需要一些额外的配置和调整。
JavaScript高级——ES6基础入门_js es6-CSDN博客
keep-alive是Vue提供的一个内置组件,被keep-alive组件包裹的内部组件,其状态将被缓存
keep-alive包裹的组件,其生命周期只能被执行一次,再次进入时不会被执行
keep-alive包裹的组件,会自动新增两个生命周期函数activated和deactivated,每次进入都会被执行
activated( ) 当keepalive包含的组件再次渲染时触发
deactivated( ) 当keepalive包含的组件销毁时触发
keep-alive两个属性include和exclude,可以让keep-alive实现有条件的进行缓存。include匹配到的组件会被进行缓存,exclude匹配到的组件不会被缓存
computed(计算属性)和watch(侦听器)的区别_watch和计算属性的区别-CSDN博客
计算属性是基于它们的依赖进行缓存的。只有在它的相关依赖发生改变时才会重新求值。
- <template>
- <div>
- <p>原始价格: {{ price }}</p>
- <p>优惠价格: {{ discountedPrice }}</p>
- </div>
- </template>
-
- <script>
- export default {
- data() {
- return {
- price: 100,
- discount: 0.1 // 10% 的折扣
- };
- },
- computed: {
- discountedPrice() {
- // 当 price 或 discount 发生变化时,这个函数才会重新计算
- return this.price * (1 - this.discount);
- }
- }
- };
- </script>
侦听器允许你执行异步或开销较大的操作,并可以访问数据变化的先前值和当前值。
- <template>
- <div>
- <input v-model="searchQuery" placeholder="搜索...">
- </div>
- </template>
-
- <script>
- export default {
- data() {
- return {
- searchQuery: ''
- };
- },
- watch: {
- // 当 searchQuery 发生变化时,这个函数会被调用
- searchQuery(newVal, oldVal) {
- // 这里可以执行一些异步操作,比如 API 请求
- console.log(`搜索查询已更改: ${oldVal} -> ${newVal}`);
- // 模拟 API 请求
- setTimeout(() => {
- console.log(`搜索结果为: ${newVal}`);
- }, 1000);
- }
- }
- };
- </script>
1.内部函数可以访问外部函数的局部变量。
2.外部函数嵌套内部函数
3.内部函数可以使用外部函数的局部变量
手写一个闭包:
- function makeSizer(size){
- return function(){
- docoment.body.style.fontsize=size+'px';
- }
- }
- var size12=makeSizer(12);//size12指向的函数,可以设置body的fontSize为12px
- var size14=makeSizer(14);//size14指向的函数,可以设置body的fontSize为14px
- var size16=makeSizer(16);//size16指向的函数,可以设置body的fontSize为16px
作用域
作用域(Scope)
作用域指的是变量、函数和表达式在代码中的可访问性范围。换句话说,它决定了在代码的不同部分,哪些变量、函数或表达式是可见(即可被访问和使用)的。
在JavaScript(以及其他许多编程语言)中,作用域主要有两种:全局作用域和局部作用域(包括函数作用域、块级作用域等)。
全局作用域
- 定义:在代码的任何地方都能访问到的变量或函数位于全局作用域中。在浏览器中,全局作用域对应
window
对象。- 优点:全局变量或函数可以在整个代码库中被访问,这使得它们在某些情况下(如跨多个函数或模块共享数据)非常有用。
- 缺点:
- 过度使用全局变量可能导致命名冲突,因为不同的脚本或模块可能会意外地使用相同的变量名。
- 全局变量难以追踪和管理,因为它们可以在任何地方被修改。
- 可能导致意外的副作用,因为对全局变量的修改可能会影响到代码的其他部分。
局部作用域
- 定义:在特定的代码块(如函数或块级作用域的大括号内)中定义的变量或函数只在该代码块内可见。
- 优点:
- 减少了命名冲突的可能性,因为每个局部变量都只在自己的作用域内存在。
- 提高了代码的可读性和可维护性,因为开发者可以更容易地理解每个变量或函数的作用和用途。
- 减少了意外的副作用,因为对局部变量的修改不会影响到代码的其他部分(除非通过特定的机制,如闭包)。
- 缺点:在大型或复杂的代码中,过多的嵌套作用域可能会使代码变得难以理解和跟踪。然而,这通常可以通过良好的代码结构和模块化来解决。
块级作用域(ES6+)
ES6(ECMAScript 2015)引入了
let
和const
关键字,它们提供了块级作用域。这意味着使用let
或const
声明的变量只在其所在的代码块(例如,if
语句、for
循环或任何由大括号{}
包围的代码块)内可见。优点
- 提供了更细粒度的作用域控制,有助于减少命名冲突和意外的副作用。
- 使得代码更加清晰和可维护。
缺点
- 对于不熟悉ES6+语法的开发者来说,可能需要一些时间来适应新的作用域规则。
- 在某些旧的浏览器或环境中可能不受支持,需要进行兼容性处理。
防抖:当事件被触发后,延迟n秒再执行核心代码,如果在这n秒内再次被触发,就重新计时(像王者里面的回城一样)
场景:可以使用防抖技术优化搜索框建议词列表展示功能
手写防抖
- //定义一个变量指向延时器
- var time=null;
- //某事件会触发的回调函数
- function doSomething(){
- //先删除有可能已存在的延时器
- clearTimeout(timer)
- //创建新的延时器
- time=seTimeout(function(){
- //真正要执行的核心代码或者调用核心函数
- //code
- console.log('核心代码')
- },500)
- }
节流:是一种有效降低单位时间内,核心代码执行次数的机制
场景:如果事件被频繁触发,节流能减少事件触发的频率,因此,节流是有选择性的执行一部分事件,如:使用节流优化滚动条事件的监听,避免滚动事件的回调函数太过频繁执行
手写:
- function throttle(func, delay) {
- let lastCall = 0;
- return function() {
- const now = new Date().getTime();
- if (now - lastCall < delay) {
- return;
- }
- lastCall = now;
- return func.apply(this, arguments);
- };
- }
- //可以像这样使用这个简化版的节流函数
- const throttledScrollHandler = throttle(function() {
- console.log('Scrolled!');
- }, 250); // 每250毫秒最多执行一次
-
- window.addEventListener('scroll', throttledScrollHandler);
模块化开发 和 面向对象编程 是两种不同的编程概念,它们各自在软件开发中扮演着重要的角色。
模块化开发(Modular Programming):
- 定义:模块化开发是一种将程序划分为多个独立模块的开发方法。每个模块都具有特定的功能和接口,模块之间通过接口进行通信,实现代码的解耦和复用。
- 优点:
- 代码复用:通过重用现有的模块,可以提高开发效率,减少代码冗余。
- 易于维护:由于每个模块具有明确的功能和接口,因此可以单独测试和维护每个模块,降低维护成本。
- 易于扩展:当需要添加新功能时,可以通过添加新的模块或修改现有模块的接口来实现。
- 实现方式:在多种编程语言中,如JavaScript、Python、Java等,都支持模块化编程。具体的实现方式可能因语言而异,但通常包括将代码组织成函数、类、包或模块等。
面向对象编程(Object-Oriented Programming, OOP):
- 定义:面向对象编程是一种编程范式,它将现实世界中的事物抽象为对象,并使用类和对象等概念来设计和组织程序。在面向对象编程中,数据和操作数据的方法被封装在对象中,对象之间通过消息传递进行通信。
- 核心概念:
- 类(Class):定义对象的属性和方法的模板。
- 对象(Object):类的实例化,具有类的属性和方法。
- 继承(Inheritance):子类继承父类的属性和方法,实现代码的重用。
- 多态(Polymorphism):不同的对象对同一消息做出不同的响应。
- 封装(Encapsulation):将数据和方法封装在对象中,隐藏对象的内部实现细节。
- 优点:
- 易于理解和维护:由于面向对象编程将现实世界中的事物抽象为对象,因此代码更易于理解和维护。
- 易于扩展:通过继承和多态等机制,可以方便地扩展和修改程序的功能。
- 易于复用:通过封装和继承等机制,可以实现代码的复用和共享。
- 实现方式:在多种编程语言中,如Java、C++、Python等,都支持面向对象编程。这些语言提供了类和对象等概念,以及继承、多态和封装等机制来实现面向对象编程。
- (对于上面第2点不清楚的延伸)什么是继承和封装
继承 和 封装 是面向对象编程(OOP)中的两个核心概念。
继承(Inheritance)定义:继承是面向对象编程中的一个重要特性,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,并且可以添加或覆盖父类的行为以定义新的行为。
- 作用:
- 代码重用:子类可以继承父类的所有公共和受保护的属性和方法,从而避免了重复编写相同的代码。
- 扩展性:子类可以在继承父类的基础上添加新的属性和方法,或者覆盖父类的方法以实现不同的行为。
- 多态性的基础:继承是多态性的前提,子类对象可以当作父类对象使用,从而在运行时确定实际调用的方法(动态绑定)。
封装(Encapsulation)
- 定义:封装是面向对象编程中的另一个核心概念,它指的是将数据(属性)和操作这些数据的方法(函数)绑定在一起,作为一个整体(即对象)来处理。封装隐藏了对象的内部状态和实现细节,只对外提供必要的接口来访问和操作对象。
- 作用:
- 数据保护:通过封装,可以确保对象内部数据的完整性和安全性。外部代码只能通过对象提供的公共方法来访问或修改对象的属性,从而防止了直接访问和修改对象内部数据的可能性。
- 提高可维护性:封装使得对象的内部实现细节对外部是隐藏的。这意味着,如果对象的内部实现发生变化,只要不改变对外提供的接口,外部代码就无需修改。这大大提高了代码的可维护性。
- 模块化设计:封装有助于将复杂系统划分为更小的、更易于管理的模块。每个模块都有明确的接口和职责,从而降低了系统的复杂性。
总的来说,继承和封装是面向对象编程中非常重要的概念,它们有助于提高代码的重用性、可扩展性和可维护性。
图片懒加载(Lazy Loading)是一种优化网页性能的技术,它的核心思想是只在用户滚动到视图内时才开始加载图片,而不是一开始就加载所有图片。这可以大大减少页面初始加载时间,并降低服务器负载。作为一个前端开发者,你可以通过以下步骤实现图片懒加载:
设置占位图:
在图片的原始位置放置一个占位图或者空白区域,这样在页面初次加载时不会请求实际的图片资源。检测滚动和视图:
使用JavaScript监听页面的滚动事件,并判断图片元素是否进入可视区域。这通常通过计算元素的位置与当前视口(viewport)的位置关系来实现。动态加载图片:
当图片元素进入可视区域时,通过修改图片的src
属性来触发图片的加载。为了避免页面重新布局,可以先将图片的真实URL存储在一个自定义属性(如data-src
)中,然后在需要时将其赋值给src
属性。处理加载状态:
在图片加载过程中,你可能希望显示一个加载指示器(如加载动画)。当图片加载完成后,可以通过监听图片的load
事件来隐藏加载指示器。优化和回退:
考虑到一些浏览器可能不支持JavaScript或图片懒加载技术,你应该提供一个回退方案,比如使用标签来包含普通的
元素,以确保在非JavaScript环境下图片仍然能够正常显示。
使用现代浏览器特性:
一些现代浏览器支持原生的图片懒加载功能,例如通过为标签添加
loading="lazy"
属性。这可以简化实现并提高效率,但你需要确保在不支持该属性的浏览器上有适当的回退策略。库和插件:
如果你不想从头开始实现图片懒加载,可以考虑使用现有的JavaScript库或插件,如lazyload.js
、lozad.js
等。这些库通常提供了丰富的配置选项和事件处理机制,可以方便地集成到你的项目中。响应式图片:
在实现图片懒加载的同时,不要忘记考虑响应式图片的需求。你可以使用元素和
元素来提供不同尺寸和分辨率的图片版本,以适应不同的设备和屏幕尺寸。
测试和优化:
在实现图片懒加载后,务必进行充分的测试,以确保在各种设备和浏览器上都能正常工作。此外,你还可以使用性能分析工具来检查图片加载对页面性能的影响,并根据需要进行优化。
案例演示:
1.使用HTML的
loading
属性
- 实现方式:
- 在
标签中添加
loading="lazy"
属性。- 浏览器会自动延迟加载设置了此属性的图片,直到它们接近视口(viewport)为止。
- 示例代码:
<img src="real-image.jpg" alt="Lazy loaded image" loading="lazy">
- 注意事项:
- 此方法依赖于浏览器的支持,一些较旧的浏览器可能不支持
loading
属性。- 需要确保在不支持此属性的浏览器中有一个回退方案。
2. 使用JavaScript监听滚动事件
- 实现原理:
- 监听页面的滚动事件(
scroll
)。- 当图片进入可视区域时,将图片的
src
属性设置为真实的图片地址。- 示例代码(简化版):
<img src="placeholder.jpg" data-src="real-image.jpg" alt="Lazy loaded image" class="lazy"> <script> // 监听滚动事件 window.onscroll = function() { // 获取所有带有lazy类的图片元素 var lazyImages = document.getElementsByClassName('lazy'); // 遍历图片元素,检查是否在可视区域 // ...(此处省略具体的实现逻辑) // 如果在可视区域,则替换src属性 lazyImage.src = lazyImage.dataset.src; }; </script>
- 注意事项:
- 监听滚动事件并计算图片位置可能会对性能产生影响,特别是在有大量图片的情况下。
- 可以使用防抖(debounce)或节流(throttle)技术来优化性能。
3. 使用Intersection Observer API
3.实现原理:
- Intersection Observer API 可以异步观察目标元素与其祖先元素或顶级文档视口的交叉状态。
- 当目标元素的可见性发生变化时,会触发一个回调函数。
- 示例代码(简化版):
var lazyImages = document.querySelectorAll('img.lazy'); if ('IntersectionObserver' in window) { let lazyImageObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { if (entry.isIntersecting) { let lazyImage = entry.target; lazyImage.src = lazyImage.dataset.src; lazyImageObserver.unobserve(lazyImage); } }); }); lazyImages.forEach(function(lazyImage){ lazyImageObserver.observe(lazyImage); }); }
- 注意事项:
- Intersection Observer API 提供了更好的性能和更简洁的代码。
- 同样需要注意浏览器兼容性。
4. 使用第三方库
- 实现方式:
- 使用如
lazyload.js
、lozad.js
等第三方库来实现图片懒加载。- 这些库通常提供了丰富的配置选项和事件处理机制。
- 注意事项:
- 引入第三方库可能会增加项目的依赖和大小。
- 需要确保所选的库与你的项目兼容,并仔细阅读其文档以了解如何配置和使用。
以上是实现图片懒加载的几种常见方式,你可以根据自己的需求和项目环境选择适合的方法。
好处:防抖(debounce)和节流(throttle)技术都是用于优化高频率触发事件(如滚动事件)的性能的手段。在图片懒加载的场景中,我们可以使用这些技术来减少不必要的计算和DOM操作,从而提升性能。
防抖的基本思想是:设置一个定时器,在事件被触发后n秒内函数只能执行一次,如果在这n秒内又被重新触发,则重新计算执行时间。在图片懒加载中,我们可以使用防抖来确保在滚动停止后才开始检查图片是否在视口内。
- function debounce(func, wait) {
- let timeout;
- return function executedFunction(...args) {
- const context = this;
- clearTimeout(timeout);
- timeout = setTimeout(() => func.apply(context, args), wait);
- };
- }
-
- // 使用防抖的图片懒加载函数
- const checkLazyImages = debounce(function() {
- // 检查并加载视口内的图片
- // ...
- }, 200); // 设置防抖时间为200毫秒
-
- window.addEventListener('scroll', checkLazyImages);
节流的基本思想是:设置一个冷却时间,在事件被触发后n秒内只执行一次,之后在这n秒内,无论事件被触发多少次,函数都不会执行。在图片懒加载中,我们可以使用节流来确保检查图片是否在视口内的函数不会过于频繁地执行。
- function throttle(func, limit) {
- let inThrottle;
- return function executedFunction(...args) {
- const context = this;
- if (!inThrottle) {
- func.apply(context, args);
- inThrottle = true;
- setTimeout(() => inThrottle = false, limit);
- }
- };
- }
-
- // 使用节流的图片懒加载函数
- const checkLazyImages = throttle(function() {
- // 检查并加载视口内的图片
- // ...
- }, 200); // 设置节流间隔为200毫秒
-
- window.addEventListener('scroll', checkLazyImages);
this
)和参数的引用。lodash
的_.debounce
和_.throttle
方法,以避免重复造轮子。ref、reactive、toRef、toRefs的区别_ref和reactive,toref的区别-CSDN博客(参考)
toRef
和toRefs
在Vue 3中都是用于处理响应式数据的重要API,但它们之间有着明显的区别。以下是它们之间的主要区别:
- 功能与作用:
toRef
:此函数用于将对象中的某个属性转化为响应式引用。它接收两个参数:一个对象和一个字符串键名。这个键名指定了要从该对象中提取并转化为响应式引用的属性。例如,let nameRef = toRef(obj, 'name')
。toRefs
:此函数用于将一个响应式对象转换为一个普通对象,其中该对象的每个属性都是指向原始对象属性的响应式引用。这通常用于在模板或计算属性中解构响应式对象,以便可以单独访问其属性,同时保持其响应性。例如,const stateRefs = toRefs(state)
。- 使用场景:
toRef
:当你需要单独处理响应式对象中的某个属性,并且希望保持该属性的响应性时,你可以使用toRef
。这样,你可以在不改变原始对象结构的情况下,轻松地修改和访问该属性的值。toRefs
:当你需要将整个响应式对象解构为多个独立的响应式引用,并在模板或计算属性中单独使用这些引用时,toRefs
非常有用。这允许你以一种更加清晰和直观的方式处理复杂的响应式数据结构。- 返回值:
toRef
:返回一个包含value
属性的对象,该value
属性是响应式的,并且与原始对象中的指定属性保持同步。toRefs
:返回一个普通对象,该对象的每个属性都是一个响应式引用,它们都与原始响应式对象的对应属性保持同步。- 示例:
toRef
示例
import { reactive, toRef } from 'vue'
const state = reactive({ name: '张三', age: 20 })
const nameRef = toRef(state, 'name')
console.log(nameRef.value) // 输出:'张三'
nameRef.value = '李四'
console.log(state.name) // 输出:'李四'
toRefs
示例:
import { reactive, toRefs } from 'vue'
const state = reactive({ name: '张三', age: 20 })
const stateRefs = toRefs(state)
console.log(stateRefs.name.value) // 输出:'张三'
stateRefs.name.value = '李四'
console.log(state.name) // 输出:'李四'
综上所述,
toRef
和toRefs
的主要区别在于它们的功能、使用场景、返回值以及处理响应式数据的方式。选择使用哪一个取决于你的具体需求和场景。
在Vue中,插槽(slot)是一种非常有用的工具,它允许父组件在子组件的模板中插入内容,从而增强组件的复用性和灵活性。以下是关于Vue中插槽的使用方法和场景的详细解释:
来占位,表示一个默认插槽。
标签。
标签添加name
属性来定义具名插槽。
标签并搭配v-slot
指令(在Vue 2.6.0+中,使用slot
属性结合
标签的方式已经被废弃,推荐使用v-slot
),来指定内容要插入到哪个具名插槽中。例如:
- <!-- 子组件 -->
- <template>
- <div>
- <slot name="header"></slot>
- <slot name="body"></slot>
- </div>
- </template>
-
- <!-- 父组件 -->
- <template>
- <my-component>
- <template v-slot:header>
- <h2>这是头部内容</h2>
- </template>
- <template v-slot:body>
- <p>这是主体内容</p>
- </template>
- </my-component>
- </template>
标签上绑定一个数据对象,然后在父组件的插槽模板中通过slot-scope
(在Vue 2.6.0+中,推荐使用v-slot
语法)来访问这些数据。子组件(ChildComponent.vue)
- <template>
- <div>
- <slot name="scopedSlot" :user="user"></slot>
- </div>
- </template>
-
- <script>
- export default {
- data() {
- return {
- user: {
- name: 'John Doe',
- age: 30,
- occupation: 'Developer'
- }
- };
- }
- };
- </script>
在这个子组件中,我们定义了一个具名插槽scopedSlot
,并通过:user="user"
将数据对象user
绑定到该插槽上。
- <template>
- <div>
- <ChildComponent>
- <template v-slot:scopedSlot="slotProps">
- <div>
- <h2>Name: {{ slotProps.user.name }}</h2>
- <p>Age: {{ slotProps.user.age }}</p>
- <p>Occupation: {{ slotProps.user.occupation }}</p>
- </div>
- </template>
- </ChildComponent>
- </div>
- </template>
-
- <script>
- import ChildComponent from './ChildComponent.vue';
-
- export default {
- components: {
- ChildComponent
- }
- };
- </script>
在父组件中,我们使用
标签和
v-slot:scopedSlot="slotProps"
来定义作用域插槽。slotProps
是一个对象,它包含了子组件传递给插槽的所有属性(在这个例子中是user
对象)。然后,我们就可以在插槽模板中通过slotProps.user
来访问这些数据了。注意:在Vue 3中,作用域插槽的语法略有不同,但基本概念是相同的。在Vue 3中,你可以直接使用
#
前缀来引用具名插槽,并使用v-slot
指令的简写形式。例如:
<ChildComponent> <template #scopedSlot="{ user }"> <div> <h2>Name: {{ user.name }}</h2> <!-- ... --> </div> </template> </ChildComponent>
13. vuex 是什么?为什么使用?
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)
主要包括以下几个模块:
首先,你需要安装 Vuex。在你的项目中,通过 npm 安装:
npm install vuex
在 store.js
或类似文件中:
- import Vue from 'vue';
- import Vuex from 'vuex';
-
- Vue.use(Vuex);
-
- export default new Vuex.Store({
- // state
- state: {
- count: 0
- },
-
- // mutations
- mutations: {
- increment(state) {
- state.count++;
- },
- decrement(state) {
- state.count--;
- }
- },
-
- // actions
- actions: {
- incrementAsync({ commit }) {
- setTimeout(() => {
- commit('increment');
- }, 1000);
- }
- },
-
- // getters
- getters: {
- doubleCount(state) {
- return state.count * 2;
- }
- },
-
- // modules
- modules: {
- // ... 你可以在这里定义模块
- }
- });
在 Vue 组件中,你可以通过 this.$store
来访问 store,但通常建议使用辅助函数 mapState
、mapGetters
、mapMutations
、mapActions
来简化代码。
- <template>
- <div>
- <p>{{ count }}</p>
- <p>{{ doubleCount }}</p>
- <button @click="increment">Increment</button>
- <button @click="decrement">Decrement</button>
- <button @click="incrementAsync">Increment Async</button>
- </div>
- </template>
-
- <script>
- import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
-
- export default {
- computed: {
- // 使用 mapState 辅助函数来映射 state 到计算属性
- ...mapState(['count']),
- // 使用 mapGetters 辅助函数来映射 getters 到计算属性
- ...mapGetters(['doubleCount'])
- },
- methods: {
- // 使用 mapMutations 辅助函数来映射 mutations 到方法
- ...mapMutations(['increment', 'decrement']),
- // 使用 mapActions 辅助函数来映射 actions 到方法
- ...mapActions(['incrementAsync'])
- }
- };
- </script>
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
例如,我们可以有一个名为 user
的模块:
- // store/modules/user.js
- const userModule = {
- state: {
- name: 'Guest'
- },
- mutations: {
- setName(state, payload) {
- state.name = payload;
- }
- },
- // ... getters, actions 等
- };
-
- export default userModule;
然后在主 store 中引入这个模块:
- // store.js
- import Vue from 'vue';
- import Vuex from 'vuex';
- import userModule from './modules/user';
-
- Vue.use(Vuex);
-
- export default new Vuex.Store({
- modules: {
- user: userModule
- }
- // ... 其他选项
- });
在组件中,你可以通过命名空间(namespaced)来访问模块的状态、getters、mutations 和 actions。例如,访问 user
模块的 name
状态:
- computed: {
- ...mapState('user', ['name'])
- }