101道经典JavaScript面试题总结(附答案,建议收藏)

您所在的位置:网站首页 json_value无法解析的成员表达式 101道经典JavaScript面试题总结(附答案,建议收藏)

101道经典JavaScript面试题总结(附答案,建议收藏)

2023-04-24 16:37| 来源: 网络整理| 查看: 265

最近在整理 JavaScript 的时候发现遇到了很多面试中常见的面试题,本部分主要是作者在 Github 等各大论坛收录的 JavaScript 相关知识和一些相关面试题时所做的笔记,分享这份总结给大家,对大家对 JavaScript 的可以来一次全方位的检漏和排查。文章末尾有彩蛋!

1. 介绍 js 的基本数据类型。

js 一共有六种基本数据类型,分别是 Undefined、Null、Boolean、Number、String,还有在 ES6 中新增的 Symbol 类型,代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题。

2. JavaScript 有几种类型的值?你能画一下他们的内存图吗?

js 可以分为两种类型的值,一种是基本数据类型,一种是复杂数据类型。

基本数据类型....(参考1)

复杂数据类型指的是 Object 类型,所有其他的如 Array、Date 等数据类型都可以理解为 Object 类型的子类。

两种类型间的主要区别是它们的存储位置不同,基本数据类型的值直接保存在栈中,而复杂数据类型的值保存在堆中,通过使用在栈中保存对应的指针来获取堆中的值。

3. 什么是堆?什么是栈?它们之间有什么区别和联系?

堆和栈的概念存在于数据结构中和操作系统内存中。

在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。

在操作系统中,内存被分为栈区和堆区。

栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。

4. 内部属性 [[Class]] 是什么?所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]](我们可以把它看作一个内部的分类,而非 传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(..) 来查看。例如: Object.prototype.toString.call( [1,2,3] ); // "[object Array]" Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]"5. 介绍 js 有哪些内置对象?

js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。

一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。

6. undefined 与 undeclared 的区别?

已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。

对于 undeclared 变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。但是我们可以使用 typeof 的安全防范机制来避免报错,因为对于 undeclared(或者 not defined )变量,typeof 会返回 "undefined"。

7. null 和 undefined 的区别?

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。

undefined 在 js 中不是一个保留字,这意味着我们可以使用 undefined 来作为一个变量名,这样的做法是非常危险的,它会影响我们对 undefined 值的判断。但是我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。

当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

8. 如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。

表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。

按惯例我们用 void 0 来获得 undefined。

9. 说几条写 JavaScript 的基本规范?

在平常项目开发中,我们遵守一些这样的基本规范,比如说:

(1)一个函数作用域中所有的变量声明应该尽量提到函数首部,用一个 var 声明,不允许出现两个连续的 var 声明,声明时如果变量没有值,应该给该变量赋值对应类型的初始值,便于他人阅读代码时,能够一目了然的知道变量对应的类型值。

(2)代码中出现地址、时间等字符串时需要使用常量代替。

(3)在进行比较的时候吧,尽量使用'===', '!=='代替'==', '!='。

(4)不要在内置对象的原型上添加方法,如 Array, Date。

(5)switch 语句必须带有 default 分支。

(6)for 循环必须使用大括号。

(7)if 语句必须使用大括号。

10. JavaScript 原型,原型链? 有什么特点?

在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当我们使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。

一般来说我们是不应该能够获取到这个值的,但是现在浏览器中都实现了proto 属性来让我们访问这个属性,但是我们最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个Object.getPrototypeOf() 方法,我们可以通过这个方法来获取对象的原型。

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因。

特点:

JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

11. js 获取原型的方法?p.protop.constructor.prototypeObject.getPrototypeOf(p)12. 在 js 中不同进制数字的表示方式以 0X、0x 开头的表示为十六进制。以 0、0O、0o 开头的表示为八进制。以 0B、0b 开头的表示为二进制格式。13. js 中整数的安全范围是多少?

安全整数指的是,在这个范围内的整数转化为二进制存储的时候不会出现精度丢失,能够被“安全”呈现的最大整数是 2^53 - 1,即9007199254740991,在 ES6 中被定义为 Number.MAX_SAFE_INTEGER。最小整数是-9007199254740991,在 ES6 中被定义Number.MIN_SAFE_INTEGER。

如果某次计算的结果得到了一个超过 JavaScript 数值范围的值,那么这个值会被自动转换为特殊的 Infinity 值。如果某次计算返回了正或负的 Infinity 值,那么该值将无法参与下一次的计算。判断一个数是不是有穷的,可以使用 isFinite 函数来判断。

14. typeof NaN 的结果是什么?

NaN 意指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

typeof NaN; // "number"

NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN != NaN为 true。

15. isNaN 和 Number.isNaN 函数的区别?

函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,这种方法对于 NaN 的判断更为准确。

16. Array 构造函数只有一个参数值时的表现?

Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。这样创建出来的只是一个空数组,只不过它的 length 属性被设置成了指定的值。

构造函数 Array(..) 不要求必须带 new 关键字。不带时,它会被自动补上。

17. 其他值到字符串的转换规则?

规范的 9.8 节中定义了抽象操作 ToString ,它负责处理非字符串到字符串的强制类型转换。

(1)Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined",

(2)Boolean 类型,true 转换为 "true",false 转换为 "false"。

(3)Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。

(4)Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。

(3)对普通对象来说,除非自行定义 toString() 方法,否则会调用toString((Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

18. 其他值到数字值的转换规则?

有时我们需要将非数字值当作数字来使用,比如数学运算。为此 ES5 规范在 9.3 节定义了抽象操作 ToNumber。

(1)Undefined 类型的值转换为 NaN。

(2)Null 类型的值转换为 0。

(3)Boolean 类型的值,true 转换为 1,false 转换为 0。

(4)String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。

(5)Symbol 类型的值不能转换为数字,会报错。

(6)对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。

为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。

如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

19. 其他值到布尔类型的值的转换规则?

ES5 规范 9.2 节中定义了抽象操作ToBoolean,列举了布尔强制类型转换所有可能出现的结果。

以下这些是假值:• undefined• null• false• +0、-0 和 NaN• ""

假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。

20. {} 和 [] 的 valueOf 和 toString 的结果是什么?

{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"

[] 的 valueOf 结果为 [] ,toString 的结果为 ""

21. 什么是假值对象?

浏览器在某些特定情况下,在常规 JavaScript 语法基础上自己创建了一些外来值,这些就是“假值对象”。假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为 false 最常见的例子是 document.all,它是一个类数组对象,包含了页面上的所有元素,由 DOM(而不是 JavaScript 引擎)提供给 JavaScript 程序使用。

22. ~ 操作符的作用?

~ 返回 2 的补码,并且 ~ 会将数字转换为 32 位整数,因此我们可以使用 ~ 来进行取整操作。

~x 大致等同于 -(x+1)。

23. 解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字,它们之间的区别是什么?

解析允许字符串(如 parseInt() )中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换(如 Number ())不允许出现非数字字符,否则会失败并返回 NaN。

24. + 操作符什么时候用于字符串的拼接?

据 ES5 规范 11.6.1 节,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用[[DefaultValue]],以数字作为上下文。如果不能转换为字符串,则会将其转换为数字类型来进行计算。

简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。

那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。

25. 什么情况下会发生布尔值的隐式强制类型转换?

(1) if (..) 语句中的条件判断表达式。(2) for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。(3) while (..) 和 do..while(..) 循环中的条件判断表达式。(4) ? : 中的条件判断表达式。(5) 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)。

26. || 和 && 操作符的返回值?

|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先进行 ToBoolean 强制类型转换,然后再执行条件判断。

对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。

&& 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。

|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果

27. Symbol 值的强制类型转换?

ES6 允许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误。

Symbol 值不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是 true )。

28. == 操作符的强制类型转换规则?

(1)字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。

(2)其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。

(3)null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。

(4)对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。

(5)如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。

(6)如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。

29. 如何将字符串转化为数字,例如 '12.3b'?

(1)使用 Number() 方法,前提是所包含的字符串不包含不合法字符。

(2)使用 parseInt() 方法,parseInt() 函数可解析一个字符串,并返回一个整数。还可以设置要解析的数字的基数。当基数的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。

(3)使用 parseFloat() 方法,该函数解析一个字符串参数并返回一个浮点数。

(4)使用 + 操作符的隐式转换。

30. 如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』?function format(number) { return number && number.replace(/(?!^)(?=(\d{3})+\.)/g, ","); }31. 常用正则表达式// (1)匹配 16 进制颜色值 var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; // (2)匹配日期,如 yyyy-mm-dd 格式 var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/; // (3)匹配 qq 号 var regex = /^[1-9][0-9]{4,10}$/g; // (4)手机号码正则 var regex = /^1[34578]\d{9}$/g; // (5)用户名正则 var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;32. 如何实现数组的随机排序?// (1)使用数组 sort 方法对数组元素随机排序,让 Math.random() 出来的数与 0.5 比较,如果大于就返回 1 交换位置,如果小于就返回 -1,不交换位置。 function randomSort(a, b) { return Math.random() > 0.5 ? -1 : 1; } // 缺点:每个元素被派到新数组的位置不是随机的,原因是 sort() 方法是依次比较的。 // (2)随机从原数组抽取一个元素,加入到新数组 function randomSort(arr) { var result = []; while (arr.length > 0) { var randomIndex = Math.floor(Math.random() * arr.length); result.push(arr[randomIndex]); arr.splice(randomIndex, 1); } return result; } // (3)随机交换数组内的元素(洗牌算法类似) function randomSort(arr) { var index, randomIndex, temp, len = arr.length; for (index = 0; index < len; index++) { randomIndex = Math.floor(Math.random() * (len - index)) + index; temp = arr[index]; arr[index] = arr[randomIndex]; arr[randomIndex] = temp; } return arr; } // es6 function randomSort(array) { let length = array.length; if (!Array.isArray(array) || length > 0) + 2); }91. 介绍一下 js 的节流与防抖?

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

92. Object.is() 与原来的比较操作符 “===”、“==” 的区别?

使用双等号进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。

使用三等号进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。

使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 认定为是相等的。

93.escape,encodeURI,encodeURIComponent 有什么区别

encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。

encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。

escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。

94. Unicode 和 UTF-8 之间的关系?

Unicode 是一种字符集合,现在可容纳 100 多万个字符。每个字符对应一个不同的 Unicode 编码,它只规定了符号的二进制代码,却没有规定这个二进制代码在计算机中如何编码传输。

UTF-8 是一种对 Unicode 的编码方式,它是一种变长的编码方式,可以用 1~4 个字节来表示一个字符。

95. js 的事件循环是什么?

因为 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。

微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。

宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。

96. js 中的深浅拷贝实现?

浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用 Object.assign 和展开运算符来实现。

深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败。

97. 手写 call、apply 及 bind 函数

call 函数的实现步骤:1.判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。2.判断传入上下文对象是否存在,如果不存在,则设置为 window 。3.处理传入的参数,截取第一个参数后的所有参数。4.将函数作为上下文对象的一个属性。5.使用上下文对象来调用这个方法,并保存返回结果。6.删除刚才新增的属性。7.返回结果。apply 函数的实现步骤:1.判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。2.判断传入上下文对象是否存在,如果不存在,则设置为 window 。3.将函数作为上下文对象的一个属性。4.判断参数值是否传入4.使用上下文对象来调用这个方法,并保存返回结果。5.删除刚才新增的属性6.返回结果bind 函数的实现步骤:1.判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。2.保存当前函数的引用,获取其余传入参数值。3.创建一个函数返回4.函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。

98. 函数柯里化的实现// 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。 function curry(fn, args) { // 获取函数需要的参数长度 let length = fn.length; args = args || []; return function() { let subArgs = args.slice(0); // 拼接得到现有的所有参数 for (let i = 0; i < arguments.length; i++) { subArgs.push(arguments[i]); } // 判断参数的长度是否已经满足函数所需参数的长度 if (subArgs.length >= length) { // 如果满足,执行函数 return fn.apply(this, subArgs); } else { // 如果不满足,递归返回科里化的函数,等待参数的传入 return curry.call(this, fn, subArgs); } }; } // es6 实现 function curry(fn, ...args) { return fn.length


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3