Javascript 引用与绑定...有什么区别?

Javascript reference vs binding...what's the difference?

我最近在 Kyle Simpson 的《你不知道的 JS》中阅读了以下内容:ES6

“ES6 模块导出] 实际绑定(几乎像指针)到内部模块定义中的标识符。”

我的困惑是这些绑定与引用有何不同...

我了解到JS中的reference只适用于非原始类型(如对象),因此给定

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

object1object2 现在引用(它们都是引用)同一个对象。
如果我将 属性 添加到 object2,我也会将 属性 添加到 object1

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

而且我可以看到 绑定 可以应用于原始类型和非原始类型

// 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

除了原始值也可以共享一个绑定(而引用仅限于非原始类型)之外,绑定是否就像引用一样?

我觉得我在这里遗漏了什么...

效果并不特定于原始值,就像访问对象上的 属性。

例如:

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

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

但是:

// 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}

因此 bar 的 a 绑定 到 foo 的 a,并且具有完全相同的值,无论是原始值还是引用。

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),但也有例外(例如with或全局对象)。

A reference is a term for a pointer to some kind of structure. For example, objects are known as "reference values" 因为它们引用具有标识的可变属性的容器。

ES6 模块现在引入了一种新的绑定类型,一种以前未知的类型。它不是一个普通的变量,而是字面上对另一个变量的引用——从另一个模块导出的变量。如果模块变量发生变化,这将通过导入反映出来——它们都指向同一个环境记录槽。
export 声明添加了从本地名称到 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 中的名称的映射,该映射指向与导出模块中的本地绑定相同的环境。

尽管@Bergi 的回答很好,但我想为背景知识较少的用户提供更详细的回复。

名称绑定

名称绑定是根据 Javascript 的词法范围规则将标识符与命名内存块(变量)关联起来。名称绑定是必需的,因为标识符可以存在于不同的范围内,因此可用于不同的变量:

function f() { let x = 0 }

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

名称绑定过程完成后,console.log(x) 引用周围范围的 x,绑定到值 4

名称绑定决定标识符在特定范围内引用哪个变量。

原始类型和引用类型

绑定的关联值可以表示

  • 一个value/primitive类型
  • 或引用类型

原始类型在 Javascript 中是不可变的。当您将原语传递给函数或将其分配给另一个标识符时,您实际上是在操作值的副本。原始值的标识由其值给出,即它没有标识。

引用类型在 Javascript 中是可变的。当您将引用类型传递给函数或将其分配给另一个标识符时,您实际上是在操作其引用的副本,这也是一个值。因此,您传递的是引用,而不是引用——这种区别至关重要:Javascript 只有按值调用评估策略,而不是按引用调用。引用类型有一个与其值分开的身份。因此它们可以跨名称绑定共享。

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"}

引用创建身份和共享相应值的能力。

ES6 模块绑定

ES6 模块导出了一种新的名称绑定类型,这在以前 JavaScript 中是未知的。当您导入模块 A 的导出绑定时,您创建了导入名称和引用的绑定。但是,此引用不引用对象,而是引用 A 的导出绑定。现在我们不仅可以共享引用类型,还可以跨模块共享原语。