ホームページ > 記事 > ウェブフロントエンド > Javascriptのガード関数パラメータ
開発者として、私たちはデバッグ、特に問題の原因を発見することに多くの時間を費やします。開発ツールは呼び出しスタックのトレースをガイドしますが、特にカスケード非同期呼び出しが発生した場合、トレース プロセスには依然としてかなり時間がかかります。この問題はずっと前に発見されました。
異なる文書構造から指定された文字列を含む要素を検索する関数があるとします。次の一見正当な呼び出しを使用します:
grep( "substring", tree );
しかし、期待した結果は得られません。過去の経験によれば、指定されたツリー文書構造のチェックにしばらく時間を費やしますが、これには長い時間がかかる場合があります。その後、おそらく他のチェックを行うことになりますが、最終的には、関数コードから、渡されたパラメーターの順序が逆になっていることがわかります。この観点から、関数のパラメータに注意を払えば、上記のエラーは発生しません。
function grep( tree, substring ){ if ( !( tree instanceof Tree ) ) { throw TypeError( "Invalid tree parameter" ); } if ( typeof substring !== "string" ) { throw TypeError( "Invalid substring parameter" ); } //... }
この検証方法は、契約による設計アプローチの一部です。ソフトウェア コンポーネントで検証する必要がある事前条件と事後条件をリストします。上記の例では、関数の入力パラメーターが指定された形式に準拠していることをテストする必要があり (最初のパラメーターをツリー ドキュメント タイプと比較し、2 番目のパラメーターを文字列タイプと比較します)、関数の出力タイプが文字列であるかどうかを確認することをお勧めします。 。
しかし、JavaScriptには今のところ、他の言語のように関数の開始と終了を検証する組み込み関数がありません。たとえば、PHP 言語には型ヒントがあります:
<?php function grep( Tree $tree, string $substring ): string {}
TypeScript には厳密な型指定があります:
function grep( tree: Tree, substring: string ): string {}
さらに、高度な型 (共用体型、オプション型、交差型、ジェネリックなど) もサポートしています:
function normalize( numberLike: number | string, modifier?: boolean ): string {}
Among the ES 仕様で提案されている機能に加えて、将来的には次の構文の使用を推奨する Guards と呼ばれる機能が登場する予定です:
function grep( tree:: Tree, substring:: String ):: String {}
これまでのところ、JavaScript では、この問題を解決するには外部ライブラリまたは変換可能なコンパイラを使用する必要があります。ただし、利用できるリソースは少なくなります。最も古いライブラリは Cerny.js です。これは DbC (Database Computer) に似ており、強力で柔軟です:
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"); }); })();
しかし、私にとっては複雑に思えます。私は、事前条件/事後条件を確認するために、簡潔でクリーンな方法を使用することを好みます。 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));
Javascript ではないことを除けば、非常に優れています。これを使用する必要がある場合は、Contractual Contracts または 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
ご覧のとおり、ドキュメントのコメントから指定された型を byContract にコピー/貼り付けて比較することができ、とても簡単です。以下では、以下のことをさらに詳しく調べます。 byContract には、UMD モジュール (AMD または CommonJS) またはグローバル変数としてアクセスできます。値/JavaScript ドキュメント式をパラメーターのペアとして byContract
byContract( value, "JSDOC-EXPRESSION" );
に渡すか、ドキュメント式リストに対応する値リストをパラメーターのペアとして渡すことができます:
byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );
byContract は受信した値を検出し、それが対応する値と一致するかどうかを検出します。 JSDoc 式の形式に一貫性がない場合、「渡された値が NaN 型に違反しています」のような情報を含む byContract.Exception 例外がスローされます。
在最简单的案例中,byContract用来验证如 `array`, `string`, `undefined`, `boolean`, `function`, `nan`, `null`, `number`, `object`, `regexp`之类的 原型类型:
byContract( true, "boolean" );
当我们需要允许输入值在一个指定类型列表中的时候,可以使用 type union 。
byContract( 100, "string|number|boolean" );
一个函数可以有必填的参数,也可以有可选参数。默认情况下,参数在和原型类型做对比的时候是必填的。但是用'='修饰符我们就可以设置成可选类型。所以 byContract 处理如 `number=` 这样的表达式时候,会转为 `number|undefined`
function foo( bar, baz ) { byContract( arguments, [ "number=", "string=" ] ); }
下面是Js文档中 nullable/non-nullable types (可空/不可空类型):
byContract( 42, "?number" ); // a number or null. byContract( 42, "!number" ); // a number, but never null.
当然,我们可以用接口来做比较。这样我们就可以引用作用域范围内任何可用的对象,包括Javascript内置接口:
var instance = new Date(); byContract( instance, "Date" ); byContract( view, "Backbone.NativeView" ); byContract( e, "Event" );
对于数组和对象,我们可以有选择性地验证其内容。比如可以验证所有数组的值必须是数字或者所有的对象的键和值是字符串类型:
byContract( [ 1, 1 ], "Array.<number>" ); byContract( { foo: "foo", bar: "bar" }, "Object.<string, string>" );
以上的验证对线性数据结构有用,其他情况下就不起作用了。所以同样的,我们可以创建一个 type definition (类型定义)来描述对象的内容(参考byContract类型定义)然后在后面作为一个类型引用它即可。
byContract.typedef( "Hero", { hasSuperhumanStrength: "boolean", hasWaterbreathing: "boolean" }); var superman = { hasSuperhumanStrength: true, hasWaterbreathing: false }; byContract( superman, "Hero" );
这个示例定义了一个'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!
事实上,你很可能不要用事件来写验证函数,而是用外部库(类似 validator )代替:
byContract.is.email = validator.isEmail;
验证逻辑取决于开发环境。使用 byContract, 我们可以用全局触发器来禁用验证逻辑 :
if ( env !== "dev" ) { byContract.isEnabled = false; }
byContract 是一个很小的验证插件(压缩文件大约1KB大小) ,你可以在你的Javascript代码中使用它从而得到对比编程设计模式的好处。
以上就是守护 Javascript 中的函数参数的内容,更多相关内容请关注PHP中文网(www.php.cn)!