最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Javascript reference vs binding...what's the difference? - Stack Overflow

programmeradmin1浏览0评论

I recently read the following in Kyle Simpson's You Don't Know JS: ES6

"[ES6 modules export] actual bindings (almost like pointers) to the identifiers in your inner module definition."

My confusion is how these bindings differ from references...

I understand that a reference in JS is only applicable to non-primitive types (like objects), so that given

let object1 = {a: 1};
let object2 = object1;

object1 and object2 now refer to (they are both references to) the same object.
If I add a property to object2, I am also adding a property to object1

object2.b = 2;
console.log(object1.b); // 2

And I can see that a binding can apply to both primitive types and non-primitive types

// foo.js
export let count = 1;
export function incrementCount() { count++; }

// bar.js
import {count, incrementCount} from foo;
console.log(count); // 1
incrementCount();
console.log(count); // 2

Is a binding just like a reference, except that primitive values can also share a binding (while references are limited to non-primitive types)?

I feel like I'm missing something here...

I recently read the following in Kyle Simpson's You Don't Know JS: ES6

"[ES6 modules export] actual bindings (almost like pointers) to the identifiers in your inner module definition."

My confusion is how these bindings differ from references...

I understand that a reference in JS is only applicable to non-primitive types (like objects), so that given

let object1 = {a: 1};
let object2 = object1;

object1 and object2 now refer to (they are both references to) the same object.
If I add a property to object2, I am also adding a property to object1

object2.b = 2;
console.log(object1.b); // 2

And I can see that a binding can apply to both primitive types and non-primitive types

// foo.js
export let count = 1;
export function incrementCount() { count++; }

// bar.js
import {count, incrementCount} from foo;
console.log(count); // 1
incrementCount();
console.log(count); // 2

Is a binding just like a reference, except that primitive values can also share a binding (while references are limited to non-primitive types)?

I feel like I'm missing something here...

Share Improve this question edited Sep 9, 2016 at 17:59 sfletche asked Aug 31, 2016 at 22:06 sfletchesfletche 49.7k31 gold badges108 silver badges120 bronze badges 6
  • "Isn't a binding just like a reference, except that primitive values can share a binding but not a reference?" --- that, plus it is readonly. – zerkms Commented Aug 31, 2016 at 22:11
  • 1 FYI, your example let a = 1; export default a; does not export a live version of a, you'd want let a = 1; export {a as default}; for that to work. export default a; in this context is no different that export default 4 + 5;, the expression is evaluated and a bindings is created for it that you do not have access to. The only cases where export default X; does indeed create a live binding is for declarations, e.g. export default class a {} – loganfsmyth Commented Sep 1, 2016 at 1:49
  • @loganfsmyth: are you sure? I was under the impression that export default a and export {a as default} were equivalent. – sfletche Commented Sep 1, 2016 at 4:05
  • Yup, I'm sure let a = 1; export default a; is equivalent to let a = 1; let _secret = a; export {_secret as default};. – loganfsmyth Commented Sep 1, 2016 at 4:08
  • Thanks @loganfsmyth. Will edit question to reflect that. – sfletche Commented Sep 1, 2016 at 22:55
 |  Show 1 more comment

4 Answers 4

Reset to default 14

A binding is a very generic term for "what a name refers to". Every identifier in a scope is bound to something. Usually they resolve to variables in a variable environment (storage slots in an environment record), but there are exceptions (e.g. with or the global object).

A reference is a term for a pointer to some kind of structure. For example, objects are known as "reference values" because they reference the container of mutable properties with an identity.

ES6 modules are now introducing a new type of binding, one that was unknown before. It is not a usual variable, but literally a reference to another variable - the one exported from the other module. If the module variable changes, this will be reflected by the import - they both point to the same environment record slot.
An export declaration adds a mapping from a local name to a name in the module interface, while an import declaration adds a mapping from a name in the respective module interface to a local name. When a module is instantiated, an indirect binding is created that points to the same environment as the local binding in the exporting module.

Despite @Bergi's great answer I'd like to give a more detailed response for users with less background knowledge.

Name Bindings

A name binding is the association of an identifier with a named memory chunk (variable) according to the lexical scope rules of Javascript. Name bindings are required because an identifier can exist in different scopes and thus be used for different variables:

function f() { let x = 0 }

let x = 1;
   
{ let x = 2 }
    
{ let x = 3;
  { let x = 4; 
    { console.log(x) } // logs 4
  }
}

Once the name binding process is done, console.log(x) refers to x of the surrounding scope, which is bound to the value 4.

A name binding decides which variable an identifier refers to in a particular scope.

Primitive and Reference Types

The associated value of a binding can either represent

  • a value/primitive type
  • or a reference type

Primitive types are immutable in Javascript. When you pass a primitive to a function or assign it to another identifier, you actually operate with copies of values. The identity of a primitive value is given by its value, i.e. it has no identity.

Reference types are mutable in Javascript. When you pass a reference type to a function or assign it to another identifier, you actually operate with a copy of its reference, which is a value as well. Thus you pass a-reference, not by-reference - this distinction is crucial: Javascript has only call-by-value evaluation strategy, not call-by-reference. Reference types have an identity that is separated from their values. Hence they can be shared across name bindings.

const x = "no identity",
 y = "no identity";

const o = {foo: "identity"},
 p = {foo: "identity"};
 
// value types don't have identity
console.log(x === y); // true

// reference types have identity
console.log(o === p); // false

let q = o;

// mutations of reference types can be shared
q.bar = "mutation";
console.log(o); // {foo: "identity", bar: "mutation"}

// but rebindings can't be chared
q = {baz: "rebinding"};
console.log(q); // {baz: "rebinding"}
console.log(o); // {foo: "identity", bar: "mutation"}

A reference creates identity and the ability to share a corresponding value.

ES6 Module Bindings

An ES6 module exports a new type of name binding, which was previously unknown in JavaScript. When you import an exported binding of a module A, you create a binding of an import name and a reference. However, this reference doesn't refer to an object but to the export binding of A. Now we can share not only reference types but also primitives across modules.

The effect is not specific to primitive values, it's like accessing a property on an object.

For example:

let foo = {a: 1};
let bar = foo;
foo = {b: 2};

console.log(bar); // {a: 1}

But:

// foo.js
let a = {a: 1};
function mutateA() {
  a = {b: 2};
}
export a;
export mutateA;

// bar.js
import {a, mutateA} from foo;

console.log(a); // {a: 1}
mutateA();
console.log(a); // {b: 2}

So bar's a is bound to foo's a, and has the exact same value, be it a primitive or a reference.

Whenever you are using import {count, incrementCount} from foo; you are calling a function require with foo which is wrapping the code available in module foo inside a wrapper function, then calling the wrapper function and returning an object containing keys count and incrementCount as they are subjected to the export keyword. Which means, the binding definitions let count = 1; and function incrementCount() { count++; } are inside the wrapper function. As the binding count is not being passed to the function incrementCount as an argument, it will be subjected to the concept of "closure" in JavaScript. So any change to count inside incrementCount will change the value of the global binding count which is being referred to inside incrementCount.

发布评论

评论列表(0)

  1. 暂无评论