Reflect.get() 是 Reflect 对象中用于读取对象属性值的方法。它把属性访问这个基础操作封装成了函数形式,并且提供了一个其他方式不具备的能力——在访问 getter 属性时可以动态指定 this 的指向。这个方法让属性读取操作变得更加灵活,也更容易融入函数式编程的范式。
方法语法与参数说明
Reflect.get(target, propertyKey[, receiver])
参数详解:
-
target:要读取属性的目标对象。必须是一个对象类型,否则会抛出 TypeError。 -
propertyKey:要读取的属性名,可以是字符串或 Symbol。 -
receiver(可选):如果目标属性是一个 getter,这个参数会作为 getter 函数内部的this值。如果省略,this默认指向target本身。
返回值: 返回属性对应的值。如果属性不存在,返回 undefined。
异常情况: 如果 target 不是对象(比如传入 null 或原始类型),方抛出 TypeError。
浏览器兼容性: Chrome 49+、Edge 12+、Firefox 42+、Opera 36+ 均支持该方法。
为什么不用 obj.prop 而用 Reflect.get()
刚开始用 Reflect 的时候,我对 Reflect.get() 是抵触的——明明 obj.prop 或者 obj['prop'] 几个字符就能搞定的事,干嘛要写一长串方法调用?后来在项目中遇到了几个场景,才慢慢理解它的存在价值。
-
函数式编程的适配性。
obj.prop是语法层面的操作,你不能把它当作参数传递。而Reflect.get是函数,可以作为回调传入map、filter等高阶函数。 -
统一处理不存在的属性。 两种方式在属性不存在时都返回
undefined,但Reflect.get在 Proxy 的get中使用时行为更一致。 -
receiver 参数的存在。 这是
Reflect.get()最核心的优势。当你在处理继承关系或者代理对象时,可以通过receiver参数精准控制 getter 中的this指向,这一点用obj.prop做不到。 -
与 Proxy 的无缝配合。 在使用 Proxy 拦截属性读取时,
get内部调用Reflect.get()并传递receiver参数,可确保原型链上的 getter 能正确识别当前的代理对象。
示例一:基础属性读取与不存在的属性
这个示例展示了 Reflect.get() 的基本用法,以及它处理不存在属性时的行为。
// 编程课程信息对象
const jsCourse = {
courseId: 'JS2026',
courseName: 'JavaScript 核心原理',
totalLessons: 24,
instructor: 'Alan'
};
// 读取存在的属性
const courseName = Reflect.get(jsCourse, 'courseName');
console.log('课程名称:', courseName); // 'JavaScript 核心原理'
// 使用方括号风格验证等价性
console.log(Reflect.get(jsCourse, 'totalLessons') === jsCourse.totalLessons); // true
// 读取不存在的属性 —— 返回 undefined
const description = Reflect.get(jsCourse, 'description');
console.log('不存在的属性值:', description); // undefined
// 验证 undefined 的严格相等
console.log(Reflect.get(jsCourse, 'description') === undefined); // true
console.log(Reflect.get(jsCourse, 'description') === null); // false
本节课程知识要点: Reflect.get() 处理不存在的属性时和 obj.prop 行为一致,都返回 undefined。这个行为与对象底层内部方法 [[Get]] 的规范定义吻合。
示例二:原型链属性的自动查找
Reflect.get() 会自动沿着原型链向上查找属性,这和普通的属性访问行为一致。这个示例演示了如何通过继承关系获取父对象的属性。
// 基础课程模板对象(作为原型)
const baseCourse = {
platform: 'CodeLearning',
publishedYear: '2026',
getType() {
return '通用课程';
}
};
// 创建继承自 baseCourse 的子对象
const pythonCourse = Object.create(baseCourse);
pythonCourse.courseName = 'Python 数据分析';
pythonCourse.difficulty = '中级';
// 通过 Reflect.get() 读取子对象自身属性
console.log(Reflect.get(pythonCourse, 'courseName')); // 'Python 数据分析'
// 读取原型链上的属性 —— Reflect.get() 自动向上查找
console.log(Reflect.get(pythonCourse, 'platform')); // 'CodeLearning'
console.log(Reflect.get(pythonCourse, 'publishedYear')); // '2026'
// 验证原型链查找
console.log(pythonCourse.hasOwnProperty('courseName')); // true(自身属性)
console.log(pythonCourse.hasOwnProperty('platform')); // false(原型属性)
// 属性读取的等价验证
console.log(Reflect.get(pythonCourse, 'platform') === pythonCourse.platform); // true
本节课程知识要点: 原型链查找是 Reflect.get() 的内置行为,不需要额外的配置。这遵循了 ECMAScript 规范中对象属性访问的标准流程。
示例三:数组元素的读取
数组在 JavaScript 中是特殊的对象,索引被视为属性名。Reflect.get() 同样可以用于读取数组元素。
// 编程语言学习路线数组
const learningPath = ['HTML基础', 'CSS布局', 'JavaScript入门', 'Vue框架', 'React进阶'];
// 通过索引读取数组元素
const firstStep = Reflect.get(learningPath, 0);
console.log('第一门课程:', firstStep); // 'HTML基础'
const fourthStep = Reflect.get(learningPath, 3);
console.log('第四门课程:', fourthStep); // 'Vue框架'
// 读取超出索引范围的元素
const outOfRange = Reflect.get(learningPath, 10);
console.log('越界索引:', outOfRange); // undefined
// 读取数组的 length 属性
const pathLength = Reflect.get(learningPath, 'length');
console.log('路线长度:', pathLength); // 5
// 验证数组索引和对象属性的等价性
console.log(Reflect.get(learningPath, 2) === learningPath[2]); // true
这个例子说明 Reflect.get() 对数组和普通对象的处理方式是一致的——数组索引本质上是字符串形式的数字键。
示例四:receiver 参数与 getter 中的 this 绑定
这是 Reflect.get() 真正区别于普通属性访问的地方。receiver 参数允许你在触发 getter 时指定 this 的值。
// 学员信息对象,包含一个 getter 属性
const studentRecord = {
firstName: 'Ming',
lastName: 'Li',
courseCount: 5,
// getter 属性:this 指向调用它的对象
get fullProfile() {
return `${this.firstName} ${this.lastName} - 已完成 ${this.courseCount} 门课程`;
},
get certificationLevel() {
if (this.courseCount >= 10) return '高级';
if (this.courseCount >= 5) return '中级';
return '初级';
}
};
// 另一个对象,用于替代 this 的绑定
const anotherStudent = {
firstName: 'Xiao',
lastName: 'Wang',
courseCount: 12
};
// 常规读取:this 指向 studentRecord
console.log(Reflect.get(studentRecord, 'fullProfile'));
// 输出: 'Ming Li - 已完成 5 门课程'
console.log(Reflect.get(studentRecord, 'certificationLevel'));
// 输出: '中级'
// 使用 receiver 参数:让 getter 中的 this 指向 anotherStudent
const profileWithReceiver = Reflect.get(studentRecord, 'fullProfile', anotherStudent);
console.log(profileWithReceiver);
// 输出: 'Xiao Wang - 已完成 12 门课程'
const levelWithReceiver = Reflect.get(studentRecord, 'certificationLevel', anotherStudent);
console.log(levelWithReceiver);
// 输出: '高级'
// 对比:不使用 receiver 时的结果
console.log(studentRecord.fullProfile); // 始终基于 studentRecord 的数据
这个例子展示了 receiver 参数的核心价值:你可以在不修改原对象的情况下,借用另一个对象的数据来执行 getter 计算。这在实现在理模式或装饰器模式时相当实用。
深入理解:receiver 参数在原型链场景中的作用
receiver 参数在涉及原型链继承时有一个不太明显但很重要的行为:它能确保 getter 正确识别当前操作的真实对象。
// 父类对象,包含一个依赖 this 的 getter
const parentCourse = {
basePrice: 200,
get finalPrice() {
// 如果子对象有自己的 discount,使用子对象的;否则默认 0
return this.basePrice * (1 - (this.discount || 0));
}
};
// 子对象继承父对象,并覆盖 basePrice 和添加 discount
const childCourse = Object.create(parentCourse);
childCourse.basePrice = 300;
childCourse.discount = 0.15; // 15% 折扣
// 不使用 receiver:getter 中的 this 指向 parentCourse
console.log(Reflect.get(parentCourse, 'finalPrice'));
// 输出: 200(parentCourse.discount 不存在,按 0 处理)
// 不使用 receiver 读取子对象:getter 被继承,this 正确指向 childCourse
console.log(Reflect.get(childCourse, 'finalPrice'));
// 输出: 255(300 * 0.85)
// 显式指定 receiver:行为与上述一致,因为属性查找本身就会传递正确的 receiver
console.log(Reflect.get(childCourse, 'finalPrice', childCourse));
// 输出: 255
// 有趣的用法:用 parentCourse 的 getter,但 this 指向一个不同的对象
const thirdObject = { basePrice: 500, discount: 0.2 };
console.log(Reflect.get(parentCourse, 'finalPrice', thirdObject));
// 输出: 400(500 * 0.8)
项目开发中的选用建议
在日常编码中,我的选择标准大致是这样:
-
普通对象属性读取:直接用
obj.prop或obj[key],语法简洁,团队里大家都熟悉。 -
需要传递属性读取操作为回调:比如
array.map(Reflect.get)配合柯里化使用时,Reflect.get是很好的选择。 -
在 Proxy 的 get 中:必须使用
Reflect.get()并正确传递receiver,否则可能破坏原型链上的 getter 行为。 -
需要动态控制 getter 的 this 指向:这是
Reflect.get()独有的能力,其他方式无法替代。
没有哪种方式是万能的,根据场景选择合适的工具,才是写代码该有的态度。