I am trying to understand Slate.js by looking through the rich text example, and I came across the following snippet of code, which I do not understand.
const isBlockActive = (editor, format) => {
const [match] = Editor.nodes(editor, {
match: n => n.type === format,
})
return !!match
}
I am not an expert in javascript, and I am new to both typescript and slate.js so I apologize in advance for not being able to frame my question better, but here is what I currently understand and what I am still unsure about:
(1) Editor.nodes() is a method which returns an Iterable. What is the "const [match]" notation? Is this javascript or typescript?
(2) Is the "match" in "const [match]" the same as the "match" in "match : n => n.type === format"? If so, does that mean "const [match]" is an array with one element which is a function? It seems odd if that were the case as then why bother making Editor.nodes() return an Iterable at all?
(3) I know double exclamation points give me a Boolean object, but since I can't wrap my head around whether match is a function or an iterable or something else, I have no idea what the truth or falsity of !!match tells me about the initial iterable returned by Editor.nodes().
Thanks for any light you may be able to shed on my confused brain!
That's called array destructuring. match is a variable that contains the first element of the array (or rather the first value yielded by the iterator) returned by the Editor.nodes function. It barely equals:
const match = Editor.nodes(...)[0];
Or more accurately:
const _it = Editor.nodes(...)[Symbol.iterator]().next();
const match = _it.done ? undefined : _it.value;
(1) Editor.nodes() is a method which returns an Iterable. What is the "const [match]" notation? Is this javascript or typescript?
It's JavaScript (and TypeScript) array destructuring. Editor.nodes returns an iterable which is iterated over to create an array, and the first element of this array is assigned to match. We're only interested in in whether there is at least one match, so looking at the first element of the array will tell us that.
(2) Is the "match" in "const [match]" the same as the "match" in "match : n => n.type === format"? If so, does that mean "const [match]" is an array with one element which is a function? It seems odd if that were the case as then why bother making Editor.nodes() return an Iterable at all?
The two variables are completely different, and could have (should have?) been named differently to make things more clear. Editor.nodes() is part of the Editor interface/API, which is used for many different things. In this case we're only interested in the first element. You could use it to find and iterate over all nodes in editor.children which your match function returns true for.
(3) I know double exclamation points give me a Boolean object, but since I can't wrap my head around whether match is a function or an iterable or something else, I have no idea what the truth or falsity of !!match tells me about the initial iterable returned by Editor.nodes().
The resulting match is a node object, or undefined if it didn't match any. An object is truthy while undefined is falsey, and doing !! just converts it to a boolean.
Related
I am calling return someArr[someArr.push(newItem)-1]; and it does pretty what it should.
Wondering is there a syntactic "sugar" for that extremely common operation? I wonder because returned new length is indeed more rarely needed, than actual constructed object (which caller definetely may want to polulate). Or is that made because it actually will return "a copy of an object", and my edits to its fields "will not survive" in actual tree?
Just like a JS <= 1.2 did: return someArr.push(newItem);
Just want to know - Is there are new "replacement" method, that I am unaware of?
Also I had a minor subquestion "does that return an 'independent' copy of an object or a 'reference' to actual object?" but simple fiddle by #PatricRoberts just figured out that it is actually a reference (as it was expected), so I've credited him, for his assistance!
You could also do it using a logical and (&&).
return someArr.push(newItem) && newItem;
Since the array length will be positive after the push, the right-most expression will be evaluated and its value returned.
This syntax is less clear than #adiga's answer using the comma operator though.
However, I personally prefer to return the new item on a new line to make it more readable:
someArr.push(newItem);
return newItem;
You could use the comma operator:
The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.
Like this:
return (someArr.push(newItem), newItem)
This is equivalent to:
someArr.push(newItem);
return newItem
You can do this by returning an assignment to the index of the current .length.
return someArr[someArr.length] = newItem;
The result of the assignment is the item being assigned, so that's what will be returned.
Assigning to the current .length of the array automatically expands it. Note that assigning to any index higher than the current .length will leave holes in the array.
Why does the first line work while the second line throws run-time exception?
The first line:
[[]][0]++; //this line works fine
The second line:
[]++; //this lines throws exception
[[]][0]++
is equivalent to
var tmp = [[]];
tmp[0] = tmp[0]+1;
tmp[0] is an empty array, which is cast to the number 0, which increments to 1.
This only works because <array>[<index>]++ looks valid. It takes some type juggling, but it gets there.
But []++ is outright invalid. There's no way to make it make sense.
[] = []+1;
The left-hand side here is indeed invalid. You can't assign to an empty array.
The ++ operator (or indeed any postfix operator) requires the operand to be a "reference" - that is, a value that can be assigned to. [] is a literal, so you can't assign to it. [[]][0] is a valid reference to an element of a temporary array.
0++; // not valid, `0` is a literal.
var a = [];
a++; // ok, `a` is assignable
This is a rare case in which Javascript does something that actually makes sense. Consider
x[3]++; // Valid
3++; // Not valid
If this make sense for you, then what is surprising about
[[]][0]++; // valid
[]++; // not valid
<array>[index] is "a place" that you can assign or increment. That's all. The fact that you can increment a[<expr>] doesn't imply that you can increment <expr>.
The absurd part is that you can use [] as an index, that has the meaning of converting the array to an empty string "" and then to the number 0, but this is the well known problem of absurd implicit conversions of Javascript. Those implicit conversion rules are a big wart of Javascript and for example imply that 1 == [1] or that both []==false and (![])==false.
Javascript is pure nonsense in a lot of places... but not really here.
I used to think that Ruby's
arr.inject(:+)
or JavaScript's
arr.reduce((a, b) => { return a + b })
// a cooler way
arr.reduce( (a, b) => a + b )
are both the equivalent of summing the array entries up. But is it actually not correct because of this one case: what if the array is empty?
Ruby will return nil, and JavaScript will raise an exception. So besides using an initial value, are there also better ways to do it?
Ruby's inject can accept two parameters, the first one being the initial value. If there's no element in the Enumerable, this initial value becomes the returned value:
[].inject(:+)
# nil
[].inject(0,:+)
# 0
As mentioned by #Ursus in the comments, Ruby 2.4 defines Enumerable#sum with a default value of 0:
[].sum
# 0
As a bonus, it's also much faster for some objects (e.g. Ranges).
Note that returning nil could sometimes be the desired result, depending on which process comes after.
Finally, kudos to JS for raising an exception when the array is empty and no default value has been defined. It's probably the cleanest solution.
To deal with the exception raised in case of empty array, declare an initial value.
var arr = [],
res = arr.reduce((a,b) => { return a + b }, 0);
//^^ initial value
console.log(res);
Just because you use a dynamically-typed language with implicit types, doesn't mean that you don't have to think about types.
In other languages, these two operations have different names (e.g. in Haskell foldl and foldl1, in Scala foldLeft and reduceLeft), whereas in Ruby, they are overloaded, but the matter still remains that they are two distinct operations:
the general fold operation
operates on a collection of As
has a zero element of type B
and a combining function which takes a B and an A and returns a B
which means
it can return an object of a type other than the element type (it returns a B, not an A)
it can operate on an empty collection
the constrained reduce operation
operates on a collection of As
has no zero element, it uses an element of the collection instead, which has type A
and a combining function which takes an A and an A and returns an A
which means
it can only return an object of the same type as the element type (it returns an A)
it can only operate on a non-empty collection
That's also where the Haskell names come from, it is called foldl1 because it requires at least 1 element, and takes element number 1 as the initial element.
Note that the general fold operation is strictly more powerful than the constrained reduce operation. In fact, the general fold operation is a universal iterator operation, it can do anything that a foreach loop can do: map, groupBy, sort, uniq, reverse, … you name it. Every collection operation can be implemented as a fold.
Example:
module Enumerable
def my_reverse
inject([]) {|acc, el| [el] + acc }
end
# this is not exactly equivalent because it's not lazy
def my_any?
inject(false) {|acc, el| acc || yield el }
end
end
reduce can not do everything. For example, you cannot implement reverse using reduce because reduce returns an A, but reverse returns a Collection<A>; likewise, you cannot implement any? because it returns a Boolean. You can only use reduce if these two properties hold:
you want to produce a value of the same type as the element type
there is at least one element
In your case, #1 is true, but #2 isn't, which is why you cannot use reduce, you must use fold. I.e. in Ruby, you need to use
ary.inject(0, :+)
and in ECMAScript
arr.reduce((acc, el) => acc + el, 0)
As mentioned elsewhere, Ruby does have Enumerable#sum; however, that only works for this specific case of summing the elements, but for example not for the practically identical problem of multiplying them.
I'd like to know how to replace a capture group with its uppercase in JavaScript. Here's a simplified version of what I've tried so far that's not working:
> a="foobar"
'foobar'
> a.replace( /(f)/, "$1".toUpperCase() )
'foobar'
> a.replace( /(f)/, String.prototype.toUpperCase.apply("$1") )
'foobar'
Would you explain what's wrong with this code?
You can pass a function to replace.
var r = a.replace(/(f)/, function(v) { return v.toUpperCase(); });
Explanation
a.replace( /(f)/, "$1".toUpperCase())
In this example you pass a string to the replace function. Since you are using the special replace syntax ($N grabs the Nth capture) you are simply giving the same value. The toUpperCase is actually deceiving because you are only making the replace string upper case (Which is somewhat pointless because the $ and one 1 characters have no upper case so the return value will still be "$1").
a.replace( /(f)/, String.prototype.toUpperCase.apply("$1"))
Believe it or not the semantics of this expression are exactly the same.
I know I'm late to the party but here is a shorter method that is more along the lines of your initial attempts.
a.replace('f', String.call.bind(a.toUpperCase));
So where did you go wrong and what is this new voodoo?
Problem 1
As stated before, you were attempting to pass the results of a called method as the second parameter of String.prototype.replace(), when instead you ought to be passing a reference to a function
Solution 1
That's easy enough to solve. Simply removing the parameters and parentheses will give us a reference rather than executing the function.
a.replace('f', String.prototype.toUpperCase.apply)
Problem 2
If you attempt to run the code now you will get an error stating that undefined is not a function and therefore cannot be called. This is because String.prototype.toUpperCase.apply is actually a reference to Function.prototype.apply() via JavaScript's prototypical inheritance. So what we are actually doing looks more like this
a.replace('f', Function.prototype.apply)
Which is obviously not what we have intended. How does it know to run Function.prototype.apply() on String.prototype.toUpperCase()?
Solution 2
Using Function.prototype.bind() we can create a copy of Function.prototype.call with its context specifically set to String.prototype.toUpperCase. We now have the following
a.replace('f', Function.prototype.apply.bind(String.prototype.toUpperCase))
Problem 3
The last issue is that String.prototype.replace() will pass several arguments to its replacement function. However, Function.prototype.apply() expects the second parameter to be an array but instead gets either a string or number (depending on if you use capture groups or not). This would cause an invalid argument list error.
Solution 3
Luckily, we can simply substitute in Function.prototype.call() (which accepts any number of arguments, none of which have type restrictions) for Function.prototype.apply(). We have now arrived at working code!
a.replace(/f/, Function.prototype.call.bind(String.prototype.toUpperCase))
Shedding bytes!
Nobody wants to type prototype a bunch of times. Instead we'll leverage the fact that we have objects that reference the same methods via inheritance. The String constructor, being a function, inherits from Function's prototype. This means that we can substitute in String.call for Function.prototype.call (actually we can use Date.call to save even more bytes but that's less semantic).
We can also leverage our variable 'a' since it's prototype includes a reference to String.prototype.toUpperCase we can swap that out with a.toUpperCase. It is the combination of the 3 solutions above and these byte saving measures that is how we get the code at the top of this post.
Why don't we just look up the definition?
If we write:
a.replace(/(f)/, x => x.toUpperCase())
we might as well just say:
a.replace('f','F')
Worse, I suspect nobody realises that their examples have been working only because they were capturing the whole regex with parentheses. If you look at the definition, the first parameter passed to the replacer function is actually the whole matched pattern and not the pattern you captured with parentheses:
function replacer(match, p1, p2, p3, offset, string)
If you want to use the arrow function notation:
a.replace(/xxx(yyy)zzz/, (match, p1) => p1.toUpperCase()
Old post but it worth to extend #ChaosPandion answer for other use cases with more restricted RegEx. E.g. ensure the (f) or capturing group surround with a specific format /z(f)oo/:
> a="foobazfoobar"
'foobazfoobar'
> a.replace(/z(f)oo/, function($0,$1) {return $0.replace($1, $1.toUpperCase());})
'foobazFoobar'
// Improve the RegEx so `(f)` will only get replaced when it begins with a dot or new line, etc.
I just want to highlight the two parameters of function makes finding a specific format and replacing a capturing group within the format possible.
SOLUTION
a.replace(/(f)/,(m,g)=>g.toUpperCase())
for replace all grup occurrences use /(f)/g regexp. The problem in your code: String.prototype.toUpperCase.apply("$1") and "$1".toUpperCase() gives "$1" (try in console by yourself) - so it not change anything and in fact you call twice a.replace( /(f)/, "$1") (which also change nothing).
let a= "foobar";
let b= a.replace(/(f)/,(m,g)=>g.toUpperCase());
let c= a.replace(/(o)/g,(m,g)=>g.toUpperCase());
console.log("/(f)/ ", b);
console.log("/(o)/g", c);
Given a dictionary (object, in this case, a Map) of property, values, and using .bind() as described at answers
const regex = /([A-z0-9]+)/;
const dictionary = new Map([["hello", 123]]);
let str = "hello";
str = str.replace(regex, dictionary.get.bind(dictionary));
console.log(str);
Using a JavaScript plain object and with a function defined to get return matched property value of the object, or original string if no match is found
const regex = /([A-z0-9]+)/;
const dictionary = {
"hello": 123,
[Symbol("dictionary")](prop) {
return this[prop] || prop
}
};
let str = "hello";
str = str.replace(regex, dictionary[Object.getOwnPropertySymbols(dictionary)[0]].bind(dictionary));
console.log(str);
In the case of string conversion from CamelCase to bash_case (ie: for filenames), use a callback with ternary operator.
The captured group selected with a regexp () in the first (left) replace arg is sent to the second (right) arg that is a callback function.
x and y give the captured string (don't know why 2 times!) and index (the third one) gives the index of the beginning of the captured group in the reference string.
Therefor a ternary operator can be used not to place _ at first occurence.
let str = 'MyStringName';
str = str.replace(/([^a-z0-9])/g, (x,y,index) => {
return index != 0 ? '_' + x.toLowerCase() : x.toLowerCase();
});
console.log(str);
I'm writing a Google Chrome extension, in JavaScript, and I want to use an array to store a bunch of objects, but I want the indexes to be specific non-consecutive ID numbers.
(This is because I need to be able to efficiently look up the values later, using an ID number that comes from another source outside my control.)
For example:
var myObjects = [] ;
myObjects[471] = {foo: "bar"} ;
myObjects[3119] = {hello: "goodbye"}
When I do console.log(myObjects), in the console I see the entire array printed out, with all the thousands of 'missing' indexes showing undefined.
My question is: does this matter? Is this wasting any memory?
And even if it's not wasting memory, surely whenever I loop over the array, it wastes CPU if I have to manually skip over every missing value?
I tried using an object instead of an array, but it seems you can't use numbers as object keys. I'm hoping there's a better way to achieve this?
First of all, everyone, please learn that what the for-in statement does is called enumeration (though it's an IterationStatement) in order to differentiate from iteration. This is very important, because it leads to confusion especially among beginners.
To answer the OP's question: It doesn't take up more space (test) (you could say it's implementation dependent, but we're talking about a Google Chrome Extension!), and it isn't slower either (test).
Yet my advice is: Use what's appropriate!
In this situation: use objects!
What you want to do with them is clearly a hashing mechanism, keys are converted to strings anyway so you can safely use object for this task.
I won't show you a lot of code, other answers do it already, I've just wanted to make things clear.
// keys are converted to strings
// (numbers can be used safely)
var obj = {}
obj[1] = "value"
alert(obj[1]) // "value"
alert(obj["1"]) // "value"
Note on sparse arrays
The main reason why a sparse array will NOT waste any space is because the specification doesn't say so. There is no point where it would require property accessors to check if the internal [[Class]] property is an "Array", and then create every element from 0 < i < len to be the value undefined etc. They just happen to be undefined when the toString method is iterating over the array. It basically means they are not there.
11.2.1 Property Accessors
The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:
Let baseReference be the result of evaluating MemberExpression.
Let baseValue be GetValue(baseReference).
Let propertyNameReference be the result of evaluating Expression.
Let propertyNameValue be GetValue(propertyNameReference).
Call CheckObjectCoercible(baseValue).
Let propertyNameString be ToString(propertyNameValue).
If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
The production CallExpression : CallExpression [ Expression ] is evaluated in exactly the same manner, except that the contained CallExpression is evaluated in step 1.
ECMA-262 5th Edition (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
You can simply use an object instead here, having keys as integers, like this:
var myObjects = {};
myObjects[471] = {foo: "bar"};
myObjects[3119] = {hello: "goodbye"};
This allows you to store anything on the object, functions, etc. To enumerate (since it's an object now) over it you'll want a different syntax though, a for...in loop, like this:
for(var key in myObjects) {
if(myObjects.hasOwnProperty(key)) {
console.log("key: " + key, myObjects[key]);
}
}
For your other specific questions:
My question is: does this matter? Is this wasting any memory?
Yes, it wastes a bit of memory for the allocation (more-so for iterating over it) - not much though, does it matter...that depends on how spaced out the keys are.
And even if it's not wasting memory, surely whenever I loop over the array, it wastes CPU if I have to manually skip over every missing value?
Yup, extra cycles are used here.
I tried using an object instead of an array, but it seems you can't use numbers as object keys. I'm hoping there's a better way to achieve this?
Sure you can!, see above.
I would use an object to store these. You can use numbers for properties using subscript notation but you can't using dot notation; the object passed as the key using subscript notation has toString() called on it.
var obj = {};
obj[471] = {foo: "bar"} ;
As I understand it from my reading of Crockford's "The Good Parts," this does not particularly waste memory, since javascript arrays are more like a special kind of key value collection than an actual array. The array's length is defined not as the number of addresses in the actual array, but as the highest-numbered index in the array.
But you are right that iterating through all possible values until you get to the array's length. Better to do as you mention, and use an object with numeric keys. This is possible by using the syntax myObj['x']=y where x is the symbol for some integer. e.g. myObj['5']=poodles Basically, convert your index to a string and you're fine to use it as an object key.
It would be implementation dependent, but I don't think you need to worry about wasted memory for the "in between" indices. The developer tools don't represent how the data is necessarily stored.
Regarding iterating over them, yes, you would be iterating over everything in between when using a for loop.
If the sequential order isn't important, then definitely use a plain Object instead of an Array. And yes, you can use numeric names for the properties.
var myObjects = {} ;
myObjects["471"] = {foo: "bar"} ;
myObjects["3119"] = {hello: "goodbye"};
Here I used Strings for the names since you said you were having trouble with the numbers. They ultimately end up represented as strings when you loop anyway.
Now you'll use a for-in statement to iterate over the set, and you'll only get the properties you've defined.
EDIT:
With regard to console.log() displaying indices that shouldn't be there, here's an example of how easy it is to trick the developer tools into thinking you have an Array.
var someObj = {};
someObj.length = 11;
someObj.splice = function(){};
someObj[10] = 'val';
console.log(someObj);
Clearly this is an Object, but Firebug and the Chrome dev tools will display it as an Array with 11 members.
[undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, "val"]
So you can see that the console doesn't necessarily reflect how/what data is actually stored.
I would simply use a constant prefix, to avoid such problems.
var myObjects = {};
myObjects['objectId_'+365] = {test: 3};
will default to Js-Objects.
You could attempt to do something like this to make it loud and clear to the JIST compiler that this is a more objecty-ish array like so:
window.SparseArray = function(len){
if (typeof len === 'number')
if (0 <= len && len <= (-1>>>0))
this.length = len;
else
new Array(len); // throws an Invalid array length type error
else
this.push.apply(this, arguments);
}
window.SparseArray.prototype = Array.prototype