Maison > interface Web > js tutoriel > le corps du texte

守护 Javascript 中的函数参数

黄舟
Libérer: 2017-02-22 13:15:01
original
1307 Les gens l'ont consulté

作为开发者,我们花费许多时间来调试,尤其是在发现问题来源方面。开发工具指导我们追踪调用栈,但是追踪过程仍然相当耗时,尤其在遇到级联异步调用的时候。这一问题在很早以前就被发现了。

假设我们有一个从不同文档结构中搜索包含指定字符串的元素的函数。我们使用以下看起来合法的调用:

grep( "substring", tree );
Copier après la connexion

但是我们并没有得到期望的结果。按照以往的经验,我们会花费一些时间来检查给定的树形文档结构,时间有可能会很长。然后我们很可能会做其他的检查,但是在最终,我们会从函数代码中发现传入的参数顺序反了。这样看来的话,我们只要关注函数的参数,就不会发生上面的错误。

function grep( tree, substring ){ if ( !( tree instanceof Tree ) ) { throw TypeError( "Invalid tree parameter" );

  } if ( typeof substring !== "string" ) { throw TypeError( "Invalid substring parameter" );

  } //... }
Copier après la connexion

这种验证方式是 Design by Contract approach 的一部分。它在软件组成部分中列出了需要验证的前置条件和后置条件。在以上示例中,我们必须测试函数输入参数符合指定的格式(比较第一个参数符合树文档的类型,第二个参数符合字符串类型)同时我们建议检查函数输出类型是否是一个字符串。

但是,Javascript目前为止还没有其他语言那样内置的功能作为函数入口和结束处的验证。对于一个示例,PHP语言有类型提示:

<?php function grep( Tree $tree, string $substring ): string {}
Copier après la connexion

TypeScript 有严格类型:

function grep( tree: Tree, substring: string ): string {}
Copier après la connexion

此外,它还支持高级类型(联合类型,可选类型,交叉类型,泛型等等):

function normalize( numberLike: number | string, modifier?: boolean ): string {}
Copier après la connexion

根据在ES规范中提出来得特性,今后会有一个叫做 Guards 的功能,它建议使用下面的语法:

function grep( tree:: Tree, substring:: String ):: String {}
Copier après la connexion

目前为止在Javascript中,我们必须使用外部库或者可转换的编译器来解决这一问题。但是,可用的资源较少。最老的库是 Cerny.js 。它类似于DbC(数据库计算机),强大且灵活:

var NewMath = {};

(function() { var check = CERNY.check; var pre = CERNY.pre; var method = CERNY.method; // The new pision function pide(a,b) { return a / b;

    }

    method(NewMath, "pide", pide); // The precondition for a pision pre(pide, function(a,b) {

       check(b !== 0, "b may not be 0");

    });

})();
Copier après la connexion

但是对我而言,它读起来很复杂。我更喜欢使用简洁干净的方式校验前提条件/后置条件即可。Contractual 提供的语法很符合我的要求:

function pide ( a, b ) {

  pre: typeof a === "number"; typeof b === "number";

    b !== 0, "May not pide by zero";

  main: return a / b;

  post:

    __result < a;

}

alert(pide(10, 0));
Copier après la connexion

除了不是Javascript之外,看起来都很不错。如果你需要使用的话,必须用 Contractual或者 Babel Contracts 把源代码编译成Javascript。我不反对跨语言编译器,但是如果让我选择的话,我宁愿用 TypeScript。

但是回到Javascript,不知道你有没有发现,除了相关库和框架外,我们在注释函数和类的时候一直在用 JSDoc 描述函数入口和返回处的格式对比。如果文档注释可以用来验证格式的话就太好了。正如你所理解的,它离不开编译器。但是,我们可以使用依赖于Jascript文档表达式的库。幸运的是, byContract 就是这样的库。 byContract 的语法看起来像这样:

/**
 * @param {number|string} sum
 * @param {Object.<string, string>} dictionary
 * @param {function} transformer
 * @returns {HTMLElement}
 */ function makeTotalElement( sum, dictionary, transformer ) { // Test if the contract is respected at entry point byContract( arguments, [ "number|string", 
 "Object.<string, string>", "function" ] ); // .. var res = document.createElement( "p" ); // .. 
 // Test if the contract is respected at exit point return byContract( res, "HTMLElement" );
} // Test it var el1 = makeTotalElement( 100, { foo: "foo" }, function(){}); // ok var el2 = makeTotalElement( 100, { foo: 100 }, function(){}); // exception
Copier après la connexion

如你所见,我们可以从文档注释处复制/粘贴指定的类型到 byContract 然后进行对比,就这么简单。下面我们更仔细地检查以下 。 byContract 可以被当做UMD模块(AMD或者CommonJS)或者全局变量来访问。我们可以把值 /Javascript 文档表达式作为一对参数传给 byContract

byContract( value, "JSDOC-EXPRESSION" );
Copier après la connexion

或者值列表对应文档表达式列表作为一对参数也可以:

byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );
Copier après la connexion

byContract 会检测传入的值 ,如果和对应的 JSDoc 表达式格式不一致,就会抛出 带有像 ` 传入的值违反类型NaN`信息的 byContract.Exception 异常 。

在最简单的案例中,byContract用来验证如 `array`, `string`, `undefined`, `boolean`, `function`, `nan`, `null`, `number`, `object`, `regexp`之类的 原型类型:

byContract( true, "boolean" );
Copier après la connexion

当我们需要允许输入值在一个指定类型列表中的时候,可以使用 type union 。

byContract( 100, "string|number|boolean" );
Copier après la connexion

一个函数可以有必填的参数,也可以有可选参数。默认情况下,参数在和原型类型做对比的时候是必填的。但是用'='修饰符我们就可以设置成可选类型。所以 byContract 处理如 `number=` 这样的表达式时候,会转为 `number|undefined`

function foo( bar, baz ) {
  byContract( arguments, [ "number=", "string=" ] );
}
Copier après la connexion

下面是Js文档中 nullable/non-nullable types (可空/不可空类型):

byContract( 42, "?number" ); // a number or null. byContract( 42, "!number" ); // a number, but never null.
Copier après la connexion

当然,我们可以用接口来做比较。这样我们就可以引用作用域范围内任何可用的对象,包括Javascript内置接口:

var instance = new Date();
byContract( instance, "Date" );
byContract( view, "Backbone.NativeView" );
byContract( e, "Event" );
Copier après la connexion

对于数组和对象,我们可以有选择性地验证其内容。比如可以验证所有数组的值必须是数字或者所有的对象的键和值是字符串类型:

byContract( [ 1, 1 ], "Array.<number>" );
byContract( { foo: "foo", bar: "bar" }, "Object.<string, string>" );
Copier après la connexion

以上的验证对线性数据结构有用,其他情况下就不起作用了。所以同样的,我们可以创建一个 type definition (类型定义)来描述对象的内容(参考byContract类型定义)然后在后面作为一个类型引用它即可。

byContract.typedef( "Hero", {
  hasSuperhumanStrength: "boolean",
  hasWaterbreathing: "boolean" }); var superman = {
  hasSuperhumanStrength: true,
  hasWaterbreathing: false };
byContract( superman, "Hero" );
Copier après la connexion

这个示例定义了一个'Hero'类型来表示一个对象/命名空间,必须有boolean类型的 `hasSuperhumanStrength`和`hasWaterbreathing` 属性。

所有的方法都通过类型验证传入的值,但是不变的量(常量)呢?我们可以用一个自定义类型来包装类型约束。比如说检测字符串是不是一个邮件地址类型,我们可以增加这样的验证:

byContract.is.email = function( val ){ var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|
(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test( val );
}
byContract( "john.snow@got.com", "email" ); // ok byContract( "bla-bla", "email" ); // Exception!
Copier après la connexion

事实上,你很可能不要用事件来写验证函数,而是用外部库(类似 validator )代替:

byContract.is.email = validator.isEmail;
Copier après la connexion

验证逻辑取决于开发环境。使用 byContract, 我们可以用全局触发器来禁用验证逻辑 :

if ( env !== "dev" ) {
  byContract.isEnabled = false;
}
Copier après la connexion

byContract 是一个很小的验证插件(压缩文件大约1KB大小) ,你可以在你的Javascript代码中使用它从而得到对比编程设计模式的好处。

 

以上就是守护 Javascript 中的函数参数的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal