So I'm working on a sort of JavaScript framework, just some utility things for myself to use in future projects, and I want to make a data binding system.
The first method I used was objects, and the code would just loop through the specified html element and look for occurences of {{key}} in the markup and then look for that key in the object and replace it that way in the HTML.
For example, if you had <div>{{name}} is a cool guy</div> in the HTML and had {name:"joseph"} in the JS then the final product would be displayed on screen as 'joseph is a cool guy'.
However, I decided later to change my method and instead the framework would except a function. So instead of {name:"joseph"} you would give it function(){ var name = "joseph" }.
This obviously looks better and gives a lot better functionality.
I changed the processing function so instead of looking for the key/value pair to replace the {{key}}, it just uses eval on the variable to gets its value.
My problem lies here: How do I run my search/replace code INSIDE the scope of the function the user passes.
If the user defines variables within that function, their values will not be available anywhere else due to scope issues.
I've tried using Function.toString() to actually modify the source code of the function, but nothing's working and it's all very complicated.
(The issues are not due to the actual solution, I think that Function.toString() might work, but due to my implementation. I keep getting errors)
So... What is the best way to run arbitrary code in the scope of another function?
Critera:
Obviously, I can't modify the function because the user is passing it in. (you can't just tell me to add the search/replace code to the bottom of the function)
The variables must stay in the local scope of the function. (no cheating by using window.name = "joseph" or anything)
I am also aware of how terrible eval is so any suggestions as to get it to work are greatly appreciated. Thanks!
Code:
function process(html) {
var vars = html.match( /({{)[^{}]*(}})/g )
// vars = ['{{variable}}', '{{anotherVariable}}']
var names = vars.map( function(x){ return x.replace("{{", "").replace("}}", "") } )
// names = ['variable', 'anotherVariable]
obj = {}
for (var i = 0; i < names.length; i++) {
obj[names[i]] = eval(names[i])
}
for (var p in obj) {
html = html.replace(new RegExp('{{'+p+'}}','g'), obj[p]);
}
return html
}
You should go back to your first method with the object, it's much better. You can still pass a function, but the function should return an object:
function () {
return { name: 'joseph' }
}
Related
I want to overwrite a function, which is a method of a prototype of an Object. This method uses this internally. How can I overwrite that function but still keep this defined as the same object / value, as it has been in the original function?
The reasons I want to keep it are the following:
The original code supports extensions. I'd rather prefer writing an extension and add that to the code as intended by the authors of the original code, than changing the original code and having to build or install a modified version.
I need always perform some action before the original function is called. So I actually want to keep the original functionality, but want to add something before it. I think this would be "decorating" the function, like a decorator in Python.
The scope in code where I overwrite the function is not the same as where it is defined originally.
Here is some example code:
//original functions
var original_execute_cells = Jupyter.Notebook.prototype.execute_cells;
// decorating functions
function decorated_execute_cells(cell_indices) {
console.log('EXTENSION: running decorated execute_cells function');
return original_execute_cells(cell_indices);
}
// overwrite original functions
Jupyter.Notebook.prototype.execute_cells = decorated_execute_cells;
However, when I do that, I get a TypeError telling me that this is not defined, in the first line of the original function, which uses this.
The original source code of the function I am trying to overwrite can be found on github:
Notebook.prototype.execute_cells = function (indices) {
if (indices.length === 0) {
return;
}
var cell;
for (var i = 0; i < indices.length; i++) {
cell = this.get_cell(indices[i]);
cell.execute();
}
this.select(indices[indices.length - 1]);
this.command_mode();
this.set_dirty(true);
};
I now did it with bind. The return statement becomes:
return original_execute_cells.bind(Jupyter.notebook)(cell_indices);
Where Jupyter.notebook is in my specific case the object this related to, when the Notebook object was build.
However, what #siebetman mentioned in the comment could also work and might even be more flexible in some situations. Also the link siebetman provided seems very useful in order to understand Javascript's this better.
Why not just bind this?
var original_execute_cells = Jupyter.Notebook.prototype.execute_cells;
Jupyter.Notebook.prototype.execute_cells = function(cell_indices) {
console.log('EXTENSION: running decorated execute_cells function');
return original_execute_cells.bind(this)(cell_indices);
};
I'm looking for a way to inject properties from "this" into local function scope, so i dont need write 'this.' when referencing to this properties.
Exact details are displayed in this code http://jsfiddle.net/wwVhu/3/, look at this part
...
//it's how it works
doStuff: function(param) { $('#output').html(this.value + param) }
//it's how i want it work - without referencing to this
//doStuff: function(param) { $('#output').html(value + param) }
I know it could be achieved by wrapping function code in "with(this) { ... }", but what are other options?
Writing "with(this)" in the beginning of every method or using js aop is what i'm trying to avoid.
Why would you want to do this? It's namespaced because it makes sence. this references to the element the listener is listening on. And it contains a lot more information than just the value.
If you want the value in another variable, you can do:
var value = this.value
There are basically four options:
You keep it the way it is. Context and local scope are different objects, combining them is bad practice and leads to collisions.
You add the value property as the 2nd parameter to the doStuff function.
You nickname this with a shorter identifier. I often find myself use $t.
You use with(this) $('#output').html(value + param);. This is a bad coding practice, as explained in 1). Your code becomes broken the second there is a param property in this.
I'm looking for the standard way to calculate a variable once, then access it within the scope of every execution of a function, without relying on global variables.
This seems like a standard use of prototype properties (variables) - but every example I can find on JS prototypes is based on prototype methods (functions). The only thing I can find about setting properties / variables in a prototype is a question from someone who also couldn't find any information about these, asking if it's good or bad practice (tldr: it's fine, but remember it's rarely worth sacrificing readability for tiny performance gains).
I've got a way to set and get prototype properties that works, but feels clunky as it depends on a reference to the function (essentially var prop = thisfunctionname.prototype.someprop). Since I found it through trial and error, I'd like to ask if there's a cleaner, more standard way to get these prototype properties from within the function, without going back up to the scope around the function and getting the function from there?
Here's a simplified light-hearted example: an imaginary jQuery plugin that adds a number to another number then returns it in a sentence with the user's name. We want to ask the user their name only once, then store that name for re-use within scope:
(function($) {
var sum = function( num1,num2 ) {
var result = num1 + num2;
// This works, but seems clunky since it depends on the variable `sum`
// from the scope around this function - is there a better way?
var name = sum.prototype.name;
$(this).text( num1+' plus '+num2+' is '+result+', '+name+'.');
return $(this);
};
var name = prompt('Please enter your name','');
// Is there a better way to set this default variable to be accessible
// in all calls to this function?
sum.prototype.name = name;
$.fn.basicArithmetic = sum;
})(jQuery);
// end of plugin. Example usage...
$('<p/>').basicArithmetic(1,5).appendTo('body');
$('<p/>').basicArithmetic(2,2).appendTo('body');
$('<p/>').basicArithmetic(25,30).appendTo('body');
$('<p/>').basicArithmetic(92.3,15.17).appendTo('body');
Live jsbin example. More realistic real-life use cases would be when the calculation for the property is expensive in memory usage, or destructive (e.g. requires changing the DOM during calculation).
Two different answers, really:
The usual way is to use a variable within a scoping function (you already have one handy in your example); no prototypes involved at all.
(function($) {
var name;
name = prompt('Please enter your name','');
function sum( num1,num2 ) {
var result = num1 + num2;
$(this).text( num1+' plus '+num2+' is '+result+', '+name+'.');
return $(this);
}
$.fn.basicArithmetic = sum;
})(jQuery);
Updated JSBin Example | Source
(Side note: I also changed your anonymous function expression into a named function declaration, but it doesn't really matter in this case.)
The usual way in a jQuery plug-in is to store the data on the element(s) the plug-in is being applied to. That doesn't work for the example you gave, which requires that the data be global to the plug-in, but normally (not always, just normally) plug-ins keep only instance-specific information, which you'd normally store on elements (probably via the data function).
Not sure if this is considered best practice or if you should even do this but I have a small block of Javascript and I want to know if you can declare a variable, display that variable and then reassign it and display it again? Syntactically this seems correct but I would assume that this is not best practice and should be avoided?
Note: I did not write this block I just want to know if it's ok or if I should change it and use 2 variables code below:
var u1 = 'something';
if (u1.indexOf('Accept') > 0)
{
var URL = 'some URL';
document.writeln(URL);
URL = 'another URL';
document.writeln(URL);
}
Thanks in advance.
EDIT:Thanks for the answers, thought it was a bit daft. :/
Yes you can
You can change variable's value as many times as you need to. Variables are quite often reused so we save memory resources. Not in the way you've used them (because that's an example that would be better off providing constant strings directly when calling functions) but think of an everyday example where we don't even think of multiple variable value assignments. A for loop:
for (var i = 0; i < 100; i++)
{
...
}
In this loop variable i gets assigned a new value 101 times. This is a rather obvious example, where we don't think of this at all, but other than that, we could have a set of loops and reuse the same variable more explicitly and assign it a value lots of times like:
var counter = 0;
for(var item = GetLinkedListFirstItem(); item != null; item = item.Next)
{
counter++;
}
// other code...
counter = 0;
while (counter < 10 || someOtherCondition)
{
// do something else
}
This may be a much better example of explicit variable reusability where its value gets changed lots of times and for different purposes.
Variable naming
Variable reuse is sometimes unwanted/undesired. And that's when we have a meaningful variable name like isUserLoggedIn. It's hard to reuse such variable for other purposes because it would make code unmaintainable.
Variables that are usually reused may hence be iterators (ie. i) or generally named variables without too much meaning. Or variables with more universal name (ie. finished) which can be reused in different contexts that can be associated with such variable name.
Asynchronous code
There are certain situations where you may have problems even though looking at code may seem perfectly fine. And that's when you use async functions which is frequently the case when using Ajax calls or time-deferred calls (ie. setTimeout). Consider the following code:
var loaded = false;
$.ajax({
url: "...",
type: "POST",
success: function(){
loaded = true;
}
});
if (loaded === true)
{
// do something important
}
// ok loaded not used any more, so we can reuse it
// we can easily change its type from number to string or anything else
loaded = "Peter loaded his gun";
This code has a bug, because important code won't be executed. Ever! This is quite a frequent misconception by unsavvy developers not understanding asynchronism.
Hint: When code issues an Ajax call it doesn't wait for a response but rather continues execution and executes if statement. Even though Ajax call would respond in 0time ticks, success function wouldn't execute until this currently running code wouldn't finish execution. That's how Javascript works. Queued code execution. In the end when Ajax async code would execute it would eventually overwrite the string that was stored in the variable.
Why not? Of course, it's normal to change variable value as much times as you want. That's actually reason why it's called "variable", not "constant" :)
I'd say it's perfectly fine to do so.
However, keep in mind that it can cause problems with asynchronous code. Take the following example for instance, where async accepts a callback that runs some time later:
var a = 123;
async(function() {
alert(a); // alerts 456, because `a` was set to 456
// *before* this callback was run.
// Because there is only one `a`, that variable
// has been overridden
});
a = 456;
async(function() {
alert(a); // alerts 456
});
Yes it is possible, and in this case there is no point in creating a new variable. However, if you have a lot of code reassigning a variable later could definitely be confusing especially if at first it's an object then later it is a string.
Variables can be reassigned in JavaScript. Whether they should or not is a question of style and context.
I normally prefer to re-use variables rather than create new ones
I am working on making all of our JS code pass through jslint, sometimes with a lot of tweaking with the options to get legacy code pass for now on with the intention to fix it properly later.
There is one thing that jslint complains about that I do not have a workround for. That is when using constructs like this, we get the error 'Don't make functions within a loop.'
for (prop in newObject) {
// Check if we're overwriting an existing function
if (typeof newObject[prop] === "function" && typeof _super[prop] === "function" &&
fnTest.test(newObject[prop])) {
prototype[prop] = (function(name, func) {
return function() {
var result, old_super;
old_super = this._super;
this._super = _super[name];
result = func.apply(this, arguments);
this._super = old_super;
return result;
};
})(prop, newObject[prop]);
}
}
This loop is part of a JS implementation of classical inheritance where classes that extend existing classes retain the super property of the extended class when invoking a member of the extended class.
Just to clarify, the implementation above is inspired by this blog post by John Resig.
But we also have other instances of functions created within a loop.
The only workaround so far is to exclude these JS files from jslint, but we would like to use jslint for code validation and syntax checking as part of our continuous integration and build workflow.
Is there a better way to implement functionality like this or is there a way to tweak code like this through jslint?
Douglas Crockford has a new idiomatic way of achieving the above - his old technique was to use an inner function to bind the variables, but the new technique uses a function maker. See slide 74 in the slides to his "Function the Ultimate" talk. [This slideshare no longer exists]
For the lazy, here is the code:
function make_handler(div_id) {
return function () {
alert(div_id);
};
}
for (i ...) {
div_id = divs[i].id;
divs[i].onclick = make_handler(div_id);
}
(I just stumbled on this questions many months after it was posted...)
If you make a function in a loop, an instance of a function is created for each iteration of the loop. Unless the function that is being made is in fact different for each iteration, then use the method of putting the function generator outside the loop -- doing so isn't just Crockery, it lets others who read your code know that this was your intent.
If the function is actually the same function being assigned to different values in an iteration (or objects produced in an iteration), then instead you need to assign the function to a named variable, and use that singular instance of the function in assignment within the loop:
handler = function (div_id) {
return function() { alert(div_id); }
}
for (i ...) {
div_id = divs[i].id;
divs[i].onclick = handler(div_id);
}
Greater commentary/discussion about this was made by others smarter than me when I posed a similar question here on Stack Overflow:
JSlint error 'Don't make functions within a loop.' leads to question about Javascript itself
As for JSLint:
Yes, it is dogmatic and idiomatic. That said, it is usually "right" -- I discover that many many people who vocalize negatively about JSLint actually don't understand (the subtleties of) Javascript, which are many and obtuse.
Literally, get around the problem by doing the following:
Create a .jshintrc file
Add the following line to your .jshintrc file
{"loopfunc" : true, // tolerate functions being defined in loops }
JSLint is only a guide, you don't always have to adhere to the rules. The thing is, you're not creating functions in a loop in the sense that it's referring to. You only create your classes once in your application, not over and over again.
If you are using JQuery, you might want to do something like this in a loop:
for (var i = 0; i < 100; i++) {
$("#button").click(function() {
alert(i);
});
}
To satisfy JSLint, one way to work around this is (in JQuery 1.4.3+) to use the additional handler data argument to .click():
function new_function(e) {
var data = e.data; // from handler
alert(data); // do whatever
}
for (var i = 0; i < 100; i++) {
$("#button").click(i, new_function);
}
Just move your:
(function (name, func) {...})()
block out of the loop and assign it to a variable, like:
var makeFn = function(name, func){...};
Then in the loop have:
prototype[prop] = makeFn(...)