标识符绑定
##命名绑定
在高度抽象的语言,不需要开发者去操作底层的地址来实现对内存中的某个数据的访问,而是通过更方便的,以变量名(标识符)的形式来获取想要的数据。
命名绑定就是将一个标识符和一个对象绑定在一起。
一个标识符不仅可以被绑定,也可以被解除绑定。如果一个标识符与一个对象绑定在一起,它便引用了这个对象,通过这个标识符就可以去访问和修改那个对象。
有两个与绑定相关的主要操作,分别是重绑定(rebinding)以及改变(mutation)(这两个操作经常会引起关于按值传递和按引用传递的混淆)。下面将分别介绍。
重绑定
重绑定与标识符有关。重绑定操作会将一个标识符从旧的对象上(如果之前绑定过)解除绑定,再将它重新绑定到另一个对象上(即指向另一个内存块)。大多数时候(特别包括ECMAScript 中),重绑定可以通过一个简单的赋值操作实现。
例子:
// 将"foo" 绑定到对象{x: 10}
var foo = {x: 10};
console.log(foo.x); // 10
// 将"bar"绑定到和"foo绑定的
//同一个对象上
//
var bar = foo;
console.log(foo === bar); // true
console.log(bar.x); // OK, 同样10
// 现在重绑定"foo"
//到一个新的对象上
foo = {x: 20};
console.log(foo.x); // 20
// "bar"仍然指向旧的对象
console.log(bar.x); // 10
console.log(foo === bar); // false
很多时候按引用传值的重绑定行为会让人困惑。有人会认为在上面这个例子中,当变量foo被分配到一个新的对象后,变量bar也应该指向新的对象。然而正如我们看到的,此时bar仍然引用的是旧对象,与此同时,foo被重绑定到一个块新的内存。下图展示了这一过程。
想一下不是作为引用方式的绑定,比如(从C的视角)通过指针(或者,有时候通过共享)。我们可以认为这种方式是一种特殊的按值传递,其中这个值是个地址。在这种情况下,对一个变量赋值仅仅改变(重绑定)这个变量的值(指向的地址),将变量的值从某个内存块换到另一个内存块。当将把一个变量赋值给另一个变量时,仅仅是把这个地址拷贝一份赋值给第二个变量(此时它俩指向同个对象)。可以说这两个变量共享一个对象,也叫这种方式为按共享传递(by-sharing)。
译注:看下面这个例子:
function setName(obj){ obj.name = "peter"; } var person = new Object(); setName(person); alert(person.name); // perter
ECMAScript中,函数参数是按值传递。然而在上面的例子中,setName函数的修改反映到了外部的person变量。和C指针一样,ECMAScript对于这种引用类型作为参数传递时采取的就是按共享传递。
person 作为参数传入函数的时候,是将person
指向的对象的地址拷贝了一份传给函数。所以此时函数内外的变量指向同一个内存地址,共享同一个对象,改变属性将会影响到外部的值。
再看下面这个例子function setName(obj){ obj.name = "peter"; obj = new Object(); obj.name = "mike" } var person = new Object(); setName(person); alert(person.name); // perter
与上面的不同,我们在函数内将另一个对象赋值给参数obj,这时候发生了重绑定,obj指向了新的内存地址,不再与person共享同一个变量。所以后面对obj的修改不再会影响到person指向的对象。
变动(mutation)
相比重绑定,变动操作能够影响对象的内容。
看下面这个简单的例子:
// 将数组绑定到标识符"foo"
var foo = [1, 2, 3];
// 该处是对数组内容
//的一个变动操作
foo.push(4);
console.log(foo); // 1,2,3,4
// 接着变动
foo[4] = 5;
foo[0] = 0;
console.log(foo); // 0,2,3,4,5
下面这张图展示了foo的变化过程:
如果想要了解更多关于绑定和赋值策略的信息(按值,按引用,按共享),可以在ES3系列中的 Evaluation strategy去查看。
下面将开始介绍环境的一般概念,以及环境的运作机制。
原文
英文原版链接 Lexical environments: Common Theory.