真实前端面试题(安**信息技术公司)

249 阅读13分钟

1. css选择器,以及列举出他们的权重

CSS 选择器用于选择 HTML 元素,并为它们应用样式规则。选择器的不同类型具有不同的权重,这些权重决定了哪些样式规则将应用于元素。以下是一些常见的 CSS 选择器及其权重:

  1. 元素选择器(Type Selector):

    • 例如:p, div, a
    • 权重:1
  2. 类选择器(Class Selector):

    • 例如:.my-class
    • 权重:10
  3. ID 选择器(ID Selector):

    • 例如:#my-id
    • 权重:100
  4. 通用选择器(Universal Selector):

    • 例如:*
    • 权重:0
  5. 属性选择器(Attribute Selector):

    • 例如:[type="text"]
    • 权重:10
  6. 伪类选择器(Pseudo-class Selector):

    • 例如::hover, :nth-child(odd)
    • 权重:10
  7. 伪元素选择器(Pseudo-element Selector):

    • 例如:::before, ::after
    • 权重:10
  8. 子选择器(Child Selector):

    • 例如:div > p
    • 权重:0
  9. 相邻兄弟选择器(Adjacent Sibling Selector):

    • 例如:h2 + p
    • 权重:0
  10. 通用兄弟选择器(General Sibling Selector):

    • 例如:h2 ~ p
    • 权重:0
  11. !important 修饰符:

    • 例如:color: red !important;
    • 权重:最高,无法被覆盖

权重是一个简单的数字表示,通常用于确定哪些样式规则将优先应用。权重规则是:ID 选择器权重最高,类选择器、属性选择器、伪类选择器、伪元素选择器权重相同,通用选择器、子选择器、相邻兄弟选择器、通用兄弟选择器权重相同。在计算权重时,通常以 10 为基数,相同类型的选择器叠加权重。

请注意,合理使用 CSS 选择器和权重是保持样式表清晰和易于维护的关键。避免过多的 !important 规则,因为它们可能会导致样式难以预测和维护。

2. vuex怎么使用,使用场景

Vuex 是 Vue.js 的官方状态管理库,用于管理 Vue 应用中的共享状态(如数据、状态和配置)。Vuex 的核心概念包括 store、state、mutations、actionsgetters。以下是如何使用 Vuex 以及一些适用的使用场景:

1. 安装 Vuex:

首先,确保你的 Vue.js 项目已经安装了 Vuex。你可以使用 npm 或 yarn 进行安装:

npm install vuex
# 或者
yarn add vuex

2. 创建 Vuex Store:

在你的 Vue.js 项目中,创建一个 Vuex store 来管理应用的状态。通常,你需要定义应用的初始状态和一些 mutations、actions 和 getters。

javascriptCopy code
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0,
    // 其他应用状态
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    // 其他 mutations
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
    // 其他 actions
  },
  getters: {
    doubleCount(state) {
      return state.count * 2;
    },
    // 其他 getters
  },
});

3. 在 Vue 组件中使用 Vuex:

在你的 Vue 组件中,你可以通过使用 mapStatemapMutationsmapActionsmapGetters 辅助函数来访问 Vuex store 中的状态、mutations、actions 和 getters。

javascriptCopy code
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex';

export default {
  computed: {
    ...mapState(['count']), // 访问 state 中的 count
    ...mapGetters(['doubleCount']), // 访问 getters 中的 doubleCount
  },
  methods: {
    ...mapMutations(['increment']), // 调用 mutation 中的 increment
    ...mapActions(['incrementAsync']), // 调用 action 中的 incrementAsync
  },
};
</script>

4. 使用场景:

Vuex 适用于以下场景:

  • 共享状态: 当多个组件需要访问和修改相同的状态数据时,可以使用 Vuex 来实现共享状态管理。
  • 复杂的数据流: 当应用的数据流比较复杂,包含异步操作、多个数据源、状态衍生等情况时,Vuex 可以帮助你更清晰地管理数据流。
  • 中大型单页应用(SPA): 对于大型的单页应用,Vuex 可以提供一个集中式的状态管理机制,使应用的状态可维护和可调试。
  • 插件拓展: Vuex 支持插件,可以方便地扩展其功能,实现日志记录、持久化等需求。

需要注意,对于小型项目或简单的应用,使用 Vuex 可能会增加不必要的复杂性,因此要根据项目的实际需求来决定是否使用 Vuex。在大多数情况下,如果你的应用不复杂,Vue.js 的组件通信机制已经足够满足需求。

3.vue父到子,子到父,兄弟组件怎么通信

在 Vue 中,有多种方式可以实现组件之间的通信,包括父子组件通信、子父组件通信以及兄弟组件通信。以下是一些常用的通信方式:

1. 父组件到子组件通信:

  • 使用 Props:通过在父组件中使用 props 将数据传递给子组件。子组件通过 props 属性接收父组件传递的数据。
<!-- 父组件 -->
<template>
  <child-component :message="parentMessage"></child-component>
</template>

<script>
export default {
  data() {
    return {
      parentMessage: 'Hello from parent!',
    };
  },
};
</script>

<!-- 子组件 -->
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  props: ['message'],
};
</script>

2. 子组件到父组件通信:

  • 使用自定义事件:子组件可以通过 $emit 方法触发自定义事件,然后父组件可以在模板中监听这些事件。
<!-- 子组件 -->
<template>
  <button @click="sendMessage">Send Message to Parent</button>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      this.$emit('message-from-child', 'Hello from child!');
    },
  },
};
</script>

<!-- 父组件 -->
<template>
  <div>
    <child-component @message-from-child="handleMessage"></child-component>
    <p>{{ receivedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      receivedMessage: '',
    };
  },
  methods: {
    handleMessage(message) {
      this.receivedMessage = message;
    },
  },
};
</script>

3. 兄弟组件通信:

  • 使用一个共享的父组件:如果两个兄弟组件有共同的父组件,你可以通过父组件作为中介来传递数据或事件。
<!-- 父组件 -->
<template>
  <div>
    <component-a @send-data="updateData"></component-a>
    <component-b :data="sharedData"></component-b>
  </div>
</template>

<script>
export default {
  data() {
    return {
      sharedData: '',
    };
  },
  methods: {
    updateData(data) {
      this.sharedData = data;
    },
  },
};
</script>

<!-- 兄弟组件 A -->
<template>
  <button @click="sendData">Send Data to B</button>
</template>

<script>
export default {
  methods: {
    sendData() {
      this.$emit('send-data', 'Data from A to B');
    },
  },
};
</script>

<!-- 兄弟组件 B -->
<template>
  <div>
    <p>{{ data }}</p>
  </div>
</template>

<script>
export default {
  props: ['data'],
};
</script>

这些示例展示了在 Vue 中实现不同组件之间通信的常见方法。你可以根据具体情况选择最适合你的方式。如果组件之间通信更加复杂,你还可以考虑使用 Vuex 或一个全局事件总线来管理状态和事件。

4.深拷贝浅拷贝区别,写一个实现深拷贝的方法

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种复制对象的不同方式:

  1. 浅拷贝(Shallow Copy): 浅拷贝仅复制对象的一层,而不复制嵌套对象的内部结构。这意味着浅拷贝后的新对象和原对象共享嵌套对象的引用。
  2. 深拷贝(Deep Copy): 深拷贝会复制整个对象,包括嵌套对象,而不是共享引用。深拷贝后的新对象和原对象是完全独立的,修改其中一个不会影响另一个。

以下是一个实现深拷贝的 JavaScript 方法的示例:

function deepCopy(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj; // 如果是基本数据类型或 null,则直接返回
  }

  if (Array.isArray(obj)) {
    // 如果是数组,创建一个新数组并递归复制每个元素
    const newArray = [];
    for (let i = 0; i < obj.length; i++) {
      newArray[i] = deepCopy(obj[i]);
    }
    return newArray;
  }

  // 如果是对象,创建一个新对象并递归复制每个属性
  const newObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepCopy(obj[key]);
    }
  }
  return newObj;
}

// 示例用法
const originalObject = {
  name: 'Alice',
  age: 30,
  address: {
    street: '123 Main St',
    city: 'Cityville',
  },
};

const copiedObject = deepCopy(originalObject);

console.log(originalObject === copiedObject); // false,不同的引用
console.log(originalObject.address === copiedObject.address); // false,深拷贝

此示例中的 deepCopy 函数会递归遍历原始对象,创建一个新的对象,以确保对象及其所有嵌套对象都被复制。这样,copiedObjectoriginalObject 的深拷贝,两者是相互独立的。

需要注意的是,这只是一个简单的深拷贝示例,它可以处理基本数据类型、对象和数组。在实际项目中,可以考虑使用现成的深拷贝库,如 Lodash 的 _.cloneDeep(),以确保更好的性能和稳定性。

5.防抖和节流,手写节流

防抖(Debouncing)和节流(Throttling)都是用于控制函数调用频率的技术,以提高性能和用户体验。

  • 防抖:在一连串触发事件中,只有等到事件停止触发一段时间后,函数才会被执行。如果在等待时间内事件再次触发,等待时间会重新计时。
  • 节流:在一连串触发事件中,确保函数以一定的频率执行。无论事件触发得多频繁,都会按照固定的时间间隔来执行。

以下是一个手写节流的 JavaScript 函数示例:

function throttle(func, delay) {
  let timerId;
  let lastExecutionTime = 0;

  return function (...args) {
    const currentTime = Date.now();
    if (currentTime - lastExecutionTime >= delay) {
      func.apply(this, args);
      lastExecutionTime = currentTime;
    } else {
      clearTimeout(timerId);
      timerId = setTimeout(() => {
        func.apply(this, args);
        lastExecutionTime = currentTime;
      }, delay);
    }
  };
}

// 示例用法
function handleScroll() {
  console.log('Scrolled!');
}

// 创建一个节流函数,限制 handleScroll 最多每 200 毫秒执行一次
const throttledScroll = throttle(handleScroll, 200);

// 添加滚动事件监听器
window.addEventListener('scroll', throttledScroll);

在上述示例中,throttle 函数接受一个要执行的函数 func 和一个时间间隔 delay,然后返回一个新的函数。这个新函数会确保 func 最多每 delay 毫秒执行一次,以防止频繁触发事件。

请注意,节流函数在事件触发后会等待一段时间,然后执行一次。如果事件在等待时间内再次触发,等待时间会重新计时。这可以用于防止某些操作过于频繁地发生,如滚动事件、输入框输入等。

6.socket通讯实现步骤

Socket通信是一种实时的双向通信方式,通常用于实现实时聊天、多人协作编辑、实时数据更新等应用。以下是实现基本Socket通信的一般步骤:

  1. 服务器端设置:

    • 选择合适的服务器端技术和库(如Node.js的socket.io、Python的socket库、Java的WebSocket等)。
    • 创建服务器程序,监听指定端口,等待客户端连接。
  2. 客户端设置:

    • 在客户端应用中引入Socket客户端库(如socket.io-client)。
    • 创建Socket连接到服务器。
  3. 建立连接:

    • 服务器和客户端都建立连接,通过指定的IP地址和端口号连接到服务器。
  4. 通信协议:

    • 定义通信协议,包括数据的格式、编码和解码规则等。通常,JSON格式是一种常用的数据传输格式。
  5. 事件监听和处理:

    • 在服务器和客户端上分别设置事件监听器,用于处理消息的接收和发送。
    • 服务器和客户端可以监听连接建立、消息接收、连接断开等事件。
  6. 消息传递:

    • 服务器和客户端可以通过发送消息事件来进行双向通信。
    • 消息可以包含各种信息,如文本、JSON数据、二进制数据等。
  7. 实时更新:

    • 当客户端或服务器接收到消息时,可以根据消息内容执行相应的操作,如更新UI、广播给其他连接的客户端等。
  8. 错误处理:

    • 实现适当的错误处理机制,以处理连接丢失、通信错误等异常情况。
  9. 安全性:

    • 考虑通信的安全性问题,如数据加密、身份验证、跨站脚本(XSS)防护等。
  10. 测试和调试:

    • 通过测试工具和日志来测试和调试Socket通信,确保通信协议和事件处理正确无误。
  11. 部署和扩展:

    • 部署服务器程序到云端或物理服务器上,并根据需求扩展服务器性能和容量。
  12. 维护和监控:

    • 定期维护服务器和客户端,监控连接状态和性能,确保应用的稳定运行。

Socket通信是一项复杂的任务,需要谨慎设计和实施。使用成熟的Socket库和框架可以大大简化开发过程。例如,使用Node.js和socket.io可以快速实现WebSocket通信。

7.less和sass的区别

Less(Leaner Style Sheets)和Sass(Syntactically Awesome Style Sheets)都是CSS预处理器,它们为CSS添加了一些强大的功能,使样式表的编写更加高效和可维护。尽管它们的目标相似,但它们有一些区别:

1. 语法风格:

  • Less: Less使用的是类似于JavaScript的语法,使用@符号来定义变量、嵌套规则、混合(Mixins),以及执行函数等。
  • Sass: Sass最初使用的是缩进式语法,使用缩进来表示嵌套规则,没有括号。后来,Sass引入了SCSS(Sassy CSS)语法,它更类似于传统的CSS,使用大括号和分号。

2. 扩展性:

  • Less: Less在语法上相对较简单,较少的符号和结构,因此对于新手更容易上手。但有一些人认为它在某些方面的扩展性受到限制。
  • Sass: Sass提供了更多的功能和高级特性,如条件语句、循环、函数等,这使得它更强大,适合于大型和复杂的项目。

3. 生态系统:

  • Less: Less的生态系统相对较小,但仍然有一些社区和库支持。
  • Sass: Sass有更大更活跃的社区支持,有许多扩展库、工具和框架,如Compass和Bourbon等。

4. 使用情况:

  • Less: Less在一些项目中使用较多,尤其是在较早的项目或一些小型项目中。
  • Sass: Sass在一些大型项目和前端框架中更常见,如Ruby on Rails使用的是Sass。

5. 编译:

  • Less: Less需要在客户端使用JavaScript进行编译,因此可能会导致一些性能问题。
  • Sass: Sass可以在服务器端进行编译,因此通常更快,也可以通过命令行或构建工具进行编译。

总的来说,选择使用Less还是Sass取决于你的个人喜好、项目需求以及团队的偏好。两者都有其优点和用途,可以根据具体情况选择合适的工具。如果你是初学者,可能会更容易上手Less,但Sass在大型项目中提供了更多的功能和灵活性。

8.事件循环机制

事件循环(Event Loop)是JavaScript运行时环境中的一种机制,用于协调和管理异步代码的执行。它是JavaScript运行时环境的核心部分,用于处理异步任务、回调函数和事件处理。

以下是事件循环机制的基本原理:

  1. 调用栈(Call Stack): JavaScript引擎使用调用栈来跟踪函数调用的顺序和当前执行的代码。当一个函数被调用时,它会被推入调用栈,当函数执行完毕后,它会被弹出调用栈。
  2. 消息队列(Message Queue): 事件循环还包括一个消息队列,用于存储待处理的事件和任务。这些事件可以是用户交互、HTTP请求的回调、定时器等等。每个事件都有一个相关联的回调函数。
  3. 事件循环(Event Loop): 事件循环是一个循环,它不断地从消息队列中取出事件,并将其推入调用栈,以便执行相关的回调函数。事件循环不断地重复这个过程,以确保异步任务得到执行。

事件循环的基本流程如下:

  • 首先,检查调用栈是否为空。如果调用栈为空,则事件循环将等待,直到有事件加入消息队列。
  • 当消息队列中有事件时,事件循环将取出一个事件,并将其相关的回调函数推入调用栈,开始执行。
  • 如果在执行回调函数的过程中有其他的异步任务(如定时器或I/O操作),这些任务将被推入消息队列。
  • 当调用栈再次为空时,事件循环将从消息队列中取出下一个事件,继续执行相关的回调函数。

这个过程不断重复,使得JavaScript可以处理并发的异步操作,而不会阻塞主线程。这是JavaScript能够实现非阻塞I/O和响应式用户界面的关键机制。

总的来说,事件循环机制是JavaScript运行时环境中的一种机制,用于管理异步任务的执行顺序。它通过调用栈和消息队列的协作来确保异步任务按照正确的顺序执行,从而实现了JavaScript的非阻塞特性。

9.用代码实现左中右布局,左右宽度100px,中间宽度自适应

可以使用flex布局实现左中右布局,具体代码如下:

<div class="container">
  <div class="left"></div>
  <div class="center"></div>
  <div class="right"></div>
</div>
.container {
  display: flex;
}

.left, .right {
  width: 100px;
  background-color: #ccc;
}

.center {
  flex: 1;
  background-color: #f2f2f2;
}
    

其中,.container使用display: flex属性将子元素设置为弹性盒子,并使其水平排列。.left.right分别设置宽度为100px,并设置背景颜色。.center使用flex: 1属性将其宽度自适应父容器,并设置背景颜色。

10、以下代码的输出是什么

image.png

在这个代码片段中,首先定义了四个函数:func1、func2、func3 和 func4。然后,创建了一个名为 func 的立即执行函数(Immediately Invoked Function Expression,IIFE)。IIFE 是一个在定义时立即执行的函数,通常用来创建一个封闭的作用域,以防止函数内部的变量泄漏到全局作用域。

让我们逐步分析这个代码片段中的函数调用和输出结果:

  1. func1() 被立即调用,它会在控制台打印 'func 1'。
  2. setTimeout(func2, 0) 创建一个定时器,它将在下一个事件循环中调用 func2()。由于延迟为0毫秒,func2() 会尽快执行,但仍然需要等待当前事件循环完成。
  3. Promise.resolve().then(() => func3()) 创建一个 Promise,当 Promise 解决时,会调用 func3()。由于 Promise.resolve() 是微任务,它会在当前事件循环的末尾立即执行,但 func3() 的执行也需要等待当前事件循环完成。
  4. func4() 被立即调用,它会在控制台打印 'func 4'。

总结一下,输出结果的顺序是:

  1. 'func 1'(来自 func1() 的调用)
  2. 'func 4'(来自 func4() 的调用)
  3. 'func 3'(来自 func3() 的调用)
  4. 'func 2'(来自 func2() 的调用,虽然它被放置在setTimeout中,但由于延迟为0,它也在当前事件循环中执行,但在Promise微任务之后)

因此,输出的结果是:

func 1
func 4
func 3
func 2