Referințe în JavaScript

Există o serie de mituri despre limitele JavaScript în ce privește trăsăturile moderne ale unui limbaj de programare. Unul dintre acestea este afirmația că ar trebui să declarați variabilele locale la începutul unei funcții deoarece ele există de-a lungul scopului local al acelei funcții. Deși variabilele locale nu sunt restricționate la nivel de scop al unui bloc precum în alte limbaje OOP, ele există totuși doar după locul unde au fost declarate. Astfel, codul în care acestea apar la început și codul în care acestea apar prima dată acolo unde sunt declarate, deși ambele valide, nu sunt echivalente (scopul de acces nu e același u scopul de existență). De fapt, este mai lizibil să declari un contor care nu e folosit decât după 20 de linii de cod, în cel loc, decât să aglomerezi începutul funcției cu variabile auxiliare. Chiar dacă declarați de două ori un contor local, browser-ele moderne nu se vor bloca acolo și puteți curăța codul final cu JSLint/JSHint/ESLint pentru a evita duplicatele.
Ali mit mai important este acela că deoarece sunt mereu transmise prin valoare , în JavaScript nu se poate programa cu pointer-i sau referințe. Deși sintaxa limbajului folosește caracterul punct pentru a se referi la membrii unui obiect, nu există nici un mecanism pentru a le accesa adresele (adică pointer-i). Dar, când obiectul este pasat unei funcții, motorul limbajului face o copie a obiectului, dar nu face și copii ale membrilor săi. Specificația precizează că membrii copiei sunt membrii inițiali ai obiectului original. Deci, ce este copiat în acest caz?
Membrii unui obiect ar trebui priviți ca fiind legați la obiectul lor părinte, în loc să fie incluși în interior. Când obiectul este copiat, legăturile la membrii săi sunt cele care devin copiate, și nu membrii. O întrebare ar putea apărea: ce sunt aceste legături? Răspunsul este legat de o altă facilitate a JavaScript: colectarea memoriei alocate. Este specificat că atunci când o variabilă nu mai este folosită, memoria alocată pentru acea variabilă este eliberată de motorul de colectare. Mai mult, anumite implementări precizează că atunci când numărul de referințe către un obiect ajunge la zero, acel obiect candidează la colectarea memoriei alocate. Aceasta este o dezvăluire despre faptul că acele legături la membrii obiectului nu sunt simple legături, ele sunt mai degrabă referințele sau pointer-ii întâlnite în celelalte limbaje OOP.
Pentru a exemplifica limitarea percepută la manevrarea membrilor obiectului în JavaScript, în cele ce urmează, vor fi propuse două situații.

Problema

1. Să presupunem că dorim să schimbăm între ele două obiecte în funcție de una din proprietățile lor. Codul de mai jos ilustrează soluția clasică.

var data = {
	version: '0.01',
	isTest: true,
	first: {
		id: 'a11',
		weight: 1.1,
		size: 2.0
	},
	second: {
		id: 'b10',
		weight: 0.5,
		size: 1.4
	}
};
// încercăm o interschimbare generică, nu va funcționa
function swap1(o1, o2, sortBy) {
	var o = null;
	if (o2[sortBy] < o1[sortBy]) {
		o = o1;
		o1 = o2;
		o2 = o;
	}
}
swap1(data.first, data.second, 'weight');
alert('first: ' + data.first.id + ', second: ' + data.second.id);

// aceasta va funcționa, dare dependentă de structura datelor
function swap2(parent, sortBy) {
	var o = null;
	if (parent.second[sortBy] < parent.first[sortBy]) {
		o = parent.first;
		parent.first = parent.second;
		parent.second = o;
	}
}
swap2(data, 'weight');
alert('first: ' + data.first.id + ', second: ' + data.second.id);

Dar putem observa că avem nevoie să modificăm declarația inițială a funcției swap(), iar codul din interiorul său este dependent de structura obiectului părinte.
2. O listă de articole este recepționată de la partea de server, trebuie să fie procesată pentru afișare în funcție de anumite câmpuri și adăugat un alt câmp calculat. Funcția qr() ce face procesarea necesită un cod ca cel de mai jos.

// structura inițială
var result1 = {
	meta: {
		weightField: 'w',
		sizeField: 's',
		qrField: 'q'
	},
	items: [
		{id: 'c20', w: 2.1, s: 0.15, q: false},
		{id: 'c21', w: 1.5, s: 1.05, q: false},
		{id: 'c22', w: 1.8, s: 2.20, q: false}
	]
};
function qr1(item, weightField, sizeField, qrField) {
	if (item[sizeField] < 0.5) {
		item[qrField] = 100 * item[weightField];
	}
	else {
		item[qrField] = 25 * item[sizeField] + 75 * item[weightField];
	}
	item[weightField] = 100 * item[weightField] / item[qrField];
	item[sizeField] = 100 * item[sizeField] / item[qrField];
}

// structura modificată
var result2 = {
	meta: {
		weightField: 'we',
		sizeField: 'sz',
		qrField: 'qr',
		qmField: 'qm'
	},
	items: [
		{id: 'c20', dataIn: {we: 2.1, sz: 0.15}, dataOut: {qr: false, qm: false}},
		{id: 'c21', dataIn: {we: 1.5, sz: 1.05}, dataOut: {qr: false, qm: false}},
		{id: 'c22', dataIn: {we: 1.8, sz: 2.20}, dataOut: {qr: false, qm: false}}
	]
};
function qr2(itemIn, weightField, sizeField, itemOut, qrField) {
	if (itemIn[sizeField] < 0.5) {
		itemOut[qrField] = 100 * itemOut[weightField];
	}
	else {
		itemOut[qrField] = 25 * itemIn[sizeField] + 75 * itemIn[weightField];
	}
	itemIn[weightField] = 100 * itemIn[weightField] / itemOut[qrField];
	itemIn[sizeField] = 100 * itemIn[sizeField] / itemOut[qrField];
}

Dar, dacă numele sau structura datelor de pe partea de server se schimbă, trebuie modificat codul din interiorul funcției qr() deși algoritmul de calcul nu s-a schimbat deloc.
Aceste exemple arată că mare parte din codul JavaScript este dependent de structura datelor procesate și necesită mentenanță continuă la schimbarea sursei de date. Ei … dar nu pentru mult timp …

Introducere în referințe în JavaScript

O referință în JavaScript este o pereche formată dintr-un obiect JavaScript existent și un șir de caractere/număr ce este folosit drept cheie pentru acel obiect.
Obiectul este denumit obiect proprietar/referit, iar șirul de caractere este denumit articolul/cheia referinței. Cheia poate fi absentă în obiectul referit la momentul referinței.
Cea mai simplă implementare în JavaScript este un hash (obiect) cu două proprietăți ce stochează proprietarul și articolul, ca în codul următor.

var data = {
	group: 'widgets',
	control: 'Panel',
	style: {
		fonts: ['Helvetica', 'Gerorgia', 'Traffic'],
		borders: {inner: '1px', outer: '2px'},
		visible: true
	}
};
// creare referințe
var p1 = {ref: data.style, key: 'visible'};
var p2 = {ref: data.style.fonts, key: 2};
var p3 = {ref: data.style.borders, key: 'inner'};
// atribuirea unei valori
p1.ref[p1.key] = false;
p2.ref[p2.key] = 'Verdana';
// aplicarea operatorului delete
delete p3.ref[p3.key];
// copierea unei referințe
var p4 = {ref: p3.ref, key: p3.key};
// schimbarea cheii
p4.key = 'shadow';
p4.ref[p4.key] = '4px';
/* the resulting object will be:
data = {
	group: 'widgets',
	control: 'Panel',
	style: {
		fonts: ['Helvetica', 'Georgia', 'Verdana'],
		borders: {outer: '2px', shadow: '4px'},
		visible: false
	}
};
------------------------------ */

După cum se poate vedea, putem implementa toate operațiile de bază cu acest concept de referință. Dar ce avantaj ar putea fi aceasta la problema codului JavaScript dependent de date?

Soluția – generici

Numim generic, un fragment de cod sau un algoritm care nu se modifică atunci când datele pasate/procesate își schimbă structura (denumirea câmpurilor, apartenența câmpurilor etc.).
Folosind referințe, putem construi funcții generice pentru cele două probleme exemplu menționate anterior. Dacă structura datelor se schimbă, trebuie modificat doar apelul efectiv al genericului, iar nu și codul său intrinsec.
1.

function swap(p1, p2, sortBy) {
	var o = null;
	var o1 = p1.ref[p1.key];
	var o2 = p2.ref[p2.key];
	if (o2[sortBy] < o1[sortBy]) {
		o = o1;
		p1.ref[p1.key] = o2;
		p2.ref[p2.key] = o;
	}
}
// apelul
swap({ref: data, key: 'first'}, {ref: data, key: 'second'}, 'weight');
alert('first: ' + data.first.id + ', second: ' + data.second.id);

2.

function qr(pWeight, pSize, pQr) {
	if (pSize.ref[pSize.key] < 0.5) {
		pQr.ref[pQr.key] = 100 * pWeight.ref[pWeight.key];
	}
	else {
		pQr.ref[pQr.key] = 25 * pSize.ref[pSize.key] + 75 * pWeight.ref[pWeight.key];
	}
	pWeight.ref[pWeight.key] = 100 * pWeight.ref[pWeight.key] / pQr.ref[pQr.key];
	pSize.ref[pSize.key] = 100 * pSize.ref[pSize.key] / pQr.ref[pQr.key];
}
// fie: var item = data.items[i]; // în interiorul unei bucle
// apelul în primul caz
qr({ref: item, key: 'w'}, {ref: item, key: 's'}, {ref: item, key: 'q'});
/// apelul în cazul al doilea
qr({ref: item.dataIn, key: 'we'}, {ref: item.dataIn, key: 'sz'}, {ref: item.dataOut, key: 'qr'});

O clasă referință în JavaScript

Modulul de Limbaj UI JavaScript conține o implementare a unei clase pentru lucrul cu referințe. Utilizând clasa JUL.Ref, codul pentru operațiile de bază poate fi rescris după cum urmează.

var p1 = new JUL.Ref(data.style, 'visible');
var p2 =  new JUL.Ref(data.style.fonts, 2);
var p3 =  new JUL.Ref(data.style.borders, 'inner');
// atribuirea unei valori
p1.val(false);
p2.val('Verdana');
// aplicarea operatorului delete
p3.del();
// copierea unei referințe
var p4 = new JUL.Ref(p3);
// schimbarea cheii
p4.key('shadow');
p4.val('4px');

Concluzie

Conceptul de referințe în JavaScript umple golul în care pointer-i sau referințe la variabile sunt necesare în celelalte limbaje de programare. El oferă de asemenea un mod de a implementa generici în JavaScript, ceea ce conduce la posibila existență a unei biblioteci standard de algoritmi pentru JavaScript (ca STL în C++).
La o examinare mai atentă, ca limbaj de programare, lui JavaScript nu îi lipsește nimic din ceea ce au celelalte limbaje moderne și este un pariu câștigător pentru platforma cloud (web, mobil, net-pc) a aplicațiilor.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Copyright © 2012 - ZB Constructorul Zonal
Frontier Theme