想当年|“克塞,前来拜访”:30年前特摄片在中国
如果你还记得上面标题中的这句台词的话,恭喜你已经暴露了年龄。“克塞,前来拜访”出自特摄片《恐龙特急克塞号》。1988年,本片在中国上映时曾经红极一时,几乎与同时期的动画片《变形金刚》一起,成为一代人的儿时记忆……
先来的后辈
在中国各电视台播出时,《恐龙特急克塞号》往往被归于“儿童动画片”一类。问题在于,虽然它的剧情比所有的卡通片都卡通,然而却是由真人演出的;严格说起来,它却并不能算作动画片,而是非常具有日本特色的“特摄片”。日语里的“特撮”二字是和制汉语,原本为“特殊摄影技术(SFX)”的略称(相当于特殊效果,特效),但后来演变成了特指使用特殊摄影技术所拍摄的超现实或者科幻题材影片。特摄片的开山鼻祖,大概算得上是全世界观众都很熟悉(并且在最近红上太空成为星座名称)的“哥斯拉”。
《哥斯拉》
1954年,美国在比基尼岛进行氢弹试验,在警戒线之外的日本渔船“第五福龙丸号”被放射性粉尘污染,部分船员回国半年不到就因此身亡。这一事件在日本称为“第三次核伤害”,也直接催生了《哥斯拉》的灵感。同年年底东宝电影公司拍摄的《哥斯拉》上映,电影中与恐龙同时代的生物哥斯拉被核弹试爆唤醒,并对人类进行报复。正是在这部影片中,第一次大量使用了特殊摄影技术拍摄,令怪兽的形象显得灵活生动,取得了当时看来出乎意料的视觉效果。从此之后,特摄片逐渐成为日本特有的一道影视风景线——让真人演员穿着各种皮套并配以大量微缩模型拍摄而成的电视剧。即使到了电脑特效早已可以以假乱真的今天,特摄依旧是日本电影中一个有影响力的表现手法,仍然有一部分人坚持用比例模型和皮套道具服来进行表演。
上世纪60-70年代,堪称“特摄片”的盛世。当时的美苏太空竞赛正如火如荼,各个国家的人们都在茶余饭后谈论太空。1963年特摄电影导演圆谷英二离开东宝电影公司,自立门户拍摄特摄怪兽电影,并于1966年拍摄完成了划时代的作品——“空想科学特摄电视剧系列”之《奥特曼》。拥有眼花缭乱超能力的宇宙生物前仆后继地前来地球实施邪恶的侵略计划,而奥特曼作为维护银河和平的使者,不远万里从M78星系远赴地球保护人类。如今《奥特曼》系列已经拍摄了超过50部,在半个世纪之后的今天依然拥有数量颇多的粉丝。
咸蛋超人奥特曼
可惜,这位“咸蛋超人”错过了抢滩登陆中国的先机,以至于上世纪90年代《奥特曼》开始陆续在中国电视台播出时,许多观众的第一反应却是本片与同属“特摄片”的《恐龙特急克塞号》似曾相识……对于《奥特曼》而言,这样的指摘或许有些令人哭笑不得。严格说起来,怎么也应该是《恐龙特急克塞号》看起来像《奥特曼》才对——论起资历,前者实在应该向后者叫上一声“前辈”。
说起来,《恐龙特急克塞号》的诞生也算是机缘巧合。1970 年之前,圆谷公司的《奥特曼》系列在本土特摄界一直处在龙头老大地位。但随着东映公司1971年制作的《假面骑士》系列及1975年的《超级战队》系列的播出,《雷欧·奥特曼》的收视率惨遭滑铁卢,迫使圆古公司改弦更张,制作了“恐龙三部曲”。其中的第一部《恐龙探险队》和第二部《恐龙大战争》属于采用沙盘及微缩模型配合动画角色的特摄片,播出后获得意外好评,也使观众发出了“希望看到完全特摄”的呼声。圆古公司在1978年趁势推出了系列的第三部,这就是采用“真人+模型”传统方式拍摄的《恐龙特急克塞号》(恐竜戦隊コセイドン)。到了上世纪八十年代中期,中国山西电视台引进并译制了这部片子,遂令《恐龙特急克塞号》抢在《奥特曼》之前捷足先登,先入为主式地定义了中国观众对于“特摄片”的理解。
恐龙噱头与“五毛特效”
话说回来,《恐龙特急克塞号》毕竟有其独到之处。其剧情与《奥特曼》系列“空想科学”特摄片大不相同。《恐龙特急克塞号》在日本有着“本格科幻”特摄片之称,设定十分严谨。本片的时间背景首先设定在2001年(对于上世纪70年代的观众而言属于遥远的未来,但21世纪已经过去了将近五分之一,生活仍然如此平淡。早知道这样,编剧应该把时间定得再晚一点)。2001年,人类发现了超光速粒子,从而进一步研究出能够跨越时空的引擎技术,日本时空管理局凭借这个技术组建了最强的队伍——能够进行时间旅行的“时代战士”。其中,队员格乌能够通过“人间大炮”变身为拥有无穷力量的超人——“克塞”战士:一身红衣服的打扮和“克塞前来拜访”的招牌动作正是《恐龙特急克塞号》留给观众最为深刻的印象之一。
“时代战士”们
本片十分讨巧地将“时间旅行”的目的地设定为7000万前的白垩纪,从而引出了在一代代少年儿童中始终魅力不减的远古生物——恐龙。尽管从今天的角度看,片中的“恐龙”一望既知为模型或是真人扮演,但在上世纪80年代,《恐龙特急克塞号》却依旧是国内荧幕上为数不多的能够看到“活生生”的恐龙的节目,并让许多儿童第一次知道了“白垩纪”这个拗口的地质名词。顺便提一句,当时国内也经制作过一部名为《漫游恐龙世界》的科普动画片,其中出现了包括霸王龙、始祖鸟、蛇颈龙等多种远古生物,惜乎片长不过20分钟,且再无下文。
《恐龙特急克塞号》中的恐龙
除了恐龙的噱头之外,《恐龙特急克塞号》的故事情节亦不乏值得称道之处。《恐龙特急克塞号》的剧本由参与过“杰克·奥特曼”和“艾斯·奥特曼”的长坂秀佳编写,当时他正在进行《时空警备队——恐龙时代》的创作,接到邀请后,便根据这个漫画中的故事设定,创作了《恐龙特急克塞号》,因此,《恐龙特急克塞号》里的大部分内容都能在那部动画片中找到原型。其整个52集的故事分两个部分:1-28集是对抗企图霸占白垩纪地球的侵略者“格德米斯”——一种“外星高等植物生命体(号称寿命达200-300年,何必来地球寻死)”;29-52集则是回归到“时空管理局”最初的刑事使命,敌人也变得形形色色,诸如妄图改写人类历史的野心家、利用时间机器进行犯罪活动的犯罪集团、不明来历的侵略者等等。故事中最为精彩的设定出现在最后两集,反派的终极Boss,巨大的黑星妄图吸干白垩纪地球的能量,时代战士与之进行了殊死搏斗之后终于将其消灭,但白垩纪地球也在战斗中遭到严重破坏,这场浩劫导致了恐龙灭绝!这正是将科幻与现实结合起来的绝佳范例。
反派之一的“格德米斯”
与至今观来显得创意十足的剧情相比,《恐龙特急克塞号》中那些40年前的“特效”今天看来就显得十分落伍了,甚至完全可以在此之前加上“五毛”两字。在故事设定中,克塞号飞船体内收纳着霞光号和闪电号,外出执行任务的时候,二者都可以分离为I号、II号共4台载具,其中的“霞光I号”更是威力巨大的武器——车身上部搭载的“披风炮”不止一次重创“格德米斯”的母舰。但如今可以很清楚地发现,那些在剧中号称人类尖端科技的飞船、战车,其实只不过些制作简陋的模型罢了。
战斗中的“五毛特效”
花香于墙外
但在1988年的中国, 这一缺憾并未阻止《恐龙特急克塞号》迅速成为全国儿童的宠儿,变成每天必修的功课。当时的首都北京甚至流行起了这样的民谣:“头戴《‘克塞’》帽,《(变形)金刚》怀里抱,晚间看《(猫和)老鼠》,《(崇明的)一休》陪睡觉”。彼时的一些评论,往往将《恐龙特急克塞号》与《变形金刚》相提并论,用“这两部不以情、趣取胜的电视剧,却磁石般地吸引着今天的少儿观众”的现实提出质疑:国内儿童电视剧是否也应具有“说教气息”之外的新观念、新思维?
这可以说是将《恐龙特急克塞号》“抬举”到了一个在原产地(日本)根本不具有的地位。就在《恐龙特急克塞号》在日本播出的同一时期,日本动漫产业迎来了一次具有里程碑意义的事件。东急公司制作的电影动画《宇宙战舰大和号》获得了巨大的票房成功。在两个多月的公映时间里,日本全国共有270万人观看,票房收入达到创纪录的21亿日元。1977 年也成为日本动漫产业的一道分水岭,“大和之前”冷清和“大和之后”的火爆形成了鲜明对照,随着动漫产业势不可挡般兴起,日本的特摄片巅峰时代一去而不复返了。迟至1978年才姗姗来迟的《恐龙特急克塞号》也因此迅速在日本市场归于平淡,成为一部连“续作”也不配拥有的寻常作品。与《奥特曼》系列的痼疾类似,《恐龙特急克塞号》的剧情模式一成不变,不论反派一开始多么嚣张,取得最后胜利的总是“时代战士”——“克塞”的绝招就是“时间停止”,可以在对方无法动作的情况下做任何事情,即使在当年的小观众看来,这也已经形如“作弊”。当最初对影片的好奇心散失殆尽后,观众自然会出现“审美疲劳”。
“人间大炮一级准备”
“人间大炮”发射
只有在中国,《恐龙特急克塞号》才成为了无数观众津津乐道的“现象级”作品。究其原因,除了风格独树一帜的特摄片本身所带来的新奇感之外,出色的配音无疑为《恐龙特急克塞号》增色不少。据说当年山西电视台引进该片的时候,为了避免声音重复,共起用了50多名配音演员,在认真揣摩原版中演员的性格和声线之后,十分用心地演绎着这些角色。
片中最为经典的台词莫过于这一幕:当“霞光I号”车身向上呈25度角抬起的时候,伴随着特茨队员“人间大炮一级准备,人间大炮二级准备,人间大炮——放(日文原文:ファイタスボンバー,ホップ!ステップ! ジャンプ! )”的经典台词,格乌队员瞬间变身成一袭红衣超人“克塞”立在敌人面前,摆着很拉风的 POSE 喊道:“克塞!前来拜访(コセイドン参上)。”其影响所及,就是当时国内满街的男孩,几乎个个头上都带着“克塞”式头盔,在彼此戏谑中高喊“克塞前来拜访”或是“时间停止”……就这样,《恐龙特急克塞号》墙内(日本)开花墙外(中国)香,为日本特摄片带来了在中国的春天。几年后《奥特曼》系列在中国的热播,恐怕也要感谢《恐龙特急克塞号》的筚路蓝缕。
“克塞,前来拜访”
JavaScript错误处理权威指南
作者|Lukas Gisder-Dubé
译者|谢丽
本文将分三部分分析 JavaScript 中的错误,首先我们将了解错误的一般情况,之后,我们将关注后端(Node.js + Express.js),最后,我们将重点看下如何处理 React.js 中的错误。选择这些框架,是因为它们是目前最流行的,但是,你应该也能够将这些新发现应用到其他框架中吧!
继上一篇文章 () 之后,我想谈谈错误。错误很好——我相信你以前听过这个说法。乍一看,我们害怕错误,因为错误往往会涉及到在公共场合受到伤害或羞辱。通过犯错误,我们实际上学会了如何不去做某事,以及下次如何做得更好。
显然,这是关于从现实生活的错误中学习。编程中的错误有点不同。它们为我们提供了很好的特征来改进我们的代码,并告诉用户什么地方出了问题(也可能是教他们如何修复它)。
GitHub() 上提供了一个完整的样例项目。
JavaScript 错误和一般处理throw new Error('something went wrong')?会在 JavaScript 中创建一个错误实例,并停止脚本的执行,除非你对错误做了一些处理。当你作为 JavaScript 开发者开启自己的职业生涯时,你自己很可能不会这样做,但是,你已经从其他库(或运行时)那里看到了,例如,类似“ReferenceError: fs 未定义”这样的错误。
Error 对象
Error 对象有两个内置属性供我们使用。第一个是消息,作为参数传递给 Error 构造函数,例如 new Error(“这是错误消息”)。你可以通过 message 属性访问消息:
const myError = new Error(‘请改进代码’)console.log(myError.message) // 请改进代码
第二个是错误堆栈跟踪,这个属性非常重要。你可以通过 stack 属性访问它。错误堆栈将为你提供历史记录(调用堆栈),从中可以查看哪个文件导致了错误。堆栈的上部也包括消息,然后是实际的堆栈,从距离错误发生最近的点开始,一直到最外层“需要为错误负责”的文件:
Error: 请改进代码 at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
抛出和处理错误
现在,Error 实例本身不会导致任何结果,例如,new Error('...') 不会做任何事情。当错误被抛出时,就会变得更有趣。然后,如前所述,脚本将停止执行,除非你在流程中以某种方式对它进行了处理。记住,是手动抛出错误,还是由库抛出错误,甚至由运行时本身(Node 或浏览器),都没有关系。让我们看看如何在不同的场景中处理这些错误。
try .... catch
这是最简单但经常被遗忘的错误处理方法——多亏 async / await,它的使用现在又多了起来。它可以用来捕获任何类型的同步错误,例如,如果我们不把 console.log(b) 放在一个 try … catch 块中,脚本会停止执行。
… finally
有时候,不管是否有错误,代码都需要执行。你可以使用第三个可选块 finally。通常,这与在 try…catch 语句后面加一行代码是一样的,但它有时很有用。
异步性——回调
异步性,这是在使用 JavaScript 时必须考虑的一个主题。当你有一个异步函数,并且该函数内部发生错误时,你的脚本将继续执行,因此,不会立即出现任何错误。当使用回调函数处理异步函数时(不推荐),你通常会在回调函数中收到两个参数,如下所示:
如果有错误,err 参数就等同于那个错误。如果没有,参数将是 undefined 或 null。要么在 if(err) 块中返回某项内容,要么将其他指令封装在 else 块中,这一点很重要,否则你可能会得到另一个错误,例如,result 可能未定义,而你试图访问 result.data,类似这样的情况。
异步性——Promises
处理异步性的更好方法是使用 Promises。在这一点上,除了代码可读性更强之外,我们还改进了错误处理。只要有一个 catch 块,我们就不再需要太关注具体的错误捕获。在链接 Promises 时,catch 块捕获会自 Promises 执行或上一个 catch 块以来的所有错误。注意,没有 catch 块的 Promises 不会终止脚本,但会给你一条可读性较差的消息,比如:
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
因此,务必要在 Promises 中加入 catch 块。
回到 try … catch
随着 JavaScript 引入 async / await,我们回到了最初的错误处理方法,借助 try … catch … finally,错误处理变得非常简单。
因为这和我们处理“普通”同步错误的方法是一样的,所以如果需要的话,更容易使用作用域更大的 catch 语句。
服务器端错误的产生和处理现在,我们已经有了处理错误的工具,让我们看下,我们在实际情况下能用它们做什么。后端错误的产生和处理是应用程序至关重要的组成部分。对于错误处理,有不同的方法。我将向你展示一个自定义 Error 构造函数和错误代码的方法,我们可以轻松地传递到前端或任何 API 消费者。构建后端的细节并不重要,基本思路不变。
我们将使用 Express.js 作为路由框架。让我们考虑下最有效的错误处理结构。我们希望:
一般错误处理,如某种回退,基本上只是说:“有错误,请再试一次或联系我们”。这并不是特别聪明,但至少通知用户,有地方错了——而不是无限加载或进行类似地处理。特殊错误处理为用户提供详细信息,让用户了解有什么问题以及如何解决它,例如,有信息丢失,数据库中的条目已经存在等等。构建一个自定义 Error 构造函数
我们将使用已有的 Error 构造函数并扩展它。继承在 JavaScript 中是一件危险的事情,但根据我的经验,在这种情况下,它非常有用。我们为什么需要它?我们仍然希望堆栈跟踪给我们一个很好的调试体验。扩展 JavaScript 原生 Error 构造函数可以让我们方便地获得堆栈跟踪。我们唯一要做的是添加代码(我们稍后可以通过错误代码访问)和要传递给前端的状态(http 状态代码)。
如何处理路由
在完成 Error 的自定义之后,我们需要设置路由结构。正如我指出的那样,我们想要一个单点真错误处理,就是说,对于每一个路由,我们要有相同的错误处理行为。在默认情况下,由于路由都是封装的,所以 Express 并不真正支持那种方式。
为了解决这个问题,我们可以实现一个路由处理程序,并把实际的路由逻辑定义为普通的函数。这样,如果路由功能(或任何内部函数)抛出一个错误,它将返回到路由处理程序,然后可以传给前端。当后端发生错误时,我们可以用以下格式传递一个响应给前端——比如一个 JSON API:
{ error: 'SOME_ERROR_CODE', description: 'Something bad happened. Please try again or contact support.'}
准备好不知所措。当我说下面的话时,我的学生总是生我的气:
如果你咋看之下并不是什么都懂,那没问题。只要使用一段时间,你就会发现为什么要那样。
顺便说一下,这可以称为自上而下的学习,我非常喜欢。
路由处理程序就是这个样子:
我希望你能读下代码中的注释,我认为那比我在这里解释更有意义。现在,让我们看下实际的路由文件是什么样子:
在这些例子中,我没有做任何有实际要求的事情,我只是假设不同的错误场景。例如,GET /city 在第 3 行结束,POST /city 在第 8 号结束等等。这也适用于查询参数,例如,GET /city?startsWith=R。本质上,你会有一个未处理的错误,前端会收到:
{ error: 'GENERIC', description: 'Something went wrong. Please try again or contact support.'}
或者你将手动抛出 CustomError,例如:
throw new CustomError('MY_CODE', 400, 'Error description')
上述代码会转换成:
{ error: 'MY_CODE', description: 'Error description'}
既然我们有了这个漂亮的后端设置,我们就不会再把错误日志泄漏到前端,而总是返回有用的信息,说明出现了什么问题。
确保你已经在 GitHub() 上看过完整的库。你可以把它用在任何项目中,并根据自己的需要来修改它!
向用户显示错误下一个也是最后一个步骤是管理前端的错误。这里,你要使用第一部分描述的工具处理由前端逻辑产生的错误。不过,后端的错误也要显示。首先,让我们看看如何显示错误。如前所述,我们将使用 React 进行演练。
把错误保存在 React 状态中
和其他数据一样,错误和错误消息会变化,因此,你想把它们放在组件状态中。在默认情况下,你想要在加载时重置错误,以便用户第一次看到页面时,不会看到错误。
接下来我们必须澄清的是不同错误类型及与其匹配的可视化表示。就像在后端一样,有 3 种类型:
全局错误,例如,其中一个常见的错误是来自后端,或者用户没有登录等。来自后端的具体错误,例如,用户向后端发送登录凭证。后端答复密码不匹配。前端无法进行此项验证,所以这样的信息只能来自后端。由前端导致的具体错误,例如,电子邮件输入验证失败。2 和 3 非常类似,虽然源头不一样,但如果你愿意,就可以在同样的 state 中处理。我们将从代码中看下如何实现。
我将使用 React 的原生 state 实现,但是,你还可以使用类似 MobX 或 Redux 这样的状态管理系统。
全局错误
通常,我将把这些错误保存在最外层的有状态组件中,并渲染一个静态 UI 元素,这可能是屏幕顶部的一个红色横幅、模态或其他什么东西,设计实现由你决定。
让我们看下代码:
正如你看到的那样,Application.js 中的状态存在错误。我们也有方法可以重置并改变错误的值。我们把值和重置方法传递给 GlobalError 组件,在点击'x'时,该组件会显示错误并重置它。让我们看看 GlobalError 组件:
你可以看到,在第 5 行,如果没有错误,我们就不做任何渲染。这可以防止我们的页面上出现一个空的红框。当然,你可以改变这个组件的外观和行为。例如,你可以将“x”替换为 Timeout,几秒钟后重置错误状态。
现在,你已经准备好在任何地方使用全局错误状态了,只是从 Application.js 把 _setError 向下传递,而且,你可以设置全局错误,例如,当一个请求从后端返回了字段 error: 'GENERIC'。例如:
如果你比较懒,到这里就可以结束了。即使你有具体的错误,你总是可以改变全局错误状态,并把错误提示框显示在页面顶部。不过,我将向你展示如何处理和显示具体的错误。为什么?首先,这是关于错误处理的权威指南,所以我不能停在这里。其次,如果你只是把所有的错误都作为全局错误来显示,那么体验人员会疯掉。
处理具体的请求错误
和全局错误类似,我们也有位于其他组件内部的局部错误状态,过程相同:
有件事要记住,清除错误通常有一个不同的触发器。用' x '删除错误是没有意义的。关于这一点,在发出新请求时清除错误会更有意义。你还可以在用户进行更改时清除错误,例如当修改输入值时。
源于前端的错误
如前所述,这些错误可以使用与处理后端具体错误相同的方式(状态)进行处理。这次,我们将使用一个有输入字段的示例,只允许用户在实际提供以下输入时删除一个城市:
使用错误代码实现错误国际化
也许你一直想知道为什么我们有这些错误代码,例如 GENERIC ,我们只是显示从后端传递过来的错误描述。现在,随着你的应用越来越大,你就会希望征服新的市场,并在某个时候面临多种语言支持的问题。如果你到了这个时候,你就可以使用前面提到的错误代码使用用户的语言来显示恰当的描述。
我希望你对如何处理错误有了一些了解。忘掉 console.error(err),它现在已经是过去时了。可以使用它进行调试,但它不应该出现在最终的产品构建中。为了防止这种情况,我建议你使用日志库,我过去一直使用 loglevel,我对它非常满意。
英文原文