var p = {}; p.foo = 'foo'; console.log( p.foo );vs
var p = 1; p.foo = 'foo'; console.log( p.foo );
var q = 1; var p = "qwe"; var s = `ala ${q} ma ${p} kota ${2*q+1}`;
var foo = { _i : 0, get bar() { return foo._i; }, set bar(i) { foo._i = i; } } console.log( foo.bar ); foo.bar = 5; console.log( foo.bar );
var foo = { _i : 0, get bar() { return foo._i; }, set bar(i) { foo._i = i; } } Object.defineProperty( foo, 'qux', { get : function() { return 17; } }); console.log( foo.qux );
function sump(x) { var _sum = x; var _f = function(y) { _sum += y; return _f; } _f.valueOf = function() { return _sum; } return _f; } console.log( sump(4)(5)(6) + 1 );
function createGenerator() { var _state = 0; return { next : function() { return { value : _state, done : _state++ >= 10 } } } } // enumerowanie przez while var it = createGenerator(); var _result; while ( _result = it.next(), !_result.done ) console.log( _result.value ); // enumerowanie przez for var it = createGenerator(); for ( var _result; _result = it.next(), !_result.done; ) console.log( _result.value );
var foo = { [Symbol.iterator] : createGenerator }; for ( var f of foo ) console.log(f);
function* createGenerator() { for ( var _state = 0; _state<10; _state++ ) yield _state; }
function Person(name, surname) { return { name : name, surname : surname, say : function() { return `${this.name} ${this.surname}`; } } } var p = Person('jan', 'kowalski'); console.log( p.say() );Proszę we własnym zakresie spróbować w tym podejściu zaimplementować dziedziczenie (czyli możliwość zdefiniowania "podklasy" w której w implementacji "konstruktora" i metod można odwołać się do implementacji konstruktora i metod z "klasy bazowej").
var p = { name : 'jan', say : function() { return this.name; } }; var q = {} // prototypem q będzie p Object.setPrototypeOf( q, p ); q.name = 'tomasz'; // q ma już metodę say, bo pochodzi ona z prototypu console.log( q.say() );Prosty eksperyment z pomocą funkcji
function getLastProto(o) { var p = o; do { o = p; p = Object.getPrototypeOf(o); } while (p); return o; }pokazuje że wszystkie obiekty Javascript mają jedną, tę samą instancję obiektu jako swój prototyp (jest to ładna analogia do języków w których mamy hierarchie typów z jednym typem bazowym dla całej hierarchii - tu mamy jeden obiekt który jest wspólnym prototypem wszystkich obiektów, to w nim znajdują się wyjściowe implementacje m.in. (toString) - dlatego te "podziedziczone" metody są dostępne dla wszystkich obiektów).
var person = { init : function(name, surname) { this.name = name; this.surname = surname; }, say : function() { return `${this.name} ${this.surname}`; } } var p = Object.create( person ); p.init( 'jan', 'kowalski' ); console.log( p.say() );
var worker = Object.create( person ); worker.init = function( name, surname, age ) { // "wywołanie konstruktora klasy bazowej" person.init.call( this, name, surname ); this.age = age; } worker.say = function() { // "wywołanie metody z klasy bazowej" var _ = person.say.call( this ); return `${_} ${this.age}`; } var w = Object.create( worker ); w.init('tomasz','malinowski',48); console.log( w.say() );
var Person = function(name, surname) { this.name = name; this.surname = surname; } Person.prototype.say = function() { return `${this.name} ${this.surname}`; } var p = new Person('jan', 'kowalski'); console.log( p.say() );
var Worker = function(name, surname, age) { // wywołanie bazowej funkcji konstruktorowej Person.call( this, name, surname ); this.age = age; } // powiązanie łańcucha prototypów Worker.prototype = Object.create( Person.prototype ); Worker.prototype.say = function() { // "wywołanie metody z klasy bazowej" var _ = Person.prototype.say.call( this ); return `${_} ${this.age}`; } var w = new Worker('jan', 'kowalski', 48); console.log( w.say() );
// alternatywa dla new f() // wyrażona przy pomocy Object.create function New( f, ...args ) { var _ = Object.create( f.prototype ); var o = f.apply( _, args ); if ( o ) return o; else return _; } var p = New( Person, 'jan', 'kowalski' ); console.log( p.say() );
// alternatywa dla Object.create( p ) // wyrażona przy pomocy "new" function ObjectCreate( p ) { var f = function() { }; f.prototype = p; return new f(); } var p = ObjectCreate( person ); p.init('jan', 'kowalski'); console.log( p.say() );
String.prototype.reverse = function() { return this.split('').reverse().join(""); } console.log( 'foo bar'.reverse() );W ten sposób można wręcz rozszerzyć prototyp Object.prototype, dodając nowe metody do wszystkich możliwych obiektów.
class Person { constructor(name, surname) { this.name = name; this.surname = surname; } say() { return `${this.name} ${this.surname}`; } } class Worker extends Person { constructor(name, surname, age ) { super(name, surname); this.age = age; } say() { // "wywołanie metody z klasy bazowej" var _ = super.say(); return `${_} ${this.age}`; } } var w = new Worker('tomasz', 'malinowski', 48); console.log( w.say() );
C++/Java/C# | Javascript |
---|---|
Klasy | Funkcje konstruktorowe |
Przestrzenie nazw | Obiekty z polami które są referencjami do innych obiektów |
Składowe prywatne | Umieszczanie elementów w domknięciach funkcji |
Modularność include/import/using | Modularność require, przykład z wykładu |
var fs = require('fs'); function read1Sync() { var content = fs.readFileSync( './test.txt', 'utf-8' ); console.log( content ); } read1Sync();
var fs = require('fs'); function read1Async() { fs.readFile( './test.txt', 'utf-8', function( err, data ) { console.log( data ); } ); } read1Async();
var fs = require('fs'); fs.readFile( 'foo.txt', 'utf-8', function( err, data ) { fs.readFile( 'bar.txt, 'utf-8', function( err, data2 ) { console.log( data ); console.log( data2 ); } } } );O takiej strukturze zagnieżdżonych wywołań funkcji oczekujących callbacków mówi się "callback-hell". Problem ten można tylko częściowo rozwiązać refaktoryzując kod do
var fs = require('fs'); fs.readFile( 'foo.txt', 'utf-8', function( err, data ) { continuation(data); } ); function continuation(data) { fs.readFile( 'bar.txt, 'utf-8', function( err, data2 ) { console.log( data ); console.log( data2 ); } } }ale to rozwiązanie nie jest najlepsze - powoduje że kod asynchroniczny staje się listą funkcji, których wzajemne wywoływania się nawzajem nie są łatwe do odczytania (niektórzy stosują konwencje komentowania, inni konwencje nazewnicze, ale to tylko półśrodki). O ultymatywnym rozwiązaniu problemu callback-hell za moment.
var fs = require('fs'); function read1Stream() { var buf =''; var s = fs.createReadStream( './test.txt', { encoding :'utf-8' } ); s.on( 'data', function(data) { buf += data.toString(); }); s.on('end', function() { console.log( buf ); }); } read1Stream();
var fs = require('fs'); function read1Stream() { var buf =''; var s = fs.createReadStream( './test.txt', { encoding :'utf-8' } ); s.pipe( process.stdout ); } read1Stream();
function fspromise(path) { return new Promise( function( res, rej ) { fs.readFile(path, function(err, data) { if ( err ) rej(err ); res( data.toString() ); }); } ); } fspromise('foo.txt') .then(function(data) { console.log(data); });Problem zaczyna się przy odczycie danych z dwóch lub więcej plików i można do tego podejść naiwnie zagnieżdżając wywołania
fspromise('foo.txt') .then(function(data) { fspromise('bar.txt', function(data2) { console.log(data); console.log(data2); }); });albo zwracając Promise z then poprzedniej Promise, wtedy kolejny then odnosi się już do tej nowej Promise
fspromise('foo.txt') .then(function(data) { console.log(data); return fspromise('bar.txt'); }); .then(function(data2) { console.log(data2); }) });
var f1 = fspromise('./alamakota.txt'); var f2 = fspromise('./alamakota2.txt'); var f3 = Promise.all([f1,f2]) .then(function([data, data2]) { console.log( data ); console.log( data2 ); } );Jak to możliwe? Bardzo naiwna implementacja zrobienia promise z dwóch innych:
function promiseall(p1, p2) { return p1.then(function(p1r) { return p2.then(function(p2r) { return new Promise( function(res, rej) { res([p1r,p2r]); }); }); }); }Implementacja biblioteczna robi to oczywiście lepiej.
Nie. Zarówno node.js jak i przeglądarki (oprócz IE) obsługują konstrukcję async/await za pomocą której pisanie kodu asynchronicznego nie będzie już wymagało ani funkcji zwrotnych ani promis, a kod będzie wyglądał jakby był synchroniczny.
W Javascript, async/await jest oparte o obiekty typu Promise.
// funkcja oznakowana jako async // zwraca Promise function promisedGet(url) { return new Promise(function (resolve, reject) { var client = http.get(url, function (res) { var buffer = ''; res .on('data', function (data) { buffer += data.toString(); }) .on('end', function () { resolve(buffer); }); }); }); } // funkcja oznakowana jako async, // wolno w niej wywoływać inną funkcję zwracającą Promise przez await (async function() { // await ___ ... var result = await promisedGet('http://www.google.pl'); console.log( result ); // jest równoważne dzisiejszemu // // promisedGet('http://www.google.pl') // .then( function(result) { console.log( result );} ); // // a umieszczeniem całego kodu napisanego "pod" await w kontynuacji // doczepionej do Promise przez then zajmuje się już sam kompilator })();
I to właśnie async/await jest ultymatywnym rozwiązaniem problemu callback-hell:
(async function() { var data1 = await promisedGet('http://www.www1.com'); var data2 = await promisedGet('http://www.www2.com'); })();
var fs = require('fs'); var util = require('util'); var readFileAsync = util.promisify(fs.readFile); (async function() { var data1 = await readFileAsync('foo.txt'); var data2 = await readFileAsync('bar.txt'); })();
var http = require('http'); http.createServer( (req, res) => { res.setHeader('Content-type', 'text/html; charset=utf-8'); res.write('Witamy w node.js, polskie znaki ąłżńóź'); res.end(); }) .listen(3000);Adresowalny z przeglądarki przez http://localhost:3000
https.createServer( { pfx : fs.readFileSync( 'test.pfx'), passphrase : 'foo' }, (req, res) => .... );
var http = require('http'); var express = require('express'); var ejs = require('ejs'); var session = require('express-session'); var cookieParser = require('cookie-parser'); function createResponse(req, res, name) { res.setHeader('Content-type', 'text/html; charset=utf-8'); res.write( `<html> <body> Witaj ${name}, <br/> <form method='POST'> <input type='text' name='name' /> <input type='submit' value='Zapisz' /> </form> </body> </html>` ); res.end(); } http .createServer((req, res) => { var imie; if ( req.method == 'GET') { createResponse(req, res, ''); } else { var buf = ''; req.on('data', data => { buf += data.toString(); }); req.on('end', () => { var postData = buf.split("&").map( s => s.split('=')) .reduce( (o,[k,v]) => (o[k] = v, o), {} ); createResponse(req, res, postData.name); }); } }) .listen(3000); console.log('started');
npm init -y npm install express --save npm install ejs --save
var http = require('http'); var express = require('express'); var app = express(); app.set('view engine', 'ejs'); app.set('views', './views'); // tu dodajemy middleware ... // tu uruchamiamy serwer var server = http.createServer(app).listen(3000); console.log( 'serwer started' );
<html> <body> <% var foo = 'bar' %> <% for ( var i=0; i<5; i++ ) { %> <input value='<%= i %>' /> <% } %> </body> </html>
app.get('/', (req, res) => { var model = { user : 'jan' }; res.render('user', model); }); // user.ejs <html> <body> Użytkownik : <%= user %> </body> </html>
app.get('/przelewy', (req, res) => { var przelewy = [ { kwota : 123, data : '2016-01-03', id = 1 }, { kwota : 124, data : '2016-01-02', id = 2 }, { kwota : 125, data : '2016-01-01', id = 3 }, ]; res.render('przelewy', { przelewy : przelewy } ); }); // przelewy.ejs <html> <body> <table> <tr> <th>Data</th> <th>Kwota</th> <th></th> </tr> <% przelewy.forEach( przelew => { %> <tr> <td><%= przelew.data %></td> <td><%= przelew.kwota %></td> <td><a href='/przelew/<%= przelew.id %>'>Więcej</a></td> </tr> <% }) %> </table> </body> </html>
ol>li*5i naciśnięcie TAB spowoduje rozwinięcie tego w listę z 5 elementami. Zamiana rozszerzeń polega na tym że zmienia się rozszerzenia plików a w kodzie aplikacji rejestruje się nowy, "udawany", silnik renderowania, na funkcję która pochodzi z EJS
var ejs = require('ejs'); ... app.set('view engine', 'html'); app.set('views', './views'); app.engine('html', ejs.renderFile);
app.get('/plik', (req, res) => { // proszę zaremować i odremować tę linijkę i porównać wynik res.setHeader('Content-disposition', 'attachment; filename=plik.txt'); res.write('123'); res.end(); });
res.redirect('innastrona');
żądanie do http://localhost:3000/strona?p1=v1&p2=v2 app.get('/strona', (req, res) => { var p1 = req.query.p1; var p2 = req.query.p2; });Uwaga na możliwy atak Querystring Tamepring
app.use( express.urlencoded({extended:true}) ) ; app.get('/login', (req, res) => { res.render('login'); }); app.post('/login', (req, res) => { var username = req.body.username; var pwd = req.body.pwd; }); // login.ejs <html> <body> <form method="POST"> <div> Login: <input type='text' name='username' /> </div> <div> Password: <input type='password' name='pwd' /> </div> <div> <button>Login</button> </div> </form> </body> </html>
// instalacja middleware app.use( express.static('./static'));Od tego momentu wszystkie pliki w folderze '/static' są serwowane na żądania adresowane przez '/', np. do pliku '/static/app.css' odwołamy się przez
<link rel='stylesheet' href='/app.css'>
import * as http from "http" // zaremować przed uruchomieniem /** * * @param {http.IncomingMessage} req * @param {http.ServerResponse} res * @param {*} next */ function middleware(req, res, next) { req. // <- tu działa podpowiadanie składni }
app.use((req,res,next) => { res.render('404.ejs', { url : req.url }); }); // 404.ejs <html> <body> Strona <%= url %> nie została znaleziona. </body> </html>
app.get('/przelew/:id', (req, res) => { var id = req.params.id; res.render('przelew', { id : id } ); });Tak zdefiniowany routing może korzystać z wyrażeń regularnych Przykładowo, ścieżka która chwyta wyłącznie liczby
app.get('/przelew/:id(\\d+)', (req, res) => { var id = req.params.id; res.render('przelew', { id : id } ); });
"console": "integratedTerminal"w .vscode/launch.json w Visual Studio Code, umożliwiająca przełączenie konsoli do terminala (z zakładki debug console)
app.disable('etag');
// instalacja middleware npm install cookie-parser --save // w aplikacji var cookieParser = require('cookie-parser'); app.use( cookieParser() ); // wydanie ciastka res.cookie('foo', 'bar'); // sprawdzenie ciastka req.cookies.foo;
// wydanie ciastka res.cookie('foo', 'bar', { maxAge : 60 * 1000 } ); // 60 sekund // usunięcie ciastka z przeglądarki res.cookie('foo', '', { maxAge : -1 } );
// w aplikacji var cookieParser = require('cookie-parser'); app.use( cookieParser('9877dg9fb8sd79b87sdt9b87ds98b') ); // secret // wydanie ciastka res.cookie('foo', 'bar', { signed : true }); // sprawdzenie ciastka req.signedCookies.foo;
// instalacja middleware npm install express-session --save // deklaracja użycia middleware w potoku app.use(session({resave:true, saveUninitialized: true, secret: 'qewhiugriasgy'})); // dodawanie danych do sesji req.session.foo = 'bar'; // odczyt danych z sesji var foo = req.session.foo;
// select.ejs <select name='<%= name %>'> <% options.forEach( option => { %> <option value='<%= option.value %>'><%= option.text %></option> <% }) %> </select> // wywołanie szablonu z innego szablonu <% var name='combo1' %> <% var options= [ { value : 1, text : 'element 1' }, { value : 2, text : 'element 2' }, { value : 3, text : 'element 3' } ] %> <% include select %>
app.get('/', (req, res) => { if ( !req.cookie.user ) res.redirect('/login'); else ... });
// middleware logowania function authorize(req, res, next) { if ( req.cookies.user ) { // użytkownik jest zalogowany // "przepisanie" danych użytkownika żeby dostęp był jednolity req.user = req.cookies.user; next(); } else { // użytkownik niezalogowany // przekierowanie z zapamiętaniem strony która spowodowała przekierowanie res.redirect( '/login?returnUrl='+req.url); } } // wymuszenie logowania na konkretnym zasobie app.get('/', authorize, (req, res) => { // to wykona się tylko jeśli middleware authorize przejdzie przez ścieżkę zalogowanego użytkownika // w przeciwnym przypadku żądanie zostanie tam przekierowane na /login?returnUrl=/ }); // logowanie app.get('/login', (req, res) => { // wyrenderuj stronę logowania res.render('login'); }); app.post('/login', (req, res) => { // użytkownik kliknął przycisk "zaloguj" na stronie logowania (POST) var username = req.body.txtUser; // na stronie jest <input type='text' name='txtUser' ... var pwd = req.body.txtPwd; // na stronie jest <input type='password' name='txtPwd' ... if ( username == pwd ) { // w rzeczywistości - sprawdzenie w bazie danych res.cookie('user', username); // wydanie ciastka, od tego momentu użytkownik jest zalogowany var returnUrl = req.query.returnUrl; res.redirect( returnUrl ); // powrót do miejsca z którego nastąpiło przekierowanie tutaj } else { // nie udało się zalogować, trzeba znów pokazać stronę logowania z komunikatem res.render('login', { message : 'Zła nazwa logowania lub hasło' }); } });
npm installżeby dociągnąć wszystkie pakiety wymienione w package.json.
git init git add . git commit -m "initial commit" heroku login heroku create git push heroku master
CREATE TABLE [dbo].[Child]( [ID] [int] IDENTITY(1,1) NOT NULL, [ChildName] [nvarchar](150) NOT NULL, [ID_PARENT] [int] NOT NULL, CONSTRAINT [PK_Child] PRIMARY KEY CLUSTERED ( [ID] ASC ) ) ON [PRIMARY] GO CREATE TABLE [dbo].[Parent]( [ID] [int] IDENTITY(1,1) NOT NULL, [ParentName] [nvarchar](150) NOT NULL, CONSTRAINT [PK_Parent] PRIMARY KEY CLUSTERED ( [ID] ASC ) ) ON [PRIMARY] ALTER TABLE [dbo].[Child] WITH CHECK ADD CONSTRAINT [FK_Child_Parent] FOREIGN KEY([ID_PARENT]) REFERENCES [dbo].[Parent] ([ID])
try { var conn = new sql.ConnectionPool('server=localhost,1433;database=database2;user id=foo;password=foo'); await conn.connect(); var request = new sql.Request(conn); var result = await request.query('select * from Parent') result.recordset.forEach( r => { console.log( `${r.ID} ${r.ParentName}<br/>`); }); conn.close(); } catch ( err ) { console.log( err ); }
// repo/parentRepo module.exports.insert = async function(conn, parent) { try { var req = new sql.Request(conn); req.input("ParentName", parent.parentName); var result = await req.query( "insert into Parent (ParentName) values (@ParentName) select scope_identity() as id"); return result.recordset[0].id; } catch ( err ) { // cokolwiek, włącznie z throw err; } }
var assert = require('assert'); describe('Top level', function() { describe('Sub level', function() { it( 'Unit test function definition', function() { assert.equal( [1,2,3].indexOf(4), -1 ); }); }); });
var webdriverio = require('webdriverio'); var options = { desiredCapabilities: { browserName: 'chrome' } }; var client = webdriverio.remote(options); client .init() .url('https://duckduckgo.com/') .setValue('#search_form_input_homepage', 'WebdriverIO') .click('#search_button_homepage') .getTitle().then(function(title) { console.log('Title is: ' + title); // outputs: "Title is: WebdriverIO (Software) at DuckDuckGo" }) .end();