前端面试题JS篇

142 阅读43分钟

一、如何准确判断一个变量是不是数组?

标准回答:

如果要准确判断一个变量是不是数组,可以使用以下几种方法。

首先,可以使用Array.isArray()方法。这是一种最为直接和准确的方式,例如,如果有一个变量data,要判断它是不是数组,可以这样写代码Array.isArray(data),如果返回true,那就说明这个变量是数组,否则不是。

其次,还可以通过比较变量的构造函数来判断。数组的构造函数是Array,所以可以这样判断,data.constructor === Array,如果这个表达式的结果为true,那么这个变量就是数组。

另外,也可以使用Object.prototype.toString.call()方法。这个方法可以返回一个表示对象类型的字符串。对于数组来说,这个方法会返回[object Array]。比如可以这样写代码Object.prototype.toString.call(data) === '[object Array]',如果结果为true,则说明变量是数组。

综上所述,通过Array.isArray()方法、比较构造函数以及使用Object.prototype.toString.call()方法都可以准确判断一个变量是不是数组。

白话回答:

要准确判断一个变量是不是数组,有这么几个办法。一是用 Array.isArray () 方法,像 Array.isArray (data) ,返回 true 就是数组,不是就不是。二是看变量的构造函数,数组的构造函数是 Array ,所以 data.constructor === Array ,结果为 true 就是数组。三是用 Object.prototype.toString.call () 方法,数组会返回 [object Array] ,像 Object.prototype.toString.call (data) === '[object Array]' ,结果为 true 就是数组。总之,这几个方法都能判断。

二、Class 的原型本质,怎么理解?

标准回答:

对于 Class 的原型本质,可以从几个方面来理解。

首先,在 JavaScript 中,Class 实际上是一种语法糖,它的底层实现是基于原型继承的。这意味着当我们创建一个类时,实际上是在定义一个函数,这个函数的原型对象包含了类的方法和属性。

比如说,当我们定义一个类,然后创建一个该类的实例对象时,这个实例对象会通过原型链查找的方式访问到类的原型上的方法和属性。这就体现了原型本质在其中的作用。

其次,Class 的原型本质也使得代码的继承更加清晰和易于理解。我们可以通过 extends 关键字来实现类的继承,子类会继承父类的原型上的方法和属性。这样在代码的组织和维护上更加方便,也符合面向对象编程的一些基本原则。

举个例子,如果我们有一个父类 Animal 和一个子类 Dog,Dog 类通过 extends 继承自 Animal 类,那么 Dog 的实例对象不仅可以访问自己定义的方法和属性,还可以通过原型链访问到 Animal 类原型上的方法和属性。

总之,理解 Class 的原型本质对于深入掌握 JavaScript 的面向对象编程特性非常重要,它有助于我们更好地组织和管理代码,提高代码的可维护性和可扩展性。

白话回答:

Class 在 JavaScript 里其实是语法糖,底层靠原型继承。创建类就是定义个函数,函数的原型对象有类的方法和属性。创建类的实例对象能通过原型链找到这些。而且 Class 的原型本质让代码继承更清楚好懂,用 extends 就能继承,子类能通过原型链用父类的方法和属性。比如父类 Animal 和子类 Dog,Dog 的实例对象能这样访问。总之,理解这个对写好面向对象的 JavaScript 代码很重要,能让代码更好管理和扩展。

三、什么是作用域?什么是自由变量?

标准回答:

首先来说作用域。作用域可以理解为在程序中定义变量的区域,以及在这个区域中变量的可见性和可访问性。它决定了变量在程序的哪些部分可以被使用。

打个比方,作用域就像是一个有边界的空间,在这个空间里定义的变量只能在这个特定的范围内被找到和使用。比如在一个函数内部定义的变量,它的作用域就局限在这个函数内部。只有在这个函数执行的时候,这个变量才存在并且可以被访问。而在函数外部,是无法直接访问这个变量的。

再来说自由变量。自由变量是指在一个函数内部使用的,但不是在这个函数内部定义的变量。

比如说,有一个外部函数定义了一个变量,然后在另一个内部函数中使用了这个变量。对于这个内部函数来说,这个变量就是自由变量。内部函数可以通过作用域链去查找并访问这个自由变量。作用域链就像是一个链条,从当前函数的作用域开始,一层一层地向外查找,直到找到这个自由变量或者到达全局作用域。

总之,作用域决定了变量的可见范围,而自由变量是在特定函数中使用但不是在该函数内部定义的变量,通过作用域链来进行访问。

白话回答:

作用域就是在程序里定义变量的范围,决定变量能在哪用。比如在一个函数里定义的变量,就在这个函数里能用,外面用不了。自由变量是在一个函数里用了但不是在这个函数里定义的变量。比如外面的函数定义了,里面的函数用,对于里面的函数这就是自由变量,通过作用域链能找到并使用它。总之,作用域管变量在哪能用,自由变量靠作用域链访问。

四、什么是闭包?闭包会用在哪里(举例说明)?

标准回答:

首先来说闭包。闭包是指有权访问另一个函数作用域中的变量的函数。简单来讲,当一个内部函数被返回并在外部环境中被使用时,这个内部函数就形成了一个闭包,因为它可以访问其外部函数的变量。

闭包在很多地方都有应用。比如在实现模块化编程的时候可以用到闭包。假设我们要创建一个模块,这个模块需要一些私有变量,不希望被外部直接访问和修改。我们可以通过闭包来实现。

例如,我们可以创建一个函数,在这个函数内部定义一些私有变量和函数,然后返回一个对象,这个对象包含一些可以公开访问的方法,这些方法可以访问内部的私有变量。这样就实现了一种封装,外部只能通过公开的方法来与内部的私有变量进行交互。

再比如,在事件处理中也会用到闭包。当我们为多个按钮添加点击事件处理程序时,闭包可以帮助我们记住每个按钮的特定状态。

举个例子,有一组按钮,我们希望点击每个按钮时都能显示出它的编号。我们可以使用闭包来实现,在循环中为每个按钮添加点击事件处理程序,这个处理程序可以访问循环变量,从而记住每个按钮的编号。

总之,闭包是一种强大的编程概念,可以用于实现模块化编程、事件处理等多种场景。

白话回答:

闭包就是能访问别的函数作用域里变量的函数。比如说一个内部函数被返回在外部用,它就能访问外部函数的变量,这就是闭包。 闭包用处多。像做模块化编程,比如创建个模块,里面有私有变量,不想让外面随便改,就能用闭包。比如弄个函数,里面有私有变量和函数,返回个对象,对象里有能公开访问的方法,能操作私有变量,实现封装。还有事件处理,比如给一堆按钮加点击事件,闭包能记住每个按钮的状态。比如有一组按钮,点每个都能显示编号,用闭包在循环里给每个按钮加处理程序,能访问循环变量记住编号。总之,闭包用处大,像模块化编程、事件处理都能用。

五、this 有几种赋值情况?

标准回答:

在 JavaScript 中,this 的赋值情况主要有以下几种。

首先,在全局作用域中,this 指向全局对象,在浏览器环境中通常是 window 对象。比如在全局直接定义一个变量赋值或者执行一个函数时,函数内部如果使用 this,在没有特殊处理的情况下,this 指向的就是全局对象。

其次,当函数作为对象的方法被调用时,this 指向调用这个方法的对象。例如,有一个对象 obj ,里面有一个方法 method,当通过 obj.method () 这样的方式调用这个方法时,这个方法内部的 this 就指向 obj 这个对象。

再者,使用构造函数创建对象时,在构造函数内部,this 指向正在被创建的新对象。构造函数通过 new 关键字来调用,在构造函数内部可以使用 this 来给新对象添加属性和方法。

还有,在使用函数的 call、apply 和 bind 方法时,可以显式地指定 this 的值。比如,有一个函数 func,通过 func.call (obj) 这样的方式调用时,可以把 this 强制指定为 obj 对象。

总之,在 JavaScript 中 this 的赋值情况主要有在全局作用域中指向全局对象、作为对象方法被调用时指向调用对象、在构造函数中指向新创建的对象以及通过 call、apply 和 bind 方法显式指定这几种情况。

白话回答:

在 JavaScript 里,this 大概有这么几种赋值的情况。

第一种,在全局范围里,this 一般就指着浏览器里的 window 对象。比如在全局直接弄个变量或者执行个函数,里面用 this ,没特殊处理的话,this 就是指全局的那个东西。

第二种,要是函数是某个对象的方法,被调用的时候,this 就指着调用这个方法的那个对象。比如有个对象 obj ,里面有个方法 method ,通过 obj.method () 这么调用,那 method 方法里的 this 就指着 obj 。

第三种,用构造函数创建对象时,在构造函数里面,this 指着正在创建的新对象。构造函数得用 new 调用,在里面能用 this 给新对象加东西。

第四种,用函数的 call 、apply 和 bind 方法的时候,可以自己指定 this 的值。比如有个函数 func ,通过 func.call (obj) 这么调用,就能把 this 弄成 obj 。

反正,JavaScript 里 this 主要就这几种赋值情况。

六、this 的不同应用场景,如何取值?

标准回答:

在 JavaScript 中,this 的取值因不同应用场景而变化。

首先,在普通函数直接调用时,this 通常指向全局对象,比如在浏览器环境下就是 window 对象。这是因为在全局环境中调用函数,函数内部的 this 会默认绑定到全局对象上。

当函数作为对象的方法被调用时,this 就指向调用这个方法的对象。因为在这种情况下,函数是与特定的对象相关联执行的,所以 this 会取这个对象的值。

在构造函数的场景中,this 指向正在被创建的新对象。构造函数的目的就是创建新对象并初始化其属性和方法,在构造函数内部,this 被用来为新对象添加各种特性。

而对于使用 call、apply 和 bind 方法的场景,我们可以显式地指定 this 的值。这让我们在需要动态改变函数执行上下文时非常有用,可以将特定的对象作为 this 的值传递进去,从而控制函数内部的 this 指向。

综上所述,在不同的应用场景下,我们需要根据具体情况来判断 this 的取值,以确保正确地使用和理解 this 在 JavaScript 中的行为。

白话回答:

在 JavaScript 里,this 的值在不同场景不一样。 要是普通函数直接调用,一般 this 就指着浏览器里的 window 对象。因为在全局环境调函数,函数里的 this 默认绑到全局对象上。 要是函数是对象的方法,被调用的时候,this 就指着调用这个方法的那个对象。因为这时候函数跟特定对象一起用,所以 this 就是那个对象。 在构造函数的时候,this 指着正在创建的新对象。构造函数就是为了弄新对象和初始化它的东西,在里面用 this 给新对象加特性。 用 call、apply 和 bind 方法的时候,可以自己指定 this 的值。这在想改函数执行的上下文时很有用,把特定对象当 this 的值传进去,就能控制函数里的 this 指向啥。 总之,不同场景得看情况判断 this 的值,这样才能用好和理解 this 在 JavaScript 里的行为。

七、同步和异步有何不同?

标准回答:

同步和异步主要有以下几方面的不同。

首先从执行方式来看,同步是指在一个任务执行过程中,必须等待当前任务完成后,才能继续执行下一个任务。比如说,我们在程序中调用一个同步函数,那么程序会一直停留在这个函数调用处,直到这个函数执行完毕并返回结果,然后才会继续执行后续的代码。

而异步则是在一个任务执行的同时,不必等待这个任务完成,可以继续执行其他任务。当异步任务完成时,会通过某种方式通知程序,比如回调函数或者事件触发。

从对程序流程的影响来说,同步的方式会使程序的执行流程比较直观,按照代码的先后顺序依次执行。但这也可能导致程序在执行某些耗时较长的任务时出现阻塞,影响程序的响应速度和性能。

异步的方式则使得程序可以在等待耗时任务完成的同时,继续处理其他任务,提高了程序的效率和响应速度。但是异步的程序流程相对复杂,需要通过回调函数等方式来处理异步任务的结果,这可能会导致代码的可读性和可维护性下降。

举个例子,假设我们有一个从服务器获取数据的操作。如果使用同步的方式,程序会在发出请求后一直等待服务器返回数据,在这个过程中程序无法进行其他操作。而如果使用异步的方式,程序在发出请求后可以继续执行其他任务,当服务器返回数据时,通过回调函数来处理数据。

总之,同步和异步在执行方式和对程序流程的影响上都有很大的不同,在实际编程中需要根据具体的需求来选择合适的方式。

白话回答:

同步和异步不一样。同步就是做一个任务时,得等这个任务完了才能做下一个任务。比如程序里用个同步函数,就得等这个函数执行完有结果了,才接着走后面的代码。异步呢,做一个任务的时候,不用等这个任务完,可以接着做别的任务。等异步任务完了,会用回调函数或者事件触发来通知程序。同步的方式程序执行流程直观,按代码顺序走,但做耗时长的任务会阻塞程序,影响性能。异步能让程序在等耗时任务时干别的,提高效率,但程序流程复杂,用回调函数处理结果,可能让代码不好读不好维护。比如从服务器拿数据,同步的话就一直等数据回来,这时候程序不能干别的。异步的话,发请求后能接着干别的,等数据回来用回调函数处理。总之,同步和异步执行方式和对程序流程影响不同,编程时得按需求选。

八、异步的应用场景有哪些?

标准回答:

异步在编程中有很多重要的应用场景。

首先,在网络请求方面,当我们从服务器获取数据时,比如进行 API 调用,由于网络延迟的不确定性,这个过程可能会比较耗时。如果使用同步方式,程序会一直等待服务器响应,在此期间无法进行其他操作,这会导致程序卡顿,用户体验很差。而采用异步方式,程序在发出请求后可以继续处理其他任务,当服务器响应回来时,通过回调函数或者 Promise 等机制来处理数据,这样可以保持程序的流畅性和响应性。

其次,在文件读写操作中,尤其是处理大文件时,同步读取或写入文件会使程序长时间阻塞。而异步文件操作可以在文件读写的同时,让程序继续执行其他任务,提高程序的效率。例如在一个图片处理软件中,当软件加载大尺寸图片文件时,可以使用异步方式,在文件加载的过程中,程序可以显示加载进度条或者让用户进行其他操作,而不是一直处于等待状态。

再者,在用户界面交互中,异步也非常关键。比如当用户点击一个按钮触发一个复杂的计算或者耗时的操作时,如果使用同步方式,界面会冻结直到操作完成,这会让用户感到困惑和不满。通过异步处理,可以在后台进行计算,同时保持界面的响应,给用户提供更好的体验。例如在网页开发中,当用户提交表单后,可以使用异步方式将数据发送到服务器,同时显示一个加载动画,让用户知道操作正在进行,而不是让页面卡住。

另外,在多任务处理的场景下,异步也能发挥很大作用。比如在一个数据处理系统中,可能需要同时处理多个任务,如从不同的数据源获取数据、进行数据计算和存储等。使用异步方式可以让这些任务同时进行,提高系统的整体效率。

总之,异步在网络请求、文件操作、用户界面交互和多任务处理等场景中都有广泛的应用,可以提高程序的性能和用户体验。

白话回答:

异步有很多用的地方。网络请求的时候,从服务器拿数据,网络延迟不确定,用同步的话程序得一直等,啥也干不了,用户体验差。用异步,发请求后能接着干别的,等服务器回应了用回调或者 Promise 处理数据,程序更流畅。文件读写也是,处理大文件同步会让程序堵住,异步能一边读写一边干别的,提高效率。像图片软件加载大图片,用异步能显示进度条或者让用户干别的。用户界面交互也得用异步,比如点按钮触发复杂操作,同步会让界面冻住,用户不爽。异步能在后台算,界面还能响应。网页开发里提交表单用异步,能显示加载动画。多任务处理的时候,异步能让多个任务同时干,提高效率。总之,异步在网络请求、文件操作、界面交互和多任务处理这些场景都很有用,能提高性能和用户体验。

九、typeof 能判断哪些类型?

标准回答:

在 JavaScript 中,typeof 运算符可以判断以下几种类型:

首先是基本数据类型。对于数值类型,不管是整数还是浮点数,typeof 返回 'number'。例如,typeof 5 的结果是 'number',typeof 3.14 的结果也是 'number'。

对于字符串类型,typeof 返回'string'。比如 typeof 'Hello World' 的结果就是'string'。 对于布尔类型,typeof 返回 'boolean'。像 typeof true 和 typeof false 都会得到 'boolean' 这个结果。

对于 undefined 类型,typeof 返回 'undefined'。当一个变量未被初始化或者显式地被赋值为 undefined 时,typeof 这个变量会得到 'undefined'。

还有一种特殊的基本数据类型 null,typeof null 的结果比较特殊,会返回 'object',这是一个历史遗留问题。

除了基本数据类型,typeof 对于函数类型会返回 'function'。例如,如果有一个函数 function test (){},typeof test 的结果是 'function'。

对于对象类型,包括普通对象、数组、正则表达式等,typeof 都会返回 'object'。所以仅用 typeof 不能准确区分不同种类的对象。

总之,typeof 可以判断数值、字符串、布尔、undefined、函数以及对象等类型,但对于对象类型的区分不够精细。

白话回答:

在 JavaScript 里,typeof 运算符能判断这些类型: 基本数据类型里,数字不管整数还是小数,typeof 都说是 'number'。字符串的话,typeof 说是'string'。布尔类型,typeof 说是 'boolean'。没初始化或者明确赋值为 undefined 的变量,typeof 说是 'undefined'。特殊的是 null ,typeof 说是 'object',这是个老问题。 函数的话,typeof 说是 'function'。 对象类型,像普通对象、数组、正则表达式这些,typeof 都说是 'object',所以光用 typeof 分不太清具体是哪种对象。 反正,typeof 能判断数、字符串、布尔、undefined、函数和对象这些类型,就是对象这块分得不太细。

十、何时使用 === 何时使用 == ?

标准回答:

在 JavaScript 中,‘===’被称为严格相等运算符,而‘==’被称为相等运算符。

一般来说,应该优先使用‘===’(严格相等运算符)。这是因为‘===’在进行比较时,不仅比较值是否相等,还会比较值的类型是否相同。例如,5 === '5' 会返回 false,因为一个是数值类型,一个是字符串类型。这样可以避免一些由于类型自动转换而导致的意外结果,使代码更加可靠和可预测。

而‘==’在进行比较时,会进行类型转换后再比较值是否相等。例如,5 == '5' 会返回 true,因为‘==’会将字符串 '5' 自动转换为数值 5 后进行比较。虽然在某些特定情况下可能会比较方便,但这种自动类型转换可能会导致难以察觉的错误,尤其是在复杂的代码逻辑中。

通常在以下情况可能会考虑使用‘==’:

当你明确知道可能会有不同类型的值进行比较,并且你希望进行类型转换后的比较,同时你对这种转换的结果有清晰的预期和理解。比如在处理一些旧代码或者与某些特定的库进行交互时,可能会遇到不得不使用‘==’的情况。但这种情况应该尽量避免,除非有充分的理由。

总之,为了保证代码的准确性和可维护性,应该尽可能地使用‘===’严格相等运算符,而谨慎使用‘==’相等运算符。

白话回答:

在 JavaScript 里,“===” 是严格相等运算符,“==” 是相等运算符。一般优先用 “===”,因为它不光比值是不是一样,还比类型是不是一样,像 5 === '5' 就是 false。这样能避免因为类型自动转换带来意外结果,让代码更可靠可预测。“==” 会先转换类型再比值,像 5 == '5' 就是 true。虽然有时候可能方便点,但容易出问题。只有在明确知道要比不同类型的值,而且清楚转换后的结果,比如处理旧代码或者跟特定库交互时才考虑用 “==”,但最好少用。总之,为了代码准确好维护,尽量用 “===”,谨慎用 “==”。

十一、请描述 Event Loop(事件循环/事件轮询)的机制,可画图

标准回答:

Event Loop,也就是事件循环,是 JavaScript 运行时环境中非常重要的一个机制。

首先,JavaScript 是单线程的语言,这意味着它在同一时间只能执行一个任务。Event Loop 的作用就是协调和管理各种任务的执行顺序。

它主要包含几个关键的部分。一个是任务队列,分为宏任务队列和微任务队列。宏任务比如 setTimeout、setInterval、Ajax 请求等,微任务比如 Promise 的 then、catch 和 finally 方法等。

当 JavaScript 开始执行时,它会先执行同步任务,也就是那些按照代码顺序依次执行的任务。当遇到一个宏任务时,它会将这个宏任务放入宏任务队列中等待执行。当同步任务执行完后,JavaScript 引擎会检查微任务队列,如果微任务队列中有任务,就会依次执行微任务队列中的所有任务。

当微任务队列清空后,JavaScript 引擎会从宏任务队列中取出一个任务来执行,执行完这个宏任务后,又会检查微任务队列,如此循环往复。

打个比方,想象 Event Loop 就像一个忙碌的服务员在餐厅里工作。同步任务就像是顾客直接走到服务员面前点餐,服务员会立即处理这些同步任务。而宏任务就像是顾客打电话预订餐位,服务员会把这个预订任务记录下来,等手头的事情忙完后再去处理。微任务则像是顾客在服务员处理其他事情的时候突然有一个紧急的小要求,服务员会优先处理这些微任务,然后再继续处理之前被中断的任务。

总之,Event Loop 通过这种不断循环检查宏任务队列和微任务队列的方式,来确保 JavaScript 程序能够有条不紊地执行各种任务。

白话回答:

Event Loop 就是 JavaScript 里管任务执行顺序的机制。JavaScript 是单线程的,同一时间只能干一个事儿。Event Loop 有任务队列,分宏任务队列和微任务队列。宏任务像 setTimeout、setInterval、Ajax 请求这些,微任务像 Promise 的 then、catch、finally 这些方法。JavaScript 开始执行的时候先干同步任务,就是按代码顺序来的任务。碰到宏任务就放宏任务队列等着。同步任务干完了,就看微任务队列,有任务就都执行了。微任务队列空了,就从宏任务队列拿一个任务出来干,干完又看微任务队列,这么一直循环。 打个比方,Event Loop 就像餐厅服务员。同步任务是顾客直接找服务员点餐,服务员马上处理。宏任务是顾客打电话订餐位,服务员记下来等会儿干。微任务是顾客突然有个紧急小要求,服务员优先处理微任务,再接着干之前的事儿。 总之,Event Loop 就是靠不断检查宏任务队列和微任务队列,让 JavaScript 程序能顺利执行各种任务。

十二、什么是宏任务和微任务,两者有什么区别?

标准回答:

首先来说宏任务和微任务。

宏任务是可以理解为较大的、相对耗时的任务。常见的宏任务有 setTimeout、setInterval、Ajax 请求、DOM 事件监听等。这些任务通常会被安排在一个特定的时间点或者在特定的事件触发后执行。

微任务则是相对较小、执行时间较短的任务。主要包括 Promise 的 then、catch 和 finally 方法,以及 process.nextTick(在 Node.js 环境中)等。

两者的区别主要有以下几点:

第一,执行时机不同。当 JavaScript 执行栈中的同步任务执行完毕后,会先检查并执行所有微任务队列中的任务,当微任务队列清空后,才会从宏任务队列中取出一个宏任务来执行。也就是说微任务的执行优先级高于宏任务。

第二,数量和复杂度。一般来说,宏任务相对较少且比较明确,比如一个页面中可能只有几个 setTimeout 或者几个 DOM 事件。而微任务可能会因为代码中的 Promise 链等情况产生较多数量,并且微任务的产生和执行相对更加灵活复杂。

第三,对程序性能的影响。由于微任务执行迅速且优先执行,所以在一些对性能要求较高的场景下,可以使用微任务来尽快处理一些关键的、需要及时响应的任务,避免长时间等待宏任务的执行而导致程序卡顿或者响应不及时。而宏任务通常用于处理一些不那么紧急的、可以在后台进行的任务。

举个例子,假设我们有一段代码,里面有一个 setTimeout 和一个 Promise。当这段代码执行时,同步任务先执行,然后遇到 Promise,会将 Promise 的 then 方法中的任务放入微任务队列。接着遇到 setTimeout,会将其放入宏任务队列。当同步任务执行完后,会先执行微任务队列中的任务,然后才会去执行宏任务队列中的 setTimeout 任务。

总之,宏任务和微任务在 JavaScript 的事件循环中扮演着不同的角色,了解它们的区别有助于我们更好地控制程序的执行流程和性能。

白话回答:

宏任务呢,就是比较大、花时间长的那些事儿。像 setTimeout、setInterval 啊,还有 Ajax 请求、DOM 事件监听这些都算宏任务。它们得在特定的时候或者特定事件发生了才去做。 微任务呢,就是小一点、很快能做完的事儿。像 Promise 的 then、catch、finally 这些方法,还有在 Node.js 环境里的 process.nextTick 都是微任务。 它们的区别有这么几个地方: 第一,啥时候干不一样。JavaScript 先把同步的事儿都弄完了,接着就会先去看看微任务队列,把里面的任务都干完了,才会从宏任务队列里拿一个任务来做。所以微任务干得更早,优先级高。 第二,数量和麻烦程度不一样。宏任务一般不多,也比较清楚,比如一个网页里可能就几个 setTimeout 或者几个 DOM 事件。但微任务可能因为代码里有好多 Promise 连起来,数量就会比较多,而且微任务产生和干起来更灵活更复杂。 第三,对程序好不好用的影响不一样。微任务干得快还先干,所以在要求程序反应快的时候,可以用微任务赶紧把关键的、得马上有反应的事儿做了,不然光等着宏任务干,程序可能就卡顿或者反应慢。宏任务一般是干那些不那么急的事儿,可以在后台慢慢弄。 举个例子,有段代码,里面有个 setTimeout 和一个 Promise。代码跑起来的时候,先干同步的事儿,碰到 Promise 呢,就把 then 方法里的任务放进微任务队列,碰到 setTimeout 就放进宏任务队列。等同步的事儿都干完了,就先去干微任务队列里的任务,然后才去干宏任务队列里的 setTimeout 任务。 反正呢,宏任务和微任务在 JavaScript 的事件循环里作用不一样,知道它们的区别能让我们更好地控制程序咋跑还有好不好用。

十三、Promise 有哪三种状态?如何变化?

标准回答:

Promise 有三种状态,分别是 pending(等待)、fulfilled(已完成,也称为 resolved)和 rejected(已拒绝)。

一开始,Promise 创建时处于 pending 状态,表示异步操作还在进行中。

如果异步操作成功完成,Promise 的状态会从 pending 转变为 fulfilled(fulfilled),并且会携带一个成功的值。

要是异步操作失败了,Promise 的状态就会从 pending 转变为 rejected,同时会携带一个失败的原因。

这种状态的变化是不可逆的,一旦 Promise 从 pending 转变为 fulfilled 或者 rejected,就不会再发生变化。

比如说,我们发起一个网络请求,如果请求成功获取到数据,那么对应的 Promise 状态就从 pending 变为 fulfilled,并将获取到的数据作为成功的值。要是网络请求失败了,Promise 状态就从 pending 变为 rejected,并将错误信息作为失败的原因。

总之,Promise 的三种状态及其变化规则为处理异步操作的结果提供了一种清晰和可靠的方式。

白话回答:

Promise 有三种状态,一个是 pending ,就是等着,这时候异步操作还在进行。还有 fulfilled,就是成功完成了,也叫 resolved 。再有就是 rejected ,就是失败拒绝了。 一开始 Promise 都是 pending 状态。要是异步操作成功了,状态就从 pending 变成 fulfilled,还会带着成功的结果。要是失败了,状态就从 pending 变成 rejected ,也会带着失败的原因。 而且这状态变了就不能再变回去,一旦变成了 fulfilled 或者 rejected ,就定住了。 比如说发个网络请求,成功拿到数据,Promise 就从 pending 变成了 fulfilled,数据就是成功的值。要是请求失败,Promise 就从 pending 变成 rejected ,错误信息就是失败原因。 反正,Promise 这三种状态和变化规则,让处理异步操作结果更清楚可靠。

十四、DOM 的本质是什么?

标准回答:

DOM,即文档对象模型(Document Object Model),其本质是一种将网页文档以树形结构进行表示和操作的编程接口。

可以把网页想象成一棵倒立的树,HTML 文档中的各种元素,比如html、body、div、p等等,都被看作是这棵树上的节点。每个节点都有自己的属性和方法,通过 DOM,我们可以使用编程语言来访问、修改这些节点的属性、内容或者结构。

DOM 为 JavaScript 等编程语言提供了一种与网页文档进行交互的标准方式。它使得开发者能够动态地修改网页的内容、样式和结构,响应用户的操作,实现各种丰富的网页功能和交互效果。

例如,当用户点击一个按钮时,我们可以通过 DOM 来添加、删除或者修改页面中的某些元素,或者改变它们的样式,从而给用户提供即时的反馈。

总之,DOM 的本质是一个桥梁,连接了网页文档和编程语言,让我们能够以编程的方式对网页进行灵活的控制和操作。

白话回答:

DOM 就是文档对象模型,它的本质呢,就是把网页文档变成一棵倒着的树那样,网页里的各种元素像 html、body、div、p 这些,都当成树上的节点。每个节点都有自己的属性和办法。通过 DOM,我们能用编程语言去访问、改这些节点的属性、内容或者结构。 DOM 给 JavaScript 这些编程语言提供了一种跟网页文档打交道的标准办法。它能让开发者动态地改网页的内容、样式和结构,响应用户的操作,做出各种好玩的网页功能和交互效果。 比如说,用户点个按钮,我们就能通过 DOM 去加、删或者改页面里的一些元素,或者改它们的样式,给用户马上有反应。 反正,DOM 的本质就是个桥,把网页文档和编程语言连起来,让我们能通过编程灵活地控制和操作网页。

十五、如何识别浏览器的类型?

标准回答:

要识别浏览器的类型,可以通过以下几种常见的方法:
一种方法是使用 JavaScript 中的navigator.userAgent属性。这个属性包含了浏览器发送的有关其自身的信息。通过对这个字符串进行解析和匹配,可以判断出浏览器的类型和版本。

例如,可以通过检查特定的关键字来识别常见的浏览器。比如,如果字符串中包含'Chrome'并且有特定的版本号模式,就可以判断是 Chrome 浏览器;如果包含'Firefox',那就是 Firefox 浏览器;对于 Internet Explorer ,可以查找类似'MSIE'或'Trident'这样的关键字。

另一种方式是利用一些前端库或框架提供的工具函数。比如 jQuery 就有方法可以方便地获取浏览器信息。

还有,现代的浏览器也支持一些特定的功能检测。通过检测某个浏览器特有的功能是否存在,也能在一定程度上推断出浏览器的类型。

总之,通过解析navigator.userAgent属性、使用前端库提供的方法或者进行功能检测,可以识别出浏览器的类型。但需要注意的是,userAgent的值是可以被篡改的,所以在实际应用中需要综合多种方法来提高识别的准确性。

白话回答:

想知道浏览器是啥类型,可以这么办。一种办法是用 JavaScript 里的 navigator.userAgent 这个东西,它有浏览器自己的信息。看这个字符串,找特定关键字,像有 “Chrome” 和特定版本号模式就是 Chrome 浏览器,有 “Firefox” 就是火狐浏览器,找 “MSIE” 或 “Trident” 就是 IE 浏览器。另一种是用一些前端库或框架的工具函数,比如 jQuery 就有办法知道浏览器信息。还有呢,现代浏览器有特定功能,检测某个功能有没有,也能猜出是啥浏览器。不过要注意,这个 userAgent 的值能被改,所以实际用的时候得多用几种方法,这样识别得更准。

十六、描述事件冒泡的流程

标准回答:

事件冒泡指的是当在页面中的一个元素上触发某个事件时,这个事件会从触发的具体元素开始,依次向上传递给它的父元素,一直到页面的根元素。

打个比方,页面就像一个家族树,最底层的元素是孩子,往上是父母、祖父母等。当孩子身上发生了一个事件,比如点击,这个事件会像消息一样往上传给父母,父母收到后如果没处理完,再继续传给祖父母,一直传到家族的最顶层,也就是根元素。

比如说,我们有一个网页结构,里面有个div包含一个button。当点击这个按钮时,首先按钮会接收到点击事件。然后,因为事件冒泡机制,这个点击事件会从按钮传递到它的父元素div。如果div上设置了针对点击事件的处理逻辑,那么就会执行相应的操作。如果div没有处理或者处理后事件还没结束,就会继续向上传递。

事件冒泡的存在为我们提供了一种便利,可以在父元素上统一处理子元素可能触发的某些事件,避免为每个子元素都单独设置处理逻辑,使代码更简洁和易于管理。

总之,事件冒泡是从具体被触发的子元素开始,逐级向上传递事件,直到根元素。

白话回答:

事件冒泡就是在网页上,一个元素发生了事儿,比如点了一下,这个事儿就会从这个元素开始,往上传给它的 “爸爸” 元素,“爸爸” 没处理完就接着传给 “爷爷” 元素,一直传到最上面的根元素。就像一个家族树,孩子身上有事儿了就往上传给家长。比如说网页里有个 div 里面有个按钮,点了按钮,按钮先收到点击事件,然后因为事件冒泡,这个事儿会传给按钮的 “爸爸” div。要是 div 设了处理这个点击事件的办法,就会做事儿。要是 div 没处理完或者处理完事儿还没完,就接着往上传。事件冒泡有个好处,就是能在 “爸爸” 元素上统一处理孩子元素可能发生的事儿,不用给每个孩子元素都单独弄处理办法,这样代码更简单好管。总之呢,事件冒泡就是从被点的子元素开始,往上一级一级传事儿,一直传到根元素。

十七、什么是事件代理?

标准回答:

事件代理,也叫事件委托,是一种在 JavaScript 中处理事件的高效方式。

简单来说,就是不直接为每个具体的子元素添加事件处理程序,而是将事件处理程序添加到它们的父元素上。

比如说,我们有一个列表,里面有很多个列表项。如果要为每个列表项添加点击事件处理程序,这会比较繁琐且可能影响性能。而使用事件代理,我们只需要为这个列表的父元素添加一个点击事件处理程序。

当点击列表中的某个具体项时,事件会冒泡到父元素。在父元素的事件处理程序中,通过判断事件的实际目标,也就是被点击的具体元素,来确定要执行的操作。

这样做的好处是,减少了大量重复的事件绑定操作,提高了性能。尤其是当处理的元素数量众多或者是动态生成的元素时,事件代理能极大地优化代码和提升效率。

例如,一个商品列表,可能会不断添加新的商品。使用事件代理,就不需要每次添加新商品时都为其单独绑定点击事件,只需要在列表的父容器上设置一次即可。

总之,事件代理通过利用事件冒泡机制,将事件处理程序添加到父元素上,从而实现对多个子元素事件的统一处理,提高了代码的效率和可维护性。

白话回答:

事件代理呢,也叫事件委托,在 JavaScript 里是个处理事件的好办法。简单讲呢,就是别直接给每个小的子元素加上事件处理的程序,而是把这个程序加到它们的 “爸爸” 元素上。比如说有个列表,里面有好多列表项,要是给每个列表项都加个点击事件的处理程序,那就挺麻烦,还可能让性能变差。用事件代理的话,只要给这个列表的 “爸爸” 元素加一个点击事件处理程序就行。要是点了列表里的某个具体项,这个事件会像泡泡一样冒到 “爸爸” 元素那里。在 “爸爸” 元素的事件处理程序里,看看这个事件到底是哪个具体元素弄出来的,也就是看看是哪个被点了,然后确定要干啥。这么做的好处呢,就是能少弄好多重复的事件绑定操作,让性能变好。特别是要处理的元素特别多的时候,或者是那些动态生成的元素,事件代理能把代码弄得更好,效率也更高。就像一个商品列表,可能会一直加新商品,用事件代理的话,就不用每次加新商品都单独给它绑个点击事件,只要在列表的 “爸爸” 容器上设一次就行。总之呢,事件代理就是借着事件冒泡的机制,把事件处理程序加到 “爸爸” 元素上,这样就能统一处理好多子元素的事件,让代码效率更高,也更好维护。

十八、什么是浏览器的同源策略?

标准回答:

浏览器的同源策略是一种重要的安全机制。

同源指的是两个 URL 的协议、域名和端口都相同。比如,example.com:8080/page1example.com:8080/page2 是同源的,而 example.com/page1example.com/page1 就不是同源的,因为协议不同;example.com/page1another.com/page1 也不是同源的,因为域名不同。

同源策略规定,来自一个源的脚本在未经明确授权的情况下,不能访问来自另一个源的资源。这包括不能获取另一个源的文档内容、不能向另一个源发送请求、不能读取另一个源的 Cookie 等。

比如说,如果一个网页是从 example1.com 加载的,那么这个网页中的 JavaScript 代码就不能直接访问来自 example2.com 的数据。

同源策略的目的是为了防止恶意网站通过脚本获取用户在其他网站上的敏感信息,保护用户的隐私和安全,防止跨站攻击。

但在某些情况下,为了实现合理的功能需求,比如跨域数据共享,可以通过一些合法的技术手段,如 JSONP、CORS 等来突破同源策略的限制。

总之,同源策略是浏览器保障网络安全和用户数据隐私的重要措施。

白话回答:

浏览器的同源策略就是个很重要的安全办法。啥叫同源呢?就是两个网址的协议、域名和端口都得一样。比如说,example.com:8080/page1example.com:8080/page2 就是同源的,但是 example.com/page1example.com/page1 就不是同源的,因为协议不一样;example.com/page1another.com/page1 也不是同源的,因为域名不一样。同源策略就是说,从一个地方来的脚本要是没经过明确允许,就不能去访问从另一个地方来的资源。这就包括不能拿到另一个地方的文档内容、不能给另一个地方发请求、不能读另一个地方的 Cookie 啥的。比如说,要是一个网页是从 example1.com 加载的,那这个网页里的 JavaScript 代码就不能直接去拿 example2.com 的数据。同源策略的目的就是为了不让坏网站用脚本拿到用户在别的网站上的敏感信息,保护用户的隐私和安全,防止跨站被攻击。不过在有些情况下,为了实现合理的功能需要,比如说跨域分享数据,可以用一些合法的技术办法,像 JSONP、CORS 这些,来突破同源策略的限制。总之呢,同源策略是浏览器保证网络安全和用户数据隐私的重要措施。

十九、实现跨域的常见方式?

标准回答:

实现跨域常见的方式主要有以下几种:

首先是 JSONP(JSON with Padding)。它利用了script标签的跨域能力。通过动态创建script标签,并指定其src属性为跨域的 URL,同时在 URL 中传递一个回调函数名。服务端返回数据时会将数据作为参数传递给这个回调函数,从而实现跨域获取数据。

其次是 CORS(Cross-Origin Resource Sharing,跨源资源共享)。这需要在服务端设置响应头,允许特定的源访问资源。当浏览器发起跨域请求时,会先发送一个预检请求,服务端返回允许的响应头后,浏览器才会发送真正的请求获取数据。

然后是代理服务器。可以在同源的服务器端设置一个代理,由代理服务器去请求跨域的资源,然后将获取到的数据返回给前端,这样就规避了跨域的问题。

还有 WebSocket 协议。它是一种全双工通信协议,在建立连接时不受同源策略的限制,只要服务器支持,就可以实现跨域通信。

总之,JSONP、CORS、代理服务器和 WebSocket 是常见的实现跨域的方式,在实际开发中需要根据具体的需求和场景选择合适的方法。

白话回答:

要实现跨域,常见的办法有这么几种。 第一种是 JSONP。它是利用 script 标签能跨域的本事。具体就是动态弄个 script 标签,把它的 src 属性设成跨域的网址,同时在这个网址里传一个回调函数的名字。服务端返回数据的时候,会把数据当成参数传给这个回调函数,这样就实现跨域拿到数据了。 第二种是 CORS。这得在服务端设置响应头,让特定的来源能访问资源。浏览器发起跨域请求的时候,会先发一个预检请求,等服务端返回允许的响应头后,浏览器才会发真正的请求去拿数据。 第三种是用代理服务器。在同源的服务器那设个代理,让代理服务器去请求跨域的资源,然后把拿到的数据返回给前端,这样就躲开跨域的问题了。 第四种是 WebSocket 协议。它是个全双工通信协议,建立连接的时候不受同源策略限制,只要服务器支持,就能实现跨域通信。 总之呢,JSONP、CORS、代理服务器和 WebSocket 就是常见的实现跨域的办法,实际开发的时候得根据具体需求和场景选合适的方法。