This article describes how to use the Definition mode of inertia functions. It describes a functional programming (functional-programming) design mode. I call it Lazy Function Definition (Lazy Function Definition ). More than once I found this mode useful in JavaScript, especially when writing a cross-browser, efficient library.
Warm-up questions
Compile a function foo, which returns the Date object, which stores the time when foo was called for the first time.
Method 1: Technology in the Ancient Age
The simplest solution uses the global variable t to save the Date object. When foo is called for the first time, the time is saved to t. In the next call, foo only returns the value stored in t.
var t; function foo() { if (t) { return t; } t = new Date(); return t; }
But such code has two problems. First, variable t is an extra global variable and may be changed during the interval between calls to foo. Second, the efficiency of the Code during the call is not optimized because each call to foo must evaluate the condition. In this example, the evaluate condition is not inefficient, but in practice in the real world, it is often very expensive to evaluate the condition, for example, in if-else -... .
Method 2: module Mode
We can compensate for the defects of the first method by the module pattern that is attributed to Cornford and Crockford. You can use the closure to hide the global variable t. Only the code in foo can access it.
var foo = (function() { var t; return function() { if (t) { return t; } t = new Date(); return t; } })();
However, this still does not optimize the call efficiency, because each call to foo still requires a value condition.
Although the module mode is a powerful tool, I firmly believe that in this case it is used incorrectly.
Method 3: functions as objects
Because JavaScript Functions are also objects, they can have attributes. Therefore, we can implement a solution similar to the module mode quality.
function foo() { if (foo.t) { return foo.t; } foo.t = new Date(); return foo.t; }
In some cases, function objects with attributes can produce clear solutions. In my opinion, this method is more conceptual than the mode module method.
This solution avoids the global variable t in the first method, but still does not solve the condition evaluation caused by each call of foo.
Method 4: inert Function Definition
Now, this is why you read this article:
var foo = function() { var t = new Date(); foo = function() { return t; }; return foo(); };
When foo is called for the first time, we instantiate a new Date object and reset foo to a new function. It contains the Date object in its closure. Before the end of the first call, the new function value of foo has also been called and the return value is provided.
The next foo call will simply return the value t is retained in its closure. This is a very fast search, especially if the conditions in the previous examples are very complex, it will be very efficient.
Another way to find out this mode is that the first call of the peripheral (outer) function to foo is a guarantee (promise ). It ensures that the first call will redefine foo as a very useful function. In general, the term "guarantee" comes from Scheme's lazy evaluation mechanism ). Every JavaScript programmer should really learn Scheme, because it has many functional programming-related things that will appear in JavaScript.
Determine the page scroll distance
When writing cross-browser JavaScript, different browser-specific algorithms are often wrapped in an independent JavaScript function. This allows you to standardize browser APIS by hiding browser differences, and makes it easier to build and maintain JavaScript with complex page features. When a function is called, it executes the appropriate browser-specific algorithm.
In the drag-and-drop library, you often need to use the cursor position information provided by the mouse event. The cursor coordinates given by the mouse event are relative to the browser window rather than the page. You can get the coordinates of the mouse relative to the page by adding the distance between the page scroll and the window coordinate of the mouse. Therefore, we need a function to feedback Page scrolling. For demonstration, this example defines a function getScrollY. Because the drag-and-drop Library will continue to run during the drag-and-drop process, our getScrollY must be as efficient as possible.
However, there are four different browser-specific page rolling feedback algorithms. Richard Cornford mentioned these algorithms in his feature detection article. The biggest trap is that one of the four page rolling feedback algorithms uses the document. body. JavaScript library, which is usually used in the HTML documentAt the same time, docment. body does not exist. Therefore, when loading a database, we cannot use the feature check to determine which algorithm to use.
With these issues in mind, most JavaScript libraries will choose one of the following two methods. The first option is to use a browser to sniff navigator. userAgent to create an efficient and concise getScrollY for the browser. the second better choice is that getScrollY uses a feature check to determine the appropriate algorithm for each call. However, the second option is not efficient.
The good news is that getScrollY In the drag-and-drop library will only be used when the user interacts with the page elements. If the element already exists on the page, the document. body also exists. For the first call of getScrollY, we can create an efficient getScrollY using the definition mode of the inert function combined with the feature check.
var getScrollY = function() { if (typeof window.pageYOffset == 'number') { getScrollY = function() { return window.pageYOffset; }; } else if ((typeof document.compatMode == 'string') && (document.compatMode.indexOf('CSS') >= 0) && (document.documentElement) && (typeof document.documentElement.scrollTop == 'number')) { getScrollY = function() { return document.documentElement.scrollTop; }; } else if ((document.body) && (typeof document.body.scrollTop == 'number')) { getScrollY = function() { return document.body.scrollTop; } } else { getScrollY = function() { return NaN; }; } return getScrollY(); }
Summary
The definition mode of the inert function allows me to write compact, robust, and efficient code. Every time I use this mode, I will take the time to admire the functional programming capabilities of JavaScript.
JavaScript supports both functional and object-oriented processing. Many books on the market that focus on object-oriented design patterns can be applied to JavaScript programming. However, there are not many examples of functional design patterns. For the JavaScript community, it still takes a long time to accumulate good functional patterns.
Update:
Although this mode is interesting, a large number of closures may cause performance problems due to poor memory management. FredCK from FCKeditor improved getScrollY, both using this mode and avoiding closures:
var getScrollY = function() { if (typeof window.pageYOffset == 'number') return (getScrollY = getScrollY.case1)(); var compatMode = document.compatMode; var documentElement = document.documentElement; if ((typeof compatMode == 'string') && (compatMode.indexOf('CSS') >= 0) && (documentElement) && (typeof documentElement.scrollTop == 'number')) return (getScrollY = getScrollY.case2)(); var body = document.body ; if ((body) && (typeof body.scrollTop == 'number')) return (getScrollY = getScrollY.case3)(); return (getScrollY = getScrollY.case4)(); }; getScrollY.case1 = function() { return window.pageYOffset; }; getScrollY.case2 = function() { return documentElement.scrollTop; }; getScrollY.case3 = function() { return body.scrollTop; }; getScrollY.case4 = function() { return NaN; };