# Object

# 1.静态方法

# 遍历

# Object.keys()

Object.keys() 方法用来遍历对象的属性,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致

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

# Object.values()

Object.values() 方法用来遍历对象的属性,的顺序与使用for...in (opens new window)循环的顺序相同

var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]
1
2

# Object.entries()

Object.entries()方法用来遍历键值,顺序与使用for...in (opens new window)循环的顺序相同

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


//将Object转换为Map
var obj = { foo: "bar", baz: 42 };
var map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }
1
2
3
4
5
6
7
8

# 对象属性

# Object.getOwnxxx

  • Object.getOwnPropertyNames(obj) 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
  • Object.getOwnPropertyDescriptor(obj,prop) 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
  • Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
  • Object.getOwnPropertySymbols(obj) 方法返回一个给定对象自身的所有 Symbol 属性的数组
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]


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


var obj = {};
var a = Symbol("a");
obj[a] = "localSymbol";
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols)        // [Symbol(a)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Object.defineProperty()/Object.defineProperties()

Object.defineProperty()方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象,它的用法如下。

Object.defineProperty方法接受三个参数,依次如下。

  • object:属性所在的对象
  • propertyName:字符串,表示属性名
  • attributesObject:属性描述对象
var obj = Object.defineProperty({}, 'p', {
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false,
  get() { return ...; }, //可选
  set(value) { ... = value; }, //可选
});
1
2
3
4
5
6
7
8

如果一次性定义或修改多个属性,可以使用Object.defineProperties()方法

# 控制对象不可变

概念: 对象不变性在任何编程语言中都是一个重要的概念。它会限制对象修改并防止不需要的更改。简而言之,对象的不变性就是将它的状态变为只读的,下面就来看看在 JavaScript 中的对象不变性。

在JavaScript中,一个对象可以有一个或多个属性。每个属性都是一个键值对,下面是一个对象:

const user = {
	name: 'CUGGZ',
  age: 24,
}
1
2
3
4

这里使用const关键字定义了一个对象,它具有两个属性。默认情况下,对象是可变的,也就是说,我们可以给对象添加新属性,修改现有属性或者删除已有属性。而在某些情况下,我们可能希望对象是不可变的,即不能添加新属性,也不能修改和删除已有属性。

# Object.freeze()

顾名思义,freeze() 就是用来冻结对象的。只需要将不想被更改的对象传递给它,就会返回该对象的不可变版本:

const user = {
	name: 'CUGGZ',
  age: 24,
}

const freezeUser = Object.freeze(user);
freezeUser.age = 18;
console.log(freezeUser.age) // 24
1
2
3
4
5
6
7
8

这时,新的对象就不可变了,相当于被冻结了。

在JavaScript了,提供了一个Object.isFrozen(),它可以用来判断一个对象是否被冻结:

Object.isFrozen(user)  // false
Object.isFrozen(freezeUser)  // true
1
2

需要注意的是, Object.freeze() 方法不会影响嵌套对象,对于嵌套对象,冻结后仍然是可以操作的:

const user = {
	name: 'CUGGZ',
  age: 24,
  article: {
  	title: 'learn javascript',
    number: 1234
  }
}

const freezeUser = Object.freeze(user);
freezeUser.age = 18
freezeUser.article.number = 666;
console.log(freezeUser.age)             // 24
console.log(freezeUser.article.number); // 666
1
2
3
4
5
6
7
8
9
10
11
12
13
14

可以看到,使用 Object.freeze() 方法冻结的对象,age是不可以更改的,但是嵌套对象的number属性还是可以修改的。如果需要冻结嵌套对象使其不可变,就需要使用循递归来逐级冻结,下面是一个简单的递归冻结实现:

const deepFreeze = obj => {
  Object.keys(obj).forEach(prop => {
    if (typeof obj[prop] === 'object') {
    	deepFreeze(obj[prop]);
    }
  });
  return Object.freeze(obj);
};

deepFreeze(user);
1
2
3
4
5
6
7
8
9
10

Object.freeze()方法除了可以用来冻结对象以外,还可以用于冻结数组,使其不可变:

const number = [1, 2, 3, 4, 5];
const freezeNumber = Object.freeze(number);
freezeNumber.push(6);
1
2
3

此时就会报错了:

VM210:3 Uncaught TypeError: Cannot add property 5, object is not extensible
1

# Object.seal()

Object.seal() 顾名思义就是密封对象,它是另一种使对象不可变的方法。相对于freeze(),Object.seal() 方法仅保护对象不能添加和删除属性,它允许更新现有的属性。

const user = {
	name: 'CUGGZ',
  age: 24,
}

const sealUser = Object.seal(user);
sealUser.age = 18;
delete sealUser.name;
console.log(sealUser)   // {name: 'CUGGZ', age: 18}
1
2
3
4
5
6
7
8
9

可以看到,我们识图删除对象中的name属性,删除失败;而修改已有的age属性,修改成功。

Object.seal()方法和Object.freeze()一样,对于嵌套的对象,是无法实现不可变的,如果想要实现,同样要使用递归来一层层来密封对象。

JavaScript同样提供了一个Object.isSealed() 方法来确认对象的密封状态:

Object.isSealed(user)      // false
Object.isSealed(sealUser)  // true
1
2

# const关键字?

你是否会认为,使用const关键字也能达到相同的结果呢?实际上,他们是不一样的,当我们使用const关键字来创建对象时,它会阻止我们重新分配值,但是我们可以更新、添加、删除已有对象的属性:

const user = {
	name: 'CUGGZ',
  age: 24,
}

delete user.age;
user.height = 180;
user.name = 'hello';
console.log(user);  // {name: 'hello', height: 180}
1
2
3
4
5
6
7
8
9

而如果我们给user重新赋值,那么就会报错了:

Uncaught TypeError: Assignment to constant variable.
1

因此,const关键字仅仅是提供了赋值的不变性,而不会提供值的不变性(对于对象来说)。

# Object.preventExtensions()

Object.seal()方法会阻止更新、删除和向对象添加新属性,Object.seal()只会阻止添加和删除属性。

除此之外,JavaScript还提供了一个Object.preventExtensions()方法,该方法可以让一个对象变的不可扩展,也就是永远不能再添加新的属性。

const user = {
	name: 'CUGGZ',
  age: 24,
}

const newUser = Object.preventExtensions(user);
newUser.height = 180;
console.log(newUser);  //  {name: 'CUGGZ', age: 24}
1
2
3
4
5
6
7
8

最后来看看它们三个的对比:

方法/操作 读取 创建 更新 删除
Object.freeze() ✔️
Object.seal() ✔️ ✔️
Object.preventExtensions() ✔️ ✔️ ✔️

# 原型

# Object.create()

该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

语法:Object.create(proto,[propertiesObject])

//模拟实现
function createObject(proto) {
    function F(){}
    F.prototype = proto;
    return new F();
}
1
2
3
4
5
6

使用:

// Shape - 父类(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// 子类继承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

rect instanceof Rectangle); // true

rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
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

实现多继承

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);

// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);

// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do a thing
};

object.assign 会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,
使 MyClass 的所有实例都可用 OtherSuperClass 的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Object.assign()

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

语法:Object.assign(target, ...sources)

  • target:目标对象
  • source:源对象

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
1
2
3
4
5
6

# Object.setPrototypeOf()

Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null (opens new window)

语法:Object.setPrototypeOf(obj, prototype)

  • obj:要设置其原型的对象
  • prototype:该对象的原型

等于

function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}
1
2
3
4

# Object.getPrototypeOf()

Object.getPrototypeOf() 方法返回指定对象的原型(标准方法)(内部[[Prototype]]属性的值)。

等于 __proto__(非标准方法)

var proto = {};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true
1
2
3

# 2.实例方法

# isPrototypeOf()

用于测试一个对象是否存在于另一个对象的原型链上。

function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true

//检查 baz 对象是否继承自 Foo.prototype:
if (Foo.prototype.isPrototypeOf(baz)) {
  // do something safe
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# hasOwnProperty()

Object.prototype.hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1'));
//  true
1
2
3
4
5

# toString()

toString方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。

不同数据类型的Object.prototype.toString方法返回值如下。

  • 数值:返回[object Number]。
  • 字符串:返回[object String]。
  • 布尔值:返回[object Boolean]。
  • undefined:返回[object Undefined]。
  • null:返回[object Null]。
  • 数组:返回[object Array]。
  • arguments 对象:返回[object Arguments]。
  • 函数:返回[object Function]。
  • Error 对象:返回[object Error]。
  • Date 对象:返回[object Date]。
  • RegExp 对象:返回[object RegExp]。
  • 其他对象:返回[object Object]。

可以用它判断类型

// 判断数据类型函数
function toRawType (value) {
  return Object.prototype.toString.call(value).slice(8, -1)
}

//结果
toRawType({}) //  Object 
toRawType([])  // Array    
toRawType(true) // Boolean
toRawType(undefined) // Undefined
toRawType(null) // Null
toRawType(function(){}) // Function
1
2
3
4
5
6
7
8
9
10
11
12

# propertyIsEnumerable()

实例对象的propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false。

var obj = {};
obj.p = 123;

obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false
1
2
3
4
5

# 3.包装对象

所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。

这三个对象作为构造函数使用(带有new)时,可以将原始类型的值转为对象;

作为普通函数使用时(不带有new),可以将任意类型的值,转为原始类型的值。

var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);

typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"

// 字符串转为数值
Number('123') // 123

// 数值转为字符串
String(123) // "123"

// 数值转为布尔值
Boolean(123) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# valueOf()

valueOf()方法返回包装对象实例对应的原始类型的值

new Number(123).valueOf()  // 123
new String('abc').valueOf() // "abc"
new Boolean(true).valueOf() // true
1
2
3

# toString()

toString()方法返回对应的字符串形式。

new Number(123).toString() // "123"
new String('abc').toString() // "abc"
new Boolean(true).toString() // "true"
1
2
3

# 自动转换

调用包装对象的属性和方法时,原始类型的值会自动当作包装对象调用。

'abc'.length // 3

// 自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性
1
2
3

调用结束后,包装对象实例会自动销毁。这意味着,下一次调用字符串的属性时,实际是调用一个新生成的对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性。

如果要为字符串添加属性,只有在它的原型对象