“安靜,你們這些眾神的子嗣……好好聽我說時間開始的故事。那時一片漆黑,沒有沙、沒有海,也無天無地、無草無風。直到烈火遇上了極冰,打破了這一道虛無。而巨人尤彌爾從一聲尖叫之中誕生了,成為萬物之始……自負的尤彌爾遭到殘忍的殺害,然而他的頭骨、血液與大腦構成了這個世界……這個你們行走與四處征戰的世界……”
伴隨先知的低語,“刺客信條”首個以北歐視角展開的系列作《刺客信條:英靈殿》拉開了帷幕。過去數天里,我扮演的維京人埃沃爾奔波于斯堪的納維亞半島的風雪之中,也奔馳在廣袤又郁郁蔥蔥的英格蘭原野之上。
很大程度上,這一切是令人熟悉的,這一代“刺客信條”仍是我們所認知的那個“刺客信條”,你在其中可以找到許多系列舊有的元素——無論是好的還是不好的,當然還是好的居多。
與此同時,也可以見到許多變化。不僅是相對于更久遠的那些系列前作,相對于更近一些的《刺客信條:起源》《刺客信條:奧德賽》,本作當中也有讓人意外的改變之處。在評價上述兩款游戲時我曾經提到過,“刺客信條”系列正在明顯地向更大規模、更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化的趨勢。
回顧“刺客信條”系列的歷史,從初代的“騎馬模擬器”到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年《刺客信條:梟雄》之后最好的一部“刺客信條”游戲,它也許為這個系列找到了更準確的定位。
(游戲評測碼由育碧提供。)
端一直是距離用戶最近的一層,隨著產品的日益完善,我們會更加注重用戶體驗,而前端異常卻如鯁在喉,甚是煩人。
一、為什么要處理異常?
異常是不可控的,會影響最終的呈現結果,但是我們有充分的理由去做這樣的事情。
對于 JS 而言,我們面對的僅僅只是異常,異常的出現不會直接導致 JS 引擎崩潰,最多只會使當前執行的任務終止。
二、需要處理哪些異常?
對于前端來說,我們可做的異常捕獲還真不少。總結一下,大概如下:
下面我會針對每種具體情況來說明如何處理這些異常。
三、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 等等,所以還需要配合服務端日志才進行排查分析才可以。
需要注意:
六、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 并不會捕捉下面這些錯誤。
我們來舉一個小例子,在下面這個 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 來實現網頁崩潰的監控:
十二、錯誤上報
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二級菜單被遮住的效果:關鍵代碼就是調整順序,和添加定位。
相關文章
*請認真填寫需求信息,我們會在24小時內與您取得聯系。