Salta el contingut

Desenvolupament web dinàmic frontend amb javascript
Conceptes

Sessió 5: Funcions

Objectiu Sessió 5

L'objectiu serà utilitzar tècniques de programació usant funcions pròpies que ajuden a una millor estructura del codi.

Funcions

Una funció és un bloc de codi que realitza una tasca determinada. Les funcions ens permeten reutlitzar blocs de codi en diferents punts de l'execució dels nostres programes.

Una funció s'executa quan la invoquem amb l'operador ().

Funcions declaratives

Son les funcions que hi definim un nom identificador. I poden ser invocades des de qualsevol part del codi.

Sintaxi

Podem definir les nostres pròpies funcions en JavaScript seguint aquesta estructura:

function nom_funció( [ paràmetre1, paràmetre2, ... ] )
{
    instruccions a executar;

    [ return valor; ]
}

Una funció pot rebre paràmetres i retornar un valor de retorn. Tant els paràmetres com el valor de retorn són opcionals. Els paràmetres es reben dins dels parèntesis () en el moment d'invocar la funció. Dins de la funció els paràmetres es comporten com variables locals.

Diferència entre paràmetres i arguments

Els paràmetres s'indiquen a la declaració d'una funció:

//a, b i c són paràmetres
function exemple( a, b, c ) { ... }

Els arguments es passen quan es crida una funció:

// Els valors 1, 2 i 3 són arguments
exemple( 1, 2, 3 );

Atenció

Si accedim a una funció sense els () se'ns retornarà la funció i no el resultat de la seva execució.

Nom de les funcions

Quan definim el nom de les funcions cal tenir en compte que:

  • han de començar per una lletra minúscula. Usarem la notació camelCase.
  • opcionalment poden començar per _ o $
  • no poden ser paraules reservades del llenguatge
  • distingeixen entre majúscules i minúscules

Valors de retorn

Idealment, una funció sempre hauria de retornar un valor. Una funció que no contigui la sentència return retornarà el valor undefined.

Quan el codi arriba a una instrucció return dins d'una funció, l'execució de la funció s'atura immediatament.

function exemple(a, b) {
    if ( a == 3 ) return b // Si a == 3 la funció retorna b i acaba
    return a               // Si a != 3 la funció retorna a i acaba
    return 0               // Mai s'executarà aquesta instrucció
}

Variables dins de funcions

Les variables declarades dins d'una funció utilitzant let i són locals de la funció.

// el codi que posem aquí NO pot utilitzar la variable "nom"

function exemple() {
  let nom = "Joan";

  // el codi que posem aquí POT utilitzar la variable "nom"
}

// el codi que posem aquí NO pot utilitzar la variable "nom"

Nombre d'arguments

Si invoquem una funció amb més arguments que paràmetres té declarats, els arguments sobrants són ignorats i no es produeix cap error.

function exemple( a, b ) { ... }

// El tercer argument no es té en compte
exemple( 10, 20, 30 );

Si invoquem una funció amb menys arguments dels paràmetres que té declarats, els que falten s'inicialitzen com a no definits (undefined):

function exemple( a, b, c ) { ... }

// El paràmetre 'c' rebrà el valor undefined
exemple( 10, 20 );

Valor per defecte dels paràmetres

Des de la versió ES6 de JavaScript es poden definir valors per defecte pels paràmetres amb = en el moment de declarar la funció.

function exemple( valor = 10 ) {
    return valor * 2;
}

exemple(1) // Retornarà 2
exemple() // Retornarà 20

Ús en l'actualitat

Tots els navegadors moderns ja suporten ES6 i podem assignar valors per defecte directament amb =

Paràmetres per valor i per referència

Els paràmetres de tipus primitius (cadenes, numèrics i booleans) es passen sempre per valor, per tant es modifica el valor dels arguments passats a la funció.

Els paràmetres que siguin de tipus array o objecte es passen per referència. Si es modifica algun dels seus elements dins de la funció, es modificarà també l'array o objecte original fora de la funció.

let x = [ 'valor_inicial' ];

function f( p ) {
    p[0] = 'valor_nou';
}

f( x ); // x[0] = 'valor_nou'

Funcions anònimes

Són aquelles en les que ometem el nom de la funció. Normalment s'utilitzen quan volem definir un bloc de codi que només s'ha d'executar en un punt concret del programa, i per tant no cal utilitzar un nom de funció ja que no la invocarem enlloc més.

Com a conseqüència no les podem invocar pel nom però sí que les podem:

  • assignar com a argument d'una altra funció
setInterval( function() { ... }, 1000 );
  • assignar a una variable o a un esdeveniment
element.onclick = function() { ... };
(function (a) { return a + 10 })(10) // Retorna 20

This

La paraula reservada this fa referència a un objecte, que variarà segons diferents casos:

  • Sol o dins d'una funció, fa referència a l'objecte global.
  • Dins d'un mètode d'un objecte, fa referència al propi objecte.
  • Dins d'un esdeveniment fa referència a l'element HTML que ha rebut l'esdeveniment.

Excepció

Si tenim activat el mode estricte, this dins d'una funció retorna undefined enlloc de l'objecte global.

Nota

this no és una variable sinó una paraula reservada del llenguatge. No podem canviar el valor de this.

Cal tenir clar quin és el context de this en cada moment.

Important

this l'utilitzarem principalment per a fer referència a l'element html del qual en fem gestió dinàmica a la funció associada a l'esdeveniment.

Per exemple, si assignem una funció directament a un esdeveniment onclick, dins de la funció this farà referència a l'element que ha rebut l'esdeveniment, per tant el que s'hagi clicat:

Exemple this

    <script>
        function pintaVermell() {
            // Dins d'aquesta funció 'this' representa l'element HTML clicat
            this.style.color = "white";
            this.style.backgroundColor = "red";
        }

        // Assignem la funció directament a l'esdeveniment click de cada botó,
        // sense posar () al final ja que provocaria l'execucio de la funció
        document.getElementById("boto_1").onclick = pintaVermell;
        document.getElementById("boto_2").onclick = pintaVermell;
        document.getElementById("boto_3").onclick = pintaVermell(); // <-- ERROR!
    </script>
    <button id="boto_1">Botó 1</button>
    <button id="boto_2">Botó 2</button>
    <button id="boto_3">Botó 3</button>
    <script>
    function pintaVermell() {
        // Dins d'aquesta funció 'this' representa l'element HTML clicat
        this.style.color = "white";
        this.style.backgroundColor = "red";
    }

    // Assignem la funció directament a l'esdeveniment click de cada botó,
    // sense posar () al final ja que provocaria l'execucio de la funció
    document.getElementById("boto_1").onclick = pintaVermell;
    document.getElementById("boto_2").onclick = pintaVermell;
    document.getElementById("boto_3").onclick = pintaVermell(); // <-- ERROR!
    </script>

En canvi, si enlloc d'assignar directament una funció a l'esdeveniment li assignem una funció anònima, tindrem la referència a this dins del context de la funció anònima, però no més enllà. Si l'hem de passar a una altra funció ho podem fer com a argument:

function pintaColor(element, colorText, colorFons) {
    // Dins d'aquesta funció 'this' representa l'objecte global (Window)
    // i 'element' és un paràmetre que representa l'objecte que volem modificar
    element.style.color = colorText;
    element.style.backgroundColor = colorFons;
}

document.getElementById("boto_1").onclick = function() {
    // Dins d'aquesta funció 'this' representa el botó clicat (HTML),
    // i el podem passar com a paràmetre a una altra funció
    pintaColor(this, "white", "red");
};

Expressions de funcions

Les expressions de funcions es declaren com un assignació de variable, però se'ls passa com a valor una funció:

let exemple = function ( a ) {
    return a + 20;
}

exemple( 10 ); // Retorna 30

En aquest cas la variable exemple és de tipus funció, i conté el codi de la funció assignada:

typeof( exemple ); // Retorna 'function'

exemple.toString(); // Retorna 'function (a) { return a+20;}'

Les funcions es poden declarar en qualsevol punt del codi, però les podem invocar abans d'haver-les declarat gràcies al Hoisting. En canvi les expressions de funcions no existeixen fins que l'execució del codi arriba a la línia on se les assigna a una variable.

Les expressions de funcions es poden reassignar, a diferència de les funcions que no es poden redeclarar en temps d'execució. D'aquesta manera una mateixa crida a una expressió de funció pot estar invocant funcions diferents segons requereixi el nostre codi.

let suma10 = function (x) { return x + 10 }
let mult10 = function (x) { return x * 10 }

let f = suma10

f( 10 ); // Retorna 20

let f = mult10

f( 10 ); // Retorna 100

let f = suma10() // <-- ERROR, aquí no estem assignant la funció
                 //     sinó el valor que retorna (NaN)

Funcions fletxa (arrow functions)

Són una notació alternativa i més compacta per declarar funcions.

Es comporten igual que les funcions declarades amb function però cal tenir en compte que:

  • no canvien el context de this al seu interior, sinó que mantenen el context del punt on s'ha invocat la funció fletxa
  • no es poden utilitzar com a constructors
  • sempre són anònimes

Sintaxi comparada amb les funcions anònimes tradicionals:

    function ( a ) {
        return a + 100;
    }

S'elimina la paraula reservada function i posem una fletxa (=>) entre ( ) i { }

    ( a ) => { return a + 100; }

Si la funció només té una línia i retorna un valor, no cal posar les claus { } i el return es fa implícitament

    ( a ) => a + 100;

Si hi ha un sol paràmetre, podem ometre els parèntesis

    a => a + 100;

Scripts externs

Tenim la possibilitat de generar un arxiu de codi javascript independent de l'arxiu .html, per a reutilitzar el codi o funcions que contingui.

Per fer-ho recordem que tenim la possibilitat d'indicar l'origen de l'script amb l'atribut src

    <script src="script.js"></script>

Bona pràctica

Pot ser una bona pràctica a seguir generar arxius de codi javascript, per exemple

Estructura carpetes

Per tant un fitxer scripts.js estaria a dins de la carpeta /scripts i en fariem la referència amb ruta relativa o absoluta a dins de l'etiqueta <script> al fitxer index.html usant:

<html>
    <head>
        <script src="scripts/scripts.js"></script>
    </head>

</html>

Fins i tot podriem incloure i usar scripts externs usant URL:

<html>
    <head>
        <script src="http://www.example.com/example.js"></script>
    </head>

</html>

Exemple d'usar scripts de google Maps

    /* Optional: Makes the sample page fill the window. */
    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    #container {
      height: 100%;
      display: flex;
    }

    #sidebar {
      flex-basis: 15rem;
      flex-grow: 1;
      padding: 1rem;
      max-width: 30rem;
      height: 100%;
      box-sizing: border-box;
      overflow: auto;
    }

    #map {
      flex-basis: 0;
      flex-grow: 4;
      height: 100%;
    }

    #floating-panel {
      position: absolute;
      top: 10px;
      left: 25%;
      z-index: 5;
      background-color: #fff;
      padding: 5px;
      border: 1px solid #999;
      text-align: center;
      font-family: "Roboto", "sans-serif";
      line-height: 30px;
      padding-left: 10px;
    }

    #floating-panel {
      background-color: #fff;
      border: 0;
      border-radius: 2px;
      box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
      margin: 10px;
      padding: 0 0.5em;
      font: 400 18px Roboto, Arial, sans-serif;
      overflow: hidden;
      padding: 5px;
      font-size: 14px;
      text-align: center;
      line-height: 30px;
      height: auto;
    }

    #map {
      flex: auto;
    }

    #sidebar {
      flex: 0 1 auto;
      padding: 0;
    }
    #sidebar > div {
      padding: 0.5rem;
    }
    <html>
      <head>
        <title>Displaying Text Directions With setPanel()</title>

        <link rel="stylesheet" type="text/css" href="./styles/p2-s5-ex2.css" />
        <script type="module" src="./scripts/p2-s5-ex2.js"></script>
      </head>
      <body>
        <div id="floating-panel">
          <strong>Inici:</strong>
          <select id="start">
            <option value="Girona">Girona</option>
            <option value="Salt">Salt</option>
            <option value="Olot">Olot</option>
            <option value="Figueres">Figueres</option>
            <option value="Cadaques">Cadaqués</option>
            <option value="Fornells de la Selva">Fornells de la Selva</option>
            <option value="Platja d'Aro">Platja d'Aro</option>
            <option value="Sant Feliu de Guixols">Sant Feliu de Guixols</option>
          </select>
          <br />
          <strong>Final:</strong>
          <select id="end">
            <option value="Girona">Girona</option>
            <option value="Salt">Salt</option>
            <option value="Olot">Olot</option>
            <option value="Figueres">Figueres</option>
            <option value="Cadaques">Cadaqués</option>
            <option value="Fornells de la Selva">Fornells de la Selva</option>
            <option value="Platja d'Aro">Platja d'Aro</option>
            <option value="Sant Feliu de Guixols">Sant Feliu de Guixols</option>
          </select>
        </div>
        <div id="container">
          <div id="map"></div>
          <div id="sidebar"></div>
        </div>
        <div style="display: none">
          <div id="floating-panel">
            <strong>Start:</strong>
            <select id="start">
            <option value="Girona">Girona</option>
            <option value="Salt">Salt</option>
            <option value="Olot">Olot</option>
            <option value="Figueres">Figueres</option>
            <option value="Cadaques">Cadaqués</option>
            <option value="Fornells de la Selva">Fornells de la Selva</option>
            <option value="Platja d'Aro">Platja d'Aro</option>
            <option value="Sant Feliu de Guixols">Sant Feliu de Guixols</option>
            </select>
            <br />
            <strong>End:</strong>
            <select id="end">
            <option value="Girona">Girona</option>
            <option value="Salt">Salt</option>
            <option value="Olot">Olot</option>
            <option value="Figueres">Figueres</option>
            <option value="Cadaques">Cadaqués</option>
            <option value="Fornells de la Selva">Fornells de la Selva</option>
            <option value="Platja d'Aro">Platja d'Aro</option>
            <option value="Sant Feliu de Guixols">Sant Feliu de Guixols</option>
            </select>
          </div>
        </div>

        <!-- 
          The `defer` attribute causes the script to execute after the full HTML
          document has been parsed. For non-blocking uses, avoiding race conditions,
          and consistent behavior across browsers, consider loading using Promises. See
          https://developers.google.com/maps/documentation/javascript/load-maps-js-api
          for more information.
          -->
        <script
          src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAhdN_4cI4sbyZkyfBpSquF0P2hR9yjd3I&callback=initMap&v=weekly"
          defer
        ></script>
      </body>
    </html>
    <script type="module" src="./index.js"></script>

    ...

    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly" defer></script>
    function initMap() {
        const directionsRenderer = new google.maps.DirectionsRenderer();
        const directionsService = new google.maps.DirectionsService();
        const map = new google.maps.Map(document.getElementById("map"), {
          zoom: 7,
          center: { lat: 41.97, lng: 2.81 },
          disableDefaultUI: true,
        });

        directionsRenderer.setMap(map);
        directionsRenderer.setPanel(document.getElementById("sidebar"));

        const control = document.getElementById("floating-panel");

        map.controls[google.maps.ControlPosition.TOP_CENTER].push(control);

        const onChangeHandler = function () {
          calculateAndDisplayRoute(directionsService, directionsRenderer);
        };

        document.getElementById("start").addEventListener("change", onChangeHandler);
        document.getElementById("end").addEventListener("change", onChangeHandler);
      }

      function calculateAndDisplayRoute(directionsService, directionsRenderer) {
        const start = document.getElementById("start").value;
        const end = document.getElementById("end").value;

        directionsService
          .route({
            origin: start,
            destination: end,
            travelMode: google.maps.TravelMode.DRIVING,
          })
          .then((response) => {
            directionsRenderer.setDirections(response);
          })
          .catch((e) => window.alert("Directions request failed due to " + status));
      }

      window.initMap = initMap;

Això ens permetria usar mapa de google, cal tenir una API-key vàlida. Notem que quedaria exposada.

Tenim:

  • codi HTML per formatar contingut
  • estils CSS per aplicar estil a la interfície a la carpeta /styles
  • a dins de script propi definim una function initMap(), que crea el mapa i el configura els paràmetres

        function initMap() {
                const directionsRenderer = new google.maps.DirectionsRenderer();
                const directionsService = new google.maps.DirectionsService();
                const map = new google.maps.Map(document.getElementById("map"), {
                zoom: 7,
                center: { lat: 41.97, lng: 2.81 },
                disableDefaultUI: true,
            });
    
            directionsRenderer.setMap(map);
            directionsRenderer.setPanel(document.getElementById("sidebar"));
    
            const control = document.getElementById("floating-panel");
    
            map.controls[google.maps.ControlPosition.TOP_CENTER].push(control);
    
            const onChangeHandler = function () {
                calculateAndDisplayRoute(directionsService, directionsRenderer);
            };
    
            document.getElementById("start").addEventListener("change", onChangeHandler);
            document.getElementById("end").addEventListener("change", onChangeHandler);
        }
    
  • a dins de script propi definim una function calculateAndDisplayRoute(directionsService, directionsRenderer), que calcula la ruta entre els dos punts

    function calculateAndDisplayRoute(directionsService, directionsRenderer) {
        const start = document.getElementById("start").value;
        const end = document.getElementById("end").value;
    
        directionsService
        .route({
            origin: start,
            destination: end,
            travelMode: google.maps.TravelMode.DRIVING,
        })
        .then((response) => {
            directionsRenderer.setDirections(response);
        })
        .catch((e) => window.alert("Directions request failed due to " + status));
    }
    
  • <script> amb src=de Google, que ens donen accés a l'objecte de google maps i a les funcionalitats que usarem.

  • Ens cal tenir una API-Key vàlida.

Exemples