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() 这个属性在之前我们已经介绍过了,该方法允许精确添加或修改对象的属性,比如 enumerable,configurable 和 writable 等,也可以用其来实现数据双向绑定,可谓是用处多多,不过我们今天主要来看这个跟它长的十分相像的 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 转换为 Map,new 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

该方法接收一个键值对的列表参数(可迭代对象,类似 Array,Map 或者其它实现了可迭代协议的对象)并返回一个由该迭代对象条目提供对应属性的新对象,生成的是一个具有两个元素的类数组的对象,第一个元素是将用作属性键的值,第二个元素是与该属性键关联的值,来看几个例子,比如将 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 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 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

×