前端面试题-js

209 阅读14分钟

1、js数据类型

JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。其中 SymbolBigInt 是ES6 中新增的数据类型。

  • Symbol代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。

原始数据类型和引用数据类型(复杂数据类型)有什么区别?两者在内存中的存储方式不同

  • 堆: 存放引用数据类型,引用数据类型占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,如ObjectArrayFunction

  • 栈: 存放原始数据类型,栈中的简单数据段,占据空间小,属于被频繁使用的数据,如StringNumberNullBoolean

2、null和undefined的区别

UndefinedNull 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefinednull

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

3、判断数据类型的方法,有什么区别?

typeof、instanceof、Object.prototype.toString.call()

typeof与instanceof的区别

  • typeof会返回一个运算数的基本类型instanceof 返回的是布尔值

  • instanceof 可以准确判断引用数据类型,但是不能正确判断原始数据类型

  • typeof虽然可以判断原始数据类型(null 除外),但是无法判断引用数据类型(function 除外)

Object.prototype.toString.call()

调用该方法,统一返回格式 [object Xxx]的字符串,用来表示该对象。

 function getType (obj) {
  const type = typeof obj
  if (type !== 'object') { // 先进行typeof判断,如果是基础数据类型,直接返回
    return type
  }
  // 如果是引用类型,再进行如下的判断,正则返回结果
  return Object.prototype.toString
    .call(obj)
    .replace(/^\[object (\S+)\]$/, '$1')
    .toLocaleLowerCase()
}

判断数组的方法有哪些?

(1)通过Object.prototype.toString.call()做判断

(2)通过原型链做判断 obj.proto === Array.prototype;

(3)通过ES6的Array.isArray()做判断

(4)通过instanceof做判断

4、数组有哪些原生方法?

  • 数组和字符串的转换方法:toString()toLocalString()join() 其中 join() 方法可以指定转换为字符串时的分隔符。

  • 数组尾部操作的方法 pop() push()push 方法可以传入多个参数。

  • 数组首部操作的方法 shift()unshift() 重排序的方法 reverse() sort()sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。

  • 数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。

  • 数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。

  • 数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf()lastIndexOf() 迭代方法 every()some()filter()map()forEach()方法

  • 数组归并方法 reduce() reduceRight() 方法

  • 改变原数组的方法fill()pop()push()shift()splice()unshift()reverse()sort()

  • 不改变原数组的方法concat()every()filter()find()findIndex()forEach()indexOf()join()lastIndexOf()map()reduce()reduceRight()slice()some()

5、字符串常用方法有哪些?

一般用字符串拼接;concat() 方法将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。

  • slice
  • substring

slice

slice() 方法提取某个字符串的一部分,并返回一个新的字符串,接收一或两个参数。

返回的新字符串由参数indexStart 和 indexEnd 决定(包括 indexStart,indexEnd)。

只传入一个参数,indexEnd 默认为字符串长度。

如果参数为负数,则等同于 index + strLength

subString(indexStart, indexEnd)

  • 如果 indexStart 等于 indexEnd,substring 返回一个空字符串。

  • 如果省略 indexEnd,substring 提取字符一直到字符串末尾。

  • 如果任一参数大于 strLength,则被当作 strLength。

  • 如果 indexStart 大于 indexEnd,则 substring 的执行效果就像两个参数调换了一样。(与 slice 不同)

  • 如果任一参数小于 0 或为 NaN,则被当作 0。(与 slice 不同)

还有一个 substr 方法,已经快被废弃了

  • trim
  • repeat
  • padEnd
  • toLowerCase、 toUpperCase

trim

trim() 方法会从一个字符串的两端删除空白字符。

trimStart() 方法从字符串的开头删除空格。trimLeft() 是此方法的别名。

trimEnd()  方法从一个字符串的末端移除空白字符。trimRight() 是这个方法的别名。

repeat

接收一个参数 n,表示要将字符串复制 n 次,然后返回拼接所有副本后的字符串,n 会被自动转成整数。

padEnd、padStart

padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

padStart() 方法从左侧填充

  • chatAt()
  • indexOf、lastIndexOf
  • includes、startsWith、endsWith

charAt()  方法从一个字符串中返回指定下标的字符。 indexOf() 方法从字符串开头去搜索传入的字符串,并返回位置。lastIndexOf() 方法是从后面往前面找。 includes() 方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。

startsWith() 方法用来判断当前字符串是否以另外一个给定的子字符串开头。

endWith() 方法则判断当前字符串是否以另外一个给定的子字符串结尾。

转换方法

split() 方法把字符串按照指定的分割符,拆分成数组中的每一项。

正则表达式方法

  • match()
  • search()
  • replace()

match() 方法接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组。 search()  方法执行正则表达式和字符串之间的一个搜索匹配。找到则返回匹配索引,否则返回 -1。 replace接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)

6、对类数组对象的理解,如何转化为数组

类数组也叫伪数组,类数组和数组类似,但不能调用数组方法,常见的类数组有arguments、通过document.getElements获取到的内容等,这些类数组具有length属性。

转换方法:

(1)通过 call 调用数组的 slice 方法来实现转换。Array.prototype.slice.call()

(2)通过 call 调用数组的 splice 方法来实现转换。Array.prototype.splice.call(array, 0)

(3)通过 apply 调用数组的 concat 方法来实现转换。Array.prototype.concat.apply([], arrayLike)

(4)通过 Array.from 方法来实现转换

7、深拷贝与浅拷贝

深拷贝:- JSON.stringify() 将js对象序列化,再通过JSON.parse反序列

-   如果对象中有函数、`undefined``symbol`时,都会丢失
-   如果有正则表达式、`Error`对象等,会得到空对象

浅拷贝:

  • Objec.assign()拷贝对象
  • 扩展运算符

8、for in和for of的区别

for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、SetMap 以及 Generator 对象。

for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、SetMap 以及 Generator 对象。

  • for…of 遍历获取的是对象的键值for…in 获取的是对象的键名
  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;

9、为什么0.1+0.2 !== 0.3,如何让其相等

因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。 因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变成0.30000000000000004,所以在计算时会产生误差。

解决方法:(1)将其先转换成整数,再相加之后转回小数。(2)使用number对象的toFixed方法,只保留一位小数点。

10、new操作符的实现原理

在JavaScript中,new是一个用于创建对象实例的关键字。它用于调用构造函数,并返回一个新的对象。

使用new关键字创建的对象,可以访问构造函数中定义的属性和方法以及构造函数原型链中的属性和方法。

new操作符的执行过程:

  1. 创建一个新的空对象。

  2. 将新创建的对象的__proto__属性指向构造函数的prototype属性。

  3. 将构造函数的this关键字指向新创建的对象。

  4. 执行构造函数中的代码,给这个空对象添加属性和方法。

  5. 如果构造函数没有显式返回一个对象,则返回新创建的对象。

11、this指向

  • this 关键字指向的是一个对象,而指向哪个对象,或者是这个对象的值是什么取决于使用(调用)的方式和环境。

  • this 指向的值是可以通过手动方式去改变的,比如call、bind、apply方法。

  • this 在严格模式和非严格模式下也会有差别。

(1)全局普通函数 非严格模式下,全局普通函数的this指向 window;严格模式下,普通函数的this指向undefined

(2)对象的函数方法 不管是严格模式还是非严格模式,调用对象的函数方法时,被调用函数中的this永远指向这个对象

(3)构造函数 不管是严格模式还是非严格模式,构造函数中的this都是指向构造函数创建的对象实例

(4)事件处理函数 this指向被绑定的目标对象

12、箭头函数

(1)箭头函数自身没有this

(2)箭头函数的this不是调用的时候决定的,而是在定义的时候所处的环境就已经决定了。

普通的箭头函数在全局环境下始终指向window

对象的箭头函数中,this则是指向执行该对象所处的执行上下文(执行环境)

事件处理中的箭头函数中,this指向window

箭头函数与普通函数的区别

  • 箭头函数是匿名函数,不能作为构造函数,使用new关键字。

  • 箭头函数没有arguments

  • 箭头函数没有自己的this,会获取所在的上下文作为自己的this

  • call()applay()bind()方法不能改变箭头函数中的this指向

  • 箭头函数没有prototype

  • 箭头函数不能用作Generator函数,不能使用yeild关键字

13、ajax的理解,实现ajax请求

一个完整的 AJAX 请求包括五个步骤:

(1)创建 XMLHTTPRequest 对象

(2)使用 open 方法创建 http 请求,并设置请求地址

xhr.open(get/post,url,async,true(异步),false(同步))经常使用前三个参数

(3)设置发送的数据,用 send 发送请求

(4)注册事件(给 ajax 设置事件)

(5)获取响应并更新页面

14、ajax、axios与fetch的区别

ajax

  • 基于原生XHR开发,XHR本身架构不清晰。
  • 针对MVC编程,不符合现在前端MVVM的浪潮。
  • 多个请求之间如果有先后关系的话,就会出现回调地狱
  • 配置和调用方式非常混乱,而且基于事件的异步模型不友好。

axios

  • 支持PromiseAPI
  • 从浏览器中创建XMLHttpRequest
  • node.js 创建 http 请求
  • 支持请求拦截和响应拦截
  • 自动转换JSON数据
  • 客服端支持防止CSRF/XSRF

fetch

  • 浏览器原生实现的请求方式,ajax的替代品
  • 基于标准 Promise 实现,支持async/await
  • fetchtch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
  • 默认不会带cookie,需要添加配置项
  • fetch没有办法原生监测请求的进度,而XHR可以。

15、Set与Map

  • Map是一种键值对的集合,和对象不同的是,键可以是任意值
  • Map可以遍历,可以和各种数据格式转换
  • Set是类似数组的一种的数据结构,类似数组的一种集合,但在Set中没有重复的值

16、作用域,作用域链

作用域是一个变量或函数的可访问范围,作用域控制着变量或函数的可见性和生命周期。

(1) 全局作用域:可以全局访问

-   最外层函数和最外层定义的变量拥有全局作用域
-   `window`上的对象属性方法拥有全局作用域
-   为定义直接复制的变量自动申明拥有全局作用域
-   过多的全局作用域变量会导致变量全局污染,命名冲突

(2) 函数作用域:只能在函数中访问使用哦

-   在函数中定义的变量,都只能在内部使用,外部无法访问
-   内层作用域可以访问外层,外层不能访问内存作用域

(3) ES6中的块级作用域:只在代码块中访问使用

-   使用ES6中新增的`let``const`什么的变量,具备块级作用域,块级作用域可以在函数中创建(由{}包裹的代码都是块级作用域)
-   `let``const`申明的变量不会变量提升,`const`也不能重复申明
-   块级作用域主要用来解决由变量提升导致的变量覆盖问题

作用域链: 变量在指定的作用域中没有找到,会依次向一层作用域进行查找,直到全局作用域。这个查找的过程被称为作用域链。

17、什么是js原型?原型链是什么?

prototype :

(1)js通过构造函数来创建对象,每个构造函数内部都会一个原型prototype属性。

(2)prototype是个对象

(3)当我们创建函数的时候会默认添加prototype这个属性

proto:

(1)对象的属性

(2)指向构造函数的prototype obj.proto === Object.prototype

(3)构造函数.prototype.proto === Object.prototype

(4)原型链顶层:Object.prototype.proto = null

原型链:每个实例对象都有一个__proto__属性指向它的构造函数的原型对象,而这个原型对象也会有自己的原型对象,一层一层向上,直到顶级原型对象null,这样就形成了一个原型链。

18、promise

Promise是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了地狱回调。

Promise构造函数接收一个带有resolvereject参数的回调函数。

  • resolve的作用是将Promise状态从pending变为fulfilled,在异步操作成功时调用,并将异步结果返回,作为参数传递出去

  • reject的作用是将Promise状态从pending变为rejected,在异步操作失败后,将异步操作错误的结果,作为参数传递出去

Promise的缺点:

  • 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

19、async await

async声明function是一个异步函数,返回一个promise对象,可以使用 then 方法添加回调函数。async函数内部return语句返回的值,会成为then方法回调函数的参数。

20、闭包

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

闭包优点:

  • 创建全局私有变量,避免变量全局污染
  • 可以实现封装、缓存等

闭包缺点:

  • 创建的变量不能被回收,容易消耗内存,使用不当会导致内存溢出

    • 解决: 在不需要使用的时候把变量设为null

使用场景:

  • 用于创建全局私有变量
  • 封装类和模块
  • 实现函数柯里化

21、call()、bind()、apply()的区别

  • 都可以用作改变this指向
  • callapply的区别在于传参,callbind都是传入对象。apply传入一个数组。
  • callapply改变this指向后会立即执行函数,bind在改变this后返回一个函数,不会立即执行函数,需要手动调用。