Reference Type
在 JavaScript 中,引用类型(Reference Type)是一种数据存储和操作的方式。
Reference Type 是 ECMA 中的一个“规范类型”。我们不能直接使用它,但它被用在 JavaScript 语言内部。
Reference Type 的值是一个三个值的组合 (base, propertyname, strict)
:
base
是对象。
在 JavaScript 中,所有对象或者函数都有所属对象。在全局上下文中,base 等同于全局对象(global);在函数的执行上下文中,base 等同于变量对象(vo)或活动对象(ao);而在处理对象属性时,base 等同于所属的对象(owerObject)。propertyname
是属性名。strict
在use strict
模式下为true
。
用伪代码表示:
var valueOfReferenceType = {base: // 对象的所属对象propertyname: // 属性名
};
示例:
let obj1 = { name: 'Alice' };
console.log(obj1.name); // Alice
当访问 obj1.name
时,得到的引用类型的值中,base
就是 obj1
对象本身,propertyname
就是属性name
。
引用类型本身并不保存真正的值,而是存储对数据实际存储位置的引用(类似于指针)。这意味着多个变量可以引用同一个对象,对其中一个变量所做的修改会反映在其他引用该对象的变量上。
常见的引用类型包括对象(Object
)、数组(Array
)和函数(Function
)、日期(Date
)、正则表达式(RegExp
)。
以Object为例,解读Reference Type
在 JavaScript 中,对象(Object)是一种引用类型(Reference Type)。
当我们在读取对象的某个属性的时候,'.'
返回的准确来说不是属性的值,而是一个特殊的Reference Type
类型的值,在这个其中存着属性的值和它的来源对象。
示例:
let obj1 = { name: 'Alice' };
let obj2 = obj1; // obj2 复制了 obj1 的引用console.log(obj1.name === obj2.name); // truefunction updateObj(obj) {obj.age = 30obj.name = 'Bob'
}
updateObj(obj2);
console.log(obj2); // { name: 'Bob', age: 30 }
console.log(obj1.name, obj1.age); // 'Bob', 30 因为 obj1 和 obj2 指向同一个对象
console.log(obj1.name === obj2.name); // true
在示例中,let obj1 = { name: 'Alice' };
做了2件事:
- 在内存的堆中创建了一个对象,这个对象具有一个名为
name
的属性,其值为"Alice"
。 - 然后,在栈中创建了变量
obj1
,obj1
存储的是指向堆中的对象{ name: 'Alice' }
的引用(而不是对象本身的值)。
let obj2 = obj1;
将 obj1
的引用赋值给了 obj2
。因此,obj1
与 obj2
保存的都是对同一个对象的引用。
当调用 updateObj(obj2)
函数时,虽然传递的是 obj2
,但由于是引用传递,实际上传递的是指向堆中对象的引用。在函数内部对这个对象进行修改,会同时反映在 obj1
和 obj2
上。
打个比方,对象{ name: 'Alice' }
就像是家里大门的锁,一家几口人都各有1把钥匙能开门回家。使用钥匙obj1
的人开门回家,并做好了饭。使用钥匙obj2
开门回家的人,就可以直接恰饭了。
以函数为例,解读Reference Type
obj.method()
语句中有两个操作:
- 首先,点
'.'
取了属性obj.method
的值。 - 接着
()
执行了它。
在通过点 '.'
操作符或方括号[]
操作符调用方法时,this
的指向与引用类型的对象有关。
在一些复杂的表达式中,可能会导致this
丢失或指向不正确。
示例:
let user = {name: "Alice",hi() { console.log(this.name); },bye() { console.log("Bye"); }
};user.hi(); // 正常运行,在hi()方法内部,this 正确地指向了 user 对象// 现在让我们基于 name 来选择调用 user.hi 或 user.bye
(user.name == "Alice" ? user.hi : user.bye)(); // Error!
//Uncaught TypeError: Cannot read properties of undefined (reading 'name')
在示例中,根据三元运算(user.name == "John" ? user.hi : user.bye)
判断具体使用哪个方法,结果是使用user.hi
。接着该方法被通过 ()
立刻调用。
执行user.hi
报错Uncaught TypeError: Cannot read properties of undefined (reading 'name')
。
为什么会报错?
在 JavaScript 中,条件表达式返回的是函数本身,而不是函数的调用结果。
因此,在严格模式下:
- 执行
user.hi()
语句,对属性user.hi
访问的结果不是一个函数,而是一个 Reference Type 的值。当()
被在 Reference Type 上调用时,它们会接收到关于对象和对象的方法的完整信息,然后可以设置正确的this
(在此处= user
)。
// user.hi() Reference Type 的值
(user, "hi", true)
其中,user
是对象本身,"hi"
是属性名,true
表示处于严格模式。
- 当执行
hi = user.hi
后,hi
仅仅是函数本身,即hi() { console.log(this.name); }
。它丢失了原有的 Reference Type 信息,也就丢失了与特定对象user
的关联以及严格模式的相关设置。
在严格模式下,如果直接调用变量函数hi
,this
的值会是undefined
。
(user.name == "Alice" ? user.hi : user.bye)();
的详细写法如下:
// 把获取方法和调用方法拆成2个步骤
let func;
if(user.name == "Alice") {func = user.hi; // 把函数 user.hi 赋值给了变量 func
}ele{func = user.bye; // 把函数 user.bye 赋值给了变量 func
}
func(); // Uncaught TypeError: Cannot read properties of undefined (reading 'name')
当执行 func()
时,this
的指向不是 user
对象,this
是undefined
,导致无法正确访问 name
属性,引发错误。
解决方案:可以使用call、apply或bind方法来明确指定this的指向。
let func = (user.name == "Alice"? user.hi : user.bye);
func.call(user);
// 通过 call 方法将 user 对象作为 this 传递给函数
Reference Type 是一个特殊的“中间人”内部类型,目的是从 '.'
传递信息给 ()
调用。
总结
Reference Type 是语言内部的一个类型。
引用类型具有以下特点:
- 存储方式
引用类型的值在内存中的存储分为两部分,一部分是对象本身,存储在堆(heap)中;另一部分是对象的引用地址,存储在栈(stack)中。变量实际上存储的是这个引用地址。
赋值了对象的变量存储的不是对象本身,而是该对象“在内存中的地址”。换句话说就是对该对象的“引用”。 - 引用共享
对象是“通过引用”存储和复制的。 - 传递方式
在函数传参时,引用类型是按引用传递。这意味着函数内部对参数的修改会影响到原始的对象或数组。 - 可变性
引用类型的值是可变的。例如,可以修改对象的属性或数组的元素。 - 比较方式
对于严格相等(===)
,比较的是引用地址是否相等,即是否是同一个对象;对于宽松相等(==)
,会进行类型转换后再比较值。 - this 指向:在通过点
'.'
操作符或方括号[]
操作符调用方法时,this
的指向与引用类型的对象有关。在一些复杂的表达式中,可能会导致this
丢失或指向不正确。