一、Object 构造器成员

Object.prototype

该属性是所有对象的原型(包括 Object 对象本身)
其他对象正是通过对该属性上添加东西来实现它们之间的继承关系的

1
2
3
4
5
6
7
function Phone(logo, core) {
this.logo = logo;
this.core = core;
}
let iphone = new Phone("apple", "ios");
Phone.prototype.owner = "Mr Thomas";
console.log(iphone.owner); //'Mr Thomas'

二、Object.prototype 的成员

Object.prototype.constructor

返回创建实例对象的 Object 构造函数的引用

所有对象都会从它的原型上继承一个 constructor 属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var o = {};
o.constructor === Object; // true

var o = new Object();
o.constructor === Object; // true

var a = [];
a.constructor === Array; // true

var a = new Array();
a.constructor === Array; // true

var n = new Number(3);
n.constructor === Number; // true

function Tree(name) {
this.name = name;
}

var theTree = new Tree("Redwood");
console.log("theTree.constructor is " + theTree.constructor);

Object.prototype.toString(radix)

返回一个用于描述目标对象的字符串

当目标是一个 Number 对象时,可以传递一个用于进制数的参数 radix,该参数 radix,该参数的默认值为 10。

1
2
3
4
5
var o = { prop: 1 };
o.toString(); // '[object Object]'
var n = new Number(255);
n.toString(); // '255'
n.toString(16); // 'ff'

Object.prototype.toLocaleString()

该方法的作用与 toString()基本相同,只不过它做一些本地化处理。该方法会根据当前对象的不同而被重写,例如 Date(),Number(),Array(),它们的值都会以本地化的形式输出。

1
2
3
var n = new Date();
n.toString(); //"Sat Apr 10 2021 20:44:46 GMT+0800 (中国标准时间)"
n.toLocaleString(); //"2021/4/10下午8:44:46"

对于包括 Object()在内的其他大多数对象来说,该方法与 toString()是基本相同的。 在浏览器环境下,可以通过 BOM 对象 Navigator 的 language 属性(在 IE 中则是 userLanguage)来了解当前所使用的语言:

Object.prototype.valueOf()

该方法返回的是用基本类型所表示的 this 值,如果它可以用基本类型表示的话。如果 Number 对象返回的是它的基本数值,而 Date 对象返回的是一个时间戳(timestamp)。如果无法用基本数据类型表示,该方法会返回 this 本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Object
var o = {};
typeof o.valueOf(); // 'object'
o.valueOf() === o; // true
// Number
var n = new Number(101);
typeof n; // 'object'
typeof n.vauleOf; // 'function'
typeof n.valueOf(); // 'number'
n.valueOf() === n; // false
// Date
var d = new Date();
typeof d.valueOf(); // 'number'
d.valueOf(); // 1503146772355

Object.prototype.hasOwnProperty(prop)

该方法仅在目标属性为对象自身属性时返回 true,而当该属性是从原型链中继承而来或根本不存在时,返回 false。

1
2
3
4
var o = { prop: 1 };
o.hasOwnProperty("prop"); // true
o.hasOwnProperty("toString"); // false
o.hasOwnProperty("formString"); // false

Object.prototype.isPrototypeOf(obj)

返回调用者是否在目标对象的原型链上

1
2
3
4
var s = new String("");
Object.prototype.isPrototypeOf(s); // true
String.prototype.isPrototypeOf(s); // true
Array.prototype.isPrototypeOf(s); // false

Object.prototype.propertyIsEnumerable(prop)

如果目标属性能在 for in 循环中被显示出来,该方法就返回 true

1
2
3
var a = [1, 2, 3];
a.propertyIsEnumerable("length"); // false
a.propertyIsEnumerable(0); // true

字面量 关联数组(associative array)了——对象做了字符串到值的映射,而数组做的是数字到值的映射

this 指向了代码所在的对象(其实代码运行时所在的对象)

三、在 ES5 中附加的 Object 属性

Object.defineProperty(obj, prop, descriptor) (ES5)

在 ES5 中,我们可以设置属性是否可以被改变或是被删除——在这之前,它是内置属性的特权。ES5 中引入了属性描述符的概念,我们可以通过它对所定义的属性有更大的控制权。这些属性描述符(特性)包括:

  • value——当试图获取属性时所返回的值。
  • writable——该属性是否可写。
  • enumerable——该属性在 for in 循环中是否会被枚举
  • configurable——该属性是否可被删除。
  • set()——该属性的更新操作所调用的函数。
  • get()——获取属性值时所调用的函数。

数据描述符(其中属性为:enumerable,configurable,value,writable)与存取描述符(其中属性为 enumerable,configurable,set(),get())之间是有互斥关系的。在定义了 set()和 get()之后,描述符会认为存取操作已被 定义了,其中再定义 value 和 writable 会引起错误。

1
2
3
4
5
6
7
var person = {};
Object.defineProperty(person, "legs", {
value: 2,
writable: true,
configurable: true,
enumerable: true,
});

Object.defineProperties(obj, props) (ES5)

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

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {};
Object.defineProperties(obj, {
property1: {
value: true,
writable: true,
},
property2: {
value: "Hello",
writable: false,
},
// {property1: true, property2: "Hello"}
});

Object.getPrototypeOf(obj) (ES5)

之前在 ES3 中,往往需要通过 Object.prototype.isPrototypeOf()去猜测某个给定的对象的原型是什么,如今在 ES5 中,可以直接询问改对象“你的原型是什么?”

1
2
3
Object.getPrototypeOf([]) === Array.prototype; // true
Object.getPrototypeOf(Array.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true

Object.create(proto,[propertiesObject]) (ES5)

创建一个新对象,使用现有的对象来提供新创建的对象的proto

propertiesObject 可选。需要传入一个对象,该对象的属性类型参照 Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

1
2
3
4
5
6
7
8
9
10
11
12
13
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
},
};

const me = Object.create(person);

me.name = "Thomas"; //name是me上面的属性,不是person对象上的
me.isHuman = true; // 继承的属性能被重写 写在me对象上

me.printIntroduction(); //My name is Thomas. Am I human? true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var o;

// 创建一个原型为null的空对象
o = Object.create(null);

o = {};
// 以字面量方式创建的空对象就相当于:
o = Object.create(Object.prototype);

o = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable: true,
configurable: true,
value: "hello",
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function () {
return 10;
},
set: function (value) {
console.log("Setting `o.bar` to", value);
},
},
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function Constructor() {}
o = new Constructor();
// 上面的一句就相当于:
o = Object.create(Constructor.prototype);
// 当然,如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码

// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
o = Object.create({}, { p: { value: 42 } });

// 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的:
o.p = 24;
o.p;
//42

o.q = 12;
for (var prop in o) {
console.log(prop);
}
//"q"

delete o.p;
//false

//创建一个可写的,可枚举的,可配置的属性p
o2 = Object.create(
{},
{
p: {
value: 42,
writable: true,
enumerable: true,
configurable: true,
},
}
);

Object.getOwnPropertyDesciptor(obj, property) (ES5)

可以让我们详细查看一个属性的定义。甚至可以通过它一窥那些内置的,之前不可见的隐藏属性。

1
2
Object.getOwnPropertyDescriptor(Object, "create");
//{writable: true, enumerable: false, configurable: true, value: ƒ create()}

Object.getOwnPropertyNames(obj) (ES5)

该方法返回一个数组,其中包含了当前对象所有属性的名称(字符串),不论它们是否可枚举。当然,也可以用 Object.keys()来单独返回可枚举的属性。

1
2
3
4
5
6
7
8
Object.getOwnPropertyNames(Object.prototype);
// ["__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "constructor", "toLocaleString", "isPrototypeOf"]
Object.keys(Object.prototype);
// []
Object.getOwnPropertyNames(Object);
// ["length", "name", "arguments", "caller", "prototype", "assign", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", "getOwnPropertyNames", "getOwnPropertySymbols", "is", "preventExtensions", "seal", "create", "defineProperties", "defineProperty", "freeze", "getPrototypeOf", "setPrototypeOf", "isExtensible", "isFrozen", "isSealed", "keys", "entries", "values"]
Object.keys(Object);
// []

Object.preventExtensions(obj) (ES5)

Object.isExtensible(obj) (ES5)

preventExtensions()方法用于禁止向某一对象添加更多属性,而 isExtensible()方法则用于检查某对象是否还可以被添加属性。

1
2
3
4
5
6
7
8
9
10
var deadline = {};
Object.isExtensible(deadline); // true
deadline.date = "yesterday"; // 'yesterday'
Object.preventExtensions(deadline);
Object.isExtensible(deadline); // false
deadline.date = "today";
deadline.date; // 'today'
// 尽管向某个不可扩展的对象中添加属性不算是一个错误操作,但它没有任何作用。
deadline.report = true;
deadline.report; // undefined

Object.seal(obj) (ES5)

Object.isSeal(obj) (ES5)

seal()方法可以让一个对象密封,并返回被密封后的对象。 seal()方法的作用与 preventExtensions()基本相同,但除此之外,它还会将现有属性 设置成不可配置。也就是说,在这种情况下,我们只能变更现有属性的值,但不能删除或(用 defineProperty())重新配置这些属性,例如不能将一个可枚举的属性改成不可枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var person = { legs: 2 };
// person === Object.seal(person); // true
Object.isSealed(person); // true
Object.getOwnPropertyDescriptor(person, "legs");
// {value: 2, writable: true, enumerable: true, configurable: false}
delete person.legs; // false (不可删除,不可配置)
Object.defineProperty(person, "legs", { value: 2 });
person.legs; // 2
person.legs = 1;
person.legs; // 1 (可写)
Object.defineProperty(person, "legs", {
get: function () {
return "legs";
},
});
// 抛出TypeError异常

Object.freeze(obj) (ES5)

Object.isFrozen(obj) (ES5)

freeze()方法用于执行一切不受 seal()方法限制的属性值变更。Object.freeze() 方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。

1
2
3
4
5
6
7
8
9
10
var deadline = Object.freeze({ date: "yesterday" });
deadline.date = "tomorrow";
deadline.excuse = "lame";
deadline.date; // 'yesterday'
deadline.excuse; // undefined
Object.isSealed(deadline); // true;
Object.isFrozen(deadline); // true
Object.getOwnPropertyDescriptor(deadline, "date");
// {value: "yesterday", writable: false, enumerable: true, configurable: false} (不可配置,不可写)
Object.keys(deadline); // ['date'] (可枚举)

Object.keys(obj) (ES5)

该方法是一种特殊的 for-in 循环。它只返回当前对象的属性(不像 for-in),而且这些属性也必须是可枚举的(这点和 Object.getOwnPropertyNames()不同,不论是否可以枚举)。返回值是一个字符串数组。

1
2
3
4
5
6
7
Object.prototype.customProto = 101;
Object.getOwnPropertyNames(Object.prototype);
// [..., "constructor", "toLocaleString", "isPrototypeOf", "customProto"]
Object.keys(Object.prototype); // ['customProto']
var o = { own: 202 };
o.customProto; // 101
Object.keys(o); // ['own']

四、在 ES6 中附加的 Object 属性

Object.is(value1, value2) (ES6)

该方法用来比较两个值是否严格相等。它与严格比较运算符(===)的行为基本一致。 不同之处只有两个:一是+0 不等于-0,二是 NaN 等于自身。

1
2
3
4
5
6
Object.is("Thomas", "Thomas"); // true
Object.is({}, {}); // false
Object.is(+0, -0); // false
+0 === -0; // true
Object.is(NaN, NaN); // true
NaN === NaN; // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ES5可以通过以下代码部署Object.is;

Object.defineProperty(Object, "is", {
value: function (x, y) {
if (x === y) {
// 针对+0不等于-0的情况
return x !== 0 || 1 / x === 1 / y;
}
// 针对 NaN的情况
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true,
});

Object.assign(target, …sources) (ES6)

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

举个例子

1
2
3
4
5
var target = { a: 1, b: 2 };
var source = { a: 3, c: 3 };
var result = Object.assign(target, source);
console.log(result); // { a:3, b:2, c:3 }
console.log(target); //{ a:3, b:2, c:3 } 注意自身也会改变

注意点:

  • 如果目标对象中属性具有相同的键值,属性将被源对象覆盖。(人话:相同属性后面的覆盖前面的)
  • 只会拷贝源对象自身的并且可枚举的属性到目标对象
  • String 类型和 Symbol 类型的属性都会被拷贝。

拷贝 symbol 类型的属性:

1
2
3
4
5
6
const o1 = { a: 1 };
const o2 = { [Symbol("foo")]: 2 };

const obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]

继承属性和不可枚举属性是不能拷贝的:

1
2
3
4
5
6
7
8
9
10
11
const obj = Object.create({foo: 1}, { // foo 是个继承属性。
bar: {
value: 2 // bar 是个不可枚举属性。
},
baz: {
value: 3,
enumerable: true // baz 是个自身可枚举属性。
}
});
onst copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }

原始类型会被包装为对象

1
2
3
4
5
6
7
8
9
const v1 = "abc";
const v2 = true;
const v3 = 10;
const v4 = Symbol("foo");

const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

异常会打断后续的拷贝任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const target = Object.defineProperty({}, "foo", {
value: 1,
writable: false,
}); // target 的 foo 属性是个只读属性。

Object.assign(target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
// TypeError: "foo" is read-only
// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。

console.log(target.bar); // 2,说明第一个源对象拷贝成功了。
console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
console.log(target.foo); // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。

拷贝访问器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const obj = {
foo: 1,
get bar() {
return 2;
},
};

let copy = Object.assign({}, obj);
console.log(copy); // { foo: 1, bar: 2 } copy.bar的值来自obj.bar的getter函数的返回值

// 下面这个函数会拷贝所有自有属性的属性描述符
function completeAssign(target, ...sources) {
sources.forEach((source) => {
let descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});

// Object.assign 默认也会拷贝可枚举的Symbols
Object.getOwnPropertySymbols(source).forEach((sym) => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}

copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }

Polyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (typeof Object.assign != "function") {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) {
// .length of function is 2
"use strict";
if (target == null) {
// TypeError if undefined or null
throw new TypeError("Cannot convert undefined or null to object");
}

let to = Object(target);

for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];

if (nextSource != null) {
// Skip over if undefined or null
for (let nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true,
});
}

Object.getOwnPropertySymbols(obj) (ES6)

该方法会返回一个数组,该数组包含了指定对象自身的(非继承的)所有 symbol 属性键。 该方法和 Object.getOwnPropertyNames() 类似,但后者返回的结果只会包含字符串类型的属性键,也就是传统的属性名。

1
2
Object.getOwnPropertySymbols({a: 'b', [Symbol('c')]: 'd'});
// [Symbol(c)]

五、在 ES8 中附加的 Object 属性

Object.getOwnPropertyDescriptors(obj) (ES8)

该方法基本与 Object.getOwnPropertyDescriptor(obj, property)用法一致,只不过它可以用来获取一个对象的所有自身属性的描述符。

1
2
3
Object.getOwnPropertyDescriptor(Object.prototype, "toString");
// {writable: true, enumerable: false, configurable: true, value: ƒ toString()}
Object.getOwnPropertyDescriptors(Object.prototype); // 可以自行在浏览器控制台查看效果。

Object.values(obj) (ES8)

Object.values() 方法与 Object.keys 类似。返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用 for…in 循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

1
2
3
var obj = { a: 1, b: 2, c: 3 };
Object.keys(obj); // ['a','b','c']
Object.values(obj); // [1,2,3]

Object.entries(obj) (ES8)

Object.entries() 方法返回一个给定对象自己的可枚举属性[key,value]对的数组,数组中键值对的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致(区别在于一个 for-in 循环也枚举原型链中的属性)。

1
2
3
4
var obj = { a: 1, b: 2, c: 3 };
Object.keys(obj); // ['a','b','c']
Object.values(obj); // [1,2,3]
Object.entries(obj); // [['a',1],['b',2],['c',3]]

六、在 ES10 中附加的 Object 属性

Object.fromEntries(iterable) (ES10)

把键值对列表转换为一个对象。

  • iterable 类似 Array 、 Map 或者其它实现了可迭代协议的可迭代对象。

Map 转化为 Object

1
2
3
4
5
6
7
8
9
const entries = new Map([
["foo", "bar"],
["baz", 42],
]);

const obj = Object.fromEntries(entries);

console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }

Array 转化为 Object

1
2
3
4
5
6
7
const arr = [
["0", "a"],
["1", "b"],
["2", "c"],
];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }

对象转换

Object.fromEntries 是与 Object.entries() 相反的方法,用 数组处理函数 可以像下面这样转换对象:

1
2
3
4
5
6
7
8
9
const object1 = { a: 1, b: 2, c: 3 };

const object2 = Object.fromEntries(
Object.entries(object1).map(([key, val]) => [key, val * 2])
);
// Object.entries(object1)==>[["a",1],["b",2],["c",3]]

console.log(object2);
// { a: 2, b: 4, c: 6 }