Here's the script:
function runScripts() {
if (arguments.length === 0) return;
chrome.tabs.executeScript(null, {
file: arguments[0]
}, function() {
arguments.shift();
runScripts.apply(null, arguments);
});
}
It doesn't work because arguments is not actually an array, it's just array-like. So how can I "shift" it or hack off the first element so that I can apply this function recursively?
var params = Array.prototype.slice.call(arguments);
params.shift();
You can check out this blog post which explains it in further detail.
I assume you want to reference the original arguments, instead of that from the callback you're passing to chrome.tabs.executeScript.
If so, you'll need to cache it first.
function runScripts() {
if (arguments.length === 0) return;
var args = [];
Array.prototype.push.apply( args, arguments );
chrome.tabs.executeScript(null, {
file: args.shift();
}, function() {
// using the modified Array based on the original arguments object
runScripts.apply(null, args);
});
}
[].shift.call(arguments) is also valid. I'm using this in production code and it works as expected.
With this approach, your function becomes a bit more succinct:
function executeScripts() {
if (arguments.length === 0) return;
chrome.tabs.executeScript(null, {
file: [].shift.call(arguments)
}, function() {
executeScripts.apply(null, arguments);
});
}
If you look on MDN, they state that shift() was implemented with this flexibility in mind.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/shift
You can transform arguments into a regular array like this:
var args = Array.prototype.slice.call(arguments);
Just wanted to point out a potential problem with [].shift.call(arguments).
This seems to have the perhaps unclear intent of shifting your arguments - even for your function's named parameters - even if used prior to the shift statement.
For example,
function testShift (param1, param2) {
[].shift.call(arguments);
if (param1=="ONE") alert("ONE");
}
If you make the following call, what might you expect to happen?
testShift("ONE", "TWO");
If you expected param1 to stay "ONE", your fix is to set a var to param1 before the shift occurs. It looks like javascript is not binding param1 until the line it is called on - not when the function is called... so modifications to arguments prior to a parameter being used can have unexpected effects.
Hopefully now, you'll be able to expect it.
In ES6 you can now use Array.from() MDN ref
e.g.
const args = Array.from(arguments);
const str = args.shift();
You'll need to convert it to an array and then shift. Or, alternatively, drop the first item when converting to an array. Array.prototype.slice.call(arguments, 1) would work for this.
In newer versions of JS, we can now write:
function f(first, ...rest) { ... }
Or
function f() {
const [first, ...rest] = arguments;
}
Which is a little nicer than "shifting" off the first arg. However, if we did want to, we could first convert arguments into a proper array via Array.from or [...arguments].
Here is an article explains this really well. I copied some key points below.
http://www.javascriptkit.com/javatutors/arrayprototypeslice.shtml
For array, remember you can call the slice function to get a sub array.
var abc = [1,2,3,4,5];
abc.slice(0); //[1,2,3,4,5]
abc.slice(1,3); //[2,3]
Since the argument object is only array like, not really an array. The call() / apply() function basically just "borrow" the slice function from Array and use it on the Argument object, and you can even pass parameters into the slice function just as acting on the array.
var myobject ={ // array-like collection
length: 4,
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three'
}
var myarray = Array.prototype.slice.call(myobject)
// returns myobject as a true array: ["zero", "one", "two", "three"]
var myarray = Array.prototype.slice.call(myobject, 1)
// returns ["one", "two", "three"]
The one remaining question is why we're calling slice() on the prototype object of Array instead of an array instance. The reason is because this is the most direct route to accessing the slice() method of Array when that's all we're interested in; we could have first created an array instance, but that's less efficient and arguably more abstruse:
var myarray = new Array().prototype.slice.call(myobject) // less efficient
You could convert the arguments to an actual array and then use that array in the rest of your logic in the function.
function runScripts()
{
var i=0, l=arguments.length, arr=[];
while(i<l)
{
arr.push(arguments[i++]);
}
...rest of your function code
Edit to add: i've had issues with prototype and call in older versions of IE, so it really depends on what support you'll need.
I went with this:
function executeScripts() {
if (arguments.length === 0) return;
var args = Array.prototype.slice.call(arguments);
chrome.tabs.executeScript(null, {
file: args.shift()
}, function() {
executeScripts.apply(null, args);
});
}
It's useful when writing Google Chrome Extensions. I wanted to use jQuery in my content script, but then you have to load it first. Turns out out by chaining calls to chrome.tabs.executeScript you can do this:
chrome.browserAction.onClicked.addListener(function(tab) {
executeScripts('jquery-1.4.4.min.js', 'content.js');
});
Related
I want to create a similar construction in my code.
var inList = findItem(list, data);
if(!inList) {
var item = inList.item;
}
function findItem(list, data) {
var item = list.find("[data-day='"+data.day+"']")
// more code.
// conditional return
return {item: item, valueOf:function(){return false}};
}
But it doesn't work because overwriting valueOf doesn't play nicely with a simple truthfull check (in the way that I want it to work).
and having code like if(inList == false){} looks less clean imo. Is there a way to make this work?
Boolean checks don't invoke valueOf - all objects are considered truthy. If you want to circumvent that, you'll have to invoke it yourself explicitly:
if (!inList.valueOf()) …
You should not depend on code that uses valueOf,
if you wanted to do something where you are returning an object,
just add another property instead.
var findResult = findItem(list, data);
if(!findResult.found) {
var item = findResult.item;
}
function findItem(list, data) {
var item = list.find("[data-day='"+data.day+"']");
// more code.
// conditional return
return {item: item, found: false};
}
Then again, I forgot what I was doing 5 years ago.
I get two different behaviors out of these, I was hoping someone could explain why.
function Test( id ) {
this.target = $(id);
this.children = [ $('<div/>').text("A"), $('<div/>').text("B") ];
}
// will add 0 1 A B
Test.prototype.addToTarget = function() {
this.children.forEach(this.target.append.bind(this.target));
};
// will add A B
Test.prototype.addToTargetEx = function() {
var target = this.target;
this.children.forEach(function(child){
target.append(child);
});
};
In the first version, you're binding the this value, but forEach passes 3 arguments to its callback, which are the item, the index and the original array.
In the second version, you're manually passing only the first argument given to the callback, and ignoring the last two.
So (for illustration of the issue) you can force your second version to behave like the first like this...
this.children.forEach(function(child, idx, arr){
target.append(child, idx, arr); // Passing all 3 args
});
Now it's clearer that .append() is receiving 3 values on each iteration.
There's not really any way around this using .bind(). If .append() were made to only recognize the first argument passed, then it would work.
One thing you could do would be to create your own custom .bindN method. Instead of being able to bind this and individual arguments, it could bind this and receive a "limiter" that will limit the number of arguments it's allowed to receive.
It could look like this:
Function.prototype.bindN = function(thisArg, n) {
var origFunc = this;
return function() {
return origFunc.apply(thisArg, Array.prototype.slice.call(arguments, 0, n));
}
};
Then use it like this:
this.children.forEach(this.target.append.bindN(this.target, 1));
This is a follow-up to this question (although this is self-contained) trying to `call` three methods but not working correctly with jQuery map.
I am trying to store a set of methods in an array but there is a set that might have arguments like below (the initial methods are in before_methods and the proposed methods are in lm_methods). I'm sure it's pretty self explanatory what I want but I'd like to be able to merge in the arguments into a reasonable call to f (specifically the arc.pLikedByTerm). I currently have the following:
// signature
pLikedByTerm:function(term, ne, sw, m){
....
}
// code before_methods just to show
this.before_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems];
this.lm_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems, arc.pLikedByTerm('surfing'),arc.pLikedByTerm('sailing')];
$.each(this.lm_methods, function(i,f){
f(ne,sw,m);
});
How would I do this or is this bad design? What would be the idiomatic way? My brain is fried.
thx in advance
Update 1
Playing around with answer below, it looks like this works which might the simplest things:
var fns=[logStuff("this is msg"), logMoreArgs("a term","a you msg")];
for (var i=0; i<fns.length; i++) {
fns[i];
}
Having an array of functions is common practice when used often. For example, consider this Callback class.
function Callback(){
this.callbacks = [];
}
Callback.prototype.run = function(cb) {
for (var i=0; i<this.callbacks.length; i++) {
this.callbacks[i]();
}
};
We can then add some callbacks.
function logStuff(msg) {
jsprint(msg || "No message");
}
obj = new Callback();
obj.callbacks.push(logStuff);
obj.callbacks.push(logStuff);
obj.run();
If we run this we see that it's only logging our default value. So if we want to bind some data, we can use the bind function.
Function.prototype.bind
thisArg
The value to be passed as the this parameter to the target
function when the bound function is called. The value is ignored if
the bound function is constructed using the new operator.
arg1, arg2, ...
Arguments to prepend to arguments provided to the bound function
when invoking the target function.
Our new code sets the first parameter to different strings, which we then see. You can bind any number of parameters.
obj = new Callback();
obj.callbacks.push(logStuff.bind(null, "My message"));
obj.callbacks.push(logStuff.bind(null, "My other message"));
obj.run();
end result
The way you are doing would work just ok. Just remove the arguments and parens:
Instead of:
this.lm_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems,
arc.pLikedByTerm('surfing'),arc.pLikedByTerm('sailing')];
Do:
this.lm_methods=[arc.pLocations,arc.pLikedLocations,arc.pLikedItems,
arc.pLikedByTerm,arc.pLikedByTerm];
Example:
function say(txt) {
console.log("say" + txt);
}
function shout(txt) {
console.log("shout" + txt);
}
function whisper(txt) {
console.log("whisper" + txt);
}
var funcArr = [say, shout, whisper];
$.each(funcArr, function(i, f) {
f("hello");
});
would print:
sayhello
shouthello
whisperhello
This may seem silly, but in this day and age, one should be able to expect JS to raise an event if contents of an array have changed.
A few questions been asked regarding getting notified when a variable changes (define getter or setter). And there seems to be a way to do that (at least for most browsers including IE6+)
My issue is that I'm trying to get notified if an item inside an array changes:
var ar = ["one", "two", "three"];
// setting the whole array will call the custom setter method
// (assuming you defined it)
ar = ["one", "three", "five"];
// however, this will only call the getter method and won't call the setter
// without defining custom setters for every item in the array.
ar[1] = "two";
Obviously, I'm trying to avoid forcing the coder to use old-school Java style .getVale() and .setValue() functions to access/modify data.
In short: no, you can't. You'll notice that Arrays don't provide any event dispatching mechanism, and their API doesn't include any callback type functionality.
In longer: as others have noted, it is possible to wrap the array… And it's also possible to poll the arrays contents:
function watchArray(arr, callback) {
var oldVal = "" + arr;
setInterval(function() {
var curVal = "" + arr;
if (curVal != oldVal) {
callback();
oldVal = curVal;
}
}, 100);
}
But this method has some obvious problems: it polls, it'll get slow to watch a bunch of arrays, etc.
I think timeout-based solutions are not the best.
If you can only use push and pop to modify your array, you can override push and pop methods of Array prototype (or only some object that you want to monitor):
var myWatchableArr = [];
myWatchableArr.setChangeCallback = function(callback){
this.changeCallback = callback;
}
myWatchableArr.push = function(e){
Array.prototype.push.call(this,e);
if(typeof this.changeCallback == "function")
this.changeCallback(this);
}
myWatchableArr.push(3);
myWatchableArr.setChangeCallback(function(arr){
console.log("the array has been changed", arr);
});
// now watching for changes
myWatchableArr.push(4);
If push and pop are not sufficient, you can add some setAt method to use like myWatchableArr.setAt(3, value) instead of myWatchableArr[3]=value.
Ok, based on #David Wolever's code and other comments, there actually is a solution:
Use the notes from John Dyer to implement the addProperty method. Place a setTimeout in the getter method to compare with original value a short time after the read takes place:
addProperty(myObject, 'vals',
function () {
var _oldVal = "" + this._val;
var _parent = this;
console.log('getter!');
setTimeout(function () {
var curVal = "" + _parent._val;
if (curVal != _oldVal)
console.log('array changed!');
}, 200);
return this._val;
},
function (value) {
console.log('setter!');
this._val = value;
});
myObject.vals = ["one", "two", "three"];
myObject.vals[1] = "five";
I want to compare each string in an Array with a given string. My current implementation is:
function startsWith(element) {
return element.indexOf(wordToCompare) === 0;
}
addressBook.filter(startsWith);
This simple function works, but only because right now wordToCompare is being set as a global variable, but of course I want to avoid this and pass it as a parameter. My problem is that I am not sure how to define startsWith() so it accepts one extra parameter, because I dont really understand how the default parameters it takes are passed. I've tried all the different ways I can think of and none of them work.
If you could also explain how the passed parameters to 'built in' callback functions (sorry, I dont know of a better term for these) work that would be great
Make startsWith accept the word to compare against and return a function which will then be used as filter/callback function:
function startsWith(wordToCompare) {
return function(element) {
return element.indexOf(wordToCompare) === 0;
}
}
addressBook.filter(startsWith(wordToCompare));
Another option would be to use Function.prototype.bind [MDN] (only available in browser supporting ECMAScript 5, follow a link for a shim for older browsers) and "fix" the first argument:
function startsWith(wordToCompare, element) {
return element.indexOf(wordToCompare) === 0;
}
addressBook.filter(startsWith.bind(this, wordToCompare));
I dont really understand how the default parameters it takes are passed
There is nothing special about it. At some point, filter just calls the callback and passes the current element of the array. So it's a function calling another function, in this case the callback you pass as argument.
Here is an example of a similar function:
function filter(array, callback) {
var result = [];
for(var i = 0, l = array.length; i < l; i++) {
if(callback(array[i])) { // here callback is called with the current element
result.push(array[i]);
}
}
return result;
}
The second parameter of filter will set this inside of the callback.
arr.filter(callback[, thisArg])
So you could do something like:
function startsWith(element) {
return element.indexOf(this) === 0;
}
addressBook.filter(startsWith, wordToCompare);
For those looking for an ES6 alternative using arrow functions, you can do the following.
let startsWith = wordToCompare => (element, index, array) => {
return element.indexOf(wordToCompare) === 0;
}
// where word would be your argument
let result = addressBook.filter(startsWith("word"));
Updated version using includes:
const startsWith = wordToCompare => (element, index, array) => {
return element.includes(wordToCompare);
}
function startsWith(element, wordToCompare) {
return element.indexOf(wordToCompare) === 0;
}
// ...
var word = "SOMETHING";
addressBook.filter(function(element){
return startsWith(element, word);
});
You can use the arrow function inside a filter, like this:
result = addressBook.filter(element => element.indexOf(wordToCompare) === 0);
Arrow functions on MDN
An arrow function expression has a shorter syntax compared to function expressions and lexically binds the this value (does not bind its own this, arguments, super, or new.target). Arrow functions are always anonymous. These function expressions are best suited for non-method functions and they can not be used as constructors.
For anyone wondering why their fat arrow function is ignoring [, thisArg], e.g. why
["DOG", "CAT", "DOG"].filter(animal => animal === this, "DOG")
returns []
it's because this inside those arrow functions are bound when the function is created and are set to the value of this in the broader encompassing scope, so the thisArg argument is ignored. I got around this pretty easily by declaring a new variable in a parent scope:
let bestPet = "DOG";
["DOG", "CAT", "DOG"].filter(animal => animal === bestPet);
=> ["DOG", "DOG"]
Here is a link to some more reading:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_separate_this
based on oddRaven answer
and
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
i did it 2 different way .
1) using function way .
2) using inline way .
//Here is sample codes :
var templateList = [
{ name: "name1", index: 1, dimension: 1 } ,
{ name: "name2", index: 2, dimension: 1 } ,
{ name: "name3", index: 3, dimension: 2 } ];
//Method 1) using function :
function getDimension1(obj) {
if (obj.dimension === 1) // This is hardcoded .
return true;
else return false;
}
var tl = templateList.filter(getDimension1); // it will return 2 results. 1st and 2nd objects.
console.log(tl) ;
//Method 2) using inline way
var tl3 = templateList.filter(element => element.index === 1 || element.dimension === 2 );
// it will return 1st and 3rd objects
console.log(tl3) ;
There is an easy way to use the filter function, access all params, and not over complicate it.
Unless the callback's thisArg is set to another scope filter does not create its own scope, and we can access params within the current scope. We can set 'this' to define a different scope in order to access other values if needed, but by default it is set to the scope it's called from. You can see this being used for Angular scopes in this stack.
Using indexOf is defeating the purpose of filter, and adding more overhead. Filter is already going through the array, so why do we need to iterate through it again? We can instead make it a simple pure function.
Here's a use-case scenario within a React class method where the state has an array called items, and by using filter we can check the existing state:
checkList = (item) => { // we can access this param and globals within filter
var result = this.state.filter(value => value === item); // returns array of matching items
result.length ? return `${item} exists` : this.setState({
items: items.push(item) // bad practice, but to keep it light
});
}