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();