2023.08.08经历某国企前端面试题一部分

179 阅读15分钟

什么时候开始记录都不算晚,即使已经是晚了,但是再早的东西也会有淘汰的时候,收藏另说。

问题主要选取一些高频和基础的问题。(问题的回答只是本人的理解,非参考答案,有些答案只给个提示,详解可谷歌百度或在掘金内搜索相关文章、同时本人每次回顾更新文章都会重新搜一下,与时俱进同步新答案)

61336c61e86040cd98e9c459240637aa_tplv-k3u1fbpfcp-zoom-crop-mark_3024_3024_3024_1702.jpg

1、ES6新增了哪些拓展?
答:
    **箭头函数(Arrow Functions):** 箭头函数提供了一种更简洁的函数定义方式,同时还改变了函数内部`this`的行为。
    **letconst 关键字:** `let``const`用于报表变量。`let`报表变量具有块级作用域,而`const`报表变量是不可重新赋值关键字的常量。
    **模板字符串(Template Strings):** 使用反引号(`)来创建多行字符串和插入变量,使字符串修剪更加简单。
    **解构赋值(DestructuringAssignment):** 允许从数组或对象中提取值,并给出赋值的变量,简化了代码。
    **默认参数(DefaultParameters):** 在函数定义中可以为参数提供默认值,当调用函数时未传递对应参数时将使用默认值。引入了关键字`import`和`export`关键字,使得标准化开发更加方便和可维护。
    **展开操作(Spread Operator):** 用于在磁盘或对象面量中展开磁盘元素或对象属性,方便地进行合并和复制。
    **Rest参数(RestParameter):** 允许将函数的多个参数收集成一个数组,使得函数能够处理任意数量的参数。
    **类和继承(Classes and Inheritance):** 引入了类和继承的概念,更接近传统面向对象编程风格。
    **(Modules):** 引入了模块化编程,使得JavaScript代码可以更好地组织和重用。
    **Promise 和异步编程(Promises and Asynchronous Programming):** 引入了 Promise 对象,用于更优雅地处理异步操作,避免了回调地狱。
    **生成器允许函数(Generators):** 在函数执行过程中暂停和恢复执行,可用于更复杂的异步控制流程。
    **符号数据类型(Symbols):** 引入了一种新的数据类型,用于创建唯一且不可修改的属性键。
    **Map 和 Set 数据结构:** 引入了更灵活和功能丰富的 Map 和 Set 数据结构,用于存储键值对和唯一值。
    **阵列扩展方法:** 引入了许多新的阵列方法,如`map`、、`filter`等`reduce`,使阵列处理更加方便。
    **字符串新增方法:** 引入了许多新的字符串方法,如`startsWith`、、`endsWith`等`includes`,增强了字符串处理能力。
    这只是 ES6 引入的一些主要特性,还有其他更多的改进和扩展。随着 ECMAScript 版本的不断更新,JavaScript 语言不断获得新的功能和语法。
    
2、如何实现深拷贝?
答:
    深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

-   _.cloneDeep()
-   jQuery.extend()
-   JSON.stringify()
-   手写循环递归
3、对一个数组去除重复项,该怎么做?
答:
    **使用 Set**: 最简单的方法是将数组转换为 Set,因为 Set 只会保留唯一的值,然后再将 Set 转回为数组。
    const originalArray = [1, 2, 2, 3, 4, 4, 5];
    const uniqueArray = Array.from(new Set(originalArray));
    console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
    
    **使用 filter 方法**: 使用 `filter` 方法可以通过检查数组中元素的索引来保留第一个出现的元素,从而去除重复项。
    const originalArray = [1, 2, 2, 3, 4, 4, 5];
    const uniqueArray = originalArray.filter((value, index, self) => {
      return self.indexOf(value) === index;
    });
    console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]

    **使用 reduce 方法**: 使用 `reduce` 方法可以构建一个新数组,逐步将不重复的元素添加到数组中。
    const originalArray = [1, 2, 2, 3, 4, 4, 5];
    const uniqueArray = originalArray.reduce((accumulator, currentValue) => {
      if (!accumulator.includes(currentValue)) {
        accumulator.push(currentValue);
      }
      return accumulator;
    }, []);
    console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]

    **使用 Map**: 使用 Map 数据结构来存储数组中的元素,以元素值作为键。这样,重复元素将自动被覆盖,最后再将 Map 中的值提取为数组。
    const array = [1, 2, 2, 3, 4, 4, 5];
    const uniqueArray = Array.from(new Map(array.map(value => [value, value])).values());
    console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
    这些方法中,使用 Set 是最简单和高效的方式。然而,如果你需要保留原始数组的顺序,可以使用 `filter``reduce` 方法来实现去重操作。
4、写出当一个ul列表,当点击li时,li内容变成...。
答:
    <ul id="list">
      <li>Item 1</li>
      <li>Item 2</li>
      <li onclick="toggleContent(this)">Item 3</li>
      <!-- 可以添加更多的 <li> 元素 -->
    </ul>
    const list = document.getElementById("list");

    list.addEventListener("click", function(event) {
      const clickedElement = event.target;
      if (clickedElement.tagName === "LI") {
        if (clickedElement.textContent === "...") {
          clickedElement.textContent = clickedElement.dataset.originalContent;
        } else {
          clickedElement.dataset.originalContent = clickedElement.textContent;
          clickedElement.textContent = "...";
        }
      }
    });
    
    const list = document.getElementById("list");
    const items = list.getElementsByTagName("li");

    for (let i = 0; i < items.length; i++) {
      items[i].addEventListener("click", function() {
        this.textContent = "...";
      });
    }

    function toggleContent(element) {
      if (element.textContent === "...") {
        element.textContent = element.dataset.originalContent;
      } else {
        element.dataset.originalContent = element.textContent;
        element.textContent = "...";
      }
    }


5、如何使用正则表达式获取地址栏的参数。
答:
    function getURLParameters(url) { const queryString = url.split('?')[1]; if (!queryString) { return {}; } const paramPairs = queryString.split('&'); const params = {}; for (const pair of paramPairs) { const [key, value] = pair.split('='); params[key] = decodeURIComponent(value); } return params; } const url = window.location.search; const parameters = getURLParameters(url); console.log(parameters);
6、对数组中的元素去重,然后对去重后的元素进行排序。
答:
    const originalArray = [3, 1, 2, 2, 4, 1, 5];

    // 使用 Set 去重
    const uniqueSet = new Set(originalArray);

    // 使用 Array.from() 转换为数组,并排序
    const sortedArray = Array.from(uniqueSet).sort((a, b) => a - b);

    console.log(sortedArray);

7、call apply bind的区别是什么?
答:
    `call`, `apply`, 和 `bind` 都是 JavaScript 中用于处理函数调用和上下文的方法。它们的作用都是改变函数的执行环境,但具体的用法和效果有一些区别。

call方法:
call方法用于调用一个函数,并将一个指定的对象作为函数的上下文(this)传递进去。此外,你可以传递任意数量的参数给函数。语法如下:
   `function functionName(arg1, arg2, ...argN) {
       // 函数体
   }
   functionName.call(context, arg1, arg2, ...argN);`
context 是函数要绑定的上下文对象,而后面的参数是函数的参数。

apply方法
apply方法与call类似,也用于调用一个函数并改变函数的上下文。然而,不同之处在于它接受一个参数数组作为参数,而不是逐个列举参数。语法如下:
   functionName.apply(context, [arg1, arg2, ...argN]);
   这里的 `context` 是函数要绑定的上下文对象,第二个参数是一个包含参数的数组。
   
bind方法:
bind方法创建一个新的函数,并将原函数的上下文(this)永久地绑定到指定的对象。返回的新函数在调用时无论在什么上下文中都会保持原来的上下文。语法如下:
   const newFunction = functionName.bind(context);
   这里的 `context` 是函数要绑定的上下文对象。
综上所述,三者的区别主要在于传递参数的方式和是否永久绑定上下文。`call``apply` 的主要区别在于参数传递的方式,而 `bind` 则是创建了一个新函数,并将上下文永久绑定到该函数。
8、请简述什么是广度优先遍历和深度优先遍历,它们的区别是什么?
答:
    广度优先遍历(Breadth-First Traversal)和深度优先遍历(Depth-First Traversal)是两种常用的树和图遍历算法。

1.  **广度优先遍历(BFS)** : 广度优先遍历是一种层次遍历算法,从根节点开始,逐层遍历树或图的节点。具体步骤是从根节点开始,先访问根节点,然后依次访问其相邻的节点,再访问相邻节点的相邻节点,依此类推。这种遍历方式类似于水波从中心向外扩散的方式。

    **区别**:

    -   广度优先遍历是逐层进行的,先遍历完当前层的所有节点,然后再进入下一层。
    -   使用队列来实现广度优先遍历,先进先出(FIFO)的顺序。

1.  **深度优先遍历(DFS)** : 深度优先遍历是一种以深度为优先级的遍历方式,从根节点开始,尽可能深地访问树或图的节点,直到达到叶子节点,然后回溯到上一层继续遍历。具体步骤是从根节点开始,先访问根节点,然后选择一个子节点继续访问,直到到达叶子节点,然后返回到父节点,再选择另一个未访问的子节点,依此类推。这种遍历方式类似于一条路走到底,然后返回走另一条路。

    **区别**:

    -   深度优先遍历是沿着一条路径一直深入到底部,然后再回溯到上一层继续。
    -   使用递归或栈来实现深度优先遍历。

区别总结:

-   广度优先遍历逐层遍历,深度优先遍历逐个路径深入。
-   广度优先遍历使用队列,深度优先遍历使用递归或栈。
-   在树的情况下,广度优先遍历得到的是层次顺序的遍历结果,深度优先遍历可能会有不同的路径顺序。
-   深度优先遍历在一些情况下可能会更快,因为它往往可以更快地到达目标节点。
9、什么是防抖和节流,它们的区别是什么?
答:
    防抖(Debounce)和节流(Throttle)是在处理频繁触发的事件时常用的两种优化技术,用于限制函数的执行频率,从而提高性能和用户体验。

**防抖**:在事件触发后,等待一段时间(例如200毫秒),如果在这段时间内没有再次触发事件,那么执行相应的处理函数。如果在等待时间内又触发了事件,则重新计时。防抖适用于处理频繁触发的事件,如窗口大小调整、搜索框输入等,以避免频繁的函数调用。

**节流**:在一定的时间间隔内(例如200毫秒),只执行一次事件的处理函数。如果事件在时间间隔内多次触发,只有第一次会执行函数,其他的会被忽略。节流适用于需要限制函数执行频率的场景,如滚动事件、鼠标移动事件等,以避免过多的计算。

区别:

  **触发时机**:

    -   防抖:在事件触发后的等待时间内没有再次触发时执行函数。
    -   节流:在一定的时间间隔内执行函数,只执行一次。

  **函数执行次数**:

    -   防抖:只在事件最后一次触发后执行一次函数。
    -   节流:在时间间隔内可能会执行多次函数,但每个时间间隔只会执行一次。

  **适用场景**:

    -   防抖:适用于需要避免频繁函数调用的场景,如窗口大小调整、搜索框输入等。
    -   节流:适用于需要限制函数执行频率的场景,如滚动事件、鼠标移动事件等。
    
    function debounce(func, delay) {
      let timeoutId;
      return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
          func.apply(this, args);
        }, delay);
      };
    }

    // 使用防抖处理滚动事件
    const debouncedScroll = debounce(() => {
      console.log("Scrolled!");
    }, 200);
    window.addEventListener("scroll", debouncedScroll);

    function throttle(func, interval) {
      let lastTime = 0;
      return function(...args) {
        const currentTime = Date.now();
        if (currentTime - lastTime >= interval) {
          func.apply(this, args);
          lastTime = currentTime;
        }
      };
    }

    // 使用节流处理滚动事件
    const throttledScroll = throttle(() => {
      console.log("Scrolled!");
    }, 200);
    window.addEventListener("scroll", throttledScroll);

10、vue的生命周期钩子有哪些?,Vue组件之间的通信方式都有哪些?父子,兄弟,隔代/祖孙与后代,非关系组件。
答:
    Vue.js 组件的生命周期钩子函数以及组件之间的通信方式如下:

**生命周期钩子函数**:

1.  **beforeCreate**:在实例被创建之前被调用,数据观测和事件配置之前。
1.  **created**:实例已经创建完成,数据观测和事件配置完成,但 DOM 尚未生成。
1.  **beforeMount**:在挂载开始之前被调用,此时还没有渲染 DOM1.  **mounted**:实例已经挂载到 DOM 上,可以访问 DOM 元素。
1.  **beforeUpdate**:数据更新时调用,但是在 DOM 更新之前。
1.  **updated**:数据更新时调用,DOM 已经更新。
1.  **beforeDestroy**:实例销毁之前调用。
1.  **destroyed**:实例已经销毁,清理工作应在这里进行。
1.  **activated**(仅在 `keep-alive` 组件中可用):组件被激活时调用。
1.  **deactivated**(仅在 `keep-alive` 组件中可用):组件被停用时调用。

**组件之间的通信方式**:

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

    -   通过 `props` 将数据从父组件传递到子组件。
    -   使用 `$emit` 在子组件中触发事件,由父组件监听该事件。

1.  **兄弟组件通信**:

    -   使用共同的父组件作为中介,通过父组件的数据传递到兄弟组件。

1.  **隔代/祖孙组件通信**:

    -   使用 `$attrs``$listeners` 属性,在嵌套的子组件中传递数据和事件。
    -   使用 `provide``inject` 来在祖先组件中提供数据,然后在后代组件中进行注入。
    -   使用 `ref` 来获取对 DOM 元素或子组件的引用。

1.  **非关系组件通信**:

    -   使用 Vuex 进行集中状态管理,允许多个组件共享状态。
    -   使用事件总线,创建一个 Vue 实例作为事件中心,组件通过它来发送和接收事件。
    -   使用 `.sync` 修饰符在父子组件之间实现双向绑定。
    -   使用 `$parent` 访问父组件的实例,使用 `$root` 访问根组件的实例。

这些通信方式可以根据实际场景来选择,每种方式都有适用的情况。例如,如果需要在父子组件之间传递数据,可以使用 `props``$emit`;如果需要在兄弟组件之间通信,可以通过共同的父组件作为中介;如果需要在隔代/祖孙组件之间通信,可以使用 `$attrs``$listeners` 或者 `provide``inject`,还可以使用 `ref`;对于非关系组件通信,可以根据项目的规模和复杂性选择使用 Vuex、事件总线、`.sync``$parent``$root`
11、简述如果做一个医院预约小程序该怎么做?你认为你会遇到什么问题?怎么解决?小程序的大概功能需要有预约,查看预约内容,支付,管理订单,查看报告。
答:
    设计医院预约小程序需要从用户界面、交互体验、前后端分离、性能优化等方面考虑。以下是一个前端设计方案,以及前端语言和框架的选择:

**前端设计:**

1.  **用户界面设计**:设计直观、易用的用户界面,确保用户可以轻松预约、查看订单、支付等操作。
1.  **交互体验**:优化用户交互体验,确保界面响应迅速、操作流畅。
1.  **导航结构**:设计清晰的导航结构,使用户能够无缝切换预约、支付、订单管理和报告查看等功能。

**前端语言和框架选择:**

1.  **前端语言**:在小程序开发中,主要使用 JavaScript。考虑到目标平台是微信小程序,你应该使用小程序支持的语言。
1.  **前端框架**:微信小程序有自己的原生框架和组件库。你可以使用原生框架,也可以选择使用小程序开发框架,如 Taro 或 uni-app,它们可以实现跨平台开发,减少重复劳动。

**页面和功能:**

1.  **预约页面**:用户可以选择医生、时间,并填写个人信息进行预约。
1.  **订单管理页面**:用户可以查看已预约的订单,取消订单等。
1.  **支付页面**:用户确认预约后,跳转至支付页面,支付预约费用。
1.  **报告查看页面**:用户可以查看已完成的检查报告。

**性能优化:**

1.  **图片优化**:对页面中的图片进行压缩,减少加载时间。
1.  **懒加载**:对长列表和大图像进行懒加载,提升页面加载速度。
1.  **异步加载**:使用异步加载技术,避免阻塞用户界面。
1.  **代码分割**:按需加载页面组件和功能,提升初始加载速度。

**用户隐私与安全:**

1.  **数据加密**:对用户隐私数据进行加密存储,确保数据安全性。
1.  **权限控制**:确保用户只能访问其自己的预约、订单和报告等数据。
1.  **合规性**:遵循相关数据隐私保护法规,保障用户隐私。

**测试和发布:**

1.  对小程序进行全面测试,包括功能测试、性能测试和兼容性测试。
1.  准备小程序的上线材料,如图标、描述、截图等。
1.  提交小程序到微信小程序平台,进行审核和发布。

综上所述,选择前端语言和框架时,考虑项目需求、开发团队技术熟悉度和目标平台特点。确保设计能提供良好的用户体验、高效的开发流程,并与后端开发紧密配合,共同实现医院预约小程序的功能。
12、现有如下数组,请修改数组下所有massage的值。
const newArray = [
            {
                title: 'title-1',
                message: 'message-1',
                children: [
                    {
                        title: 'title-1-1',
                        children: [
                            {title: 'title-1-1-1',message: 'message-1-1-1',children...},
                            {title: 'title-1-1-2',message: 'message-1-1-2',children...}
                        ]
                    },
                    {
                        title: 'title-1-2',
                        children: [
                            {title: 'title-1-2-1',message: 'message-1-2-1',children...},
                            {title: 'title-1-2-2',message: 'message-1-2-2',children...}
                        ]
                    }
                ]
            },
            {
                title: 'title-2',
                message: 'message-2',
                children: [
                    ...
                ]
            }
        ]
答:
    递归遍历的方法
    function modifyMessages(arr) {
        for (let i = 0; i < arr.length; i++) {
            const item = arr[i];
            
            if (item.message) {
            // 修改 message 的值,这里假设你想将 message 加上前缀
            item.message = `Modified: ${item.message}`; } 
            
            if (item.children && item.children.length > 0) {
            // 递归遍历子集
            modifyMessages(item.children); 
            } 
        } 
    }
    modifyMessages(newArray);
    
    使用栈(Stack)或队列(Queuefunction modifyMessages(arr) {
            const queue = [...arr];

            while (queue.length > 0) {
                const item = queue.shift();
                if (item.message) {
                    item.message = 'New Message'; // 修改 message 值
                }
                if (item.children) {
                    queue.push(...item.children); // 将子集加入队列继续处理
                }
            }
        }

        modifyMessages(newArray);
        
        使用递归的同时利用数组的 `map` 方法来修改对象属性
        function modifyMessages(arr) {
            return arr.map(item => {
                const newItem = { ...item }; // 创建新对象,避免修改原对象
                if (newItem.message) {
                    newItem.message = 'New Message'; // 修改 message 值
                }
                if (newItem.children) {
                    newItem.children = modifyMessages(newItem.children); // 递归处理子集
                }
                return newItem;
            });
        }

        const modifiedArray = modifyMessages(newArray);
        console.log(modifiedArray);
        
        使用生成器函数(Generator)来实现迭代修改
        function* modifyGenerator(arr) {
            for (const item of arr) {
                const newItem = { ...item }; // 创建新对象,避免修改原对象
                if (newItem.message) {
                    newItem.message = 'New Message'; // 修改 message 值
                }
                if (newItem.children) {
                    newItem.children = yield* modifyGenerator(newItem.children); // 递归处理子集
                }
                yield newItem;
            }
        }

        const modifiedArray = [...modifyGenerator(newArray)];
        console.log(modifiedArray);
        
        基于递归和 ES6 的解构赋值的方法
        function modifyMessages(arr) {
            return arr.map(item => {
                const { children, ...rest } = item; // 使用解构赋值获取除 children 外的其他属性
                const newItem = { ...rest }; // 创建新对象,避免修改原对象
                if (newItem.message) {
                    newItem.message = 'New Message'; // 修改 message 值
                }
                if (children) {
                    newItem.children = modifyMessages(children); // 递归处理子集
                }
                return newItem;
            });
        }

        const modifiedArray = modifyMessages(newArray);
        console.log(modifiedArray);