Scoping rules
The main difference is scoping rules. Variables declared by var
keyword are scoped to the immediate function body (hence the function scope) while let
variables are scoped to the immediate enclosing block denoted by { }
(hence the block scope).
function run() {
var foo = "Foo";
let bar = "Bar";
console.log(foo, bar); // Foo Bar
{
var moo = "Mooo"
let baz = "Bazz";
console.log(moo, baz); // Mooo Bazz
}
console.log(moo); // Mooo
console.log(baz); // ReferenceError
}
run();
The reason why let
keyword was introduced to the language was function scope is confusing and was one of the main sources of bugs in JavaScript.
Take a look at this example from another stackoverflow question:
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
My value: 3
was output to console each time funcs[j]();
was invoked since anonymous functions were bound to the same variable.
People had to create immediately invoked functions to capture correct values from the loops but that was also hairy.
Hoisting
While variables declared with var
keyword are hoisted (initialized with undefined
before the code is run) which means they are accessible in their enclosing scope even before they are declared:
function run() {
console.log(foo); // undefined
var foo = "Foo";
console.log(foo); // Foo
}
run();
let
variables are not initialized until their definition is evaluated. Accessing them before the initialization results in a ReferenceError
. The variable is said to be in "temporal dead zone" from the start of the block until the initialization is processed.
function checkHoisting() {
console.log(foo); // ReferenceError
let foo = "Foo";
console.log(foo); // Foo
}
checkHoisting();
Creating global object property
At the top level, let
, unlike var
, does not create a property on the global object:
var foo = "Foo"; // globally scoped
let bar = "Bar"; // not allowed to be globally scoped
console.log(window.foo); // Foo
console.log(window.bar); // undefined
Redeclaration
In strict mode, var
will let you re-declare the same variable in the same scope while let
raises a SyntaxError.
'use strict';
var foo = "foo1";
var foo = "foo2"; // No problem, 'foo1' is replaced with 'foo2'.
let bar = "bar1";
let bar = "bar2"; // SyntaxError: Identifier 'bar' has already been declared
After many long hours of being unable to get this working, I finally stumbled across a demo that I don't think is linked any of the documentation: http://bl.ocks.org/1095795:
This demo contained the keys which finally helped me crack the problem.
Adding multiple objects on an enter()
can be done by assigning the enter()
to a variable, and then appending to that. This makes sense. The second critical part is that the node and link arrays must be based on the force()
-- otherwise the graph and model will go out of synch as nodes are deleted and added.
This is because if a new array is constructed instead, it will lack the following attributes:
- index - the zero-based index of the node within the nodes array.
- x - the x-coordinate of the current node position.
- y - the y-coordinate of the current node position.
- px - the x-coordinate of the previous node position.
- py - the y-coordinate of the previous node position.
- fixed - a boolean indicating whether node position is locked.
- weight - the node weight; the number of associated links.
These attributes are not strictly needed for the call to force.nodes()
, but if these are not present, then they would be randomly initialised by force.start()
on the first call.
If anybody is curious, the working code looks like this:
<script type="text/javascript">
function myGraph(el) {
// Add and remove elements on the graph object
this.addNode = function (id) {
nodes.push({"id":id});
update();
}
this.removeNode = function (id) {
var i = 0;
var n = findNode(id);
while (i < links.length) {
if ((links[i]['source'] === n)||(links[i]['target'] == n)) links.splice(i,1);
else i++;
}
var index = findNodeIndex(id);
if(index !== undefined) {
nodes.splice(index, 1);
update();
}
}
this.addLink = function (sourceId, targetId) {
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);
if((sourceNode !== undefined) && (targetNode !== undefined)) {
links.push({"source": sourceNode, "target": targetNode});
update();
}
}
var findNode = function (id) {
for (var i=0; i < nodes.length; i++) {
if (nodes[i].id === id)
return nodes[i]
};
}
var findNodeIndex = function (id) {
for (var i=0; i < nodes.length; i++) {
if (nodes[i].id === id)
return i
};
}
// set up the D3 visualisation in the specified element
var w = $(el).innerWidth(),
h = $(el).innerHeight();
var vis = this.vis = d3.select(el).append("svg:svg")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([w, h]);
var nodes = force.nodes(),
links = force.links();
var update = function () {
var link = vis.selectAll("line.link")
.data(links, function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line")
.attr("class", "link");
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id;});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("image")
.attr("class", "circle")
.attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", "16px")
.attr("height", "16px");
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id});
node.exit().remove();
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
// Restart the force layout.
force.start();
}
// Make it all go
update();
}
graph = new myGraph("#graph");
// You can do this from the console as much as you like...
graph.addNode("Cause");
graph.addNode("Effect");
graph.addLink("Cause", "Effect");
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");
</script>
Best Answer
I have made a small code snippet. Hope this helpful.