前言

导致白屏的起因大略有两种,一为资源的加载,二为 JS 执行出错

本文就 JS 中执行的报错,会比拟容易造成"白屏"场景,和能解决这些问题的一些办法,作出一个汇总

常见的谬误

SyntaxError

SyntaxError(语法错误)对象代表尝试解析不合乎语法的代码的谬误。当 Javascript 引擎解析代码时,遇到了不合乎语法标准的标记(token)或标记程序,则会抛出 SyntaxError

这里排列下 SyntaxError 的常见谬误

保留字谬误

SyntaxError: "x" is a reserved identifier (Firefox)
SyntaxError: Unexpected reserved word (Chrome)

如在控制台执行下方代码,则会上述谬误呈现

const enum = 1

enum严格模式和非严格模式下都是保留字。

而以下标记符只会在严格模式下才作为保留字:

  • implements
  • interface
  • let
  • package
  • private
  • protected
  • public
  • static

例如:

const implements = 1 // ✅"use strict";const implements = 1; // caught SyntaxError: Unexpected strict mode reserved word

命名谬误

一个 JavaScript 标识符必须以字母结尾,下划线(_)或美元符号($)。他们不能以数字结尾。只有后续字符能够是数字(0-9)。
var 1life = 'foo';// SyntaxError: identifier starts immediately after numeric literalvar foo = 1life;// SyntaxError: identifier starts immediately after numeric literal

谬误的标点

在代码中有非法的或者不冀望呈现的标记符号呈现在不该呈现的地位。
“This looks like a string”;// SyntaxError: illegal character42 – 13;// SyntaxError: illegal character

代码里应用了中文的引号和横杠,造成了解析谬误,这里就体现了编辑器的重要性

JSON 解析

JSON.parse('[1, 2, 3, 4, ]');JSON.parse('{"foo" : 1, }');// SyntaxError JSON.parse: unexpected character// at line 1 column 14 of the JSON data

json 解析失败的类型有很多,这里就不赘述了,咱们在进行 json 解析的时候,肯定要加上 try...catch 语句来防止谬误

分号问题

通常状况下,这个谬误只是另一个谬误一个导致的,如不正确本义字符串,应用 var 的谬误
const foo = 'Tom's bar';// SyntaxError: missing ; before statement

通过其余计划申明:

var foo = "Tom's bar";var foo = 'Tom\'s bar';var foo = `Tom's bar`; // 举荐这种计划

应用 var 谬误

var array = [];var array[0] = "there"; // SyntaxError missing ; before 

相似以后谬误的还有很多,比方:

  • SyntaxError: missing ) after argument list
  • SyntaxError: missing ) after condition
  • SyntaxError: missing } after function body
  • SyntaxError: missing } after property list

这些都是语法的谬误,在编辑器/IDE应用期间都能解析,然而在某些比拟古老的框架下,
编辑器可能并不能辨认进去他的语法,这便是此谬误经常出现的场景

小结

SyntaxError 属于运行时代码谬误,通常也是老手开发者容易犯得的谬误 ,在 dev 期间就能够发现,不然无奈通过编译,是属于比拟容易发现的问题

TypeError

TypeError(类型谬误)对象通常(但并不只是)用来示意值的类型非预期类型时产生的谬误。

以下状况会抛出 TypeError

  • 传递给运算符的操作数或传递给函数的参数与预期的类型不兼容;
  • 尝试批改无奈更改的值;
  • 尝试以不适当的办法应用一个值。

不可迭代属性

当应用 for...of ,右侧的值不是一个可迭代值时,或者作为数组解构赋值时,会报此问题

例如:

const myobj = { arrayOrObjProp1: {}, arrayOrObjProp2: [42] };const {  arrayOrObjProp1: [value1],  arrayOrObjProp2: [value2],} = myobj; // TypeError: object is not iterableconst obj = { France: "Paris", England: "London" };for (const p of obj) {  // …} // TypeError: obj is not iterable

JS 中有内置的可迭代对象,如: StringArrayTypedArrayMapSet 以及 Intl.Segments (en-US),因为它们的每个 prototype 对象都实现了 @@iterator 办法。

Object 是不可迭代的,除非它们实现了迭代协定。

简略来说,对象中短少一个可迭代属性: next 函数

将上述 obj 革新:

const obj = {  France: "Paris", England: "London",  [Symbol.iterator]() {    // 用原生的空数组迭代器来兼容    return [][Symbol.iterator]();  },};for (const p of obj) {  // …}

如此可不报错,然而也不会进入循环中

点此查看什么是迭代协定

空值问题

null.foo;// 谬误类型:null 没有这个属性undefined.bar;// 谬误类型:undefined 没有这个属性const foo = undefined;foo.substring(1); // TypeError: foo is undefined

尽管看起来简略,然而他是呈现白屏最为频繁的报错起因之一

在以前咱们通常这样解决问题:

var value = null;value && value.foo;

当初咱们能够应用 可选链 Optional chaining 来解决这个问题

var value = null;value?.foo;// 然而他也不能用来赋值:value?.foo = 1

可选链语法:

obj.val?.propobj.val?.[expr]obj.func?.(args)

谬误的函数执行

谬误的函数名称:

var x = document.getElementByID("foo");// TypeError: document.getElementByID is not a functionvar x = document.getElementById("foo"); // 正确的函数

不存在的函数:

var obj = { a: 13, b: 37, c: 42 };obj.map(function(num) {  return num * 2;});// TypeError: obj.map is not a function

in 的谬误场景

在判断一个对象中是否存在某个值时,比拟罕用的是一种办法是应用 in 来判断:

var foo = { baz: "bar" };if('baz' in foo){  // operation }

因为不能确定 foo['baz'] 的具体值,所以这种计划也是不错的,然而当 foo 的类型也不能确认的时候就会容易呈现报错了

var foo = null;"bar" in foo;// TypeError: invalid 'in' operand "foo""Hello" in "Hello World";// TypeError: invalid 'in' operand "Hello World"

字符串和空值不适宜应用此语法

_另外须要留神的是_,在数组中须要小心应用

const number = [2, 3, 4, 5];3 in number // 返回 true.2 in number // 返回 true.5 in number // 返回 false,因为 5 不是数组上现有的索引,而是一个值;

小结

因为谬误是跟随着不同的值类型,而数据的接管/转变咱们并不能做到 100% 的把控。
它是咱们平时线上报错最频繁的一种类型,也是最容易造成页面白屏的。须要放弃 120% 的小心。

RangeError

RangeError 对象示意一个特定值不在所容许的范畴或者汇合中的谬误。

在以下的状况中,可能会遇到这个问题:

  • 将不容许的字符串值传递给 String.prototype.normalize(),或
  • 尝试应用 Array 构造函数创立一个具备不非法的长度的字符串,或
  • 传递谬误值到数值计算方法(Number.toExponential()Number.toFixed()Number.toPrecision())。

这里举几个例子:

String.fromCodePoint("_"); // RangeErrornew Array(-1); // RangeErrornew Date("2014-25-23").toISOString(); // RangeError(2.34).toFixed(-100); // RangeError(42).toString(1);const b = BigInt(NaN);// RangeError: NaN cannot be converted to a BigInt because it is not an integer

总的来说 RangeError 都是因为传入了不正确的值而导致的,这种状况产生的概率较小,局部数字都是本人能够手动管制或者写死在代码里的
除非是定制化很高的状况,比方低代码,让用户随便输出的时候,在应用的时候,最好先做出判断,或者加上 try...catch

ReferenceError

ReferenceError(援用谬误)对象代表当一个不存在(或尚未初始化)的变量被援用时产生的谬误。

这种报错的场景大多处于严格模式下,在失常状况下 "变量未定义" 这种报错呈现的状况较多

foo.substring(1); // ReferenceError: foo is not defined

如上,foo 未定义即间接应用,则就会呈现报错

还有一类报错是赋值的问题,比方上方讲过的可选链性能,他是不能赋值的:

foo?.bar = 123

这一类在编码因为容易剖析,个别在编辑器中就能容易发现,所以并不会带来很多困扰。

其余

InternalError 对象示意呈现在 JavaScript 引擎外部的谬误。尚未成为任何标准的一部分,所以咱们能够疏忽。


EvalError 代表了一个对于 eval() 全局函数的谬误。

他不在以后的 ECMAScript 标准中应用,因而不会被运行时抛出。然而对象自身依然与标准的晚期版本向后兼容。


URIError 对象用来示意以一种谬误的形式应用全局 URI 处理函数而产生的谬误。

例如:

decodeURIComponent('%')// caught URIError: URI malformeddecodeURI("%")     // Uncaught URIError: URI malformed at decodeURI

所以应用 decodeURIComponent 函数时,须要加上 try...catch 来放弃正确性

另类谬误

unhandledrejection

Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;
这个时候,就会报一个谬误:unhanled rejection;没有堆栈信息,只能依附行为轨迹来定位谬误产生的机会。

window.addEventListener('unhandledrejection', event =>{    console.log('unhandledrejection: ', event.reason); // 打印});let p = Promise.reject("oops");// 打印 unhandledrejection:  oops// caught (in promise) oops

手动抛出谬误

咱们在书第三方库的时候,能够手动抛出谬误。然而throw error会阻断程序运行,请审慎应用。

throw new Error("出错了!"); // caught Error: 出错了!throw new RangeError("出错了,变量超出无效范畴!");throw new TypeError("出错了,变量类型有效!");

同样的,此种计划咱们能够应用在 Promisethen 中:

// 模仿一个接口的返回Promise.resolve({code: 3000, message: '这是一个报错!'}).then(res => {  if (res.code !== 200) {    throw new Error(`code 3000: ${res.message}`)  }  console.log(res); // 这里能够看做是执行失常操作, 抛出谬误时, 此处就不会执行了}).catch(err => {  alert(err.message)});

catch 中咱们能够通过 name 来判断不同的 Error:

try {  throw new TypeError(`This is an Error`)} catch (e) {  console.log(e.name); // TypeError}

再加上自定义的 Error,咱们就能够制作更加自在的报错信息:

class ValidationError extends Error {  constructor(message) {    super(message);    this.name = "ValidationError";  }}try {  throw new ValidationError(`This is an Error`)} catch (e) {  console.log(e.name);  // 'ValidationError'  if (e instanceof ValidationError) {    alert("Invalid data: " + e.message); // Invalid data: This is an Error  }}

Error 的根底上咱们还能够做更深刻的继承,来制作更多的自定义 Error

报错在 react 中的影响

react 报错依照地位,我将他分成两类,一类是渲染报错,另一类是执行报错;
渲染即 render 函数中的视图渲染报错,另一个则是执行函数报错;

函数的执行报错,是不会影响视图的渲染的,即白屏,然而他会有一些不良影响,如

  • 代码执行暂停,局部逻辑未执行,未能闭环整体逻辑,如点击按钮始终卡在 loading
  • 数据的渲染出现异常,两边数据对不上

在视图渲染中(包含函数的 return) ,触发 JS 谬误,都会渲染问题

那为什么整个页面都会白屏呢 ?

起因是自 React 16 起,任何未被谬误边界捕捉的谬误将会导致整个 React 组件树被卸载。

谬误边界

在 react 中存在此生命周期 componentDidCatch,他会在一个子组件抛出谬误后被调用。

class ErrorBoundary extends React.Component {  constructor(props) {    super(props);    this.state = { hasError: false };  }  // 最新的官网举荐, 通过此 api 获取是否触发谬误  static getDerivedStateFromError(error) {    return { hasError: true };  }  // 旧计划是在此处 setState  componentDidCatch(error, info) {    // Example "componentStack":    //   in ComponentThatThrows (created by App)    //   in ErrorBoundary (created by App)    //   in div (created by App)    //   in App    logComponentStackToMyService(info.componentStack);  }  render() {    if (this.state.hasError) {      return <h1>Something went wrong.</h1>;    }    return this.props.children;  }}
<ErrorBoundary fallback={<p>Something went wrong</p>}>  <Profile /></ErrorBoundary>

这是来自官网的一个简略例子,能够笼罩子组件出错的状况,防止自身组件或兄弟组件收到波及,而谬误边界组件的粒度须要开发者自身来界定

降级和熔断

在官网的文档中他更加举荐此组件 react-error-boundary,它有着更加丰盛的应用:

他能够简略的显示谬误:

function Fallback({ error }) {  return (    <div role="alert">      <p>Something went wrong:</p>      <pre style={{ color: "red" }}>{error.message}</pre>    </div>  );}<ErrorBoundary  FallbackComponent={Fallback}>  <ExampleApplication /></ErrorBoundary>;

也能够应用重置计划:

function Fallback({ error, resetErrorBoundary }) {  return (    <div role="alert">      <p>Something went wrong:</p>      <pre style={{ color: "red" }}>{error.message}</pre>      <button onclick={resetErrorBoundary}></button>    </div>  );}

通过此办法重置组件,防止了刷新页面,对于用户来说更加敌对

更多应用,能够查看此处博客

总结

JS中有很多报错,然而编辑器/编译,曾经帮忙咱们过滤了一大部分的谬误,然而依然会有局部报错会在非凡条件下呈现
所以一方面须要充沛的测试,如最大值/最小值/非凡值等等,另一方面就是须要积攒教训,一些写法就是容易呈现问题,能够通过 codeReview 来预防局部问题
但最终要坚守软件开发的 不信赖准则,放弃 overly pessimistic (过于乐观),把和程序无关的所有申请、服务、接口、返回值、机器、框架、中间件等等都当做不可信的,步步为营、处处设防。

援用

  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
  • https://zhuanlan.zhihu.com/p/602293047
  • https://baobangdong.cn/the-story-about-blank-screen/