# 17. 面试问题2

# 数据类型

JavaScript中的数据类型包括:

  1. 基本数据类型(Primitive Data Types):
    • 数字(Number)
    • 字符串(String)
    • 布尔值(Boolean)
    • 未定义(Undefined)
    • 空值(Null)
    • 符号(Symbol)(ES6新增)
  2. 引用数据类型(Reference Data Types):
    • 对象(Object)
    • 数组(Array)
    • 函数(Function)
    • 日期(Date)
    • 正则表达式(RegExp)等 进行数据类型检测的方法:
  • 使用typeof操作符:返回一个字符串,表示未经计算的操作数的类型。

    typeof 42 // "number"
    typeof "hello" // "string"
    typeof true // "boolean"
    typeof undefined // "undefined"
    typeof null // "object"
    typeof {} // "object"
    typeof [] // "object"
    typeof function(){} // "function"
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 使用instanceof操作符:用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。

    [] instanceof Array // true
    {} instanceof Object // true
    new Date() instanceof Date // true
    
    1
    2
    3
  • 使用Object.prototype.toString方法:

    Object.prototype.toString.call(42) // "[object Number]"
    Object.prototype.toString.call("hello") // "[object String]"
    Object.prototype.toString.call(true) // "[object Boolean]"
    Object.prototype.toString.call(undefined) // "[object Undefined]"
    Object.prototype.toString.call(null) // "[object Null]"
    Object.prototype.toString.call({}) // "[object Object]"
    Object.prototype.toString.call([]) // "[object Array]"
    Object.prototype.toString.call(function(){}) // "[object Function]"
    
    1
    2
    3
    4
    5
    6
    7
    8

    这些方法可以帮助我们在JavaScript中进行数据类型的检测。

# 闭包

JavaScript闭包是指在函数内部创建另一个函数,并且内部函数可以访问外部函数的作用域。这意味着内部函数可以使用外部函数的变量、参数和其他局部变量,即使外部函数已经执行结束。闭包能够“记住”创建它们的作用域,这使得它们可以在其创建的作用域之外被调用和访问。闭包在JavaScript中常用于模块化编程和实现私有变量。

function outerFunction() {
  var outerVariable = 'I am from the outer function';
  
  function innerFunction() {
    console.log(outerVariable); // Inner function accessing the outer variable
  }
  
  return innerFunction;
}
var closure = outerFunction();
closure(); // This will print: "I am from the outer function"
1
2
3
4
5
6
7
8
9
10
11

# this

在JavaScript中,this关键字代表当前执行代码的对象。其值取决于函数的调用方式。当函数被调用时,this的指向可能会有所不同:

  1. 在全局作用域中,this指向全局对象(在浏览器中是window对象)。
  2. 在对象方法中,this指向调用该方法的对象。
  3. 在构造函数中,this指向正在创建的实例。
  4. 使用callapplybind方法时,可以显式指定this的值。 在箭头函数中,this的值取决于外层(定义时)的上下文,而不是调用时的情况。

# 原型&原型链

在 JavaScript 中,每个对象都有一个指向另一个对象的引用,这个对象就是它的原型(prototype)。每个对象从它的原型继承属性和方法。原型链是指当你访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 就会沿着原型链向上搜索直到找到为止。 原型链的概念可以归结为以下几个要点:

  1. 每个对象都有一个原型对象,可以通过 __proto__ 属性来访问。
  2. 如果对象本身没有某个属性或方法,JavaScript 引擎会沿着原型链向上搜索直到找到该属性或方法,如果一直找到顶层的 Object.prototype 仍然没有找到,则返回 undefined。
  3. 当你创建一个对象时,它的原型会自动指向构造函数的 prototype 属性。

# 深拷贝 vs 浅拷贝

浅拷贝创建一个新对象或数组,新对象的属性值是原始对象的引用。这意味着如果原始对象中的属性是引用类型(如数组或对象),则新对象中的相应属性仍然指向原始对象中的属性。 深拷贝创建一个新对象或数组,新对象中的所有属性值都是原始对象或数组中属性的副本。即使原始对象中的属性是引用类型,深拷贝也会递归地复制这些属性的值,而不是简单地复制引用。

# 事件循环

事件循环是用于处理异步操作的一种机制,常见于JavaScript环境。在浏览器中,事件循环用于处理用户交互、网络请求、定时器等事件。 事件循环由以下几个部分组成:

  1. 调用栈(Call Stack):用于存储执行上下文(函数调用)的栈结构。
  2. 消息队列(Message Queue):用于存储待处理的消息和事件。
  3. 微任务队列(Microtask Queue):存储微任务,如Promise、MutationObserver等。
  4. 宏任务队列(Task Queue):存储宏任务,如setTimeout、setInterval、I/O操作等。 事件循环的工作原理如下:
  5. 执行全局同步代码,将同步任务推入调用栈。
  6. 执行栈中的任务,如果遇到异步任务,则将其推入消息队列。
  7. 当调用栈为空时,事件循环将检查微任务队列,依次执行所有微任务。
  8. 执行完所有微任务后,事件循环从宏任务队列中取出一个任务,推入调用栈执行。
  9. 重复上述步骤,直至所有任务完成。 这种机制确保了JavaScript运行时的异步操作能够以非阻塞的方式进行,同时保持了一定的执行顺序和优先级。

# Promise

Promise是JavaScript中用于处理异步操作的对象,Promise也是ES6引入的一项重要特性,为异步编程提供了更好的解决方案。Promise对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。 创建Promise对象时,传入一个执行器函数,该函数接受两个参数,resolve和reject,分别用于表示操作成功和失败。这样可以在异步操作完成后调用resolve来将Promise状态从pending变为fulfilled,或者调用reject来将Promise状态从pending变为rejected。 Promise对象具有then方法,可以用来指定在Promise状态改变时的回调函数。它接受两个参数,第一个参数用于处理成功的情况,第二个参数用于处理失败的情况。 通过使用Promise,可以更清晰地表达异步操作的执行流程,并且避免了回调地狱(callback hell)的问题。。Promise通过其链式调用的特性和then方法,可以更清晰地表达异步操作的执行顺序,从而避免了回调地狱问题。Promise也是ES6引入的一项重要特性,为异步编程提供了更好的解决方案。

。而async/await结合Promise,让异步代码看起来更像同步代码,使得异步操作的流程更加清晰和易于理解。

# 函数式编程

函数式编程是一种编程范式,它将计算视为数学函数的评估。函数式编程强调使用纯函数(Pure Function),避免状态变化和可变数据。主要特点包括:

  1. 纯函数:纯函数是指具有相同输入时总是产生相同输出,并且没有副作用(不会修改外部状态或引起其他可观察的行为)的函数。

函数式编程的优点包括代码简洁、易于理解和测试,以及更容易进行并发处理。

# 生命周期

Vue.js组件有不同的生命周期钩子,这些生命周期钩子允许我们在组件的不同阶段执行代码。以下是Vue.js 2.x版本的生命周期钩子方法:

  1. beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  2. created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前尚不可用。
  3. beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
  4. mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  6. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。
  7. beforeDestroy:在实例销毁之前调用。实例仍然完全可用。
  8. destroyed:在实例销毁之后调用。Vue 实例的所有指令都被解绑,所有事件监听器被移除,所有子实例被销毁。 这些生命周期钩子为我们提供了在组件生命周期的不同阶段执行代码的机会,从而可以控制应用程序的行为。

# computed vs watch vs watchEffect

在Vue.js中,computed、watch 和 watchEffect 都用于响应式地处理数据变化,但它们有不同的用途和工作方式:

  1. Computed:
    • 用于基于响应式依赖进行计算,它是基于响应式数据的声明式计算属性。
    • computed 属性只有在依赖的响应式数据发生变化时才会重新计算。
    • 适用于派生出一些基于现有数据计算得出的新数据。
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}
1
2
3
4
5
  1. Watch:
    • 用于观察和对响应式数据的变化做出反应,可以执行一些异步或复杂的操作。
    • 可以监听一个具体的数据的变化,也可以监听多个数据的变化。
watch: {
  firstName(newVal, oldVal) {
    // Do something when firstName changes
  }
}
1
2
3
4
5
  1. watchEffect:
    • 用于执行响应式数据变化时的副作用,类似于 watch,但不需要显式地指定要观察的数据,它会自动追踪组件中使用的响应式数据,并在其变化时执行相应的副作用。
    • watchEffect 内部会自动追踪响应式数据的变化,并在其变化时执行回调函数。
watchEffect(() => {
  // This will run whenever any reactive dependency changes
  console.log(this.firstName + ' ' + this.lastName);
});
1
2
3
4

# 通信方式

Vue 中有多种通信方式,其中包括:

  1. Props / Events (父子组件通信):
    • 通过props向子组件传递数据,通过events触发父组件中的方法。
    • 父组件通过props向子组件传递数据,子组件通过events将信息传递回父组件。
  2. Custom Events (自定义事件):
    • 可以在Vue实例上使用$on(eventName, eventHandler)监听事件,使用$emit(eventName, payload)触发事件。
    • 用于非父子组件之间的通信。
  3. Vuex (集中式状态管理):
    • 用于在大型应用程序中进行集中式状态管理。
    • 通过store中的state、mutations、actions等来实现组件间的通信和状态管理。
  4. Provide / Inject (祖先传递数据给后代):
    • 祖先组件通过provide提供数据,后代组件通过inject注入数据。
    • 用于祖先组件向后代组件传递数据,不论层级有多深。
  5. Event Bus:
    • 可以创建一个空的 Vue 实例作为事件总线,然后在组件中使用$on和$emit来监听和触发事件。
    • 用于简单的跨组件通信。
  6. $attrs / $listeners:
    • 用于在组件之间传递所有未被props捕获的特性和监听器。 这些通信方式各有优劣,应根据具体的场景和需求来选择合适的通信方式。

# vue2 vs vue3

Vue 3 introduces several key improvements over Vue 2:

  1. Performance: Vue 3 offers better performance due to its optimized reactivity system and a new virtual DOM.
  2. Composition API: Vue 3 introduces the Composition API as an alternative to the Options API, making it easier to organize and reuse code logic.
  3. Teleport: Vue 3 introduces Teleport, a feature that allows components to be moved to a different place in the DOM.
  4. Fragments: Vue 3 supports Fragments, allowing multiple root elements in a component.
  5. Better TypeScript support: Vue 3 offers improved TypeScript integration.
  6. Smaller bundle size: Vue 3 has a smaller bundle size compared to Vue 2. These improvements make Vue 3 a more powerful and efficient framework for building web applications compared to Vue 2.

# vue导航守卫钩子

  1. 全局前置守卫 ( beforeEach ): 在每次路由跳转前都会被调用,可以用来进行全局的身份验证、权限控制等操作。
  2. 路由独享的守卫 ( beforeEnter ): 在路由配置中针对某个特定的路由使用,它会在进入该路由前被调用。
  3. 组件内的守卫 ( beforeRouteEnter , beforeRouteUpdate , beforeRouteLeave ): 在组件内部使用,可以在组件加载前、组件更新前和组件离开前分别被调用。

# Vue 的数据绑定原理

Vue 的数据绑定原理主要包括两个部分:数据劫持和发布-订阅模式。

  1. 数据劫持(响应式系统):Vue 使用了 Object.defineProperty() 方法对数据对象进行劫持,通过给数据对象的属性添加 getter 和 setter 实现对属性的劫持。当属性被读取时,Vue 的 getter 会被触发,用于收集依赖;当属性被修改时,Vue 的 setter 会被触发,用于通知依赖更新。这样可以实现数据的响应式更新。
  2. 发布-订阅模式:每个 Vue 实例都有一个订阅者 Watcher,它用于收集当前组件需要监听的属性依赖,当属性被修改时,Watcher 负责通知订阅者执行相应的更新操作。当数据发生变化,Vue 会将变化的消息发送给所有订阅该数据的 Watcher,然后 Watcher 再触发组件的重新渲染。 具体的数据绑定流程如下:
  3. 创建 Vue 实例时,会对数据对象进行劫持,将对象的每个属性转换为 getter 和 setter。
  4. 在组件的模板中使用数据时,模板编译过程中会通过访问数据对象的属性,触发相应属性的 getter。此时,Watcher 会收集该属性的依赖,并将 Watcher 与属性关联起来。
  5. 当属性被修改时,会触发属性的 setter,setter 会通知相关的 Watcher 更新数据。
  6. Watcher 收到数据更新的通知后,会执行相关的更新操作,比如重新计算属性或更新组件的 DOM。 通过这种方式,Vue 实现了数据与视图的双向绑定,即当数据发生变化时,视图会自动更新;反之,当视图有用户操作时,数据也会相应地更新。这种数据绑定机制使得代码编写更简洁、高效,并且提供了良好的用户体验。

# vue组件data为什么是一个函数

组件是可以复用的,如果data是一个对象,那么多个相同组件的data是共享的。所以需要创建一个函数返回一个对象,这个对象在多个组件中是独立存在的。不会互相印象。

# 虚拟dom如何对应真实dom

虚拟 DOM (Virtual DOM) 是 Vue 中的一种机制,它是一个轻量级的 JavaScript 对象,用来描述真实 DOM 的结构和状态。当数据发生变化时,Vue 会通过对比新旧虚拟 DOM,找出需要更新的部分,并最终将这些更新应用到真实 DOM 上。 虚拟 DOM 是通过 JavaScript 对象模拟的真实 DOM,它具有和真实 DOM 类似的结构。每个虚拟 DOM 节点表示了一个真实 DOM 节点,它包含了节点的标签名、属性、子节点等信息。 当 Vue 更新组件的视图时,会首先生成一个全新的虚拟 DOM 树。然后,Vue 会将新旧虚拟 DOM 进行对比,找出需要更新的部分。 对比过程中,Vue 会逐个遍历新旧虚拟 DOM 的节点,对比节点的标签名、属性值等信息,找出需要更新的节点。这个过程称为虚拟 DOM 的 Diff 算法。 最后,Vue 会根据 Diff 算法的结果,将需要更新的节点应用到真实 DOM 上。具体的 DOM 操作由 Vue 的虚拟 DOM 渲染器进行处理,将对应的更新操作应用到真实 DOM 上,确保视图与数据的同步。 总结来说,Vue 的虚拟 DOM 通过 JavaScript 对象模拟真实 DOM 的结构和状态,在数据变化时,通过对比新旧虚拟 DOM,找出需要更新的部分,最后将这些更新应用到真实 DOM 上,实现了高效的视图更新。这种机制可以帮助我们优化视图更新的性能,同时提供了便捷的开发体验。

# 浏览器缓存强制缓存 vs 协商缓存

浏览器缓存是一种用于提高网页加载速度和减少网络带宽消耗的机制。在浏览器缓存中,存在两种主要的缓存策略:强制缓存和协商缓存。

  1. 强制缓存(Cache-Control 和 Expires):强制缓存是浏览器根据响应头中的缓存相关字段来判断是否使用缓存。当浏览器第一次请求资源时,服务器会将该资源的相关信息(比如过期时间等)一并返回给浏览器。之后,浏览器再次请求同一个资源时,会先根据缓存规则判断是否需要发送请求到服务器。如果缓存未过期,则直接使用浏览器本地的缓存副本,不再发送请求到服务器。
  • Cache-Control: 响应头中的 Cache-Control 字段用于控制强制缓存的行为。可以设置为 max-age,表示资源的有效期;或设置为 no-cache,表示不使用强制缓存。
  • Expires: 响应头中的 Expires 字段设置资源的过期时间,即到达过期时间后,浏览器再次请求该资源。
  1. 协商缓存(Etag 和 Last-Modified):如果资源的强制缓存已过期(比如 max-age 已经过期),浏览器会向服务器发送一个带有缓存相关信息的请求。服务器会根据请求头中的缓存相关字段和资源的最后修改时间来判断该资源是否需要更新。如果服务器判断资源未改变,则返回一个 304 Not Modified 响应,浏览器直接使用本地缓存。否则,服务器会返回新的资源数据和相关信息,浏览器会更新本地缓存。
  • Etag: 响应头中的 Etag 字段是服务器生成的表示资源的唯一标识符,用于判断资源是否有更新。
  • Last-Modified: 响应头中的 Last-Modified 字段记录资源的最后修改时间。当再次请求资源时,浏览器会将该字段作为 If-Modified-Since 请求头发送到服务器,服务器使用该字段判断资源是否有更新。 强制缓存和协商缓存是浏览器缓存中的两种主要策略,它们可以结合使用来提高性能和减少网络流量消耗。如果资源数据较少变化,可以使用强制缓存来减少对服务器的请求;如果资源频繁变化,可以使用协商缓存来节省带宽和服务器资源。

# http1.0 vs http2.0

1.0每次都需要建立tcp链接。2.0多路复用。

# Vue.js中的性能优化有哪些常见的技巧?

使用v-if和v-for时注意避免不必要的渲染。 合理使用computed属性和watch监听器。 使用keep-alive组件缓存组件状态。 使用异步组件进行按需加载。 避免在模板中使用复杂的表达式。 使用key属性管理组件和元素的复用。 合理使用懒加载和分割代码。

# 优势和劣势

优势:有责任心,排期内完成需求开发,上线。 劣势:遇到后台和前台都可以处理的问题时,在能接受的范围内会退让,不会坚持己见。