一、Reflect 对象
Reflect 是 JavaScript 中的一个全局对象,它提供了一些方法,用于对对象进行低级操作。这些方法与那些在 Object 对象上定义的方法相似,但有以下几个不同点:
- 函数化:
Reflect对象的方法是函数化的,不像有些对象方法是原型方法。例如,Object.defineProperty是一个方法,而Reflect.defineProperty是一个函数。 - 返回布尔值:在很多情况下,
Reflect方法返回布尔值,表示操作是否成功。这与传统的抛出异常的行为不同。 - 操作对象:
Reflect提供的很多方法都可以被用来直接操作对象,比如获取、设置和删除属性,以及函数调用。
以下是 Reflect 对象的一些常用方法及其作用:
- Reflect.apply(target, thisArgument, argumentsList) :与
Function.prototype.apply类似,调用一个目标函数,参数是目标函数、this 值和参数列表。 - Reflect.construct(target, argumentsList[, newTarget]) :类似于
new target(...argumentsList),构造一个新对象。 - Reflect.defineProperty(target, propertyKey, attributes) :类似于
Object.defineProperty,定义对象的属性并返回一个布尔值。 - Reflect.deleteProperty(target, propertyKey) :类似于
delete target[propertyKey],删除对象的属性并返回一个布尔值。 - Reflect.get(target, propertyKey[, receiver]) :获取对象的属性值。
- Reflect.set(target, propertyKey, value[, receiver]) :设置对象的属性值并返回一个布尔值。
- Reflect.has(target, propertyKey) :类似于
propertyKey in target,检查对象是否具有某个属性。 - Reflect.ownKeys(target) :返回一个包含所有自身属性(包括不可枚举属性,但不包括 Symbol 属性)的数组。
- Reflect.isExtensible(target) :类似于
Object.isExtensible,检查对象是否是可扩展的。 - Reflect.preventExtensions(target) :类似于
Object.preventExtensions,使对象变为不可扩展并返回一个布尔值。 - Reflect.getPrototypeOf(target) :类似于
Object.getPrototypeOf,返回对象的原型。 - Reflect.setPrototypeOf(target, prototype) :类似于
Object.setPrototypeOf,设置对象的原型并返回一个布尔值。
// 使用 Reflect.apply 调用函数
function greet(name) {
return `Hello, ${name}!`;
}
console.log(Reflect.apply(greet, undefined, ['Alice'])); // 输出 "Hello, Alice!"
// 使用 Reflect.construct 创建对象
class Person {
constructor(name) {
this.name = name;
}
}
const person = Reflect.construct(Person, ['Bob']);
console.log(person.name); // 输出 "Bob"
// 使用 Reflect.defineProperty 定义属性
const obj = {};
Reflect.defineProperty(obj, 'x', { value: 42 });
console.log(obj.x); // 输出 42
// 使用 Reflect.deleteProperty 删除属性
Reflect.deleteProperty(obj, 'x');
console.log(obj.x); // 输出 undefined
// 使用 Reflect.get 获取属性值
const myObject = { a: 1, b: 2 };
console.log(Reflect.get(myObject, 'a')); // 输出 1
// 使用 Reflect.set 设置属性值
Reflect.set(myObject, 'c', 3);
console.log(myObject.c); // 输出 3
// 使用 Reflect.has 检查属性
console.log(Reflect.has(myObject, 'b')); // 输出 true
// 使用 Reflect.ownKeys 获取所有属性键
console.log(Reflect.ownKeys(myObject)); // 输出 ["a", "b", "c"]
// 使用 Reflect.isExtensible 检查对象是否可扩展
console.log(Reflect.isExtensible(myObject)); // 输出 true
// 使用 Reflect.preventExtensions 防止对象扩展
Reflect.preventExtensions(myObject);
console.log(Reflect.isExtensible(myObject)); // 输出 false
// 使用 Reflect.getPrototypeOf 获取对象原型
console.log(Reflect.getPrototypeOf(myObject)); // 输出 {} (Object.prototype)
// 使用 Reflect.setPrototypeOf 设置对象原型
const newProto = { z: 10 };
Reflect.setPrototypeOf(myObject, newProto);
console.log(Reflect.getPrototypeOf(myObject)); // 输出 { z: 10 }
Reflect 对象的这些方法提供了一种更加一致和函数化的方式来操作 JavaScript 对象,并且在使用 Proxy 对象时,通常会用到这些方法。
作为函数的本身运行起来更加轻便简洁,更加符合函数式编程思想,相比较原型链上面的逐层冒泡要来的轻便的多,同时也不容易被友方单位所篡改。那么对象和方法的区别到底有哪些?
二、对象与方法的区别
在 JavaScript 中,方法和函数本质上都是函数,但它们的使用场景和调用方式有所不同。以下是它们的主要区别:
-
定义与调用方式:
- 函数(Function):是独立的,可在任何地方调用的代码块。定义方式为
function关键字或箭头函数。 - 方法(Method):是对象的属性,其值为一个函数。通过对象调用时,该函数称为方法。(原型链)
// 函数定义 function myFunction() { console.log("This is a function"); } // 方法定义 const myObject = { myMethod: function() { console.log("This is a method"); } }; // 调用函数 myFunction(); // 输出 "This is a function" // 调用方法 myObject.myMethod(); // 输出 "This is a method" - 函数(Function):是独立的,可在任何地方调用的代码块。定义方式为
-
上下文(Context)与
this关键字:- 函数:在全局上下文或普通调用时,
this通常指向全局对象(浏览器中为window,Node.js 中为global)。 - 方法:通过对象调用时,
this指向调用该方法的对象。
const name = "Global"; function showName() { console.log(this.name); } const person = { name: "Alice", showName: showName }; showName(); // 输出 "Global" (在浏览器中) person.showName(); // 输出 "Alice" - 函数:在全局上下文或普通调用时,
-
箭头函数的
this绑定:- 箭头函数:不绑定自己的
this,它会捕获所在上下文的this值,因此不能用作构造函数,也不能作为对象的方法。
const myObject = { name: "Alice", myMethod: () => { console.log(this.name); } }; myObject.myMethod(); // 输出 undefined - 箭头函数:不绑定自己的
-
构造函数:
- 函数:可以作为构造函数使用,用来创建对象实例。通过
new关键字调用时,函数内部的this指向新创建的对象。 - 方法:一般不会作为构造函数使用。
function Person(name) { this.name = name; } const alice = new Person("Alice"); console.log(alice.name); // 输出 "Alice" - 函数:可以作为构造函数使用,用来创建对象实例。通过
总结:
- 函数 是独立的代码块,可以在任何地方调用。
- 方法 是对象的属性,调用时与对象绑定。
this的绑定方式在函数和方法中有所不同,尤其是在箭头函数中。
三、JSON
经常用到 JSON.parse() 与 JSON.stringify() 这里面其实有很多细节,若不了解,会大大增加日常编程中的问题排查复杂度,同时也可以利用一些点来省掉数据格式化的过程
JSON.stringify()
JSON.stringify(value[, replacer [, space]])
第二个参数接受的类型:
- 函数,在序列化的过程中,被序列化的值的每个属性都会经过该函数的转换和处理
- 数组,则只有包含在这个数组中的属性名会被序列化到最终的字符串中
- 为 null 或 undefined ,则所有属性都会被序列化
第三个参数指定缩进用的空白字符串,用来美化输出,平时用的比较少:
- 数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格
- 字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格
- 若为 undefined 或 null,将没有空格
注意:
- 当在循环引用时会抛出异常
TypeError("cyclic object value")(循环对象值) - 当尝试去转换
BigInt类型的值会抛出TypeError("BigInt value can't be serialized in JSON")(BigInt 值不能 JSON 序列化)
JSON.stringify()将值转换为相应的 JSON 格式:
- 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。
- 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){})orJSON.stringify(undefined).- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
- 所有以 symbol 为属性键的属性都会被完全忽略掉,即便
replacer参数中强制指定包含了它们。 - Date 日期调用了 toJSON() 将其转换为了 string 字符串(同 Date.toISOString()),因此会被当做字符串处理。
- NaN 和 Infinity 格式的数值及 null 都会被当做 null。
- 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
- 上面的一些转换方式是可以进行简单利用的,如果属性不想被序列化,可以赋值为 undefined 、函数 或 symbol,而不用辛苦的 delete ,那么如果转化之后某个属性丢了,则可能也是因为这个原因导致的
- NAN、Infinity 会转化为 null,即 number 转换成 null 了。所以如果使用类型判断的话也得考虑到这一点。
- 重写 toJSON 方法,可以自定义序列化过程,但是并不推荐
JSON.stringify({}); // '{}'
JSON.stringify(true); // 'true'
JSON.stringify("foo"); // '"foo"'
JSON.stringify([1, "false", false]); // '[1,"false",false]'
JSON.stringify({ x: 5 }); // '{"x":5}'
JSON.stringify({ x: 5, y: 6 });
// "{"x":5,"y":6}"
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'
JSON.stringify({ x: undefined, y: Object, z: Symbol("") });
// '{}'
JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'
JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'
JSON.stringify({ [Symbol.for("foo")]: "foo" }, [Symbol.for("foo")]);
// '{}'
JSON.stringify({ [Symbol.for("foo")]: "foo" }, function (k, v) {
if (typeof k === "symbol") {
return "a symbol";
}
});
// undefined
// 不可枚举的属性默认会被忽略:
JSON.stringify(
Object.create(null, {
x: { value: "x", enumerable: false },
y: { value: "y", enumerable: true },
}),
);
// "{"y":"y"}"
replacer 参数可以是一个函数或者一个数组。作为函数,它有两个参数,键(key)和值(value),它们都会被序列化。
在开始时,replacer 函数会被传入一个空字符串作为 key 值,代表着要被 stringify 的这个对象。随后每个对象或数组上的属性会被依次传入。
函数应当返回 JSON 字符串中的 value, 如下所示:
- 如果返回一个
Number, 转换成相应的字符串作为属性值被添加入 JSON 字符串。 - 如果返回一个
String, 该字符串作为属性值被添加入 JSON 字符串。 - 如果返回一个
Boolean, "true" 或者 "false" 作为属性值被添加入 JSON 字符串。 - 如果返回任何其他对象,该对象递归地序列化成 JSON 字符串,对每个属性调用 replacer 方法。除非该对象是一个函数,这种情况将不会被序列化成 JSON 字符串。
- 如果返回 undefined,该属性值不会在 JSON 字符串中输出。
注意: 不能用 replacer 方法,从数组中移除值(values),如若返回 undefined 或者一个函数,将会被 null 取代。
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, replacer);
JSON 序列化结果为 {"week":45,"month":7}.
如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是该对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:
var obj = {
foo: "foo",
toJSON: function () {
return "bar";
},
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({ x: obj }); // '{"x":"bar"}'
JSON.parse()
JSON.parse(text[, reviver])
reviver: 转换器,如果传入该参数 (函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前。
返回值:与给定的 JSON text 相对应的 Object、Array、string、number、boolean 或者 null 值。
如果指定了 reviver 函数,则解析出的 JavaScript 值(解析值)会经过一次转换后才将被最终返回(返回值)。更具体点讲就是:解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中。如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值。
当遍历到最顶层的值(解析值)时,传入 reviver 函数的参数会是空字符串 ""(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了),当前的 this 值会是 {"": 修改过的解析值},在编写 reviver 函数时,要注意到这个特例。(这个函数的遍历顺序依照:从最内层开始,按照层级顺序,依次向外遍历)