Javascript check if object property exists, even when object is undefined - javascript

I want to check if an object exists, and has a property. Currently I get a "myObject is undefined" error that stops the check.
How can I make the following still work correctly even when myObject may not exist?
if (myObject.myProperty) {
...
} else {
...
}
I am trying to just even check if a object / variable exists but getting an error:
if (foo) { console.log('hello'); } gives the error Uncaught ReferenceError: foo is not defined. Here is a jsfiddle http://jsfiddle.net/cfUss/

You can use the "short circuit" && operator:
if (myObject && myObject.myProperty) {
...
}
If myObject is "falsey" (e.g. undefined) the && operator won't bother trying to evaluate the right-hand expression, thereby avoiding the attempt to reference a property of a non-existent object.
The variable myObject must have course already have been declared, the test above is for whether it has been assigned a defined value.

You can use the optional chaining operator ?. to keep things succinct:
if (myObject?.myProperty) { ... }
which is equal to the a bit more verbose
if (myObject && myObject.myProperty) { ... }
It comes in very handy especially for deeply nested objects.
It's currently (September 2019) a ECMAScript stage 3 proposal but things are looking promising that this feature will become officially available. For the time being, you can already start using it via the respective Babel plugin: https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining

Try:
if(myObject && myObject.myProperty){ ... }
This code enters the body of the if block if myObject exists and also has myproperty. If myObject doesn't exist for some reason, the && short-circuits and does not evaluated myObject.myProperty.

I've been suprised not to find this helpful function in basic Javascipt interface.
Below is a helper function I often use in my projects. It checks if the value of the final chain element is reachable without an error "can't get ... of undefined". Use it this way:
getChained(this, "this.path.to.some.object.someFunction().result");
/**
* Deconstructs a chain, checking if every other element is undefined.
* You can use arrays and functions in chain and even pass parameters
* inside them.
*
* The only limitation is no string values with dots are allowed.
*
* #param {Object} chainScope Starting object, the chain scope.
* Guaranteed not to be be undefined.
* Required parameter.
* #param {String} chain A chain from code without first object
* and without first dot.
* Required parameter.
* #param {Array<Array<Object||String||Number||Boolean||undefined>>}
* functionsParameters Parameters for functions in chain.
* Object in the array correspond the order
* of functions in chain. Every object
* contains parameters for its function.
* Object syntax forces us to create parameter
* names even if you only have its value.
* #returns {Object||String||Number||Boolean||undefined} Final chain element value or undefined, if any other chain element returned undefined.
*/
getChained: function (
chainScope,
chain,
functionsParameters) {
var parts;
var part;
var partIndex;
var target = undefined;
var functionIndex = 0;
if (
chainScope === undefined ||
chainScope === null) {
return target;
}
target = chainScope; // The starting scope of a chain.
parts = getParts();
// Relay chain substituting calculated parts with values
// for function calls and arrays:
for (
partIndex = 0;
partIndex < parts.length;
partIndex++) {
if (target === undefined) {
// Chain element is undefined and so is the chain itself:
return undefined;
}
part = parts[partIndex];
if (
part.indexOf("(") >
part.indexOf("\"") &&
part.indexOf("(") >
part.indexOf("\'")) {
// It's a function:
target = getFunctionValue();
functionIndex++;
continue;
}
if (
part.indexOf("[") >
part.indexOf("\"") &&
part.indexOf("]") >
part.indexOf("\'")) {
// It's an array's element:
target = getArrayValue();
continue;
}
if (
typeof part === "string" &&
target !== null &&
target !== undefined) {
// It's an object:
target = target[part];
continue;
}
}
return target;
/**
* Splits string. Separators are dots outside the brackets.
* No splitting for dots inside the brackets.
*/
function getParts() {
var SEPARATOR = ".";
var OPEN_CHARS = [
"(",
"[",
"\"",
"\'"
];
var CLOSE_CHARS = [
")",
"]",
"\"",
"\'"
];
var SUB_SEPARATOR_OPEN = "[";
var SUB_SEPARATOR_CLOSE = "]";
return(
splitBySubSeparator(
splitBySeparator(
chain)));
/**
* Split by chain root separator.
* No splitting between opening and closing characters.
*
* #param {String} chainString Chain to analyse characters.
* #returns {Array<String>} Chain elements splitted.
*/
function splitBySeparator(chainString) {
var parts = [
];
var opened = 0;
var char1;
var chainIndex;
var extract;
var cutFromIndex = 0;
var chainArray;
// String to array and attach the ending dot
// to be able to split using common rule:
chainArray =
(chainString + ".").
split("");
for (
chainIndex = 0;
chainIndex < chainArray.length;
chainIndex++) {
char1 = chainArray[chainIndex];
if (OPEN_CHARS.indexOf(char1) > 0) {
// It's an opening bracket:
opened++;
continue;
}
if (CLOSE_CHARS.indexOf(char1) > 0) {
// It's a closing bracket:
opened--;
continue;
}
if (opened === 0) {
// It's character outside the brackets:
if (char1 === SEPARATOR) {
// It's a dot - everything before it is an element:
extract =
chainArray.slice(
cutFromIndex,
chainIndex). // Cut an element.
join(""); // Array to String.
parts.push(
extract);
cutFromIndex = chainIndex + 1; // Shift to escape a dot.
} else {
// It's an ordinary character:
continue;
}
}
}
return parts;
}
/**
* Splits by root subobject or array elements calls.
* Subcalls are searched inside the splitted chain elements.
* (now separator is "[" instead of ".").
* Can split several consequently called subobjects
* without a need to deconstruct enclosures.
* Second iteration finds array elements and object subcalls
* inside resulting elements (now separator is "[" instead of "."):
*/
function splitBySubSeparator(parts) {
var newParts = [
];
var opened = 0;
var char1;
var partIndex;
var chainIndex;
var chainArray;
for (
partIndex = 0;
partIndex < parts.length;
partIndex++) {
var part = parts[partIndex];
chainArray = part.split("");
for (
chainIndex = 0;
chainIndex < chainArray.length;
chainIndex++) {
char1 = chainArray[chainIndex];
if (
opened === 0 &&
char1 === SUB_SEPARATOR_OPEN) {
// Start of subcall for an array element or object:
part =
part.substr(0, chainIndex) +
SEPARATOR +
part.substr(chainIndex + 1);
opened++;
}
if (
opened > 0 &&
char1 === SUB_SEPARATOR_CLOSE) {
// End of subcall for an array element or object:
part =
part.substr(0, chainIndex) +
"" +
part.substr(chainIndex + 1);
opened--;
}
}
// Split changed element by separators again and
// relay into a cumulative array:
newParts =
newParts.concat(
splitBySeparator(part));
}
return newParts;
}
}
/**
* Creates and returns method call result. Puts required
* parameters into method.
*
* #returns {Object||String||Number||Boolean||undefined} Method execution result.
*/
function getFunctionValue() {
var value;
var name;
name =
part.
split("(")[0];
if (functionsParameters) {
value =
target[name].
apply(
target,
functionsParameters[
functionIndex
]);
} else {
value =
target[name].
apply(
target);
}
return value;
}
/**
* Returns array element.
*
* #returns {Object||String||Number||Boolean||undefined} Value of array element.
*/
function getArrayValue() {
var value;
var arrayName;
var itemName;
arrayName =
part.
split("[")[0];
itemName =
(part.
split("[")[1].
split("]")[0]).
split("\'").
join("").
split("\"").
join("");
if (target[arrayName]) {
value =
target[arrayName][itemName];
}
return value;
}
}

Related

Implementing the randomized set class with O(1) operation

This is related to the leetcode - question here, which asks:
Implement the RandomizedSet class:
RandomizedSet() Initializes the RandomizedSet object.
bool insert(int val) Inserts an item val into the set if not present. Returns true if the item was not present, false otherwise.
bool remove(int val) Removes an item val from the set if present. Returns true if the item was present, false otherwise.
int getRandom() Returns a random element from the current set of elements (it's guaranteed that at least one element exists when this method is called). Each element must have the same probability of being returned.
You must implement the functions of the class such that each function works in average O(1) time complexity.
I am able to pass some of the test cases but it fails one of the test cases (where in some places, my program returns undefined). What is it that I am doing wrong here? I am unable to find my mistake.
var RandomizedSet = function() {
this.map = new Map();
this.vector = [];
};
/**
* #param {number} val
* #return {boolean}
*/
RandomizedSet.prototype.insert = function(val) {
if(this.map.has(val)) return false;
else {
let position = 0;
if(this.vector.length > 0)
position = this.vector.length-1;
this.map.set(val, position);
this.vector.push(val);
return true;
}
};
/**
* #param {number} val
* #return {boolean}
*/
RandomizedSet.prototype.remove = function(val) {
if(this.map.has(val)) {
const index = this.map.get(val);
const lastVal = this.vector[this.vector.length-1];
this.vector[index] = lastVal;
this.vector.pop();
this.map.delete(val);
return true;
}else {
return false;
}
};
/**
* #return {number}
*/
RandomizedSet.prototype.getRandom = function() {
const randIndex = Math.floor(Math.random() * this.vector.length);
return this.vector[randIndex];
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* var obj = new RandomizedSet()
* var param_1 = obj.insert(val)
* var param_2 = obj.remove(val)
* var param_3 = obj.getRandom()
*/
When moving the last element into the place of the removed element, you forgot to update the moved element's index in the map:
RandomizedSet.prototype.remove = function(val) {
if(this.map.has(val)) {
const index = this.map.get(val);
const lastVal = this.vector[this.vector.length-1];
this.vector[index] = lastVal;
this.vector.pop();
this.map.set(lastVal, index); // ADDED
this.map.delete(val);
return true;
}else {
return false;
}
};
Be aware of the case index == this.vector.length-1; the order of operations shown above will handle this case correctly without additional code.

How can I use a new version of an object in a loop, without it getting overriden?

I have a function that loops over an array of words, and checks if a string passed to the function contains any of the letters in the word. The word with the most matching letters is returned.
My issue is that the const I am copying to the loop scope is getting overriden. Specifically let loopIndex = alphabetIndex; How can I make a variable that I can manipulate in the loop, without it overriding the const?
import _forEach from 'lodash/foreach';
const dictionary = require('./dictionary.json');
/**
* #constructor
*/
var Game = function () {
};
/**
* #desc Return an object of the repeating string characters.
* #param string
* #returns {{}}
*/
Game.prototype.setAlphabet = function (string) {
let alphabet = {};
for (let i = 0; i < string.length; i += 1) {
if (string[i] in alphabet) {
alphabet[string[i]] += 1;
} else {
alphabet[string[i]] = 1;
}
}
return alphabet;
};
/**
* #desc Return the largest word using the string passed to the function.
* #param string
*/
Game.prototype.getWinningWord = function (string) {
let max = 0;
let maxIndex = 0;
// Get the amount of letters used in the string as an object.
const alphabetIndex = this.setAlphabet(string);
// Loop through every word in the dictionary
_forEach(dictionary, function (word, position) {
let sum = 0;
let loopIndex = alphabetIndex;
// For each of the letters, check the index; and if the letter exists in the word, add to the sum.
for (let i = 0; i < word.length; i += 1) {
if (loopIndex[word[i]] > 0) {
sum += 1;
loopIndex[word[i]] -= 1;
}
}
// If the current word has more matching letters than the previous max, set this word to max.
if (sum > max) {
max = sum;
maxIndex = position;
}
if (position > 10) return false;
});
return dictionary[maxIndex];
};
/**
* Init game.
* #type {Game}
*/
var newGame = new Game();
console.log(newGame.getWinningWord('eeosnndsm'));
The distinction here is that while a const variable can never be assigned to again, if the value of that variable is a reference then that reference can change. To avoid this, you need to make a new copy in each iteration of your loop, a simple way to do this is to use Object.assign():
let loopIndex = Object.assign({}, alphabetIndex);

How could I modify the array elements recursively with backtracking?

I wrote a kind of N-Queens algorithm, handling only the vertical and horizontal threats detection. Thus, it's rather a N-Towers solutions finder.
To do this, I use recursion. It's a well-known algorithm. For each square of the chessboard, I place a tower. For each placed tower, I try to place another tower (this is the recursive call). If there is not any remaining tower to place, it means the program has found a solution and the recursive level has to return. If all the chessboard has been crossed with remaining tower(s) to place, it means the program didn't find a solution and the recursive level has to return.
My recursive function has two parameters : the number of towers which have to be placed and the chessboard (an array of array of string ; the string equals "T" to indicate a tower has been placed in this chessboard's square and "-" to indicate the square is empty).
The problem
My algorithm seems to find all the solutions and displays them as chessboards, using the "-" (and, if it worked well, "T") notation. This notation is explained above.
However, even if the number of solutions seems to be correct, the displayed solutions/chessboards contain only "-".
I think I don't pass my array of array (ie. : the chessboard) correctly in my recursive call.
Illustration of this problem
For 2 towers and a chessboard of 2*2 squares, two solutions are found and it's normal. But there are only "-" and no "T" appears... That's the problem. Indeed :
--
--
Code : focus on my recursive function
/**
* RECURSIVE FUNCTION. If there are still towers to place, this function tries to place them. If not, it means a
* solution has been found : it's stored in an array (external to this function).
* If this function can't place a tower, nothing happens.
* Else, it places it and makes the recursive call.
* Each recursion level does this for each next (to the placed tower) chessboard's squares.
* #param number_of_left_towers how many remaining towers to place there are (if 0, it means a solution has been
* found)
* #param array_array_chessboard the chessboard
* #returns {Number} the return is not important
*/
function placeTower(number_of_left_towers, array_array_chessboard) {
if (number_of_left_towers == 0) {
return solutions.push(array_array_chessboard);
}
for (var current_x = 0; current_x < number_of_lines; current_x++) {
for (var current_y = 0; current_y < number_of_columns; current_y++) {
if (array_array_chessboard[current_x][current_y] == "-" && canBePlaced(array_array_chessboard, current_x, current_y)) {
array_array_chessboard[current_x][current_y] = "T";
placeTower(number_of_left_towers - 1, array_array_chessboard);
array_array_chessboard[current_x][current_y] = "-";
}
}
}
}
Code : JSFiddle with all the source
https://jsfiddle.net/btcj6uzp/
You can also find the same code below :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Recursive algorithm of the N-Towers</title>
</head>
<body>
<script type="text/javascript">
/**
* Finds all the solutions to the N-Towers algorithm.
*
* #param number_of_towers number of towers to try to place in the chessboard
* #param number_of_lines chessboard's ones
* #param number_of_columns chessboard's ones
* #returns {nTowersSolutions} array containing all the solutions
*/
function nTowersSolutions(number_of_towers, number_of_lines, number_of_columns) {
/*
NB
"T" = "Tower" = presence of a tower in this square of the chessboard
"-" = "nothing" = no tower in this square of the chessboard
(used for both solutions displaying and finding)
*/
var solutions = [];
var array_array_chessboard = []; // Represents the chessboard
for(var i = 0; i < number_of_lines; i++) {
array_array_chessboard[i] = new Array(number_of_columns);
for(var j = 0; j < number_of_columns; j++) {
array_array_chessboard[i][j] = "-"; // We initialize the chessboard with "-"
}
}
/**
* Uses HTML to display the found solutions, in the Web page
*/
this.displaySolutions = function() {
var body = document.body;
solutions.forEach((array_array_chessboard) => {
array_array_chessboard.forEach(function(array_chessboard) {
array_chessboard.forEach((square) => {
body.innerHTML += square; // New cell
});
body.innerHTML += "<br />"; // New line
});
body.innerHTML += "<br /><br />"; // New solution
});
};
/**
* RECURSIVE FUNCTION. If there are still towers to place, this function tries to place them. If not, it means a
* solution has been found : it's stored in an array (external to this function).
* If this function can't place a tower, nothing happens.
* Else, it places it and makes the recursive call.
* Each recursion level does this for each next (to the placed tower) chessboard's squares.
* #param number_of_left_towers how many remaining towers to place there are (if 0, it means a solution has been
* found)
* #param array_array_chessboard the chessboard
* #returns {Number} the return is not important
*/
function placeTower(number_of_left_towers, array_array_chessboard) {
if (number_of_left_towers == 0) {
return solutions.push(array_array_chessboard);
}
for (var current_x = 0; current_x < number_of_lines; current_x++) {
for (var current_y = 0; current_y < number_of_columns; current_y++) {
if (array_array_chessboard[current_x][current_y] == "-" && canBePlaced(array_array_chessboard, current_x, current_y)) {
array_array_chessboard[current_x][current_y] = "T";
placeTower(number_of_left_towers - 1, array_array_chessboard);
array_array_chessboard[current_x][current_y] = "-";
}
}
}
}
/**
* Can this tower be placed ?
* #param array_array_chessboard
* #param new_x
* #param new_y
* #returns {boolean}
*/
function canBePlaced(array_array_chessboard, new_x, new_y) {
for(var i = 0; i < array_array_chessboard.length; i++) {
for(var z = 0; z < array_array_chessboard[i].length; z++) {
if(array_array_chessboard[i][z] == "T"
&& (
new_x == z || new_y == i // Horizontal and vertical checks
)
) {
return false;
}
}
}
return true;
}
placeTower(number_of_towers, array_array_chessboard);
return this;
}
// <!-- CHANGE THESE PARAMETERS' VALUE TO TEST -->
nTowersSolutions(2, 2, 2).displaySolutions();
</script>
</body>
</html>
Your problem is most likely that there is only one (two dimentional) array, which is global, so in the end your solutions are all pointing to the same array which will be the last state it had before our recursive function completely returned.
array_array_chessboard[current_x][current_y] = "T";
placeTower(number_of_left_towers - 1, array_array_chessboard);
array_array_chessboard[current_x][current_y] = "-";
If I understand the above correctly, you are (looping over all positions, ish)
1) assigning T to a location
2) solving all boards with T in that location
3) assigning "-" to the previous location
so in the end you have an array full of "-", and all solutions point to this same array
Try replacing
return solutions.push(array_array_chessboard);
by
return solutions.push(JSON.decode(JSON.encode(array_array_chessboard)));
The above will make a deep copy of your solution, and while it might not be the utmost efficient way to make a deep copy it is a simple one. If your algorithm needs to be really fast you might want to look in a faster way to clone your solution.
Though I can't guarantee that this will work since I can't run your fiddle
(also for readability I suggest you write your return like so:)
solutions.push(JSON.parse(JSON.stringify(array_array_chessboard)));
return;
EDIT: why to use JSON.parse+stringify over Array::from:
if you simply do
solutions.push(Array.from(array_array_chessboard));
The second dimention will still reference the same arrays, and that is where your string data is stored after all.
to demonstrate (note that you need to polyfill the Array.from in IE, or simply try on a different browser):
var arr1 = ["a"];
var arr2 = ["b"];
var metaArr = [arr1, arr2];
console.log(metaArr[0][0], metaArr[1][0]); // "a b"
var metaArrClone = Array.from(metaArr);
var metaArrClone[0][0] = "c";
console.log(metaArrClone[0][0]); // "c"
console.log(metaArr[0][0]); // "c"
var metaArrClone2 = JSON.parse(JSON.stringify(metaArr));
console.log(metaArrClone2[0][0]); // "c"
metaArrClone2[0][0] = "d";
console.log(metaArrClone2[0][0]); // "d"
console.log(metaArr[0][0]); // "c"
You do not need to keep the solutions outside your recursive function. Could be better if you keep the solutions inside your recursive function and than return all the solutions, so you do not need to worry about the state outside the function.
Of course if you have to use the finded solutions before that the recursive function returns (maybe you have a big cheesboard) your solution could be better. Or you could use a generator.
Could be also good keep this kind of logic separate from the ui so first focus on the solution and then try to draw the result in the browser or where you want, or do the opposite.
You can start from the code below, but please check if it actually finds all the solutions before use it.
'use strict'
/* Finds all the solutions to the N-Towers algorithm.
*
* #param number_of_towers number of towers to try to place in the chessboard
* #param number_of_lines chessboard's ones
* #param number_of_columns chessboard's ones
* #returns {nTowersSolutions} array containing all the solutions
* "Tower" = presence of a tower in this square of the chessboard
* "Nothing" = no tower in this square of the chessboard
* "Blocked" = the cell is blocked
*/
function nTowersSolutions(number_of_towers, number_of_lines, number_of_columns) {
var chessboard = _initChessboard(number_of_lines, number_of_columns);
var solutions = _findAllSolution(chessboard, number_of_towers);
return solutions;
}
// nuber, * -> array
var _newArrFromLenAndElement = function(length, element) {
return Array.apply(null, Array(length)).map(function(){ return element; });
};
// number, number -> cheesboard
var _initChessboard = function(number_of_lines, number_of_columns) {
var oneColumnChessboard = _newArrFromLenAndElement(number_of_lines, []);
var chessboard = oneColumnChessboard.map(function() {
var line = _newArrFromLenAndElement(number_of_columns, 'Nothing');
return line;
});
return chessboard;
};
// chessboard, line_index, column_index -> chessboard
var _placeTower = function(chessboard, line_index, column_index) {
var ff = chessboard.map(function(line, index) {
if (index === line_index) {
return line.map(function() { return 'Blocked'; });
}
else {
return line.map(function(x, index){
if(index===column_index){return'Blocked';}
else{return x;}
});
}
});
ff[line_index][column_index] = 'Tower';
return ff;
};
// array[][] -> array[]
var _flatten = function(arr) {
return [].concat.apply([], arr);
};
// *, array -> boolean
var _isInArray = function(value, array) {
return array.indexOf(value) > -1;
};
// cheesboard, numberm number -> array
// this could be a bruteforce recursive solution at your problem ( actually you have to check if
// it is correct )
// we need _lines_done for don't look for solutions already finded (if you have tried all the
// pattern with a tower in the first line you don't want try to place a tower in the first line)
var _findAllSolution = function(chessboard, number_of_towers, _lines_done, _deep) {
_lines_done = _lines_done || [];
_deep = _deep || 0;
if (number_of_towers === 0){
return chessboard;
}
//for all the cells in the ceesboard try to place a tower
var solutions = chessboard.map(function(line, line_index) {
var solution = line.map(function(cell, column_index) {
if (_isInArray(line_index, _lines_done)) {
return 'alreadyVisitedLine';
}
else if (cell === 'Nothing') {
var fulfilledChessboard = _placeTower(chessboard, line_index, column_index);
if (line_index > 0 && _deep === 0 && !(_isInArray(line_index - 1, _lines_done))) {
_lines_done.push(line_index - 1);
}
return _findAllSolution(fulfilledChessboard, number_of_towers -1, _lines_done, _deep + 1);
}
else {
return 'DeadEnd!';
}
});
return _flatten(solution);
});
var flatSolutions = _flatten(solutions);
//you should .filter the solutions
return _flatten(solutions);
};
var h = nTowersSolutions(2,2,2)
console.log(h)

Alternative or polyfill for Array.from on the Internet Explorer

I have a problem with my Angular App on the Internet Explorer. It runs everywhere without a problem (Chrome, Mozilla, Edge), but NOT on the IE.
I have analyzed with the Developer Explorer where the error is and it returned that the error occurs on the following line:
myDataSet[index - 1].data = Array.from(tmp);
Where this is the following error message I am getting:
Object does not support property or method from at Anonymous function....(etc.)
What I am doing there is that I have a Set() named tmp which contains the following data:
Afterwards I am simply creating a simple array object from this Set.
How can I solve this problem?
EDIT
Based on the recommendations I have added the following code to my App:
if (!Array.from) {
Array.from = (function () {
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) { return 0; }
if (number === 0 || !isFinite(number)) { return number; }
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
Array.from not supported in the following document modes: Quirks, Internet Explorer 6 standards, Internet Explorer 7 standards, Internet Explorer 8 standards, Internet Explorer 9 standards, Internet Explorer 10 standards, Internet Explorer 11 standards. Not supported in Windows 8.1 (compatibility reference)
Just add the code below to your page (JS code was copied from developer.mozilla.org). It will emulate an ES6's Array.from method.
Array.from was added to the ECMA-262 standard in the 6th edition; as
such it may not be present in other implementations of the standard.
You can work around this by inserting the following code at the
beginning of your scripts, allowing use of Array.from in
implementations that don't natively support it. This algorithm is
exactly the one specified in ECMA-262, 6th edition, assuming Object
and TypeError have their original values and that callback.call
evaluates to the original value of Function.prototype.call. In
addition, since true iterables can not be polyfilled, this
implementation does not support generic iterables as defined in the
6th edition of ECMA-262.
if (!Array.from) {
Array.from = (function () {
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) { return 0; }
if (number === 0 || !isFinite(number)) { return number; }
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
I faced the same issue. Looked at the polyfill and it is threatening huge. Here is 2 lines short solution.
The OP basically needs to create simple array from his array-like object. I used to my taste the most efficient 2 lines plain for loop (I had to make array from HTML DOM nodelist array-like object, same applicable to JavaScript arguments object).
For the OP's case it could sound this way:
var temp_array = [],
length = tmp.length;
for (var i = 0; i < length; i++) {
temp_array.push(tmp[i]);
}
// Here you get the normal array "temp_array" containing all items
// from your `tmp` Set.
Make it separate function and you get 3 lines universal reusable solution for the IE<9 case.
Here is how the separate function may look like:
/**
* #param arr The array | array-like data structure.
* #param callback The function to process each element in the 'arr'.
* The callback signature and usage is assumed similar to the
* native JS 'forEach' callback argument usage.
*/
function customEach(arr, callback) {
'use strict';
var l = arr.length;
for (var i = 0; i < l; i++) {
callback(arr[i], i, arr);
}
};
PS: here is the relevant description for forEach callback to see how to use the customEach callback.
While it's not supported on IE, you may use the polyfill from MDN.
You could use slice.call for array-like objects. This means your code would read:
myDataSet[index - 1].data = [].slice.call(tmp);
The first CORRECT answer here and for MDN
Original poster of question wrote:
What I am doing there is that I have a Set() named tmp ...
But we have here 3 answers for already more than 4 years and nobody has tested it with a Set object.The huge polyfill from MDN does not work with Set object!
It was copied from MDN and pasted into the accepted answer without testing.
My polyfill solution is also much shorter than incorrect and huge polyfill from MDN.
In the following solution you will find the explanation about the function(s) and their parameters in comments.
/**
* #param "arr" (required) - array-like or iterable object to convert it to an array.
* #param "callbackFn" (optional) - function to call on every element of the array.
* #param "thisArg" (optional) - value to use as this when executing callback
* Return value - new Array instance
*
* The callbackFn argument usage is like in Array.map() callback.
* The callbackFn function accepts the following arguments:
* #param "currentValue" (required) - the current element being processed in the array.
* #param "index" (optional) - the index of the current element being processed in the array.
* #param "array" (optional) - he array map was called upon.
* Callback function that is called for every element of "arr". Each time callback executes, the returned value is added to new array ("arNew").
*/
function arrayFrom(arr, callbackFn, thisArg)
{
//if you need you can uncomment the following line
//if(!arr || typeof arr == 'function')throw new Error('This function requires an array-like object - not null, undefined or a function');
var arNew = [],
k = [], // used for convert Set to an Array
i = 0;
//if you do not need a Set object support then
//you can comment or delete the following if statement
if(window.Set && arr instanceof Set)
{
//we use forEach from Set object
arr.forEach(function(v){k.push(v)});
arr = k
}
for(; i < arr.length; i++)
arNew[i] = callbackFn
? callbackFn.call(thisArg, arr[i], i, arr)
: arr[i];
return arNew
}
//You could also use it without the following line, but it is not recommended because native function is faster.
Array.from = Array.from || arrayFrom; //We set it as polyfill
//HOW TO USE IT:
function myCallback1(x){return x+x}
function myCallback2(o){return o.innerHTML}
var str = 'Super!',
array = str.split(''),//['S','u','p','e','r','!']
arrayLike1 = window.Set ? new Set(str) : array, //array for IE < 10. Only 11 version of IE supports Set.
arrayLike2 = document.querySelectorAll('b');//NodeList
arrayLike3 = document.getElementsByTagName('b');//HTMLCollection
console.log(arrayFrom(str).join(','));//S,u,p,e,r,!
console.log(arrayFrom(array).join(','));//S,u,p,e,r,!
console.log(arrayFrom(str, myCallback1).join(','));//SS,uu,pp,ee,rr,!!
console.log(arrayFrom(arrayLike1, myCallback1).join(','));//SS,uu,pp,ee,rr,!!
console.log(arrayFrom(arrayLike2, myCallback2).join(','));//aaa,bbb
console.log(arrayFrom(arrayLike3, myCallback2).join(','));//aaa,bbb
//You can also use it as polyfill:
console.log(Array.from(str).join(','));//S,u,p,e,r,!
<b>aaa</b> <b>bbb</b>
Do not forget that a Set has unique values! For example:
//Only 11 version of IE and all modern browsers support Set.
var ar = [];
new Set('mama').forEach(function(v){ar.push(v)});
console.log(ar.join(',')); // 'm','a'
While it is not a full polyfill, the following has worked for all my use cases so far:
Array.from||(Array.from=function(ar){return Array.prototype.slice.call(ar)});
If found this years ago on MDN, but they have removed it since.

Is there a preferred method of transferring 'intentionally generic' String or Array JavaScript methods to other objects?

In the midst of doing some debugging, I came upon a situation in which it would be preferable to generalize the toUpperCase method. Here are a few ways that I came up with:
//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.push("a");
foo.toUpperCase();
//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.push("a");
toUpperCase(foo);
//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.push("a");
foo.toUpperCase();
//Constructor Prototype
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.push("a");
foo.constructor();
//toString override
var foo = [];
foo.push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();
Most of the String and Array methods have this disclaimer in the spec:
Therefore, it can be transferred to other kinds of objects for use as a method.
Is there a conventional approach to implementing this type of abstraction?
In case you need toUpperCase function to work only with arrays then you could extend Array class like that:
Array.prototype.toUpperCase = function () {
return String(this).toUpperCase();
};
After that you can write:
var foo = [];
foo.push('a');
foo.toUpperCase();
You don't need to do anything:
String.prototype.toUpperCase.call(anyObject)
If you want to shorten that, you'll need to bind call to toUpperCase:
var toUpperCase = Function.call.bind(String.prototype.toUpperCase);
Static methods attached to a global object is the previously implemented solution (less code, more data).
Generic methods have to be written so that they require this to only have a minimal set of methods. For example, most generic array methods only need this to provide length and indexed access.
There's a drawback when using Array.prototype.slice to convert arguments in an array: it prevents the browser JavaScript engine from performing optimizations
The recommended approach is to use Array.slice generic method – Array generic methods are specially implemented to be used on other types – but it is not a part of the ECMAScript specification
/**
* Implementation of standard Array methods (introduced in ECMAScript 5th
* edition) and shorthand generics (JavaScript 1.8.5)
*
* Copyright (c) 2013 Alex K #plusdude
* http://opensource.org/licenses/MIT
*/
(function (global, infinity, undefined) {
/*jshint bitwise:false, maxlen:95, plusplus:false, validthis:true*/
"use strict";
/**
* Local references to constructors at global scope.
* This may speed up access and slightly reduce file size of minified version.
*/
var Array = global.Array;
var Object = global.Object;
var Math = global.Math;
var Number = global.Number;
/**
* Converts argument to an integral numeric value.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
*/
function toInteger(value) {
var number;
// let number be the result of calling ToNumber on the input argument
number = Number(value);
return (
// if number is NaN, return 0
number !== number ? 0 :
// if number is 0, Infinity, or -Infinity, return number
0 === number || infinity === number || -infinity === number ? number :
// return the result of computing sign(number) * floor(abs(number))
(0 < number || -1) * Math.floor(Math.abs(number))
);
}
/**
* Returns a shallow copy of a portion of an array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.10
*/
function slice(begin, end) {
/*jshint newcap:false*/
var result, elements, length, index, count;
// convert elements to object
elements = Object(this);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// calculate begin index, if is set
if (undefined !== begin) {
// convert to integer
begin = toInteger(begin);
// handle -begin, begin > length
index = 0 > begin ? Math.max(length + begin, 0) : Math.min(begin, length);
} else {
// default value
index = 0;
}
// calculate end index, if is set
if (undefined !== end) {
// convert to integer
end = toInteger(end);
// handle -end, end > length
length = 0 > end ? Math.max(length + end, 0) : Math.min(end, length);
}
// create result array
result = new Array(length - index);
// iterate over elements
for (count = 0; index < length; ++index, ++count) {
// current index exists
if (index in elements) {
// copy current element to result array
result[count] = elements[index];
}
}
return result;
}
/**
* Returns the first index at which a given element
* can be found in the array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.14
*/
function indexOf(target, begin) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// calculate begin index, if is set
if (undefined !== begin) {
// convert to integer
begin = toInteger(begin);
// handle -begin, begin > length
index = 0 > begin ? Math.max(length + begin, 0) : Math.min(begin, length);
} else {
// default value
index = 0;
}
// iterate over elements
for (; index < length; ++index) {
// current index exists, target element is equal to current element
if (index in elements && target === elements[index]) {
// break loop, target element found
return index;
}
}
// target element not found
return -1;
}
/**
* Returns the last index at which a given element
* can be found in the array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.15
*/
function lastIndexOf(target, begin) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// calculate begin index, if is set
if (undefined !== begin) {
// convert to integer
begin = toInteger(begin);
// handle -begin, begin > length - 1
index = 0 > begin ? length - Math.abs(begin) : Math.min(begin, length - 1);
} else {
// default value
index = length - 1;
}
// iterate over elements backwards
for (; -1 < index; --index) {
// current index exists, target element is equal to current element
if (index in elements && target === elements[index]) {
// break loop, target element found
return index;
}
}
// target element not found
return -1;
}
/**
* Executes a provided function once per array element.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18
*/
function forEach(callback, scope) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements) {
// execute callback
callback.call(scope, elements[index], index, elements);
}
}
}
/**
* Tests whether all elements in the array pass the test
* implemented by the provided function.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.16
*/
function every(callback, scope) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements &&
// callback returns false
!callback.call(scope, elements[index], index, elements)) {
// break loop, test failed
return false;
}
}
// test passed, controversy began..
return true;
}
/**
* Tests whether some element in the array passes the test
* implemented by the provided function.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.17
*/
function some(callback, scope) {
/*jshint newcap:false*/
var elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements &&
// callback returns true
callback.call(scope, elements[index], index, elements)) {
// break loop, test passed
return true;
}
}
// test failed
return false;
}
/**
* Creates a new array with all elements that pass the test
* implemented by the provided function.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.20
*/
function filter(callback, scope) {
/*jshint newcap:false*/
var result = [], elements, length, index, count;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = count = 0; index < length; ++index) {
// current index exists
if (index in elements &&
// callback returns true
callback.call(scope, elements[index], index, elements)) {
// copy current element to result array
result[count++] = elements[index];
}
}
return result;
}
/**
* Creates a new array with the results of calling a provided function
* on every element in this array.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.19
*/
function map(callback, scope) {
/*jshint newcap:false*/
var result = [], elements, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements) {
// copy a return value of callback to result array
result[index] = callback.call(scope, elements[index], index, elements);
}
}
return result;
}
/**
* Apply a function against values of the array (from left-to-right)
* as to reduce it to a single value.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21
*/
function reduce(callback, value) {
/*jshint newcap:false*/
var elements, isset, length, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// status of the initial value
isset = undefined !== value;
// convert length to unsigned 32 bit integer
length = elements.length >>> 0;
// iterate over elements
for (index = 0; index < length; ++index) {
// current index exists
if (index in elements) {
// initial value is set
if (isset) {
// replace initial value with a return value of callback
value = callback(value, elements[index], index, elements);
} else {
// current element becomes initial value
value = elements[index];
// status of the initial value
isset = true;
}
}
}
// make sure the initial value exists after iteration
requireValue(isset);
return value;
}
/**
* Apply a function against values of the array (from right-to-left)
* as to reduce it to a single value.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.22
*/
function reduceRight(callback, value) {
/*jshint newcap:false*/
var elements, isset, index;
// convert elements to object
elements = Object(this);
// make sure callback is a function
requireFunction(callback);
// status of the initial value
isset = undefined !== value;
// index of the last element
index = (elements.length >>> 0) - 1;
// iterate over elements backwards
for (; -1 < index; --index) {
// current index exists
if (index in elements) {
// initial value is set
if (isset) {
// replace initial value with a return value of callback
value = callback(value, elements[index], index, elements);
} else {
// current element becomes initial value
value = elements[index];
// status of the initial value
isset = true;
}
}
}
// make sure the initial value exists after iteration
requireValue(isset);
return value;
}
/**
* Returns true if an argument is an array, false if it is not.
* #see http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.3.2
*/
function isArray(value) {
return "[object Array]" === Object.prototype.toString.call(value);
}
/**
* Tests if an argument is callable and throws an error if it is not.
* #private
*/
function requireFunction(value) {
if ("[object Function]" !== Object.prototype.toString.call(value)) {
throw new Error(value + " is not a function");
}
}
/**
* Throws an error if an argument can be converted to true.
* #private
*/
function requireValue(isset) {
if (!isset) {
throw new Error("reduce of empty array with no initial value");
}
}
/**
* Tests implementation of standard Array method.
* #private
*/
function supportsStandard(key) {
var support = true;
// a method exists
if (Array.prototype[key]) {
try {
// apply dummy arguments
Array.prototype[key].call(undefined, /test/, null);
// passed? implemented wrong
support = false;
} catch (e) {
// do nothing
}
} else {
support = false;
}
return support;
}
/**
* Tests implementation of generic Array method.
* #private
*/
function supportsGeneric(key) {
var support = true;
// a method exists
if (Array[key]) {
try {
// apply dummy arguments
Array[key](undefined, /test/, null);
// passed? implemented wrong
support = false;
} catch (e) {
// do nothing
}
} else {
support = false;
}
return support;
}
/**
* Assigns method to Array constructor.
* #private
*/
function extendArray(key) {
if (!supportsGeneric(key)) {
Array[key] = createGeneric(key);
}
}
/**
* Creates generic method from an instance method.
* #private
*/
function createGeneric(key) {
/** #public */
return function (elements) {
var list;
if (undefined === elements || null === elements) {
throw new Error("Array.prototype." + key + " called on " + elements);
}
list = Array.prototype.slice.call(arguments, 1);
return Array.prototype[key].apply(elements, list);
};
}
/**
* Assign ECMAScript-5 methods to Array constructor,
* and Array prototype.
*/
var ES5 = {
"indexOf": indexOf,
"lastIndexOf": lastIndexOf,
"forEach": forEach,
"every": every,
"some": some,
"filter": filter,
"map": map,
"reduce": reduce,
"reduceRight": reduceRight
};
for (var key in ES5) {
if (ES5.hasOwnProperty(key)) {
if (!supportsStandard(key)) {
Array.prototype[key] = ES5[key];
}
extendArray(key);
}
}
Array.isArray = Array.isArray || isArray;
/**
* Assign ECMAScript-3 methods to Array constructor.
* The toString method is omitted.
*/
[
"concat",
"join",
"slice",
"pop",
"push",
"reverse",
"shift",
"sort",
"splice",
"unshift"
].forEach(extendArray);
/**
* Test the slice method on DOM NodeList.
* Support: IE < 9
*/
/*jshint browser:true*/
if (document) {
try {
Array.slice(document.childNodes);
} catch (e) {
Array.prototype.slice = slice;
}
}
}(this, 1 / 0));
/*globals define*/
// Assumes all supplied String instance methods already present
// (one may use shims for these if not available)
(function() {
'use strict';
var i,
// We could also build the array of methods with the following, but the
// getOwnPropertyNames() method is non-shimable:
// Object.getOwnPropertyNames(String).filter(function(methodName) {
// return typeof String[methodName] === 'function';
// });
methods = [
'quote', 'substring', 'toLowerCase', 'toUpperCase', 'charAt',
'charCodeAt', 'indexOf', 'lastIndexOf', 'startsWith', 'endsWith',
'trim', 'trimLeft', 'trimRight', 'toLocaleLowerCase',
'toLocaleUpperCase', 'localeCompare', 'match', 'search',
'replace', 'split', 'substr', 'concat', 'slice'
],
methodCount = methods.length,
assignStringGeneric = function(methodName) {
var method = String.prototype[methodName];
String[methodName] = function(arg1) {
return method.apply(arg1, Array.prototype.slice.call(arguments, 1));
};
};
for (i = 0; i < methodCount; i++) {
assignStringGeneric(methods[i]);
}
}());
References
MDN: Array Generic Methods
MDN: String Generic Methods
array.generics.js
Mastering the Arcane Art of JavaScript-mancy for C# Developers - Chapter 4: More Useful Function Patterns - Multiple Arguments

Categories