class: center, middle # Working with `Array`s and `Object`s in modern JavaScript --- # About Me
### Mohsen Azimi #### Software Engineer @Apigee * Github: [`@mohsen1`](https://github.com/mohsen1) * Twitter: [`@mohsen____`](https://twitter.com/mohsen____) * Website: [`azimi.me`](http://azimi.me) --- # `Array` ### ES5 * `Array.isArray` * `Array.prototype.map` * `Array.prototype.reduce` * `Array.prototype.filter` * `Array.prototype.every` * `Array.prototype.some` ### ES6 * `Array.of` * `Array.from` * `Array.prototype.find` * `Array.prototype.entries` * `Array.prototype.keys` --- # `Array.isArray` > The `Array.isArray()` method returns true if an object is an array, false if it is not. ```js Array.isArray([]) // true Array.isArray({length: 100}) // false ``` ```js function Foo(){}; Foo.prototype = new Array(); var f = new Foo(); f.push('a'); Array.isArray(f); // false ``` --- # `Array.isArray` ### Polyfill
✔
```js if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } ``` ### Browsers * IE9+ * Safari 5 --- # `Array.prototype.map` > The `map()` method creates a new array with the results of calling a provided function on every element in this array. ```js var sizes = [ {width: 10, height: 20}, {width: 4, height: 15}, {width: 30, height: 20} ]; var areas = sizes.map(function (item) { return item.width * item.height; }); // areas => [200, 60, 600] ``` --- # `Array.prototype.map` ```js var rows = [ document.querySelector('.row-0'), document.querySelector('.row-1'), document.querySelector('.row-2'), document.querySelector('.row-3'), document.querySelector('.row-4'), document.querySelector('.row-5') ]; var highlightedRows = rows.map(function (row, index) { if (index % 2 === 0) { row.classList.add('even'); } return row; }); ``` ```html
``` --- # `Array.prototype.map` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill) ### Browsers * IE9+ --- # `Array.prototype.reduce` > The `reduce()` method applies a function against an accumulator and each value of the array (from left-to-right) has to reduce it to a single value. ```js var sizes = [ {width: 10, height: 20}, {width: 4, height: 15}, {width: 30, height: 20}, {width: 9, height: 15} ]; var totalArea = sizes .map(function (item) { return item.width * item.height; }) .reduce(function (memory, item) { return memory + item; }, 0); // totalArea => 995 ``` --- # `Array.prototype.reduce` ```js var people = [ {name: 'Mike', age: 21}, {name: 'Tracy', age: 23}, {name: 'Noah', age: 29} ]; var namesAndAges = people.reduce(function (soFar, person) { soFar[person.name] = person.age; return soFar; }, {}); ``` ```js namesAndAges // => { Mike: 21, Tracy: 23, Noah: 29 } ``` --- # `Array.prototype.reduce` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Polyfill) ### Browsers * IE9+ --- # `Array.prototype.filter` > The `filter()` method creates a new array with all elements that pass the test implemented by the provided function. ```js var students = [ {name: 'Noah', score: 2.1}, {name: 'Liam', score: 3.2}, {name: 'Jacob', score: 1.8}, {name: 'Mason', score: 3.4}, {name: 'Ella', score: 3.9} ]; var passed = students.filter(function (student) { return student.score >= 3; }); ``` ```js [ {name: 'Liam', score: 3.2}, {name: 'Mason', score: 3.4}, {name: 'Ella', score: 3.9} ] ``` --- # `Array.prototype.filter` ```js ['0', 0, 1, 2, undefined, 3, null, 4, null, 5].filter(Number); // => [1, 2, 3, 4, 5] ``` ```js ['/login', 'http://www.example.com'].map(encodeURIComponent) // => ["%2Flogin", "http%3A%2F%2Fwww.example.com"] ``` ```js [ [1], [2], 3, [4] ] .filter(Array.isArray) // => [ [1], [2], [4]] ``` --- # `Array.prototype.filter` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Polyfill) ### Browsers * IE9+ --- # `Array.prototype.every` > The `every()` method tests whether all elements in the array pass the test implemented by the provided function. ```js var checkboxes = [ document.querySelector('.checkbox-1'), document.querySelector('.checkbox-2'), document.querySelector('.checkbox-3'), document.querySelector('.checkbox-4') ]; var isAllChecked = checkboxes.every(function (checkbox) { return checkbox.checked; }); ``` [JSBin](http://jsbin.com/joxeze/1/edit?html,js,output) --- # `Array.prototype.every` ```js if ([file1, file2, file2].every(fs.existsSync)) { // do things } ``` --- # `Array.prototype.every` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every#Polyfill) ### Browsers * IE9+ --- # `Array.prototype.some` > The `some()` method tests whether some element in the array passes the test implemented by the provided function. ```js var classes = [ {name: 'Physics', score: 85}, {name: 'Math', score: 73}, {name: 'History', score: 58} ]; var failed = classes.some(function (item) { return item.score < 60; }); ``` [JSBin](http://jsbin.com/yobuxo/1/edit?html,js,output) --- #`Array.prototype.some` ```js var inputs = [ document.querySelector('.field-1'), document.querySelector('.field-2'), document.querySelector('.field-3') ]; var hasEmptyField = inputs.some(function (input) { return input.value === ''; }); ``` --- #`Array.prototype.some` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some#Polyfill) ### Browsers * IE9+ --- class: center, middle # New `Array` methods in ES6 --- #`Array.of` > The `Array.of()` method creates a new Array instance with a variable number of arguments, regardless of number or type of the arguments. ```js Array.of(1); // => [1] Array.of(1,2,3); // => [1,2,3] ``` --- #`Array.of` ### Polyfill
✔
```js if (!Array.of) { Array.of = function() { return Array.prototype.slice.call(arguments); }; } ``` ### Browsers * Chrome 39+ * Firefox 25+ --- #`Array.from` > The `Array.from()` method creates a new Array instance from an array-like or iterable object. ```js function addAll() { return Array.from(arguments).reduce((m, i) => m + i , 0); } ``` ```js function doMath(operator, ...rest) { return Array.from(rest).reduce((mem, item) => { switch (operator) { case '+': return mem + item; break; case '-': return mem - item; break; default: return undefined; } }, 0) } ``` --- #`Array.from` ### Using with `Map` ```js const map = new Map(); map.set(window, 'win'); map.set(document, 'doc'); Array.from(map); // => [[window, 'win'], [document, 'doc']] ``` --- #`Array.from` ### Using with `Set` ```js const set = new Set(); set.add(1); set.add(2); Array.from(set); // => [1, 2]; Array.from(set).reduce(/*...*/); ``` --- #`Array.from` ### Using with `String` ```js Array.from('1 2 3 4').filter(Number).map(Number) // => [1, 2, 3, 4] ``` ```js 'Yes! 👍'.split('') // => ["Y", "e", "s", "!", " ", "�", "�"] Array.from('Yes! 👍') // => [ "Y", "e", "s", "!", " ", "👍" ] ``` --- #`Array.from` ### Using with DOM elements ```js Array.from(document.querySelectorAll('input[type="checkbox"]')) .filter(el => el.checked) ``` ```js myElement.addEventListener('click', event => { Array.from(event.target.childNodes).forEach(child => { child.classList.toggle('active'); }); }) ``` --- #`Array.from` ### Using with iterators ```js function* gen(){ yield 1; yield 2; yield 3; } Array.from(gen()); // => [1, 2, 3] ``` --- #`Array.from` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Polyfill) ### Browsers * Firefox 32 --- #`Array.prototype.find` > The `find()` method returns a value in the array, if an element in the array satisfies the provided testing function. Otherwise `undefined` is returned. ```js const students = [ {name: 'Noah', score: 2.1}, {name: 'Liam', score: 3.2}, {name: 'Jacob', score: 1.8}, {name: 'Mason', score: 3.4}, {name: 'Ella', score: 1.9} ]; const weakStudent = students.find(student => student.score < 2); // => {name: 'Jacob', score: 1.8} ``` --- #`Array.prototype.find` ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find#Polyfill) ### Browsers * Firefox 25 --- #`Array.prototype.entries` > The `entries()` method returns a new Array Iterator object that contains the key/value pairs for each index in the array. ```js const arr = [1, 2, 3]; const arrIterator = arr.entries(); let nextVal while (nextVal = arrIterator.next().value) { console.log(nextVal); } ``` ```js [0, 1] [1, 2] [2, 3] ``` --- #`Array.prototype.entries` ```js const arr = ['a', 'b', 'c']; for (let item of arr) { console.log(item); // => 'a' 'b' 'c' } for (let pair of arr.entries()) { console.log(pair); // => [0, 'a'] [1, 'b'] [2, 'c'] } ``` ```js for (let pair of theArray.entries()) { if (pair[0] % 197 === 0) { console.log(pair[1]); } } ``` --- #`Array.prototype.entries` ### Polyfill
✔
### Browsers * Firefox 28 * Chrome 38 --- #`Array.prototype.keys` > The `keys()` method returns a new Array Iterator that contains the keys for each index in the array. ```js const arr = [1, 2, 3]; const arrIterator = arr.keys(); let nextVal while (nextVal = arrIterator.next().value) { console.log(nextVal); } ``` ```js 1 2 3 ``` --- #`Array.prototype.keys` ### Polyfill
✔
### Browsers * Firefox 28 * Chrome 38 --- #`Object` ### ES5 * `Object.create` * `Object.defineProperty` and `Object.defineProperties` * `Object.keys` * `Object.preventExtensions` * `Object.seal` and `Object.freeze` ### ES6 * `Object.is` * `Object.assign` * `Object.observe` --- #`Object.create` > The Object.create() method creates a new object with the specified prototype object and properties. ```js var empty = Object.create(null); empty.toString // => undefined ``` ```js var o = Object.create(Object, { p: { value: 42, writable: true, enumerable: true, configurable: true } }); ``` --- #`Object.create` ### Polyfill
✔
```js Object.create = (function() { var Temp = function() {}; return function (prototype) { if (arguments.length > 1) { throw Error('Second argument not supported'); } if (typeof prototype != 'object') { throw TypeError('Argument must be an object'); } Temp.prototype = prototype; var result = new Temp(); Temp.prototype = null; return result; }; })(); ``` ### Browsers * IE9+ --- # `Object.defineProperty` and `Object.defineProperties` > The `Object.defineProperties()` method defines new or modifies existing properties directly on an object, returning the object. > The `Object.defineProperty()` method defines a new property directly on an object, or modifies an existing property on an object, and returns the object. --- # `Object.defineProperty` and `Object.defineProperties` ### `writable` ```js var o = {}; Object.defineProperty(o, 'a', { value: 37, writable: false }); o.a = 11 o.a // => 37 delete o.a o.a // => 37 ``` --- # `Object.defineProperty` and `Object.defineProperties` ### `writable` ```js Object.defineProperty(o, 'a', {value: 1000}); // => TypeError: Cannot redefine property: a Object.defineProperty(o, 'a', {configurable: true}); // => TypeError: Cannot redefine property: a Object.defineProperty(o, 'a', {configurable: false}); // no issues. you can make non-writable properties non-configurable ``` --- # `Object.defineProperty` and `Object.defineProperties` ### `enumerable` ```js var o = Object.create(HTMLElement); Object.defineProperties(o, { a: {value: 100, enumerable: false}, b: {value: 200, enumerable: true }, c: {value: 300, enumerable: true } }); Object.keys(o); //=> ['b', 'c'] for (var key in o) { console.log(key); // => 'b' 'c' 'toString' ... All HTMLElement enumerable properties } ``` --- # `Object.defineProperty` and `Object.defineProperties` ### `configurable` ```js var o = {}; Object.defineProperty(o, 'a', { value: 100, configurable: false }); Object.defineProperty(o, 'a', {/* any configuration */}); // => throws error ``` --- # `Object.defineProperty` and `Object.defineProperties` ### `configurable` ```js var o = {}; var val; Object.defineProperty(o, 'a', { get: function() { return val; }, set: function(v) { val = v; } configurable: false }); o.a = 10; // o.a => 10 ``` ```js var o = {}; var val; Object.defineProperty(o, 'a', { get: function() { return val; }, set: function(v) { val = v; }, writable: false }); // => TypeError: Invalid property. A property cannot both have accessors and be writable or have a value ``` --- # `Object.defineProperty` and `Object.defineProperties` ### Browsers * IE9 and partially IE8 --- # `Object.preventExtensions` > The `Object.preventExtensions()` method prevents new properties from ever being added to an object ```js var obj = {a: 1}; Object.preventExtensions(obj); obj.b = 2 // silently ignores it obj.b // => undefined ``` --- # `Object.preventExtensions` ```js function Person(name, location) { this.name = name; this.location = location; Object.preventExtensions(this); } var p = new Person('Mohsen', 'San Jose'); p.age = 98; p.age // => undefined ``` --- # `Object.preventExtensions` ### Browsers * IE9 --- # `Object.seal` and `Object.freeze` > The `Object.seal()` method seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable. > The `Object.freeze()` method freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed. In essence the object is made effectively immutable. The method returns the object being frozen. --- # `Object.seal` and `Object.freeze` ```js var obj = {a: 1}; Object.sea(obj); delete obj.a; // won't work obj.b = 2; // won't work obj.a = 10; // works ``` --- # `Object.seal` and `Object.freeze` ```js var obj = {a: 1}; Object.freeze(obj); delete obj.a; // won't work obj.b = 2; // won't work obj.a = 10; // won't work ``` --- # `Object.seal` and `Object.freeze` ### Browsers * IE9 --- class: center, middle # New `Object` methods in ES6 --- # `Object.is` > The `Object.is()` method determines whether two values are the same value. ```js NaN === NaN // => false Object.is(NaN, NaN) // => true ``` ```js function readUserInput(string) { if (Object.is(parseInt(string, 10), NaN)) { window.alert('Bad input'); } } ``` --- # `Object.is` ### Polyfill
✔
```js if (!Object.is) { Object.is = function(v1, v2) { if (v1 === 0 && v2 === 0) { return 1 / v1 === 1 / v2; } if (v1 !== v1) { return v2 !== v2; } return v1 === v2; }; } ``` ### Browsers * Chrome: 30 * Firefox: 22 --- # `Object.assign` > The `Object.assign()` method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object. ```js var obj = { a: 1 }; var copy = Object.assign({}, obj); copy // => {a: 1} ``` ```js Object.assign({a: 1}, {a: 2}); // => {a: 2} ``` --- # `Object.assign` ```js var obj = Object.create({root: 0}, { a: {value: 1, enumerable: true}, b: {value: 2, enumerable: false} }) Object.assign(obj) // => {a: 1} ``` --- ### Polyfill
✔
[`polyfill`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill) ### Browsers * Firefox 34 --- # `Object.observe` (ES7) > The `Object.observe()` method is used for asynchronously observing the changes to an object. It provides a stream of changes in the order in which they occur. ```js var user = { firstName: 'Peter', lastName: 'Griffin' }; Object.observe(user, updateView); function updateView(){ document.querySelector('span.first').innerText = user.firstName; document.querySelector('span.last').innerText = user.lastName; document.querySelector('input.first').value = user.firstName; document.querySelector('input.last').value = user.lastName; } ``` ```html
``` [JSBin](http://jsbin.com/pepuvi/4/edit) --- # `Object.observe` (ES7) ### Limit change types ```js Object.observe(user, updateView, ['update']); // only trigger on update events ``` --- # `Object.observe` (ES7) ### Custom change type using `Object.getNotifier` ```js function Person(name, salary) { this.name = name; var _salary = salary; Object.defineProperty(this, 'salary', { set: function(newVal) { _salary = newVal; Object.getNotifier(this).performChange('salary-change', function() { return this; }) } }) Object.preventExtensions(this); } var tony = new Person('Tony', 1000); Object.observe(tony, alert.bind(this, 'Salary changed!'), ['salary-change']); tony.salary = 1200; // Alert trigged ``` --- # `Object.observe` (ES7) ### Browsers * Chrome 36 --- # Credits ### **MDN** Method descriptions are all cited from [MDN](https://developer.mozilla.org/en-US/) ### **Babel** for most accurate implementation of ES6 and ES7. [Babel](https://babeljs.io/) --- class: center, middle # Questions --- class: center, middle # Thank you! [Slides](http://azimi.me/presentations/objects-arrays-js/index.html#1) [Contact](mailto:me@azimi.me)