Object 上的原生 API

Object 上的原生 API

在平常开发过程当中,虽然 Object 上的一些原生方法经常会看到,比如在一些扩展的第三方插件中可能会遇到,但是遇到了类似于 Object.fromEntries()Object.entries() 这种长的比较像的但是使用频率较低的 API,往往又是傻傻分不清楚,更别说它们具体是做什么用的,所以今天就打算将其汇总一下,将 Object 上涉及到的平常可能会遇到的 API 整体的学习记录一下,免得下次再次遇到又是一头雾水

其实简单来说,Object 上的原生 API 主要分为两部分,一部分是 Object 上面的方法,而另一部分则是 Object.prototype 上面的方法,下面我们就一个一个来看

Object 上的方法

Object 上面涉及到的方法其实不算很多,大致都可以按类别划分,所以我们将会分类来进行介绍,下表是一些本文当中没有涉及到的方法,其中有一些在之前的文章当中我们也都已经介绍过了,感兴趣的话可以自行参考,所以这里就不详细展开了

方法 描述
Object.assign() 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,一般克隆场景使用较多,详细可见 对象的浅拷贝
Object.create() 创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__,详细可见 Object.create()
Object.is(value1, value2) 判断两个值是否是 相同的值(同 ===),但是这个可以比对 NaN
Object.getOwnPropertyNames() 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组,详细可见 Object.getOwnPropertyNames()
Object.getOwnPropertySymbols() 返回一个给定对象自身的所有 Symbol 属性的数组
Object.values() 返回一个给定对象自身的所有可枚举属性值的数组
Object.keys() 返回一个由一个给定对象的自身可枚举属性组成的数组,这两个详细可见 Object.keys()

Object.defineProperty() 和 Object.defineProperties()

涉及到的两个方法如下

方法 描述
Object.defineProperty() 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象,详细可见 Object.defineProperty()
Object.defineProperties() 这个同上面那个类似,但是可以同时定义和修改多个属性

Object.defineProperty() 这个属性在之前我们已经介绍过了,该方法允许精确添加或修改对象的属性,比如 enumerableconfigurablewritable 等,也可以用其来实现数据双向绑定,可谓是用处多多,不过我们今天主要来看这个跟它长的十分相像的 Object.defineProperties()

其实它俩是一个东西,不过当定义或修改对象的多个属性时,使用 Object.defineProperty() 就会比较麻烦了,在这种情况下我们可以考虑使用 Object.defineProperties()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var man = {}

Object.defineProperties(man, {
name: {
value: 'zhangsan',
writable: true
},
age: {
value: 20,
writable: true
}
})

console.log(man.name) // zhangsan
console.log(man.age) // 20

Object.entries() 和 Object.fromEntries()

涉及到的两个方法如下

方法 描述
Object.entries() 返回一个给定对象自身可枚举属性的键值对数组,详细可见 Object.entries(obj)
Object.fromEntries() 可以把把键值对列表转换为一个对象

Object.entries() 在迭代器相关章节我们曾经使用过这个方法,它接收一个可以返回其可枚举属性的键值对的对象,返回给定对象自身可枚举属性的键值对数组,Object 之所以不能被 for-of 遍历,主要是因为它没有部署 Iterator 接口,在这种情况下我们可以使用 Object.entries() 将其包裹一下,利用其返回的键值对数组再来进行遍历(会存在一定问题,以实际使用场景来决定是否这样使用),看下面这两个例子

1
2
3
4
5
6
7
const obj = {
100: 'a',
2: 'b',
7: 'c'
}

console.log(Object.entries(obj)) // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

这里需要注意,上面例子的返回结果的排序是改变过的,至于为什么可以参考 为什么 Object.keys 的返回值会自动排序,另外一个例子就是将 Object 转换为 Mapnew Map() 函数接受一个可迭代的 entries,借助 Object.entries 方法可以很容易的将 Object 转换为 Map

1
2
3
var obj = { foo: 'bar', baz: 42 }

console.log(new Map(Object.entries(obj))) // Map(2) { 'foo' => 'bar', 'baz' => 42 }

下面我们再来看看 Object.fromEntries() 这个方法,其实简单来说,就是 Object.entries 的反转

但是需要注意的是,这个 API 现在的兼容性还不是很好,可以考虑使用 polyfill

该方法接收一个键值对的列表参数(可迭代对象,类似 ArrayMap 或者其它实现了可迭代协议的对象)并返回一个由该迭代对象条目提供对应属性的新对象,生成的是一个具有两个元素的类数组的对象,第一个元素是将用作属性键的值,第二个元素是与该属性键关联的值,来看几个例子,比如将 Map 转化为 Object

1
2
3
const map = new Map([['foo', 'bar'], ['baz', 42]])

console.log(Object.fromEntries(map)) // { foo: 'bar', baz: 42 }

也可以将 Array 转化为 Object

1
2
3
const arr = [['0', 'a'], ['1', 'b'], ['2', 'c']]

console.log(Object.fromEntries(arr)) // { 0: 'a', 1: 'b', 2: 'c' }

getOwnPropertyDescriptor() 和 getOwnPropertyDescriptors()

涉及到的几个方法如下

方法 描述
Object.getOwnPropertyDescriptor() 返回指定对象上一个自有属性对应的属性描述符(直接赋予的,不需要从原型链上进行查找的属性)
Object.getOwnPropertyDescriptors() 获取一个对象的所有自身属性的描述符
Object.setPrototypeOf() 这个方法不建议使用,它的作用是设置一个指定的对象的原型到另一个对象,更推荐使用 Object.create()
Object.getPrototypeOf() 返回指定对象的原型(内部 [[Prototype]] 属性的值)

这两个方法的作用也是一样的,都是返回指定对象『自有属性』对应的属性描述符,不过一个是返回指定的,一个是返回全部的

需要注意,这里指的是自有属性,意思是直接赋予该对象的属性,而不需要从原型链上进行查找的属性

两者的语法如下

1
2
3
Object.getOwnPropertyDescriptor(obj, prop)

Object.getOwnPropertyDescriptors(obj)

下面我们通过一个例子来了解它们如何使用

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
var obj = {}

Object.defineProperty(obj, 'age', {
value: 20,
writable: false,
enumerable: false
})

Object.getOwnPropertyDescriptor(obj, 'age')

// {
// configurable: false
// enumerable: false
// value: 20
// writable: false
// }

Object.getOwnPropertyDescriptors(obj)

// age: {
// configurable: false
// enumerable: false
// value: 20
// writable: false
// }

关于 Object.getOwnPropertyDescriptor(obj, prop) 方法有一个需要注意的地方

  • ES5 中,如果该方法的第一个参数不是对象(而是基本类型),那么就会产生出现 TypeError
  • 而在 ES6 中,第一个的参数不是对象的话就会被强制转换为对象

但是 Object.getOwnPropertyDescriptors(obj) 这个方法的作用不仅仅只是用于查看对象的属性描述符,比如还可以用来『浅拷贝』对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
a: 1,
b: {
name: 'zhangsan'
}
}

var newObj = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)

console.log(newObj) // { a: 1, b: { name: 'zhangsan' } }

newObj.a = 2
newObj.b.name = 'lisi'

console.log(obj) // { a: 1, b: { name: 'lisi' } }

另外还可以用来创建子类,创建子类的典型方法是定义子类,将其原型设置为超类的实例,然后在该实例上定义属性,其实就是我们常说的继承,也就是原来经常使用的

1
2
3
4
5
6
father.call(this)
child.prototype = new father()

// 但是推荐使用下面这种方式
child.prototype = Object.create(father.prototype)
child.prototype.constructor = child

但是现在我们可以通过 Object.getOwnPropertyDescriptors() 更为优雅的来实现

1
2
3
4
5
6
7
8
9
10
11
function Foo() { }

Foo.prototype = {
// 在这里定义方法和属性
}

function Bar() { }

Bar.prototype = Object.create(Foo.prototype, Object.getOwnPropertyDescriptors({
// 在这里定义方法和属性
}))

在上面浅拷贝的例子当中涉及到一个方法 Object.getPrototypeOf(obj),它的作用是返回指定对象的原型(内部 [[Prototype]] 属性的值),比如下面这个例子

1
2
3
4
5
var reg = /^\s/
Object.getPrototypeOf(reg) === RegExp.prototype // true

var obj = { }
Object.getPrototypeOf(obj) === Object.prototype // true

但是这里特别需要注意了,Object.getPrototypeOf(Object) 返回的并不是 Object.prototype,看下面这个特殊的例子

1
2
3
4
5
6
7
Object.prototype === Function.prototype.__proto__ // true
Object.prototype === Function.prototype // false

Object.getPrototypeOf(Object) // ƒ () { [native code] }
Object.getPrototypeOf(Function) // ƒ () { [native code] }

Object.getPrototypeOf(Object) === Function.prototype // true

JavaScript 中的 Object 其实是构造函数,即是创建对象的包装器,所以我们一般用法是

1
var obj = new Object()

Object.getPrototypeOf(Object) 的意思是把 Object 这一构造函数看作对象,返回的当然是函数对象的原型,也就是 Function.prototype,所以结果是 true,所以正确的方法应该是,Object.prototype 是构造出来的对象的原型

1
2
3
4
var obj = new Object()

Object.getPrototypeOf(obj) === Object.prototype // true
Object.getPrototypeOf({}) === Object.prototype // true

同样的,Object.getPrototypeOf(obj) 方法在 ES5 中的参数如果不是对象,也会出现 TypeError,而 ES6 中会被强制转换为对象

Object.preventExtensions(),Object.seal() 和 Object.freeze()

涉及到的几个方法如下

方法 描述
Object.preventExtensions() 让一个对象变的不可扩展,也就是永远不能再添加新的属性
Object.isExtensible() 判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)
Object.seal() 封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置
Object.isSealed() 判断一个对象是否被密封
Object.freeze() 冻结一个对象,一个被冻结的对象再也不能被修改
Object.isFrozen() 判断一个对象是否被冻结

最后这六个我们放到一起来进行介绍,因为它们主要涉及到的都是对象的扩展、密封和冻结,通过字面意思也可以发现,它们针对于对象限制的严格程度是一层更胜一层

Object.preventExtensions()

先来看看 Object.preventExtensions(),它的作用是阻止对象扩展,让一个对象变的不可扩展,也就是永远不能再添加新的属性,但是也有几个需要注意的地方

  • 一般来说,不可扩展对象的属性可能仍然可被删除,只是不可扩展
  • Object.preventExtensions() 仅阻止添加属性,但属性仍然可以添加到对象原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 示例一
var obj = {
name: 'zhangsan'
}

Object.preventExtensions(obj)
obj.age = 20

console.log(obj.age) // undefined

// 示例二
var obj = {
name: 'zhangsan'
}

Object.preventExtensions(obj)
delete obj.name

console.log(obj.name) // undefined

如果是严格模式,则会报错

1
2
3
4
5
6
7
8
9
'use strict'

var obj = {
name: 'zhangsan'
}

Object.preventExtensions(obj)

obj.age = 20 // TypeError

JavaScript 当中新增了一个 Object.isExtensible() 方法用来判断一个对象是否是可扩展的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新对象默认是可扩展的
var obj = {}
Object.isExtensible(obj) // ==> true

// 让其不可扩展
Object.preventExtensions(obj)
Object.isExtensible(obj) // ==> false

// 密封对象是不可扩展的
var sealed = Object.seal({})
Object.isExtensible(sealed) // ==> false

// 冻结对象也是不可扩展
var frozen = Object.freeze({})
Object.isExtensible(frozen) // ==> false

Object.seal()

下面我们再来看看密封,密封则会在不可扩展的基础之上更进一步,Object.seal() 方法会封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置,简单来说就是不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但可以修改已有属性的值的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = { name: 'zhangsan' }

// 密封
Object.seal(obj)

// 不能添加新属性
obj.age = 20
console.log(obj.age) // undefined

// 注意,现在删除属性也是无效的
delete obj.name
console.log(obj.name) // zhangsan

// 但是可以修改已有属性
obj.name = 'lisi'
console.log(obj.name) // lisi

如果修改已有属性的可枚举性、可配置性、可写性,会提示报错

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
name: 'zhangsan'
}

Object.seal(obj)

Object.defineProperty(obj, name, {
configurable: true,
writable: true,
enumerable: true
})

// TypeError

同样的,也提供了 Object.isSealed() 方法用来判断一个对象是否被密封

1
2
3
4
var obj = {}

Object.seal(obj)
Object.isSealed(obj) // true

如果一个对象不可扩展,并且它的属性也变的不可配置,则这个对象也就成了密封对象

1
2
3
4
5
6
7
8
9
10
var obj = {
name: 'zhangsan'
}

Object.preventExtensions(obj)
Object.defineProperty(obj, 'name', {
configurable: false
})

Object.isSealed(obj) // true

Object.freeze()

最后我们再来看看 Object.freeze() 这个方法,这个方法比 Object.seal 更绝,冻结对象是指那些不能添加新的属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象,也就是说,这个对象永远是不可变的,比如在一般情况下任何修改尝试都会静默失败

1
2
3
4
5
6
7
8
9
var obj = {
a: 1
}

Object.freeze(obj)

obj.a = 2 // 不报错,但是会静默失败

console.log(obj.a) // 1

但是在严格模式下则会报错

1
2
3
4
5
6
7
8
9
'use strict'

var obj = {
a: 1
}

Object.freeze(obj)

obj.a = 2 // TypeError

比如之前的示例当中,密封一个对象还是可以修改的,但是 Object.freeze() 这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = { name: 'zhangsan' }

// 密封
Object.seal(obj)

// 但是可以修改已有属性
obj.name = 'lisi'
console.log(obj.name) // lisi

// 冻结
Object.freeze(obj)
obj.name = 'wangwu'

console.log(obj) // { name: 'lisi' }

不仅仅适用于对象,数组也是一样的情况,不过被冻结的对象也不是不可变的,比如冻结对象不是常量对象

1
2
3
4
5
6
7
8
obj = {
a: { }
}

Object.freeze(obj)
obj.a.name = 'zhangsan'

console.log(obj.a.name) // zhangsan

同拷贝一样,要使对象不可变,需要递归冻结每个属性

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
// 深冻结函数
function deepFreeze(obj) {

// 取回定义在 obj 上的属性名
var propNames = Object.getOwnPropertyNames(obj)

// 在冻结自身之前冻结属性
propNames.forEach(function (name) {
var prop = obj[name]

// 如果 prop 是个对象,冻结它
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop)
})

// 最后在冻结自身
return Object.freeze(obj)
}

obj = {
a: {}
}

deepFreeze(obj)
obj.a.name = 'zhangsan'

console.log(obj.a.name) // undefined

Object.prototype 上的方法

相较于 Object 而言,Object.prototype 上的方法就少了许多,排除掉一些实验性的,主要有下面这些

方法 描述
Object.prototype.hasOwnProperty() 返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键),见 JavaScript 中的类型判断
Object.prototype.toString() 返回一个表示该对象的字符串,见 JavaScript 中的类型判断
Object.prototype.isPrototypeOf() 用于测试一个对象是否存在于另一个对象的原型链上,见 JavaScript 中的类型判断
Object.prototype.propertyIsEnumerable() 返回一个布尔值,表示指定的属性是否可枚举
Object.prototype.valueOf() 返回指定对象的原始值

一眼看去,是不是发现很多熟悉的面孔,一些之前我们已经详细介绍过的方法就不再展开了,这里主要看几个比较少见的

Object.prototype.propertyIsEnumerable()

该方法返回一个布尔值,表示指定的属性是否可枚举,看下面这个例子

1
2
3
4
5
6
7
8
9
10
11
const obj = {}
const newObj = {
name: 123
}

Object.defineProperty(obj, 'name', {
enumerable: false
})

obj.propertyIsEnumerable('name') // false
newObj.propertyIsEnumerable('name') // true

每个对象都有一个 propertyIsEnumerable 方法,此方法可以确定对象中指定的属性是否可以被 for-in 循环枚举,但是通过原型链继承的属性除外,如果对象没有指定的属性,则此方法返回 false

Object.prototype.valueOf()

最后的最后,我们来看一个特殊的方法 Object.prototype.valueOf(),调用该方法会返回指定对象的原始值,但是很少需要我们自己手动的调用 valueOf 方法,因为当遇到要预期的原始值的对象时,JavaScript 会自动调用它

默认情况下,valueOf 方法由 Object 后面的每个对象继承,每个内置的核心对象都会覆盖此方法以返回适当的值,如果对象没有原始值,则 valueOf 将返回对象本身,JavaScript 的许多内置对象都重写了该函数,以实现更适合自身的功能需要,因此不同类型对象的 valueOf() 方法的返回值和返回值类型均可能不同,具体可见下表

对象 返回值
Array 返回数组对象本身
boolean 布尔值
Date 存储的时间是从 197011 日午夜开始计的毫秒数 UTC
Function 函数本身
number 数字值
Object 对象本身,这是默认情况
string 字符串值
Math 和 Error 没有 valueOf 方法

下面我们主要来看几个特殊的例子,先来看看布尔类型

1
2
3
4
5
// 布尔类型
var newBool = new Boolean(true)

console.log(newBool.valueOf() == newBool) // true ==> valueOf() 返回的是 true,两者的值相等(注意是 ==)
console.log(newBool.valueOf() === newBool) // false ==> 但是不全等,两者类型不相等,前者是 Boolean 类型,后者是 Object 类型

函数

1
2
3
4
5
6
7
8
9
10
function foo() { }
var bar = new Function('x', 'y', 'return x + y')

console.log(foo.valueOf() === foo) // true ==> Function 返回函数本身
console.log(bar.valueOf() === bar) // true ==> 虽然它返回的结果是下面这样

bar.valueOf()
// ƒ anonymous(x, y) {
// return x + y
// }

字符串

1
2
3
4
5
var str = 'string'
var newStr = new String('string')

console.log(str.valueOf() === str) // true ==> String 返回字符串值
console.log(newStr.valueOf() === newStr) // false ==> 两者的值相等,但不全等,因为类型不同,前者为 String 类型,后者为 Object 类型

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×