分享 100 道基础的前端面试题(附答案)

您所在的位置:网站首页 前端设计题 分享 100 道基础的前端面试题(附答案)

分享 100 道基础的前端面试题(附答案)

2024-07-10 23:29| 来源: 网络整理| 查看: 265

99d6a51583f0747fb3910667e0364b7b.png

最近在整理 JavaScript 的时候发现遇到了很多面试中常见的面试题,本部分主要是作者在 Github 等各大论坛收录的 JavaScript 相关知识和一些相关面试题时所做的笔记,分享这份总结给大家,对大家对 JavaScript 的可以来一次全方位的检漏和排查,感谢 CavsZhouyou 的付出,原文链接放在文章最下方,如果出现错误,希望大家共同指出!

附笔记链接,阅读往期更多优质文章可移步查看,喜欢的可以给我点赞鼓励哦:github.com/Wscats/articles[1]

1. 介绍 js 的基本数据类型。 js 一共有六种基本数据类型,分别是 Undefined、Null、Boolean、Number、String,还有在 ES6 中新增的 Symbol 类型, 代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题。 复制代码 2. JavaScript 有几种类型的值?你能画一下他们的内存图吗?

涉及知识点:

栈:原始数据类型(Undefined、Null、Boolean、Number、String)

堆:引用数据类型(对象、数组和函数)

两种类型的区别是:存储位置不同。 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在 栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实 体。 复制代码

回答:

js 可以分为两种类型的值,一种是基本数据类型,一种是复杂数据类型。 基本数据类型....(参考1) 复杂数据类型指的是 Object 类型,所有其他的如 Array、Date 等数据类型都可以理解为 Object 类型的子类。 两种类型间的主要区别是它们的存储位置不同,基本数据类型的值直接保存在栈中,而复杂数据类型的值保存在堆中,通过使用在栈中 保存对应的指针来获取堆中的值。 复制代码

详细资料可以参考:《JavaScript 有几种类型的值?》[2] 《JavaScript 有几种类型的值?能否画一下它们的内存图;》[3]

3. 什么是堆?什么是栈?它们之间有什么区别和联系? 堆和栈的概念存在于数据结构中和操作系统内存中。 在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全 二叉树是堆的一种实现方式。 在操作系统中,内存被分为栈区和堆区。 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。 复制代码

详细资料可以参考:《什么是堆?什么是栈?他们之间有什么区别和联系?》[4

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 有哪些内置对象?

涉及知识点:

全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在 全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。 标准内置对象的分类 (1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。 例如 Infinity、NaN、undefined、null 字面量 (2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。 例如 eval()、parseFloat()、parseInt() 等 (3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。 例如 Object、Function、Boolean、Symbol、Error 等 (4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。 例如 Number、Math、Date (5)字符串,用来表示和操作字符串的对象。 例如 String、RegExp (6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array (7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。 例如 Map、Set、WeakMap、WeakSet (8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。 例如 SIMD 等 (9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。 例如 JSON 等 (10)控制抽象对象 例如 Promise、Generator 等 (11)反射 例如 Reflect、Proxy (12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。 例如 Intl、Intl.Collator 等 (13)WebAssembly (14)其他 例如 arguments 复制代码

回答:

js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函 数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构 造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。 复制代码

详细资料可以参考:《标准内置对象的分类》[5] 《JS 所有内置对象属性和方法汇总》[6]

6. undefined 与 undeclared 的区别? 已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。 对于 undeclared 变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。但是我们可以使用 typ eof 的安全防范机制来避免报错,因为对于 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。 复制代码

详细资料可以参考:《JavaScript 深入理解之 undefined 与 null》[7]

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 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与 之相关的对象也会继承这一改变。 复制代码

详细资料可以参考:《JavaScript 深入理解之原型与原型链》[8]

11. js 获取原型的方法?

p.proto

p.constructor.prototype

Object.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() )中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换(如 Nu mber ())不允许出现非数字字符,否则会失败并返回 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。 复制代码

详细资料可以参考:《JavaScript 字符串间的比较》[9]

29. 如何将字符串转化为数字,例如 '12.3b'? (1)使用 Number() 方法,前提是所包含的字符串不包含不合法字符。 (2)使用 parseInt() 方法,parseInt() 函数可解析一个字符串,并返回一个整数。还可以设置要解析的数字的基数。当基数的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。 (3)使用 parseFloat() 方法,该函数解析一个字符串参数并返回一个浮点数。 (4)使用 + 操作符的隐式转换。 复制代码

详细资料可以参考:《详解 JS 中 Number()、parseInt() 和 parseFloat() 的区别》[10]

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}$/; 复制代码

详细资料可以参考:《前端表单验证常用的 15 个 JS 正则表达式》[11] 《JS 常用正则汇总》[12]

32. 生成随机数的各种方法?

《JS - 生成随机数的方法汇总(不同范围、类型的随机数)》[13]

33. 如何实现数组的随机排序? // (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  0) + 2); } 复制代码

详细资料可以参考:《如何更有效的获取文件扩展名》[102]

91. 介绍一下 js 的节流与防抖?

相关知识点:

// 函数防抖: 在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。 // 函数节流: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。 // 函数防抖的实现 function debounce(fn, wait) {   var timer = null;   return function() {     var context = this,       args = arguments;     // 如果此时存在定时器的话,则取消之前的定时器重新记时     if (timer) {       clearTimeout(timer);       timer = null;     }     // 设置定时器,使事件间隔指定事件后执行     timer = setTimeout(() => {       fn.apply(context, args);     }, wait);   }; } // 函数节流的实现; function throttle(fn, delay) {   var preTime = Date.now();   return function() {     var context = this,       args = arguments,       nowTime = Date.now();     // 如果两次时间间隔超过了指定时间,则执行函数。     if (nowTime - preTime >= delay) {       preTime = Date.now();       return fn.apply(context, args);     }   }; } 复制代码

回答:

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。 复制代码

详细资料可以参考:《轻松理解 JS 函数节流和函数防抖》[103] 《JavaScript 事件节流和事件防抖》[104] 《JS 的防抖与节流》[105]

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

相关知识点:

两等号判等,会在比较时进行类型转换。 三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false)。 Object.is 在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会返回 true. Object.is 应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格。 复制代码

回答:

使用双等号进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。 使用三等号进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 认定为是相等的。 复制代码 93. escape,encodeURI,encodeURIComponent 有什么区别?

相关知识点:

escape 和 encodeURI 都属于 Percent-encoding,基本功能都是把 URI 非法字符转化成合法字符,转化后形式类似「%*」。 它们的根本区别在于,escape 在处理 0xff 之外字符的时候,是直接使用字符的 unicode 在前面加上一个「%u」,而 encode URI 则是先进行 UTF-8,再在 UTF-8 的每个字节码前加上一个「%」;在处理 0xff 以内字符时,编码方式是一样的(都是「%XX」,XX 为字符的 16 进制 unicode,同时也是字符的 UTF-8),只是范围(即哪些字符编码哪些字符不编码)不一样。 复制代码

回答:

encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行转义。 encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。 escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。 复制代码

详细资料可以参考:《escape,encodeURI,encodeURIComponent 有什么区别?》[106]

94. Unicode 和 UTF-8 之间的关系? Unicode 是一种字符集合,现在可容纳 100 多万个字符。每个字符对应一个不同的 Unicode 编码,它只规定了符号的二进制代码,却没有规定这个二进制代码在计算机中如何编码传输。 UTF-8 是一种对 Unicode 的编码方式,它是一种变长的编码方式,可以用 1~4 个字节来表示一个字符。 复制代码

详细资料可以参考:《字符编码详解》[107] 《字符编码笔记:ASCII,Unicode 和 UTF-8》[108]

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

相关知识点:

事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。 复制代码

回答:

因为 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。 微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。 宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲 染等。 复制代码

详细资料可以参考:《浏览器事件循环机制(event loop)》[109] 《详解 JavaScript 中的 Event Loop(事件循环)机制》[110] 《什么是 Event Loop?》[111] 《这一次,彻底弄懂 JavaScript 执行机制》[112]

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

相关资料:

// 浅拷贝的实现; function shallowCopy(object) {   // 只拷贝对象   if (!object || typeof object !== "object") return;   // 根据 object 的类型判断是新建一个数组还是对象   let newObject = Array.isArray(object) ? [] : {};   // 遍历 object,并且判断是 object 的属性才拷贝   for (let key in object) {     if (object.hasOwnProperty(key)) {       newObject[key] = object[key];     }   }   return newObject; } // 深拷贝的实现; function deepCopy(object) {   if (!object || typeof object !== "object") return;   let newObject = Array.isArray(object) ? [] : {};   for (let key in object) {     if (object.hasOwnProperty(key)) {       newObject[key] =         typeof object[key] === "object" ? deepCopy(object[key]) : object[key];     }   }   return newObject; } 复制代码

回答:

浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用  Object.assign 和展开运算符来实现。 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败。 复制代码

详细资料可以参考:《JavaScript 专题之深浅拷贝》[113] 《前端面试之道》[114]

97. 手写 call、apply 及 bind 函数

相关资料:

// call函数实现 Function.prototype.myCall = function(context) {   // 判断调用对象   if (typeof this !== "function") {     console.error("type error");   }   // 获取参数   let args = [...arguments].slice(1),     result = null;   // 判断 context 是否传入,如果未传入则设置为 window   context = context || window;   // 将调用函数设为对象的方法   context.fn = this;   // 调用函数   result = context.fn(...args);   // 将属性删除   delete context.fn;   return result; }; // apply 函数实现 Function.prototype.myApply = function(context) {   // 判断调用对象是否为函数   if (typeof this !== "function") {     throw new TypeError("Error");   }   let result = null;   // 判断 context 是否存在,如果未传入则为 window   context = context || window;   // 将函数设为对象的方法   context.fn = this;   // 调用方法   if (arguments[1]) {     result = context.fn(...arguments[1]);   } else {     result = context.fn();   }   // 将属性删除   delete context.fn;   return result; }; // bind 函数实现 Function.prototype.myBind = function(context) {   // 判断调用对象是否为函数   if (typeof this !== "function") {     throw new TypeError("Error");   }   // 获取参数   var args = [...arguments].slice(1),     fn = this;   return function Fn() {     // 根据调用方式,传入不同绑定值     return fn.apply(       this instanceof Fn ? this : context,       args.concat(...arguments)     );   }; }; 复制代码

回答:

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 调用,其余情况都传入指定的上下文对象。

详细资料可以参考:《手写 call、apply 及 bind 函数》[115] 《JavaScript 深入之 call 和 apply 的模拟实现》[116]

98. 函数柯里化的实现 // 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。 function curry(fn, args) {   // 获取函数需要的参数长度   let length = fn.length;   args = args || [];   return function() {     let subArgs = args.slice(0);     // 拼接得到现有的所有参数     for (let i = 0; i = 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