我们在写代码的时候为了提高代码的可读性和降低耦合性,经常需要将一大块逻辑代码拆解成小块。
但经常会遇到一个问题就是,有一个变量它贯穿于整个逻辑中,那么在拆分过程中他就需要被当成参数传入各个子模块。
这就涉及到一个问题,每个子模块对传入变量的操作有没有直接改变原变量的值呢?
下面我们来通过代码实现一下。
基本类型变量
Number
,String
,Boolean
// Number
var a = 111;
function changeNumber(param) {
param = 222;
}
changeNumber(a);
console.log(a); // 111
// String
var b = "hello";
function changeString(param) {
param = "world";
param.concat(" world");
}
changeString(b);
console.log(b); // "hello"
// Boolean
var c = true;
function changeBoolean(param) {
param = false;
}
changeBoolean(c);
console.log(c); // true
引用类型
// 对象
var a = {test: 1};
function changeObject(param) {
param.test = 2;
}
changeObject(a);
console.log(a); // {test: 2}
但如果这里不是改变的对象属性,而是直接赋新的值呢?
var b = {test: 1};
function changeObject2(param) {
param = {test: 2};
}
changeObject2(b);
console.log(b); // {test: 1}
注意对象里这两种的区别, 下面从编译器角度详细分析整个过程:
// 1. 开始处理var a, 编译器询问作用域是否有一个名叫a的变量,如果有的话,编译器忽略,继续向下走。
// 2. 结果是没有,则在当前作用域集合中声明一个新变量名叫a。
// 3. 编译器为引擎生成运行时的代码来处理a = {test: 1}。
// 4. 运行时会先询问作用域是否存在一个叫a的变量,如果是,引擎则会使用这个变量,否则的话会继续查找该变量。
// 5. 如果4最终未找到a,则抛出一个异常,这里显然已经找到了2中声明的a,所以申请一块内存,里面的值是{test: 1},并且将它赋值给a。
// 以上这个过程叫引擎为变量a做LHS查询(变量出现在赋值操作的左侧)。
var a = {test: 1};
// 6. 引擎在作用域中为changObject进行LHS查询。
function changeObject(param) {
param.test = 2;
}
// 7. 引擎在作用域中为changeObject进行RHS引用,即“把changeObject的值找给引擎”
// 这个过程还隐含一个param = a操作,包含两步:一是对a的RHS引用,找到a的值,二是对param的LHS查询,即对param赋a的值。
// 注意,此时param和a共同指向了内存中的同一个对象{test: 2}。
// 如果是第一种param.test = 2;那么相当于对param.test这个变量做LHS查询,故param和a同时都改变了。
// 如果是第二种param = {test: 2};那么相当于对param这个变量做LHS查询,即断开param与原对象的指向,将其指向内存中新开辟的对象{test: 2}。原a指向原对象保持不变。
changeObject(a);
console.log(a); // {test: 2}
类似的,对于数组也是对象这种结果。