如果你声明了一个数据为对象类型,正常来说,你认为它适合用对象来表示,并且希望使用对象的特征和相关的操作,这也就意味着你应该慎重去重写对象现有的一切特征。
但是特殊的情况下,依然提供一些方式去修改或者添加某些对象的特征,比如修改对象的原型,删除某个对象属性,重写某个对象的方法等。
一、正确的调用原型上方法
几乎所有的对象都是Object的实例,并从Object.prototype继承属性和方法,并同时可以重写这些数据。
唯一具有不可变原型的对象——Object.prototype的原型始终为null且不可更改。
如果对象实例明确没有重写过对象默认的一切操作的,可以正常使用对象的所有方法。
如果你从外部输入接收对象,同时希望调用Object.prototype上的方法,应该通过call()直接在目标对象上调用,而不是直接通过实例来调用,防止因目标对象上重写了原型上的方法导致意外结果。
const obj = {
foo: 1,
// 如果可能的话,你不应该在自己的对象上定义这样的方法
// 对象方法调用和对象的隐蔽性,这意味着在重写对象原型上的方法
// 但是如果你从外部输入接收对象,可能无法防止这种情况的发生
propertyIsEnumerable() {
return false;
},
};
obj.propertyIsEnumerable("foo"); // false;预期外的结果
Object.prototype.propertyIsEnumerable.call(obj, "foo"); // true;预期的结果
二、修改对象原型
1、Object.create(proto[, propertiesObject])
proto:新对象的原型对象。
propertiesObject(可选):一个对象,其属性和属性描述符将会被复制到新对象上。
返回一个新的对象。
var obj = {
a: 1,
b: 2
};
var newObject = Object.create(obj);
console.log(newObject.a); // 输出:1
console.log(newObject.b); // 输出:2
var obj = {
a: 1,
b: 2
};
var newObject = Object.create(obj, {
prop1: {
value: 'value1',
writable: true,
enumerable: true,
configurable: true
},
prop2: {
value: 'value2',
writable: false,
enumerable: false,
configurable: false
}
});
console.log(newObject.prop1); // 输出:'value1'
console.log(newObject.prop2); // 输出:'value2'
2、Object.assign(obj.prototype, newObjPrototype)
const personPrototype = {
greet() {
console.log(`你好,我的名字是 ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// 或
// Person.prototype.greet = personPrototype.greet;
3、Object.setPrototypeOf(obj, prototype)
const obj = {};
const parent = { foo: 'bar' };
console.log(obj.foo);
// Expected output: undefined
Object.setPrototypeOf(obj, parent);
console.log(obj.foo);
三、null原型
将现有对象的原型设置成null
// 字面量创建的
const obj = Object.create(null);
const obj2 = { __proto__: null };
Object.setPrototypeOf(obj, null);
四、判断对象上存在某个key
不包含原型链上的
1. hasOwnProperty 方法
hasOwnProperty 方法可以检查对象自身是否含有指定的键,不包括原型链上的键。
var obj = { key: 'value' };
console.log(obj.hasOwnProperty('key')); // 输出:true
2. Object.prototype.hasOwnProperty.call()
这是一种显式调用 hasOwnProperty 方法的方式,可以用来检查对象自身是否含有指定的键,不包括原型链上的键。
var obj = { key: 'value' };
console.log(Object.prototype.hasOwnProperty.call(obj, 'key')); // 输出:true
3. Object.keys() 或 Object.entries()
通过 Object.keys()获取到自由属性的key,不包括原型链上的键
var obj = { key: 'value' };
console.log(Object.keys(obj).includes('key')); // 输出:true
或者使用 Object.entries():
var obj = { key: 'value' };
console.log(Object.entries(obj).some(([key]) => key === 'key')); // 输出:true
4. Object.getOwnPropertyDescriptor()
通过 Object.getOwnPropertyDescriptor() 获取对象某个键的属性描述符,如果该键存在,则返回一个属性描述符对象;如果不存在,则返回 undefined。
var obj = { key: 'value' };
console.log(Object.getOwnPropertyDescriptor(obj, 'key') !== undefined); // 输出:true
5.Object.hasOwn()
如果指定属性是指定对象的自有属性,则返回 true,否则返回 false。如果该属性是继承的或不存在,则返回 false。
const proto = { b: 2 };
const obj = Object.create(proto);
obj.a = 1;
console.log(Object.hasOwn(obj, 'a')); // true,因为 'a' 是 obj 的自有属性
console.log(Object.hasOwn(obj, 'b')); // false,因为 'b' 不是 obj 的自有属性,而是其原型链上的属性
包含原型链上
1. in 操作符
in 操作符可以检查对象自身或其原型链上是否存在指定的键。
var obj = { key: 'value' };
console.log('key' in obj); // 输出:true
2. Reflect.has()
Reflect.has() 方法是 in 操作符的函数形式,可以用来检查对象自身或其原型链上是否存在指定的键。
var obj = { key: 'value' };
console.log(Reflect.has(obj, 'key')); // 输出:true
五、判断原型对象
1、Object.prototype.isPrototypeOf()
用于检查一个对象是否存在于另一个对象的原型链中。
A.prototype.isPrototypeOf(B) ,A是否在B的原型链上
function Foo() {}
function Bar() {}
function Car() {}
Bar.prototype = Object.create(Foo.prototype);
const bar = new Bar();
console.log(Foo.prototype.isPrototypeOf(bar));
// Expected output: true
console.log(Bar.prototype.isPrototypeOf(bar));
// Expected output: true
console.log(Car.prototype.isPrototypeOf(bar));
// Expected output: false
console.log(Object.prototype.isPrototypeOf(bar));
// Expected output: true
由上面的例子可以看出,构造函数原型对象的constructor是自身,构造函数的原型对象的原型对象的constructor是Object,再往后就是null
2、instanceof
在表达式 object instanceof AFunction 中,会检查 object 的原型链是否与 AFunction.prototype 匹配,而不是与 AFunction本身匹配。
function Constructor() {}
const instance = new Constructor();
console.log(instance instanceof Constructor); // true,因为 instance 是 Constructor 的实例
六、包装对象
包装对象(Wrapper Object)是指将原始数据类型(如字符串、数字和布尔值)转换为对象的一种机制。这样可以调用这些对象的方法和属性。
JavaScript 会自动创建这些包装对象,并在操作完成后销毁它们。
let str = "hello";
console.log(str.length); // 5
console.log(str.toUpperCase()); // HELLO
let num = 123;
console.log(num.toString()); // "123"
console.log(num.toFixed(2)); // "123.00"
let bool = true;
console.log(bool.toString()); // "true"
避免不必要的手动包装对象,因为这会增加内存开销和执行时间
let str = "hello";
// 手动创建包装对象
let strObj = new String("hello");
console.log(typeof str); // "string"
console.log(typeof strObj); // "object"
七、toString() 和 valueOf()
这两个方法都是定义在 Object.prototype 上的,但是它们有不同的用途和返回值:
valueOf()
const obj = { name: "Kimi" };
console.log(obj.valueOf()); // 输出:{ name: "Kimi" }
// 包装对象
// Number
const numObj = new Number(10);
console.log(numObj.valueOf()); // 输出:10
// String
const strObj = new String("Hello");
console.log(strObj.valueOf()); // 输出:"Hello"
// Boolean
const boolObj = new Boolean(true);
console.log(boolObj.valueOf()); // 输出:true
// 其他内置对象
// Date
const date = new Date();
console.log(date.valueOf()); // 输出:时间戳
// Array
const arr = [1, 2, 3];
console.log(arr.valueOf()); // 输出:[1, 2, 3]
toString()
const obj = { name: "Kimi" };
console.log(obj.toString()); // 输出:[object Object]
const strObj = new String("Hello");
console.log(strObj.toString()); // 输出:"Hello"
const numObj = new Number(10);
console.log(numObj.toString()); // 输出:"10"
const boolObj = new Boolean(true);
console.log(boolObj.toString()); // 输出:"true"
const arr = [1, 2, 'Kimi', true];
console.log(arr.toString()); // 输出:"1,2,Kimi,true"