Cannot reproduce MDN's example («Using an object in an array-like fashion»).
let obj = {
length: 0,
addEl: function (element) {
[].push.call(this, element);
};
};
// Node REPL still expect me to do something, so there's an error. Why?
Could you, guys, explain what's wrong here? Also it seems that I don't get the point with the mechanics here:
// from the example:
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
What if we call the function with some different agrument, not {}, will it work? And if it won't, then why we should use {} exactly? What is the this context here: addEl method or the object itself? If the second, why not addEl function: it's not an array function, so it should have its own this (and, I guess, I'd use something like objThis = this; property).
One more related question is here.
The code in your post has some typos:
let obj = {
length: 0,
addEl: function (element) {
[].push.call(this, element);
};
^ syntax error
};
// Node REPL still expect me to do something, so there's an error. Why?
As you suspected in your comment in the code,
there is a syntax error, which I marked for you.
Remove that semicolon.
And then, when trying the example you wrote obj.addElem,
but in the above object literal you have addEl.
The example should work just fine, if you simply copy-paste it.
var obj = {
length: 0,
addElem: function addElem(elem) {
// obj.length is automatically incremented
// every time an element is added.
[].push.call(this, elem);
}
};
// Let's add some empty objects just to illustrate.
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
What if we call the function with some different argument, not {}, will it work?
Sure it will. Why wouldn't it? An array in JavaScript can contain values of different types.
It doesn't need to be homogeneous,
so yes, you can insert other things than {}.
What is the this context here: addEl method or the object itself?
It's the object on which the method is called. So it's obj.
This is how method invocation works.
When you call obj.something(), the this inside something will be the obj.
If you still have some doubts about this example, feel free to drop a comment.
Since an object is not an array, but can behave like an array you need to borrow push from the Array object.
But in this case this refers to the array object created with the shorthand []. So we need to change this into the scope for obj using call.
Because there is a length property defined, push will update this value.
An empty object is passed as an element {}, but any other will do:
let obj = {
length: 0,
addEl: function(element) {
Array.prototype.push.call(this, element); //also borrowing push from the array.prototype prevents an extra array to be made in memory every time we call upon this function.
} //« fixed the typo here
};
obj.addEl({});
obj.addEl(1);
obj.addEl('a');
obj.addEl(true);
console.log(obj);
var array = {
length: 0,
push: function(obj){
this[this.length] = obj;
this.length++;
}
}
array.push(23);
You can try this, this solves your problrm i guess.
Related
I'm trying to add a custom method on the prototype of the Array object:
Array.prototype.demo = function(){
this.forEach(i=>console.log(i))
}
But, I'm receiving the error below when I'm calling the method like this:
[1,2,3].demo()
// Error: TypeError: Cannot read property 'demo' of undefined
However, it runs successfully when I change it to:
const arr = [1,2,3];
arr.demo()
// Output: 1, 2, 3
PS. This is in nodejs
To reproduce the error in the browser, copy/paste the full block at once and click enter.
UPDATE: It sounds like we need to add a semicolon to make it work:
Array.prototype.demo = function(){
this.forEach(i=>console.log(i))
}; <=== added semicolon here to work #jfriend00
[1,2,3].demo();
However, now this next code works WITHOUT semicolon!!
String.prototype.demo = function(){
this.split('').forEach(c=>console.log(c))
}
'hello'.demo();
Quick Fix - Add Semi-colon
Add a semi-colon at the end of your function definition:
Array.prototype.demo = function(){
this.forEach(i=>console.log(i))
}; // <======
[1,2,3].demo();
And, it will work.
What's Happening?
The problem is that the [1,2,3] is being combined with the previous function (whitespace between them collapsed). In that circumstance, the [1,2,3] becomes just [3] and tries to read the [3] property from the function object. If you put the semi-colon at the end of the function definition, then that signals the end of the function definition statement and the [1,2,3] can then be interpreted as a static array definition.
It's all about context. In some circumstances in Javascript, [x] is a property access. In other circumstances, it's a static array definition. Without the semi-colon, it was getting interpreted as the property access instead of the array definition.
Remember that functions are objects in Javascript so they can have properties and can respond to [x] as a property access on them.
So, without the semi-colon at the end of the function you essentially have this:
Array.prototype.demo = function() {...}[3].demo();
Because the whitespace is collapsed between the end of your function and the [1,2,3]. That means the JS interpreter is expecting the [] to be a property name so it evaluates the statement inside the [] and in that context [1,2,3] turns into [3] (the 1,2,3 is evaluated which takes the value of the last comma separated statement which is 3).
More Detailed Explanation
Think of it like this:
// defines function
let f = function() {};
// attempts to read a property from that function object
let o = f [1,2,3]; // this is the same as let o = f[3]
// tries to call `.demo()` on the value read from that property
// which was undefined so this throws
o.demo();
Functions Are Objects
As a demonstration of how functions are objects, see this example that actually works!
// defines function
let f = function() {};
f[3] = {demo: function() { console.log("demo!!!");}}
// attempts to read a property from that function object
let o = f[1,2,3]; // this is the same as let o = f[3]
// tries to call `.demo()` on the value read from that property
// which was undefined so this throws
o.demo();
Here, we actually put a property on the [3] property of the function so when f[1,2,3] reads that property, it actually gets an object with a .demo() method on it so when we then call it, it all works. I'm not suggesting one would ever code this way, but I am trying to illustrate how f[1,2,3] is just reading the [3] property from the function object.
Good Reason Not to Leave out the Semi-colons
These odd cases are a good reason not to leave out semi-colons, even though you usually (but not always) get away with it.
The reason is that functions are objects, so if we don't add a semicolon, JavaScript will try to access a property on the function object, after it evaluates the comma operator like this:
function() { ... }[1,2,3].demo();
function() { ... }[3].demo();
undefined.demo();
Is there a way to allow "unlimited" vars for a function in JavaScript?
Example:
load(var1, var2, var3, var4, var5, etc...)
load(var1)
Sure, just use the arguments object.
function foo() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
In (most) recent browsers, you can accept variable number of arguments with this syntax:
function my_log(...args) {
// args is an Array
console.log(args);
// You can pass this array as parameters to another function
console.log(...args);
}
Here's a small example:
function foo(x, ...args) {
console.log(x, args, ...args, arguments);
}
foo('a', 'b', 'c', z='d')
=>
a
Array(3) [ "b", "c", "d" ]
b c d
Arguments
0: "a"
1: "b"
2: "c"
3: "d"
length: 4
Documentation and more examples here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
Another option is to pass in your arguments in a context object.
function load(context)
{
// do whatever with context.name, context.address, etc
}
and use it like this
load({name:'Ken',address:'secret',unused:true})
This has the advantage that you can add as many named arguments as you want, and the function can use them (or not) as it sees fit.
I agree with Ken's answer as being the most dynamic and I like to take it a step further. If it's a function that you call multiple times with different arguments - I use Ken's design but then add default values:
function load(context) {
var defaults = {
parameter1: defaultValue1,
parameter2: defaultValue2,
...
};
var context = extend(defaults, context);
// do stuff
}
This way, if you have many parameters but don't necessarily need to set them with each call to the function, you can simply specify the non-defaults. For the extend method, you can use jQuery's extend method ($.extend()), craft your own or use the following:
function extend() {
for (var i = 1; i < arguments.length; i++)
for (var key in arguments[i])
if (arguments[i].hasOwnProperty(key))
arguments[0][key] = arguments[i][key];
return arguments[0];
}
This will merge the context object with the defaults and fill in any undefined values in your object with the defaults.
It is preferable to use rest parameter syntax as Ramast pointed out.
function (a, b, ...args) {}
I just want to add some nice property of the ...args argument
It is an array, and not an object like arguments. This allows you to apply functions like map or sort directly.
It does not include all parameters but only the one passed from it on. E.g. function (a, b, ...args) in this case args contains
argument 3 to arguments.length
Yes, just like this :
function load()
{
var var0 = arguments[0];
var var1 = arguments[1];
}
load(1,2);
As mentioned already, you can use the arguments object to retrieve a variable number of function parameters.
If you want to call another function with the same arguments, use apply. You can even add or remove arguments by converting arguments to an array. For example, this function inserts some text before logging to console:
log() {
let args = Array.prototype.slice.call(arguments);
args = ['MyObjectName', this.id_].concat(args);
console.log.apply(console, args);
}
Although I generally agree that the named arguments approach is useful and flexible (unless you care about the order, in which case arguments is easiest), I do have concerns about the cost of the mbeasley approach (using defaults and extends). This is an extreme amount of cost to take for pulling default values. First, the defaults are defined inside the function, so they are repopulated on every call. Second, you can easily read out the named values and set the defaults at the same time using ||. There is no need to create and merge yet another new object to get this information.
function load(context) {
var parameter1 = context.parameter1 || defaultValue1,
parameter2 = context.parameter2 || defaultValue2;
// do stuff
}
This is roughly the same amount of code (maybe slightly more), but should be a fraction of the runtime cost.
While #roufamatic did show use of the arguments keyword and #Ken showed a great example of an object for usage I feel neither truly addressed what is going on in this instance and may confuse future readers or instill a bad practice as not explicitly stating a function/method is intended to take a variable amount of arguments/parameters.
function varyArg () {
return arguments[0] + arguments[1];
}
When another developer is looking through your code is it very easy to assume this function does not take parameters. Especially if that developer is not privy to the arguments keyword. Because of this it is a good idea to follow a style guideline and be consistent. I will be using Google's for all examples.
Let's explicitly state the same function has variable parameters:
function varyArg (var_args) {
return arguments[0] + arguments[1];
}
Object parameter VS var_args
There may be times when an object is needed as it is the only approved and considered best practice method of an data map. Associative arrays are frowned upon and discouraged.
SIDENOTE: The arguments keyword actually returns back an object using numbers as the key. The prototypal inheritance is also the object family. See end of answer for proper array usage in JS
In this case we can explicitly state this also. Note: this naming convention is not provided by Google but is an example of explicit declaration of a param's type. This is important if you are looking to create a more strict typed pattern in your code.
function varyArg (args_obj) {
return args_obj.name+" "+args_obj.weight;
}
varyArg({name: "Brian", weight: 150});
Which one to choose?
This depends on your function's and program's needs. If for instance you are simply looking to return a value base on an iterative process across all arguments passed then most certainly stick with the arguments keyword. If you need definition to your arguments and mapping of the data then the object method is the way to go. Let's look at two examples and then we're done!
Arguments usage
function sumOfAll (var_args) {
return arguments.reduce(function(a, b) {
return a + b;
}, 0);
}
sumOfAll(1,2,3); // returns 6
Object usage
function myObjArgs(args_obj) {
// MAKE SURE ARGUMENT IS AN OBJECT OR ELSE RETURN
if (typeof args_obj !== "object") {
return "Arguments passed must be in object form!";
}
return "Hello "+args_obj.name+" I see you're "+args_obj.age+" years old.";
}
myObjArgs({name: "Brian", age: 31}); // returns 'Hello Brian I see you're 31 years old
Accessing an array instead of an object ("...args" The rest parameter)
As mentioned up top of the answer the arguments keyword actually returns an object. Because of this any method you want to use for an array will have to be called. An example of this:
Array.prototype.map.call(arguments, function (val, idx, arr) {});
To avoid this use the rest parameter:
function varyArgArr (...var_args) {
return var_args.sort();
}
varyArgArr(5,1,3); // returns 1, 3, 5
Use the arguments object when inside the function to have access to all arguments passed in.
Be aware that passing an Object with named properties as Ken suggested adds the cost of allocating and releasing the temporary object to every call. Passing normal arguments by value or reference will generally be the most efficient. For many applications though the performance is not critical but for some it can be.
Use array and then you can use how many parameters you need. For example, calculate the average of the number elements of an array:
function fncAverage(sample) {
var lenghtSample = sample.length;
var elementsSum = 0;
for (var i = 0; i < lenghtSample; i++) {
elementsSum = Number(elementsSum) + Number(sample[i]);
}
average = elementsSum / lenghtSample
return (average);
}
console.log(fncAverage([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // results 5.5
let mySample = [10, 20, 30, 40];
console.log(fncAverage(mySample)); // results 25
//try your own arrays of numbers
In Safari with no add-ons (and actually most other browsers), console.log will show the object at the last state of execution, not at the state when console.log was called.
I have to clone the object just to output it via console.log to get the state of the object at that line.
Example:
var test = {a: true}
console.log(test); // {a: false}
test.a = false;
console.log(test); // {a: false}
I think you're looking for console.dir().
console.log() doesn't do what you want because it prints a reference to the object, and by the time you pop it open, it's changed. console.dir prints a directory of the properties in the object at the time you call it.
The JSON idea below is a good one; you could even go on to parse the JSON string and get a browsable object like what .dir() would give you:
console.log(JSON.parse(JSON.stringify(obj)));
What I usually do if I want to see it's state at the time it was logged is I just convert it to a JSON string.
console.log(JSON.stringify(a));
Vanilla JS:
#evan's answer seems best here. Just (ab)use JSON.parse/stringify to effectively make a copy of the object.
console.log(JSON.parse(JSON.stringify(test)));
JQuery specific solution:
You can create a snapshot of an object at a certain point in time with jQuery.extend
console.log($.extend({}, test));
What is actually happening here is jQuery is creating a new object with the test object's content, and logging that (so it will not change).
AngularJS (1) specific solution:
Angular provides a copy function that can be used to the same effect: angular.copy
console.log(angular.copy(test));
Vanilla JS wrapper function:
Here is an function which wraps console.log but will make a copy of any objects before logging them out.
I wrote this in response to a few similar but less robust functions in the answers. It supports multiple arguments, and will not try to copy things if they are not regular objects.
function consoleLogWithObjectCopy () {
var args = [].slice.call(arguments);
var argsWithObjectCopies = args.map(copyIfRegularObject)
return console.log.apply(console, argsWithObjectCopies)
}
function copyIfRegularObject (o) {
const isRegularObject = typeof o === 'object' && !(o instanceof RegExp)
return isRegularObject ? copyObject(o) : o
}
function copyObject (o) {
return JSON.parse(JSON.stringify(o))
}
example usage: consoleLogWithObjectCopy('obj', {foo: 'bar'}, 1, /abc/, {a: 1})
That > Object in the console, isn't only showing the current state. It actually is deferring reading the object and it's properties until you expand it.
For example,
var test = {a: true}
console.log(test);
setTimeout(function () {
test.a = false;
console.log(test);
}, 4000);
Then expand the first call, it will be correct, if you do it before the second console.log returns
using Xeon06's hint, you may parse his JSON in an object, and here is the log function I now use to dump my objects :
function odump(o){
console.log($.parseJSON(JSON.stringify(o)));
}
There is an option to use a debugger library.
https://debugjs.net/
Just include the script into your web page and put log statements.
<script src="debug.js"></script>
Logging
var test = {a: true}
log(test); // {a: true}
test.a = false;
log(test); // {a: false}
I defined an utility:
function MyLog(text) {
console.log(JSON.stringify(text));
}
and when I want to log on console I simply do:
MyLog("hello console!");
It works very well!
You might want to log the object in a human readable way:
console.log(JSON.stringify(myObject, null, 2));
This indents the object with 2 spaces at each level.
How can I pretty-print JSON using JavaScript?
There's a new option as of late 2022:
Deep copy the object with the new DOM structuredClone method:
console.log(structuredClone(obj))
which uses the same cloning algorithm that's used to transfer messages between web workers.
This should be faster and work with more types of objects than the JSON.parse(JSON.stringify(obj)) technique.
See https://developer.mozilla.org/en-US/docs/Web/API/structuredClone for details.
I may be shot for suggesting this, but this can be taken one step further. We can directly extend the console object itself to make it more clear.
console.logObject = function(o) {
(JSON.stringify(o));
}
I don't know if this will cause some type of library collision/nuclear meltdown/rip in the spacetime continuum. But it works beautifully in my qUnit tests. :)
Simply refresh the page after you open the console or open the console before you submit the request to the targeted page....
Just print whole object on console.
console.log(object);
I'm sure this has definitively been answered before, and I've tried to search for it.. maybe my search terms are wrong...
Basically I have an object myObject, and I have a set of defined properties and methods for it. What I want to do is be able to handle calls/references to properties and methods that I have not defined.
For example, let's say I have this:
var myObject = {
someProperty : 'foobar',
someFunction : function () { /* Do stuff */ }
}
Currently, if someone tries to make a call to myObject.someOtherFunction(), JavaScript yells and screams about it. What I want to do is setup a way to automatically handle that. So for example, instead of JavaScript throwing an error, my object just returns false. Is this possible?
Another way to look at it is this:
var myObject = {
someFunction : function () { /* Do stuff */ }
magicBucket : function () { /* Do stuff */ }
}
If I call myObject.someFunction(), well that is defined and does something. What I want to happen is if I were to for instance call myObject.someOtherFunction(), instead of JavaScript throwing an error, it would call myObject.magicBucket().
The reason is that I have a client that uses a third-party library on their site. They want to discontinue using it, but completely removing it is going to take a lot of time and effort. So as a short-term solution, they wanted to know if I could make a dummy file that basically does nothing. Well, this library uses several objects that has lots of methods. I could go through everything and make dummy objects, but I thought maybe there might be some easy "catch-all" method to do this.
Some have mentioned checking if the method exists first, wrapping it in a condition or try..catch, etc. Well, the point of this is that at this time I can't touch the actual calls to the methods. And since the overall goal is to eventually remove the coding altogether, it's not even applicable.
There's a special property called __noSuchMethod__ which does precisely what you just described. However it's a non-standard property. It only works in Firefox. Here's how you use it:
var o = {
__noSuchMethod__: function (name, args) {
alert(name); // prints the name of the method
alert(args); // prints the array of arguments
}
};
o.abc(1, 2, 3); // OUTPUT: abc 1,2,3
The future however are proxy objects. The following is a short tutorial on proxies: Proxy Tutorial
No, you can't have arbitrary getters in JavaScript. You can test if a function exists before calling it to prevent the error though:
if (myObject.someOtherFunction)
myObject.someOtherFunction();
Or, better, if you don't know that it's necessarily a function:
if (typeof myObject.someOtherFunction == 'function')
myObject.someOtherFunction();
An update on Proxies, here is an infinite array:
$ var squares = new Proxy([], {get:(target,key) => key*key});
$ squares[2]
4
$ Array.isArray(squares)
true
Unfortunately:
$ squares.length
NaN // Want Infinity.
And a dummy object:
$ x = new Proxy({}, {get:(target,key) => console.error("The computer says no", key)})
$ x.laugh
The computer says no laugh
This latter would help the OP make a dummy object, although it would take a bit of black magic to divine what sort of dummy to return.
An up-to date reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy
You could create a wrapper function like so:
function callFunction(fn, args) {
var funct = this[fn];
return (typeof funct == "function")
? funct.apply(this, args)
: false;
}
And call with:
callFunction("blah", [1, 2, 3]);
>>> false
An example:
this.foo = function(a, b) {
console.log(a);
return b;
}
callFunction("foo", [1, 2, 3]);
>>> 1
>>> 2 # return value
With Proxy the following works:
function createPseudoObject() {
return new Proxy(new Function(), { get() { return createPseudoObject() } })
}
Eg. createPseudoObject().foo.bar.toString() does not throw error and returns undefined. By using new Function() as a base for the dummy object I can reference not just any property, but call any function on the dummy object.
Maybe there are some edge cases that are not covered by this, but something like this should work.
I achieve a forEach function:
function forEach(arr, fn) {
for (var i = 0; i < arr.length; i++) {
fn.call({}, arr[i], i);
}
}
what I confused is about fn.call({}, arr[i], i);
the first parameter is pass empty just like above {} is better
or pass this in: fn.call(this, arr[i], i); is better?
Or it doesn't matter
It matters quite a bit. The first parameter to .call() is the value to be used for this inside the called function. Thus, it doesn't make sense to talk about what value is "better"; the right value to pass is the one you need in order for the called function to operate properly.
For example, if you want to call a function on the Array prototype, then the value of this inside that function has to be something that "feels like" an array (a "length" property and numerically-indexed properties). Thus:
var sneaky = {
"0": "hello",
"1": "world",
"length": 2
};
alert( Array.prototype.join.call(sneaky, " - ") ); // "hello - world"
That works because that function expects this to refer to the array to be joined.
There are as many other examples as there are functions that have expectations about this. In your sample code, passing {} gives the called function a this reference to that newly-created empty object. Will that work? I don't know, because that function could expect anything. There's no way to find out, either, except by looking at the code (or trusting documentation). If all you know is that it's some random arbitrary function, then {} is a reasonable guess, though undefined might be better, to force early failure.
Personally I would go with passing this. By passing {} you are limiting the flexibility of your function. You will never be able to bind another object to this function the way it is currently written. This won't work:
forEach.call(newContext, array, fn)
Neither will this:
forEach(array, fn.bind(newContext));
By binding {} inside your forEach you are adding unexpected behavior.