面试题vue+uniapp(易理解)可先收藏点赞方便查找,未编辑完整....

本文深入对比Vue2和Vue3的区别,涵盖响应式原理的差异、组件间通信方式、路由守卫的使用和Vue2与Vue3的构建工具Webpack与Vite的比较。文章详细介绍了Vue2的Object.defineProperty与Vue3的Proxy在响应式方面的改进,以及Vue2和Vue3监听的区别。同时,文章还探讨了Vue组件缓存、Vue插槽的使用场景和Vuex在状态管理中的作用,以及脚本标签defer和async的差异和iframe优缺点。
摘要由CSDN通过智能技术生成

1.vue2和vue3的区别(vue3与vue2的区别(你不知道细节全在这)_vue2和vue3区别-CSDN博客)参考

  • Vue3 在组合式(Composition )API,中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子
// 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.definePropertyProxy 提供了更多的灵活性,可以监听对象属性的添加、删除、修改等操作。

  • 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和Vue3在监听功能上存在一些明显的区别,这些区别主要体现在监听机制、语法和性能优化等方面。以下是详细的比较:

  1. 监听机制
    • Vue2使用Object.defineProperty进行数据的劫持,实现双向绑定。但这种方式对于新添加的属性无法直接进行监听,需要通过Vue.setthis.$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的示例,展示如何在组件中直接添加新属性:
      <template>  
        <div>  
          <p>Name: {{ person.name }}</p>  
          <p>Age: {{ person.age }}</p>  
          <p>New Property: {{ person.newProperty }}</p>  
        
          <button @click="addNewProperty">Add New Property</button>  
        </div>  
      </template>  
        
      <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会自动触发必要的更新。

  2. 监听语法
    • Vue2中的监听主要通过watch选项或this.$watch方法来实现,用于监听data中的属性变化,并在变化时执行相应的回调函数。
    • Vue3同样提供了watchwatchEffect函数来监听数据变化,但它们的用法更加灵活和强大。watch函数可以监听特定的数据源,并在其变化时执行回调函数;而watchEffect则会自动收集其依赖,并在依赖变化时重新执行该函数。
  3. 深度监听与立即执行
    • 在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)时。

  4. 性能优化
    • Vue2的监听机制在性能上可能存在一定的开销,尤其是在处理大量数据或复杂逻辑时。
    • Vue3通过引入Proxy和Composition API等新技术,对性能进行了优化和提升,使得监听功能更加高效和灵活。
  5. 其他特性
    • Vue3中的监听功能还支持一些其他特性,如监听多个数据源、使用异步函数作为回调函数等。这些特性使得Vue3的监听功能更加强大和灵活。

综上所述,Vue2和Vue3在监听功能上存在明显的区别。Vue3通过引入Proxy和Composition API等新技术,对监听机制进行了改进和优化,使得其性能更加高效、语法更加灵活、功能更加强大。因此,在开发Vue项目时,建议根据项目需求和技术栈选择合适的Vue版本进行使用。

2.vue2和vue3中组件之间的传值方式

1.父传子

v2和v3的父传子差不多
// ParentComponent.vue  
<template>  
  <ChildComponent :message="parentMessage" />  父组件定义的变量parentMessage用message来绑定
</template>  

<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  
<template>  
  <button @click="notifyParent">Notify Parent</button>  
</template>  

<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 (或更高层级的组件)  
<script>  
export default {  
  provide() {  
    return {  
      message: 'Hello from Provide/Inject'  
    };  
  }  
};  
</script>  

// ChildComponent.vue (或任意深层的组件)  
<script>  
export default {  
  inject: ['message'],  
  mounted() {  
    console.log(this.message); // "Hello from Provide/Inject"  
  }  
};  
</script>

 组件之间的通信有几种:

  • 父传子--通过自定义属性(兄弟之间的通信通过父组件中间人)
  • 子传父--通过自定义事件
  • bus方式--发布者、订阅者
  • vuex方式
  • ref方式--打破组件间的壁垒,直接拿来用

3.路由守卫Vue路由守卫详解-CSDN博客

Vue路由守卫是Vue Router提供的一种机制,允许开发者在路由跳转前后执行一些逻辑判断或操作,以确保应用程序的导航逻辑正确性和增强用户体验。以下是关于Vue路由守卫的详细解释:

一、路由守卫的概念

路由守卫(Route Guards)是Vue Router中的一个功能,允许我们在路由发生之前、之中、之后执行逻辑检查和操作。这些守卫可以用来实现权限验证、页面访问控制、数据预加载等逻辑,确保用户在应用中的导航流程符合业务需求。

二、路由守卫的分类

Vue路由守卫主要分为以下三种类型:

  1. 全局守卫
    • beforeEach(to, from, next):全局前置守卫,在路由进入之前调用。常用于权限验证,如果用户未登录,可以重定向到登录页面。每个守卫方法接收三个参数:to(即将进入的目标路由对象)、from(当前导航正要离开的路由对象)、next(必须调用的函数来解析这个钩子)。
    • beforeResolve(to, from, next):全局解析守卫,在路由解析之前调用,它在beforeEach之后和afterEach之前调用。
    • afterEach(to, from):全局后置守卫,在路由跳转完成后调用,通常用于记录日志或执行一些全局操作。
  2. 路由独享守卫
    • beforeEnter(to, from, next):在路由配置中直接定义,这些守卫只适用于特定的路由。
  3. 组件内守卫
    • 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提供的一个强大功能,通过它,我们可以在路由跳转前后执行逻辑检查和操作,以确保应用程序的导航逻辑正确性和增强用户体验。开发者可以根据具体需求选择使用全局守卫、路由独享守卫或组件内守卫。

4.webpack和vite的配置 webpack 与 vite的区别_webpack和vite的区别-CSDN博客

Vue2的打包方式

Vue2项目通常使用Webpack作为构建工具进行打包。Webpack是一个模块打包器,可以将项目中的多个模块打包成一个或多个bundle,供浏览器使用。在Vue2项目中,Webpack的配置通常位于vue.config.js文件中,或者通过其他方式进行配置。

在Vue2项目中,打包过程可能包括以下几个步骤:

  1. 设置路由模式:根据项目需求选择hash模式或history模式。
  2. 性能分析和CDN应用:使用Vue CLI提供的性能分析工具对项目进行打包分析,并根据需要将一些大文件或库文件通过CDN引入,以减轻服务器负担和提高加载速度。
  3. Webpack配置:通过vue.config.js或其他方式配置Webpack,包括入口文件、输出目录、加载器(loader)和插件等。

Vue3的打包方式

Vue3项目在打包方式上与Vue2类似,但也有一些新的特性和工具可以使用。

  1. 使用Vite:Vite是一个由原生ESM驱动的Web开发构建工具,它提供了更快的冷启动速度、即时的热模块替换(HMR)和真正的按需编译。目前,Vite主要用于Vue3项目,因为它针对Vue3做了优化。使用Vite可以更快地构建和开发Vue3项目。
  2. Webpack:虽然Vite是Vue3的推荐构建工具,但Webpack仍然可以用于Vue3项目。在Vue3项目中,Webpack的配置和使用方式与Vue2类似,但可能需要针对Vue3的一些新特性进行调整。
  3. cross-env:在Vue3项目中,可以使用cross-env进行分环境配置,根据不同的环境使用不同的接口和配置。这有助于更好地管理不同环境下的项目配置和依赖。

总结

  • Vue2:主要使用Webpack作为构建工具进行打包,配置方式相对传统和灵活。
  • Vue3:推荐使用Vite作为构建工具,因为它提供了更快的构建速度和更好的开发体验。但Webpack仍然可以用于Vue3项目,只是可能需要一些额外的配置和调整。

5.es6新语法有哪些20分钟上手ES6,不会ES6好意思说自己会JS ?_const m = (l + r) >> 1;-CSDN博客

JavaScript高级——ES6基础入门_js es6-CSDN博客

ES6常用方法 总结大全_es6常用的方法-CSDN博客

6.vue中使用keep-alive进行组件缓存Keep-alive组件缓存的使用_keepalive: false-CSDN博客(参考)

keep-alive是Vue提供的一个内置组件,被keep-alive组件包裹的内部组件,其状态将被缓存
keep-alive包裹的组件,其生命周期只能被执行一次,再次进入时不会被执行
keep-alive包裹的组件,会自动新增两个生命周期函数activated和deactivated,每次进入都会被执行
activated( ) 当keepalive包含的组件再次渲染时触发
deactivated( ) 当keepalive包含的组件销毁时触发
keep-alive两个属性include和exclude,可以让keep-alive实现有条件的进行缓存。include匹配到的组件会被进行缓存,exclude匹配到的组件不会被缓存

7. 计算属性computed和watch的区别

computed(计算属性)和watch(侦听器)的区别_watch和计算属性的区别-CSDN博客

计算属性(Computed Properties)

计算属性是基于它们的依赖进行缓存的。只有在它的相关依赖发生改变时才会重新求值。

<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>

侦听器(Watchers)

侦听器允许你执行异步或开销较大的操作,并可以访问数据变化的先前值和当前值。

<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>

 8.闭包和作用域

  • 闭包

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)引入了letconst关键字,它们提供了块级作用域。这意味着使用letconst声明的变量只在其所在的代码块(例如,if语句、for循环或任何由大括号{}包围的代码块)内可见。

优点
  • 提供了更细粒度的作用域控制,有助于减少命名冲突和意外的副作用。
  • 使得代码更加清晰和可维护。
缺点
  • 对于不熟悉ES6+语法的开发者来说,可能需要一些时间来适应新的作用域规则。
  • 在某些旧的浏览器或环境中可能不受支持,需要进行兼容性处理。

9.防抖节流

防抖:当事件被触发后,延迟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);

9.模块化开发 和 面向对象编程 

模块化开发 和 面向对象编程 是两种不同的编程概念,它们各自在软件开发中扮演着重要的角色。

  1. 模块化开发(Modular Programming)

    • 定义:模块化开发是一种将程序划分为多个独立模块的开发方法。每个模块都具有特定的功能和接口,模块之间通过接口进行通信,实现代码的解耦和复用。
    • 优点:
      • 代码复用:通过重用现有的模块,可以提高开发效率,减少代码冗余
      • 易于维护:由于每个模块具有明确的功能和接口,因此可以单独测试和维护每个模块,降低维护成本。
      • 易于扩展:当需要添加新功能时,可以通过添加新的模块或修改现有模块的接口来实现。
    • 实现方式:在多种编程语言中,如JavaScript、Python、Java等,都支持模块化编程。具体的实现方式可能因语言而异,但通常包括将代码组织成函数、类、包或模块等。
  2. 面向对象编程(Object-Oriented Programming, OOP)

    • 定义:面向对象编程是一种编程范式,它将现实世界中的事物抽象为对象,并使用类和对象等概念来设计和组织程序。在面向对象编程中,数据和操作数据的方法被封装在对象中,对象之间通过消息传递进行通信。
    • 核心概念:
      • 类(Class):定义对象的属性和方法的模板。
      • 对象(Object):类的实例化,具有类的属性和方法。
      • 继承(Inheritance):子类继承父类的属性和方法,实现代码的重用。
      • 多态(Polymorphism):不同的对象对同一消息做出不同的响应。
      • 封装(Encapsulation):将数据和方法封装在对象中,隐藏对象的内部实现细节。
    • 优点:
      • 易于理解和维护:由于面向对象编程将现实世界中的事物抽象为对象,因此代码更易于理解和维护。
      • 易于扩展:通过继承和多态等机制,可以方便地扩展和修改程序的功能。
      • 易于复用:通过封装和继承等机制,可以实现代码的复用和共享。
    • 实现方式:在多种编程语言中,如Java、C++、Python等,都支持面向对象编程。这些语言提供了类和对象等概念,以及继承、多态和封装等机制来实现面向对象编程。
  3. (对于上面第2点不清楚的延伸)什么是继承和封装

    继承 和 封装 是面向对象编程(OOP)中的两个核心概念。

    继承(Inheritance)定义:继承是面向对象编程中的一个重要特性,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,并且可以添加或覆盖父类的行为以定义新的行为。

  4. 作用
    • 代码重用:子类可以继承父类的所有公共和受保护的属性和方法,从而避免了重复编写相同的代码。
    • 扩展性:子类可以在继承父类的基础上添加新的属性和方法,或者覆盖父类的方法以实现不同的行为。
    • 多态性的基础:继承是多态性的前提,子类对象可以当作父类对象使用,从而在运行时确定实际调用的方法(动态绑定)。
  5. 封装(Encapsulation)

  6. 定义:封装是面向对象编程中的另一个核心概念,它指的是将数据(属性)和操作这些数据的方法(函数)绑定在一起,作为一个整体(即对象)来处理。封装隐藏了对象的内部状态和实现细节,只对外提供必要的接口来访问和操作对象。
  7. 作用
    • 数据保护:通过封装,可以确保对象内部数据的完整性和安全性。外部代码只能通过对象提供的公共方法来访问或修改对象的属性,从而防止了直接访问和修改对象内部数据的可能性。
    • 提高可维护性:封装使得对象的内部实现细节对外部是隐藏的。这意味着,如果对象的内部实现发生变化,只要不改变对外提供的接口,外部代码就无需修改。这大大提高了代码的可维护性。
    • 模块化设计:封装有助于将复杂系统划分为更小的、更易于管理的模块。每个模块都有明确的接口和职责,从而降低了系统的复杂性。
  8. 总的来说,继承和封装是面向对象编程中非常重要的概念,它们有助于提高代码的重用性、可扩展性和可维护性。

 10.作为一个前端怎么处理图片,也就是怎么实现图片懒加载

图片懒加载(Lazy Loading)是一种优化网页性能的技术,它的核心思想是只在用户滚动到视图内时才开始加载图片,而不是一开始就加载所有图片。这可以大大减少页面初始加载时间,并降低服务器负载。作为一个前端开发者,你可以通过以下步骤实现图片懒加载:

  1. 设置占位图
    在图片的原始位置放置一个占位图或者空白区域,这样在页面初次加载时不会请求实际的图片资源。

  2. 检测滚动和视图
    使用JavaScript监听页面的滚动事件,并判断图片元素是否进入可视区域。这通常通过计算元素的位置与当前视口(viewport)的位置关系来实现。

  3. 动态加载图片
    当图片元素进入可视区域时,通过修改图片的src属性来触发图片的加载。为了避免页面重新布局,可以先将图片的真实URL存储在一个自定义属性(如data-src)中,然后在需要时将其赋值给src属性。

  4. 处理加载状态
    在图片加载过程中,你可能希望显示一个加载指示器(如加载动画)。当图片加载完成后,可以通过监听图片的load事件来隐藏加载指示器。

  5. 优化和回退
    考虑到一些浏览器可能不支持JavaScript或图片懒加载技术,你应该提供一个回退方案,比如使用<noscript>标签来包含普通的<img>元素,以确保在非JavaScript环境下图片仍然能够正常显示。

  6. 使用现代浏览器特性
    一些现代浏览器支持原生的图片懒加载功能,例如通过为<img>标签添加loading="lazy"属性。这可以简化实现并提高效率,但你需要确保在不支持该属性的浏览器上有适当的回退策略。

  7. 库和插件
    如果你不想从头开始实现图片懒加载,可以考虑使用现有的JavaScript库或插件,如lazyload.jslozad.js等。这些库通常提供了丰富的配置选项和事件处理机制,可以方便地集成到你的项目中。

  8. 响应式图片
    在实现图片懒加载的同时,不要忘记考虑响应式图片的需求。你可以使用<picture>元素和<source>元素来提供不同尺寸和分辨率的图片版本,以适应不同的设备和屏幕尺寸。

  9. 测试和优化
    在实现图片懒加载后,务必进行充分的测试,以确保在各种设备和浏览器上都能正常工作。此外,你还可以使用性能分析工具来检查图片加载对页面性能的影响,并根据需要进行优化。

案例演示:

1.使用HTML的loading属性

  • 实现方式
    • <img>标签中添加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.jslozad.js等第三方库来实现图片懒加载。
    • 这些库通常提供了丰富的配置选项和事件处理机制。
  • 注意事项
    • 引入第三方库可能会增加项目的依赖和大小。
    • 需要确保所选的库与你的项目兼容,并仔细阅读其文档以了解如何配置和使用。
  • 以上是实现图片懒加载的几种常见方式,你可以根据自己的需求和项目环境选择适合的方法。

延伸:怎么用防抖(debounce)或节流(throttle)技术来优化图片懒加载性能

好处:防抖(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方法,以避免重复造轮子。
  • 在图片懒加载的实现中,除了使用防抖和节流外,还可以考虑使用Intersection Observer API来更高效地检测图片是否进入视口。

11.vue3 toRef和toRefs的使用和区别

ref、reactive、toRef、toRefs的区别_ref和reactive,toref的区别-CSDN博客(参考)

toReftoRefs在Vue 3中都是用于处理响应式数据的重要API,但它们之间有着明显的区别。以下是它们之间的主要区别:

  1. 功能与作用
    • toRef:此函数用于将对象中的某个属性转化为响应式引用。它接收两个参数:一个对象和一个字符串键名。这个键名指定了要从该对象中提取并转化为响应式引用的属性。例如,let nameRef = toRef(obj, 'name')
    • toRefs:此函数用于将一个响应式对象转换为一个普通对象,其中该对象的每个属性都是指向原始对象属性的响应式引用。这通常用于在模板或计算属性中解构响应式对象,以便可以单独访问其属性,同时保持其响应性。例如,const stateRefs = toRefs(state)
  2. 使用场景
    • toRef:当你需要单独处理响应式对象中的某个属性,并且希望保持该属性的响应性时,你可以使用toRef。这样,你可以在不改变原始对象结构的情况下,轻松地修改和访问该属性的值。
    • toRefs:当你需要将整个响应式对象解构为多个独立的响应式引用,并在模板或计算属性中单独使用这些引用时,toRefs非常有用。这允许你以一种更加清晰和直观的方式处理复杂的响应式数据结构。
  3. 返回值
    • toRef:返回一个包含value属性的对象,该value属性是响应式的,并且与原始对象中的指定属性保持同步。
    • toRefs:返回一个普通对象,该对象的每个属性都是一个响应式引用,它们都与原始响应式对象的对应属性保持同步。
  4. 示例
    • 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) // 输出:'李四'

综上所述,toReftoRefs的主要区别在于它们的功能、使用场景、返回值以及处理响应式数据的方式。选择使用哪一个取决于你的具体需求和场景。

12.在vue里面插槽的使用方法和场景有哪些 

在Vue中,插槽(slot)是一种非常有用的工具,它允许父组件在子组件的模板中插入内容,从而增强组件的复用性和灵活性。以下是关于Vue中插槽的使用方法和场景的详细解释:

一、插槽的使用方法

1. 默认插槽(匿名插槽)
  • 定义:在子组件中使用<slot></slot>来占位,表示一个默认插槽。
  • 使用:在父组件中,当使用子组件时,可以直接在子组件标签内写入需要插入的内容,这些内容将会替换子组件中的<slot></slot>标签。
2. 具名插槽
  • 定义:在子组件中,可以给<slot>标签添加name属性来定义具名插槽。
  • 使用:在父组件中,需要使用<template>标签并搭配v-slot指令(在Vue 2.6.0+中,使用slot属性结合<template>标签的方式已经被废弃,推荐使用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>
3. 作用域插槽
  • 定义:作用域插槽是一种特殊的插槽,它允许子组件将数据传递给父组件的插槽内容。
  • 使用:在子组件的<slot>标签上绑定一个数据对象,然后在父组件的插槽模板中通过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绑定到该插槽上。

父组件(ParentComponent.vue)

<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>

在父组件中,我们使用<template>标签和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)
主要包括以下几个模块:

  • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
  • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
  • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
  • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

使用 

首先,你需要安装 Vuex。在你的项目中,通过 npm 安装:

npm install vuex

1. 创建 Vuex Store

在 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: {  
    // ... 你可以在这里定义模块  
  }  
});

2. 在 Vue 组件中使用 Vuex

在 Vue 组件中,你可以通过 this.$store 来访问 store,但通常建议使用辅助函数 mapStatemapGettersmapMutationsmapActions 来简化代码。

<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>

3. 使用 Vuex Modules

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'])  
}

13.script标签中defer和async的区别,iframe的优点和缺点script标签的defer和async的区别是什么?_script defer async-CSDN博客

defer和async的选择

defer和async的选择取决于你的具体需求。

  1. defer
    • 优点:
      • 脚本会在文档解析完成后、DOMContentLoaded事件触发前执行,确保了在执行脚本时DOM已经准备好,避免了由于脚本执行时DOM未完全加载而导致的错误。
      • 如果有多个defer脚本,它们会按照在文档中出现的顺序执行,确保了脚本之间的依赖关系得以正确处理。
    • 缺点:
      • 脚本的执行仍然会阻塞页面的渲染,尽管这种阻塞是在文档解析完成后。
  2. async
    • 优点:
      • 脚本下载完成后会立即执行,不会等待整个页面加载完成,这可以加快脚本的执行速度。
      • 适用于那些不依赖于页面其他部分的脚本,或者需要尽快执行的脚本。
    • 缺点:
      • 多个async脚本可能会并行加载和执行,因此执行顺序是不确定的,这可能导致依赖问题。
      • 脚本的执行可能会打断页面的渲染,尤其是在脚本较大或网络条件较差的情况下。

iframe的优点和缺点

  1. 优点
    • 独立性:iframe可以在页面上独立显示一个页面或者内容,不会与页面其他元素产生冲突。
    • 可重用性:iframe可以在多个页面中重用同一个页面或者内容,减少了代码的冗余。
    • 异步加载:iframe的加载是异步的,页面可以在不等待iframe加载完成的情况下进行展示。
    • 跨域访问:使用iframe可以方便地实现跨域访问,这在某些场景下非常有用。
  2. 缺点
    • 加载时间:由于每个iframe都需要加载自己的HTML、CSS和JavaScript文件,这会增加服务器请求的次数,导致页面加载时间延长。当页面中存在多个iframe时,这个问题会更加明显。
    • SEO效果:搜索引擎爬虫无法读取iframe中的内容,因此iframe中的内容无法被搜索引擎收录和展示,从而降低了网页的可见性和搜索排名。
    • 可访问性:有些屏幕阅读器可能无法正确读取iframe中的内容,这会影响网页的可访问性。
    • 安全性:iframe可以嵌入来自其他网站的内容,这可能会带来安全问题,例如点击劫持等攻击。此外,由于跨域限制,父页面与iframe之间的通信也会受到一定的限制。

总结:选择使用defer还是async取决于你的具体需求和对页面性能的要求。而iframe作为一种在页面上嵌入其他网页或内容的技术,虽然具有一些优点,但也存在一些明显的缺点,需要在使用时权衡利弊。

 14.重排和重绘

什么是重排与重绘,如何减少重排_如何减少重排和重绘-CSDN博客重排也叫回流,当 DOM 的变化影响了元素的几何信息(位置、尺寸大小等),浏览器需要重新计算元素的几何属性,将其安放在界面的正确位置,这个过程叫做重排,

当一个元素的外观发生变化,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

  重排一定导致重绘,重绘不一定导致重排。       
     1. 每个页面初始化渲染时至少触发一次重排和重绘。

     2. 重排和重绘的代价都很高昂,频繁重排重绘,会破坏用户体验,让页面显示变迟缓。

     3. 所以我们要尽可能避免频繁触发重排和重绘,尤其是重排。

15.SPA(单页应用)和MPA(多页应用)

SPA(单页应用)和MPA(多页应用)的核心区别可以归纳如下:

  1. 结构
    • SPA:由单个主页面和多个模块的组件组成。整个应用只有一个HTML页面,初次加载时,服务器返回这个页面,此后所有的页面内容切换和数据加载都通过JavaScript在客户端完成。
    • MPA:由多个完整的页面组成,每个页面需要重复加载js、css等相关资源。
  2. 刷新方式
    • SPA:页面局部刷新,用户在浏览器地址栏中的URL变化不会触发整个页面的刷新,而是由JavaScript捕获并处理。这种技术主要依赖于AJAX和前端路由来实现页面的无刷新更新。
    • MPA:整页刷新,多页应用跳转需要整页资源刷新。
  3. 用户体验
    • SPA:页面切换快速,体验好,类似于桌面应用的操作体验。
    • MPA:页面切换相对较慢,体验可能不如SPA流畅。
  4. 使用场景
    • SPA:适用于对体验度和流畅度有较高要求的应用,但可能不利于SEO。
    • MPA:适用于对SEO要求较高的应用。
  5. 路由模式
    • SPA:可以使用hash或history模式进行前端路由管理。
    • MPA:使用普通链接跳转。
  6. 首屏时间
    • SPA:由于初次加载需要下载所有必要的HTML、CSS和JavaScript文件,可能导致首次加载时间较长。
    • MPA:每个页面独立加载,首屏时间可能相对较快。
  7. 开发和维护
    • SPA:前端复杂度增加,需要处理复杂的前端路由和状态管理,但前后端分离更清晰,前端负责页面显示和交互,后端负责数据处理和业务逻辑。
    • MPA:开发过程可能更传统,前后端耦合度可能较高,但开发和维护难度可能相对较低。
  8. SEO优化
    • SPA:由于动态内容难以被传统搜索引擎抓取,SEO优化较困难,但可以通过服务器端渲染(SSR)或预渲染等技术缓解。
    • MPA:每个页面都是独立的,更容易被搜索引擎抓取,SEO优化相对容易。

综上所述,SPA和MPA在结构、刷新方式、用户体验、使用场景、路由模式、首屏时间、开发和维护以及SEO优化等方面存在显著区别。在选择使用哪种架构时,需要根据项目的具体需求进行评估和选择。

16.tob和toc是什么意思

TOB和TOC是两种不同的商业模式,分别代表面向企业的服务和面向个体消费者的服务。以下是关于TOB和TOC的详细解释:

  1. 定义

    • TOB(To Business):指的是公司的产品或服务所面向的用户是企业。这里的“Business”可以是各种企业、组织或机构。
    • TOC(To Customer):指的是公司的产品或服务所面向的用户是广大个体用户,即消费者。这里的“Customer”指的是普通大众,是个人用户。
  2. 用户群体

    • TOB:面对的是企业客户,这些客户可能是其他企业、组织或机构。
    • TOC:面对的是个体用户,即广大消费者。
  3. 产品形态

    • TOB:通常涉及解决企业的需求,如为企业定制OA系统、ERP系统、CRM系统、医疗系统等。这些产品较为复杂,并且与日常生活相关不大。
    • TOC:产品形态多样,包括各种面向个人的应用、设备、服务等。例如,衣食住行各类APP都是TOC类产品。
  4. 关注点

    • TOB:通常关注效率提升、成本控制、流程优化等,以满足企业的业务需求。
    • TOC:关注用户体验、产品易用性、性价比等,以满足个人消费者的需求。
  5. 优劣势

    • TOB:门槛较高,需要一定的技术实力和专业能力才能进入市场。但一旦建立稳定的合作关系,收入相对稳定。
    • TOC:初级门槛较低,可以迅速推向市场。但竞争激烈,需要不断创新和改进以吸引用户。
  6. 运营商

    • TOB:运营商一般是软件厂商或技术提供商,他们主要面向企业市场。
    • TOC:运营商一般是IT行业的巨头或创业公司,他们主要面向个人用户市场。

综上所述,TOB和TOC在定义、用户群体、产品形态、关注点、优劣势和运营商等方面存在明显的区别。这两种商业模式各有特点,适用于不同的市场和应用场景。

17.如何获取事件对象?

什么是事件对象(Event Object)?

在前端开发中,事件处理是构建交互式用户界面的核心。每当用户与网页进行互动,如点击按钮、滚动页面或输入文本时,浏览器都会触发一个事件。为了响应这些事件,JavaScript 提供了 Event对象,它封装了所有关于事件的信息。

基本概念和作用说明
Event对象是所有事件的基础,当特定的用户操作发生时由浏览器创建并传递给事件处理函数。它携带了与事件相关的重要信息,包括事件类型、事件目标、鼠标位置、键盘按键等。理解并正确使用Event对象可以让我们编写更加高效、灵活的事件处理器。

方式一:默认获取

不填写时,默认第一个参数就是 event 对象。

<template>
    <div>
        <button @click="onClick">点击</button>
    </div>
</template>
 
<script>
export default {
    methods: {
        onClick(e) {
            console.log(e);
        }
    }
}
</script>

方式二:$event

特殊变量 $event。 内联处理器中的方法 和 API 指令 v-on 中都有介绍。

必须是 $event 的写法,该参数位置可随意,也可放在第一个。

<template>
    <div>
        <button @click="onClick('hello', $event)">点击</button>
        <!-- <button @click="onClick($event, 'hello')">点击</button> -->
    </div>
</template>
 
<script>
export default {
    methods: {
        onClick(str, e) {
            console.log(str, e);
        }
    }
}
</script>

方式三:监听click事件 

document.getElementById('myButton').addEventListener('click', function(event) {
    console.log('Button clicked!');
});

方式四:访问事件对象的属性 

document.getElementById('myButton').addEventListener('click', function(event) {
    console.log('Event type:', event.type);
    console.log('Event target:', event.target);
    console.log('Event currentTarget:', event.currentTarget);
});

 方式五:阻止默认行为

有时我们可能不希望事件执行其默认动作,比如阻止链接的跳转。我们可以使用preventDefault()方法来实现这一点。

document.getElementById('myLink').addEventListener('click', function(event) {
    event.preventDefault();
    console.log('Link click prevented.');
});

方式六:取消事件冒泡

事件冒泡是指事件从最深的节点开始逐级向上传播,直到文档根节点。我们可以使用stopPropagation()来阻止这种传播。

document.getElementById('myDiv').addEventListener('click', function(event) {
    event.stopPropagation();
    console.log('Click on div handled.');
});

方式七:键盘事件

window.addEventListener('keydown', function(event) {
    if (event.key === 'Enter') {
        console.log('Enter key pressed.');
    }
});

 

事件委托:通过监听父元素的事件,可以更高效地处理子元素的事件,这称为事件委托。这是因为事件会冒泡到父元素上,所以不需要为每个子元素单独添加事件监听器。


性能优化:避免在事件处理函数中进行大量计算或DOM操作,因为这会影响页面的性能。如果需要进行复杂处理,考虑将逻辑移到事件处理函数之外。


兼容性检查:不同的浏览器可能对某些事件的处理方式略有不同,使用Event对象前应检查其属性和方法是否可用。

18.$refs在通信时的弊端?

vue中 this.$refs的坑_$refs的弊端-CSDN博客

Vue中ref和$refs的介绍及使用 - 知乎 (zhihu.com)

使用$refs进行通讯的弊端主要有以下几点:

破坏了单向数据流的原则:在Vue中,数据应该是单向流动的,即从父组件向子组件流动。使用$refs通信时会破坏这种单向数据流的原则,因为父组件可以直接修改子组件中的数据,容易导致状态管理的混乱和难以维护。

引起脆弱的组件关系:使用$refs通信时,父组件和子组件之间形成了紧密的耦合关系,这使得组件变得更加脆弱,如果其中一个组件发生了变化,则可能导致另一个组件无法正常工作。

可能导致组件复用困难:在Vue中,组件是可以复用的。但是,如果使用$refs进行通信,则只能将父组件和子组件绑定在一起,不能实现真正的组件复用。

19.vuex页面刷新state是否存在?

Vue 之 vuex 解决刷新页面 state 数据丢失的问题,使用vuex-persistedstate进行state持久化_vue state 缓存-CSDN博客

在 Vuex 中,当页面刷新时,默认情况下 state 中的数据会丢失。Vuex 的状态存储在内存中,当页面刷新时,页面会重新加载,内存中的数据会被重置,包括 Vuex 的 state。但是,可以通过一些方法来解决这个问题,例如:
// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    importantData: null
  },
  mutations: {
    setImportantData(state, data) {
      state.importantData = data;
      // 将数据存储到本地存储
      localStorage.setItem('importantData', JSON.stringify(data));
    },
    loadImportantDataFromLocalStorage(state) {
      const dataFromLocalStorage = localStorage.getItem('importantData');
      if (dataFromLocalStorage) {
        state.importantData = JSON.parse(dataFromLocalStorage);
      }
    }
  },
  actions: {
    // 在某个适当的 action 中调用 setImportantData 来保存数据
    saveImportantData({ commit }, data) {
      commit('setImportantData', data);
    }
  }
});

// 在页面加载时调用加载本地存储数据的 mutation
store.commit('loadImportantDataFromLocalStorage');

export default store;

 

利用本地存储;
  这个时候我们就需要利用到浏览器的会话缓存 sessionStorage 来实现(当然你也可以考虑使用localStorage或者 cookies),具体实现代码如下:
  • 在 App.vue 中增加监听刷新事件:
  • export default {
        name: "App",
        mounted() {
            window.addEventListener("unload", this.savaState);
        },
        methods: {
            savaState() {
                sessionStorage.setItem("tabList", JSON.stringify(this.$store.state.tabsView.tabList));
            },
        }
    }
    

利用插件vuex-persistedstate; 通过插件的方式实现 Vuex 的状态持久化

安装npm install --save vuex-persistedstate

方式1:
  1. 在 Vuex store 中引入并使用插件:
  2.    import Vue from 'vue';
       import Vuex from 'vuex';
       import createPersistedState from 'vuex-persistedstate';
    
       Vue.use(Vuex);
    
       const store = new Vuex.Store({
         // 你的状态、mutations、actions 等
         plugins: [createPersistedState()],
       });
    
       export default store;

    这个插件默认会将所有的 state 持久化到本地存储(localStorage)中。你可以通过配置参数来指定存储方式、存储的键名、要持久化的模块等。例如:

  3.    plugins: [
         createPersistedState({
           storage: window.sessionStorage, // 使用 sessionStorage 而不是 localStorage
           key: 'my-app-state', // 存储在本地存储中的键名
           paths: ['moduleA', 'moduleB.someState'], // 只持久化指定模块或状态的路径
         }),
       ],
    方式2:
  • tabsView.js
  • /**
     * 顶部页签栏
     */
    
    const state = {
        // tabList: sessionStorage.getItem('tabList') ? JSON.parse(sessionStorage.getItem('tabList')) : [], // 页签数组
        tabList: [], // 页签数组
    }
    
    const getter = {}
    
    const mutations = {
    }
    
    const actions = {
    }
    
    export default {
        namespaced: true, // 开启命名空间
        state,
        getter,
        mutations,
        actions,
    }
    

  • store 出口文件 index.js
  • import Vue from "vue"
    import Vuex from "vuex"
    // 引入插件api
    import createPersistedState from "vuex-persistedstate";
    // 引入module js文件
    import tabsView from "./modules/tabsView"
    
    const tabListState = createPersistedState({
        storage: window.sessionStorage, // 使用sessionStorage 进行state 持久化
        paths: ['tabsView.tabList'] // 只对 tabsView module 文件中的 tabList state 持久化
        // paths: ['tabsView'] // 对 tabsView module 文件中的整个 state 持久化
    })
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
        modules: {
            tabsView,
        },
        plugins: [tabListState]
    })
    
    export default store
    
    方式3:

在 Vuex 的 mutations 中添加保存状态到本地存储的逻辑:(手动实现持久化

   mutations: {
     setSomeState(state, value) {
       state.someState = value;
       localStorage.setItem('myAppSomeState', value);
     },
   },

在 store 创建时从本地存储中加载状态:
   const store = new Vuex.Store({
     state: {
       someState: localStorage.getItem('myAppSomeState') || initialValue,
     },
     // 其他配置
   });
这种方式需要手动管理每个要持久化的状态,但可以更灵活地控制持久化的逻辑。

 

总结:

方案一:比较适合只对一个state 进行持久化,代码程度不复杂,简单,但是随之而来的就是书写的复杂度,在对state 命名赋值的时候都需要判断是从本地存储中取,还是直接赋值,代码不美观,不易于维护。
方案二:相比方案一,依赖插件,代码美观,统一的都在store的出口文件index.js中进行维护,具有一定的优势,特别是如果需要对多个state 进行持久化的化,方案二无疑是最佳选择。

 

20.cookies,localStorage,sessionStorage有什么区别,一般情况用什么存储,各自有什么弊端 Cookie、LocalStorage和SessionStorage:一次非常详细的对比!_cookie localstorage sessionstorage-CSDN博客

一、存储容量

  • cookies:一般存储容量较小,通常为 4KB 左右。(1KB = 1024 字节,1MB = 1024KB)
  • localStoragesessionStorage:存储容量相对较大,一般为 5MB 左右。(一般手机拍摄的照片可能在 2MB 到 5MB 左右;一首普通音质的歌曲大概在几 MB,通常在 3MB 到 10MB 之间。)

二、数据有效期

  • cookies:可以设置过期时间,如果不设置,在浏览器关闭时会被删除;也可以设置长期有效的过期时间,使其在特定日期之后才过期。
  • localStorage:数据长期有效,除非手动清除或者用户通过浏览器设置清除本地存储数据。
  • sessionStorage:数据仅在当前会话有效,即当浏览器窗口或标签页关闭时,数据会被清除。

三、与服务器的交互

  • cookies:每次 HTTP 请求时会自动携带在请求头中发送给服务器,会增加网络流量。
  • localStoragesessionStorage:仅存储在客户端,不会随请求自动发送给服务器。

四、存储位置和访问范围

  • cookies:可以被同一域名下的不同页面以及不同子域名共享(具体取决于设置的domainpath属性)。
  • localStorage:在同一域名下的所有页面都可以访问和修改。
  • sessionStorage:仅在创建它的窗口或标签页内的页面可以访问,不同窗口或标签页即使是同一域名也不能共享。

一般情况的使用场景

  • 如果需要在不同页面之间共享一些小量的、不敏感的数据,且希望数据在一段时间内有效,可以使用cookies,比如存储用户登录状态的令牌等。
  • 如果需要在同一域名下的不同页面之间共享大量数据,且希望数据长期保存,使用localStorage,例如存储用户的偏好设置。
  • 如果需要在当前会话中临时存储一些数据,仅在当前窗口或标签页有效,可以使用sessionStorage,比如在表单填写过程中临时保存数据,以防页面意外刷新。

各自的弊端

  • cookies
    • 存储容量小,不适合存储大量数据。
    • 每次请求都会携带,增加网络流量。
    • 可能被用户禁用。
  • localStorage
    • 存储的敏感数据可能会被恶意脚本获取,存在安全风险。
    • 长期存储的数据可能会占用过多的存储空间,影响浏览器性能。
    • 如果存储的数据结构发生变化,可能需要手动处理数据迁移问题。
  • sessionStorage
    • 数据仅在当前会话有效,关闭窗口或标签页后数据丢失,不适合长期存储重要数据。
    • 同样存在被恶意脚本获取的安全风险。

 21..sync修饰符

 vue中我们经常会用v-bind(缩写为:)给子组件传入参数。
或者我们会给子组件传入一个函数,子组件通过调用传入的函数来改变父组件的状态。
例如:

//父组件给子组件传入一个函数
 <MyFooter :age="age" @setAge="(res)=> age = res">
 </MyFooter>
 //子组件通过调用这个函数来实现修改父组件的状态。
 mounted () {
      console.log(this.$emit('setAge',1234567));
 }

 这样有点复杂,这时我们可以直接这样写

//父组件将age传给子组件并使用.sync修饰符。
<MyFooter :age.sync="age">
</MyFooter>
//子组件触发事件
 mounted () {
    console.log(this.$emit('update:age',1234567));
 }

 

这里注意我们的事件名称被换成了update:age
update:是被固定的也就是vue为我们约定好的名称部分
age是我们要修改的状态的名称,是我们手动配置的,与传入的状态名字对应起来

这样就完成了,是不是感觉简单了很多。

注意事项:
这里我们必须在事件执行名称前加上update:的前缀才能正确触发事件。

22.v-model语法糖vue中内置指令v-model的作用和常见使用方法介绍以及在自定义组件上支持-CSDN博客

单向数据绑定:
在Vue中,我们可以使用v-bind实现单项的数据绑定,也就是通过父组件向子组件传入数据 ,但是反过来,子组件不可以修改父组件传递过来的数据 ,这也就是所谓的单向数据绑定。

双向数据绑定
v-bind和v-on实现了双向绑定实现了双向数据绑定。

1、对于输入框(input):

<input type="text" v-bind:value="value" v-on:input="value = $event.target.value" />

<input type="text" :value="value" @input="value = $event.target.value" />

 v-model是v-bind和v-on的语法糖,即,v-model算是v-band和v-on的简洁写法。

<input type="text" v-model="value" />

 在这个例子中,v-model将输入框的值与数据对象中的value属性进行了绑定。当用户输入时,value的值会自动更新。

23.wue首屏优化如何处理?

A.减少Http的请求
html5 - 前端性能优化(1)--减少HTTP请求 - 个人文章 - SegmentFault 思否

前端性能优化 - 静态资源合并与压缩减少HTTP请求_静态资源压缩-CSDN博客

如何减少Http请求:
1.合并文件:将多个 CSS 或 JavaScript 文件合并为一个文件,可以减少 HTTP 请求的数量。

2.使用雪碧图:将多个小图像合并为一个大图像,然后通过 CSS 的 background-position 属性来显示需要的部分。这可以减少图像的 HTTP 请求。

3.使用数据 URI:数据 URI 允许你将小文件(如小图像)直接嵌入到 HTML 或 CSS 中,从而避免发送额外的 HTTP 请求。

4.使用字体图标:字体图标可以将多个图标合并为一个字体文件,从而减少 HTTP 请求。

5.懒加载:懒加载是一种技术,可以延迟加载非视口区域的内容。当用户滚动到这些内容时,再发送 HTTP 请求去加载。

6.使用 CDN:虽然使用 CDN 本身不会减少 HTTP 请求的数量,但 CDN 可以缩短服务器响应时间,从而提高网页的加载速度。

7.开启 HTTP/2:HTTP/2 支持多路复用,可以在一个 TCP 连接上并行发送多个 HTTP 请求,从而减少网络延迟。

8.内联图片 & base64

B.路由懒加载

前端路由懒加载是一种优化网页加载性能的技术,主要用于单页应用(SPA)。其核心思想是将路由对应的组件进行按需加载,而不是一次性加载所有的页面组件。这样可以减少初始加载时间,提高页面响应速度。

实现步骤

以Vue.js为例,前端路由懒加载的实现通常结合Webpack的动态import功能来完成。

  1. 配置路由:在路由配置文件中使用动态import语法来定义路由组件。
  2. 设置Webpack:确保Webpack配置支持代码分割和动态import。
  3. 加载组件:当用户访问某个路由时,才会触发对该路由组件的加载。

安装:npm install vue-router 

配置路由:

// router/index.js
import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

const routes = [
  {
    path: '/home',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '../components/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../components/About.vue')
  }
];

const router = new Router({
  routes
});

export default router;

在上述代码中,component属性使用了动态import语法,这会使Webpack在打包时将每个路由组件分割成单独的文件(chunk)。

优点
减小初始加载体积:只加载用户当前需要的部分,减少了初始加载时间。
提高加载速度:后续页面可以在后台异步加载,提高了整体用户体验。
按需加载:根据用户的操作来加载资源,避免了不必要的加载。
缺点
首屏加载时间:如果首屏需要懒加载的组件,可能会导致首屏加载时间增加。
更多的HTTP请求:每个懒加载的组件都需要单独的HTTP请求,可能会带来额外的请求开销。
管理复杂度:代码分割和懒加载的配置增加了项目的复杂性,需要更多的测试和维护。
通过懒加载技术,前端应用可以显著提升加载性能和用户体验,特别是在组件较多的大型应用中。

C.数据预获取和缓存:
在首屏加载之前,通过 API 请求预获取必要的数据,并将其缓存起来。这样可以避免在首屏显示时进行额外的网络请求,提高数据的获取速度。

D.服务器端渲染(SSR):

考虑使用 Vue 的服务器端渲染技术,将首屏在服务器端生成 HTML 发送到客户端,减少客户端的初始化负载。SSR 可以提供更好的首屏性能和 SEO 支持。

E.代码压缩和合并:

对 JavaScript、CSS 和 HTML 代码进行压缩和合并,减少文件大小和网络传输量。可以使用工具如 Webpack 进行构建和优化。

F.使用 CDN 加速:

将静态资源(如 JavaScript、CSS 和图片)部署到内容分发网络(CDN)上,利用 CDN 的全球分布节点来加速资源的加载。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值