have been using a pretty much loving Crockford's constructor, but, having problems adding scoped functions to the object:
'use strict';
var Constructor = function( params ) {
let config = params,
data = params.datum,
action = function(a,b) { return config.actions[a](b); };
return Object.freeze({
action: action
});
};
var cns = Constructor({
datum: 123,
actions: {
getData: function(b) { return data; }
}
});
cns.action('getData',0);
get Uncaught ReferenceError: data is not defined.
how do I have a function as an argument to the constructor and have that function have the scope of object?
If you are following Crockford's private members in JavaScript post, then getData should be a "privileged" function (a function that has access to private members such as data). Therefore, this function should follow the "privileged" pattern given in his post (JSFiddle example).
var Constructor = function (params) {
var config = params;
var data = params.data;
// Privileged function pattern:
// By using a closure, this method has access to private members.
this.getData = function (b) {
return data;
};
};
// Note: Changed to `new` in order to instantiate the class
var cns = new Constructor({
data: 123
});
console.log(cns.getData(0));
the easiest way seems to be to manually pass object guts to the function, either with call or as an extra argument. since I'm dodging this, am using the extra argument, here self. self is not exposed to the world at large, only to the functions that need to see it.
'use strict';
var Constructor = function( params ) {
let config = params,
data = params.datum,
self = { data: data },
action = function(a,b) { return config.actions[a](b,self); };
return Object.freeze({
action: action
});
};
var cns = Constructor({
datum: 123,
actions: {
getData: function(b,s) { return s.data; }
}
});
cns.action('getData',0);
Related
I'm trying to make the following code works without any luck, and I can't see a clear solution on how to do it.
export default {
model: null,
set: function (data) {
this.model = data
},
account: {
update: function (data) {
this.model.account = data
}
}
}
My issue here is that account.update fails because this.model does not exists. I suspect that the sub object gets a new this, hence my issue, but I don't know how to fix it.
I tried the alternative here :
export default (function () {
let model = null
function set (data) {
this.model = data // I also tried without the `this.` but without any luck too
},
function updateAccount(data) {
this.model.account = data
}
return {
'model': model,
'set': set,
'account': {
'update': updateAccount
}
}
})()
But apparently the same rule applies.
Maybe it's worth noting that I'm using Babel to compile ES6 down to ES5 javascript.
It fails because this refers (in this case) to the window object. Reference the object itself like this:
let myModel = {
model: null,
set: function (data) {
myModel.model = data // reference myModel instead of this
},
account: {
update: function (data) {
myModel.model.account = data // reference myModel instead of this
}
}
}
I would take an approach similar to your alternative solution. There is however no need to wrap your code in an IIFE, ES2015 modules are self-contained; you don't need an IIFE for encapsulation.
let model = null,
set = (data) => {
model = data;
},
updateAccount = (data) => {
if (!model) {
throw('model not set');
}
model.account = data;
};
export default {
model,
set,
account: {
update: updateAccount
}
};
Since you are already using Babel, I also used arrow functions and the new shorthand properties to make the code a little shorter/readable.
My use case is the following: I want to create a factory which produces various kinds of data transfer objects (DTOs). They must be easily serializable and they must have a few additional methods.
My current implementation looks like this (simplified):
window.Dto = function(type, properties)
{
var
self = this,
values = {},
object = Object.create(self);
properties.forEach(function(prop){
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
this.getType = function()
{
return type;
};
this.doSomeMagic = function()
{
// ...
};
return object;
};
// creating a DTO of the Transport.Motorized.Car class
var carObject = new Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
(Note: I do not want to create an explicit class for each of these objects, because there are hundets of them, and they are exported from the server side. Also, what you see as properties parameter above, is actually a map of meta data with validation constraints etc.)
I did a quick performance check with a loop where 50,000 of such objects were created. performance.now() tells me that it took a bit more than 1s – which looks ok, but not too impressive.
My question is mainly: Is it ok that the factory creates an instance from its own prototype (if I understand correctly what that code does) and returns it? What side effects can it have? Is there a better way?
As far as I understand factory functions, their whole point is not needing to create new instances of the function itself. Instead, it just returns a newly created object.
So instead of using instance properties (via this) of the newly created instance (via the new operator), I would just create an object (let's call it factoryProto) and assign all the "instance" methods to that object instead.
Then, you can use factoryProto as the [[Prototype]] for your new object:
window.Dto = function(type, properties) {
var factoryProto = {
getType: function() {
return type;
},
doSomeMagic: function() {
// ...
}
},
values = {},
object = Object.create(factoryProto);
properties.forEach(function(prop) {
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
return object;
};
// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
If you want to fully profit from the prototype-chain, you could define the factoryProto outside of the factory function. To keep track of type, you could add it as a non-enumerable object property:
window.Dto = (function() {
var factoryProto = {
getType: function() {
return this.type;
},
doSomeMagic: function() {
// ...
}
};
return function(type, properties) {
var values = {},
object = Object.create(factoryProto);
properties.forEach(function(prop) {
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
Object.defineProperty(object, 'type', {
value: type,
enumerable: false
});
return object;
};
})();
// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
I am creating a factory for my angular app.
I am asking how to use a method (here setCurrentUser()) in the same factory I am declaring:
app.factory('User', function ($rootScope) {
var User = {
create: function (authUser, username) {
// ...
setCurrentUser(username); // <== ERROR
});
},
// ...
setCurrentUser: function (username) {
$rootScope.currentUser = User.findByUsername(username);
},
// ...
};
return User;
});
Using this.setCurrentUser(username) gives an undefined error, of course...
Please note I need setCurrentUser to be a method of User factory, since I have to use it from controllers (I can't define it to be a local function of User factory...).
What you could do instead is use functions as local vars and return the User object (your public API) like this:
app.factory('User', function ($rootScope) {
var setCurrentUser = function (username) {
$rootScope.currentUser = ...
};
var create = function(authUser, username) {
// ...
setCurrentUser(username); // <== NO ERROR
};
return {
create: create,
setCurrentUser: setCurrentUser
};
});
I'd try to use a closure, since by the time at which you call User.create() User is defined, so you can call setCurrentUser.
User.setCurrentUser(username);
Try to declare setCurrentUser function before create function declaration. If this won't help, you can use this trick:
var that = this;
and in User object call setCurrentUser as:
that.setCurrentUser
I have a question regarding definitions of factories in AngularJS. I am unsure if this is a Javascript doubt or AngularJS, but I believe it's Angular.
Say I have a factory definition like this:
angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000";
var service = {
// our factory definition
user: {},
setName: function(newName) {
service.user['name'] = newName;
},
setEmail: function(newEmail) {
service.user['email'] = newEmail;
},
save: function() {
return $http.post(backendUrl + '/users', {
user: service.user
});
}
};
return service;
});
How is it possible that the function setName is able to set service.user['email'], if service is actually defining setName itself?
This is basic javascript. Forget about the factory for a minute, only consider definition of service.
var service = {
//service will have an empty user js object
user: {},
//a key setName with the value as a function
//which sets a key value pair in user defined above
//service = { user: {name: 'Tony'} }
setName: function(newName) {
service.user['name'] = newName;
},
//a key setEmail with the value as a function
//which sets a key value pair in user defined above
//(consider setName has been called already)
//service = { user: {name: 'Tony', email: 'tony#stark.com'} }
setEmail: function(newEmail) {
service.user['email'] = newEmail;
},
//a key save with the value as a function
//which saves
save: function() {
//return something from a call mimicing $http.post
//return $http.post(backendUrl + '/users', { user: service.user });
}
}
Then, return or use service which has become an API by itself.
Check out dev tool console for log in this FIDDLE
setName function is going to be called by some other code like this,
User.setName("newname "); when this is called the service already defined and it's about setting new value to a property.
and you could use this keyword to make the code more meaningful,
setName: function(newName) {
this.user['name'] = newName;
}
I see one issue with your code, even though you have defined setName function you have the user variable is accessible to outside of the factory,now you can do like this as well,
User.user['name'] = newName;
I think what you need to do is make user a private variable,
angular.module('myApp.services')
.factory('User', function($http) { // injectables go here
var backendUrl = "http://localhost:3000";
var user={};
var service = {
// our factory definition
setName: function(newName) {
user['name'] = newName;
},
setEmail: function(newEmail) {
user['email'] = newEmail;
},
getUser:function(){
return user;
},
save: function() {
return $http.post(backendUrl + '/users', {
user: service.user
});
}
};
return service;
});
What you're describing is a behavior of JavaScript's closures.
Basically, a function has access to the variables outside of the function's scope. If those variables change, it's changed everywhere.
When the functions are created, service is still undefined
The functions create a closure around the service variable
service is then assigned to be the object
All the functions are now pointing to the service object
Here's a way to visualize this:
var service = null;
var getService = function() { return service; };
service = 5;
getService(); // returns 5
service = "service can change";
getService(); // returns "service can change";
I'm writing a javascript test for my UserRepository.
I want to stub the data in the success callback function of the $http object.
User Repository code:
function UserRepository($http) {
return {
getUsers: function () {
$http({ url: '/GetUsers' }).success(function (data) {
//populate users
});
return users;
}
};
}
My test code:
var httpStub = function() {
return new {
success: function(callback) {
var array = [];
array.push({ forename: 'john', surname: 'smith' });
callback(array);
}
};
};
var userRepository = new UserRepository(httpStub);
userRepository.getUsers();
The error i'm getting is "the object is not function" which i think is happening where my httpstub returns the object literal containing the success function, but I can't figure out how to fix it.
I've fixed the problem. The httpStub was doing
return new{
success : function .....
}
When there should have been no new keyword e.g:
return {
success : function .....
}