Advertisement

前端面试十大刁钻的问题,看看你能接住几个

阅读量:
在这里插入图片描述

文章目录

    • 1. 闭包
    • 2. this 指针
      • 1. 全局环境
      • 2. 函数调用
      • 3. 方法调用
      • 4. 构造函数
      • 5. callapply 方法
  • 3. 事件循环

  • 4. 跨域资源共享机制 (CORS)

  • 5. 事件委托

  • 6. 渲染性能优化

  • 7. 盒模型

    • 标准盒模型(W3C盒模型) 宽度计算基于宽度属性 width
      • IE盒模型 宽度计算基于 width + padding + border
  • 8. CSS选择器的优先级

    • 9. 重绘与回流

    • 10. CSS动画与 JavaScript 动画

      • CSS animation:
          • JavaScript animation:
        • 在选择CSS animation 和 JavaScript animation时, 可以根据以下几点考虑:
    • 附录:「简历必备」前后端实战项目(推荐:⭐️⭐️⭐️⭐️⭐️)

📈「作者简介」:不知名十八线技术博主
📚「推荐主页」:阿珊和她的猫
🕐「简历必备」前后端实战项目(推荐:⭐️⭐️⭐️⭐️⭐️)

前端面试中常见会出现一些难度较高的题目,用于考察候选人的深度理解和专业技能.以下列举了前端岗位常见的"十大棘手题目"供参考.

1. 闭包

请解释什么是闭包并提供一个实际的例子。

闭包是指内部函数可以访问外部函数作用域中变量或函数的特性

JavaScript 提供了一种机制,在嵌入式内部函数访问其外围环境中的变量时会形成闭包结构。

具体来说,闭包由两部分组成:

  • 内部函数 以及它所在的词法环境 (包含了内部函数定义时的作用域链)。

内部函数允许外部函数访问其变量,在外部函数完成后(即完成时),该变量仍保留在内部函数的词法环境中。

以下是一个具体的闭包示例:

复制代码
    function outerFunction() {
      var outerVariable = 'I am from outer';
    
      function innerFunction() {
    console.log(outerVariable);
      }
    
      return innerFunction;
    }
    
    var closure = outerFunction();
    closure(); // 输出: "I am from outer"

如前所述,在示例中外层函数内部嵌套定义了一个内层函数,并获取了 access 到外层函数的变量outerVariable。即便外层函数已经执行完成后,在调用外层函数并将其实例赋值给closure变量时(即将返回值赋给closure变量),该closure捕获了 reference 到内层函数的引用。内层函数仍然能够访问外层函数的变量。因此在调用该closure实例时(即通过调用closure),它能成功输出 'I am from outer'。

这即是闭包的核心概念:内层函数 innerFunction 基于其对外层函数 outerFunction 的语义关联而构建出一个独立的语义空间。这种结构允许外部函数所拥有的变量得以在内部函数体内被访问。该机制在 JavaScript 中得到了广泛应用,在实际开发中能够有效实现封装、模块化以及保护数据隐私等功能。

2. this 指针

请解释 JavaScript 中的 this 关键字,并指出它在不同情况下的值。

JavaScript 中的关键字 this 是一个特别的对象引用,在 JavaScript 中特别的对象引用是指向当前执行代码所在的上下文对象的一个特殊标识符。其值受函数调用的方式以及所处的环境的影响。

在 JavaScript 中,this 的值在以下情况下可能会有不同的取值:

1. 全局环境

在全局作用域内运行时,在不将函数绑定到任何对象之前的情况下(即未将该函数与任何一个对象关联之前),这个变量 (this) 将指向当前的全局环境(即,在浏览器环境中它指向的是 window 对象,在 Node.js 环境中则指向 global 对象)

2. 函数调用

当独立函数被调用时,在这种情况下它引用全局对象。(在严格的执行模式下,则会返回 undefined。)

复制代码
    function myFunction() {
      console.log(this);
    }
    
    myFunction(); // 浏览器环境下输出: Window,Node.js 环境下输出: global

3. 方法调用

当函数作为对象的方法调用时,this 指向调用该方法的对象。

复制代码
    var obj = {
      name: 'ChatAI',
      greet: function() {
    console.log('Hello, ' + this.name);
      }
    };
    
    obj.greet(); // 输出: "Hello, ChatAI"

4. 构造函数

当函数作为构造函数使用时,this 指向正在创建的实例对象。

复制代码
    function Person(name) {
      this.name = name;
    }
    
    var person = new Person('Alice');
    console.log(person.name); // 输出: "Alice"

5. callapply 方法

借助调用 call 或 apply 方法时, 我们可以通过明确设定 context 对象, 将 This 的值成功绑定到目标对象上.

复制代码
    function sayHello() {
      console.log('Hello, ' + this.name);
    }
    
    var person1 = { name: 'Alice' };
    var person2 = { name: 'Bob' };
    
    sayHello.call(person1); // 输出: "Hello, Alice"
    sayHello.apply(person2); // 输出: "Hello, Bob"

在程序运行时依据代码上下文和函数调用方式来确定 this 的值是一个关键问题。准确掌握 this 关键字的规则是深入理解和应用 JavaScript 面向对象编程和技术的重要基础步骤之一。

3. 事件循环

请解释事件循环的工作原理,并说明微任务与宏任务之间的区别。

JavaScript 的异步任务处理机制通过事件循环实现了高效的非阻塞并发执行 。该机制的主要运作模式包括接收输入信号后触发相应的响应函数,并将控制权传递给预先定义好的回调函数进行处理,在完成特定操作后等待返回结果或异常信息并作出相应响应。

JavaScript 引擎负责处理当前代码块中的同步代码,并且是依次执行的

处理任务队列:在遇到异步操作(如定时器中断、网络请求发起或事件监听触发等)时,并不会立即触发相应的响应函数;相反地,在这里会将这些操作暂存至任务队列中以便后续进行处理。

事件处理周期:每次事件循环周期中,在JavaScript引擎的作用下会从任务队列中提取并执行当前待办事项。整个流程会持续不断地重复这一操作直至任务队列被清空为止。

事件循环中的任务可以分为两种类型:微任务和宏任务。

Microtasks are tasks assigned the highest priority, processed immediately within each event loop cycle. Among common microtasks are the Promise's then and catch callbacks, the callback functions of MutationObserver, as well as functions like queueMicrotask. The execution of microtasks is immediate following their current task's completion, making them ideal for handling tasks requiring prompt attention.

宏任务(macrotask):在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,在优先级较低的情况下,
在优先级较低的情况下,
其具体实施内容包括但不限于:
setTimeout、setInterval、I/O操作以及UI渲染等。
这些宏任务在其运行时,
其执行过程需依赖当前子任务的完成,
并将在下一个事件循环周期内展开。

在每一个事件循环周期内,在处理完所有微任务直至微任队列归空后才会开始处理宏任。这种安排确保了微任能够得到及时处理,并使得程序能够实现用户界面更新到下一渲染周期之前完成。

下面是一个简单的示例来展示事件循环的工作原理:

复制代码
    console.log('Synchronous 1');
    
    setTimeout(function() {
      console.log('Timeout');
    }, 0);
    
    Promise.resolve().then(function() {
      console.log('Promise');
    });
    
    console.log('Synchronous 2');

执行上述代码,会按照以下顺序输出:

复制代码
    Synchronous 1
    Synchronous 2
    Promise
    Timeout

通过观察可以发现,在使用 Promise 时会立即触发对应的即时脚本,在该脚本完成运行之后会被立即注册并执行;而 setTimeout 则会在当前脚本运行结束后的下一事件周期开始时被注册并触发。

掌握事件循环与微任务、宏任务之间的差异对构建高效异步JavaScript代码至关重要。这种差异有助于我们更加有效地处理异步操作并保证操作顺序及优先级的一致性。

4. 跨域资源共享 (CORS)

请解释什么是 CORS,以及如何解决跨域请求。

跨域资源共享机制(CORS)是一种技术手段,在浏览器中基于跨域请求的技术提供不同网络环境下的服务

浏览器的同源机制限制了脚本从不同来源加载或传递数据的能力;与此同时,CORS通过向服务器发送特定的HTTP响应头来实现这一目的

CORS的工作原理如下:

当浏览器中的脚本采用AJAX通信或其他方式试图进行跨域通信时**:浏览器将向目标服务器提交一个预检选项(OPTIONS)请示以确认该次跨域通信是否被允许。

服务器处理检测请求 :接收方服务器接收到检测请求后,并将依据请求中Origin头及其他指定信息进行验证工作。

响应机制 :当服务器允许跨域请求时 ,系统会配置并设置相应的CORS头信息 ,以便以指示浏览器当前域名是被允许访问该资源的。

授权访问 :当浏览器接收到服务器返回的数据,并且CORS头中指定当前域允许访问特定来源的资源时,则浏览器会解析这些数据并将其传递给脚本进行处理。

除了通过代理服务器、JSONP或WebSocket之外,还能够实现跨域请求的功能。然而CORS作为一种更为通用且规范化的方案,并非仅仅局限于实现跨域资源的安全共享与访问控制机制。它不仅能够与现代浏览器兼容,并且提供了更为安全和灵活的控制机制。

为实现服务器端的CORS配置,在响应中必须确保包含适当的HTTP头部信息

Access-Control-Allow-Origin: 明确该资源被允许访问的域名。通常不会用于涉及用户凭据的请求;然而,在某些情况下可以选择将其设置为*以表示所有域名均可访问。

Access-Control-Allow-Methods:允许多种HTTP方法被访问(如GET, POST, PUT, DELETE)

Access-Control-Allow-Credentials:指示是否授权请求发送凭据(例如Cookie、HTTP认证)。

以下是一个使用Node.js和Express设置CORS头的示例:

复制代码
    const express = require('express');
    const app = express();
    
    app.use(function(req, res, next) {
      res.setHeader('Access-Control-Allow-Origin', 'http://example.com');
      res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      next();
    });
    
    // 处理请求的路由和逻辑...

在上述示例中,在Access-Control-Allow-Origin中将http://example.com指定为多个来源或某个特定来源;在Access-Control-Allow-Methods中设置了可允许多个HTTP方法;在Access-Control-Allow-Headers中设置了可允许自定义头;在Access-Control-Allow-Credentials中将设置设为true则表示可发送凭据。

为了实现CORS配置的目的,在服务器端通过设置CORS头这一操作,在客户端能够清晰指示哪些位于不同域的资源是可以被访问的,并支持相应的请求方法及其头部信息。从而使得客户端能够在遵循安全规范的前提下与不同域上的资源进行交互。

5. 事件委托

请解释什么是事件委托,并提供一个使用事件委托的示例。

**事件转发(Event routing)**是处理JavaScript中事件的一种技术。它是依赖于浏览器支持的冒泡机制,并将执行逻辑与这些共享容器绑定在一起。该技术通过将操作关联到一个父容器元素来实现对子节点响应的集中控制

通过事件委托机制的使用, 我们能够从而减少了事件处理程序的数量, 从而提升了性能水平, 同时在动态地添加或移除元素时, 无需再次绑定事件处理程序

下面是一个使用事件委托的示例:

设想我们有一个包含多个按钮的列表。我们需要为每个按钮注册一个独立且不冲突的点击事件处理程序。通过事件委托机制, 我们可以将所有的事件处理程序关联到整个列表的父元素, 并根据目标元素来确定哪些按钮被成功点击。

HTML:

复制代码
    <ul id="buttonList">
      <li><button>按钮 1</button></li>
      <li><button>按钮 2</button></li>
      <li><button>按钮 3</button></li>
    </ul>

JavaScript:

复制代码
    var buttonList = document.getElementById('buttonList');
    
    buttonList.addEventListener('click', function(event) {
      if (event.target.nodeName === 'BUTTON') {
    console.log('按钮被点击:', event.target.textContent);
      }
    });

在之前的示例中

采用事件委托机制的优势在于无需对每个按钮单独配置事件处理程序

函数调用在用于处理大型页面、列表或动态内容时特别有效。它具有更高的性能以及更简洁的代码结构。

6. 渲染性能优化

请提供一些用于优化网页渲染性能的技术和策略。

优化网页渲染性能是提升用户体验和减少页面加载时间的关键。

下面是一些用于优化网页渲染性能的常见技术和策略:

降低HTTP请求频率 :通过优化资源利用从而缩短网页加载所需的时间。为了实现这一目标可以通过以下几种方式进行:首先对网页中的CSS与JavaScript文件进行合并并压缩;其次结合CSS Sprites技术将图片打包以及采用Base64编码将小型图标嵌入到HTML结构中以进一步降低HTTP请求次数。

Deferred loading and on-demand loading resources:仅在必要时或需要的时候才会被加载到网页中来,默认情况下不会显示图片、视频或JavaScript库的内容。特别强调的是,在实际应用中可以通过采用延迟加载技术(如deferred.js)、异步负载技术和懒惰化缓存技术等多种方法来优化网页的整体性能表现。

压缩和优化资源 :采用压缩工具(如Gzip)对HTML, CSS, JavaScript以及图像等数据进行处理以降低文件体积或存储空间需求。通过精简空白内容区域以及实施图像压缩策略来进一步提升效率。

降低页面渲染中的重绘与回流操作:由于页面渲染过程中会频繁执行重绘(Repaint)和回流(Reflow)操作所带来的性能消耗较大问题。因此,在进行CSS样式优化时应当尽量减少对样式表的频繁修改(Modify),并合理利用动画效果功能(如transform与opacity),从而降低页面渲染中的开销较大操作次数。

缓存机制的合理配置:科学制定缓存策略,在服务器端通过浏览器实现静态资源的缓存,并规范配置HTTP响应头信息(如:Set-Cookie指令),以降低服务器端的请求频率和缩短网页加载所需时间。具体而言,在HTTP响应中包含适当设置的缓存 expiration 和 validator 信息,并进行版本号校验以避免冲突。

采用内容分发网络技术进行加速:通过部署内容分发网络(CDN),将静态资源分布在世界各地的服务器节点上。从而使用户能够从最近 proximity 的服务器快速获取所需资源,并降低数据传输的时间成本。

图像优化 :为图像进行适当压缩与优化处理;根据场景需求选用相应的图片文件格式(如将JPEG替换为WebP文件类型,并用SVG替代图标文件),按需求加载不同尺寸与分辨率的图片文件。

去除无用插件与脚本 :清理页面中无价值、陈旧或非必要的插件与脚本以减少服务器负担。

异步与延时脚本处理:为脚本设置为异步或延时运行模式以确保网页显示效果。通过后台进程下载完成后立即启动,在网页完全加载完毕后进行处理

采用最新的 CSS 和 JavaScript 技术,并结合 CSS 新特性(如 Flexbox 和 Grid 排版系统)以及 JavaScript 最新功能(如 Intersection Observer 和 Web Workers)来实现显著提升性能并显著增强响应速度。

这类常用的网页渲染性能优化方法和技术方案是开发人员在实际应用中常会使用到的一些关键手段。基于不同场景的应用需求,开发人员可以根据具体情况采用相应的优化策略来提升页面加载速度并带来更好的用户体验。

7. 盒模型

请解释标准盒模型和 IE 盒模型的区别,并说明如何在 CSS 中选择盒模型。

box model is a concept in CSS used to represent the size and borders of elements. The default box model and Internet Explorer box model are two distinct ways to calculate element sizes.

标准盒模型(W3C盒模型) Width (width)

在标准盒模型中,在线元素的实际尺寸基于内容区域的宽度参数(width)与高度参数(height),而不包含内侧间距(padding)或边界(border)的影响。该尺寸不受内侧间距与边界影响

复制代码
       +----------------------------------+
|Content|Padding|Border|
|---|---|---|
|Width (width)|||
||||
||||
||||
||||

       +----------------------------------+

IE盒模型 Width (width + padding + border)

在IE盒模型中,“元素的实际尺寸”由三个部分组成——即内容区域、内边距以及边框;而这些总和则等于将内容区域的大小与内边距和边框相加的结果

复制代码
       +------------------------------------------------+
|Content + Padding + Border|
|---|
|Width (width + padding + border)|
||
||
||
||

       +------------------------------------------------+

在CSS中选择盒模型:

CSS的box-sizing属性用于选择使用哪种盒模型。它有两个可选值:

content-box(默认值):使用标准盒模型,元素的尺寸只包括内容区域。

border-box:使用IE盒模型,元素的尺寸包括内容、内边距和边框。

例如,我们可以通过以下方式将元素的盒模型设置为border-box

复制代码
    .element {
      box-sizing: border-box;
    }

在配置中选择box-sizing属性的相关参数或选项时

8. CSS 选择器的优先级

请解释 CSS 选择器的优先级计算规则,并给出一个示例。

CSS选择器的顺序运算方法用于判断在多个选择器应用于同一元素的情况下哪些选择器具有较高的顺序,在此基础上来决定最终样式。

下面是CSS选择器优先级计算规则的总结:

内联样式具有最高等级的优先权

选择器的特殊性:选择器的特殊性属性用于确定其 precedence level 的大小;specialty value较大的选择器意味着其 precedence level 较高

  • ID类型的特殊属性值设定为100,并且这种设置方式通常会以标签的形式表示出来。例如,在HTML中使用#myElement标签即可实现此功能。
  • 类选型(包括属性选型和伪类选型)通常具有统一的特殊属性值设定均为10,并且这种设置习惯在实际应用中较为常见。例如,在CSS中可以通过.myClass实现对同类元素的选择效果。
  • 元素选型(以及其派生形式如伪元素选型)则通常具有较低的统一属性值设定均为1,并且这种设计方式能够有效提升代码的一致性和可维护性。例如,在HTML文档中使用div标签或::before伪元素即可实现类似效果。

特殊性值的计算规则是将各类别特殊性值相加,并按照特定顺序进行比较。举个例子来说,在.myClass div的情况下,类选择器贡献了较高的得分(因为有类名),而元素选择器提供了较低的得分(因为div属于基本类型),因此总分为两者之和即为该组合的选择权得分。相反地,在div.myClass的情况下,则会先考虑元素位置带来的较高权重因素加上类名的影响,则整体得分会显著提升。

继承样式 :其会被直接设置在其父元素样式表中的相应位置。

重要样式:通过使用带有!important标记的样式可以在CSS中实现最高优先级的效果,并且能够掩盖所有其他非重要级别的样式设置。为了确保代码的一致性和可维护性 ,尽量避免过度依赖或滥用带有!important标记的样式 ,因为它们可能会导致复杂的管理问题。

下面是一个示例,演示CSS选择器优先级计算规则的应用:

复制代码
    <style>
      /* 优先级:内联样式 > ID选择器 > 类选择器 */
      #myElement {
    color: red; /* ID选择器优先级高 */
      }
    
      .myClass {
    color: blue;
      }
    </style>
    
    <div id="myElement" class="myClass" style="color: green;">Hello, World!</div>

在上述示例中,div元素应用了多个样式规则:

内联样式color: green;指定了字体颜色为绿色。
ID选择器#myElement指定了字体颜色为红色。
当特殊性值较高时,红色样式将覆盖其他样式设置。
类选择器.myClass指定了字体颜色为蓝色。
当特殊性值较低时,蓝色样式设置将被红色样式覆盖。

所以, 最后div元素的字体颜色将呈现为红色. 这一示例表明这种比较关系中不同选择器间的优先级差异.

9. 重绘与回流

请解释重绘和回流的概念,并提供一些优化网页性能的建议。

重绘(Repaint)和回流(Reflow)是与网页渲染相关的两个概念。

当元素的显示状态发生转变但基本结构保持不变时**(即不改变其布局)**时,浏览器会主动重绘这些元素的显示部分以适应新的外观展示它们。

重定位指的是当元素的布局属性发生变化时 ,必须重新计算元素的几何尺寸与位置,并导致整个文档结构发生相应调整的过程。

由于重绘与刷新都会引发浏览器对元素样式与布局进行重新计算, 因此它们都带来了额外的性能消耗. 频繁进行的重绘与刷新操作可能导致页面整体性能出现下降, 从而可能导致页面出现卡顿现象并延长初始加载时间.

以下是一些优化网页性能的建议:

采用CSS3提供的动画与过渡功能:不建议直接进行样式操作以实现动画效果。通过CSS3的transform属性及其类似属性(如opacity)来生成平滑且流畅的效果。这些属性会导致合成线程被触发,并从而降低页面刷新频率。

统一调整样式:在需要为多个对象设置样式时,请尽量采取统一调整的方式而非逐一操作。这是因为单独的样式更改可能导致反复渲染与网络传输开销增加的情况发生。

当需要对DOM进行复杂操作时(例如插入、移动或删除多个元素),推荐采用Document Fragment技术来进行批量处理,并将处理结果一次性导入到整体文档中以降低数据回流的频率。

尽量减少对布局信息的频繁查询:当试图确定元素的位置或大小时,请尽量减少对诸如OffsetTop、OffsetLeft等属性的频繁调用(因为它们可能导致页面重绘)。通过一次性获取所有相关信息并存储起来(而不是反复调用),可以有效减少计算开销。

采用 transform 替代 position: fixed; top; left

尽量不用<table>元素:
对于基于<table>元素的结构来说,在复杂的计算逻辑中可能会遇到性能问题。
如果可能的话,请尽量采用CSS中的Flexbox或Grid框架来实现页面布局。

虚拟化列表技术:在处理大规模数据时(如表单或表格),采用虚拟化列表技术能够仅动态渲染当前可见区域的数据片段而非全部内容。这种做法可有效降低页面加载时间和用户体验负担

通过减小冗余的绘制操作和减少页面刷新缓冲区的影响来显著提高网页加载速度,并从而提升用户体验感受。在进行这些方面的优化时,请确保采取综合考量的方法,并根据具体情况进行最佳实践的选择。

10. CSS 动画与 JavaScript 动画

请阐述CSS动画与JavaScript动画之间的主要差异,并深入分析它们各自适用的应用场景。

CSS动画和JavaScript动画各自都用于表现网页中的动态效果,然而它们之间存在一些差异。

CSS动画:

  • CSS动画是借助@keyframes规则描述关键帧与过渡效果,并通过附加样式类或伪类触发动画展示。
  • CSS动画利用浏览器内置的渲染引擎进行展示,并具有高效的运行效率能够在大多数现代浏览器上流畅运行。
  • 仅需以简洁的方式设定样式即可达成包括渐变、旋转变换以及缩放等多种复杂效果无需编写额外JavaScript代码。
  • CSS动画特别适合表现简单到复杂的变化过程尤其适用于对样式属性进行动态转换的应用场景。

JavaScript动画:

  • JavaScript动画是通过生成JavaScript代码来影响元素属性值的方式呈现动态效果。
  • JavaScript动画主要依靠定时器函数如setIntervalrequestAnimationFrame持续更改元素属性值以完成平滑过渡。
  • JavaScript animation能够支持更为复杂的互动效果例如基于用户的输入操作并完成相关动画逻辑处理。
  • 相较于CSS animation JavaScript animation展现出更高的灵活性能够应对更为复杂的场景包括创建粒子效果以及模拟物理现象。

在选择CSS动画和JavaScript动画时,可以根据以下几点考虑:

  • 基础视觉效果方面:对于简单的过渡特效和样式动画而言,在不影响性能的情况下优先选择CSS animation。
    • 复杂场景下:当需要处理复杂的运算、交互流程以及依赖用户动态输入的情况时,请考虑使用JavaScript animation。
    • 多元素场景或高要求性能情况下:JavaScript animation通常能提供更好的控制和性能。
    • 兼容性考量:CSS animation在现代浏览器有良好的支持,并且得益于内置的硬件加速功能表现优异。然而,在较旧的设备上可能存在兼容性问题。

综上所述,在设计中我们通常会采用CSS来完成基本的样式动画和过渡效果的设计。这是因为通过简单的样式声明就能轻松实现这些功能。而对于更复杂的动画逻辑和交互式效果,则更适合使用JavaScript技术来开发。在实际应用中,我们需要根据具体的需求以及所处的场景来选择最适合的动画技术方案。

这些考察项主要围绕前端的关键理论与技术实践展开,在解答过程中要求应聘者不仅掌握相关理论知识,还需在实践中积累丰富的工作经验,以便更深入地探讨问题并提供切实可行的解决方案.请注意不同公司的岗位设置可能因公司性质与招聘人员偏好而有所差异.

附录:「简历必备」前后端实战项目(推荐:⭐️⭐️⭐️⭐️⭐️)

通过 Vue.js 和 Egg.js 开发的企业级健康管理系统项目
助你系统地从零开始学习并实践uni-app技术与应用课程

全部评论 (0)

还没有任何评论哟~