荀令衣香--JavaScript全对象方法总结

前言

对象(object)是 JavaScript 最重要的数据结构。ES6中的重大升级,更使其如虎添翼,关于数据结构本身的改变,详见《纵深精耕--ES6对JS对象的极大提升》。这次先把JS中的对象方法做一次最详全的展示。

这篇的结构与拙文《拘神遣将--最详全的JS数组方法总结》相仿,在此省略赘述。

开始之前,劣者依旧先将本文谈到的方法(21个)按讲解次序阵列下方,供读者参考:

ES5:

Object.create()、Object.defineProperty()、Object.defineProperties()、

Object.getPrototypeOf()、Object.getOwnPropertyDescriptor()、Object.keys()、

Object.getOwnPropertyNames()、Object.preventExtensions()、Object.isExtensible()、

Object.seal()、Object.isSealed()、Object.freeze()、Object.isFrozen()

ES6:

Object.is()、Object.assign()、Object.getOwnPropertyDescriptors()、

Object.setPrototypeOf()、Object.getPrototypeOf()、Object.keys()、

Object.values()、Object.entries()、Object.fromEntries()

正文

1、Object.create(obj):方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

Object.create方法的实质是新建一个空的构造函数F,然后让F.prototype属性指向参数对象obj,最后返回一个F的实例,从而实现让该实例继承obj的属性。
实际上,Object.create也可以用下面的代码代替:

Object.create = function (obj) {
    function F() {}
    F.prototype = obj
    return new F()
 }

// 或者可以更直观些

Object.create = function (obj) {
    let F = {}
    F.__proto__=obj
    return F
}

Object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,都会立刻反映在新对象之上,可以将其视作是一种浅拷贝。下面的代码是一个例子:

let obj1 = { p: 1 };
let obj2 = Object.create(obj1);

obj1.p = 2;
obj2.p // 2

//在obj1上添加的属性,立刻反应到了新的对象obj2上

除了对象原型,Object.create方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到目标对象,作为目标对象自身的属性。

let obj = Object.create({}, {
  p1: {
    value: 123,
    enumerable: true,
    configurable: true,
    writable: true,
  },
  p2: {
    value: 'abc',
    enumerable: true,
    configurable: true,
    writable: true,
  }
});

// 等同于
let obj = Object.create({})
obj.p1 = 123
obj.p2 = 'abc'

如果理解了Object.create,就可以轻松去理解ES6转ES5的继承代码了。

2、Object.defineProperty(obj, prop, descript): 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

本方法接收三个参数,分别如下:
obj:要在其上定义属性的对象。
prop:要定义或修改的属性的名称。
Descriptor:将被定义或修改的属性描述符,返回被传递给函数的对象。

下面的代码是一个例子:

let obj = {};
Object.defineProperty(obj, 'name', {
    value: '斛律光'
});

console.log(obj); // '斛律光'

// 成功地给目标对象定义了name属性

使用Object.defineProperty可以实现vue中的双向绑定,详见拙文《浅析Vue的两项原理》。

3、Object.defineProperties(obj, props):方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

该方法有两个参数:
obj: 在其上定义或修改属性的对象。
props: 要定义其可枚举属性或修改的属性描述符的对象。

在这里稍微提一下对象中存在的属性描述符,主要有两种:数据描述符和访问器描述符,如下:

configurable、enumerable、value、writable、get、set

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
  // Object
    property1: true
    property2: "Hello"
    __proto__: Object
});

4、Object.getPrototypeOf(obj):方法返回指定对象的原型(内部[[Prototype]]属性的值)

该方法只有一个参数obj,表示要返回其原型的对象,返回值是给定对象的原型。

如果没有继承属性,则返回 null。下面的代码是一个例子:

const prototype1 = {};
const object1 = Object.create(prototype1);

console.log(Object.getPrototypeOf(object1) === prototype1);
// true

// object1是Object.create()以prototype1为蓝本创造的对象,它的原型自然指向prototype1

5、Object.getOwnPropertyDescriptor(obj, prop):方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

该方法接收两个参数,分别如下:

obj:需要查找的目标对象
prop:目标对象内属性名称

关于其返回值,如果指定的属性存在于对象上,则返回其属性描述符对象,否则返回 undefind。下面的代码是一个例子:

let o, d

o = { get foo() { return 17 } }

d = Object.getOwnPropertyDescriptor(o, "foo")
// d {
//   configurable: true,
//   enumerable: true,
//   get: /*the getter function*/,
//   set: undefined
// }
// 拿到了foo()的自有属性描述对象


o = { bar: 66 }

d = Object.getOwnPropertyDescriptor(o, "bar")
// d {
//   configurable: true,
//   enumerable: true,
//   value: 66,
//   writable: true
// }
// 

o = {}

Object.defineProperty(o, "baz", {
  value: 76341,
  writable: false,
  enumerable: false
})

d = Object.getOwnPropertyDescriptor(o, "baz")
// d {
//   value: 76341,
//   writable: false,
//   enumerable: false,
//   configurable: false
// }

/* Object.defineProperty()方法给o定义了baz属性,d通过
Object.getOwnPropertyDescriptor()获得了o的自有属性描述符对象。*/

6、Object.keys(obj):方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。如果对象的键-值都不可枚举,那么将返回由键组成的数组。

参数对象是 被要求返回其自身属性的对象,返回值是一个可表示给定对象所有可枚举属性字符串数组

下面的代码从四个不同的角度去举例:

// 数组
let arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

// 类似数组的对象
let obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

// 随机索引的类数组对象
let anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

// 不可枚举属性:getFoo
let myObj = Object.create({}, {
  getFoo: {
    value: function () { return this.foo; }
  } 
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']

7、Object.getOwnPropertyNames(obj):方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

该方法的返回值是一个字符串数组。

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

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

// 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
  console.log(val + " -> " + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c

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

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

8、Object.preventExtensions(obj): 方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

该方法会返回一个已经不可拓展的对象。比较直观,简单。下面代码是一个例子:

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

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

9、Object.isExtensible(obj): 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

很简单的方法,返回值是Boolean。

// 新对象默认是可扩展的.
let empty = {};
Object.isExtensible(empty); // === true

// ...可以变的不可扩展.
Object.preventExtensions(empty);
Object.isExtensible(empty); // === false

10、Object.seal(obj):方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。

参数是将要被密封的对象,返回值是被密封的对象。

Object.seal() 比 Object.isExtensible() 严格的一点是,它将不在允许配置当前所有属性。

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

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

// 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
  console.log(val + " -> " + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c

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

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

11、Object.isSealed(obj):方法判断一个对象是否被密封。

很简单的方法,返回值是Boolean。

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

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

12、Object.freeze(obj):方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象

冻结可以视作是对一个对象最严格的限制。

该方法返回传递的对象,而不是创建一个被冻结的副本。下面的例子将展示一些对冻结对象操作的结果:

var obj = {
  prop: function() {},
  foo: 'bar'
};

// 新的属性会被添加, 已存在的属性可能
// 会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;

// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
var o = Object.freeze(obj);

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

// 现在任何改变都会失效
obj.foo = 'quux'; // 静默地不做任何事
// 静默地不添加此属性
obj.quaxxor = 'the friendly duck';

// 在严格模式,如此行为将抛出 TypeErrors
function fail(){
  'use strict';
  obj.foo = 'sparky'; // throws a TypeError
  delete obj.quaxxor; // 返回true,因为quaxxor属性从来未被添加
  obj.sparky = 'arf'; // throws a TypeError
}

fail();

// 试图通过 Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError.
Object.defineProperty(obj, 'ohai', { value: 17 });
Object.defineProperty(obj, 'foo', { value: 'eit' });

// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }

13、Object.isFrozen(obj):方法判断一个对象是否被冻结。

很简单的方法,返回值是Boolean。

// 一个对象默认是可扩展的,所以它也是非冻结的.
Object.isFrozen({}); // === false

// 一个不可扩展的空对象同时也是一个冻结对象.
let vacuouslyFrozen = Object.preventExtensions({});
Object.isFrozen(vacuouslyFrozen) //=== true;

// 一个非空对象默认也是非冻结的.
let oneProp = { p: 42 };
Object.isFrozen(oneProp) //=== false

// 让这个对象变的不可扩展,并不意味着这个对象变成了冻结对象,
// 因为p属性仍然是可以配置的(而且可写的).
Object.preventExtensions(oneProp);
Object.isFrozen(oneProp) //=== false

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

Object.is()的参数是要比较的两个值,返回值是Boolean。

看下面的代码:

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

// +0不等于-0
+0 === -0 //true
NaN === NaN // false

// NaN等于自身
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
​

15、Object.assign():方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象

该方法有不少值得推敲和注意的点,下面的代码举出基本用法,其他部分详见拙文《Object.assign详解》

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

// 如果目标对象与源对象有同名属性,或多个源对象有同名属性
// 则后面的属性会覆盖前面的属性。

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

// source2 中的 c属性覆盖了source1  中的 c属性

16、Object.getOwnPropertyDescriptors():返回指定对象所有自身属性(非继承属性)的描述对象。

ES5的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor)。ES2017引入了

Object.getOwnPropertyDescriptor()方法,返回指定对象所有的自身属性(非继承属性)的描述对象。

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

上面代码中,Object.getOwnPropertyDescriptors()方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。

17、Object.setPrototypeOf():方法的作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

// 格式
Object.setPrototypeOf(object, prototype)

// 用法
const o = Object.setPrototypeOf({}, null);

该方法等同于下面的函数。

function setPrototypeOf(obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

下面是一个例子。

let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40

18、Object.getPrototypeOf():该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

Object.getPrototypeOf(obj);

下面是一个例子。

function Rectangle() {
  // ...
}

const rec = new Rectangle();

Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype

20、Object.values():方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

返回数组的成员顺序,与本章的《属性的遍历》部分介绍的排列规则一致。

const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]

上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是bca

Object.values只返回对象自身的可遍历属性。

const obj = Object.create({}, {p: {value: 42}});
Object.values(obj) // []

上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的,因为p的属性描述对象的enumerable默认是falseObject.values不会返回这个属性。只要把enumerable改成trueObject.values就会返回属性p的值。

const obj = Object.create({}, {p:
  {
    value: 42,
    enumerable: true
  }
});
Object.values(obj) // [42]

Object.values会过滤属性名为 Symbol 值的属性。

Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc']

如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

Object.values('foo')
// ['f', 'o', 'o']

上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组。

如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

Object.values(42) // []
Object.values(true) // []

20、Object.entries():方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

除了返回值不一样,该方法的行为与Object.values基本一致。

如果原对象的属性名是一个 Symbol 值,该属性会被忽略。

Object.entries({ [Symbol()]: 123, foo: 'abc' });
// [ [ 'foo', 'abc' ] ]

上面代码中,原对象有两个属性,Object.entries只输出属性名非 Symbol 值的属性。将来可能会有Reflect.ownEntries()方法,返回对象自身的所有属性。

Object.entries的基本用途是遍历对象的属性。

let obj = { one: 1, two: 2 };
for (let [k, v] of Object.entries(obj)) {
  console.log(
    `${JSON.stringify(k)}: ${JSON.stringify(v)}`
  );
}
// "one": 1
// "two": 2

Object.entries方法的另一个用处是,将对象转为真正的Map结构。

const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }

自己实现Object.entries方法,非常简单。

// Generator函数的版本
function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

// 非Generator函数的版本
function entries(obj) {
  let arr = [];
  for (let key of Object.keys(obj)) {
    arr.push([key, obj[key]]);
  }
  return arr;
}

21、Object.fromEntries():方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

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

该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。

// 例一
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }

该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。

Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

你可能感兴趣的:(JS,ES6,Javascript,对象方法)