Let’s say I have an options variable and I want to set some default value.
What’s is the benefit / drawback of these two alternatives?
Using object spread
options = {...optionsDefault, ...options};
Or using Object.assign
options = Object.assign({}, optionsDefault, options);
This is the commit that made me wonder.
This isn't necessarily exhaustive.
Spread syntax
options = {...optionsDefault, ...options};
Advantages:
If authoring code for execution in environments without native support, you may be able to just compile this syntax (as opposed to using a polyfill). (With Babel, for example.)
Less verbose.
Disadvantages:
When this answer was originally written, this was a proposal, not standardized. When using proposals consider what you'd do if you write code with it now and it doesn't get standardized or changes as it moves toward standardization. This has since been standardized in ES2018.
Literal, not dynamic.
Object.assign()
options = Object.assign({}, optionsDefault, options);
Advantages:
Standardized.
Dynamic. Example:
var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
options = Object.assign.apply(Object, [{}].concat(sources));
// or
options = Object.assign({}, ...sources);
Disadvantages:
More verbose.
If authoring code for execution in environments without native support you need to polyfill.
This is the commit that made me wonder.
That's not directly related to what you're asking. That code wasn't using Object.assign(), it was using user code (object-assign) that does the same thing. They appear to be compiling that code with Babel (and bundling it with Webpack), which is what I was talking about: the syntax you can just compile. They apparently preferred that to having to include object-assign as a dependency that would go into their build.
For reference object rest/spread is finalised in ECMAScript 2018 as a stage 4. The proposal can be found here.
For the most part object assign and spread work the same way, the key difference is that spread defines properties, whilst Object.assign() sets them. This means Object.assign() triggers setters.
It's worth remembering that other than this, object rest/spread 1:1 maps to Object.assign() and acts differently to array (iterable) spread. For example, when spreading an array null values are spread. However using object spread null values are silently spread to nothing.
Array (Iterable) Spread Example
const x = [1, 2, null , 3];
const y = [...x, 4, 5];
const z = null;
console.log(y); // [1, 2, null, 3, 4, 5];
console.log([...z]); // TypeError
Object Spread Example
const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};
console.log(z); //{a: 1, b: 2}
This is consistent with how Object.assign() would work, both silently exclude the null value with no error.
const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);
console.log(z); //{a: 1, b: 2}
I think one big difference between the spread operator and Object.assign that doesn't seem to be mentioned in the current answers is that the spread operator will not copy the the source object’s prototype to the target object. If you want to add properties to an object and you don't want to change what instance it is of, then you will have to use Object.assign.
Edit: I've actually realised that my example is misleading. The spread operator desugars to Object.assign with the first parameter set to an empty object. In my code example below, I put error as the first parameter of the Object.assign call so the two are not equivalent. The first parameter of Object.assign is actually modified and then returned which is why it retains its prototype. I have added another example below:
const error = new Error();
error instanceof Error // true
const errorExtendedUsingSpread = {
...error,
...{
someValue: true
}
};
errorExtendedUsingSpread instanceof Error; // false
// What the spread operator desugars into
const errorExtendedUsingImmutableObjectAssign = Object.assign({}, error, {
someValue: true
});
errorExtendedUsingImmutableObjectAssign instanceof Error; // false
// The error object is modified and returned here so it keeps its prototypes
const errorExtendedUsingAssign = Object.assign(error, {
someValue: true
});
errorExtendedUsingAssign instanceof Error; // true
See also: https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
There's a huge difference between the two, with very serious consequences. The most upvoted questions do not even touch this, and the information about object spread being a proposal is not relevant in 2022 anymore.
The difference is that Object.assign changes the object in-place, while the spread operator (...) creates a brand new object, and this will break object reference equality.
First, let's see the effect, and then I'll give a real-world example of how important it is to understand this fundamental difference.
First, let's use Object.assign:
// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };
// Let's get a reference to the child object;
const childObject = parentObject.childObject;
// Let's change the child object using Object.assign, adding a new `foo` key with `bar` value;
Object.assign(parentObject.childObject, { foo: 'bar' });
// childObject is still the same object in memory, it was changed IN PLACE.
parentObject.childObject === childObject
// true
Now the same exercise with the spread operator:
// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };
// Let's get a reference to the child object;
const childObject = parentObject.childObject;
// Let's change the child object using the spread operator;
parentObject.childObject = {
...parentObject.childObject,
foo: 'bar',
}
// They are not the same object in memory anymore!
parentObject.childObject === childObject;
// false
It's easy to see what is going on, because on the parentObject.childObject = {...} we are cleary assigning the value of the childObject key in parentObject to a brand new object literal, and the fact it's being composed by the old childObject content's is irrelevant. It's a new object.
And if you assume this is irrelevant in practice, let me show a real world scenario of how important it is to understand this.
In a very large Vue.js application, we started noticing a lot of sluggishness when typing the name of the customer in an input field.
After a lot of debugging, we found out that each char typed in that input triggered a hole bunch of computed properties to re-evaluate.
This wasn't anticipated, since the customer's name wasn't used at all in those computeds functions. Only other customer data (like age, sex) was being used. What was goin on? Why was vue re-evaluating all those computed functions when the customer's name changed?
Well, we had a Vuex store that did this:
mutations: {
setCustomer(state, payload) {
// payload being { name: 'Bob' }
state.customer = { ...state.customer, ...payload };
}
And our computed were like this:
veryExpensiveComputed() {
const customerAge = this.$store.state.customer.age;
}
So, voilá! When the customer name changed, the Vuex mutation was actually changing it to a new object entirely; and since the computed relied on that object to get the customer age, Vue counted on that very specific object instance as a dependency, and when it was changed to a new object (failing the === object equality test), Vue decided it was time to re-run the computed function.
The fix? Use Object.assign to not discard the previous object, but to change it in place ...
mutations: {
setCustomer(state, payload) {
// payload being same as above: { name: 'Bob' }
Object.assign(state.customer, payload);
}
BTW, if you are in Vue2, you shouldn't use Object.assign because Vue 2 can't track those object changes directly, but the same logic applies, just using Vue.set instead of Object.assign:
mutations: {
setCustomer(state, payload) {
Object.keys(payload).forEach(key => {
Vue.set(state.customer, key, payload[key])
})
}
NOTE: Spread is NOT just syntactic sugar around Object.assign. They operate much differently behind the scenes.
Object.assign applies setters to a new object, Spread does not. In addition, the object must be iterable.
Copy
Use this if you need the value of the object as it is at this moment, and you don't want that value to reflect any changes made by other owners of the object.
Use it for creating a shallow copy of the object
good practice to always set immutable properties to copy - because mutable versions can be passed into immutable properties, copy will ensure that you'll always be dealing with an immutable object
Assign
Assign is somewhat the opposite to copy.
Assign will generate a setter which assigns the value to the instance variable directly, rather than copying or retaining it.
When calling the getter of an assign property, it returns a reference to the actual data.
I'd like to summarize status of the "spread object merge" ES feature, in browsers, and in the ecosystem via tools.
Spec
https://github.com/tc39/proposal-object-rest-spread
This language feature is a stage 4 proposal, which means it's been merged into the ES language spec, but not yet widely implemented.
Browsers: in Chrome, in SF, Firefox soon (ver 60, IIUC)
Browser support for "spread properties" shipped in Chrome 60, including this scenario.
Support for this scenario does NOT work in current Firefox (59), but DOES work in my Firefox Developer Edition. So I believe it will ship in Firefox 60.
Safari: not tested, but Kangax says it works in Desktop Safari 11.1, but not SF 11
iOS Safari: not teseted, but Kangax says it works in iOS 11.3, but not in iOS 11
not in Edge yet
Tools: Node 8.7, TS 2.1
NodeJS has supported since 8.7 (via Kangax). Confirmed on 9.8 when I tested locally.
TypeScript has suported it since 2.1, current is 2.8
Links
Kangax "property spread"
https://davidwalsh.name/merge-objects
Code Sample (doubles as compatibility test)
var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }
Again: At time of writing this sample works without transpilation in Chrome (60+), Firefox Developer Edition (preview of Firefox 60), and Node (8.7+).
Why Answer?
I'm writing this 2.5 years after the original question. But I had the very same question, and this is where Google sent me. I am a slave to SO's mission to improve the long tail.
Since this is an expansion of "array spread" syntax I found it very hard to google, and difficult to find in compatibility tables. The closest I could find is Kangax "property spread", but that test doesn't have two spreads in the same expression (not a merge). Also, the name in the proposals/drafts/browser status pages all use "property spread", but it looks to me like that was a "first principal" the community arrived at after the proposals to use spread syntax for "object merge". (Which might explain why it is so hard to google.) So I document my finding here so others can view, update, and compile links about this specific feature. I hope it catches on. Please help spread the news of it landing in the spec and in browsers.
Lastly, I would have added this info as a comment, but I couldn't edit them without breaking the authors' original intent. Specifically, I can't edit #ChillyPenguin's comment without it losing his intent to correct #RichardSchulte. But years later Richard turned out to be right (in my opinion). So I write this answer instead, hoping it will gain traction on the old answers eventually (might take years, but that's what the long tail effect is all about, after all).
As others have mentioned, at this moment of writing, Object.assign() requires a polyfill and object spread ... requires some transpiling (and perhaps a polyfill too) in order to work.
Consider this code:
// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);
// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);
These both produce the same output.
Here is the output from Babel, to ES5:
var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);
This is my understanding so far. Object.assign() is actually standardised, where as object spread ... is not yet. The only problem is browser support for the former and in future, the latter too.
Play with the code here
Hope this helps.
The object spread operator (...) doesn't work in browsers, because it isn't part of any ES specification yet, just a proposal. The only option is to compile it with Babel (or something similar).
As you can see, it's just syntactic sugar over Object.assign({}).
As far as I can see, these are the important differences.
Object.assign works in most browsers (without compiling)
... for objects isn't standardized
... protects you from accidentally mutating the object
... will polyfill Object.assign in browsers without it
... needs less code to express the same idea
Too many wrong answers...
I did a search and found a lot of misinformation on this.
Summary
Neither ...spread nor Object.assign is faster. It depends.
Object.assign is almost always faster if side-effects/object mutation is not a consideration.
Performance aside, there is generally no up/downside to using either, until you reach edge cases, such copying objects containing getters/setters, or read-only properties. Read more here.
Performance
Whether Object.assign or ...spread is faster, it depends on what you are trying to combine, and the runtime you are using (implementations and optimisations happen from time to time). For small objects, it does not matter.
For bigger objects, Object.assign is usually better. Especially if you do not need to care about side-effects, the speed gains come from saving time by just adding properties to the first object, rather than copying from two and creating a brand new one. See:
async function retrieveAndCombine() {
const bigPayload = await retrieveData()
const smallPayload = await retrieveData2()
// only the properties of smallPayload is iterated through
// whereas bigPayload is mutated.
return Object.assign(bigPayload, smallPayload)
}
If side-effects is a concern
In cases where side-effects matters, such as if an object to be combined with another is passed in as an argument.
In the example below, bigPayload will be mutated, which is a bad idea if other functions/objects outside of retrieveAndCombine depends on bigPayload. In this case, you can swap the arguments passed to Object.assign, or just use {} as the first argument to create a new object:
async function retrieveAndCombine(bigPayload) {
const smallPayload = await retrieveData2()
// this is slightly more efficient
return Object.assign(smallPayload, bigPayload)
// this is still okay assuming `smallPayload` only has a few properties
return Object.assign({}, smallPayload, bigPayload)
}
Test
See if for yourself, try the code snippet below. Note: It takes awhile to run.
const rand = () => (Math.random() + 1).toString(36).substring(7)
function combineBigObjects() {
console.log('Please wait...creating the test objects...')
const obj = {}
const obj2 = {}
for (let i = 0; i < 100000; i++) {
const key = rand()
obj[rand()] = {
[rand()]: rand(),
[rand()]: rand(),
}
obj2[rand()] = {
[rand()]: rand(),
[rand()]: rand(),
}
}
console.log('Combine big objects using spread:')
console.time()
const result1 = {
...obj,
...obj2,
}
console.timeEnd()
console.log('Combine big objects using assign:')
console.time()
Object.assign({}, obj, obj2)
console.timeEnd()
console.log('Combine big objects using assign, but mutates first obj:')
console.time()
Object.assign(obj, obj2)
console.timeEnd()
}
combineBigObjects()
function combineSmallObjects() {
const firstObject = () => ({ [rand()]: rand() })
const secondObject = () => ({ [rand()]: rand() })
console.log('Combine small objects using spread:')
console.time()
for (let i = 0; i < 500000; i++) {
const finalObject = {
...firstObject(),
...secondObject(),
}
}
console.timeEnd()
console.log('Combine small objects using assign:')
console.time()
for (let i = 0; i < 500000; i++) {
const finalObject = Object.assign({}, firstObject(), secondObject())
}
console.timeEnd()
console.log('Combine small objects using assign, but mutates first obj:')
console.time()
for (let i = 0; i < 500000; i++) {
const finalObject = Object.assign(firstObject(), secondObject())
}
console.timeEnd()
}
combineSmallObjects()
Other answers are old, could not get a good answer.
Below example is for object literals, helps how both can complement each other, and how it cannot complement each other (therefore difference):
var obj1 = { a: 1, b: { b1: 1, b2: 'b2value', b3: 'b3value' } };
// overwrite parts of b key
var obj2 = {
b: {
...obj1.b,
b1: 2
}
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}} // NOTE: b2,b3 still exists
// overwrite whole of b key
var obj3 = {
b: {
b1: 2
}
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
// res3: {"a":1,"b":{"b1":2}} // NOTE: b2,b3 values are lost
Several more small examples here, also for array & object:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
The ways to (1) create shallow copies of objects and (2) merge multiple objects into a single object have evolved a lot between 2014 and 2018.
The approaches outlined below became available and widely used at different times. This answer provides some historical perspective and is not exhaustive.
Without any help from libraries or modern syntax, you would use for-in loops, e.g.
var mergedOptions = {}
for (var key in defaultOptions) {
mergedOptions[key] = defaultOptions[key]
}
for (var key in options) {
mergedOptions[key] = options[key]
}
options = mergedOptions
2006
jQuery 1.0 has jQuery.extend():
options = $.extend({}, defaultOptions, options)
⋮
2010
Underscore.js 1.0 has _.extend()
options = _.extend({}, defaultOptions, options)
⋮
2014
2ality published an article about Object.assign() coming to ES2015
object-assign published to npm.
var objectAssign = require('object-assign')
options = objectAssign({}, defaultOptions, options)
The Object Rest/Spread Properties syntax proposed for ES2016.
2015
Object.assign is supported by Chrome (45), Firefox (34) and Node.js (4). Polyfill is required for older runtimes though.
options = Object.assign({}, defaultOptions, options)
The Object Rest/Spread Properties proposal reaches stage 2.
2016
The Object Rest/Spread Properties syntax did not get included in ES2016, but proposal reaches stage 3.
2017
The Object Rest/Spread Properties syntax did not get included in ES2017, but is usable in Chrome (60), Firefox (55), and Node.js (8.3). Some transpilation is needed for older runtimes though.
options = { ...defaultOptions, ...options }
2018
The Object Rest/Spread Properties proposal reaches stage 4 and the syntax is included in ES2018 standard.
Object.assign is necessary when the target object is a constant and you want to set multiple properties at once.
For example:
const target = { data: "Test", loading: true }
Now, suppose you need to mutate the target with all properties from a source:
const source = { data: null, loading: false, ...etc }
Object.assign(target, source) // Now target is updated
target = { ...target, ...source) // Error: cant assign to constant
Keep in mind that you are mutating the target obj, so whenever possible use Object.assign with empty target or spread to assign to a new obj.
This is now part of ES6, thus is standardized, and is also documented on MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
It's very convenient to use and makes a lot of sense alongside object destructuring.
The one remaining advantage listed above is the dynamic capabilities of Object.assign(), however this is as easy as spreading the array inside of a literal object. In the compiled babel output it uses exactly what is demonstrated with Object.assign()
So the correct answer would be to use object spread since it is now standardized, widely used (see react, redux, etc), is easy to use, and has all the features of Object.assign()
I'd like to add this simple example when you have to use Object.assign.
class SomeClass {
constructor() {
this.someValue = 'some value';
}
someMethod() {
console.log('some action');
}
}
const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok
const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!
It can be not clear when you use JavaScript. But with TypeScript it is easier if you want to create instance of some class
const spread: SomeClass = {...new SomeClass()} // Error
The spread operator spread the Array into the separate arguments of a function.
let iterableObjB = [1,2,3,4]
function (...iterableObjB) //turned into
function (1,2,3,4)
We’ll create a function called identity that just returns whatever parameter we give it.
identity = (arg) => arg
And a simple array.
arr = [1, 2, 3]
If you call identity with arr, we know what’ll happen
Related
context:
const Constructor = Vue.extend(MyComponent);
function createComponent() {
vm = new Constructor({
props,
}).$mount();
return vm;
}
Question:
While on tests, I find
vm.$nextTick().then(() => {
expect(result).to.equal(expectedResult);
})
and
vm.$nextTick().then(() => {
expect(result).to.not.equal(expectedResult);
})
both passing.
How to get rid of this situation ? Would aync await in someway make sure the truth only passes ?
First: Just because you can negate any assertion with .not doesn’t mean you should. With great power comes great responsibility. It’s often best to assert that the one expected output was produced, rather than asserting that one of countless unexpected outputs wasn’t produced.
Equal asserts that the target is strictly (===) equal to the given val.
expect(2).to.equal(2); // Recommended
expect(2).to.not.equal(1); // Not recommended
Ok, now, in your case you may want to add .deep earlier in the chain to use deep equality instead:
// Target object deeply (but not strictly) equals `{a: 1}`
expect({a: 1}).to.deep.equal({a: 1});
expect({a: 1}).to.not.equal({a: 1});
// Target array deeply (but not strictly) equals `[1, 2]`
expect([1, 2]).to.deep.equal([1, 2]);
expect([1, 2]).to.not.equal([1, 2]);
Explaining why is easy:
1 === 1 // These are primitives, they hold the same reference - they are strictly equal
1 == '1' // These are two different primitives, through type coercion they hold the same value - they are loosely equal
{ a: 1 } !== { a: 1 } // These are two different objects, they hold different references and so are not strictly equal - even though they hold the same values inside
{ a: 1 } != { a: 1 } // They have the same type, meaning loose equality performs the same check as strict equality - they are still not equal.
Chai "to.equal" use "deep-eql" algorithm => package.
var deepEql = require("deep-eql");
deepEql({ a: 1 }, { a: 1 }) === true // deepEql can determine that they share the same keys and those keys share the same values, therefore they are deeply equal!
Do you want to know more about chai methods? Their docs are amazing.
My CasperJS asserts seem to be overly strict. I have a function where I am trying to test the names of client logo images from an array, using Casperjs. However I do not seem to be able to use a variable from a forLoop in casperJS.
I understand there are probably hoisting issues that I am not accounting for, but this does not seem to be the primary problem. I have tried several things to resolve hoisting issues, such as immediately invoked functions, try catch blocks, and using ES6 term "Let" in my loop. None seem to work. Then I notice if I simply hard-code the string my variable should represent, and stick a console.log into my assert of a PASSING test, right before the return, the passing test fails.
Here is my failing code
var clients = 'https://www.google.com/';
var logoArray = ["images/logos/AC.png", "images/logos/Affiny.png", "images/logos/ffintus.png", "images/logos/agileAsset.png"]
function checkClientsArrayTest() {
casper.test.begin('The layout is as expected', 10, function suite(test) {
casper.start(clients, function () {
casper.then(function () {
for (var i = 0; i < logoArray.length; i++) {
try { throw i }
catch (ii) {
console.log(ii);
console.log(i);
test.assertEvalEquals(function () {
return document.querySelectorAll('div.client_logo a img')[ii].getAttribute('src')
.match(logoArray[ii]).toString();
}, logoArray[ii], 'Test searches for Client Logos in DOM.');
}
}
});
}).run(function () {
test.done();
});
});
}
If I change logoArray[ii] to a hardcoded string from the first index of the array, it passes. If I consolelog logoArray[ii], it seems to be what I expect. But if I pass a variable to the assert, or even stick a console.log inside of it, the test fails with the following
Running check for the layout of URL: https://www.google.com
0
0
FAIL Test searches for Client Logos in DOM.
type: assertEvalEquals
file: headlessTester.js
subject: null
fn: undefined
params: undefined
expected: "images/logos/AC.png"
Is this an issue of me getting hoisting wrong (shouldn't fail by sticking in a logger if this is the case afaik), or is this due to strictly structured asserts in CasperJS?
Jest documentation reads:
toBe just checks that a value is what you expect. It uses === to check strict equality.
And for toEqual:
Use .toEqual when you want to check that two objects have the same value. This matcher recursively checks the equality of all fields, rather than checking for object identity—this is also known as "deep equal". For example, toEqual and toBe behave differently in this test suite, so all the tests pass.
const x = { a: { b: 3 } };
const y = { a: { b: 3 } };
expect(x).toEqual(y);
expect(x).toBe(y);
In this case, toEqual passes but toBe fails. I understand that toEqual passes because it does a deep equal check. Why is toBe failing in this case?
Also, are there best practices for using toBe and toEqual (not just in Jest but in other testing frameworks, too)?
It fails cause x and y are different instances and not equal as in (x === y) === false. You can use toBe for primitives like strings, numbers or booleans for everything else use toEqual. For example
x = 4
y = 4
x === y // true
x = 'someString'
y = 'someString'
x === y // true
Even empty objects are not equal
x = {}
y = {}
x === y //false
Suppose there are two players with same name and both of them scored 20.
let player1 = {
name: "Amit",
score: 20,
}
let player2 = {
name: "Amit",
score: 20,
}
Now I have a function which gives me 1st player.
function getFirstPlayer(player1,player2){
return player1;
}
How will I test this function?
# 1st way
expect(getFirstPlayer(player1,player2)).toBe(player1); // Passes
expect(getFirstPlayer(player1,player2)).not.toBe(player2); // Passes
# 2nd way
expect(getFirstPlayer(player1,player2)).toEqual(player1); // Pases
expect(getFirstPlayer(player1,player2)).not.toEqual(player2); // Fails
toBe tests Identity and toEqual tests features. So twin kids can have same features but their real identity is different from each other.
The way this function is designed we should use toBe.
Now I have a another function which increases the player score.
function addScore(player,scoreToAdd){
player.score += scoreToAdd;
}
How will I test this function?
# 1st way
addScore(player1,20);
expect(player1).toBe({name:"Amit", score:40}); // Fails
# 2nd way
addScore(player1,20);
expect(player1).toEqual({name:"Amit", score:40}); // Passes
Did you notice that in 1st way we are passing a new player like entity in right hand side. Is there any chance that player1 has the same identity of newly created entity? Nope. So toBe will always fail in this case.
2nd way passes as in toEqual we are comparing features. Here player1 and newly created entity has same features.
Note: In context of javascript, primitive values like "Amit" is identity in itself. So
expect("Amit").toBe("Amit") // Passes
I have written this answer for particular case, when you get this idea of identity and features, you can implement it in your scenario.
You have also toStrictEqual() since Jest v23
Explanations: https://jestjs.io/docs/en/expect#tostrictequalvalue
There is an ESLint plugin for Jest with a rule to enforce toStrictEqual(): https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-strict-equal.md
npm install --save-dev eslint-plugin-jest
// .eslintrc.js
module.exports = {
extends: [
'plugin:jest/recommended'
],
rules: {
'jest/prefer-strict-equal': 'error'
}
}
It is all about object references.
.toBe compares primitive values or checks referential identity of object instances while toEqual looks for deep equailty.
expect({ name: 'john doe' }).toEqual({ name: 'john doe'}); // PASSES
expect({ name: 'john doe' }).toBe({ name: 'john doe'}); // FAILS
The second assertion fails because they are different instances even though they are deeply equal. Remember, object literal syntax creates a new instance of the base object.
let a = { name: 'john doe' };
let b = a;
Here, the assignment operator copies the object reference stored in the variable a to b.
expect(a).toBe(b); // PASSES
The assertion passes because 'a' and 'b' points to same object. Please note that { name: 'john doe' } is not an object, it is an instruction to create one. Objects live in memory and interacted with via their references stored in a variable.
There's a few people saying that .toBe() is the same as x === y, but it's actually slightly different. Jest uses Object.is(x, y) when doing expect(x).toBe(y).
MDN - Object.is()
Jest .toBe source code
Unless you are verifying if a value is the same as a reference (like when checking if something got deepcloned properly), you should always use .toEqual(). And even in the deepclone example, I think it is cleaner to just do expect(x === y).toEqual(true) just to remove any confusion as to what you are trying to do.
You should not expect others to know the differences between toBe and toEqual, or to even know that Object.is exists and how it differs from ===. To avoid communication problems and testing issues, always use .toEqual, never use .toBe.
I have a (rather philosophical) question which refers to cyclejs components : Is isolate() referentially transparent?.
Looking at the simplified code, reproduced thereafter, I could not discriminate any source of 'impurity'. Is that because the not simplified code introduces it, or because the function would return two different objects with two different references?
In that case, would not those two objects have the same behaviour (i.e. listening and reacting to the same events on the same targets, and producing different vTree$ but which encapsulate exactly the same sequence?). And if that is so, aren't those two objects essentially the same, i.e. replacing one by the other anywhere in the program should not change anything? Which means isolate is referentially transparent? Where did I go wrong?
Actually if both calls returns different objects which cannot be substituted, how do those objects differ?
function isolate(Component, scope) {
return function IsolatedComponent(sources) {
const {isolateSource, isolateSink} = sources.DOM;
const isolatedDOMSource = isolateSource(sources.DOM, scope);
const sinks = Component({DOM: isolatedDOMSource});
const isolatedDOMSink = isolateSink(sinks.DOM, scope);
return {
DOM: isolatedDOMSink
};
};
}
I could not discriminate any source of 'impurity'. Is that because the not simplified code introduces it, or because the function would return two different objects with two different references?
The simplified code does not introduce impurity. The impurity comes from the fact that the parameter scope defaults to newScope() if it is not specified. The actual implementation of isolate() has:
function isolate(dataflowComponent, scope = newScope()) {
// ...
}
Where newScope() is:
let counter = 0
function newScope() {
return `cycle${++counter}`
}
Meaning, if the scope is not given as argument, it defaults to the next value of a hidden global counter which is incremented every time isolate() is called.
In conclusion, isolate(component, scope) is referentially transparent because we give the scope, but isolate(component) is not.
Since getting started in Dart I've been watching for a way to execute Dart (Text) Source (that the same program may well be generating dynamically) as Code. Like the infamous "eval()" function.
Recently I have caught a few hints that the communication port between Isolates support some sort of "Spawn" that seems like it could allow this "trick". In Ruby there is also the possibility to load a module dynamically as a language feature, perhaps there is some way to do this in Dart?
Any clues or a simple example will be greatly appreciated.
Thanks in advance!
Ladislav Thon provided this answer on the Dart forum:
I believe it's very safe to say that Dart will never have eval. But it will have other, more structured ways of dynamically generating code (code name mirror builders). There is nothing like that right now, though.
There are two ways of spawning an isolate: spawnFunction, which runs an existing function from the existing code in a new isolate, so nothing you are looking for, and spawnUri, which downloads code from given URI and runs it in new isolate. That is essentially dynamic code loading -- but the dynamically loaded code is isolated from the existing code. It runs in a new isolate, so the only means of communicating with it is via message passing (through ports).
You can run a string as Dart code by first constructing a data URI from it and then passing it into Isolate.spawnUri.
import 'dart:isolate';
void main() async {
final uri = Uri.dataFromString(
'''
void main() {
print("Hellooooooo from the other side!");
}
''',
mimeType: 'application/dart',
);
await Isolate.spawnUri(uri, [], null);
}
Note that you can only do this in JIT mode, which means that the only place you might benefit from it is Dart VM command line apps / package:build scripts. It will not work in Flutter release builds.
To get a result back from it, you can use ports:
import 'dart:isolate';
void main() async {
final name = 'Eval Knievel';
final uri = Uri.dataFromString(
'''
import "dart:isolate";
void main(_, SendPort port) {
port.send("Nice to meet you, $name!");
}
''',
mimeType: 'application/dart',
);
final port = ReceivePort();
await Isolate.spawnUri(uri, [], port.sendPort);
final String response = await port.first;
print(response);
}
I wrote about it on my blog.
Eval(), in Ruby at least, can execute anything from a single statement (like an assignment) to complete involved programs. There is a substantial time penalty for executing many small snippets over most any other form of execution that is possible.
Looking at the problem closer, there are at least three different functions that were at the base of the various schemes where eval might be used. Dart handles at least 2 of these in at least minimal ways.
Dart does not, nor does it look like there is any plan to support "general" script execution.
However, the NoSuchMethod method can be used to effectively implement the dynamic "injection" of variables into your local class environment. It replaces an eval() with a string that would look like this: eval( "String text = 'your first name here';" );
The second function that Dart readily supports now is the invocation of a method, that would look like this: eval( "Map map = SomeClass.some_method()" );
After messing about with this it finally dawned on me that a single simple class can be used to store the information needed to invoke a method, for a class, as a string which seems to have general utility. I can replace a big maintenance prone switch statement that might otherwise be necessary to invoke a series of methods. In Ruby this was almost trivial, however in Dart there are some fairly less than intuitive calls so I wanted to get this "trick" in one place, which fits will with doing ordering and filtering on the strings such as you may need.
Here's the code to "accumulate" as many classes (a whole library?) into a map using reflection such that the class.methodName() can be called with nothing more than a key (as a string).
Note: I used a few "helper methods" to do Map & List functions, you will probably want to replace them with straight Dart. However this code is used and tested only using the functions..
Here's the code:
//The used "Helpers" here..
MAP_add(var map, var key, var value){ if(key != null){map[key] = value;}return(map);}
Object MAP_fetch(var map, var key, [var dflt = null]) {var value = map[key];if (value==null) {value = dflt;}return( value );}
class ClassMethodMapper {
Map _helperMirrorsMap, _methodMap;
void accum_class_map(Object myClass){
InstanceMirror helperMirror = reflect(myClass);
List methodsAr = helperMirror.type.methods.values;
String classNm = myClass.toString().split("'")[1]; ///#FRAGILE
MAP_add(_helperMirrorsMap, classNm, helperMirror);
methodsAr.forEach(( method) {
String key = method.simpleName;
if (key.charCodeAt(0) != 95) { //Ignore private methods
MAP_add(_methodMap, "${classNm}.${key}()", method);
}
});
}
Map invoker( String methodNm ) {
var method = MAP_fetch(_methodMap, methodNm, null);
if (method != null) {
String classNm = methodNm.split('.')[0];
InstanceMirror helperMirror = MAP_fetch(_helperMirrorsMap, classNm);
helperMirror.invoke(method.simpleName, []);
}
}
ClassMethodMapper() {
_methodMap = {};
_helperMirrorsMap = {};
}
}//END_OF_CLASS( ClassMethodMapper );
============
main() {
ClassMethodMapper cMM = new ClassMethodMapper();
cMM.accum_class_map(MyFirstExampleClass);
cMM.accum_class_map(MySecondExampleClass);
//Now you're ready to execute any method (not private as per a special line of code above)
//by simply doing this:
cMM.invoker( MyFirstExampleClass.my_example_method() );
}
Actually there some libraries in pub.dev/packages but has some limitations because are young versions, so that I can recommend you this library expressions to dart and flutter.
A library to parse and evaluate simple expressions.
This library can handle simple expressions, but no blocks of code, control flow statements and so on. It supports a syntax that is common to most programming languages.
There I create an example of code to evaluate arithmetic operations and comparations of data.
import 'package:expressions/expressions.dart';
import 'dart:math';
#override
Widget build(BuildContext context) {
final parsing = FormulaMath();
// Expression example
String condition = "(cos(x)*cos(x)+sin(x)*sin(x)==1) && respuesta_texto == 'si'";
Expression expression = Expression.parse(condition);
var context = {
"x": pi / 5,
"cos": cos,
"sin": sin,
"respuesta_texto" : 'si'
};
// Evaluate expression
final evaluator = const ExpressionEvaluator();
var r = evaluator.eval(expression, context);
print(r);
return Scaffold(
body: Container(
margin: EdgeInsets.only(top: 50.0),
child: Column(
children: [
Text(condition),
Text(r.toString())
],
),
),
);
}
I/flutter (27188): true