前端面试题详解整理1|,垂直居中,合并有序 js的模块化能力,token,登陆,commenJSjs的面向对象和继承, 组件通信方式 vuex里的model

186 阅读14分钟

www.nowcoder.com/feed/main/d…

腾讯前端 一面

1. css布局方式(没看过,提醒之后说了弹性盒子和定位,响应式)
CSS 布局是网页设计中至关重要的一部分,它决定了网页中各个元素的位置和排列方式。主要的 CSS 布局方式包括:

  1. 流式布局(Flow Layout):HTML 元素默认的布局方式,元素按照其在 HTML 文档中的出现顺序从上到下依次排列,如果空间不足则自动换行。

  2. 弹性盒子布局(Flexbox Layout):通过 display: flex 属性来实现的一种灵活的布局方式,可以在一维空间上(水平或垂直方向)对元素进行排列。使用弹性盒子布局可以轻松实现元素的对齐、分布和排列等。

  3. 网格布局(Grid Layout):通过 display: grid 属性来实现的二维网格布局方式,可以将页面划分为行和列,并通过网格线将元素放置在网格中的位置。网格布局提供了更多的灵活性和控制力,适用于复杂的页面布局需求。

  4. 定位布局(Positioning Layout):通过 position 属性来实现的一种基于文档流以外位置定位的布局方式,常见的定位方式包括相对定位(position: relative)、绝对定位(position: absolute)和固定定位(position: fixed)等。

  5. 响应式布局(Responsive Layout):针对不同设备尺寸和屏幕分辨率进行优化的布局方式,通过媒体查询(Media Queries)和 CSS Flexbox、Grid 等特性来实现页面在不同设备上的适配和响应。

以上是常见的 CSS 布局方式,它们可以单独或结合使用,根据项目需求和设计目标选择合适的布局方式来实现页面布局。

3. css的垂直居中布局
垂直居中布局在 CSS 中可以通过多种方式实现,以下是一些常见的方法:

  1. 使用 Flexbox
.container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
}
  1. 使用绝对定位和 transform
.container {
  position: relative;
}

.item {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
  1. 使用表格布局
.container {
  display: table;
}

.item {
  display: table-cell;
  vertical-align: middle;
}
  1. 使用网格布局
.container {
  display: grid;
}

.item {
  align-self: center;
  justify-self: center;
}
  1. 使用伪元素
.container {
  position: relative;
}

.item {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

以上是几种常见的垂直居中布局方式,具体选择哪种方法取决于项目需求和兼容性要求。

5. css类的优先级,我没说伪类,还问了伪类在哪里

在 CSS 中,选择器的优先级是指确定样式应用的权重或优先级的规则。选择器的优先级决定了当多个规则应用于同一个元素时,哪个规则的样式会被应用。CSS 的选择器优先级通常按照以下顺序进行计算:

  1. 内联样式(Inline Styles):应用于 HTML 元素上的 style 属性,具有最高的优先级。

  2. ID 选择器:使用 #id 来选择元素,具有较高的优先级。

  3. 类选择器、属性选择器、伪类选择器:包括 .class[attribute]:pseudo-class 等,具有中等优先级。

  4. 元素选择器:如 divpa 等,具有较低的优先级。

  5. 通配符选择器、子选择器、相邻选择器等:具有较低的优先级。

如果两个规则具有相同的优先级,则按照它们在样式表中出现的顺序决定哪个规则应用。

在上述规则中,伪类选择器属于第三条规则中的一种,即和类选择器、属性选择器等具有相同的优先级。伪类选择器通常用于选择元素的特定状态或行为,例如 :hover:focus 等,它们的优先级和其他普通的选择器是相同的。

总的来说,了解选择器优先级是编写 CSS 样式时的关键,可以帮助避免样式冲突和预期外的样式覆盖问题。

8. 你说标签里的style是最高的,还有更高的吗(important,这个完全忘记了,是面试官说的)
对不起,我之前忘记提及 !important。确实,在 CSS 中,!important 是一种用于提升样式优先级的机制。当样式规则使用了 !important 标记时,该规则的样式会比其他具有相同选择器的规则更具优先级,即使其他规则具有更高的特殊性或权重。

因此,!important 可以被认为是最高优先级的。然而,它应该谨慎使用,因为过度使用 !important 会导致样式的不可维护性和混乱,使得代码难以阅读和理解。通常情况下,建议尽量避免使用 !important,而是通过正确的选择器和规则结构来管理样式的优先级。

10. js的模块化能力,commenJS什么的(没看过)

JavaScript 的模块化能力是指在 JavaScript 中组织和管理代码的能力,使得代码可以按照模块的方式进行分割和组织,从而提高代码的可维护性、可重用性和可扩展性。主要的 JavaScript 模块化规范包括 CommonJS、AMD、CMD 和 ES6 Modules。

  1. CommonJS:是 Node.js 使用的模块化规范,也是 JavaScript 后端开发中最常用的模块化规范之一。它通过 requiremodule.exports 来导入和导出模块,可以实现同步加载模块的功能。

    示例:

    // 导入模块
    const moduleA = require('./moduleA');
    // 导出模块
    module.exports = { ... };
    
  2. AMD(Asynchronous Module Definition):是 RequireJS 提出的模块化规范,主要用于浏览器端的异步加载模块。它通过 define 函数来定义模块,使用 require 函数来异步加载模块。

    示例:

    // 定义模块
    define(['moduleA'], function(moduleA) { ... });
    // 异步加载模块
    require(['moduleA'], function(moduleA) { ... });
    
  3. CMD(Common Module Definition):是 SeaJS 提出的模块化规范,与 CommonJS 类似,但是采用异步加载模块的方式。它通过 define 函数来定义模块,使用 require 函数来异步加载模块。

    示例:

    // 定义模块
    define(function(require, exports, module) { ... });
    // 异步加载模块
    require(['moduleA'], function(moduleA) { ... });
    
  4. ES6 Modules:是 ECMAScript 2015 标准引入的官方模块化规范,主要用于浏览器端和 Node.js 环境。它通过 importexport 关键字来导入和导出模块,具有静态解析和异步加载的特性。

    示例:

    // 导入模块
    import moduleA from './moduleA';
    // 导出模块
    export { ... };
    

以上是 JavaScript 的几种主要模块化规范,它们都为 JavaScript 开发提供了灵活的模块化能力,使得代码组织和管理更加方便和高效。

13. js的闭包

15. js的面向对象和继承(不要因为多就不背了!看,被拷打了吧)
JavaScript 是一种面向对象的语言,它支持基于原型的继承机制。在 JavaScript 中,对象可以通过构造函数或者类来创建,可以通过原型链实现继承。

面向对象

  1. 构造函数和原型:构造函数可以用来创建对象实例,原型(prototype)用来共享属性和方法。通过构造函数创建的对象实例可以访问构造函数的原型对象上的属性和方法。

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.sayHello = function() {
        console.log(`Hello, my name is ${this.name}.`);
    };
    
    const person1 = new Person('Alice', 30);
    person1.sayHello(); // 输出:Hello, my name is Alice.
    
  2. 类和对象:ES6 引入了类(class)的概念,可以更加方便地创建对象和实现继承。

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    
        sayHello() {
            console.log(`Hello, my name is ${this.name}.`);
        }
    }
    
    const person1 = new Person('Alice', 30);
    person1.sayHello(); // 输出:Hello, my name is Alice.
    

继承

  1. 原型链继承:通过将子类的原型对象指向父类的实例来实现继承。

    function Animal(name) {
        this.name = name;
    }
    
    Animal.prototype.sayName = function() {
        console.log(`My name is ${this.name}.`);
    };
    
    function Dog(name, breed) {
        Animal.call(this, name);
        this.breed = breed;
    }
    
    Dog.prototype = new Animal();
    Dog.prototype.constructor = Dog;
    
    const dog1 = new Dog('Buddy', 'Golden Retriever');
    dog1.sayName(); // 输出:My name is Buddy.
    
  2. ES6 的类继承:ES6 中可以使用 extends 关键字来实现类的继承。

    class Animal {
        constructor(name) {
            this.name = name;
        }
    
        sayName() {
            console.log(`My name is ${this.name}.`);
        }
    }
    
    class Dog extends Animal {
        constructor(name, breed) {
            super(name);
            this.breed = breed;
        }
    }
    
    const dog1 = new Dog('Buddy', 'Golden Retriever');
    dog1.sayName(); // 输出:My name is Buddy.
    

以上是 JavaScript 中面向对象和继承的基本概念和实现方式。通过构造函数、原型和类,以及原型链继承和 ES6 类继承,可以灵活地实现面向对象编程和对象之间的继承关系。

16. (我说了一个原型链继承,还要我说其他的,就不清楚了)
17. 组件通信方式 在前端开发中,组件通信是非常常见和重要的一个方面,特别是在大型应用程序中,不同组件之间的通信可以通过多种方式来实现:

  1. Props/属性传递:父组件可以通过props向子组件传递数据,子组件通过props接收父组件传递的数据。这是React和Vue等框架中最常见的一种组件通信方式。

  2. 事件:子组件可以通过事件向父组件传递数据或触发父组件的方法。父组件可以通过监听子组件的事件来获取子组件传递的数据或进行相应的操作。

  3. 全局事件总线:可以使用事件总线或者发布/订阅模式来实现组件之间的通信。在Vue中可以使用Vue实例作为事件总线,通过$emit$on来发布和订阅事件。在React中可以使用第三方库如EventEmitter或者自定义全局事件对象来实现。

  4. Vuex/Redux等状态管理工具:适用于大型应用程序中复杂的状态管理需求,可以把共享的状态抽取出来,集中存放在全局的状态容器中,并通过派发动作来修改状态。不同组件可以通过连接器(connect)或者映射状态(mapState)的方式来获取或修改全局状态,从而实现组件之间的通信。

  5. **refs:在Vue中可以使用refs**:在Vue中可以使用`refs`来获取子组件的引用,并直接调用子组件的方法或访问子组件的属性。这种方式适用于父子组件之间紧密耦合的情况。

  6. Context API:在React中可以使用Context API来实现跨层级组件之间的数据传递,可以避免props层层传递的繁琐和不必要的渲染。

  7. WebSocket/HTTP请求:在需要与服务器进行实时通信或者获取远程数据的情况下,可以使用WebSocket或者HTTP请求来进行数据的交换。

这些是前端开发中常见的几种组件通信方式,根据具体的场景和需求可以选择合适的方式来实现组件之间的通信。

19.vuex
20. vuex里的model是干嘛的(就是把一部分数据再进行分组管理,我大概猜了个这个意思)
在Vuex中,并没有内置的"model"概念,可能你指的是"module"。Vuex中的module是用来将store分割成多个小模块的一种方式,以便更好地组织和管理应用的状态。

通过将状态、mutations、actions和getters分割成独立的模块,可以使得代码更加清晰和易于维护。每个模块可以拥有自己的状态、mutations、actions和getters,而且可以嵌套使用。

使用模块化的Vuex可以让你更好地组织你的代码,并且可以让多人协作开发变得更加容易。每个模块可以由不同的开发人员负责,提高了代码的可维护性和可扩展性。

总的来说,Vuex中的module是用来组织和管理状态的一种方式,通过分割成多个小模块,可以更好地管理应用的状态,并提高代码的可维护性。

22. 浏览器的缓存方式
23.项目里面,如何实现用户登录的(使用了token)
24. 那你的token是怎么保存的,你用了token如何判断登录有没有过期(我说项目是网上借鉴的,不涉及token过期的内容)
在项目中实现用户登录通常涉及以下几个步骤:

  1. 用户认证:用户输入用户名和密码进行登录,后端验证用户身份是否合法。如果合法,后端会生成一个令牌(token)并返回给客户端。

  2. 保存token:客户端通常会将token保存在本地,比如使用localStorage、sessionStorage或者cookie等方式保存token,以便在后续的请求中发送给后端进行身份验证。

  3. 发送token:在每次向后端发送请求时,客户端会将token作为请求的一部分发送给后端,通常是在请求的头部(header)中携带token。

  4. 后端验证:后端在接收到客户端的请求后,会验证请求中携带的token是否合法。如果token合法且未过期,则认为是有效的用户请求,否则返回未认证的错误信息。

  5. 过期判断:在客户端,可以通过token的有效期来判断token是否过期。一般来说,token会包含一个过期时间(expiration time),客户端可以解析token获取过期时间,并在每次请求前判断当前时间是否超过了过期时间,如果超过了,则需要重新登录获取新的token。

需要注意的是,token的保存和验证是项目安全性的关键部分,需要谨慎处理,避免泄露和被恶意利用。另外,token的过期时间需要根据实际情况来设置,通常会根据应用的安全性和业务需求来确定合适的过期时间。

在项目中,token通常是保存在客户端的本地存储中,比如localStorage或者sessionStorage中。一般来说,登录成功后,后端会返回一个包含token的响应,在前端的登录逻辑中,我们会将这个token保存在本地存储中,以便在后续的请求中使用。

关于如何判断token是否过期,通常的做法是在前端解析token,提取其中的过期时间字段(比如exp),然后与当前时间进行对比。如果当前时间大于过期时间,则说明token已过期,需要重新登录获取新的token。

以下是一个简单的示例代码,演示了如何判断token是否过期:

// 从本地存储中获取token
const token = localStorage.getItem('token');

if (token) {
    // 解析token获取过期时间
    const decodedToken = jwt_decode(token); // 假设使用了JWT格式的token
    const expirationTime = decodedToken.exp * 1000; // 过期时间是以秒为单位,需转换成毫秒

    // 获取当前时间
    const currentTime = Date.now();

    // 判断token是否过期
    if (currentTime > expirationTime) {
        // token已过期,需要重新登录
        console.log('Token has expired, please log in again.');
    } else {
        // token未过期,可以继续使用
        console.log('Token is still valid.');
    }
} else {
    // 本地存储中没有token,需要登录获取
    console.log('No token found, please log in.');
}

这段代码首先从本地存储中获取token,然后解析token获取其过期时间,与当前时间进行对比,从而判断token是否过期。如果token过期,则需要提示用户重新登录获取新的token。

26. 手撕 合并有序数组(这里我直接写之前练过的leetcode代码,但是有细节错误所以调试没过。循环是从后往前循环的,直接返回了nums1数组。但是面试官说我逻辑错误,可以直接写一个新数组从前往后依次放入。反正当时太懵了,一下子按照他的思路改,前后的东西都没改,手撕失败。面试官说了一下我哪里有错误)
这里提供一个从前往后合并有序数组的简单示例代码:

function merge(nums1, m, nums2, n) {
    let result = [];
    let i = 0; // nums1的指针
    let j = 0; // nums2的指针

    // 循环比较nums1和nums2中的元素,将较小的元素放入result数组中
    while (i < m && j < n) {
        if (nums1[i] < nums2[j]) {
            result.push(nums1[i]);
            i++;
        } else {
            result.push(nums2[j]);
            j++;
        }
    }

    // 将nums1或nums2中剩余的元素放入result数组中
    while (i < m) {
        result.push(nums1[i]);
        i++;
    }

    while (j < n) {
        result.push(nums2[j]);
        j++;
    }

    return result;
}

这段代码首先创建一个空数组result,然后使用两个指针i和j分别指向nums1和nums2的起始位置。接着,循环比较nums1和nums2中的元素,将较小的元素依次放入result数组中。最后,将nums1或nums2中剩余的元素依次放入result数组中。最终返回合并后的有序数组result。

后续:秒挂😂

作者:牛客172256776号
链接:www.nowcoder.com/feed/main/d…
来源:牛客网