Slajd 1wzychla/files/Javascript.pdfTitle: Slajd 1 Author: Olam Created Date: 2/2/2015 10:53:18 PM
Transcript of Slajd 1wzychla/files/Javascript.pdfTitle: Slajd 1 Author: Olam Created Date: 2/2/2015 10:53:18 PM
Organizacja ucząca się Javascript - wprowadzenie
07-03-2014
Wiktor Zychla
Agenda
• Wprowadzenie
• Funkcje/domknięcia
• Obiekty • this
• Dziedziczenie
• Enkapsulacja
• Perspektywy
• Egzamin
Wprowadzenie - historia
• 1995 – Brendan Eich dla Netscape
• 1996 – ECMA 262
• 1996 – Microsoft wprowadza do IE3
• 1998 – ES2
• 1999 – ES3
• 2009 – ES5
• 2011 – ES5.1
• 201? – ES6
http://kangax.github.io/es5-compat-table/
Host
Kod Javascript wykonuje się w ramach hosta,
typowo jest to przeglądarka. Do obiektu hosta jest
dostęp jawny:
window.x = 3;
x = 3; // równoważne, zmienna globalna
Zmienne lokalne
var x = 3;
Host Host daje dostęp do struktury dokumentu który jest aktualnie załadowany
(DOM). Javascript może tę strukturę dynamicznie modyfikować.
Obecnie większość przeglądarek ma silniki kompilujące Javascript do
kodu natywnego przed wykonaniem.
Javascript nie tylko jest więc coraz bardziej zgodny, ale też jest bardzo
szybki.
http://www.wiktorzychla.com/2014/02/animated-javascript-julia-
fractals.html
http://jsdosbox.appspot.com/
Funkcje
Funkcje nazwane/nienazwane function() {}
function f() {}
Funkcje
Funkcje zdefiniowane w compile-time vs run-time
f();
function f() {}
vs f();
var f = function() {}
Zasięg widoczności zmiennych
Globalny
var x = 5;
var f = function() {
console.log('x='+x);
}
f();
Zasięg widoczności zmiennych
Lokalny
var x = 5;
var f = function() {
var x = 1;
console.log('x='+x);
}
f();
Zasięg widoczności zmiennych
Uwaga na „hoisting”
var x = 5;
var f = function() {
console.log('x='+x);
var x = 3;
}
f();
Zasięg widoczności zmiennych
Hoisting polega na tym, że w zasięgu bloku
wszystkie zmienne są „wirtualnie” zdefiniowane na
początku bloku.
var x = 5;
var f = function() {
var x;
console.log('x='+x);
x = 3;
}
f();
Funkcje
Javascript jest językiem funkcyjnym – funkcje mogą
być przekazywane do funkcji jako argumenty i
zwracane jako wyniki.
Funkcje
Funkcja przekazywana jako argument
function f(g) {
return g();
}
f(function() { return 1; });
Funkcje
Funkcja zwracana jako wartość
function f() {
function g() {
return 1;
}
return g;
}
f()();
Domknięcia
Domknięcie to „ukryty” parametr funkcji, który łapie
wszystkie zmienne z których funkcja korzysta, ale
nie definiuje ich.
function sum1(x,y) {
return x + y;
}
console.log( sum1(2,3) );
Domknięcia To samo z domknięciem
function sum2(x) {
function helper(y) {
return x + y;
}
return helper;
}
console.log(sum2(2)(3));
Kontekst funkcji wewnętrznej helper domyka
zmienną x.
Domknięcia
Ekstremalnie: function sum3(x) {
var _s = x;
function helper(y) {
_s += y;
return helper;
}
helper.toString = function() { return _s; }
return helper;
}
var result = sum3(1)(2)(3)(4); // dowolna liczba parametrów
console.log(result);
Domknięcia
Kontekst jest obiektem, więc może być modyfikowany: function f(person) {
function g(message) {
console.log( 'message [' + message + '] to ' +
person.name );
}
return g;
}
var p = { name : 'jan' }
var messenger = f(p);
messenger('witaj!'); // witaj to jan
p.name = 'tomasz';
messenger('ukłony'); // ukłony to tomasz
Domknięcia Co czasem bywa nieoczekiwane function createFs(n) { // tworzy tablicę n funkcji
var fs = []; // i-ta funkcja z tablicy ma zwrócić i
var i;
for ( i=0; i<n; i++ ) {
fs[i] = function() {
return i;
}; // <- funkcja się nie wykonuje, jest obiekt kontekstu do którego trafia i
// ale wartość i się zmienia zanim funkcje zaczną się wykonywać
};
return fs;
}
var myfs = createFs(10);
console.log( myfs[0]() ); // zerowa funkcja miała zwrócić 0
console.log( myfs[2]() ); // druga miała zwrócić 2
console.log( myfs[7]() );
// output : 10,10,10, oops, wszystkie zwracają 10!
Domknięcia Ale nietrudno to naprawić function createFs(n) {
var fs = [];
var i;
for ( i=0; i<n; i++ ) {
fs[i] = function(x) {
return function() {
return x;
};
}(i); // <- funkcja od razu się wykonuje i za każdym razem tworzy nowy kontekst
// (n różnych kontekstów vs jeden i ten sam)
};
return fs;
}
var myfs = createFs(10);
console.log( myfs[0]() );
console.log( myfs[2]() );
console.log( myfs[7]() );
// output: 0, 2, 7
Obiekty Najprościej
var p = {}; // albo new Object();
p.name = 'jan';
console.log(p.name);
Notacja . vs []
var p = {}
p.name = 'jan';
p['name'] = 'tomasz';
console.log(p.name);
Notacja [] jest bardziej ogólna bo parametr może być wyrażeniem
var p = {}
p.name = 'jan';
p['na'+'me'] = 'tomasz';
console.log(p.name);
Obiekty Składnia literalna (literal syntax)
var p = {
name : 'jan',
age : 15
}
console.log( p.name );
Drzewa obiektów (obiekty kompozytowe)
var p = {
name : 'jan',
address : {
city : 'wrocław'
}
}
console.log( p.address.city );
Obiekty - dygresja Iteracja po tablicy vs iteracja po polach obiektu
var a = [];
a.push(1);
a.push(2);
for ( var i=0; i<a.length; i++ )
console.log( a[i] );
vs
var p = {
name : 'jan',
age : 17
}
for ( var key in p ) {
console.log( key + ' : ' + p[key] );
}
Obiekty - dygresja Parametry są przekazywane przez wartość
function f( x ) {
x = 123;
}
var a = 1;
f(a);
console.log(a); // 1. funkcja nie modyfikuje wartości
czyli w przypadku obiektów – przez wartość referencji
function f( x ) {
x.age = 123;
}
var p = { age : 1 };
f(p);
console.log(p.age);} // 123.
// referencja nie jest modyfikowana ale pole za nią tak
This v.1 W funkcjach instancji obiektu this odnosi się do instancji obiektu …
var person = {
name : 'jan',
say : function() {
return this.name;
}
}
console.log( person.say() );
This v.2 W funkcjach spoza obiektów this odnosi się domyślnie do hosta (window)
var name = 'jan';
say = function() {
return this.name;
}
console.log( say() );
This v.2 … ale można wywołać funkcję nie wprost, tylko przez call, które jako pierwszy
parametr przyjmuje obiekt dowiązywany to this wewnątrz funkcji
p = { name : 'jan' };
say = function() {
return this.name;
}
console.log( say.call( p ) );
This v.2 - dygresja Wywołania przez call a apply
p = { name : 'jan' };
say = function(m1, m2) {
return this.name + ' has ' + m1 + ' and ' + m2;
}
console.log( say.call( p, 'a', 'b' ) ); // lista parametrów
p = { name : 'jan' };
say = function(m1, m2) {
return this.name + ' has ' + m1 + ' and ' + m2;
}
console.log( say.apply( p, ['a', 'b'] ) ); // tablica parametrów
Dziedziczenie W Javascript nie ma klas.
Tyle że w języku obiektowym nie potrzeba klas – potrzeba obiektów.
Nowe obiekty tworzy się albo ad-hoc albo klonując istniejące „wzorcowe” obiekty
– mówimy o nich prototypy.
var personProto = { // prototyp
name : 'jan',
say : function() {
return this.name;
}
}
var p1 = Object.create( personProto ); // klonowanie prototypu
p1.name = 'tomasz';
var p2 = Object.create( personProto );
p2.name = 'janusz';
console.log( p1.say() );
console.log( p2.say() );
Dziedziczenie Nieoczekiwany problem - obiekt kompozytowy w prototypie
var personProto = {
name : 'jan',
address : {
city : 'wrocław'
},
say : function() {
return this.address.city;
}
}
var p1 = Object.create( personProto );
p1.address.city = 'kraków';
var p2 = Object.create( personProto );
p2.address.city = 'łódź';
console.log( p1.say() );
console.log( p2.say() );
// output : łódź, łódź zamiast kraków, łódź
Jak to obejść?
Dziedziczenie Można tak
var personProto = {
name : 'jan',
address : {
city : 'wrocław'
},
say : function() {
return this.address.city;
}
}
var p1 = Object.create( personProto );
p1.address = { city : 'kraków' }; // za każdym razem twórz nowy podobiekt
var p2 = Object.create( personProto );
p2.address = { city : 'łódź' };
console.log( p1.say() );
console.log( p2.say() );
Ale to nie wygląda dobrze, bo trzeba o tym pamiętać.
Dziedziczenie Rozwiązaniem jest taki prototyp, który klonuje się dedykowaną metodą tworzącą (factory function):
var person = {
create : function(name, city) {
var _ = Object.create( this );
_.name = name;
_.address = {
city : city
};
return _;
},
say : function() {
return this.address.city;
}
}
var p1 = person.create( 'jan', 'kraków' );
var p2 = person.create( 'tomasz', 'łódź' );
console.log( p1.say() );
console.log( p2.say() );
Dziedziczenie W ten sposób można zaimplementować dziedziczenie var person = {
create : function(name, city) {
var _ = Object.create( this );
_.name = name;
_.address = {
city : city
};
return _;
},
say : function() {
return this.address.city;
}
}
var pupil = {
create : function(name, city, school) {
var _ = person.create.call( this, name, city );
_.school = school;
return _;
},
say : function() {
var _ = person.say.call( this );
return _ + ' ' + this.school;
}
}
var p1 = person.create( 'jan', 'kraków' );
var p2 = pupil.create( 'tomasz', 'łódź', 'sp1' );
console.log( p1.say() );
console.log( p2.say() );
Dziedziczenie Wszystko pięknie tylko co się stanie jeśli pupil nie ma dostarczonej implementacji metody say, a
zamiast tego oczekujemy że zawoła się metoda z prototypu (mówiąc żargonem obiektowym: z klasy bazowej)?
Zakomentowanie metody say w pupil pokazuje że całość przestaje działać. pupil jako prototyp nie
ma metody say!
Można próbować to ratować:
var person = {
create : function(name, city) {
var _ = Object.create( person );
_.name = name;
_.address = {
city : city
};
return _;
},
say : function() {
return this.address.city;
}
}
ale wtedy przestaje działać przeciążanie metody say w pupil
Dziedziczenie Prawdziwym źródłem problemu jest to, że pupil nie powstał z person w taki sam sposób w jaki
konstruujemy pozostałe obiekty – przez sklonowanie.
Zamiast tego zarówno pupil jak i person powstały w sposób literalny.
Niestety, dostęp do prototypu obiektu nie jest jawny (jeśli się da to nie jest to standard). Nie można więc
tego łatwo naprawić.
Jedyny uniwersalny sposób to wprowadzić nieładną funkcję do klonowania prototypów i rozszerzania ich
o dodatkowe właściwości:
var extend = function(base, extension) {
// klonuj prototyp
var object = Object.create(base);
// wkopiuj mu rozszerzone właściwości
var hasOwnProperty = Object.hasOwnProperty;
for (var property in extension)
if (hasOwnProperty.call(extension, property) ||
typeof object[property] === "undefined")
object[property] = extension[property];
return object;
};
Dziedziczenie W takim środowisku rozszerzanie obiektów (dziedziczenie) działa tak jak powinno:
var pupil = extend( person, {
create : function(name, city, school) {
var _ = person.create.call( this, name, city );
_.school = school;
return _;
}
//,
//say : function() {
// var _ = person.say.call( this );
// return _ + ' ' + this.school;
//}
} );
Metodę say można zakomentować (i wtedy działa implementacja z prototypu, z person) albo
odkomentować (i wtedy działa implementacja z pupil).
Wiele frameworków mimo to podąża tą ścieżką.
Dziedziczenie Ale dziedziczenie można osiągnąć inaczej, przez funkcje konstruktorowe (constructor function)
function Foo() {
...
}
var f = new Foo();
to jest równoważne
var f = new Object();
f.[[prototype]] = Foo.prototype;
f.Foo(); // binduje this
gdzie
Foo.prototype – to prototyp który funkcja konstruktorowa przypina jako prototyp do nowo tworzonego
obiektu. W przeciwieństwie do poprzedniego podejścia, prototyp ten jest jawny i można go modyfikować.
Dziedziczenie Mamy wtedy …
function Person(name, city) {
this.name = name;
this.address = {
city : city
};
this.say = function() {
return this.name + ' ' + this.address.city;
};
}
var p1 = new Person('jan', 'wrocław');
console.log( p1.say() );
object Prototype1
Person.prototype
p1
name = name
say = function
p2
name = name
say = function
Dziedziczenie … albo (uwaga!)
function Person(name, city) {
this.name = name;
this.address = {
city : city
};
}
Person.prototype.say = function() {
return this.name + ' ' + this.address.city;
};
var p1 = new Person('jan', 'wrocław');
console.log( p1.say() );
object Prototype2
Person.prototype
say = function
p2
name = name
p1
name = name
Dziedziczenie Dziedziczenie jest łatwiejsze:
function Person(name, city) {
this.name = name;
this.address = {
city : city
};
}
Person.prototype.say = function() {
return this.name + ' ' + this.address.city;
};
function Pupil(name, city, school) {
Person.call( this, name, city );
this.school = school;
}
var p1 = new Person('jan', 'wrocław');
console.log( p1.say() );
var p2 = new Pupil('tomasz', 'łódź', 'sp1');
console.log( p2.say() ); // problem!
Oops, nie działa say w pupil!
Dziedziczenie Tym razem obrona jest łatwa:
function Person(name, city) {
this.name = name;
this.address = {
city : city
};
}
Person.prototype.say = function() {
return this.name + ' ' + this.address.city;
};
function Pupil(name, city, school) {
Person.call( this, name, city );
this.school = school;
}
Pupil.prototype = new Person();
// lub Pupil.prototype = Person.prototype jeżeli metody są ustawiane tylko w prototypie
var p1 = new Person('jan', 'wrocław');
console.log( p1.say() );
var p2 = new Pupil('tomasz', 'łódź', 'sp1');
console.log( p2.say() );
class Jav ascript
Person.prototype Pupil.prototype p2
Dziedziczenie Wywołanie metody z klasy bazowej jest łatwe:
function Person(name, city) {
this.name = name;
this.address = {
city : city
};
}
Person.prototype.say = function() {
return this.name + ' ' + this.address.city;
};
function Pupil(name, city, school) {
Person.call( this, name, city );
this.school = school;
}
Pupil.prototype = new Person();
Pupil.prototype.say = function() {
var _ = Person.prototype.say.call( this );
return _ + ' ' + this.school;
}
var p1 = new Person('jan', 'wrocław');
console.log( p1.say() );
var p2 = new Pupil('tomasz', 'łódź', 'sp1');
console.log( p2.say() );
Enkapsulacja Składowe prywatne w klasie – przez domknięcie
function Person(name) {
var _private = 5;
this.name = name;
this.say = function() {
return this.name + ' ' + _private;
}
}
var p = new Person('jan');
console.log( p.say() );
console.log( p._private ); // niedostępne
Enkapsulacja Moduły – zagnieżdżone obiekty
function CreateNamespace( moduleName ) {
var parent = window;
var seg = moduleName.split('.');
for ( var part=0; part<seg.length; part++ )
{
var segName = seg[part];
if ( typeof parent[segName] === 'undefined' )
parent[segName] = {};
parent = parent[segName];
}
}
CreateNamespace( 'Vulcan.Architekt.CentralneSlowniki' );
Enkapsulacja I teraz
Vulcan.Architekt.CentralneSlowniki = {
// klasa publiczna
Person : function(name) {
this.name = name;
this.say = function() {
return name;
}
}
}
var p = new Vulcan.Architekt.CentralneSlowniki.Person('jan');
console.log( p.say() );
Enkapsulacja Można nawet mieć w module prywatną klasę:
Vulcan.Architekt.CentralneSlowniki = (function() {
// klasa prywatna bo w domknięciu funkcji która się od razu wykonuje
function Private() {
this.helper = function(s) {
return s + ' from helper!';
}
}
return {
// klasa publiczna
Person : function(name) {
this.name = name;
this.say = function() {
var _pr = new Private();
return _pr.helper(this.name);
}
}
}
}());
var p = new Vulcan.Architekt.CentralneSlowniki.Person('jan');
console.log( p.say() );
var q = new Vulcan.Architekt.CentralneSlowniki.Private(); // nie
ECMAScript 6 Wiele fajnych nowych rzeczy, m.in. lambda wyrażenia:
var a = [ "Wlazł kotek na płotek",
"I mruga"
];
// po staremu
var a2 = a.map(function(s){ return s.length });
// po nowemu
var a3 = a.map( s => s.length );
Podsumowanie
Javascript to nowoczesny i wydajny język tworzenia
aplikacji.
Jedynym minusem jest uboga biblioteka
standardowa, co oznacza, że trzeba wspierać się
zewnętrznymi frameworkami.
Dziękuję za uwagę