面试笔记-js基础篇

news2024/11/23 18:58:32

1、因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

typeof null // 'object'

2、最准确判断类型的是 Object.prototype.toString.call(null) === '[object Type]' 

注意type的第一个字母为大写

3、判断两个值是否相等

null可以用 null === null以及Object.is(null, null)

NaN需要用Object.is(NaN, NaN)

undefined,一般情况下使用undefined === undefined判断,但在一些古早的老版本的浏览器中undefined不是保留词,可能会存在undefined 变量被重新赋值。可以使用undefined === void 0,

void 0 总会安全地返回undefined

4、Boolean把 null,undefined,false,NaN,'', +/-0转化为false,其他值包括对象都转化为true

5、instanceof通常用于检查一个对象是否是某个构造函数的实例。它在判断某个对象是否属于特定类型时非常有用,但它主要适用于引用类型(对象)。对于基本类型(如字符串、数字、布尔值等),instanceof 不适用,因为它们不是对象。

instanceof 运算符的内部工作机制如下:

  1. 获取 constructorprototype 属性值,记为 prototypeObj
  2. 获取 object 的原型链(__proto__Object.getPrototypeOf(object))。
  3. 在原型链中从 object 开始逐级向上查找。
    • 如果找到了一个原型等于 prototypeObj,返回 true
    • 如果到达原型链的顶端(即 null),仍然没有找到,返回 false

当我们运行 foo instanceof Foo 时:

  • JavaScript 首先检查 foo.__proto__ 是否等于 Foo.prototype,是的话返回 true
  • 如果不是,继续检查 foo.__proto__.__proto__ 是否等于 Foo.prototype,直到找到为止。
  • 如果到达了原型链的顶端(null)仍然没有找到 Foo.prototype,则返回 false

6、原型链:在 JavaScript 中,每个对象都有一个内部属性([[Prototype]]),它指向其构造函数的原型对象(即 prototype)。这就形成了一条“原型链”。当我们访问对象的一个属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(null)为止。

7、箭头函数的this:

  • this 是静态的,取决于函数定义时的上下文: 箭头函数不会有自己的 this,它会捕获其所定义时所在上下文的 this
  • 不能通过 callapplybind 改变箭头函数的 this 对于箭头函数,这些方法对 this 的绑定没有效果。
  • 没有 arguments 对象: 箭头函数没有自己的 arguments 对象,但你可以使用 rest 参数 (...args) 来获取所有传入的参数。
  • 不能作为构造函数(没有 new 绑定): 你不能使用 new 关键字来调用箭头函数,否则会抛出错误。

8、call、apply、bind

bind与call、apply最大的不同:
 当我们把一个绑定函数用作构造函数时,bind 的行为会发生变化。具体来说,当一个绑定函数被用作构造函数调用时:

  1. 忽略绑定时传入的上下文:绑定函数会忽略你在 bind 时传入的 this 上下文,而是将 this 指向新创建的实例对象。
  2. 保持原函数的原型链:绑定函数创建的实例对象会继承原始函数(未绑定的函数)的原型链,这意味着实例对象可以访问原始函数的所有方法和属性。
// 手写call
Function.prototype.call2 = function(context, ...args) {
    context = context || window
    context.fn = this
    const result = context.fn(args)
    delete context.fn
    return result
}
// 手写apply
Function.prototype.apply2 = function(context, args) {
    context.fn = this
    let result
    // args为数组或为空
    if (args) result = context.fn(...args)
    else result = context.fn()

    delete context.fn
    return result
}
/** 手写bind
* bind实现有两个比较关键的点;
* 1、参数柯里化(即绑定时传入的参数和调用时传入的参数可以合并到原函数)
* 2、当把返回的函数作为构造函数时,
* 1)会忽略bind时传入的this上下文,而是将this指向新创建的实例对象;
* 2)保持原函数的原型链,既实例对象可以访问原始函数的所有方法和属性
**/
Function.prototype.bind2 = function(context, ...args) {
    let self = this
    const foundFun = function(...args1) {
        const isConstructor = this instanceOf foundFun // 判断当前是否作为构造函数被调用
        const bindArgs = isConstructor ? this : context // 如果是构造函数就指向构造函数调用时的this
        self.apply(bindArgs, [...args, ...args1])
    }
    // 还需要保持原函数的原型链,让其可以访问原始函数的方法与属性
    foundFun.prototype = Object.create(self.prototype) // 使用Object.create是因为它会创建了一个新对象,保证foundFun和Object互相独立,新增属性等情况时不会互相干扰
    return foundFun
}

9、setTimeout可以传入多个参数

function greet(name, message) {
  console.log(name + ": " + message);
}

// 延迟 2 秒后执行函数,并传递参数 "Alice" 和 "Hello!"
setTimeout(greet, 2000, "Alice", "Hello!");

传递给回调函数的参数

  • 从第三个参数开始,可以传递任意数量的参数,这些参数会作为回调函数的参数传入。

10、深浅拷贝

浅拷贝的多种方式。Object.assign()、扩展运算符(...)、slice()。

浅拷贝只能解决第一层的问题,如果需要解决多层拷贝的问题,就需要用到深拷贝

深拷贝通常可以通过 JSON.parse(JSON.stringify(object)) 来解决。

但是该方法也是有局限性的:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

11、三种模块化处理的区别

  • commonjs是同步导入,适合服务端,ES Modules是异步导入,适合浏览器端
  • commonjs导出的时候是对值的拷贝,ES Modules导出的是对值的引用
  • commonjs在第一次加载时被执行,并缓存其导出结果。ES Modules在导入时不会立即执行,而是在需要时进行异步加载和执行

 随着 ES Modules (ESM) 成为 JavaScript 的标准模块系统,以及现代打包工具(如 Webpack)的使用,AMD 的使用场景有所减少。

12、如何避免频繁重排和重绘

  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)

  • 使用 translate 替代 top,避免使用 topleft 等定位属性,使用 transform 结合 translate 来改变位置,不会引发重排,只会引发合成(compositing)重绘。

  • 使用Document Fragment或隐藏元素修改:在更新多个 DOM 时,使用 DocumentFragment 或者将元素 display: none 之后再进行批量更新,完成后再显示,这样只会触发一次重排和重绘。

  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame

13、如何渲染几万条数据并不卡住界面

  • 虚拟列表
  • 懒加载
  • requestAnimationFrame

14、关于执行上下文

执行上下文定义了代码的执行环境,包括变量、函数和对象的可访问性。执行上下文可以帮助我们理解作用域、变量提升以及 this 的指向等概念。

执行上下文的类型可以分为:

  1. 全局上下文;在全局上下文中定义的变量和函数是全局可访问的
  2. 函数上下文;函数上下文中包含该函数的参数、局部变量和对外部变量的访问权限。
  3. Eval 上下文;使用 eval 语句时会创建一个特殊的执行上下文。谨慎使用eval,会带来性能和安全隐患
  • 性能问题。eval 会使 JavaScript 引擎无法进行优化,因为它需要在运行时解析和执行代码。这可能导致性能下降,尤其是在大规模使用时。
  • 安全隐患。使用 eval 可能导致安全问题,特别是在处理不可信的输入时。如果用户输入的字符串被直接传递给 eval,可能会导致代码注入攻击。

执行上下文由三个部分组成:

  • 变量环境;存储该上下文中定义的变量和函数声明;
  • 作用域链;指的是代码中可以访问变量和函数的区域。JavaScript 中主要有两种作用域:全局作用域和局部作用域。JavaScript 采用的是静态作用域,函数的作用域在函数定义的时候就决定了。
  • this 关键字的指向;

15、闭包

闭包:就是可以访问外部作用域变量的内部函数

注意事项

虽然外部函数已经执行完成,但是变量仍然存在于内存中,因为闭包保持着对它们的引用,被引用的变量直到闭包被销毁时才会被销毁。可能导致对象无法被垃圾回收机制回收,从而导致内存泄漏(内存泄漏是指程序在运行时未能正确释放不再需要的内存空间,导致这部分内存无法被重新利用。这通常会导致应用程序的内存使用不断增加,最终可能导致性能下降或崩溃。)

使用场景

  1. 数据封装与私有变量:使用闭包可以创建具有私有变量的对象,控制对数据的访问。

  2. 防止全局命名冲突:闭包可以用来封装代码,防止全局作用域被污染。

  3. 函数记忆:闭包可以用于生成带有特定环境的函数(函数工厂),这些函数可以记住其创建时的上下文。

比较常见的节流和防抖的函数就是利用了函数记忆这个特点

防抖函数

// 防抖一般用于防止按钮被多次点击,从而频繁触发点击时间。防抖的处理思想是,一段时间内,无论操作多少次,都只执行最后一次

function debounce(fn, wait) {
    let timer;
    return function (...args) {
        if (timer) clearTimeout(timer)
        const context = this
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}

节流函数

// 节流的原理是一段时间内频繁触发某件事,只会执行一次,用于减少浏览器开销。常见的场景有 滚动事件:滚动页面时频繁触发 scroll 事件,节流可以确保在每隔一定时间内执行一次,避免性能问题

// 最简单的实现方式是时间戳

function throttle(fn, delay) {
    let lastTime = 0
    return function (...args) => {
        const now = Data.now()
        const context = this
        if (now - lastTime >= delay) {
            lastTime = now
            fn.apply(context, args)
        }
    }
}

看下以下的执行顺序

for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
}
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
}

第一个输出0,1,2,3,4;第二个输出5,5,5,5,5

原因:

let是块级作用域,这意味着每次迭代时都会创建一个新的 i 变量实例,所以每一次循环的i其实都是一个新的变量。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

var 声明的变量,作用域是整个包含它的函数。如果它在全局上下文中声明,则会成为全局变量;如果在某个函数内部,它的作用域就是该函数。意味着在整个循环中只有一个 i 变量实例。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值

16、垃圾回收机制

浏览器的垃圾回收机制主要用于管理内存,确保在执行Javascript时不会再出现内存泄漏或资源浪费。垃圾回收机制的核心是通过识别那些对象不再被使用,将它从内存中清除。JS使用的是自动垃圾回收。主要的方式是标记清除。

标记清除通过“标记”和“清除”两个阶段来识别并清除不再需要的对象,核心思想是 从根对象开始,标记所有仍然可访问的对象,未被标记的对象则视为“垃圾”并进行清除。

缺点:
  • “暂停-世界”现象(Stop-the-world):标记-清除算法在执行时,必须暂停程序的执行,这意味着程序会在垃圾回收期间停止响应(虽然现代垃圾回收器通过分代回收、并发和增量标记来优化这种情况)。
  • 标记和清除的性能问题:每次垃圾回收时,都会遍历整个内存空间的对象,时间复杂度与堆中的对象数量成正比,随着对象数量的增加,性能可能下降。

现代垃圾回收器通过各种优化技术,如分代回收、增量回收和并发回收,来减少标记-清除算法带来的性能问题。

  1. 分代回收:将对象按生命周期分为“新生代”和“老生代”。新生代对象往往生命周期短,因此垃圾回收会频繁检查新生代中的对象。而老生代对象生命周期长,垃圾回收检查的频率较低。
  2. 增量回收:将一次完整的垃圾回收拆分成多个小步骤,每次只进行一部分回收工作,以减少“暂停-世界”的时间。
  3. 并发回收:在现代浏览器中,垃圾回收器可以利用多核 CPU,并发处理标记和清除的工作,进一步减少对主线程的影响。

例子:

假设我们有以下代码:

function outer() {
    let obj1 = { name: "object 1" };
    let obj2 = { name: "object 2" };

    function inner() {
        console.log(obj1);
    }

    return inner;
}

const closure = outer();

在这个例子中:

  • obj1outer 函数的局部变量,它被 inner 函数引用。因此,尽管 outer 执行结束后,obj1 没有从根对象直接引用,但由于它被闭包引用,所以 obj1 仍然是可达的,不会被回收。
  • obj2 没有被 inner 函数引用,也没有其他地方引用它,因此 obj2outer 函数执行结束后,将在下一次垃圾回收时被回收。

标记-清除算法的回收流程将标记 obj1 为可达对象,而 obj2 则会被清除。

1)什么时候发生垃圾回收?

浏览器通常会在以下几个场景执行垃圾回收:

  • 内存占用达到某个阈值时。
  • 程序进入空闲阶段(例如 JavaScript 代码执行完毕后,等待用户输入或其他事件时)。
  • 显示的浏览器空闲时段(Idle Periods)。

2)内存泄漏的常见原因

尽管浏览器有垃圾回收机制,但仍可能发生内存泄漏,主要原因包括:

  1. 全局变量未释放:全局变量会一直存在于全局执行上下文,难以回收。
  2. 被遗忘的定时器或事件监听器:未清除的 setIntervalsetTimeout 或未移除的事件监听器,仍然引用着对象,导致对象无法被回收。
  3. 闭包:闭包会保持对外部变量的引用,如果这些变量不再需要,但依旧被引用,内存就无法释放。

17、this

函数的this完全取决于调用时的上下文决定。

箭头函数则比较特殊,是根据定义时的上下文。这意味着箭头函数不会创建自己的 this,它会捕获并继承外部作用域的 this 值。

下面是 Babel 转箭头函数产生的 ES5 代码,就能清楚地说明this的指向。箭头函数里面根本没有自己的this,而是引用外层的this

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

看下以下代码的输出:

var name = 'window';
var student = {
    name: '若川',
    doSth: function(){
        // var self = this;
        var arrowDoSth = () => {
            // console.log(self.name);
            console.log(this.name);
        }
        arrowDoSth();
    },
    arrowDoSth2: () => {
        console.log(this.name);
    }
}
student.doSth(); // '若川'
student.arrowDoSth2(); // 'window'

其实就是相当于箭头函数外的this是缓存的该箭头函数上层的普通函数this。如果没有普通函数,则是全局对象(浏览器中则是window)。 

使用箭头函数有几个需要注意的点:

  • 不能用作构造函数,无 new 关键字
  • 不具备 arguments:箭头函数没有自己的 arguments 对象,如果要用,可以用 rest 参数代替(即在箭头函数定义时(...arg))
  • 无法通过callapplybind绑定箭头函数的this(它自身没有this)
function Person(name) {
  this.name = name;
}

const person1 = new Person('Alice');
const person2 = Person('Bob');

console.log(person1.name);
console.log(person2);
console.log(name);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2209400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络第1章(概述)万字笔记详细版

1.1、计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成为一种重要的信息服务基础设施计算机网络已经像水&#xff0c;电&#xff0c;煤气这些基础设施一样&#xff0c;成为我们生活中不可或缺的一部分 我国互联网发展状况 中国互联网络信息中心CNNIC 1.2、…

万字详解AI实践,零手写编码用AI完成开发 + 数据清洗 + 数据处理 的每日新闻推荐,带你快速成为AI大神

用AIdify完成前后端开发数据处理和数据清洗。 引言数据获取和数据处理dify构建workflow进行数据清洗前端页面构建和前后端交互总结 引言 AI时代对开发人员的加强是非常明显的&#xff0c;一个开发人员可以依靠AI横跨数个自己不熟悉的领域包括前后端、算法等。让我们来做个实践…

模板和静态文件

模板和静态文件 1、templates模板2、静态文件2.1、static目录2.2、引用静态文件 1、templates模板 "templates"目录用于存放模板文件&#xff0c;通常是用于动态生成页面的文件。 在app01目录下创建templates文件夹&#xff0c;html文件均保存在templates中 在urls.p…

Linux的hadoop集群部署

1.hadoop是一个分布式系统基础架构,主要解决海量数据额度存储与海量数据的分析计算问题 hdfs提供存储能力,yarn提供资源管理能力,MapReduce提供计算能力 2.安装 一:调整虚拟机内存,4G即可 二:下载安装包 网址:https://mirrors.aliyun.com/apache/hadoop/common/hadoop-3.4.0/…

【文心智能体 AI大师工坊】『​​​​​​​人间夸夸机』情感类智能体开发调优全过程详解

&#x1f680;『人间夸夸机』点击前往体验&#xff1a;https://snhoio.smartapps.baidu.com/?_swebScene3611000000000000 最近参加了百度文心智能体平台AI大师工坊&#x1f389;活动&#xff0c;在这个活动中&#xff0c;我利用文心平台提供的各种插件、大模型等工具&#xf…

Linux内核USB3.0驱动框架分析--USB主机控制器hcd驱动分析

一&#xff0c;概述 usb主机控制器驱动一般以platform的形式将驱动注册进内核&#xff0c;&#xff0c;因此我们需要从前面一篇文章的框图说起。主要分析下图中橙色部分的内容。 二&#xff0c;usb主机控制器相关函数 2.1 usb_create_hcd 我们来看一下usb_create_hcd函数&a…

如何成为 Rust 核心贡献者?Rust 开发的核​​心是什么?Rust 重要技术专家揭秘

10 月 17 - 18日&#xff0c;由 GOSIM 开源创新汇主办、CSDN 承办的 GOSIM CHINA 2024 将在北京盛大启幕。作为 GOSIM 开源年度大会的第三届盛会&#xff0c;本次活动邀请了 60 多位国际开源专家&#xff0c;汇聚了来自全球百余家顶尖科技企业、知名高校及开源社区的技术大咖、…

图像增强论文精读笔记-Kindling the Darkness: A Practical Low-light Image Enhancer(KinD)

1. 论文基本信息 论文标题&#xff1a;Kindling the Darkness: A Practical Low-light Image Enhancer 作者&#xff1a;Yonghua Zhang等 发表时间和期刊&#xff1a;2019&#xff1b;ACM MM 论文链接&#xff1a;https://arxiv.org/abs/1905.04161 2. 研究背景和动机 现有…

C++入门基础知识110—【关于C++ if...else 语句】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C if...else 语句的相关内容&#xff01…

SAP SD学习笔记09 - 受注传票中的不完全Log 和 Business Partner(取引先机能)

好久没写SD了&#xff0c;今天继续写。 上一章讲了SD的如下知识 - SD的售前的流程&#xff08;引合和見積&#xff08;询价和报价&#xff09;&#xff09; - 数据流的概念&#xff0c;主要就是后传票可以参照前传票&#xff0c;以实现数据的流动&#xff0c;减少输入 - Co…

PHP游泳馆会员管理系统-计算机毕业设计源码86627

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于游泳馆会员管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了游泳馆会员管理系统&#xff0c;它彻底改…

二分查找法 ← Python实现

【二分查找法】 ★ 二分查找&#xff0c;是一种效率较高的查找方法。但是&#xff0c;二分查找要求元素按关键字有序排列。 ★ 二分查找每一次查找都使查找范围缩小一半&#xff0c;与顺序查找相比&#xff0c;很显然会提高查找效率。为了标记查找过程中每一次的查找区间&#…

自动猫砂盆真的有必要吗?买自动猫砂盆不看这四点小心害死猫。

现在越来越多铲屎官选择购买自动猫砂盆来代替自己给猫咪铲屎&#xff0c;可是自动猫砂盆真的有必要吗&#xff1f;要知道&#xff0c;在现在忙碌的生活中&#xff0c;有很多人因为工作上的忙碌而不小心忽视了猫咪&#xff0c;猫咪的猫砂盆堆满粪便&#xff0c;要知道猫砂盆一天…

windows如何设置右键新建文档

1. windows如何设置右键新建文档 文章目录 1. windows如何设置右键新建文档1.1. 注意注意注意1.2. 参考资料1.3. 注册列表中各项的意思1.4. 右键新建文档1.4.1. 新建文件夹快捷键1.4.2. 新建txt文件快捷键1.4.3. 新建Word文档快捷键1.4.4. 新建PowerPoint文档快捷键1.4.5. 新建…

Linux基础(五):linux目录配置

1.Linux系统目录 不同的Linux发布版本的目录都是类似的&#xff0c;这是因为Linux系统的目录配置有一个大概的标准——Filesystem Hierarchy Standard&#xff08; FHS&#xff09;。FHS规定了目录有4种交互形态&#xff1a; ①可分享的&#xff1a; 可以分享给其他系统挂载使…

【优选算法】(第三十五篇)

目录 验证栈序列&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 N叉树的层序遍历&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 验证栈序列&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#xff08;L…

校园网网页认证设备限制环境下基于OpenWRT的路由器选型与解决方案

校园网环境下基于OpenWRT的路由器选型与解决方案 网页认证(锐捷认证)解除校园网设备限制,路由器选型和解决方案 openwrt 我们学校校园网一个账号只能登录两台设备&#xff0c;多了直接就退出联网状态&#xff0c;然后校园网是基于锐捷认证进行认证的&#xff0c;然后通过ment…

【closerAI ComfyUI】爹妈都认不出的美女模糊照片,这个高清放大模型竟然能还原出来!这个AI模型我给满分

兄弟们&#xff0c;太离谱了&#xff0c;大家都知道FLUX模型的牛逼&#xff0c;现在基于FLUX的生态越发成熟&#xff0c;但一张模糊到五官都不能辨认的图片&#xff0c;通过AI模型的计算&#xff0c;竟然能还原出来。当然&#xff0c;这里我们的测试是过于极端。一般模糊一点的…

SpringCloud网关聚合knife4j方案

微服务开发中想将Spring-Cloud-Gateway网关聚合knife4j&#xff0c;形成一个统一入口方便查阅的开发辅助接口文档&#xff0c;并且将Swagger抽取成一个公共模块&#xff0c;那么我们可以参考以下的做法 约定&#xff1a; Java Version&#xff1a;11.0.24 Spring Boot&#xff…

一探究竟:全能型人体存在传感器如何革新全屋智能生活

如今&#xff0c;全屋智能家居已蔚然成风&#xff0c;亲历过个性化智能家居配置的用户无不深知传感器在其联动逻辑中扮演的关键角色。市场上主流的传感器类型多为移动监测型&#xff0c;此类设备通过探测区域内是否存在人员活动来触发相应的自动化操作。尽管它们在众多应用场景…