この記事では、JavaScript 関数の 5 つの応用テクニックを紹介します。必要な方は参考にしていただければ幸いです。
関数は、あらゆる言語、特に JavaScript の中心となる概念です。この記事では、関数の 5 つの高度なスキルを紹介します。
スコープセーフ コンストラクター
コンストラクターは、実際には new 演算子を使用して呼び出される関数です。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; } var person=new Person('match',28,'Software Engineer'); console.log(person.name);//match
If new 演算子は使用されず、もともと person オブジェクトを対象としていた 3 つの属性が window オブジェクトに追加されます。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; } var person=Person('match',28,'Software Engineer'); console.log(person);//undefinedconsole.log(window.name);//match
ウィンドウの name 属性は、リンク ターゲットとフレームを識別するために使用されます。この問題の解決策は、スコープ セーフなコンストラクターを作成することです。
function Person(name,age,job){ if(this instanceof Person){ this.name=name; this.age=age; this.job=job; }else{ return new Person(name,age,job); } }var person=Person('match',28,'Software Engineer'); console.log(window.name); // ""console.log(person.name); //'match'var person= new Person('match',28,'Software Engineer'); console.log(window.name); // ""console.log(person.name); //'match'
ただし、コンストラクターの継承には副作用があります。これは、次のコードでは、このオブジェクトは Polygon オブジェクトのインスタンスではないため、コンストラクター Polygon() は新しいインスタンスを作成して返します。
function Polygon(sides){ if(this instanceof Polygon){ this.sides=sides; this.getArea=function(){ return 0; } }else{ return new Polygon(sides); } } function Rectangle(wifth,height){ Polygon.call(this,2); this.width=this.width; this.height=height; this.getArea=function(){ return this.width * this.height; }; } var rect= new Rectangle(5,10); console.log(rect.sides); //undefined
スコープ セーフなコンストラクターを使用して盗用したい場合は、プロトタイプチェーンの継承と組み合わせて、そのインスタンスが Polygon のインスタンスにもなるように Rectangle のプロトタイプ属性を書き換えます。
function Polygon(sides){ if(this instanceof Polygon){ this.sides=sides; this.getArea=function(){ return 0; } }else{ return new Polygon(sides); } } function Rectangle(wifth,height){ Polygon.call(this,2); this.width=this.width; this.height=height; this.getArea=function(){ return this.width * this.height; }; } Rectangle.prototype= new Polygon();var rect= new Rectangle(5,10); console.log(rect.sides); //2
遅延読み込み関数
理由ブラウザ間の違い ブラウザ間の動作の違いのため、ブラウザの特性をチェックし、異なるブラウザとの互換性の問題を解決するために、関数に多数の if ステートメントを含めることがよくあります。たとえば、dom ノードにイベントを追加するための最も一般的な関数です。
function addEvent(type, element, fun) { if (element.addEventListener) { element.addEventListener(type, fun, false); } else if(element.attachEvent){ element.attachEvent('on' + type, fun); } else{ element['on' + type] = fun; } }
addEvent 関数が呼び出されるたびに、ブラウザーでサポートされている機能を確認する必要があります。サポートされていない場合は、まず addEventListener メソッドがサポートされているかどうかを確認します。 , サポートされている場合は、attachEvent メソッドがサポートされているかどうかを確認し、まだサポートされていない場合は、dom0 レベルのメソッドを使用してイベントを追加します。このプロセスは、addEvent 関数が呼び出されるたびに実行する必要があります。実際、ブラウザーがいずれかのメソッドをサポートしている場合は、常にそれをサポートするため、他のブランチを検出する必要はありません。つまり、if ステートメントを毎回実行する必要がなく、コードをより高速に実行できます。
解決策は遅延読み込みです。いわゆる遅延ロードとは、関数実行の分岐が 1 回だけ発生することを意味します。遅延ロードを実装するには 2 つの方法があります。
1. 1 つ目は、関数が呼び出されたときに関数を処理することです。関数が初めて呼び出されるとき、その関数は適切な方法で実行される別の関数によって上書きされるため、元の関数への呼び出しは実行のブランチを経由する必要がありません。
次のメソッドを使用できます。遅延読み込みを使用して addEvent() を書き換えます
function addEvent(type, element, fun) { if (element.addEventListener) { addEvent = function (type, element, fun) { element.addEventListener(type, fun, false); } } else if(element.attachEvent){ addEvent = function (type, element, fun) { element.attachEvent('on' + type, fun); } } else{ addEvent = function (type, element, fun) { element['on' + type] = fun; } } return addEvent(type, element, fun); }
この遅延読み込み addEvent() では、if ステートメントの各分岐が addEvent 変数に値を割り当て、元の関数を効果的にカバーします。最後のステップは、新しい割り当て関数を呼び出すことです。次回 addEvent() を呼び出すと、新しく割り当てられた関数が直接呼び出されるため、if ステートメントを実行する必要はありません
ただし、このメソッドには関数名が変更される場合に不利な点があります。トラブル
2. 2番目の方法は、関数の宣言時に適切な関数を指定することです。このように、関数を初めて呼び出すときはパフォーマンスが失われませんが、コードがロードされるときに少しパフォーマンスが失われます。以下は、この考え方に従って addEvent() を書き直したものです。次のコードは、匿名の自己実行関数を作成します。この関数は、さまざまなブランチに分岐して、実装に使用する関数を決定します。
var addEvent = (function () { if (document.addEventListener) { return function (type, element, fun) { element.addEventListener(type, fun, false); } } else if (document.attachEvent) { return function (type, element, fun) { element.attachEvent('on' + type, fun); } } else { return function (type, element, fun) { element['on' + type] = fun; } } })();
JavaScript で、 DOM 多くの場合、関数バインディングを使用して関数を定義し、それを特定の DOM 要素またはコレクションのイベント トリガーにバインドする必要があります。バインディング関数は、関数を変数として渡すためにコールバック関数およびイベント ハンドラーとともに使用されます。同時に、コードの実行環境は保持されます
<button id="btn">按钮</button><script> var handler={ message:"Event handled.", handlerFun:function(){ alert(this.message); } }; btn.onclick = handler.handlerFun; </script>
上記のコードは、ハンドラーと呼ばれるオブジェクトを作成します。 handler.handlerFun() メソッドは、DOM ボタンのイベント ハンドラーとして割り当てられます。ボタンが押されると、この関数が呼び出され、アラート ボックスが表示されます。警告ボックスには「処理されたイベント」と表示されるはずですが、実際には「undefend」と表示されます。問題は、handler.handleClick() の環境が保存されていないため、このオブジェクトがハンドラーではなく DOM ボタンを指すことになってしまうことです。
この問題はクロージャを使用して修正できます
<button id="btn">按钮</button> <script> var handler={ message:"Event handled.", handlerFun:function(){ alert(this.message); } }; btn.onclick = function(){ handler.handlerFun(); } </script>
もちろんこれは特定のシナリオです。このシナリオに対する解決策として、複数のクロージャを作成すると、コードの理解とデバッグが困難になる可能性があります。より良いアプローチは、関数バインディングを使用することです。
シンプルなバインディング関数 binding() は、関数と環境を受け入れ、指定された環境で指定された関数を呼び出す関数を、すべての引数とともに返します。 そのまま渡します
function bind(fn,context){ return function(){ return fn.apply(context,arguments); } }
この関数は単純そうに見えますが、その機能は非常に強力です。クロージャは、bind() で作成されます。クロージャは、apply() を使用して受信関数を呼び出し、コンテキスト オブジェクトとパラメータを apply() に渡します。返された関数が呼び出されると、指定された環境で渡された関数が実行され、すべてのパラメータが与えられます
<button id="btn">按钮</button><script> function bind(fn,context){ return function(){ return fn.apply(context,arguments); } } var handler={ message:"Event handled.", handlerFun:function(){ alert(this.message); } }; btn.onclick = bind(handler.handlerFun,handler); </script>
ECMAScript5 はすべての関数に対してネイティブの binding() メソッドを定義し、操作をさらに簡素化します
只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用
函数柯里化
与函数绑定紧密相关的主题是函数柯里化(function currying),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数
function add(num1,num2){ return num1+num2; }function curriedAdd(num2){ return add(5,num2); } console.log(add(2,3));//5console.log(curriedAdd(3));//8
这段代码定义了两个函数:add()和curriedAdd()。后者本质上是在任何情况下第一个参数为5的add()版本。尽管从技术来说curriedAdd()并非柯里化的函数,但它很好地展示了其概念
柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式
function curry(fn){ var args = Array.prototype.slice.call(arguments, 1); return function(){ var innerArgs = Array.prototype.slice.call(arguments), finalArgs = args.concat(innerArgs); return fn.apply(null, finalArgs); }; }
curry()函数的主要工作就是将被返回函数的参数进行排序。curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在arguments对象上调用了slice()方法,并传入参数1表示被返回的数组包含从第二个参数开始的所有参数。然后args数组包含了来自外部函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数(又一次用到了slice())。有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将它们组合为finalArgs,然后使用apply()将结果传递给函数。注意这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null。curry()函数可以按以下方式应用
function add(num1, num2){ return num1 + num2; }var curriedAdd = curry(add, 5); alert(curriedAdd(3)); //8
在这个例子中,创建了第一个参数绑定为5的add()的柯里化版本。当调用cuurriedAdd()并传入3时,3会成为add()的第二个参数,同时第一个参数依然是5,最后结果便是和8。也可以像下例这样给出所有的函数参数:
function add(num1, num2){ return num1 + num2; } var curriedAdd2 = curry(add, 5, 12); alert(curriedAdd2()); //17
在这里,柯里化的add()函数两个参数都提供了,所以以后就无需再传递给它们了
函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数
function bind(fn, context){ var args = Array.prototype.slice.call(arguments, 2); return function(){ var innerArgs = Array.prototype.slice.call(arguments), finalArgs = args.concat(innerArgs); return fn.apply(context, finalArgs); }; }
对curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果。curry()仅仅接受一个要包裹的函数作为参数,而bind()同时接受函数和一个object对象。这表示给被绑定的函数的参数是从第三个开始而不是第二个,这就要更改slice()的第一处调用。另一处更改是在倒数第3行将object对象传给apply()。当使用bind()时,它会返回绑定到给定环境的函数,并且可能它其中某些函数参数已经被设好。要想除了event对象再额外给事件处理程序传递参数时,这非常有用
var handler = { message: "Event handled", handleClick: function(name, event){ alert(this.message + ":" + name + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));
handler.handleClick()方法接受了两个参数:要处理的元素的名字和event对象。作为第三个参数传递给bind()函数的名字,又被传递给了handler.handleClick(),而handler.handleClick()也会同时接收到event对象
ECMAScript5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可
var handler = { message: "Event handled", handleClick: function(name, event){ alert(this.message + ":" + name + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
javaScript中的柯里化函数和绑定函数提供了强大的动态函数创建功能。使用bind()还是curry()要根据是否需要object对象响应来决定。它们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销
函数重写
由于一个函数可以返回另一个函数,因此可以用新的函数来覆盖旧的函数
function a(){ console.log('a'); a = function(){ console.log('b'); } }
这样一来,当我们第一次调用该函数时会console.log('a')会被执行;全局变量a被重定义,并被赋予新的函数
当该函数再次被调用时, console.log('b')会被执行
再复杂一点的情况如下所示
var a = (function(){ function someSetup(){ var setup = 'done'; } function actualWork(){ console.log('work'); } someSetup(); return actualWork; })()
我们使用了私有函数someSetup()和actualWork(),当函数a()第一次被调用时,它会调用someSetup(),并返回函数actualWork()的引用.
以上がJavaScript関数の5つの応用テクニックを紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。