nntrn
4/22/2019 - 6:02 PM

[objects (getters and setters) vs class] #DOMSnippets

[objects (getters and setters) vs class] #DOMSnippets

/*
To illustrate how they work, let’s look at a person object which has two properties: firstName and lastName, and one computed value fullName.
*/

var obj = {
  firstName: "Maks",
  lastName: "Nemisj"
}

/*
The computed value fullName would return a concatenation of both firstName and lastName.
*/


Object.defineProperty(person, 'fullName', {
  get: function () {
    return this.firstName + ' ' + this.lastName;
  }
});

/*
To get the computed value of fullName there is no more need for awful braces like person.fullName(), but a simple var fullName = person.fullName can be used.
*/


/*
The same applies to the setters, you could set a value by using the function:
*/

Object.defineProperty(person, 'fullName', {
  set: function (value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
});

/*
Usage is just as simple with getter: person.fullName = 'Boris Gorbachev' This will call the function defined above and will split Boris Gorbachev into firstName and lastName.
*/


/*
JavaScript would give an error:
*/

person.getFulName();
       
/*
This error is triggered at the right place and at the right moment
Accessing non existing functions of an object will trigger an error – that’s good.
*/


/*
Now let’s see what happens when setter is used with the wrong name?
*/

person.fulName = 'Boris Gorbachev';

/*
Nothing
Objects are extensible and can have dynamically assigned keys and values, so no error will be thrown in runtime.
*/


/*
Time to improve the person object and make a real class of it ( as real as class can be in JavaScript)
Person defines the interface for getting and setting fullName.
*/

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
 
  getFullName() {
    return this.firstName + ' ' + this.lastName;
  }
 
  setFullName(value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

/*
Classes define a strict interface description, but getters and setters make it less strict than it should be
We’re already used to the swollen errors when typos occur in keys when working with object literals and with JSON
At least I was hoping that Classes would be more strict and provide better feedback to the developers in that sense.
*/


/*
Though this situation is not any different when defining getters and setters on a class
It will not stop others from making typos without any feedback.
*/

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
 
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
 
  set fullName(value) {
    var names = value.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

/*
Executing with a typo, won’t give any error:
*/


var person = new Person('Maks', 'Nemisj');
console.log(person.fulName);

/*
The same non-strict, non-verbose, non-traceable behavior leading to possible errors.
*/


/*
First, to find out what kind of getters and setters are available on the class Person, it’s
possible to use getOwnPropertyNames and getOwnPropertyDescriptor:
*/

var names = Object.getOwnPropertyNames(Person.prototype);
 
var getters = names.filter((name) => {
  var result =  Object.getOwnPropertyDescriptor(Person.prototype, name);
  return !!result.get;
});
 
var setters = names.filter((name) => {
  var result =  Object.getOwnPropertyDescriptor(Person.prototype, name);
  return !!result.set;
});

/*
After that, create a Proxy object, which will be tested against these lists:
*/


var handler = {
  get(target, name) {
    if (getters.indexOf(name) != -1) {
      return target[name];
    }
    throw new Error('Getter "' + name + '" not found in "Person"');
  },
 
  set(target, name) {
    if (setters.indexOf(name) != -1) {
      return target[name];
    }
    throw new Error('Setter "' + name + '" not found in "Person"');
  }
};
 
person = new Proxy(person, handler);

/*
Now, whenever you will try to access person.fulName, message Error: Getter "fulName" not found in "Person" will be shown.
*/


/*
Note that one can put creation of the Proxy inside the constructor like in:
*/

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    if (new.target && DEBUG)
        return createProxyPropertyCheckker(this);
  }}

/*
This uses the fact that a class constructor, when called, behaves as ordinary constructor invocation
That is, if it returns an object, that object is returned by the new operator
Here new.target from ES6 is used to check that function is called as a constructor and as an ordinary function, for example, by a subclass constructor.
*/

//shorter version (taken from "You don't know js" book, Kyle Simpson)
let pobj = new Proxy( {}, {
  get() {
    throw "No such property/method!";
  },
  set() {
    throw "No such property/method!";
  }
});
 
let obj = {
  a: 1,
  foo() {
    console.log( "a:", this.a );
  }
};
 
// setup `obj` to fall back to `pobj`
Object.setPrototypeOf( obj, pobj );
 
obj.a = 3;
obj.foo();          // a: 3
 
obj.b = 4;          // Error: No such property/method!
obj.bar();          // Error: No such property/method!
function prettyComments(str) {
	str = str.split(". ").join("\n")

	return "\n/*\n" + str + "\n*/\n";
}

// for vanilla JS:
// snippets = [].slice.call(document.querySelectorAll('.syntaxhighlighter'));

// using google console API:
snippets = $$(".syntaxhighlighter");

// removes line numbers for each code block
removeme = $$("td.number");
removeme.forEach(e => {
	e.parentNode.removeChild(e);
});

// get unique sibling
var sibling = new Set();

snippets
	.map(e => {
		var previousElement = "",
			nextElement = "";

		if (e.previousElementSibling) {
			previousElement = sibling.has(e.previousElementSibling) ?
				"" :
				prettyComments(e.previousElementSibling.innerText);
		}

		if (e.nextElementSibling) {
			nextElement = sibling.has(e.nextElementSibling) ?
				"" :
				prettyComments(e.nextElementSibling.innerText);
		}

		sibling.add(e.previousElementSibling);
		sibling.add(e.nextElementSibling);

		return previousElement + "\n" + e.innerText + "\n" + nextElement;
	})
	.join("\n")
	.replace(/\n*(\.{3,}|\^.*\nTypeError:.*)\n/gi, ""); // remove syntax errors