什么是栈溢出?
在前端开发中,栈溢出是指JavaScript引擎执行代码时,调用栈(call stack)变得太大,超过了浏览器或JavaScript引擎所分配的栈空间,从而导致栈溢出错误。调用栈是一种数据结构,用于存储函数调用的信息,包括每个函数的局部变量、参数和返回地址。
当一个函数被调用时,它的信息被推送到调用栈的顶部,当函数执行完毕时,该信息被弹出。如果在一个递归函数或深度嵌套的函数调用链中,调用栈的深度变得过大,超过了引擎的限制,就会导致栈溢出。
一般产生溢出的原因如下:
(下面将会举例一些常见的错误)
1.递归调用未正确终止
function fn() {
return fn();
}
fn();
执行上面的代码出现报错:这是告诉开发者调用栈已经超出了最大限制
上面的函数调用,明显就是有问题的,我们需要确保递归调用有正确的终止条件
比如我们可以增加传参,设定可以终止的条件,比如:
function fn(count) {
console.log(count);
if (count <= 0) {
return '停止'; //等于小于0时停止递归
}
return fn(count - 1); //每次减1
}
fn(10); // 适当的终止条件
2.事件处理函数中的递归调用
我们获取了一个btn实例进行监听点击事件然后触发handleCllick事件,但是点击后进行了无限制触发
document.getElementById('btn').addEventListener('click',
function handleClick() {
handleClick(); // 递归调用
});
上面的代码需要确保不会无限制地在事件处理函数中触发相同的事件
document.getElementById('myButton').addEventListener('click',
function handleClick() {
// 处理点击事件的逻辑
});
3.深度嵌套的回调函数
报错代码:
function fn(callback) {
callback(fn); // 可能导致栈溢出
}
fn(function callback1(val) {
val(function callback2(val2) {
// 更多的嵌套回调
});
});
因为函数调用层次太深,函数递归调用时,系统要在栈中不断保存函数调用产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回
为了避免过度嵌套回调函数,可以使用 Promise
或 async/await
进行异步控制。
function fn() {
return new Promise(resolve => {
resolve();
});
}
fn()
.then(() => fn())
.then(() => fn());
4.变量未定义也会栈溢出
变量未定义通常不会导致栈溢出,而是会引发 ReferenceError
错误。栈溢出通常是由于函数调用栈的深度过大导致的。然而,如果在递归中使用未定义的变量,可能会导致递归调用的栈溢出。
function fn(count) {
console.log(count);
// 忘记定义变量 x
return fn(count - 1)+ x
}
fn(5);
报错如下:
由于变量 x
没有被定义,它的值为 undefined
。在递归调用中,这可能导致栈溢出,因为每次递归都会尝试访问 fn(...)+undefined
,从而形成无限递归。
我们需要确保所有变量在使用之前都被正确地定义
function fn(count) {
// 定义变量 `x`,或者在递归中使用参数 `count`
let x = 0;
console.log(count);
return fn(count - 1)+x
}
fn(5);
在实际开发中,为了避免使用未定义的变量,可以使用严格模式 ("use strict"
) 来帮助捕获未定义的变量。
上面说到了严格模式:
简单的了解下什么是严格模式,我们可以通过使用严格模式 ("use strict") 来强制执行更严格的语法和错误处理规则,其中包括捕获未定义的变量。
严格模式能够帮助我们更早地发现潜在的问题,因为我们可能有些东西在平时使用中被忽略或者遗忘的。
在Vue 2中,可以在单文件组件或者JavaScript文件的顶部启用严格模式,加上"use strict"即可,
请注意,这行代码必须位于文件的最顶部,不能有任何代码出现在它的前面。
如下所示:
例如vue文件中:
<script>
"use strict";
export default {
// 组件定义
}
</script>
//------------------------------------
单独的纯js文件中:
// 在 JavaScript 文件的顶部启用严格模式
"use strict";
// 其他代码...
严格模式下会抛出的错误:
1.未定义变量报错: 在严格模式下,如果使用未声明的变量,会抛出
ReferenceError
错误。2.删除不可删除的属性报错: 在严格模式下,尝试删除一个不可删除的属性会抛出
TypeError
错误。3.禁止使用
with
语句: 在严格模式下,使用with
语句会导致语法错误
WITH方法:
由于大量使用with语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用with语句,现在这个with已经废弃了
with
语句扩展一个语句的作用域链,语法为:
with (expression) //将给定的表达式添加到在评估语句时使用的作用域链上。表达式周围的括号是必需的。
statement //任何语句。要执行多个语句,请使用一个块语句 ({ ... }) 对这些语句进行分组。
with'语句将某个对象添加到作用域链的顶部,如果在 statement 中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。如果沒有同名的属性,则将拋出ReferenceError异常。
使用举例:
使用了JavaScript变量和Math对象的方法来计算。
var a, x, y;
var r = 10;
with (Math) {
a = PI * r * r; //圆的面积
x = r * cos(PI); //计算了在极坐标系中半径为 r,角度为 PI 弧度的点的 x 坐标
y = r * sin(PI / 2); //计算了在极坐标系中半径为 r,角度为 PI / 2 弧度的点的 y 坐标
}
//其中:
with (Math) { ... }:
这个语句块用于指定之后的代码中的变量或函数来自于 Math 对象。
这样可以省略每次引用 Math 对象的前缀。
解决了像:(简单举例)
获取对象obj的属性的值
原来的:
var a = obj.a;
var b = obj.b;
var c = obj.c;
使用with后:
with(obj){
var a = a;
var b = b;
var c = c;
}