Object原型上的一些操作

204 阅读5分钟

如果你声明了一个数据为对象类型,正常来说,你认为它适合用对象来表示,并且希望使用对象的特征和相关的操作,这也就意味着你应该慎重去重写对象现有的一切特征。

但是特殊的情况下,依然提供一些方式去修改或者添加某些对象的特征,比如修改对象的原型,删除某个对象属性,重写某个对象的方法等。

一、正确的调用原型上方法

几乎所有的对象都是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"