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丢失或指向不正确。
