Skip to content

对象-Object

1.基本概念

概念

英文:object obj

说明:一堆无序数据的集合

对象的创建

字面量创建

js
var obj = {} // 空对象

var obj3 = {      //对象可以存放数据,数组,函数 ,也可以存放对象
    name: '李雷',
    age: 18, 
    sex: '男', 
    list: [111,222,333,444,555], 
    sayHi: function(){
        console.log("我是一个方法") 
    }
} 
console.log(obj3);

构造函数创建

js
var obj = new Object() 	//空对象
var obj = new Object({
    a:1,
    b:'hello'
})

对象的操作

语法:对象.键

可以实现对 对象的 查(访问)  改(改变)  增(增加)

删除对象的成员: delete 对象.键

js
// 查 
console.log(obj3.name); // '李雷'
console.log(obj3.list); // [111, 222, 333, 444, 555] 

// 改 ---- 针对已有的 键 赋值就是 改变 
obj3.sex = "女"; 

// 增 ---- 针对没有的 键 赋值就是 增加
obj3.abc = 'xyz'; 

// 删
delete obj3.age;
console.log(obj3);

对象的遍历

JS专门用来遍历对象的循环结构体:for...in。

js
var obj = { 
    uname:"夏至", 
    sex:"男", 
    age:"18岁",
    character:function(){
        alert("想找对象"); 
    }, 
    like:['唱','跳','rap','篮球'] 
} 
console.log(obj.uname); 

for( var item in obj){ 	// 变量item就是对象的键(属性) 
    console.log(item); 
    console.log(obj[item]);
}

注意:

  1. 如果对象的键以变量形式存在则不能通过对象.键实现针对对象的访问
    1. 如果通过对象.键访问,则会给对象添加新成员
  2. 而应该通过 对象[键] 的形式实现访问

2.对象的原始写法

对象的原始写法中,所有属性名都是字符串,因此我们要用引号包裹起来

js
var obj={
    "name":"xiazhi",
    "age":18,
    "dream house":{
        "area":"180m*m",
        "city":"苏州"
    }
}

为了书写方便,当对象的属性名式纯数字符合标识符规范时,可以省略引号

对象的原始读取:

  • 通过数组的形式,obj["key"]
  • 若符合标识符规范,可以通过.符号链接属性名,从而访问对象属性
js
console.log(obj["name"])      // xiazhi
console.log(obj["age"])       // 18
console.log(obj["dream house"]["area"])   // 180m*m

这样的好处是,可以在数组里通过变量访问对象的属性

js
var val="name"
console.log(obj[val])	// xiazhi

由此,我们可以通过for..in更方便的遍历对象

3.对象静态方法

更多Object方法Object - JavaScript | MDN (mozilla.org)

Object.defineProperty()

Object.defineProperty()的作用就是用于定义一个对象的属性

js
Object.defineProperty(obj, prop, desc)

参数

  • obj:需要定义属性的当前对象
  • prop:当前需要定义的属性名
  • desc:一个对象,包含属性描述符,如下
    • value:any,表示所添加属性对应的,默认是undefine
    • configurable:boolean,表示是否可是从对象中删除此属性,以及是否可以重新定义属性,默认false
    • enumerable:boolean,表示该属性是否可以被枚举,默认false
    • writable:boolean,表示该属性是否可以被修改,默认false
    • get():function,当获取对象属性的值时,会调用此函数
    • set(value):function,当对象属性的值被修改时,会调用此函数
      • value:设置属性的新值
js
var obj = {};
Object.defineProperty(obj, "newKey", {
    value: any,
    configurable: true | false,
    writable: true | false,
    enumerable: true | false,
    get: function () {} | undefined,
    set: function (value) {} | undefined,
});

WARNING

如果设置了 set 或 get, 就不能设置 writable 和 value 中的任何一个,否则会报以下错误

Cannot both specify accessors and a value or writable attribute

Object.defineProperties()

直接在一个对象上定义新的属性或修改现有的属性,并返回该对象。

js
Object.defineProperties(obj, props)

参数:

  • obj:在其上定义或修改属性的对象。
  • props:一个对象,其中每个键表示要定义或修改的属性的名称,键的值也是一个对象,其中包含属性修饰符
js
var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
  // ...
});

Object.entries()

Object.entries()接受一个可以返回其可枚举属性的键值对的对象,可把一个对象的键和值拆分并组成成一个数组

js
Object.entries(obj)

参数:

  • obj:一个对象

返回值:一个由给定对象自有的可枚举字符串键属性的键值对组成的数组。每个键值对都是一个包含两个元素的数组:第一个元素是属性的键(始终是字符串),第二个元素是属性值。

参数为对象

js
const obj={
    a:1,
    b:'xiazhi',
    c:true
}

const res=Object.entries(obj)
console.log(res)	// [ [ 'a', 1 ], [ 'b', 'xiazhi' ], [ 'c', true ] ]

参数为数组

js
const arr=[1,2,3,4,5]

const res=Object.entries(arr)
console.log(res)	// [ [ '0', 1 ], [ '1', 2 ], [ '2', 3 ], [ '3', 4 ], [ '4', 5 ] ]

参数为数组包含对象

js
const arr=[1,2,{a:1},{b:2}]

const res=Object.entries(arr)
console.log(res)	// [ [ '0', 1 ], [ '1', 2 ], [ '2', { a: 1 } ], [ '3', { b: 2 } ] ]

参数为数组包纯对象

js
const arr=[{a:1},{b:2}]

const res=Object.entries(arr)
console.log(res)	// [ [ '0', { a: 1 } ], [ '1', { b: 2 } ] ]

Object转为Map

js
const obj = { name: 'xiaoming', age: 'seven',sex: 'man', grade: 'four' }; 

const map = new Map(Object.entries(obj)); 
console.log(map); 
/*
Map(4) {
  'name' => 'xiaoming',
  'age' => 'seven',
  'sex' => 'man',
  'grade' => 'four'
}
*/

Object.fromEntries()

Object.fromEntries()方法是Object.entries()的逆操作,将键值对列表转换为一个对象。

js
Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

Object.create()

Object.create(obj) 静态方法以一个现有对象作为原型,创建一个新对象。

js
Object.create(proto)
Object.create(proto, propertiesObject)

参数:

  • proto:新创建对象的原型对象。
  • propertiesObject:如果该参数被指定且不为 undefined,则该传入对象可枚举的自有属性将为新创建的对象添加具有对应属性名称的属性描述符。这些属性对应于 Object.defineProperties() 的第二个参数。

返回值:根据指定的原型对象和属性创建的新对象。

在下面的案例中,obj即作为obj1的原型

js
const obj={
    a:1,
    say:()=>{
        console.log('hi');
    }

}

const obj1=Object.create(obj,{
    like:{
        value:'牛肉'
    },
    hobby:{
        value:()=>{
            console.log('吃');
        }
    }
})


console.log(obj1.like);		// '牛肉'
obj1.hobby()			// '吃'
console.log(obj1.a);	// 1
obj1.say()				// 'hi'

Object.assign()

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。

它将返回目标对象。是ES6提供的一个Object的静态方法

js
Object.assign(target, ...sources)

参数:

  • target:目标对象,接收源对象属性的对象,也是修改后的返回值。
  • sources:源对象,包含将被合并的属性。

assign()返回的对象地址是第一个参数的地址

js
const obj = {
  a: 1,
};
const obj2 = {
  b: 2,
};

const obj3 = Object.assign(obj, obj2);

console.log(obj3)       // { a: 1, b: 2 }

console.log(obj3===obj)     // true

浅拷贝

js
var obj1 ={
	a:{n:11,m:22,x:33},
	b:[1,2,3,4],
	c:123
}
// 因为assige返回的对象地址是 第一个参数的地址
// 所以为了实现浅拷贝,需要给第一个参数传一个空对象
var obj2 = Object.assign({},obj1);

Object.keys()

返回对象所有的,以数组形式存放

js
Object.keys(obj)

参数:

  • obj:一个对象。

返回值:一个由给定对象自身可枚举的字符串键属性键组成的数组。

js
const obj = {
  a: 1,
  b: "aa",
  c: true,
  d: [1, 2],
  e: { f: 3 },
};

console.log(Object.keys(obj))  //  [ 'a', 'b', 'c', 'd', 'e' ]

Object.values()

返回对象所有的,以数组形式存放

js
Object.values(obj)

参数:

  • obj:一个对象。

返回值:一个包含了给定对象的自有可枚举字符串键属性值的数组。

js
const obj = {
  a: 1,
  b: "aa",
  c: true,
  d: [1, 2],
  e: { f: 3 },
};

console.log(Object.values(obj))  // [ 1, 'aa', true, [ 1, 2 ], { f: 3 } ]

Object.freeze()

Object.freeze(obj)方法可以冻结一个对象。一个被冻结的对象再也不能被修改

冻结了一个对象则:

  • 不能向这个对象添加新的属性
  • 不能删除已有属性
  • 不能修改该对象已有属性的可枚举性、可配置性、可写性
  • 不能修改已有属性的值。

此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

js
const obj = {
  prop: 42
};

Object.freeze(obj);

obj.prop = 33;
// Throws an error in strict mode

console.log(obj.prop);
// Expected output: 42

Object.isFrozen()

判断一个对象是否被冻结

js
Object.isFrozen(obj)

参数:

  • obj:要检测的对象。

返回值:指示给定对象是否被冻结的布尔值

js
const object1 = {
  property1: 42
};

console.log(Object.isFrozen(object1));
// Expected output: false

Object.freeze(object1);

console.log(Object.isFrozen(object1));
// Expected output: true

Object.is()

Object.is() 确定两个值是否为相同值。如果以下其中一项成立,则两个值相同:

  • 都是 undefined
  • 都是 null
  • 都是 true 或者都是 false
  • 都是长度相同、字符相同、顺序相同的字符串
  • 都是相同的对象(意味着两个值都引用了内存中的同一对象)
  • 都是 BigInt 且具有相同的数值
  • 都是 symbol 且引用相同的 symbol 值
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都有相同的值,非零且都不是 NaN
js
Object.is(value1, value2)

参数:

  • value1:要比较的第一个值
  • value2:要比较的第二个值

返回值:一个布尔值,指示两个参数是否为相同的值。

TIP

Object.is()== 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 "" == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。

Object.is()等价于 === 运算符。Object.is()=== 之间的唯一区别在于它们处理 NaN 和带符号的 0 值的时候。=== 运算符(和 == 运算符)将数值 -0+0 视为相等,但是会将 NaN 视为彼此不相等。

js
// 案例 1:评估结果和使用 === 相同
Object.is(25, 25); // true
Object.is("foo", "foo"); // true
Object.is("foo", "bar"); // false
Object.is(null, null); // true
Object.is(undefined, undefined); // true
Object.is(window, window); // true
Object.is([], []); // false
const foo = { a: 1 };
const bar = { a: 1 };
const sameFoo = foo;
Object.is(foo, foo); // true
Object.is(foo, bar); // false
Object.is(foo, sameFoo); // true

// 案例 2: 带符号的 0
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(-0, -0); // true

// 案例 3: NaN
Object.is(NaN, 0 / 0); // true
Object.is(NaN, Number.NaN); // true

Object.getOwnPropertyDescriptor()

获取对象中某个自有属性的属性描述符,自有属性也就是只存在于本身,不包括原型链

js
Object.getOwnPropertyDescriptor(obj, prop)

参数:

  • obj:一个对象
  • prop:对象中的属性

返回值:如果指定的属性存在于对象上,则返回其属性描述符,否则返回 undefined。属性描述符包含以下内容

  • value:与属性关联的值(仅限数据描述符)。

  • writable:当且仅当与属性关联的值可以更改时,为 true(仅限数据描述符)。

  • get:作为属性 getter 的函数,如果没有 getter 则为undefined(仅限访问器描述符)。

  • set:作为属性 setter 的函数,如果没有 setter 则为 undefined(仅限访问器描述符)。

  • configurable:当且仅当此属性描述符的类型可以更改且该属性可以从相应对象中删除时,为 true

  • enumerable:当且仅当此属性在相应对象的属性枚举中出现时,为 true

js
const obj = {
  name: "ikun",
};

const desc = Object.getOwnPropertyDescriptor(obj, "name");
console.log(desc);  

// { value: 'ikun', writable: true, enumerable: true, configurable: true }

Object.getOwnPropertyDescriptors()

返回给定对象的所有自有属性描述符。

js
Object.getOwnPropertyDescriptors(obj)

参数:

  • obj:一个对象

返回值:一个包含给定对象的所有自有属性描述符的对象。如果没有属性,则可能是一个空对象。

js
const obj = {
    name: "ikun",
    age: 18,
    hobby: "唱、跳、rap、篮球",
};
const desc = Object.getOwnPropertyDescriptors(obj);
console.log(desc);

/*
{
  name: {
    value: 'ikun',
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: { value: 18, writable: true, enumerable: true, configurable: true },
  hobby: {
    value: '唱、跳、rap、篮球',
    writable: true,
    enumerable: true,
    configurable: true
  }
}
*/

Object.getOwnPropertyNames()

返回一个数组,其包含给定对象中所有自有属性(包括不可枚举属性,但不包括使用 symbol 值作为名称的属性)。

js
Object.getOwnPropertyNames(obj)

参数:

  • 一个对象,其自有的可枚举和不可枚举属性的名称被返回。

返回值:在给定对象上找到的自有属性对应的字符串数组。

js
const arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort());
// ["0", "1", "2", "length"]

// 类数组对象
const obj = { 0: "a", 1: "b", 2: "c" };
console.log(Object.getOwnPropertyNames(obj).sort());
// ["0", "1", "2"]

Object.getOwnPropertyNames(obj).forEach((val, idx, array) => {
  console.log(`${val} -> ${obj[val]}`);
});
// 0 -> a
// 1 -> b
// 2 -> c

// 不可枚举属性
const myObj = Object.create(
  {},
  {
    getFoo: {
      value() {
        return this.foo;
      },
      enumerable: false,
    },
  },
);
myObj.foo = 1;

console.log(Object.getOwnPropertyNames(myObj).sort()); // ["foo", "getFoo"]

如果你只想获取可枚举属性,请参见 Object.keys() 或使用 for...in 循环(请注意,这也将返回对象原型链中找到的可枚举属性,除非使用 hasOwn() 过滤)。

原型链上的属性不会被列出:

js
function ParentClass() {}
ParentClass.prototype.inheritedMethod = function () {};

function ChildClass() {
  this.prop = 5;
  this.method = function () {};
}
ChildClass.prototype = new ParentClass();
ChildClass.prototype.prototypeMethod = function () {};

console.log(Object.getOwnPropertyNames(new ChildClass()));
// ["prop", "method"]

只获取不可枚举的属性

这个方法使用Array.prototype.filter()函数从所有键(使用 Object.getOwnPropertyNames() 获得)的列表中过滤可枚举键(使用 Object.keys() 获得),从而仅以不可枚举键作为输出。

js
const target = myObject;
const enumAndNonenum = Object.getOwnPropertyNames(target);
const enumOnly = new Set(Object.keys(target));
const nonenumOnly = enumAndNonenum.filter((key) => !enumOnly.has(key));

console.log(nonenumOnly);

Object.getOwnPropertySymbols()

返回一个包含给定对象所有自有 Symbol 属性的数组。

js
Object.getOwnPropertySymbols(obj)

参数:

  • obj:要返回 Symbol 属性的对象。

返回值:在给定对象找到的所有自有 Symbol 属性的数组。

js
const obj = {};
const a = Symbol("a");
const b = Symbol.for("b");

obj[a] = "localSymbol";
obj[b] = "globalSymbol";

const objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols.length); // 2
console.log(objectSymbols); // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0]); // Symbol(a)

Object.groupBy()

兼容性检查

根据提供的回调函数返回的字符串值对给定可迭代对象中的元素进行分组。返回的对象具有每个组的单独属性,其中包含组中的元素的数组。

当分组名称可以用字符串表示时,应使用此方法。如果你需要使用某个任意值作为键来对元素进行分组,请改用 Map.groupBy() 方法。

js
Object.groupBy(items, callbackFn(element, index))

参数:

  • items:一个将进行元素分组的可迭代对象(例如 Array)。
  • callbackFn:对可迭代对象中的每个元素执行的函数。它应该返回一个值,可以被强制转换成属性键(字符串或 symbol),用于指示当前元素所属的分组。该函数被调用时将传入以下参数:
    • element:数组中当前正在处理的元素。
    • index:正在处理的元素在数组中的索引。

返回值:一个带有所有分组属性的 null 原型对象,每个属性都分配了一个包含相关组元素的数组。

TIP

Object.groupBy() 为可迭代对象中的每个元素调用一次提供的 callbackFn 函数。

回调函数应返回一个字符串或 symbol(不属于这两种类型的值会被强制转换为字符串),用于指示元素所属的分组。

callbackFn 的返回值会被用作 Map.groupBy() 返回的对象的键。每个键都有一个相关联的数组,其中包含回调函数返回相同值的所有元素。

返回的对象中的元素和原始可迭代对象中的元素相同(不是深拷贝)。更改元素的内部结构将反映在原始可迭代对象和返回的对象中。

js
const inventory = [
  { name: "芦笋", type: "蔬菜", quantity: 5 },
  { name: "香蕉", type: "水果", quantity: 0 },
  { name: "山羊", type: "肉", quantity: 23 },
  { name: "樱桃", type: "水果", quantity: 5 },
  { name: "鱼", type: "肉", quantity: 22 },
];

const result = Object.groupBy(inventory, ({ type }) => type);

/* 结果是:
{
  蔬菜: [
    { name: "芦笋", type: "蔬菜", quantity: 5 },
  ],
  水果: [
    { name: "香蕉", type: "水果", quantity: 0 },
    { name: "樱桃", type: "水果", quantity: 5 }
  ],
  肉: [
    { name: "山羊", type: "肉", quantity: 23 },
    { name: "鱼", type: "肉", quantity: 22 }
  ]
}
*/

我们还可以创建根据元素的一个或多个属性中的值推断的分组。下面是一个非常类似的示例,根据 quantity 字段的值将项目分为 okrestock 组。

js
function myCallback({ quantity }) {
  return quantity > 5 ? "ok" : "restock";
}

const result2 = Object.groupBy(inventory, myCallback);

/* 结果是:
{
  restock: [
    { name: "芦笋", type: "蔬菜", quantity: 5 },
    { name: "香蕉", type: "水果", quantity: 0 },
    { name: "樱桃", type: "水果", quantity: 5 }
  ],
  ok: [
    { name: "山羊", type: "肉", quantity: 23 },
    { name: "鱼", type: "肉", quantity: 22 }
  ]
}
*/

Object.hasown()

如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false

js
Object.hasOwn(obj, prop)

参数:

  • obj:要测试的 JavaScript 实例对象。
  • prop:要测试属性的 String 类型的名称或者 Symbol

返回值:如果指定的对象中直接定义了指定的属性,则返回 true;否则返回 false

TIP

如果指定的属性是该对象的直接属性——Object.hasOwn() 方法返回 true,即使属性值是 nullundefined

如果属性是继承的或者不存在,该方法返回 false。它不像in 运算符,这个方法不检查对象的原型链中的指定属性。

建议使用此方法替代 Object.prototype.hasOwnProperty(),因为它适用于使用 Object.create(null) 创建的对象,以及重写了继承的 hasOwnProperty() 方法的对象。尽管可以通过在外部对象上调用 Object.prototype.hasOwnProperty() 解决这些问题,但是 Object.hasOwn() 更加直观。

使用 hasOwn 去测试属性是否存在

以下代码展示了如何确定 example 对象中是否包含名为 prop 的属性。

js
const example = {};
Object.hasOwn(example, "prop"); // false——目标对象的属性 'prop' 未被定义

example.prop = "exists";
Object.hasOwn(example, "prop"); // true——目标对象的属性 'prop' 已被定义

example.prop = null;
Object.hasOwn(example, "prop"); // true——目标对象本身的属性存在,值为 null

example.prop = undefined;
Object.hasOwn(example, "prop"); // true——目标对象本身的属性存在,值为 undefined

直接属性和继承属性

以下示例区分了直接属性和通过原型链继承的属性:

js
const example = {};
example.prop = "exists";

// `hasOwn` 静态方法只会对目标对象的直接属性返回 true:
Object.hasOwn(example, "prop"); // 返回 true
Object.hasOwn(example, "toString"); // 返回 false
Object.hasOwn(example, "hasOwnProperty"); // 返回 false

// `in` 运算符对目标对象的直接属性或继承属性均会返回 true:
"prop" in example; // 返回 true
"toString" in example; // 返回 true
"hasOwnProperty" in example; // 返回 true

迭代对象的属性

要迭代对象的可枚举属性,你应该这样使用:

js
const example = { foo: true, bar: true };
for (const name of Object.keys(example)) {
  // …
}

但是如果你使用 for...in,你应该使用 Object.hasOwn() 跳过继承属性:

js
const example = { foo: true, bar: true };
for (const name in example) {
  if (Object.hasOwn(example, name)) {
    // …
  }
}

检查数组索引是否存在

Array中的元素被定义为直接属性,所以你可以使用 hasOwn() 方法去检查一个指定的索引是否存在:

js
const fruits = ["Apple", "Banana", "Watermelon", "Orange"];
Object.hasOwn(fruits, 3); // true ('Orange')
Object.hasOwn(fruits, 4); // false——没有定义的

hasOwnProperty 的问题案例

本部分证明了影响 hasOwnProperty 的问题对 hasOwn() 是免疫的。首先,它可以与重新实现的 hasOwnProperty() 一起使用:

js
const foo = {
  hasOwnProperty() {
    return false;
  },
  bar: "The dragons be out of office",
};

if (Object.hasOwn(foo, "bar")) {
  console.log(foo.bar); //true——重新实现 hasOwnProperty() 不会影响 Object
}

它也可以用于测试使用Object.create(null)创建的对象。这些对象不会继承自 Object.prototype,因此 hasOwnProperty() 方法是无法访问的。

js
const foo = Object.create(null);
foo.prop = "exists";
if (Object.hasOwn(foo, "prop")) {
  console.log(foo.prop); //true——无论对象是如何创建的,它都可以运行。
}

Object.isExtensible()

判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

js
Object.isExtensible(obj)

参数:

  • obj:要检查的对象。

返回值:指示给定对象是否可扩展的一个布尔值。

TIP

默认情况下,对象是可扩展的:可以向它们添加新属性,并且它们的 [[Prototype]] 可以被重新赋值。

可以使用 Object.preventExtensions()Object.seal()Object.freeze()Reflect.preventExtensions() 中的任一方法将对象标记为不可扩展。

js
// 新对象是可拓展的。
const empty = {};
Object.isExtensible(empty); // true

// 它们可以变为不可拓展的
Object.preventExtensions(empty);
Object.isExtensible(empty); // false

// 根据定义,密封对象是不可拓展的。
const sealed = Object.seal({});
Object.isExtensible(sealed); // false

// 根据定义,冻结对象同样也是不可拓展的。
const frozen = Object.freeze({});
Object.isExtensible(frozen); // false

Object.seal()

密封一个对象。密封一个对象会阻止其扩展并且使得现有属性不可配置

密封对象有一组固定的属性:不能添加新属性、不能删除现有属性或更改其可枚举性和可配置性、不能重新分配其原型。只要现有属性的值是可写的,它们仍然可以更改。seal() 返回传入的同一对象。

js
Object.seal(obj)

参数:

  • obj:要密封的对象。

返回值:被密封的对象。

TIP

密封一个对象等价于阻止其扩展,然后将现有的属性描述符更改为 configurable: false。这会使对象上的属性集固定。令对象的所有属性不可配置且可以防止它们从数据属性转换成访问器属性,反之亦然,但它不会防止数据属性的值被更改。尝试删除或者向密封对象添加属性,或者将数据属性和访问器属性互相转换,都将失败,可能会静默失败,也可能会抛出 TypeError(最常见的是在严格模式代码中,但不仅限于此)。

原型链保持不变。然而,由于扩展被阻止[[Prototype]] 不能重新赋值。

不同于 Object.freeze() 的是,通过 Object.seal() 密封的对象可以更改其现有属性,只要它们是可写的。

js
const obj = {
  prop() {},
  foo: "bar",
};

// 可以添加新属性,可以更改或删除现有属性。
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;

const o = Object.seal(obj);

o === obj; // true
Object.isSealed(obj); // true

// 更改密封对象的属性值仍然有效。
obj.foo = "quux";

// 但不能将数据属性转换成访问者属性,反之亦然。
Object.defineProperty(obj, "foo", {
  get() {
    return "g";
  },
}); // 抛出 TypeError

// 现在,除了属性值之外的任何更改都将失败。
obj.quaxxor = "the friendly duck";
// 静默不添加属性
delete obj.foo;
// 静默不添删除属性

// ...且严格模式下,这种尝试将会抛出 TypeError。
function fail() {
  "use strict";
  delete obj.foo; // 抛出一个 TypeError
  obj.sparky = "arf"; // 抛出一个 TypeError
}
fail();

// 尝试通过 Object.defineProperty 添加属性也会抛出错误。
Object.defineProperty(obj, "ohai", {
  value: 17,
}); // 抛出 TypeError
Object.defineProperty(obj, "foo", {
  value: "eit",
}); // 更改现有属性值

Object.isSealed()

判断一个对象是否被密封

js
Object.isSealed(obj)

参数:

  • obj:要被检查的对象。

返回值:一个表示给定对象是否被密封的布尔值

js
// 新建的对象默认不是密封的。
const empty = {};
Object.isSealed(empty); // false

// 如果你令一个空对象不可扩展,则它同时也会变成个密封对象。
Object.preventExtensions(empty);
Object.isSealed(empty); // true

// 但如果这个对象不是空对象,则它不会变成密封对象,因为密封对象的所有自身属性必须是不可配置的。
const hasProp = { fee: "fie foe fum" };
Object.preventExtensions(hasProp);
Object.isSealed(hasProp); // false

// 如果把这个属性变的不可配置,则这个属性也就成了密封对象。
Object.defineProperty(hasProp, "fee", {
  configurable: false,
});
Object.isSealed(hasProp); // true

// 密封一个对象最简单的方法当然是 Object.seal。
const sealed = {};
Object.seal(sealed);
Object.isSealed(sealed); // true

// 根据定义,密封对象是不可扩展的。
Object.isExtensible(sealed); // false

// 一个密封对象可能被冻结,但不一定。
Object.isFrozen(sealed); // true
//(所有属性也是不可写的)

const s2 = Object.seal({ p: 3 });
Object.isFrozen(s2); // false
//('p' 仍然可写)

const s3 = Object.seal({
  get p() {
    return 0;
  },
});
Object.isFrozen(s3); // true
//(对于访问器属性,只有可配置性才有影响)

Object.preventExtensions()

防止新属性被添加到对象中(即防止该对象被扩展)。它还可以防止对象的原型被重新指定。

js
Object.preventExtensions(obj)

参数:

  • obj:将要变得不可扩展的对象。

返回值:已经不可扩展的对象。

TIP

如果一个对象可以添加新的属性,则这个对象是可扩展的。Object.preventExtensions() 将对象标记为不再可扩展,这样它将永远不会具有它被标记为不可扩展时持有的属性之外的属性。注意,一般来说,不可扩展对象的属性仍然可以被删除。尝试向不可扩展对象添加新属性将静默失败,或在严格模式中抛出 TypeError

Object.seal()Object.freeze() 不同,Object.preventExtensions() 调用了内在的 JavaScript 行为,不能用几个其他操作的组合替代。它还有它的 Reflect 对应方法(仅存在于内部操作中),Reflect.preventExtensions()

Object.preventExtensions() 只能防止添加自有属性。但其对象类型的原型依然可以添加新的属性。

该方法使得目标对象的 [[Prototype]] 不可变;任何重新赋值 [[Prototype]] 操作都会抛出 TypeError 。这种行为只针对内部的 [[Prototype]] 属性,目标对象的其他属性将保持可变。

一旦将对象变为不可扩展的对象,就再也不能使其可扩展。

js
// Object.preventExtensions 将原对象变的不可扩展,并且返回原对象。
const obj = {};
const obj2 = Object.preventExtensions(obj);
obj === obj2; // true

// 字面量方式定义的对象默认是可扩展的。
const empty = {};
Object.isExtensible(empty); // true

// 可以将其改变为不可扩展的。
Object.preventExtensions(empty);
Object.isExtensible(empty); // false

// 使用 Object.defineProperty 方法为一个不可扩展的对象添加新属性会抛出异常。
const nonExtensible = { removable: true };
Object.preventExtensions(nonExtensible);
Object.defineProperty(nonExtensible, "new", {
  value: 8675309,
}); // 抛出 TypeError

// 在严格模式中,为一个不可扩展对象的新属性赋值会抛出 TypeError 异常。
function fail() {
  "use strict";
  // 抛出 TypeError
  nonExtensible.newProperty = "FAIL";
}
fail();

Object.getPrototypeOf()

返回指定对象的原型(即内部 [[Prototype]] 属性的值)

js
Object.getPrototypeOf(obj)

参数:

  • obj:要返回其原型的对象。

返回值:给定对象的原型,可能是null

js
const proto = {};
const obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true

Object.setPrototypeOf()

将一个对象设置为另一个对象的原型

js
Object.setPrototypeOf(obj, prototype)

参数:

  • obj:要设置其原型的对象。
  • prototype:该对象的新原型(一个对象或null

返回值:指定的对象。

js
const obj = {};
const parent = { foo: "bar" };

Object.setPrototypeOf(obj, parent);

console.log(obj.__proto__ === parent);      // true

使用 Object.setPrototypeOf() 实现伪类继承

JS 中可以这样实现类继承。

js
class Human {}
class SuperHero extends Human {}

const superMan = new SuperHero();

但是,如果我们想要在不使用 class 的情况下实现子类,我们可以这么做:

js
function Human(name, level) {
  this.name = name;
  this.level = level;
}

function SuperHero(name, level) {
  Human.call(this, name, level);
}

Object.setPrototypeOf(SuperHero.prototype, Human.prototype);

// 将 `SuperHero.prototype` 的 `[[Prototype]]` 设置为 `Human.prototype` 以设置原型继承链

Human.prototype.speak = function () {
  return `${this.name} says hello.`;
};

SuperHero.prototype.fly = function () {
  return `${this.name} is flying.`;
};

const superMan = new SuperHero("Clark Kent", 1);

console.log(superMan.fly());
console.log(superMan.speak());

4.对象原型方法

Object.prototype.hasownProperty()

判断一个对象中是否含有某个自有属性

js
hasOwnProperty(prop)

参数:

  • prop:要测试的属性的字符串名称或者 Symbol

返回值:如果对象有指定属性作为自有属性,则返回 true;否则返回 false

TIP

如果指定的属性是对象的直接属性——即使值为 null 或者 undefinedhasOwnProperty() 方法也会返回 true。如果属性是继承的,或者根本没有声明该属性,则该方法返回 false。与 in运算符不同的是,该方法不会在对象原型链中检查指定的属性。

该方法可以在大多数 JavaScript 对象中使用,因为大多数对象都是从Object派生而来,因此会继承该方法。例如 Array是一个Object,所以你可以使用 hasOwnProperty() 方法来检查索引是否存在:

js
const fruits = ["Apple", "Banana", "Watermelon", "Orange"];
fruits.hasOwnProperty(3); // 返回 true ('Orange')
fruits.hasOwnProperty(4); // 返回 false——未定义

如果在对象中重新实现了该方法,或者使用 Object.create(null) 创建了对象(这些对象不继承自 Object.prototype),则该方法将不可用。下面给出了这些情况的示例。

使用 hasOwnProperty 测试自有属性是否存在

js
const example = {};
example.hasOwnProperty("prop"); // 返回 false

example.prop = "exists";
example.hasOwnProperty("prop"); // 返回 true——“prop”已定义

example.prop = null;
example.hasOwnProperty("prop"); // 返回 true——自有属性存在且值为 null

example.prop = undefined;
example.hasOwnProperty("prop"); // 返回 true——自有属性存在且值为 undefined

直接属性 vs 继承属性,以下示例区分了直接属性和通过原型链继承的属性:

js
const example = {};
example.prop = "exists";

// `hasOwnProperty` 仅对直接属性返回 true:
example.hasOwnProperty("prop"); // 返回 true
example.hasOwnProperty("toString"); // 返回 false
example.hasOwnProperty("hasOwnProperty"); // 返回 false

// 对于直接或继承的属性,`in` 运算符将返回 true:
"prop" in example; // 返回 true
"toString" in example; // 返回 true
"hasOwnProperty" in example; // 返回 true

遍历对象的属性,以下示例展示了如何在忽略继承属性的情况下迭代一个对象的可枚举属性。

js
const buz = {
  fog: "stack",
};

for (const name in buz) {
  if (buz.hasOwnProperty(name)) {
    console.log(`this is fog (${name}) for sure. Value: ${buz[name]}`);
  } else {
    console.log(name); // toString 或其他的方法等
  }
}

请注意,for...in循环只迭代可枚举属性:循环中没有发出非可枚举属性并不意味着 hasOwnProperty 本身被严格限制在可枚举属性中(和 Object.getOwnPropertyNames()一样)。

使用 hasOwnProperty 作为属性名称,JavaScript 并不保护属性名称 hasOwnProperty;具有此名称属性的对象可能会返回不正确的结果:

js
const foo = {
  hasOwnProperty() {
    return false;
  },
  bar: "Here be dragons",
};

foo.hasOwnProperty("bar"); // 该重新实现始终返回 false

克服这个问题的推荐方法是,在支持的浏览器中使用 Object.hasOwn()。其他替代方案包括使用外部的 hasOwnProperty

js
const foo = { bar: "Here be dragons" };

// 使用 Object.hasOwn() 方法——推荐
Object.hasOwn(foo, "bar"); // 返回 true

// 使用 Object 原型中的 hasOwnProperty 属性
Object.prototype.hasOwnProperty.call(foo, "bar"); // 返回 true

// 使用另一个 Object 的 hasOwnProperty,并在“this”设置为 foo 的情况下调用它
({}).hasOwnProperty.call(foo, "bar"); // 返回 true

请注意,在前两种情况下,没有新创建的对象。

Object.create(null) 创建的对象,使用 Object.create(null)创建的对象不从 Object.prototype 继承,使得 hasOwnProperty() 不可访问。

js
const foo = Object.create(null);
foo.prop = "exists";
foo.hasOwnProperty("prop"); // Uncaught TypeError: foo.hasOwnProperty is not a function

这种情况下的解决方案与上一节相同:使用Object.hasOwn(),否则使用外部对象的 hasOwnProperty()

Object.prototype.isPrototypeof()

检查一个对象是否存在于另一个对象的原型链中。

js
isPrototypeOf(object)

参数:

  • object:要搜索其原型链的对象。

返回值:一个布尔值,指示调用 isPrototypeOf() 方法的对象(即 this)是否位于 object 的原型链中。当 object 不是一个对象(即基本类型)时,直接返回 false

js
class Foo {}
class Bar extends Foo {}
class Baz extends Bar {}

const foo = new Foo();
const bar = new Bar();
const baz = new Baz();

// 原型链:
// foo: Foo --> Object
// bar: Bar --> Foo --> Object
// baz: Baz --> Bar --> Foo --> Object
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Baz.prototype.isPrototypeOf(bar)); // false
console.log(Baz.prototype.isPrototypeOf(foo)); // false
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(foo)); // false
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(bar)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true

Object.prototype.propertyIsEnumerable

返回一个布尔值,表示指定的属性是否是对象的可枚举自有属性。

js
propertyIsEnumerable(prop)

参数:

  • prop:需要测试的属性名,可以是字符串或Symbol

返回值:一个布尔值,指示指定的属性是否可枚举并且是对象自有的属性。

MIT Licensed