Kliensoldali webprogramozás

Objektumok. Modulok. Eszközök.

Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu

Ismétlés

Weboldalak progresszív fejlesztése

  • Elv
    • JS nélkül is működjön
    • JS-sel kényelmesebb, szebb
  • Felépítés
    1. szerkezet (szemantikus HTML)
    2. megjelenés, stílus (CSS)
    3. viselkedés (JavaScript)

Web komponensek

  • Web komponensek
    • Custom elements
    • Templates
    • Shadow DOM
  • Progresszív fejlesztésre használható
<show-password>
  <input type="password" name="password1" value="secret">
</show-password>

<input type="password" name="password1" value="secret" is="show-password">

jQuery

  • DOM absztrakciós függvénykönyvtár
  • Alapvetően nincs rá szükség új projekteknél
$('.card-header')
  .append($('<button class="btn btn-primary">Expand</button>'))
  .find('button')
    .on('click', function (e) {
      $(this)
        .closest('.card')
          .parent()
            .toggleClass('col-lg-4 col-lg-12')
    })

Függvények

  • Funkcionális paradigma a JS-ben
  • Függvény paraméterezése
  • Függvény mint paraméter (callback)
  • Függvény mint visszatérési érték (függvénygenerátor, HOF)
  • Lexikális hatókör (closure)
  • Függvény mint objektum
  • this kontextusa

Objektumok

JavaScript nyelv forrásai

Objektumok

  • Szinte minden objektum
  • Név-érték párok
  • Két alappillér
    • Objektumok dinamikussága
    • Minden objektumhoz prototípus-objektum csatlakozik

Dinamikus objektumok

//Creating an object
let obj = {
    a: 1,
    b: 2
};

//New property
obj.c = 3;      

//Accessing property (read)
obj.a    === 1
obj['a'] === 1  
obj.d    === undefined

//Modifying the value of a property (write)
obj.b = 42;   

//Deleting a property
delete obj.c;

Eszközök

  • Object.create(obj)
  • Object.defineProperty(obj, név, leíró)
  • Object.defineProperties(obj, leírók)
  • Object.getOwnPropertyDescriptor(obj, név)
const obj = {}

const descriptor = {
  value: "a",
  writable: true,
  enumerable: true,
  configurable: true,
  set: function(value) { /* ... */ },
  get: function() { /* ... */ }
}
Object.defineProperty(obj, 'a', descriptor)

Eszközök

  • Object.keys(obj): felsorolható tulajdonságok neve
  • Object.getOwnPropertyNames(obj): összes tulajdonság neve
  • Object.preventExtensions(obj): nincs új tulajdonság
  • Object.isExtensible(obj)
  • Object.seal(obj): csak a tulajdonság értéke változhat
  • Objcect.isSealed(obj)
  • Object.freeze(obj): nincs semmilyen változás
  • Object.isFrozen(obj)

Prototípus-objektum

  • Minden objektum tartalmaz egy rejtett hivatkozást egy másik objektumra
  • → prototípus-objektum

Prototípuslánc

Prototípus beállítása és lekérdezése

//Setting up the prototype chain
let obj2 = Object.create(obj1);

//Getting the prototype object
obj1.isPrototypeOf(obj2)                //obj1 is in the prototype chain of obj2
Object.getPrototypeOf(obj2) === obj1    //obj2 is the prototype object of obj1

Tulajdonságok lekérdezése és beállítása

  • Olvasás: a prototípuslánc végignézése
  • Írás: mindig az adott objektumon

Tulajdonságok lekérdezése

Tulajdonságok írása

Prototípusosság

  • Kód-újrahasznosítás eszköze
  • Prototípus = modell objektum
  • A JavaScript prototípusos nyelv
  • Prototípusosság
    • dinamikus objektumokkal
    • prototípus-objektumokkal

Kód-újrahasznosítás dinamikus objektumokkal

Kód-újrahasznosítás dinamikus objektumokkal

Bővítés vagy mixin minta

//Kiindulási objektumok
const o1 = { a: 1,  b: 2 };
const o2 = {        b: 42, c: 3 };

//Mixin
o2.a = o1.a;
o2.b = o1.b;

//Teszt
o2.a === 1
o2.b === 2
o2.c === 3
//Kiindulási objektumok
const o1 = { a: 1,  b: 2 };
const o2 = {        b: 42, c: 3 };

// Mixin
Object.assign(o2, o1);
const o3 = Object.assign(o2, o1);
const o4 = Object.assign({}, o2, o1);
const o5 = {...o2, ...o1};

Mixin

Referenciatípusok másolása

Shallow copy (Object.assign, spread)

Referenciatípusok másolása

Deep copy

Funkcionális bővítés

let funcFrom = function funcFrom() {
  this.arr = [];
  this.obj = {
    a: 1
  };
  this.put = function (elem) {
    this.arr.push(elem);
  };
  return this;
};
let o1 = funcFrom.call({});
let o2 = funcFrom.call({});

//Teszt
o1.put !== o2.put
o1.arr !== o2.arr
o1.obj !== o2.obj

Funkcionális bővítés

Kód-újrahasznosítás prototípus-objektumokkal

Prototípus-objektum

//Prototípus
let oFrom = {
  a: 1,
  obj: { l: true },
  hello() { return 'hello'; }
};

//Célobjektumok
let o1 = Object.create(oFrom);
let o2 = Object.create(oFrom);

o1.a === 1
o1.hello() === 'hello'

//Írás a célobjektumokba
o1.b = 2;
o2.a = 11;

o1.b === 2
o2.a === 11
o1.hello === o2.hello

//Összetett adatszerkezetek
o1.obj.l = false;

o1.obj === o2.obj
o2.obj.l === false

o2.obj = { l: true };

o1.obj !== o2.obj
o2.obj.l !== o1.obj.l

Objektum-létrehozási minták

  • Objektumliterál (Singleton)
  • Objektumgenerátor
  • Objektumkonstruktor
  • class

Egy objektum létrehozása

Objektumliterál, Object.create()

const c = {
  name: 'Sári',
  dateOfBirth: { year: 2005, month: 3, day: 23 },
  greet() {
    return `Hello, my name is ${this.name}`;
  },
  rename(newName) {
    this.name = newName;
  }
};

Objektumgenerátor

const childGenerator = function(name, dateOfBirth) {
  return {
    name: name,
    dateOfBirth: dateOfBirth,
    greet() {
      return `Hello, my name is ${this.name}`;
    },
    rename(newName) {
      this.name = newName;
    }
  }
}
const c1 = childGenerator('Sári',  { year: 2005, month: 3, day: 23 });
const c2 = childGenerator('Zsófi', { year: 2007, month: 5, day: 8  });

c1 !== c2
c1.dateOfBirth !== c2.dateOfBirth
c1.greet !== c2.greet

Objektumkonstruktor

let c = new Child('Sári', { year: 2005, month: 3, day: 23 })
function Child(name, dateOfBirth) {
  this.name = name;
  this.dateOfBirth = dateOfBirth;
  this.greet = function() {
    return `Hello, my name is ${this.name}`;
  }
  this.rename = function(newName) {
    this.name = newName;
  }
};

Függvény mint objektum

Konstruktorhívás

function Child() {
  //Új objektum létrehozása a this-ben
  let this = Object.create(Child.prototype);
  //További tulajdonságok hozzáadása
  this.name = 'Anonymous';
  //Visszatérés a létrehozott objektummal
  return this;
}

Metódusok hatékony tárolása

Metódusok hatékony tárolása bővítéssel

Metódusok hatékony tárolása prototípuslánccal

Metódusok hatékony tárolása

function Child(name, dateOfBirth) {
  this.name = name;
  this.dateOfBirth = dateOfBirth;
};
Child.prototype.greet = function() {
  return `Hello, my name is ${this.name}`;
}
Child.prototype.rename = function(newName) {
  this.name = newName;
}
const c = new Child('Sári', { year: 2005, month: 3, day: 23 })
c.greet() // "Hello, my name is Sári"

class

class Child {
  constructor(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;
  }
  greet() {
    return `Hello, my name is ${this.name}`;
  }
  rename(newName) {
    this.name = newName;
  }
}
const c = new Child('Sári', { year: 2005, month: 3, day: 23 })
c.greet() // "Hello, my name is Sári"

function

function Child(name, dateOfBirth) {
  this.name = name;
  this.dateOfBirth = dateOfBirth;
};
Child.prototype.greet = function() {
  return `Hello, my name is ${this.name}`;
}
Child.prototype.rename = function(newName) {
  this.name = newName;
}
const c = new Child('Sári', { year: 2005, month: 3, day: 23 })
c.greet() // "Hello, my name is Sári"

class

class Child {
  constructor(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;
  }
  greet() {
    return `Hello, my name is ${this.name}`;
  }
  rename(newName) {
    this.name = newName;
  }
}
const c = new Child('Sári', { year: 2005, month: 3, day: 23 })
c.greet() // "Hello, my name is Sári"

TypeScript példa

var Child = /** @class */ (function () {
  function Child(name, dateOfBirth) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;
  }
  Child.prototype.greet = function () {
    return "Hello, my name is " + this.name;
  };
  Child.prototype.rename = function (newName) {
    this.name = newName;
  };
  return Child;
}());
function Child(name, dateOfBirth) {
  this.name = name;
  this.dateOfBirth = dateOfBirth;
};
Child.prototype.greet = function() {
  return `Hello, my name is ${this.name}`;
}
Child.prototype.rename = function(newName) {
  this.name = newName;
}

Öröklés – sematikus

Öröklés – bővítés

Öröklés – prototípuslánc

Öröklés – prototípuslánc

let inherit = function (C, P) {
  let F = function () {};
  F.prototype = P.prototype;
  C.prototype = new F();
  C.prototype._super = P.prototype;
  C.prototype.constructor = C;
};

function

function Preschool(name, dateOfBirth, sign) {
  Child.call(this, name, dateOfBirth)
  this.sign = sign
}

inherit(Preschool, Child);

Preschool.prototype.greet = function() {
  return `${this._super.greet.call(this)} (preschool)`
}

class

class Preschool extends Child {
  constructor(name, dateOfBirth, sign) {
    super(name, dateOfBirth)
    this.sign = sign
  }
  greet() {
    return `${super.greet()} (preschool)`
  }
}

TypeScript példa

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Preschool = /** @class */ (function (_super) {
    __extends(Preschool, _super);
    function Preschool(name, dateOfBirth, sign) {
        var _this = _super.call(this, name, dateOfBirth) || this;
        _this.sign = sign;
        return _this;
    }
    Preschool.prototype.greet = function () {
        return _super.prototype.greet.call(this) + " (preschool)";
    };
    return Preschool;
}(Child));

Modulok

Kódszervezési koncepciók

  • Cél
    • áttekinthetőség
    • újrahasznosíthatóság
    • izoláltság
    • karbantarthatóság
    • továbbfejleszthetőség
  • Kódszervezés nyelvi lehetőség
    • objektumok
    • függvények
    • modulok

Függvények

const elems = [],
      push = function (e) {
        elems.push(e);
      },
      pop = function () {
        return elems.pop();
      },
      top = function () {
        return elems[size()-1];
      },
      size = function () {
        return elems.length;
      };

Objektumok

let stack = {
    elems: [],
    push: function (e) {
        this.elems.push(e);
        return this;
    },
    pop: function () {
        return this.elems.pop();
    },
    top: function () {
        return this.elems[this.size()-1];
    },
    size: function () {
        return this.elems.length;
    }
};

stack.push(10).push(20);

stack.top() === 20
stack.size() === 2
stack.elems.join(',') === '10,20'

Minden publikus

Modul minta

Függvény hatókört ad, closure elrejt

const module = (function () {
  //Inicializáló kód
  //Rejtett változók és függvények
  
  //Visszatérés egy objektummal
  return {
    //Publikus interfész
  };
})();

Verem modul

let stack = (function () {
    const elems = [];

    return {
        push: function (e) {
            elems.push(e);
            return this;
        },
        pop: function () {
            return elems.pop();
        },
        top: function () {
            return elems[this.size()-1];
        },
        size: function () {
            return elems.length;
        }
    };
})();

stack.push(10).push(20);

Modul minta variánsai

  • Névterek
  • Globális változók importálása
  • Gyárfüggvény visszaadása
  • Felfedő modul minta
  • Alkalmazásfüggetlen modul minta

Névterek

MyApp.namespace('dataStructures.stack') = (function () {
    /* ... */
})();

Globális változók importálása

let module = (function (win, doc, $, undefined) {
    /* ... */
})(window, document, jQuery);

Konstruktorfüggvény visszaadása

let module = function () {
    
};

//vagy
let module = (function () {
    let Constr = function () {} {
        /* ... */   
    };
    return Constr;
})();

Felfedő modul minta

let stack = (function () {
  const elems = [],
        push = function (e) {
          elems.push(e);
          return this;
        },
        pop = function () {
          return elems.pop();
        },
        top = function () {
          return elems[size()-1];
        },
        size = function () {
          return elems.length;
        };

  return {
    push: push,
    pop: pop,
    top: top,
    size: size
  };
})();

Alkalmazásfüggetlen modul minta

(function (exports) {
    /* ... */
    Object.assign(exports, {
        //Publikus interfész
    });
})(exports);

Modulkezelő könyvtárak

  • CommonJS
    • Node.js, szinkron
  • Aszinkron Modul Definíció (AMD)
    • böngésző, aszinkron, elavult
  • Unified Modul Definition (UMD)
    • egységes, elavult
  • Natív modul
    • egységes, korszerű

CommonJS

//stack.js
let elems = [],
    push = function (e) { /* ... */ },
    pop = function () { /* ... */ },
    top = function () { /* ... */ },
    size = function () { /* ... */ };

Object.assign(exports, {
    push: push,
    pop: pop,
    top: top,
    size: size
});
//app.js
let stack = require('./stack.js');

stack.push(10).push(20);

AMD

define('stack', [], function () {
    let elems = [],
        push = function (e) { /* ... */ },
        pop = function () { /* ... */ },
        top = function () { /* ... */ },
        size = function () { /* ... */ };

    return {
        push: push,
        pop: pop,
        top: top,
        size: size
    };
});
require(['stack'], function (stack) {
    stack.push(10).push(20);
});

UMD

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(typeof self !== 'undefined' ? self : this, function (b) {
    // Use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

Natív modul

// game.js
export class Jatek {
  constructor() {
    this.szam = 0;
  }
  novel() {
    this.counter += 1;
  }
}
import { Jatek } from './game.js';

const jatek = new Jatek();
jatek.novel();
console.log(jatek.szam);

Eszközök

  • Node, npm, nvm
  • http-server, live-server
  • Babel
  • jsbin, codesandbox
  • webpack
  • nodemon, forever

Végszó

  • Objektumok
    • dinamikusak
    • prototípus
    • class, extends
  • Module
  • Eszközök