Slajd 1wzychla/files/Javascript.pdfTitle: Slajd 1 Author: Olam Created Date: 2/2/2015 10:53:18 PM

Post on 24-Sep-2020

2 views 0 download

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ę