The introduction

After the first six baptisms:

  • “XDM, JS how functional programming? See this is enough!
  • “XDM, JS how functional programming? See this is enough!
  • “XDM, JS how functional programming? See this is enough!
  • “XDM, JS how functional programming? See this is enough!
  • “XDM, JS how to function programming? See this is enough!
  • “XDM, JS how functional programming? See this is enough!

We can come to the conclusion that:

Functional programming represents a declarative style of code that is superior to imperative! It gives the code readability value more room to grow!

Figure 1.1

Declarative code style is based on imperative code style, declarative is the upper layer encapsulation of imperative!

Imperative concern: what to do, declarative concern: what to be.

Figure 1.2

If our code has a mixture of imperative and declarative styles, at the intersection of the red and green lines in Figure 1.2, then our code is probably at the intersection of the dotted and solid lines in Figure 1.1, with very low readability. So, we in THE JS functional programming on the road to be more assertive! Don’t end up being a laughingstock

The seventh chapter as the end of the series, everything will start from actual combat! All for actual combat!

FP will never be slaves.

Weapons ready

In the previous part, we introduced a lot of advanced functions for practicing JS functional programming.

The authors put it in this file: fp-helpers.js

Just to name a few, all of them familiar faces:

function partial(fn,... PresetArgs) {return function partiallyApplied(... laterArgs){ return fn( ... presetArgs, ... laterArgs ); }; } function curry(fn, length = fn.length) {return (function nextCurried(prevArgs){return function curried(nextArg){ var args = [ ...prevArgs, nextArg ]; if (args.length >= arity) { return fn( ... args ); } else { return nextCurried( args ); }}; }) ([]); } function compose(... ReduceRight (function reducer(fn1,fn2){return function reducer(... args){ return fn2( fn1( ... args ) ); }; }); } var pipe = reverseArgs(compose); Function reverseArgs(fn) {return function argsReversed(...); args){ return fn( ... args.reverse() ); }; } function zip(arr1,arr2) {var zipped = []; arr1 = [...arr1]; arr2 = [...arr2]; while (arr1.length > 0 && arr2.length > 0) { zipped.push( [ arr1.shift(), arr2.shift() ] ); } return zipped; }...Copy the code

We will use these advanced functions to completely upgrade our programming style from imperative to functional!

Practice scenario

We use native simulation of a stock information operation scenario:

The DOM structure:

<span style =" box-sizing: border-box! Important; word-wrap: break-word! Important; word-wrap: break-word! Important; <span class="stock" data-stock-id="MSFT"> <span class="stock" data-stock-id="MSFT"> <span class="stock-name"> <span class="stock-name"> $65.78</span> <span class="stock-change">+1.51</span> <span style =" box-sizing: border-box! Important; word-wrap: break-word! Important; Class = "stock price" - > $821.31 < / span > < span class = "stock - change" > 8.84 < / span > < / li > < / ul >Copy the code

Formatting data

Some basic helper functions:

Function addStockName(stock) {return setProp("name", stock, stock.id); } function formatSign($val) {if (Number(val) > 0) {return '+${val}'; } return val; } function formatCurrency(val) {// formatCurrency($$) return '$${val}'; } function transformObservable(mapperFn,obsv){return Observable monitor (RxJS) obsv.map(mapperFn); }Copy the code

The data we mock from the server looks like this:

Stock = {id: "AAPL", price: 121.7, change: 0.01}Copy the code

Before displaying the price value to the DOM, you need to use the formatCurrency(..) Function (e.g., “$121.70”) and use formatChange(..) The function formats the value of change (such as “+0.01”). But we don’t want to modify price and change in the message object. So write an auxiliary function like this:

function formatStockNumbers(stock) {
    var updateTuples = [
        [ "price", formatPrice( stock.price ) ],
        [ "change", formatChange( stock.change ) ]
    ];

    return reduce( function formatter(stock,[propName,val]){
        return setProp( propName, stock, val );
    } )
    ( stock )
    ( updateTuples );
}
Copy the code

We created the updateTuples tuple to hold the information for price and change, including the attribute names and formatted values. Reduce the tuple with the stock object as initialValue (..) Operations (see Article 5). Decomposing the information in the tuple into propName and val, and returning setProp(..) The result of the call is a new object that has been copied and whose properties have been modified. (Can be understood as an advanced version of the deep copy)

Let’s define a few more helper functions based on the above:

var formatDecimal = unboundMethod( "toFixed" )( 2 );
var formatPrice = pipe( formatDecimal, formatCurrency );
var formatChange = pipe( formatDecimal, formatSign );
var processNewStock = pipe( addStockName, formatStockNumbers );
Copy the code
  1. formatDecimal(..)The function takes a number as an argument (e.g. 2.1) and calls the toFixed(2) method on the number.
  2. unboundMethod(..)Function is used to create a separate delayed binding function.
  3. formatPrice(..) , formatChange (..) And processNewStock (..)Are used topipe(..)To combine from left to right.

Operating the DOM

Next, define some helper functions to manipulate the DOM:

function isTextNode(node) { return node && node.nodeType == 3; } function getElemAttr(elem,prop) { return elem.getAttribute( prop ); } function setElemAttr(elem,prop,val) { return elem.setAttribute( prop, val ); } function matchingStockId(id) { return function isStock(node){ return getStockId( node ) == id; }; } function isStockInfoChildElem(elem) { return /\bstock-/i.test( getClassName( elem ) ); } function appendDOMChild(parentNode,childNode) { parentNode.appendChild( childNode ); return parentNode; } function setDOMContent(elem, HTML) {// side effect!! elem.innerHTML = html; return elem; } var createElement = document.createElement.bind( document ); var getElemAttrByName = curry( reverseArgs( getElemAttr ), 2 ); var getStockId = getElemAttrByName( "data-stock-id" ); var getClassName = getElemAttrByName( "class" );Copy the code

These functions are self-explanatory, and you can see what they mean by their names.

The side effects of manipulating DOM elements are highlighted above. Because you can’t simply replace it with a cloned DOM object, you have to accept some side effects. But if an error occurs in the DOM rendering, by doing so, we can easily search through these code comments to narrow down the possible error code.

Finding a specific DOM

Now, we use getDOMChildren(..) Utility function to define the utility function in the stock quote tool to find a specific DOM element:

getDOMChildren(..) Using listify (..) To make sure we get an array (even if it has only one element). flatMap(..) This function flattens an array containing arrays into a shallow array. Array.from(..) Turn this array into a real array (not a NodeList).

var getDOMChildren = pipe(
    listify,
    flatMap(
        pipe(
            curry( prop )( "childNodes" ),
            Array.from
        )
    )
);

function getStockElem(tickerElem,stockId) {
    return pipe(
        getDOMChildren,
        filterOut( isTextNode ),
        filterIn( matchingStockId( stockId ) )
    )
    ( tickerElem );
}
function getStockInfoChildElems(stockElem) {
    return pipe(
        getDOMChildren,
        filterOut( isTextNode ),
        filterIn( isStockInfoChildElem )
    )
    ( stockElem );
}
Copy the code

getStockElem(..) And getStockInfoChildElems (..) Both utility functions filter out the literal nodes, ensuring that they return an array of DOM elements that match the stock symbol.

The main function

We use the stockTickerUI object to save the three main methods of modifying the interface, as follows:

Var stockTickerUI = {updateStockElems (stockInfoChildElemList, data) {/ / render it / / on the stock data to the DOM elements.. }, updateStock(tickerElem,data) {// updateStock data //.. }, addStock(tickerElem,data) {// add new stock //.. }};Copy the code

updateStockElems

updateStockElems(..) Internal implementation:

Var stockTickerUI = {updateStockElems (stockInfoChildElemList, data) {/ / 1 * * * * interpretations var getDataVal = curry (reverseArgs ( prop ), 2 )( data ); // ** definition 2** var extractInfoChildElemVal = pipe(getClassName, stripPrefix(/\bstock-/ I), getDataVal); // ** definition 3** var orderedDataVals = map(extractInfoChildElemVal)(stockInfoChildElemList); Var elemsValsTuples = filterOut(function updateValueMissing([infoChildElem,val]]){return val === undefined; } ) ( zip( stockInfoChildElemList, orderedDataVals ) ); // ** definition 5** compose(each, spreadArgs) (setDOMContent) (elemsValsTuples); }, / /.. };Copy the code

Definition:

  1. getDataVal(..)GetDataVal (..) getDataVal(..) getDataVal(..) Functions;
  2. extractInfoChildElemVal(..): Takes a DOM element as an argument, gets the value of the class attribute, drops the “stock-” prefix, and uses the attribute value (“name”, “price”, or “change”) via getDataVal(..) Function to find the corresponding data in data;
  3. orderedDataVals(..): The purpose of this is to get the data from data in the order of the <span> elements in stockInfoChildElemList. We call the extractInfoChildElem mapping function on the stockInfoChildElemList array to get this data;
  4. elemsValsTuples(..): we use to filter out null tuples in data objects. The filtered result is an array of tuples.[ <span>, ".." ]);
  5. Finally, we update the elements in the DOM:

updateStock

updateStock(..) Is the simplest of the three functions:

var stockTickerUI = {

    // ..

    updateStock(tickerElem,data) {
        var getStockElemFromId = curry( getStockElem )( tickerElem );
        var stockInfoChildElemList = pipe(
            getStockElemFromId,
            getStockInfoChildElems
        )
        ( data.id );

        return stockTickerUI.updateStockElems(
            stockInfoChildElemList,
            data
        );
    },

    // ..

};
Copy the code
  1. The helper function before curriegetStockElem(..)Pass it tickerElem and you get itgetStockElemFromId(..)Function that takes data.id as an argument.
  2. Pass in the <li> element (which is actually an array)getStockInfoChildElems(..)We got threeTo display stock information, we keep them instockInfoChildElemListVariable.
  3. Then pass the array along with the stock information data objectstockTickerUI.updateStockElems(..)To updateData in.

addStock

addStock(..) , according to an empty DOM structure to produce new stock information, and then call stockTickerUI. UpdateStockElems (..) Method to update its contents.

var stockTickerUI = { // .. AddStock (tickerElem,data) {// ** definition 1** var [stockElem,...infoChildElems] = map(createElement) (["li", "span", "span", "span" ] ); / / 2 * * * * interpretations var attrValTuples = [[[" class ", "stock"], [" data - stock - id ", the data. The id]], [[" class ", "stock - the name"]]. [ ["class","stock-price"] ], [ ["class","stock-change"] ] ]; // ** definition 3** var elemsAttrsTuples = zip([stockElem,...infoChildElems], attrValTuples); // ** ** ** ** ** each( function setElemAttrs([elem,attrValTupleList]){ each( spreadArgs( partial( setElemAttr, elem ) ) ) ( attrValTupleList ); } ) ( elemsAttrsTuples ); // ** ** ** ** ** stockTickerUI.updateStockElems( infoChildElems, data ); reduce( appendDOMChild )( stockElem )( infoChildElems ); tickerElem.appendChild( stockElem ); }};Copy the code

Definition:

  1. We create the <li> parent and three <span> children and assign them to eachstockEleminfoChildElemsThe array;
  2. To set the corresponding property of the DOM element, we declare an array of tuples. In order, each tuple array corresponds to one of the four DOM elements above. The tuple in each tuple array consists of the attributes and values of the corresponding element:
  3. Let’s add the four DOM elements toattrValTuplesAn array ofzip(..)Rise up;
  4. Set attributes and values to each DOM element;
  5. Call;

The implementation results are:

[
    [ <li>, [ ["class","stock"], ["data-stock-id",data.id] ] ],
    [ <span>, [ ["class","stock-name"] ] ],
    ..
]
Copy the code

So at this point, we have an array of elements, we have the attributes on each element, but we don’t have the innerHTML yet. Here, we’re going to use stockTickerUI. UpdateStockElems (..) Function to set data to , just like the stock update event.

summary

Above, we simulated the stock data operation scenario is how to carry out functional programming! It may be hard to read, but there’s an old saying: if you read a book a hundred times, it will show you what you mean. See much, see for a long time, also can feel one or two naturally!

If you’ve been doing imperative programming conventionally, can you imagine, in imperative programming, how do they do that?

In fact, there is a very important point, which is not written above, is to implement observable subscription, to pass the event to the main function.

Therefore, the examples here are more recommended to go to the project [CH11-code] read as a whole!

This example is more about connecting the concepts from the previous one and providing more realistic examples to learn from.

Keep practicing until you see the light!

Function library

By now, you’ve got the idea: The helper functions (advanced functions) mentioned above are awesome, but I can’t write them myself, so what should I do?

It doesn’t matter, the person using the weapon doesn’t need to know how it was made!

That’s right, we need to learn to use these powerful third-party arsenals of functional programming first!

Then we can talk about it. Anyone who uses weapons should know how they work

They are:

  • Ramda: General functional programming utility function
  • Sanctuary: Functional programming type Ramda companion
  • Lodash/FP: Generic functional programming utility function
  • Functional.js: general-purpose functional programming utility functions
  • Immutable: Data structures that are not mutable
  • Mori :(inspired by ClojureScript) immutable data structures
  • Seamless-Immutable data assistants
  • Tranducers-js: Data converter
  • Monet.js: indicates the Monad type

More on Monad, dig a hole and fill in the back.

conclusion

So much for functional programming at this stage. Finally, a little metaphysics, a little perspective.

I don’t need to think of any more lofty reasons for developers to be motivated. Thank you for joining us in learning functional programming in JavaScript. Hope you and I are full of hope! – cognitect – lab

When you are at the lowest point of despair and depression, don’t stop. A better way of thinking awaits you ahead!

It can be foreseen that this melon later will be many times for THE JS functional programming for more exploration and sharing!

Thanks for reading!

Nuggets Anthony, output exposed input, technology insight to life! Goodbye ~