I'm kinda new with js + ES6 + class; I have problem with creating function inside constructor.
#1. I need to add new Hobby, a person allowed to have plenty hobbies ;
#2. I don't know how to show all the data of students;
another questions are in the comments ,in case if you want to answer it too, if not i'm also fine.
so here's my code :
class Student {
constructor(name,hobbies){
this.name = name;
var hobby = new Set(); //do I set here or inside the function ??
//since the function addHobbies also need, then it's fine to be global right ?
this.hobbies = (hobbies) => { //function ES6 like this right ??
this.hobbies = hobby.add(hobbies);
return this.hobbies; //can I return hobby instead of this.hobbies ??
};
}
setName(newName){
this.name = newName;
}
addHobbies(newHobbies){
this.Hobbies = hobby.add(newHobbies); //it should be like this to add >> to set ?
}
getName(){
return this.name;
}
getHobbies(){
return this.hobbies;
}
}
and how to return all the data ?
let andy = new Student("andy","dance");
let vince = new Student("vince","codding");
so it will show all students-attribute by getCode() ?
do I set here or inside the function ??
That depends on what you need. Do you want each Student instead to have one set of hobbies, or do you want to create a new set every time the function is called?
this.hobbies = (hobbies) => { //function ES6 like this right ??
this.hobbies = hobby.add(hobbies);
That doesn't work at all. You're creating the property with a function value, but when the method is called you're overwriting the property with the return value of the add method.
To make it work, I'd recommend making the .hobbies set an instance property instead of a local variable.
class Student {
constructor(name, ...hobbies) {
this.name = name;
this.hobbies = new Set();
this.addHobbies(...hobbies);
}
getName() {
return this.name;
}
setName(newName) {
this.name = newName;
}
getHobbies() {
return this.hobbies;
}
addHobbies(...newHobbies) {
for (const newHobby of newHobbies)
this.hobbies.add(newHobby);
}
}
Alternatively, if you insist on using a local constructor variable, it would look like this:
class Student {
constructor(name, ...hobbies) {
this.name = name;
this.hobbies = new Set(...hobbies);
this.getHobbies = () => {
return this.hobbies;
};
this.addHobbies = (...newHobbies) => {
for (const newHobby of newHobbies)
this.hobbies.add(newHobby);
};
}
… // further methods (for name etc)
}
Try this:
class Student {
constructor(name, hobbies) {
this.name = name;
// Allow passing both an array of hobbies and a single hobby
this.hobbies = Array.isArray(hobbies) ? new Set(hobbies) : new Set([hobbies]);
}
setName(newName) {
this.name = newName;
}
addHobbies(newHobbies) {
if (Array.isArray(newHobbies)) {
newHobbies.forEach((hobby) => this.hobbies.add(hobby));
} else {
this.hobbies.add(newHobbies);
}
}
getName() {
return this.name;
}
getHobbies() {
return this.hobbies;
}
}
let andy = new Student("andy","dancing");
let vince = new Student("vince",["codding", "running"]);
andy.addHobbies("slipping");
vince.addHobbies(["running", "eating"]);
You are in the correct direction. I have rewritten your class to do what I think is more similar to what you are trying to achieve.
Play with the code at: https://jsbin.com/vejumo/edit?js,console
And here's the rewritten class:
class Student {
constructor(name, hobbies = []){
this.name = name;
// new Set() is used to work with objects. It does not work with well with strings
// Let's use an array to store the hobbies.
// if a hobby or an hobbies array is passed, store it, otherwise set an empty array.
this.hobbies = this.parseHobbies(hobbies);
}
// This function will normalize the hobbies to an Array
parseHobbies(hobbies) {
if (typeof hobbies === "string") {
// hobbies is a string, means it's a single hobby and not an array
return [hobbies];
}
// Assuming the hobbies is a an Array
return hobbies;
}
setName(newName) {
this.name = newName;
}
// this function will allow you to add a single hobby to the array
addHobbies(hobbies = []) {
// Same logic like in constract, this can accept a string or an array
// We use Array.concat and push to append to array
this.hobbies = this.hobbies.concat(this.parseHobbies(hobbies));
}
getName() {
return this.name;
}
getHobbies() {
return this.hobbies
}
// This will return all student attributes.
getAttributes() {
// Return a copy of all the attributes instead of returning references
return Object.assign({}, this);
}
}
let george = new Student("George", "Sports");
george.addHobbies(["Singing", "Fishing"]);
george.addHobbies("Dancing");
console.log(george.getAttributes());
Related
According to second answer from here, I'm trying to create Singleton pattern in JS for storing data and invoking its prototypes from the other instances.
A main problem is Singleton doesn't store the data after receives the first instance.
[{…}]
0: {firstName: "John", lastName: "Grand"}
This is how I've done:
export default class Terminal {
static cache(output) {
// Singleton
if (!Terminal.instance) {
Terminal.instance = new Terminal(output);
}
return Terminal.instance;
}
constructor(output) {
// Create an array
this.logs = [];
// Switch to an object
const data = Object.assign({}, output);
// Add the object to the array
this.logs.push(data);
// Inspect
console.log(this.logs);
}
}
// instance 1
import Terminal from './terminal.js';
class Person {
constructor(firstName, lastName, input) {
this.firstName = firstName;
this.lastName = lastName;
// Assign the Singleton
this.input = input || Terminal.cache(this);
}
}
let player1 = new Person('John', 'Grand');
// instance 2
import Terminal from './terminal.js';
class Grocery {
constructor(name, stock, input) {
this.name = name;
this.stock = stock;
// Assign the Singleton
this.input = input || Terminal.cache(this);
}
}
let shop1 = new Grocery('Apple', 12);
I want to let the new keyword inside of the class when I define the Singleton pattern.
Any tips to accomplish my problem?
Thanks.
The cache() method needs to push output onto the logs array when the object already exists.
static cache(output) {
// Singleton
if (!Terminal.instance) {
Terminal.instance = new Terminal(output);
} else {
Terminal.instance.logs.push(Object.assign({}, output));
}
return Terminal.instance;
}
I haven't be able to use the push() method to use the new Food constructor to create new items in my pantry array. I need a function to call that will add objects to the array, and I would like for the id value to be calculated based on the current length of the array.
I have tried to create functions like
groceries = function ()
{
pantry.push(new Food)};
var pantry =
[
egg = new Food (1, "Egg", ...)
...
...
];
function Food (id, name,...)
{
this.id = id;
this.name = name;
...
};
this.inquiry = function() {
return '\n' + this.name +
'\n' +
};
I can go to the browser and pantry.push(chicken = new Food(... arguments...); but I'm coming up short on knowledge in how to construct a function that would make the process cleaner. I'm not sure if this is just how it is. Maybe I can't do this with a constructor in mind. I'm very new.
I expect there is a way to type in a function that will use my constructor to format new variables and then push them to the end of my pre made variable pantry. I'd like to be able to use this function in the browser.
so far i'm getting errors like function not defined or syntax error.
I think you mean class instead of function. new can only use for class. Like below.
class Food {
constructor(id, name) {
this.id = id;
this.name = name;
}
inquiry() {
return '\n' + this.name + '\n';
}
}
var pantry = [ new Food(1, "Egg") ];
console.log(pantry);
console.log(pantry[0].inquiry());
Here is yet another solution you could use
class FoodStore{
constructor() {
this.data = [];
}
add(name){
this.data.push({id: this.data.length,name});
}
toList(){
return this.data;
}
}
var store = new FoodStore();
store.add("test");
//inquiry
console.log(store.toList()[0].name);
// All items
console.log(store.toList());
I'm looking for the best way to update in object instance, for the example in this case myParentObjects's name property. The only way I understand to achieve this would be to pass reference of the parent object to the child object instance as a parameter into either new myChildObj(this,name) in the constructor, or a method of the myChildObj instance like myChildObj.updateParentProperty(name).
I can't imagine a child object nested 4-5 levels down, and having to update properties on it's parents passing (parent1,parent2,parent3,etc) it's params, that would be a managing nightmare! there must be a better way to update parent properties!
function myParentObj(){
this.name = 'jordan'
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(this,name);
childObj.updateParentProperty();
})
}
}
function myChildObj(parentObj,name){
this.parent = parentObj;
this.name = name;
this.updateParentProperty=()=>{
this.parent.name = this.name;
};
}
function init(){
var parentObj = new myParentObj();
parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);
Question: What is the best method to update parent object parameters?
What puzzles me is this:
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(this,name);
childObj.updateParentProperty();
})
}
because as soon as all three children are created, your parent will have a name of cassie (since that's the last child calling updateParentProperty). That and your tags is why I'm inclined to claim that you're looking for classical inheritance, like so:
Classical inheritance
function myParentObj (name) {
this.name = name;
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(name);
console.log(childObj);
});
}
}
function myChildObj (name) {
myParentObj.call(this, name);
// Other stuff, only available for myChildObj
}
function init(){
var parentObj = new myParentObj();
parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);
var parent = new myParentObj();
console.log(parent);
Which would mean the child inherits properties from its parent, but would of course not update them for the parent when their own name changes.
Some simple observer pattern
Okay, your example just has a single property which change should be noticed. I think an observer pattern like in the example below is a bit too much, but for larger applications it's quite useful.. the observer pattern is not distinct to JavaScript only (see enter link description here). Basically, you have an object which changes shall be monitored (observable) and one or many objects which want to perform actions depending on these changes (observers). The implementation can be quite simple (like my demonstration) or really complex. The basic idea is for the observable to maintain a list of its observers. The example uses an own, simple implementation. There are also libraries available, we're using knockout, which offers a MVVM structure for your page and can even update the DOM depending on changes.
function myParentObj(){
this.name = 'jordan'
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(name);
childObj.subscribe('name', newValue => this.name = newValue);
})
}
}
function myChildObj(name){
this.subscribers = {};
Object.defineProperty(this, 'name', {
set: newValue => {
this._name = newValue;
this.notify('name');
},
get: () => this._name
});
this.subscribe = function (prop, callback) {
if (!this.subscribers.hasOwnProperty(prop)) {
this.subscribers[prop] = [];
}
this.subscribers[prop].push(callback);
};
this.notify = function (prop) {
if (this.subscribers[prop]) {
this.subscribers[prop].forEach(callback => callback(this[prop]));
}
};
}
function init(){
var parentObj = new myParentObj();
parentObj.init();
}
document.addEventListener('DOMContentLoaded',init);
var parent = new myParentObj();
var child = new myChildObj('bob');
child.subscribe('name', newName => parent.name = newName);
console.log(parent.name);
child.name = 'cassie';
console.log(parent.name);
Using Object.assign
You could also use Object.assign, which will copy all enumerable properties from one object to another. You should note, though, that it will copy all properties, even those you might not want to change.
function myParentObj(){
this.name = 'jordan'
this.names = ['jordan','danny','cassie'];
this.init=()=>{
this.names.forEach((name)=>{
var childObj = new myChildObj(this,name);
childObj.updateParentProperty();
})
}
}
function myChildObj(name){
this.name = name;
}
var parent = new myParentObj();
var child = new myChildObj('cassie');
Object.assign(parent, child);
console.log(child);
Classical inheritance
I see you mixed in some ES6, so you might be looking for an answer using the ES6 standards of object-oriented inheritance like so:
class Parent {
constructor(name = 'jordan') {
this.name = name
this.names = ['jordan', 'danny', 'cassie']
}
init() {
return this.names.map((name) => {
return new Child(name)
})
}
}
class Child extends Parent {
constructor(name) {
super(name)
// other stuff for Child
}
}
let parent = new Parent()
console.log(parent)
let children = parent.init()
children.forEach((child) => console.log(child))
In ES5, inheritance was usually implemented like this instead, though this is not exactly what ES6 class does, since ES6 does things like making instance methods non-enumerable, etc.
function Parent(name) {
name = arguments.length > 0 ? name : 'jordan'
this.name = name
this.names = ['jordan', 'danny', 'cassie']
}
// member methods
Parent.prototype = {
init: function init() {
return this.names.map(function (name) {
return new Child(name)
})
}
}
function Child(name) {
// basically, super(name)
Parent.call(this, name)
// other stuff for Child
}
// extending Parent
Child.prototype = Object.create(Parent.prototype)
Parent.prototype.constructor = Parent
var parent = new Parent()
console.log(parent)
var children = parent.init()
children.forEach(function (child) { console.log(child) })
Below is almost exactly the ES5 equivalent for the ES6 code in the first example:
function Parent() {
var name = arguments.length > 0 && name !== undefined ? name : 'jordan'
this.name = name
this.names = ['jordan', 'danny', 'cassie']
}
// member methods
Object.defineProperties(Parent.prototype, {
init: {
configurable: true,
value: function init() {
return this.names.map(function (name) {
return new Child(name)
})
},
writable: true
}
})
function Child(name) {
// basically, super(name)
Parent.call(this, name)
// other stuff for Child
}
// extending Parent
Child.prototype = Object.create(Parent.prototype, {
constructor: {
configurable: true,
value: Child,
writable: true
}
})
var parent = new Parent()
console.log(parent)
var children = parent.init()
children.forEach(function (child) { console.log(child) })
EventEmitter
After looking at the comments below the other answer, it appears you are most interested in observer-type patterns. EventEmitter, defined natively in Node.js, is the most widely used implementation of the observer pattern. Below is a polyfill demo of it for client-side JavaScript:
class Parent extends EventEmitter {
constructor(name = 'jordan') {
super()
this.name = name
}
}
class Child {
constructor(parent, name = parent.name) {
this.parent = parent
this._name = name
}
set name(name) {
console.log('setting child name')
this._name = name
this.parent.emit('name', name)
}
get name() {
return this._name
}
}
let parent = new Parent()
parent.on('name', function (name) {
console.log('setting parent name')
this.name = name
}.bind(parent))
let child = new Child(parent)
console.log('parent', parent.name)
console.log('child', child.name)
child.name = 'danny'
console.log('parent', parent.name)
console.log('child', child.name)
<script src="https://cdn.rawgit.com/mudge/5830382/raw/a4bc230f5bce01ea9a34b0d42247256531b97945/eventemitter.js"></script>
Proxy
Another neat API that JavaScript has is called Proxy, which allows metaprogramming, that is, redefining how JavaScript works in isolated cases:
var parent = {}
var child = {}
var proxy = new Proxy(child, {
set: (target, property, value) => {
// target === child
target['child-' + property] = typeof value + ' ' + value
parent['parent-' + property] = typeof value + ' ' + value
return value
}
})
proxy.name = 'jordan'
proxy.age = 54
console.log(parent)
console.log(child)
I have a basic library I created as follows:
(function () {
function Store() {
var store = [];
if (!(this instanceof Store)) {
return new Store();
}
this.add = function (name, price) {
store.push(new StoreItem(name, price));
return this;
};
}
function StoreItem(name, price) {
if (!(this instanceof StoreItem)) {
return new StoreItem();
}
this.Name = name || 'Default item';
this.Price = price || 0.0;
}
Store.prototype.toString = function () {
// build a formatted string here
};
StoreItem.prototype.toString = function () {
return this.Name + ' $' + this.Price;
};
window.shop = window.shop || {
Store: function () {
return new Store();
}
};
}());
The large majority of this works well! However, I do not want to expose my store array defined in the Store constructor as I do not want it modified in anyway outside this library's control.
But, on the contrary, I would like to override the Store's toString method to make use of the StoreItems in the store array so I can return a formatted string of all the StoreItems using its toString method.
E.g. if store was exposed, the toString method would look something like:
Store.prototype.toString = function () {
return this.store.join('\r\n');
};
// shop.Store().add('Bread', 2).add('Milk', 1.5).toString() result:
// Bread $2
// Milk $1.5
Is there anyway I can achieve this without exposing my store array publicly?
You can give each Store it's own .toString method, being privileged to access the local store variable through closure - just like you did with .add:
function Store() {
if (!(this instanceof Store)) {
return new Store();
}
var store = [];
this.add = function(name, price) {
store.push(new StoreItem(name, price));
return this;
};
this.toString = function () {
return store.join('\n');
};
}
Alternatively, you will have to define some kind of accessor method anyway, or your store would be useless if you only can add to it but never read it. Something will need to display the store items, will need to iterate them, will need to test them for availability… If you create a generic accessor, maybe in form of some iterator (an each method with a callback?), then a .prototype.toString method could use that as well.
It's possible to keep the prototype toString method whilst protecting store, by adding a public method that returns the stringified store, and calling it from Store.prototype.toString.
Below I added this.getStoreString to the constructor, and called it from the original toString.
(function () {
function Store() {
var store = [];
if (!(this instanceof Store)) {
return new Store();
}
this.add = function (name, price) {
store.push(new StoreItem(name, price));
return this;
};
// new method
this.getStoreString = function(){
return store.join('\r\n');
};
}
function StoreItem(name, price) {
if (!(this instanceof StoreItem)) {
return new StoreItem();
}
this.Name = name || 'Default item';
this.Price = price || 0.0;
}
Store.prototype.toString = function () {
// call the new method
return this.getStoreString();
};
StoreItem.prototype.toString = function () {
return this.Name + ' $' + this.Price;
};
window.shop = window.shop || {
Store: function () {
return new Store();
}
};
}());
Fiddle
#Bergi's answer seems simpler though, but this shows another option.
I am building a javascript library where I have to create a log of classes and most of them have a lot of properties which have to make public for the user.
For example:
function Person(name,age){
}
Now I want to create the getter and setter for properties (name and age).
Nornall, I have to add these methods to Person.prototype:
Person.prototype.getName=function(){}
Person.prototype.setName=function(x){
//check if x is typeof String
}
Person.prototype.getAge=function(){}
Person.prototype.setAge=function(x){
//check if x is typeof Number
}
This will result in two many lines of repeated codes.
So I wonder if I can call a method like this:
makeThesePropertiesPublic(Person,{
name:"string",
age:"number"
});
Then I can call this:
var p=new Person("xx",1);
p.getName();
p.getAge();
.......
Is there a out-of-box method to implement this?
First of all you can't define the getter and setter functions on the prototype because they need to be able to access name and age which are only accessible inside the constructor. Hence you would need to define the getter and setter functions inside the constructor.
I would do this:
function Person(name, age) {
var private = {
name: name,
age: age
};
Object.defineProperties(this, {
name: getAccessor(private, "name", "String"),
age: getAccessor(private, "age", "Number")
});
}
function getAccessor(obj, key, type) {
return {
enumerable: true,
configurable: true,
get: function () {
return obj[key];
},
set: function (value) {
if (typeOf(value) === type)
obj[key] = value;
}
};
}
function typeOf(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
Now you can access create a Person and access their name and age properties as follows:
var person = new Person("Aadit M Shah", 20);
person.name = 0; // it won't set the name
person.age = "twenty"; // it won't set the age
alert(person.name);
alert(person.age);
See the demo: http://jsfiddle.net/aVM2J/
What about something like this?
function Person(n, a) {
var name = n;
var age = a;
var person = {};
person.getName = function () {
return name
}
person.getAge = function () {
return age;
}
return person;
}
var p = Person("jon",22);
console.log(p.getName());//jon