前言
本次总结了关于JavaScript的上百道高频面试考点,感谢大家的留言点赞收藏 💗
如果文中有不对、疑惑或者错字的地方,欢迎在评论区留言指正🌻
基础篇
1. 将数组的length设置为0,取第一个元素会返回什么?
设置 length = 0
会清空数组,所以会返回 undefined
2. e.target和e.currentTarget有什么区别?
当你触发一个元素的事件的时候,该事件从该元素的祖先元素传递下去,此过程为捕获
,而到达此元素之后,又会向其祖先元素传播上去,此过程为冒泡
我们可以总结出区别:
e.target
:触发事件的元素e.currentTarget
:绑定事件的元素
3. 如何获取到一个实例对象的原型对象?
- 从
构造函数
获得 原型对象:
构造函数.prototype 复制代码
- 从
对象实例
获得父级原型对象
:
方法一: 对象实例.__proto__ 【 有兼容性问题,不建议使用】 方法二:Object.getPrototypeOf(对象实例) 复制代码
4. Math.ceil和Math.floor有什么区别?
Math.ceil()
: 向上取整,函数返回一个大于或等于给定数字的最小整数。
Math.floor()
: 向下取整,函数返回一个小于或等于给定数字的最大整数。
5. 浏览器的同源策略是什么?
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端口相同。
另外,同源策略又分为以下两种:
- DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
- XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
6. document.write和innerHTML有什么区别?
document.write:是直接写入到页面的内容流,如果在写之前没有调用document.open
, 浏览器会自动调用open。
- 每次写完关闭之后重新调用该函数,会导致页面被重写。
innerHTML:则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement
。
- innerHTML将内容写入某个DOM节点,不会导致页面全部重绘
- innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的哪一个部分。
7. 使用原生js给一个按钮绑定两个onclick事件
使用事件监听 绑定多个事件
//事件监听 绑定多个事件 var btn = document.getElementById("btn"); btn.addEventListener("click",hello1); btn.addEventListener("click",hello2); function hello1(){ alert("hello 1"); } function hello2(){ alert("hello 2"); } 复制代码
8. 什么是类数组对象?
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象。
类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有以下4种:
//(1)通过 call 调用数组的 slice 方法来实现转换 Array.prototype.slice.call(arrayLike); //(2)通过 call 调用数组的 splice 方法来实现转换 Array.prototype.splice.call(arrayLike, 0); //(3)通过 apply 调用数组的 concat 方法来实现转换 Array.prototype.concat.apply([], arrayLike); //(4)通过 Array.from 方法来实现转换 Array.from(arrayLike); 复制代码
9. typeof NaN 的结果是什么?
NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
typeof NaN; // "number" 复制代码
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。
10. JavaScript中的基本数据类型有哪些?
JavaScript共有七种基本数据类型,分别是 Undefined、Null、Boolean、Number、String、Symbol、BigInt。
11. 如何把十进制的0.2转换成二进制?
进制转换是比较基础的,如果大家熟悉 js 的 API ,那么会首先想到这两个方法:
- 十进制转二进制:num.toString(2)
- 二进制转十进制:parseInt(num, 2)
所以答案就是 (2).toString(2)
12. map和filter有什么区别?
首先,map和filter函数的参数,是完全相同的
array.map(function(currentValue,index,arr), thisValue)
array.filter(function(currentValue,index,arr), thisValue)
- currentValue:数组元素;
- index:索引
- arr:原数组;
- thisValue:作为该执行回调时使用,传递给函数,用作 "this" 的值
但是在用途上,它们是有区别的:
- map方法返回的新数组是原数组的映射,何为映射?就是和原数组的长度相同,数值做相应处理。
- filter方法返回的值是过滤原数组后的新数组,和原数组长度不同,数值不变。
示例
let arr = ["1","2","3"]; let a = arr.map((item,index,a) =>{ return item + 1 }); console.log(a);//["11", "21", "31"] let b = arr.filter((item,index,a) =>{ return item > 1 }) console.log(b);//["2", "3"] 复制代码
另外,filter可过滤NaN、null、undefined、0
let arr = [NaN,null,undefined,"0",0,1,2,3]; let newArr = arr.filter(item => item); console.log(newArr);//["0", 1, 2, 3] 复制代码
13. argments如何转换成数组?
argments属于类数组对象,转换成数组的方法可以看第八题
14. ['1', '2', '3'].map(parseInt) 的返回值是什么?
首先整个题目考校的是两个函数,和一个字符串转数字的概念
- 数组的
map
函数,接受三个参数,当前值,当前索引,当前数组。 - parseInt接受两个参数,需要转换的字符串,基数(基数取值范围2~36)
var new_array = arr.map(function callback(currentValue, index, array) { // Return element for new_array }) parseInt(string, radix) 复制代码
- 根据上面的两个函数的解释,我们可以发现实际上,上面的
['1','2','3'].map(parseInt)
其实就是等价于下面的代码。
['1','2','3'].map((item, index) => { return parseInt(item, index) }) // parseInt('1', 0) 1 // parseInt('2', 1) NaN // parseInt('3', 2) NaN 复制代码
- 如果我们需要返回1,2,3需要怎么办?
function parseIntFun(item) { return parseInt(item, 10) } ['1','2','3'].map(parseIntFun) // parseInt('1', 10) 1 // parseInt('2', 10) 2 // parseInt('3', 10) 3 复制代码
综上所述,返回值是 [1,NaN,NaN]
15. 说说你对new.target的理解
new.target
属性允许你检测函数或构造方法是否是通过new运算符被调用的。
在通过new运算符被初始化的函数或构造方法中,new.target
返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target
的值是undefined。
我们可以使用它来检测,一个函数是否是作为构造函数通过new被调用的。
function Foo() { if (!new.target) { throw "Foo() must be called with new"; } console.log("Foo instantiated with new"); } Foo(); // throws "Foo() must be called with new" new Foo(); // logs "Foo instantiated with new" 复制代码
16. arguments这种类数组,如何遍历?
- 使用普通的for循环
for(var i = 0, len = arrayLike.length; i < len; i++) { …… } 复制代码
- 转换为真正的数组,再进行遍历(具体看第八题)
17. new Fn() 和 new Fn 有区别吗?
用 new
创建构造函数的实例时,通常情况下 new
的构造函数后面需要带括号(譬如:new fn()
)。具体的区别要看在什么场景下
有些情况下new
的构造函数后带括号和不带括号的情况一致,譬如:
function Fn(){ this.num = 1; } console.log(new Fn()); //Fn {num:1} console.log(new Fn); //Fn {num:1} 复制代码
但有些情况下new
的构造函数后带括号和不带括号的情况并不一致,譬如:
function Fn(){ this.num = 1; } console.log(new Fn().num); // 1 console.log(new Fn.num); // 报错 复制代码
结果分析:
- 从报错信息来看,
new Fn.num
执行顺序是这样的:先执行Fn.num
,此时返回结果为undefined
;后执行new
,因new
后面必须跟构造函数,所以new undefined
会报错。 new Fn().num
相当于(new Fn()).num
,所以结果返回1。
从结果来看,new Fn.num
代码相当于new (Fn.num)
,new Fn().num
相当于(new Fn()).num
。由此看来 new
的构造函数后跟括号优先级会提升。
18. Object与Map有什么区别?
- Object
在ECMAScript中,Object
是一个特殊的对象。它本身是一个顶级对象,同时还是一个构造函数,可以通过它(如:new Object()
)来创建一个对象。我们可以认为JavaScript中所有的对象都是Object
的一个实例,对象可以用字面量的方法const obj = {}
声明。
- Map
Map是Object的一个子类,可以有序保存任意类型的数据,使用键值对去存储,其中键可以存储任意类型,通过const m = new Map()
即可得到一个map实例。
- 使用场景
- 如果只需要简单的存储key-value的数据,并且key不需要存储复杂类型的,直接用对象
- 如果该对象必须通过JSON转换的,则只能用对象,目前暂不支持Map
- map的阅读性更好,所有操作都是通过api形式去调用,更有编程体验
20. cookie的有效时间设置为0会怎么样?
Cookie过期时间设置为0,表示跟随系统默认,其销毁与Session销毁时间相同,即都在浏览器关闭后的特定时间删除。如果我们写程序的时候不设置Cookie的有效时间,那么,Cookie的有效时间等效于会话时间。
21. const声明了数组,还能push元素吗,为什么?
可以
数组是引用类型,const声明的引用类型变量,不可以变的是变量引用始终指向某个对象,不能指向其他对象,但是所指向的某个对象本身是可以变的
22. 如何检测数组和对象?
- 方法1:通过 ES6 中的 Array.isArray 来识别
console.log(Array.isArray([])) // true console.log(Array.isArray({})) // false 复制代码
- 方法2:通过 instanceof 来识别
console.log([] instanceof Array) // true console.log({} instanceof Array) // false 复制代码
- 方法3:通过调用 constructor 来识别
console.log([].constructor) // [Function: Array] console.log({}.constructor) // [Function: Object] 复制代码
- 方法4:通过 Object.prototype.toString.call 方法来识别
console.log(Object.prototype.toString.call([])) // [object Array] console.log(Object.prototype.toString.call({})) // [object Object] 复制代码
23. 请简述 == 的机制
undefined == null
,结果是true。且它俩与所有其他值比较的结果都是false。String == Boolean
,需要两个操作数同时转为Number。String/Boolean == Number
,需要String/Boolean转为Number。Object == Primitive
,需要Object转为Primitive(具体通过valueOf和toString方法)。
24. js中的undefined和ReferenceError:xxx is not defined 有什么区别?
- undefined:当一个变量声明后,没有被赋值,那么它就是undefined类型。
- ReferenceError:当尝试引用一个未定义的变量/函数时,就会抛出ReferenceError。
25. Math.ceil()、Math.round()、Math.floor() 三者的区别是什么?
- Math.ceil() 向上取整
- Math.round() 四舍五入
- Math.floor() 向下取整
26. 解释下面代码的意图
Array.prototype.slice.apply(arguments) 复制代码
arguments 为类数组对象,并不是真正的数组。
slice可以实现数组的浅拷贝。
由于 arguments不是真正的数组,所以没有slice方法,通过apply可以调用数组对象的slice方法,从而将arguments 类数组转换为数组。
27. 直接在script标签写export为什么会报错?
现代浏览器可以支持用 script 标签引入模块或者脚本,如果要引入模块,必须给 script 标签添加 type=“module”。如果引入脚本,则不需要 type。
28. mouseover和mouseenter有什么区别?
当鼠标移动到元素上时就会触发 mouseenter 事件,类似 mouseover,它们两者之间的差别是 mouseenter 不会冒泡。
由于 mouseenter 不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其 mouseover 和 mouseout 事件,但是却不会触发 mouseenter 和 mouseleave 事件。
29. offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别?
offsetWidth/offsetHeight 返回的是元素的布局宽度,它的值包含 content + padding + border 包含了滚动条。
offsetTop 返回的是当前元素相对于其 offsetParent 元素的顶部的距离。
offsetLeft 返回的是当前元素相对于其 offsetParent 元素的左部的距离。
clientTop 返回的是上边框的宽度。
clientLeft 返回的左边框的宽度。
clientWidth/clientHeight 返回的是元素的内部宽度,它的值只包含 content + padding,如果有滚动条,不包含滚动条。
scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸。
scrollTop 属性返回的是一个元素的内容垂直滚动的像素数。
scrollLeft 属性返回的是元素滚动条到元素左边的距离。
30. toPrecision、toFixed、Math.round 三者的区别
- toPrecision 用于处理精度,精度是从左至右第一个不为 0 的数开始数起。
- toFixed 是对小数点后指定位数取整,从小数点开始数起。
- Math.round 是将一个数字四舍五入到一个整数。
31. 什么是Polyfill?
Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。
比如说 querySelectorAll
是很多现代浏览器都支持的原生 Web API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能使这些浏览器也支持了这个功能,那么这就可以成为一个 Polyfill。
32. setTimeout为什么不能保证能够及时运行?
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop。
setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
浏览器的JS引擎遇到setTimeout,拿走之后不会立即放入异步队列,同步任务执行之后,timer模块会到设置时间之后放到异步队列中。js引擎发现同步队列中没有要执行的东西了,即运行栈空了就从异步队列中读取,然后放到运行栈中执行。所以setTimeout可能会多了等待线程的时间。
这时setTimeout函数体就变成了运行栈中的执行任务,运行栈空了,再监听异步队列中有没有要执行的任务,如果有就继续执行,如此循环,就叫Event Loop。
33. JS中如何阻止事件冒泡和默认事件?
- 阻止事件的冒泡方法:event.stopPropagation()
不让事件向 document 上蔓延,但是默认事件仍然会执行,当你调用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,
- 阻止默认事件的方法:event.preventDefault()
比如在a标签的绑定事件上调用此方法,连接则不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
- return false
这个方法比较暴力,它会同时阻止事件冒泡也会阻止默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()
和event.preventDefault()
34. 谈谈你对事件冒泡和捕获的理解
事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
事件冒泡: 微软提出了名为事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。
因此在事件冒泡的概念下在p元素上发生click事件的顺序应该是p -> div -> body -> html -> document
事件捕获: 网景提出另一种事件流名为事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
因此在事件捕获的概念下在p元素上发生click事件的顺序应该是document -> html -> body -> div -> p
35. 什么是事件代理?
事件代理(Event Delegation)也称之为事件委托。是JavaScript中绑定事件的常用技巧。
顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。
事件代理的原理是DOM元素的事件冒泡。
一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
- 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
- 目标阶段:在目标节点上触发,称为“目标阶段”
- 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层。
事件代理的优点:
- 可以大量节省内存占用,减少事件注册。
- 可以实现当新增子对象时无需再次对其绑定(动态绑定事件)
36. 浏览器为什么要有跨域限制?
因为存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。
如果浏览器没有同源策略,会存在什么样的安全问题呢。下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:
如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:
做一个假网站,里面用 iframe 嵌套一个银行网站 xxxbank.com 把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。这时如果用户输入账号密码,我们的主网站可以跨域访问到 xxxbank.com 的 dom 节点,就可以拿到用户的账户密码了。
如果没有 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:
用户登录了自己的银行页面 xxxbank.com, 该网站向用户的 cookie 中添加用户标识。用户浏览了恶意页面 evil.com, 执行了页面中的恶意 AJAX 请求代码。evil.com 向 xxxbank.com 发起 AJAX HTTP 请求,请求会默认把 xxxbank.com 对应 cookie 也同时发送过去。银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。而且由于 Ajax 在后台执行,用户无法感知这一过程。
因此,有了浏览器同源策略,我们才能更安全的上网。
37. XML和JSON有什么区别?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它完全独立于语言。它基于JavaScript编程语言,易于理解和生成。
xml 是可扩展标记语言,它定义了一组规则,用于以人类可读和机器可读的格式编码文档。XML的设计目标侧重于Internet上的简单性,通用性和可用性。它是一种文本数据格式,通过Unicode为不同的人类语言提供强大的支持。尽管XML的设计侧重于文档,但该语言被广泛用于表示任意数据结构,例如Web服务中使用的那些数据结构。
以下是JSON和XML之间的一些区别:
1、JSON是一种轻量级的数据交换格式;XML是可扩展标记语言。
2、JSON是基于JavaScript语言;XML源自SGML。
3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。
4、JSON不提供对命名空间的任何支持;XML支持名称空间。
5、JSON支持数组;XML不支持数组。
6、XML的文件相对难以阅读和解释;JSON的文件非常易于阅读。
7、JSON不使用结束标记;XML有开始和结束标签。
8、JSON的安全性较低;XML比JSON更安全。
38. null和undefined有什么区别?
首先 Undefined
和 null
都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined
代表的含义是未定义,一般变量声明了但还没有定义的时候会返回 undefined.null
代表的含义是空对象,主要用于赋值给一些可能会返回对象的变量,作为初始化。- 当对
null
类型使用 typeof 进行判断时,会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。 - 当对
undefined
类型使用 typeof 进行判断时,会返回'undefined'
undefined
在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
39. 为什么JavaScript是单线程?
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
40. “严格模式”是什么?
除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。
设立"严格模式"的目的,主要有以下几个:
- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫。
"严格模式"体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。
另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。
41. Babel是什么?
Babel 是一个 JavaScript 编译器,同时也是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
42. npm是什么?
npm是Node.js的包管理工具,它的诞生也极大的促进了前端的发展,在现代前端开发中都离不开npm的身影。 常见的使用场景有以下几种:
- 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
- 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
- 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
42. '1'.toString() 为什么不会报错?
其实在这个语句运行的过程中做了这样几件事情:
let s = new Object('1'); s.toString(); s = null; 复制代码
- 第一步: 创建Object类实例。注意为什么不是String?由于Symbol和BigInt的出现,对它们调用new都会报错,目前ES6规范也不建议用new来创建基本类型的包装类。
- 第二步: 调用实例方法。
- 第三步: 执行完方法立即销毁这个实例。
整个过程体现了 基本包装类型
的性质,而基本包装类型恰恰属于基本数据类型,包括Boolean, Number和String。
43. WebSocket如何兼容低版本浏览器?
- Adobe Flash Socket;
- ActiveX HTMLFile (IE) ;
- 基于 multipart 编码发送 XHR;
- 基于长轮询的 XHR;
44. 什么是跨域?
跨域本质是浏览器基于同源策略的一种安全手段
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)具有以下三个相同点
- 协议相同(protocol)
- 主机相同(host)
- 端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。
45. JavaScript字符串常用的方法有哪些?
- concat():用于将一个或多个字符串拼接成一个新字符串
- slice()、substr()、substring():这三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。
- trim()、trimLeft()、trimRight():删除前、后或前后所有空格符,再返回新的字符串
- repeat():接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
- padEnd():复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
- toLowerCase()、 toUpperCase():大小写转化
- charAt():返回给定索引位置的字符,由传给方法的整数参数指定
- indexOf():从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
- startWith()、includes():从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
- split:把字符串按照指定的分割符,拆分成数组中的每一项
- match:接收一个参数,可以是一个正则表达式字符串,也可以是一个
RegExp
对象,返回数组 - replace:接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)
面试时能说几个是几个
46. ES6 中数组做了哪些新扩展?
点击前往:# ES6中数组做了哪些新扩展?
47. 如何判断当前脚本运行在浏览器还是Node中?
通过判断 Global 对象是否为 window,如果不为 window,当前脚本没有运行在浏览器中。
this === window ? 'browser' : 'node'; 复制代码
48. 说说你对以下几个生命周期事件的理解?
- DOMContentLoaded
- load
- beforeunload
- unload
点击前往:# 浅析HTML页面的生命周期
49. JavaScript中的变量提升是什么?
函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。
在创建执行上下文的过程中会做三件事:创建变量对象,创建作用域链,确定 this 指向,其中创建变量对象的过程中,首先会为 arguments 创建一个属性,值为 arguments,然后会扫描 function 函数声明,创建一个同名属性,值为函数的引用,接着会扫描 var 变量声明,创建一个同名属性,值为 undefined,这就是变量提升。
50. null是对象吗?为什么?
null不是对象。
虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。
51. JavaScript本地存储的方式有哪些,有什么区别,以及有哪些应用场景?
javaScript本地缓存的方式主要有以下四种:
- localStorage
- sessionStorage
- cookie
- indexedDB
它们的区别和应用场景,点击前往:# 关于JavaScript的本地存储方案
52. typeof与instanceof的区别?
首先,typeof
与instanceof
都是判断数据类型的方法,区别如下:
typeof
会返回一个变量的基本类型,instanceof
返回的是一个布尔值instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型(除非自定义instanceof
方法)- 而
typeof
也存在弊端,它虽然可以判断基础数据类型(null
除外),但是引用数据类型中,除了function
类型以外,其他的也无法判断
可以看到,上述两种方法都有弊端,并不能满足所有场景的需求
如果需要通用检测数据类型,可以采用Object.prototype.toString
,调用该方法,统一返回格式“[object Xxx]”
的字符串
52. 谈谈你知道的DOM常见的操作
点击前往:# 面试官:谈谈你知道的DOM常见的操作
53. ==和===的区别,分别在什么情况下使用?
- == 相等:先检查两个操作数数据类型,如果相同, 则进行
===
比较, 如果不同, 会进行一次类型转换, 转换成相同类型后再进行比较。比较规则如下:
- 两个都为简单类型,字符串和布尔值都会转换成数值,再比较
- 简单类型与引用类型比较,对象转化成其原始类型的值,再比较
- 两个都为引用类型,则比较它们是否指向同一个对象
null
和undefined
相等- 存在
NaN
则返回false
- === 全等:即数据类型与值都必须相等。如果数据类型不一致,则返回false
null
和undefined
比较,会返回false
使用场景: 除了在比较对象属性为null
或者undefined
的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)
54. JavaScript中的数组有哪些常用的方法?
- 增加:push、unshift、splice、concat
- 删除:pop、shift、splice、slice
- 修改:splice
- 查找:indexOf、includes、find
- 排序:reverse、sort
- 转换:join
- 迭代:some、every、forEach、filter、map、reduce
具体用法请前往:# JavaScript数组常用方法大全
55. ES6中对函数做了哪些拓展?
- 允许为函数的参数设置默认值,并且可以结合解构赋值使用;
- 函数具有
length
属性,返回没有指定默认值的参数个数; - 函数具有
name
属性,返回该函数的函数名; - 只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
- 新增“箭头函数”写法
拓展:箭头函数写法有以下注意点
- 函数体内的
this
指向外层做作用域 - 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用rest
参数代替 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数
55. ES6中对象新增了哪些拓展?
- 当对象键名与对应值名相等的时候,可以进行简写
const obj = { name }
- 允许字面量定义对象时,将表达式放在括号内
let lastWord = 'last word'; const a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world" 复制代码
- 在解构赋值中,未被读取的可遍历的属性,可以分配到指定的对象上面。注意:拓展运算符形式的解构赋值必须是最后一个参数,否则会报错
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 } 复制代码
- ES6 一共有 5 种方法可以遍历对象的属性。
for...in
:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)Object.keys(obj)
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名Object.getOwnPropertyNames(obj)
:回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有 Symbol 属性的键名Reflect.ownKeys(obj)
:返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
对象新增的方法:
Object.is()
:严格判断两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身Object.assign()
:方法用于对象的合并,将源对象source
(第二个参数)的所有可枚举属性,复制到目标对象target
(第一个参数)Object.getOwnPropertyDescriptors()
:返回指定对象所有自身属性(非继承属性)的描述对象Object.setPrototypeOf()
:方法用来设置一个对象的原型对象Object.getPrototypeOf()
: 用于读取一个对象的原型对象Object.keys()
: 返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组Object.values()
: 返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组Object.entries()
: 返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组Object.fromEntries()
: 用于将一个键值对数组转为对象
对象的方法具体使用可点击前往 MDN-Object
56. var、let、const三者的区别?
1. var特点
- 声明作用域(函数作用域)
- 变量提升
- 可重复声明同个变量
- 全局声明的变量会成为全局对象的属性
2. let特点
- 块级作用域
- 不存在变量提升
- 不允许重复声明
- 防止变量泄露(for循环的迭代变量)
3. const特点
- 声明变量时,同时初始化
- 块级作用域
- 暂时性死区
- 不可修改
- 不允许重复声明
- 不能用来声明迭代变量
详细解析可点击前往:# 面试官:你说说var、let、const三者的区别
57. script标签放在header里和放在body底部有什么区别?
1、浏览器是从上到下解析HTML的。
2、放在head里的js代码,会在body解析之前被解析;放在body底部的js代码,会在整个页面加载完成之后解析。
- head 部分中的脚本: 需调用才执行的脚本或事件触发执行的脚本放在HTML的head部分中。当你把脚本放在head部分中时,可以保证脚本在任何调用之前被加载,
- body 部分中的脚本: 当页面被加载时立即执行的脚本放在HTML的body部分。放在body部分的脚本通常被用来生成页面的内容。
脚本会阻塞页面的渲染,所以推荐将其放在 body 底部,因为当解析到 script 标签时,通常页面的大部分内容都已经渲染完成,让用户马上能看到一个非空白页面。
58. 什么是同步和异步?
同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据。
59. 遍历数组的方法都有哪些?
for循环方法:for
、for...of
、for...in
内置方法:some、every、forEach、filter、map、reduce
具体用法请前往:# JavaScript数组常用方法大全
60. 简单说说对web worker的了解
Web Workers 是在HTML5中出现的,它可以使一个Web应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是UI)线程运行而不被阻塞。
它的作用就是给JS创造多线程运行环境,允许主线程创建worker
线程,分配任务给后者,主线程运行的同时worker
线程也在运行,相互不干扰,在worker
线程运行结束后把结果返回给主线程。这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker
线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。
61. js实现的动画和CSS动画分别有什么优缺点?
1. JS 动画
优点
- 控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的。
- 动画效果比
css3
动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有js
动画才能完成 CSS3
有兼容性问题,而JS
大多时候没有兼容性问题
缺点
- 代码的复杂度高于
CSS
动画 JavaScript
在浏览器的主线程中运行,而主线程中还有其它需要运行的JavaScript
脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞,从而造成丢帧的情况
2. CSS动画
优点
- 浏览器可以对动画进行优化
- 代码相对简单,性能调优方向固定
- 对于帧速表现不好的低版本浏览器,
CSS3
可以做到自然降级,而JS
则需要撰写额外代码
缺点
- 运行过程控制较弱,无法附加事件绑定回调函数
- 代码冗长,想用
CSS
实现稍微复杂一点动画,最后CSS
代码都会变得非常笨重
62. 前端实现动画有哪些方式?
前端常用的动画实现方式有以下种:
- css3的
transition
属性 - css3的
animation
属性 - 原生JS动画
- 使用
canvas
绘制动画 - SVG动画
- Jquery的
animate
函数 - 使用gif图片
- 代码复杂度方面,简单动画:
css
代码实现会简单一些,js
复杂一些。 复杂动画的话:css
代码就会变得冗长,js
实现起来更优。 - 动画运行时,对动画的控制程度上,
js
比较灵活,能控制动画暂停,取消,终止等,css
动画不能添加事件,只能设置固定节点进行什么样的过渡动画。 - 兼容方面,
css
有浏览器兼容问题,js
大多情况下是没有的。 - 性能方面,
css
动画相对于优一些,css
动画通过GUI
解析,js
动画需要经过js
引擎代码解析,然后再进行GUI
解析渲染。
63. 说一说Cookie、SessionStorage、LocalStorage三者的区别?
首先,Cookie、SessionStorage、 LocalStorage都是浏览器的本地存储。它们之前的区别可以从以下几个方面来说:
- 写入方式:cookie是由服务器端写入的,而SessionStorage、 LocalStorage都是由前端写入的
- 生命周期:cookie的生命周期是由服务器端在写入的时候就设置好的,LocalStorage是写入就一直存在,除非手动清除,SessionStorage是页面关闭的时候就会自动清除。
- 存储大小:cookie的存储空间比较小大概4KB,SessionStorage、 LocalStorage存储空间比较大,大概5M。
- 数据共享:三者数据共享都遵循同源原则,SessionStorage还限制必须是同一个页面。
- 发送请求是否携带:在前端给后端发送请求的时候会自动携带Cookie中的数据,但是SessionStorage、 LocalStorage不会。
- 应用场景:Cookie一般用于存储登录验证信息SessionID或者token,LocalStorage常用于存储不易变动的数据,减轻服务器的压力,SessionStorage可以用来检测用户是否是刷新进入页面,如音乐播放器恢复播放进度条的功能。
扩展:从安全性来说,因为每次http请求都会携带cookie信息,这样无形中浪费了带宽,所以cookie应该尽可能少的使用,另外cookie还需要指定作用域,不可以跨域调用,限制比较多。但是用来识别用户登录来说,cookie还是比storage更好用的。其他情况下,尽量使用storage。
storage在存储数据的大小上面秒杀了cookie,现在基本上很少使用cookie了。
localStorage和sessionStorage唯一的差别一个是永久保存在浏览器里面,一个是关闭网页就清除了信息。localStorage可以用来跨页面传递参数,sessionStorage用来保存一些临时的数据,防止用户刷新页面之后丢失了一些参数。
64. js中数组是如何在内存中存储的?
数组不是以一组连续的区域存储在内存中,而是一种哈希映射的形式。它可以通过多种数据结构来实现,其中一种是链表。
js分为基本类型和引用类型:
- 基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问;
- 引用类型是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用
65. 如何检测浏览器版本?
检测浏览器版本一共有两种方式:
- 一种是检测
window.navigator.userAgent
的值,但这种方式很不可靠,因为userAgent
可以被改写,并且早期的浏览器如 ie,会通过伪装自己的 userAgent 的值为 Mozilla 来躲过服务器的检测。 - 第二种方式是功能检测,根据每个浏览器独有的特性来进行判断,如 ie 下独有的
ActiveXObject
。
66. JavaScript中的错误有哪些类型?
- Error:
Error
是最基本的错误类型,其他的错误类型都继承自该类型。因此,所有错误的类型共享了一组相同的属性。 这个类型的错误很少见。一般使用开发人员自定义抛出的错误。 - EvalError:这个错误会在使用
eval()
函数发生异常时候抛出。 - RangeError:在数值超出相应范围时触发。
- ReferenceError:一般出现在变量找不到的情况时触发。
- SyntaxError:当Javascript语言解析代码时,Javascript引擎发现了不符合语法规范的tokens或token顺序时抛出SyntaxError。
- TypeError:这个错误在JavaScript中是经常遇到的,不管是初学者还是老手。在变量中保存着以外的类型时,或者在访问不存在的方法时。都会导致这种错误。但是归根结底还是由于在执行特定于类型的操作时,变量的类型并不符合要求所致。
- URIError:在使用encodeURI或者decodeURI因为URL格式不正确时,就会导致URIError错误。这种错误也很少见。
67. ajax、fetch、axios有什么区别?
(1) AJAX
Ajax
即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:
- 本身是针对MVC编程,不符合前端MVVM的浪潮
- 基于原生XHR开发,XHR本身的架构不清晰
- 不符合关注分离(Separation of Concerns)的原则
- 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
(2)Fetch
fetch
号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
fetch的优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
fetch的缺点:
- fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- fetch默认不会带cookie,需要添加配置项:fetch(url, {credentials: 'include'})
- fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
- fetch没有办法原生监测请求的进度,而XHR可以
(3)Axios
Axios
是一种基于Promise封装的HTTP客户端,其特点如下:
- 浏览器端发起XMLHttpRequests请求
- node端发起http请求
- 支持Promise API
- 监听请求和返回
- 对请求和返回进行转化
- 取消请求
- 自动转换json数据
- 客户端支持抵御XSRF攻击
「2023」JavaScript最新高频面试题指南(上)(二):https://developer.aliyun.com/article/1414025