整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

《刺客信条:英灵殿》初体验:从RPG化回潮,更强的世

《刺客信条:英灵殿》初体验:从RPG化回潮,更强的世界探索感

“安静,你们这些众神的子嗣……好好听我说时间开始的故事。那时一片漆黑,没有沙、没有海,也无天无地、无草无风。直到烈火遇上了极冰,打破了这一道虚无。而巨人尤弥尔从一声尖叫之中诞生了,成为万物之始……自负的尤弥尔遭到残忍的杀害,然而他的头骨、血液与大脑构成了这个世界……这个你们行走与四处征战的世界……”

伴随先知的低语,“刺客信条”首个以北欧视角展开的系列作《刺客信条:英灵殿》拉开了帷幕。过去数天里,我扮演的维京人埃沃尔奔波于斯堪的纳维亚半岛的风雪之中,也奔驰在广袤又郁郁葱葱的英格兰原野之上。

很大程度上,这一切是令人熟悉的,这一代“刺客信条”仍是我们所认知的那个“刺客信条”,你在其中可以找到许多系列旧有的元素——无论是好的还是不好的,当然还是好的居多。

与此同时,也可以见到许多变化。不仅是相对于更久远的那些系列前作,相对于更近一些的《刺客信条:起源》《刺客信条:奥德赛》,本作当中也有让人意外的改变之处。在评价上述两款游戏时我曾经提到过,“刺客信条”系列正在明显地向更大规模、更RPG化前进,随之而来的是更多重复性的体验,有打不完的宝箱和要塞,刷不完的日常任务,大家都很烦恼。可是在正常情况下,一个新游戏必然要规模更大、比以往更复杂,不太可能做减法,开发者们其实也面临关键选择。

是继续扩大规模,还是试试别的路子,《刺客信条:英灵殿》应该说是针对上述问题交出的一份答卷。

提前预告,剧情里的标题画面出来得会有些晚

北欧的调调很吸引人,但这只是前菜

(本节有剧透,请谨慎阅读。)

游戏开场是一段精彩的长镜头:斯堪的纳维亚半岛上的佛恩伯格王国里,史帝比翁王和他的人民正在宴饮狂欢,忽然传来了敌对氏族库德维突袭的消息,人们仓促应战,死伤惨重。年幼的埃沃尔一夜之间失去双亲,在与王子席格德逃亡的路上又被恶狼袭击,这让她(或他)获得了“狼吻者”的名号……

坦白说,这是一段常见的“家破人亡”式开局,可贵的是剧情演出很棒,从小埃沃尔走出长屋,加入战局,到冰原遇袭,整个桥段一镜到底。这与以往“刺客信条”系列开场的处理方式完全不同,它的好处是戏剧效果震撼,而且简洁明了地交代了故事背景与人物关系。

v.qq.com/iframe/player.html?vid=j32025mjq2o&tiny=0&auto=0

《刺客信条:英灵殿》开场前10分钟

17年后,狼吻者成长为坚强的北方勇士;外出游历两年的席格德荣耀地归来,带回了两位无形者。国恨家仇锤炼了埃沃尔的性格,和席格德一样,埃沃尔主张用斧头解决残酷的库德维,谨慎的史帝比翁则不愿独自冒险。雄心勃勃的哈拉尔王愿意帮助他们剿灭仇敌,代价是换取史帝比翁的永远臣服。

游戏前两个章节的主要舞台是白雪覆盖的北欧大陆,初始场景的色调、建筑的风格、人物的妆发等等很容易让人联想到那些有名的北欧背景游戏——《上古卷轴5:天际》《地平线:黎明时分》或是新《战神》,这倒不是什么坏事,我们都喜欢北欧风的不是吗?

北欧的雪山群峰

故事里有一些暗示,埃沃尔与席格德的命运走向令人好奇

在埃沃尔成年以后,历史的车轮已经滚动到公元872年,挪威统一的前夜,游戏初始的明线是向库德维复仇,暗含的是金发王哈拉尔使用各种手段降服各部族的进程,这对仇恨、征战中的人民来说也许是好事,但也让王子席格德失去了继承权。在埃沃尔的支持下,在无形者的潜移默化下,席格德准备去混乱无序的英格兰闯出一片新天地。

以篇幅而论,来到英格兰以后才是游戏真正的主菜。初来乍到的维京人沿河而上,在渡鸦屯建立了自己的据点。玩家可以建设和升级这些据点,解锁更多的据点功能和支线任务。这时故事的主线是与英格兰各地的友好势力结成联盟,每个联盟任务的故事线都很长,而且风格各异,足以消耗掉玩家大量的时间。

这是个奇妙的搭配:从茫茫雪原来到苍翠欲滴的英格兰原野上,眼前是视野极佳、绿草如茵的平原地带,地平线上屹立着神秘悠远的巨石阵以及罗马统治时代的神庙遗迹,远处山巅高耸着古老的瞭望塔跟修道院,你还可以拜访中古时代的英国城镇——剑桥、伦敦、雷普顿、诺丁汉……尤其是,如果你刚在《看门狗:军团》里体验过未来版伦敦,那么在这里会经受巨大的反差震撼……

不管怎么说,能见到公元9世纪末伦敦,对历史爱好者来说,那种亲切和喜悦的心情是油然而生的。

黄昏时的英格兰仿佛油画一般,一切都很舒服

现代线,我能说的只是,蕾拉·哈桑的颜值提升了

改变比想象中多,有的还挺复古

当我第一次可以控制埃沃尔的时候,我本以为一切跟之前的“刺客信条”差不多。

确实可以找到很多熟悉的迹象,从看到游戏界面开始,你可能就会认为跟系列的前两代不会有太大区别。是,这毕竟是个“刺客信条”游戏,你可以像以往一样选择男或女性角色作为主角——我选了女性,因为“埃沃尔”(Eivor)是个女性的名字,在《刺客信条:英灵殿》的前传漫画和前传小说中,埃沃尔也以女性形象出现。

你可以选择带有提示的任务模式,也可以选择探索模式,自行寻找任务地点;你也能控制主角奔跑、攀爬、战斗、开鸟瞰点,也能招募队友,沿着海岸航行……这都没什么不同。但很快,你会发现埃沃尔不能暗杀,也不会信仰之跃(这些能力在后续任务里解锁,会有剧情交待);生命值不再自动恢复,回血需要吃野果子或是口粮,而且口粮袋有容量限制;在冰冷的水中不能停留很久,不仅会损血,视野也变得模糊起来……

这些设定与过去几代“刺客信条”有所不同,它们对低难度下的玩家意义不大,对高难度下的游玩就提出了更大的挑战。本作的游戏难度分为探索难度、战斗难度和匿踪难度3项,可以分别选择,显然,游戏制作者不想为难只想走剧情通关的玩家,但也给硬核玩家提供了解决方案。

游戏初期的地图

还有一些变化对任何玩家来说都是更积极的。比如说,技能树非常人性,可以随时洗点;典籍回归,方便玩家了解故事背景;武器上的符文随插随用,不想用了还可以随时取下来,或者换给别的武器,并不会损毁;鸟瞰点同步后会在玩家视野里直接标记出周围的财宝、奥秘以及资源的位置,相关图标会高悬在天上,便于查找。

画面表现显然是历代之中最强的,优化也非常好。在我运行游戏的PS4 Pro上,前代《刺客信条:奥德赛》不仅读取缓慢,而且发出巨大的噪音,仿佛下一秒主机就会炸裂。《刺客信条:英灵殿》刚好相反,读取场景很快,游玩时帧数稳定,贴图清晰。这真的令人欣慰。

总而言之,在让玩家更便捷地进行游戏上面,《刺客信条:英灵殿》比它的两部前作做得好。唯一的问题是,寻路仍然非常糟糕,陆地上骑马常常南辕北辙,走海路时长船也可能卡在岸边无法动弹——不知两年来这方面究竟做了什么。

同步之后,大量的探索元素出现在地平线上

贴图精度比前两作有很大提升,基本上达到了PS4主机上的最佳画质

新作的某些变化是带有鲜明“个人喜好”的。读取画面风格的回归、支线任务中对话的呈现方式、解谜时以幻影还原事发情景等,包括击杀Boss时废话连篇但又能渲染气氛的剧情演出,这些设计明显是育碧蒙特利尔的风格,而不是育碧魁北克的。所以,简单来说,游戏里的很多细节更像是回归到了《刺客信条:起源》,而不是更贴近《刺客信条:奥德赛》。

还有一些变化就非常有趣了。我们知道,从《刺客信条:起源》开始,“刺客信条”系列迎来了一次事实上的重启,开发者们保留了一些系列特色,也丢弃了一些,有意思的是,在《刺客信条:英灵殿》当中会看到相当多的细节在向旧系列——从《刺客信条2》到《刺客信条:枭雄》——汲取养分。

可以举出很多例子,比如收集飞散的纸,让我回到了《刺客信条3》的梦魇中;劫掠!这当然是《刺客信条:黑旗》里我常做的事;解锁和升级自己的家,这在早期几代里很常见……然后,还有混入人群、稻草堆击杀的回归,我已经忘了它们分别是哪一代取消的,反正真的是好久没见到了。

海上跟陆地上都可以劫掠,维京人就是干这个的

混入人群,老玩家会很熟悉

解锁和升级家园是游戏发售前被重点提到的玩法,其中有一些系列前作的影子,也有所发展。简单来说,抵达英格兰之后,我们在游戏里有了一个大本营,里面规划有各种设施,从兵营、马厩、烘焙坊,到贸易站、酿酒厂、猎人小屋,还有无形者据点,甚至博物馆,什么都有,只不过刚开始一穷二白,需要玩家亲手建立和升级这些设施。

比方说,埃沃尔从北地带来了铁匠古纳,但这只是人来了,你必须给他修好了铁匠铺,他才能开始打铁,为玩家提升装备。还有一些设施建立后提供生命值等增益效果或提供奖励,也有的设施建立后会开启新的任务线,有的任务还需要去新地图完成……

总之,这个家园里可以玩到的东西还是挺多的。

猎杀上古维序者的任务需要无形者据点开放后才能进行

可以说是有些意外,在上述细节调整的累积之下,《刺客信条:英灵殿》相比系列前作的变化还是很多的,至少比我想象中要多。而且这种变化并不是线性的,它不只是针对《刺客信条:奥德赛》的变化、改进,也不仅是回到《刺客信条:起源》的舒适圈里,而是在有一定创新的同时融入了许多系列前作的东西(某些元素的来源还很久远),成了一个新旧结合的集大成者。

从RPG化回潮,新作为此做了些什么

现在的问题是,这种新和旧的结合效果好吗?或者说,游戏设计者们为什么要这么做?探讨这个问题,可能必须联系到这几年育碧游戏总体上在RPG化的趋势。

回顾“刺客信条”系列的历史,从初代的“骑马模拟器”到2代的Ezio三部曲是一个玩法上的巨大飞跃,从《刺客信条:黑旗》到《刺客信条:枭雄》,事后来看是缓慢的进化,在为后来的RPG化做准备,自《刺客信条:起源》开始的两代才是RPG化真正的实施者。

和RPG化相伴的是规模的扩大,系列每一代的地图都在不断扩大,从半开放到开放世界,从城市开放世界到大片的国家和地区,如此庞大的规模需要足够的东西去填充,但至少从《刺客信条:起源》来看,是没有足够东西填充的,许多沙漠地图借助背景设定只是设计了几个可有可无的据点。庞大的规模导致故事元素被冲淡,随机或每日任务又不可能让玩家真正保持兴趣,设计者加入了大量重复性的宝箱、要塞……没有人真正喜欢这样。

《刺客信条:奥德赛》DLC里一个常见的要塞,打通本体加所有DLC之后,我升到了77级

必须指出的是,RPG化不是原罪,低劣的RPG化、只片面追求数值和刷刷刷的RPG化才是,但也许育碧不想再过多地跟RPG几个字发生联系了,至少暂时如此。《幽灵行动:断点》的发售可能是个转折点,对这款游戏的批评让育碧采取了一些保守措施,开发者老早就放出话来说,《刺客信条:英灵殿》中不会有等级的概念。那时人们纷纷猜测,游戏里可能不会有“硬”等级,但可能有“软”的等级,现在来看基本上属实。

新作中的确取消了等级概念,但保留了经验值,经验值累积之后,在一定情况下转化为技能点数,还有的技能点数是特定目标给予的,玩家使用技能点数获得力量值,力量值对一些游戏内容作出了限制,力量值要求过高的劫掠或任务暂时无法进行。

每个技能点数还能解锁技能树上的一颗星。解锁一颗小星,可以为角色增加生命、攻击、防御等各种属性,解锁若干颗小星之后可以解锁一颗大星,只有大星对应特殊的“技能”,有主动技能也有被动的,所以这个技能树跟前两代有很大不同。另外,需要靠组合键发动的特殊“能力”不是通过技能树点出来,而是在游戏中依靠收集直接获取,在额外的能力界面上指定后使用,这就跟等级完全没关系了。

技能树设计成星座的样子,只有主星可以解锁能力,其他星星只提供属性加成

能力界面,玩过前代的朋友应该很熟悉了

以上这些设计,可以说是“软”的等级,也可以说是把等级和角色半脱钩了,即使不去刻意刷刷刷,也能玩得了游戏中的大部分内容,不得到那么多的特殊能力,勉勉强强游戏也能打得过去。当你把前面的任务完成得差不多了,你的力量值差不多也达到了接下来任务的最低要求。

与此相对应,游戏中重复性的据点、要塞少了很多。要塞里不再显示需要完成的各种目标,仅仅在地图上标出宝箱、装备的位置,大多数据点、要塞还对应任务,所以多数时候跟着流程走就行了,实在需要升级的时候再出去单独刷刷也不迟。

武器和装备乍看起来跟前两代差不多。武器有单手、双手之分(单手武器可以同时持盾),有匕首、短斧等相对攻速快的武器,也有链锤、双手斧这样攻速慢但杀伤力巨大的。武器和装备带各种属性和效果(比如暴击后着火),可以升级,可以添加符文,甚至添加多个符文,但武器和装备的规模大大缩减了,武器和装备的品质也令人迷惑,在Ubisoft Connect中兑换的传奇弓,属性还不如游戏里的白色初始武器。

前两代的一大特色就是开箱必出武器,游戏后期武器多到爆炸,没什么用,还占负重,都是拆解了作为材料的。本作中的武器和装备则少得可怜,如果不刻意去刷,可能打了好几个章节,身上穿的还是剧情送的渡鸦套装,斧子也只有几把能用。即使刻意去刷,看地图上标记的数量,能收集的好货也并不多。

前作中的武器和装备很多,但一般来说有用的还是带额外加成的套装

不刻意去刷的话,目前为止我只拿到了这些武器

总而言之,《刺客信条:英灵殿》很明显在RPG化的高速路上踩了刹车,并且后退了几步,让自己更像是一个动作游戏。很难说这种处理是好是坏,是不是走进了另一个极端。制作者的思路是清楚的,就是不再让武器泛滥,但由奢入俭难,一下子砍到这么少,许多本来不错的东西也没有了,有点打击刚刚培养起的刷装备的乐趣——如果在前作和本作之间找个平衡或许更好。

世界探索,也许是另一个方向

既然在RPG上做了减法,那么自然得做一些加法去填充游戏内容。《刺客信条:英灵殿》做了一些加法,而且这些加法还真不是那么取巧的。

首先,带随机组合的日常任务当然可以填充内容,但所有人都知道这玩意填多了可能影响游戏观感,所以本作里的随机任务比较适可而止,而且开发者请出了老相识瑞达来发送日常任务,也聊为增加了一丝欢乐感。

瑞达的建模看起来都没有改过

其次,是探索。本作中被归为“探索”的支线多了很多。在世界地图的图例上查看“探索”一项,下面列出了20多种可以探索的项目,一些事是装备、能力、书页、文物等可收集的东西,可以结合一些支线任务去做,并不算太枯燥。谜题类的支线探索也不算少,立石谜题、石冢挑战,等等,还有延续前一代的上古维序者,需要先收集线索再锁定对象,一个个去暗杀。

自从昆特牌在游戏界有了名号之后,许多游戏里都加入了打牌环节,对应在《刺客信条:英灵殿》里就是“命运骰牌”。详细的规则很难一两句话说清,也不清楚这是不是来自北欧的正宗玩法,不过这个小游戏确实还挺好玩的,每到一个新的营地和城镇,我会首先看一样地图,上面有没有熟悉的六面骰子标志……

还有,你听说过“斗句”吗?你可以理解为现场对诗,或者说,更像是说唱者之间的押韵,你可以满世界找人押韵斗嘴,逐渐成长为成一代押韵鬼才。斗句的胜利可以提升角色的魅力值,达到一定数值可以在特定任务里开启额外对话选项……

可以探索的东西非常之多……

一局命运骰牌,规则并不复杂,掷骰子之后尽量把带额外效果的牌先摆出去就对了

斗句一般是连对3句,赢两句就可以了,对押韵很自信的话可以赌个大的

“世界事件”也是重要的探索。世界地图上以浅蓝色图标标志出了相当多的世界事件,真的相当多,分布很广且题材多样,即使不刻意去找,只是在地图上随意漫游也能碰到许多。它们触发之后大多有短小精悍的剧情,完成方式有时候比较考验脑洞——孱弱的诗人请你帮忙猎杀一只野兽,不合的兄弟俩请你帮忙解决家庭纠纷,要不就是美丽的女子请你帮忙找东西,找到了就可以跟她一起享受“黑画面”……有些故事还分几集呈现,是有后续的。

这些要素的添加倒是有迹可循的,系列从前代就加入了“探索模式”,也就是在游戏中不开启任务导航,不直接标出任务和NPC的地点,你需要自行收集线索了解任务和人物的具体位置,这是非常具有角色扮演精神的游戏方式。

还可以列举一些细节,比如渡鸦不再标记敌人和NPC的准确位置,而是用奥丁视野标出大致范围,这也是为了增强探索感。

大概如此,尽管削弱了等级和武装、装备,但游戏里加入了一些“比较RPG”的设计,这些内容没办法取巧,算是比较扎实地做出来了,从结果看,这么做塑造了世界气氛,让你觉得这片土地是活的,并不像以前那么枯燥。

世界事件:蠢人传说第一集

可以撸猫

亦可以撸狗

还可以钓鱼……

最后,本作的主线和重要支线任务含量上升。最开始的三四章节里,我几乎都在跟着主线跑,剧情体验比较集中。来到英格兰以后,剧情主要是与各地诸侯结盟,如果按照《刺客信条:大革命》那种路数,解放巴黎的每个地区,或者《刺客信条:奥德赛》的路数,刺杀希腊领袖,那就太无趣了。万幸的是,本作中每个结盟的任务链都很长、很故事性,分了很多章节,打完一个需要耗费不少时间,而且任务风格多样,人物塑造也很鲜明,毛头小子切奥贝特、拉格纳之子伊瓦尔与乌巴,每个人都能过目不忘。

令人印象深刻的是在剑桥为女爵梭玛寻找叛徒的任务:梭玛手下有3个最信任的人,其中一个肯定是叛徒。埃沃尔需要在跟3个当事人对话、跟散落四处的NPC询问口供,也要调查多个事发现场寻找线索,最终形成结论。

这多少有点“逆转裁判”的感觉。任务完全是开放的,系统不会告诉你线索是否已经收集齐了,也不会给你明显的暗示,当你觉得可以,你就可以去指认叛徒。有意思的是,系统甚至不会马上揭晓谜底,而是要等到打过本章的Boss之后才知道你干掉了坏人还是冤杀了好人——这点小腹黑还是挺讨人喜欢的。所以,为了Happy Ending和不必重打一遍,努力收集、谨慎指证吧!

《刺客信条:英灵殿》的主线我还没有打完,这是目前为止给我印象最深的一个任务。

这个故事,我还不能泄露太多……

结论:最平衡的一代“刺客信条”

是的,时间所限,这款游戏我到现在还没有打完,也许打通之后有些结论还会有所调整,目前这只能算是一篇“Review in Progress”,但总体上应该不会有太大变化了。

总体而言,为新旧主机世代承上启下的《刺客信条:英灵殿》里有一些意外之喜,它继承了《刺客信条:起源》的框架,又做出了一些变化,融入了整个系列的闪光点。它的规模相对缩小,在数值上不再更为追求RPG化,这种处理有好的一面,也有的(比如武器和装备)似乎过于精简了,但在世界探索方面实际上又加重了角色扮演的感觉。

为什么会是这样,有几种可能:一种是设计者本来就这么想的,简化等级和武器、装备,强化世界探索;一种可能是,本来他们想要全方位更进一步的RPG化,后来出于可想而知的原因做了妥协,把内容删削和修改成如今的样子。也许还有什么外界想不到的原因,这只有育碧自己知道,总之,不管这是出于制作者的本意,还是因为大环境的变化不得已而为之,从结果看,《刺客信条:英灵殿》的确去除了大量的刷刷刷内容,以更多的剧情任务、收集和世界探索作为补充,这些改变的效果总体还是好的。

说白了就是“偶遇”变多了,时不时有一些小惊喜

“刺客信条”的未来会不会继续RPG化,这谁也不知道,也许在更RPG化的时候,它会更复杂、更精致,也更有趣,这是可能的,不过至少在现在可能性不大。一部分原因是“刺客信条”的年货性质决定的,虽然去年它没有出新作,但每一作的开发周期也就两三年。换个角度看,两三年开发周期的《刺客信条:英灵殿》有这样的完成度,其实还不错——它在动作、冒险、解谜、RPG等等方向之间找到了一个平衡点。

不管是什么原因塑造了如今这样的《刺客信条:英灵殿》,这可能是自2015年《刺客信条:枭雄》之后最好的一部“刺客信条”游戏,它也许为这个系列找到了更准确的定位。

(游戏评测码由育碧提供。)

端一直是距离用户最近的一层,随着产品的日益完善,我们会更加注重用户体验,而前端异常却如鲠在喉,甚是烦人。

一、为什么要处理异常?

异常是不可控的,会影响最终的呈现结果,但是我们有充分的理由去做这样的事情。

  • 1.增强用户体验;
  • 2.远程定位问题;
  • 3.未雨绸缪,及早发现问题;
  • 4.无法复线问题,尤其是移动端,机型,系统都是问题;
  • 5.完善的前端方案,前端监控系统;

对于 JS 而言,我们面对的仅仅只是异常,异常的出现不会直接导致 JS 引擎崩溃,最多只会使当前执行的任务终止。

二、需要处理哪些异常?

对于前端来说,我们可做的异常捕获还真不少。总结一下,大概如下:

  • JS 语法错误、代码异常
  • AJAX 请求异常
  • 静态资源加载异常
  • Promise 异常
  • Iframe 异常
  • 跨域 Script error
  • 崩溃和卡顿

下面我会针对每种具体情况来说明如何处理这些异常。

三、Try-Catch 的误区

try-catch 只能捕获到同步的运行时错误,对语法和异步错误却无能为力,捕获不到。

1.同步运行时错误:

try {
 let name='jartto';
 console.log(nam);
} catch(e) {
 console.log('捕获到异常:',e);
}

输出:

捕获到异常: ReferenceError: nam is not defined
 at <anonymous>:3:15

2.不能捕获到语法错误,我们修改一下代码,删掉一个单引号:

try {
 let name='jartto;
 console.log(nam);
} catch(e) {
 console.log('捕获到异常:',e);
}

输出:

Uncaught SyntaxError: Invalid or unexpected token

不过语法错误在我们开发阶段就可以看到,应该不会顺利上到线上环境。

3.异步错误

try {
setTimeout(()=> {
undefined.map(v=> v);
}, 1000)
} catch(e) {
console.log('捕获到异常:',e);
}

我们看看日志:

Uncaught TypeError: Cannot read property 'map' of undefined
at setTimeout (<anonymous>:3:11)

并没有捕获到异常,这是需要我们特别注意的地方。

四、window.onerror 不是万能的

当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。

/**
* @param {String} message 错误信息
* @param {String} source 出错文件
* @param {Number} lineno 行号
* @param {Number} colno 列号
* @param {Object} error Error对象(对象)
*/
window.onerror=function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}

1.首先试试同步运行时错误

window.onerror=function(message, source, lineno, colno, error) {
// message:错误信息(字符串)。
// source:发生错误的脚本URL(字符串)
// lineno:发生错误的行号(数字)
// colno:发生错误的列号(数字)
// error:Error对象(对象)
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
Jartto;

可以看到,我们捕获到了异常:

2.再试试语法错误呢?

window.onerror=function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
let name='Jartto

控制台打印出了这样的异常:

Uncaught SyntaxError: Invalid or unexpected token

什么,竟然没有捕获到语法错误?

3.怀着忐忑的心,我们最后来试试异步运行时错误:

window.onerror=function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
setTimeout(()=> {
Jartto;
});

控制台输出了:

捕获到异常: {message: "Uncaught ReferenceError: Jartto is not defined", source: "http://127.0.0.1:8001/", lineno: 36, colno: 5, error: ReferenceError: Jartto is not defined
at setTimeout (http://127.0.0.1:8001/:36:5)}

4.接着,我们试试网络请求异常的情况:

<script>
window.onerror=function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
return true;
}
</script>
<img src="./jartto.png">

我们发现,不论是静态资源异常,或者接口异常,错误都无法捕获到。

补充一点:window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx

window.onerror=function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
return true;
}
setTimeout(()=> {
Jartto;
});

控制台就不会再有这样的错误了:

Uncaught ReferenceError: Jartto is not defined
at setTimeout ((index):36)

需要注意:

onerror 最好写在所有 JS 脚本的前面,否则有可能捕获不到错误;

onerror 无法捕获语法错误;

到这里基本就清晰了:在实际的使用过程中,onerror 主要是来捕获预料之外的错误,而 try-catch则是用来在可预见情况下监控特定的错误,两者结合使用更加高效。

问题又来了,捕获不到静态资源加载异常怎么办?

五、window.addEventListener

当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror() 处理函数。这些 error 事件不会向上冒泡到 window ,不过(至少在 Firefox 中)能被单一的window.addEventListener 捕获。

<scritp>
window.addEventListener('error', (error)=> {
console.log('捕获到异常:', error);
}, true)
</script>
<img src="./jartto.png">

控制台输出:

由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。

需要注意:

  • 不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
  • 需要注意避免 addEventListener 重复监听。

六、Promise Catch

在 promise 中使用 catch 可以非常方便的捕获到异步 error ,这个很简单。

没有写 catch 的 Promise 中抛出的错误无法被 onerror 或 try-catch 捕获到,所以我们务必要在 Promise 中不要忘记写 catch 处理抛出的异常。

解决方案: 为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听Uncaught Promise Error。使用方式:

window.addEventListener("unhandledrejection", function(e){
console.log(e);
});

我们继续来尝试一下:

window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
Promise.reject('promise error');

可以看到如下输出:

那如果对 Promise 不进行 catch 呢?

window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
new Promise((resolve, reject)=> {
reject('jartto: promise error');
});

嗯,事实证明,也是会被正常捕获到的。

所以,正如我们上面所说,为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听 Uncaught Promise Error。

补充一点:如果去掉控制台的异常显示,需要加上:

event.preventDefault();

七、VUE errorHandler

Vue.config.errorHandler=(err, vm, info)=> {
console.error('通过vue errorHandler捕获的错误');
console.error(err);
console.error(vm);
console.error(info);
}

八、React 异常捕获

React 16 提供了一个内置函数 componentDidCatch,使用它可以非常简单的获取到 react 下的错误信息

componentDidCatch(error, info) {
console.log(error, info);
}

除此之外,我们可以了解一下:error boundary

UI 的某部分引起的 JS 错误不应该破坏整个程序,为了帮 React 的使用者解决这个问题,React 16 介绍了一种关于错误边界(error boundary)的新观念。

需要注意的是: error boundaries 并不会捕捉下面这些错误。

  • 1.事件处理器
  • 2.异步代码
  • 3.服务端的渲染代码
  • 4.在 error boundaries 区域内的错误

我们来举一个小例子,在下面这个 componentDIdCatch(error,info) 里的类会变成一个 error boundary:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state={ hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}

然后我们像使用普通组件那样使用它:

<ErrorBoundary>
<MyWidget />
</ErrorBoundary>

componentDidCatch() 方法像 JS 的 catch{} 模块一样工作,但是对于组件,只有 class 类型的组件(class component )可以成为一个 error boundaries 。

实际上,大多数情况下我们可以在整个程序中定义一个 error boundary 组件,之后就可以一直使用它了!

九、iframe 异常

对于 iframe 的异常捕获,我们还得借力 window.onerror:

window.onerror=function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}

一个简单的例子可能如下:

<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
window.frames[0].onerror=function (message, source, lineno, colno, error) {
console.log('捕获到 iframe 异常:',{message, source, lineno, colno, error});
return true;
};
</script>

十、Script error

一般情况,如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:

跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性。

<script src="http://jartto.wang/main.js" crossorigin></script>

或者动态去添加 js 脚本:

const script=document.createElement('script');
script.crossOrigin='anonymous';
script.src=url;
document.body.appendChild(script);

特别注意,服务器端需要设置:Access-Control-Allow-Origin

十一、崩溃和卡顿

卡顿也就是网页暂时响应比较慢, JS 可能无法及时执行。但崩溃就不一样了,网页都崩溃了,JS都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢?

崩溃和卡顿也是不可忽视的,也许会导致你的用户流失。

1.利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控。

不错的文章,推荐阅读:Logging Information on Browser Crashes。

window.addEventListener('load', function () {
sessionStorage.setItem('good_exit', 'pending');
setInterval(function () {
sessionStorage.setItem('time_before_crash', new Date().toString());
}, 1000);
});
window.addEventListener('beforeunload', function () {
sessionStorage.setItem('good_exit', 'true');
});
if(sessionStorage.getItem('good_exit') &&
sessionStorage.getItem('good_exit') !=='true') {
/*
insert crash logging code here
*/
alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
}

2.基于以下原因,我们可以使用 Service Worker 来实现网页崩溃的监控:

  • Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
  • Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
  • 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。

十二、错误上报

1.通过 Ajax 发送数据

因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。

2.动态创建 img 标签的形式

function report(error) {
let reportUrl='http://jartto.wang/report';
new Image().src=`${reportUrl}?logs=${error}`;
}

收集异常信息量太多,怎么办?实际中,我们不得不考虑这样一种情况:如果你的网站访问量很大,那么一个必然的错误发送的信息就有很多条,这时候,我们需要设置采集率,从而减缓服务器的压力:

Reporter.send=function(data) {
// 只采集 30%
if(Math.random() < 0.3) {
send(data) // 上报错误信息
}
}

采集率应该通过实际情况来设定,随机数,或者某些用户特征都是不错的选择。

我们开发的时候,经常会遇得到一些网站,头部和尾部一毛一样,这样头部尾部相同的网站,怎么都不可能把头部和尾部重新书写一遍吧,不仅浪费时间还显示的是自己的网站代码重复率比较多,显示自己没有水平。下面长沙前端培训班分享:iframe中的二级菜单被遮盖怎么办:

解决这个问题首先需要我们经常会把这样重复的头部和尾部都单独提出来,制作成一个独立的网页,然后通过iframe框架进行引入。如果公共头部中有对应的一级导航和二级菜单导航的胡被遮住这可怎么解决呢?

各种网站和各种公众号上面方法使用了js,但是js比较麻烦,并且代码粘贴下来使用的时候会出现问题,跑不起来,接下来由我提供给你们一个简单快速高效的解决问题办法。那叫一个so easy;并且关键代码也给出了注释哟,值得你来查看。问题代码:

(一)公共头部----带有二级菜单

HTML源码

<!-- 这里是头部logo区域 -->

<div class="box">这里是logo</div>

<!-- 通栏和导航 -->

<div class="layout">

<ul>

<li>你好

<ul>

<li>你好1</li>

<li>你好2</li>

<li>你好3</li>

</ul>

</li>

<li>我好

<ul>

<li>我好1</li>

<li>我好2</li>

<li>我好3</li>

<li>我好4</li>

</ul>

</li>

<li>大家好

<ul>

<li>大家好1</li>

<li>大家好2</li>

</ul>

</li>

<li>勇哥

<ul>

<li>勇哥1</li>

<li>勇哥2</li>

<li>勇哥3</li>

</ul>

</li>

<li>真的

<ul>

<li>真的1</li>

<li>真的2</li>

<li>真的3</li>

<li>真的4</li>

<li>真的5</li>

</ul>

</li>

<li>很帅

<ul>

<li>真的1</li>

<li>真的2</li>

<li>真的3</li>

<li>真的4</li>

<li>真的5</li>

</ul>

</li>

<li>好帅啊

<ul>

<li>真的1</li>

<li>真的2</li>

<li>真的3</li>

<li>真的4</li>

<li>真的5</li>

</ul>

</li>

<li>太帅了

<ul>

<li>真的1</li>

<li>真的2</li>

<li>真的3</li>

<li>真的4</li>

<li>真的5</li>

</ul>

</li>

<li>真的哦

<ul>

<li>真的1</li>

<li>真的2</li>

<li>真的3</li>

<li>真的4</li>

<li>真的5</li>

</ul>

</li>

<li>结束了

<ul>

<li>真的1</li>

<li>真的2</li>

<li>真的3</li>

<li>真的4</li>

<li>真的5</li>

</ul>

</li>

</ul>

</div>

CSS源码

<style>

*{margin:0;padding:0}

ul{list-style:none}

a{text-decoration: none;}

img{vertical-align: middle;}

.box{

width:1000px;

height:100px;

background-color: pink;

margin:0 auto;

font-size:50px;

font-weight: bold;

text-align: center;

line-height: 100px;

}

.layout{

height:60px;

background-color:cornflowerblue;

}

.layout>ul{

width: 1000px;

margin: 0 auto;

height:60px;

}

.layout>ul>li{

font-size:14px;

float: left;

width:100px;

text-align: center;

line-height: 60px;

border-right:1px dashed white;

box-sizing: border-box;

color:white

}

.layout>ul>li:last-child{

border-right:0px

}

.layout>ul>li>ul{

background-color: pink;

color:white;display: none;

}

.layout>ul>li:hover>ul{

display: block;

position: relative;

}

/* 取消滚动条 */

::-webkit-scrollbar{

display: none;

}

</style>

效果:

(二)公共主体----需要引入头部

HTML源码

<!-- 引入头部 -->

<iframe id="one" src="01-header.html" frameborder="0" width="100%" height="160px"frameborder="0"></iframe>

<!-- 独立主体 -->

<div class="tip" wmode="transparent">

我是独立的页面主体部分

</div>

<!-- 引入尾部 -->

<iframe src="03-footer.html" frameborder="0" frameborder="0" width="100%" height="100px"frameborder="0"></iframe>

CSS源码

*{margin:0;padding:0}

.tip{

height:500px;

background-color: yellowgreen;

text-align: center;

line-height: 500px;

font-size:80px;

font-weight:bold;

}

/*取消3像素间距*/

iframe{vertical-align: middle;}

/*取消滚动条*/

::-webkit-scrollbar{

display: none;

}

效果:

问题所在

描述:引入公共的头部之后,二级菜单,被主体区域内容给遮盖住了,使用js实现起来也是非常的困难的;

解决问题:描述如下

将在主体引入的头部页面中,引入的顺序改变一下,放在主体的后面;放在主体后面的话加载顺序就会出现问题,主体就会显示在主体后面。那如何调整顺序问题呢?使用固定定位,定位在引入页面的最上面,但是定位后也会把布局遮盖住,如何解决遮盖主体的问题呢,就是需要给主体添加margin-top;来解决问题,这样二级菜单就能显示出来了

修改后的主体代码:HTML

<!-- 独立主体 -->

<div class="tip" wmode="transparent">

我是独立的页面主体部分

</div>

<!-- 引入头部 -->

<iframe id="one" src="01-header.html" frameborder="0" width="100%" height="160px"frameborder="0"></iframe>

<!-- 引入尾部 -->

<iframe src="03-footer.html" frameborder="0" frameborder="0" width="100%" height="100px"frameborder="0"></iframe>

效果:加载顺序

修改代码:CSS

<style>

*{margin:0;padding:0}

.tip{

height:500px;

background-color: yellowgreen;

text-align: center;

line-height: 500px;

font-size:80px;

font-weight:bold;

/*修改的代码*/

margin-top:160px

}

iframe{vertical-align: middle;}

::-webkit-scrollbar{

display: none;

}

/*修改的代码*/

#one{

position:fixed;

top:0px;

width:100%;

height:500px;

}

</style>

效果:

以上就是:通过HTML和CSS来解决,iframe二级菜单被遮住的效果:关键代码就是调整顺序,和添加定位。

相关文章