诺亚财富面试题及答案整理

164 阅读24分钟

一. 讲讲Js中的作用域链

  • JavaScript的作用域链(Scope Chain)是一个包含所有当前执行上下文(Execution Context)以及它们嵌套关系的列表,用于解析变量和函数的引用。

    当JavaScript引擎创建一个新的执行上下文时,会创建一个新的作用域链。这个作用域链包含当前执行上下文的变量对象(Variable Object)以及其父级执行上下文的作用域链。当引擎查找一个变量或函数时,它会首先在当前执行上下文的变量对象中查找,如果找不到就沿着作用域链向上查找,直到找到该变量或函数为止。

    作用域链的顶端是全局执行上下文的变量对象,因此所有未声明变量或函数的引用都会在全局作用域链中进行查找。如果在整个作用域链中都找不到该变量或函数,则会抛出“未定义”的错误。

    JavaScript中的作用域链是静态的,也就是说,它在执行上下文创建时就已经确定,并且在整个执行上下文的生命周期中保持不变。

  • 作用域链具体例子

function outerFunction() {
  var outerVar = 'outer';
​
  function innerFunction() {
    var innerVar = 'inner';
​
    console.log(innerVar); // 输出 'inner'
    console.log(outerVar); // 输出 'outer'
    console.log(globalVar); // 抛出 "未定义" 错误
  }
​
  innerFunction();
}
​
var globalVar = 'global';
​
outerFunction();

在这个例子中,我们定义了一个全局变量 globalVar,一个外部函数 outerFunction,以及一个内部函数 innerFunction

outerFunction 被调用时,一个新的执行上下文会被创建,并且作用域链会包含当前执行上下文的变量对象和全局执行上下文的变量对象。因此,在 innerFunction 中,变量 innerVarouterVar 可以被引用,并且它们可以在作用域链中被找到。另一方面,变量 globalVar 不在当前执行上下文或其父级执行上下文的变量对象中,所以当在 innerFunction 中引用它时会抛出一个 "未定义" 的错误。

需要注意的是,在 innerFunction 中,变量 outerVar 被引用时,它的值来自其定义所在的执行上下文 outerFunction,而不是 innerFunction。这是因为 JavaScript 中的作用域链是在函数定义时确定的,而不是在函数执行时确定的。因此,在 innerFunction 中查找变量 outerVar 时,它会沿着作用域链向上查找,直到找到 outerFunction 的变量对象并找到 outerVar 的值。

二. javascript怎么修改网址

在JavaScript中,可以通过修改window.location对象的属性来修改当前网址。

例如,可以使用window.location.href属性来设置整个网址,包括协议、主机名、路径和查询参数。例如,下面的代码将当前网址设置为http://example.com/path?query=value

window.location.href = 'http://example.com/path?query=value';

另外,还可以使用window.location.pathname属性来设置当前网址的路径部分。例如,下面的代码将当前网址的路径部分设置为/new-path

window.location.pathname = '/new-path';

同样地,还可以使用window.location.search属性来设置当前网址的查询参数部分。例如,下面的代码将当前网址的查询参数部分设置为?new-query=new-value

window.location.search = '?new-query=new-value';

需要注意的是,当修改window.location对象的属性时,会导致浏览器重新加载页面。如果不想重新加载页面,可以考虑使用HTML5的pushStatereplaceState方法来修改浏览器的历史记录,而不影响页面的加载。这些方法允许将新的网址添加到浏览器历史记录中,同时不会导致浏览器重新加载页面。但是,使用这些方法需要谨慎,因为不当使用可能会影响用户体验和SEO等方面。

三.Vue中 v-model分解成什么

在Vue中,v-model指令用于在表单元素(如<input><textarea><select>等)和组件之间创建双向数据绑定。当使用v-model指令时,Vue会自动为表单元素添加value属性,并根据用户输入来更新数据模型的值;同时,当数据模型的值发生变化时,Vue会自动将新的值设置回表单元素。这样,就实现了表单元素和数据模型之间的双向数据绑定。

具体来说,当我们使用v-model指令时,Vue会将其分解成一个属性绑定和一个事件绑定。例如,下面的代码使用v-model指令将input元素的值绑定到message数据模型:

<input v-model="message">

在这个例子中,Vue会将v-model指令分解成以下两个部分:

  1. 属性绑定:将input元素的value属性绑定到message数据模型:
<input :value="message">
  1. 事件绑定:在input元素的input事件触发时,将输入的值更新到message数据模型:
<input :value="message" @input="message = $event.target.value">

这样,当我们在input元素中输入内容时,Vue会触发input事件,并将输入的值赋值给message数据模型。同时,当message数据模型的值发生变化时,Vue会将新的值设置回input元素的value属性,从而实现了双向数据绑定。

需要注意的是,v-model指令只能用于表单元素和组件之间的双向数据绑定,而不能用于普通的HTML元素或组件。如果想要在普通的HTML元素或组件中实现双向数据绑定,可以考虑使用自定义指令或组件等技术实现。

四. javascript如何实现深拷贝

在JavaScript中,实现深拷贝可以使用递归和循环两种方式。

一、递归方式

递归方式是比较常见的实现深拷贝的方法,可以使用typeof操作符来判断对象的类型,然后根据类型进行处理。

下面是一个使用递归方式实现深拷贝的示例代码:

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }
​
  let result;
​
  if (obj instanceof Array) {
    result = [];
​
    for (let i = 0, len = obj.length; i < len; i++) {
      result[i] = deepClone(obj[i]);
    }
  } else {
    result = {};
​
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        result[key] = deepClone(obj[key]);
      }
    }
  }
​
  return result;
}  

在这个示例代码中,首先判断obj的类型,如果是基本类型或null,则直接返回;否则,根据obj的类型创建一个新的对象或数组result,然后使用for...in循环遍历obj的所有属性,递归调用deepClone函数对每个属性进行深拷贝,并将拷贝后的属性赋值给result对象或数组,最后返回result对象或数组。

五.讲讲使用vue 封装组件例子

Vue 是一个组件化框架,所以组件开发是 Vue 开发中非常重要的一部分。下面是一个使用 Vue 封装组件的示例代码,以一个按钮组件为例:

<template>
  <button :class="classes" @click="handleClick">
    <slot></slot>
  </button>
</template><script>
export default {
  name: 'MyButton',
  props: {
    disabled: {
      type: Boolean,
      default: false,
    },
    primary: {
      type: Boolean,
      default: false,
    },
    danger: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    classes() {
      return {
        'my-button': true,
        'my-button--primary': this.primary,
        'my-button--danger': this.danger,
        'my-button--disabled': this.disabled,
      };
    },
  },
  methods: {
    handleClick() {
      if (!this.disabled) {
        this.$emit('click');
      }
    },
  },
};
</script><style scoped>
.my-button {
  display: inline-block;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease-in-out;
}
​
.my-button--primary {
  background-color: #007bff;
  color: #fff;
  border: none;
}
​
.my-button--danger {
  background-color: #dc3545;
  color: #fff;
  border: none;
}
​
.my-button--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

在这个示例代码中,我们创建了一个名为 MyButton 的组件,该组件有三个 props:disabledprimarydanger,分别表示按钮是否禁用、是否是主要按钮和是否是危险按钮。我们使用计算属性 classes 来根据这些 props 计算出按钮的样式类。在模板中,我们使用 :class 绑定样式类,并使用 @click 监听按钮点击事件,当按钮未被禁用时,触发 click 事件并通过 $emit 发送出去。最后,我们使用 <slot> 插槽来支持自定义按钮的文本内容。

使用这个按钮组件非常简单,只需要在父组件中引入 MyButton 组件,然后使用 <MyButton> 标签即可:

<template>
  <div>
    <MyButton>普通按钮</MyButton>
    <MyButton primary>主要按钮</MyButton>
    <MyButton danger>危险按钮</MyButton>
    <MyButton disabled>禁用按钮</MyButton>
    <MyButton @click="handleClick">点击按钮</MyButton>
  </div>
</template><script>
import MyButton from './MyButton.vue';
​
export default {
  components: {
    MyButton,
  },
  methods: {
    handleClick() {
      console.log('按钮被点击了!');
    },
  },
};
</script>

下面再举一个 Vue 封装组件的例子,以一个简单的列表组件为例:

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">{{ item }}</li>
  </ul>
</template><script>
export default {
  name: 'MyList',
  props: {
    items: {
      type: Array,
      default: () => [],
    },
  },
};
</script>

在这个示例代码中,我们创建了一个名为 MyList 的组件,该组件有一个 prop:items,表示要展示的列表项数组。在模板中,我们使用 v-for 循环遍历 items 数组,并使用 :key 绑定每个列表项的唯一标识。在实际使用时,我们可以在父组件中传递一个数组作为 items prop 的值,例如:

<template>
  <div>
    <MyList :items="['Apple', 'Banana', 'Orange']" />
  </div>
</template><script>
import MyList from './MyList.vue';
​
export default {
  components: {
    MyList,
  },
};
</script>

在这个示例代码中,我们在父组件中引入了 MyList 组件,并传递一个字符串数组作为 items prop 的值,这样就会在页面上展示一个包含三个列表项的列表。在实际使用时,我们还可以根据需要自定义每个列表项的样式、添加分页功能等等。

六. 前端 性能优化和防止白屏 怎么做

前端性能优化和防止白屏是一个非常重要的话题,以下是一些常见的技巧和建议:

  1. 减少 HTTP 请求次数:通过合并和压缩 CSS、JavaScript 文件,使用 CSS 雪碧图和图片懒加载等技术来减少 HTTP 请求次数,从而提升网站的加载速度。
  2. 使用浏览器缓存:通过设置缓存策略,尽可能地利用浏览器缓存机制来减少重复加载资源,从而提升网站的加载速度。
  3. 减少 DOM 操作:减少不必要的 DOM 操作,尽可能减少 DOM 元素的数量,从而提升页面的渲染速度。
  4. 使用代码分割和懒加载:通过使用代码分割和懒加载等技术,将代码按需加载,尽可能减少页面初始化时需要加载的资源,从而提升页面的加载速度。
  5. 压缩和优化图片:通过压缩和优化图片,减少图片文件的大小,从而提升网站的加载速度。
  6. 使用 CDN:使用 CDN(内容分发网络)来缓存和分发静态资源,从而提升网站的加载速度。
  7. 预加载和预解析:通过使用 <link> 标签中的 preloadprefetch 属性,预加载和预解析页面所需的资源,从而提升页面的加载速度。
  8. 防止白屏:使用 skeleton、loading、splash 等技术,在网站资源加载完成前提供一个优雅的用户体验,防止页面白屏。

总的来说,前端性能优化和防止白屏需要结合具体的业务场景和项目需求,综合考虑使用不同的技术手段来达到最佳的效果。

七. 图片怎么在页面中垂直居中

要让图片在页面中垂直居中,有多种方法可以实现。以下是其中一种比较简单的方法:

HTML:

<div class="container">
  <img src="path/to/image" alt="image">
</div>

CSS:

.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh; /* 设置容器高度,这里使用视口高度作为示例 */
}
​
img {
  max-width: 100%; /* 让图片在容器内自适应宽度 */
  max-height: 100%; /* 让图片在容器内自适应高度 */
}

在上面的示例代码中,我们首先创建了一个容器 div,包含了要居中的图片。然后,我们使用 CSS Flexbox 布局来让容器的内容垂直居中,并设置容器的高度为视口高度 100vh。接着,我们使用 max-widthmax-height 让图片在容器内自适应宽度和高度。这样,无论图片的尺寸和容器的大小如何变化,图片都会始终垂直居中。

除了使用 Flexbox 布局外,还有其他方法可以实现图片在页面中垂直居中,例如使用绝对定位和 transform 属性等。但需要注意的是,不同的方法适用于不同的情况和需求,需要根据具体情况选择合适的方法。

.container {
  position: relative;
  height: 100vh; /* 设置容器高度,这里使用视口高度作为示例 */
}
​
img {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 100%; /* 让图片在容器内自适应宽度 */
  max-height: 100%; /* 让图片在容器内自适应高度 */
}

在上面的示例代码中,我们首先创建了一个容器 div,包含了要居中的图片。然后,我们设置容器的 position 属性为 relative,并设置容器的高度为视口高度 100vh。接着,我们使用 CSS 绝对定位和 transform 属性来将图片垂直居中,具体来说,我们将图片的 topleft 属性都设置为 50%,然后使用 transform: translate(-50%, -50%) 将图片在垂直和水平方向上都向左上方偏移了自身宽高的一半。最后,我们使用 max-widthmax-height 让图片在容器内自适应宽度和高度。这样,无论图片的尺寸和容器的大小如何变化,图片都会始终垂直居中。

除了使用 Flexbox 布局和绝对定位与 transform 属性外,还有一种方法可以实现图片在页面中垂直居中,那就是使用 CSS Grid 布局。以下是示例代码:

.container {
  display: grid;
  place-items: center;
  height: 100vh; /* 设置容器高度,这里使用视口高度作为示例 */
}
​
img {
  max-width: 100%; /* 让图片在容器内自适应宽度 */
  max-height: 100%; /* 让图片在容器内自适应高度 */
}

在上面的示例代码中,我们首先创建了一个容器 div,包含了要居中的图片。然后,我们使用 CSS Grid 布局来让容器的内容垂直居中,并设置容器的高度为视口高度 100vh。具体来说,我们将容器的 display 属性设置为 grid,然后使用 place-items: center 将容器的内容垂直和水平居中。接着,我们使用 max-widthmax-height 让图片在容器内自适应宽度和高度。这样,无论图片的尺寸和容器的大小如何变化,图片都会始终垂直居中。

需要注意的是,CSS Grid 布局需要考虑更多的兼容性问题,因此需要根据具体情况选择合适的方法。

八. 页面的form表单数据怎么储存

在前端,可以使用多种方法来存储页面的表单数据,以下是一些常用的方法:

  1. 使用浏览器本地存储:可以使用 localStorage 或 sessionStorage 将表单数据存储在浏览器端,以便在用户离开页面或关闭浏览器后仍能够保留数据。使用方法很简单,可以使用 JavaScript 的 localStorage.setItem(key, value) 方法将表单数据存储到本地存储中,然后使用 localStorage.getItem(key) 方法获取数据。需要注意的是,存储在本地存储中的数据是以字符串形式储存的,因此需要进行数据类型转换。
  2. 使用 Cookies:Cookies 是另一种在浏览器端存储数据的方式,与本地存储相比,Cookies 具有一些特殊的属性,例如可以设置过期时间、域名和路径等。可以使用 JavaScript 的 document.cookie 属性来设置和获取 Cookies。
  3. 在后端存储数据:如果表单数据需要持久化存储,可以将数据发送到后端服务器,使用后端技术来进行储存和处理。常用的后端技术包括 PHP、Node.js、Java 等。

需要注意的是,由于表单数据涉及到用户的隐私和安全问题,因此需要对表单数据进行加密和校验,以确保数据的安全性和完整性。同时,还需要考虑表单数据的存储方式和数据的传输方式,以避免数据泄露和攻击。

九. 简单讲一个封装的自定义指令

在 Vue 中,可以使用自定义指令来扩展 Vue 的功能,以下是一个简单的封装自定义指令的示例:

// 定义一个名为 v-focus 的自定义指令
Vue.directive('focus', {
  // 指令的定义
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
​

在上面的代码中,我们使用 Vue.directive 方法来定义一个名为 v-focus 的自定义指令。该指令的功能是在元素插入到 DOM 中时自动将焦点聚焦到该元素上。

自定义指令包含多个钩子函数,例如 insertedbindupdatecomponentUpdatedunbind 等。这些钩子函数分别在不同的生命周期中调用,可以在不同的阶段对元素进行不同的操作。在上面的示例中,我们使用了 inserted 钩子函数,在元素插入到 DOM 中时调用该函数,将焦点聚焦到元素上。

使用自定义指令时,可以通过 v-指令名 的形式将指令绑定到元素上。例如,可以在模板中的一个输入框上使用 v-focus 指令,以实现自动聚焦的效果:

<template>
  <div>
    <input v-focus type="text" />
  </div>
</template>

需要注意的是,自定义指令的作用域仅限于当前组件的范围,如果想要在多个组件中共享自定义指令,可以将指令定义为全局指令,例如:

// 定义全局自定义指令 v-focus
Vue.directive('focus', {
  inserted: function (el) {
    el.focus()
  }
})

这样,在任何组件中都可以使用 v-focus 指令来实现自动聚焦的效果。

以下是一个自定义指令的示例,用于将元素设置为可拖动:

// 自定义指令 v-draggable
Vue.directive('draggable', {
  bind: function (el, binding) {
    // 获取可拖动元素
    let draggable = el.querySelector('.draggable-handle') || el
​
    // 初始化拖动状态
    let isDragging = false
    let startX = 0
    let startY = 0
    let currentX = 0
    let currentY = 0
​
    // 定义拖动处理函数
    const handleMouseDown = function (event) {
      event.preventDefault()
​
      // 标记开始拖动
      isDragging = true
​
      // 记录开始位置
      startX = event.clientX
      startY = event.clientY
​
      // 绑定事件处理函数
      window.addEventListener('mousemove', handleMouseMove)
      window.addEventListener('mouseup', handleMouseUp)
    }
​
    const handleMouseMove = function (event) {
      event.preventDefault()
​
      // 计算拖动距离
      const deltaX = event.clientX - startX
      const deltaY = event.clientY - startY
​
      // 更新当前位置
      currentX = el.offsetLeft + deltaX
      currentY = el.offsetTop + deltaY
​
      // 移动元素
      el.style.left = currentX + 'px'
      el.style.top = currentY + 'px'
    }
​
    const handleMouseUp = function () {
      // 标记结束拖动
      isDragging = false
​
      // 解绑事件处理函数
      window.removeEventListener('mousemove', handleMouseMove)
      window.removeEventListener('mouseup', handleMouseUp)
    }
​
    // 绑定拖动处理函数
    draggable.addEventListener('mousedown', handleMouseDown)
  }
})
​

在上面的代码中,我们定义了一个名为 v-draggable 的自定义指令,该指令将元素设置为可拖动。当指令绑定到元素上时,我们首先获取可拖动的元素,然后为该元素绑定鼠标按下事件处理函数。在鼠标按下事件处理函数中,我们记录开始拖动的位置,并绑定鼠标移动和鼠标抬起事件处理函数。在鼠标移动事件处理函数中,我们计算拖动的距离,然后更新当前位置,最后移动元素。在鼠标抬起事件处理函数中,我们解绑鼠标移动和鼠标抬起事件处理函数,结束拖动。

在模板中使用该自定义指令时,只需将 v-draggable 指令绑定到需要拖动的元素上即可:

在上面的示例中,我们将 v-draggable 指令绑定到一个名为 draggablediv 元素上,使该元素可以被拖动。

十. 讲讲Vue组件的生命周期

Vue 组件的生命周期可以分为四个阶段:创建阶段、挂载阶段、更新阶段和销毁阶段。在组件的生命周期中,Vue 会在特定的时间点触发一些钩子函数,这些钩子函数可以让我们在组件不同的阶段进行一些操作和逻辑处理。

下面是 Vue 组件的生命周期钩子函数列表:

创建阶段

  1. beforeCreate:在实例创建之前调用。此时 data、methods、computed 等属性都不能被访问。
  2. created:在实例创建之后调用。此时 data 已经可以被访问,但是 template 还没有被编译成 render 函数。

挂载阶段

  1. beforeMount:在挂载开始之前调用。此时 template 已经被编译成 render 函数,但是 render 函数还没有被调用。
  2. mounted:在挂载完成之后调用。此时组件已经被渲染到页面中,可以访问到组件的 DOM 元素。

更新阶段

  1. beforeUpdate:在更新之前调用。此时组件的数据已经更新,但是页面还没有被重新渲染。
  2. updated:在更新之后调用。此时组件的数据已经更新,并且页面已经被重新渲染。

销毁阶段

  1. beforeDestroy:在实例销毁之前调用。此时组件实例仍然可以访问到,可以进行一些清理工作。
  2. destroyed:在实例销毁之后调用。此时组件实例已经被销毁,无法再进行访问。

在开发过程中,我们可以利用这些钩子函数来处理一些特定的场景和需求,例如在创建阶段进行一些全局变量的初始化,在 mounted 钩子函数中访问组件的 DOM 元素并进行一些 DOM 操作,在 beforeDestroy 钩子函数中进行一些清理工作等。同时,在使用 Vue 组件时,也需要注意不要在某些钩子函数中进行耗时的操作,否则可能会导致页面卡顿或性能问题。

十一. 讲讲vue请求放在哪个生命周期

在 Vue 组件中,通常会在 created 钩子函数中发起请求。created 钩子函数是在组件实例创建完成之后被调用,此时组件实例已经初始化完成,并且 data 数据已经被观测,因此可以进行一些初始化操作,例如请求数据。

在 created 钩子函数中发起请求的好处是,此时组件已经可以访问到 data 数据和 methods 方法,可以根据需要进行数据处理和逻辑处理,然后再根据处理结果发起请求。同时,created 钩子函数是在 beforeMount 钩子函数之前被调用的,因此可以避免在页面渲染之前就发起请求,降低页面性能。

在 Vue 中,created 和 mounted 都是组件生命周期钩子函数,分别在组件实例被创建和挂载到 DOM 上时被调用。它们都可以用于发起数据请求,但是它们的调用时机不同,因此适用于不同的场景。

  • created 钩子函数是在组件实例被创建后立即调用的。在该钩子函数中,可以访问到组件的 props、data、computed、methods 等属性,但是无法访问到组件的 DOM 元素,因为组件尚未被渲染到 DOM 中。因此,如果需要在组件创建时就发起数据请求,可以将请求放在 created 钩子函数中。
  • mounted 钩子函数是在组件被挂载到 DOM 上后调用的。在该钩子函数中,可以访问到组件的 DOM 元素,因此可以进行一些操作,比如获取 DOM 元素的宽度、高度等信息。同时,也可以在该钩子函数中发起数据请求,并将数据更新到组件的 data 中,以更新视图。因此,如果需要在组件挂载后才发起数据请求,可以将请求放在 mounted 钩子函数中。

需要注意的是,由于 created 和 mounted 的调用时机不同,因此在发起数据请求时,需要根据具体的业务场景选择合适的钩子函数。如果将请求放在 created 钩子函数中,可能会在组件渲染完成前就完成了数据请求,导致数据更新失败。而如果将请求放在 mounted 钩子函数中,可能会出现数据闪烁的问题,因为在数据请求完成前,组件已经被渲染出来了。此外,如果数据请求需要一定的时间,还需要考虑使用 loading 状态来避免用户等待过久的情况。

十二 vue2和vue3区别

Vue2 和 Vue3 是 Vue.js 的两个主要版本,它们在许多方面都有所不同。以下是 Vue2 和 Vue3 之间的一些区别:

  1. 性能优化:Vue3 做了很多的性能优化,如优化了虚拟 DOM 的生成和更新过程,提升了响应式系统的性能等。通过这些优化,Vue3 在性能方面比 Vue2 有了明显的提升。
  2. Composition API:Vue3 引入了 Composition API,它可以更好地组织和重用代码。Composition API 与 Vue2 的 Options API 相比,能够更好地解决代码复用问题,同时也可以使代码更加可读和易于维护。
  3. Tree-shaking:Vue3 通过使用 ES 模块语法,使得在应用程序中只导入需要使用的模块,从而减少了应用程序的大小。这对于性能和应用程序加载时间都是非常有益的。
  4. 新的响应式系统:Vue3 的响应式系统比 Vue2 更加灵活,具有更高的运行时性能和更好的类型推导。Vue3 的响应式系统还可以更好地与 TypeScript 集成。
  5. Teleport 组件:Vue3 引入了 Teleport 组件,它可以让开发人员更加容易地在应用程序中处理弹出层、对话框等组件。

总的来说,Vue3 相比 Vue2,具有更好的性能、更好的开发体验、更好的组件复用能力等优势,可以更好地支持大规模应用程序的开发。但由于 Vue3 与 Vue2 在一些方面有着较大的差异,因此需要开发人员重新学习和适应。

十三. JSON.parse(JSON.stringfy())的缺点

在 JavaScript 中,使用 JSON.parse(JSON.stringify(obj)) 进行对象的深拷贝是一种常见的方法。该方法的原理是将对象序列化为 JSON 字符串,再将字符串解析为新的对象,从而实现对象的深拷贝。虽然这种方法简单易用,但是在某些情况下会有一些缺点,具体如下:

  1. 无法拷贝函数、正则表达式等特殊对象:由于 JSON.stringify 只能序列化对象的可枚举属性,并且会忽略函数和 Symbol 类型的属性,因此在执行 JSON.parse(JSON.stringify(obj)) 时,无法深拷贝对象的函数、正则表达式等特殊对象。
  2. 无法处理循环引用的情况:如果对象中存在循环引用,即对象属性之间相互引用,那么在执行 JSON.stringify 时会抛出异常,因此无法深拷贝包含循环引用的对象。
  3. 无法处理 Date 类型对象:在执行 JSON.stringify 时,Date 类型的属性会被序列化为 ISO 格式的字符串,但在解析时会变成字符串类型,而不是 Date 类型对象。
  4. 无法处理 NaN、Infinity 和 -Infinity:在执行 JSON.stringify 时,如果对象属性的值是 NaN、Infinity 或 -Infinity,那么在解析时会变成 null。

综上所述,虽然 JSON.parse(JSON.stringify(obj)) 方法使用起来简单,但是在某些情况下可能会出现上述问题,因此需要根据具体情况选择合适的方法进行对象的深拷贝。

十四. 讲讲如何实现虚拟滚动

虚拟滚动是一种优化长列表的方式,它通过只渲染可见部分的列表项来降低渲染开销,从而提高性能和用户体验。实现虚拟滚动的基本思路是根据滚动位置计算出需要渲染的列表项的起始索引和结束索引,然后只渲染这些列表项。

以下是一些实现虚拟滚动的常见方式:

  1. 使用第三方组件库:一些 UI 组件库如 Element UI、Ant Design 等已经提供了虚拟滚动的功能,使用这些组件库可以方便地实现虚拟滚动。
  2. 使用 IntersectionObserver:IntersectionObserver 是浏览器提供的一种异步观察元素是否进入视口的 API,可以用来实现虚拟滚动。具体实现方式是,将列表项包裹在一个容器中,并给容器设置高度,然后使用 IntersectionObserver 监听容器的进入和离开视口事件,在进入视口时渲染列表项。
  3. 手动计算可见区域:这种方式需要手动计算可见区域的起始和结束索引,然后只渲染这些列表项。具体实现方式是,在列表项容器中添加一个高度为列表项高度乘以可见区域数量的虚拟容器,然后监听滚动事件,根据滚动位置计算出需要渲染的列表项的起始索引和结束索引,只渲染这些列表项,并根据当前滚动位置计算出虚拟容器的偏移量来模拟滚动效果。

需要注意的是,实现虚拟滚动需要考虑的问题比较多,包括列表项高度的计算、滚动位置的同步、虚拟容器的高度等等,因此在实际开发中需要仔细考虑,选择合适的实现方式来提高性能和用户体验。

十五. javascript 怎么声明一个不能被修改的对象

在 JavaScript 中,可以使用 Object.freeze() 方法来声明一个不可变的对象。当一个对象被冻结后,它的属性将不能被修改、添加或删除。

例如,我们可以使用如下方式声明一个不可变的对象:

const obj = Object.freeze({foo: 'bar'});

在这个例子中,我们使用 Object.freeze() 方法来冻结了一个对象,使其变成不可变的,然后将其赋值给 obj 变量。由于 obj 是一个常量,因此它不能被重新赋值,也就不能被修改。

需要注意的是,Object.freeze() 方法只会冻结对象本身的属性,如果对象的某个属性是一个引用类型(如数组或对象),那么这个属性的值仍然是可变的。如果需要冻结对象中的所有属性,包括引用类型的属性,需要递归地对属性值调用 Object.freeze() 方法。

十六. type 和 interface 的区别

在 TypeScript 中,typeinterface 都可以用来定义类型,它们有以下几个区别:

  1. interface 可以被扩展、继承和实现,而 type 不行。也就是说,interface 可以定义一个接口并在其他接口、类或对象字面量中继承该接口。而 type 定义的类型别名只能被引用,但不能被继承。
  2. interface 可以定义合并,多个同名接口会被合并成一个,而 type 不会。当定义了多个同名的 interface 时,TypeScript 会把它们合并为一个。合并后的接口包含了所有同名接口中的成员。而 type 定义的类型别名不支持合并。
  3. type 可以用于定义任何类型,而 interface 只能定义对象、函数、类等结构类型。例如,type 可以定义原始类型、联合类型、交叉类型等非结构化类型,而 interface 只能定义结构化类型。
  4. type 可以使用 typeof 操作符获取类型,而 interface 不行。例如,type 可以使用 typeof 操作符获取某个变量的类型,然后再使用这个类型定义新的类型别名。而 interface 不支持这种用法。

总的来说,interface 用于描述结构化类型,可以被扩展和实现,支持合并;而 type 则适用于更广泛的类型定义,不支持继承和合并,但支持更多的类型定义方式。具体使用哪个,取决于具体的应用场景和个人习惯。