js 对象
# js 对象
JavaScript 对象是拥有属性和方法的数据。
# 对象的循环引用
循环引用定义为对象的地址和源的地址相同
function circularReference() {
let obj1 = {};
let obj2 = {
b: obj1,
};
obj1.a = obj2;
}
2
3
4
5
6
7
obj1 中的 a 属性引用 obj2,obj2 中的 b 属性引用 obj1,这样就构成了循环引用
# Object.is
Object.is() 方法判断两个值是否为同一个值
Object.is()的用法与全等===基本一致,唯有不同的两点:
- +0 与-0 为 false
- NaN 与 NaN 为 true
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(0, +0); // true
Object.is(NaN, 0 / 0); // true
2
3
4
Object.is() 的实现原理:
Object.is = function(x, y) {
if (x === y) {
// 1/+0 = +Infinity, 1/-0 = -Infinity, +Infinity不等于-Infinity
// Infinity 属性用于存放表示正无穷大的数值。负无穷大是表示负无穷大一个数字值。
return x !== 0 || 1 / x === 1 / y;
}
// 一个变量不等于自身变量,那么它一定是 NaN
// 两个都是NaN的时候返回true
return x !== x && y !== y;
};
2
3
4
5
6
7
8
9
10
# Object.freeze 和 Object.seal 的区别
Object.freeze()冻结一个对象。不能添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。冻结一个对象后该对象的原型也不能被修改。
Object.seal()封闭一个对象。不能添加新属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,已有属性的值以然可以修改。(也就是说 descriptor 里的 writable 变成没有变,configurable 变成 false 了)
# Object 和 Map 区别
- 意外的键
Map 默认情况不包含任何键。只包含显式插入的键。
一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
- 键的类型
一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。
一个 Object 的键必须是一个 String 或是 Symbol。
- 键的顺序
Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。
一个 Object 的键是无序的
- Size
Map 的键值对个数可以轻易地通过 size 属性获取
Object 的键值对个数只能手动计算
- 迭代
Map 是 iterable 的,所以可以直接被迭代。
迭代一个 Object 需要以某种方式获取它的键然后才能迭代。
- 性能
Map 在频繁增删键值对的场景下表现更好。
Object 在频繁添加和删除键值对的场景下未作出优化。
使用 Map:
- 储存的键不是字符串/数字/或者 Symbol 时,选择 Map,因为 Object 并不支持
- 储存大量的数据时,选择 Map,因为它占用的内存更小
- 需要进行许多新增/删除元素的操作时,选择 Map,因为速度更快
- 需要保持插入时的顺序的话,选择 Map,因为 Object 会改变排序
- 需要迭代/遍历的话,选择 Map,因为它默认是可迭代对象,迭代更为便捷
使用 Object:
- 只是简单的数据结构时,选择 Object,因为它在数据少的时候占用内存更少,且新建时更为高效
- 需要用到 JSON 进行文件传输时,选择 Object,因为 JSON 不默认支持 Map
- 需要对多个键值进行运算时,选择 Object,因为句法更为简洁
- 需要覆盖原型上的键时,选择 Object
参考:https://zhuanlan.zhihu.com/p/358378689 (opens new window)
# 对象的扁平化和反扁平化
// 实现一个 flatten 函数,实现如下的转换功能
const obj = {
a: 1,
b: [1, 2, { c: true }],
c: { e: 2, f: 3 },
g: null,
};
// 转换为
let objRes = {
a: 1,
"b[0]": 1,
"b[1]": 2,
"b[2].c": true,
"c.e": 2,
"c.f": 3,
g: null,
};
Object.flatten = function(obj) {
var result = {};
function recurse(src, prop) {
var toString = Object.prototype.toString;
if (toString.call(src) == "[object Object]") {
var isEmpty = true;
for (var p in src) {
isEmpty = false;
recurse(src[p], prop ? prop + "." + p : p);
}
if (isEmpty && prop) {
result[prop] = {};
}
} else if (toString.call(src) == "[object Array]") {
var len = src.length;
if (len > 0) {
src.forEach(function(item, index) {
recurse(item, prop ? prop + ".[" + index + "]" : index);
});
} else {
result[prop] = [];
}
} else {
result[prop] = src;
}
}
recurse(obj, "");
return result;
};
Object.unflatten1 = function(data) {
if (Object(data) !== data || Array.isArray(data)) return data;
var result = {},
cur,
prop,
idx,
last,
temp;
for (var p in data) {
(cur = result), (prop = ""), (last = 0);
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = !isNaN(parseInt(temp)) ? [] : {});
prop = temp;
last = idx + 1;
} while (idx >= 0);
cur[prop] = data[p];
}
return result[""];
};
Object.unflatten2 = function(data) {
if (Object(data) !== data || Array.isArray(data)) return data;
var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while ((m = regex.exec(p))) {
cur = cur[prop] || (cur[prop] = m[2] ? [] : {});
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 判断空对象的几种方法
- JSON.stringify
JSON.stringify(data) === "{}"; //true
- Object.getOwnPropertyNames
var data = {};
var arr = Object.getOwnPropertyNames(data);
arr.length === 0; //true
2
3
- Object.keys
var data = {};
var arr = Object.keys(data);
arr.length === 0; //true
2
3
- for in
var obj = {};
var b = function() {
for (var key in obj) {
return false;
}
return true;
};
alert(b()); //true
2
3
4
5
6
7
8
# js 对象循环
# 1. for in
适用范围:对象
for in 概念:以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性。
for-in 语法:for(keys in zhangsan){}
keys 表示 obj 对象的每一个键值对的键!!所有循环中,需要使用 obj[keys]来取到每一个值!!!
for-in 循环,遍历时不仅能读取对象自身上面的成员属性,也能延续原型链遍历出对象的原型属性
所以,可以使用 hasOwnProperty 判断一个属性是不是对象自身上的属性。
obj.hasOwnProperty(keys)==true 表示这个属性是对象的成员属性,而不是原先属性
使用 for in 也可以遍历数组,但是会存在以下问题:
for in 遍历的是数组的索引(即键名),而 for of 遍历的是数组元素值。
特别情况下, for ... in 循环会以看起来任意的顺序遍历键名
使用 for in 会遍历数组所有的可枚举属性,包括原型属性
index 索引为字符串型数字,不能直接进行几何运算
for in 会遍历手动添加的其他键
var obj = { "0": "a", "1": "b", "2": "c" };
for (var i in obj) {
console.log(i, ":", obj[i]);
}
2
3
4
5
# 2. Object.keys()
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性).
var obj = { "0": "a", "1": "b", "2": "c" };
Object.keys(obj).forEach(function(key) {
console.log(key, obj[key]);
});
2
3
4
5
# 3. Object.getOwnPropertyNames
返回一个数组,包含对象自身的(不含继承的)所有属性(不含 Symbol 属性,但是包括不可枚举属性)
var obj = { "0": "a", "1": "b", "2": "c" };
Object.getOwnPropertyNames(obj).forEach(function(key) {
console.log(key, obj[key]);
});
2
3
4
# 4. Reflect.ownKeys
返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举.但不包括继承自原型的属性
var obj = { "0": "a", "1": "b", "2": "c" };
Reflect.ownKeys(obj).forEach(function(key) {
console.log(key, obj[key]);
});
2
3
4
# Object.keys 和 for in 区别
Object.keys()不会走原型链,而 for in 会走原型链;
Object.keys()会返回一个数组,而 for in 无返回值;
# 创建对象三种方式
# 1. {} 字面量
# 2. new Object()
new Object()将会根据参数 value 的数据类型,返回对应类型的对象:
如果 value 为基本数据类型 String、Number、Boolean,则返回对应类型的对象。
如果 value 本身为对象,则返回其本身。
如果省略了 value 参数,或 value 为 null、undefined,则返回自身无任何属性的 Object 对象,即返回一个空对象。
var obj = new Object("text");
console.log(obj instanceof Object); //true
console.log(obj instanceof String); //true
console.log(obj.length); //4
console.log(obj[0]); //t
/*即类似于obj = {0:"t", 1:"e", 2:"x", 3:"t"};*/
2
3
4
5
6
new 关键字做了几件事
创建一个新对象
链接到原型:将新对象的隐式原型指向构造函数的原型
执行构造函数,绑定 this
返回这个对象
比如创建一个 Car 对象,伪代码
// new Car()
var obj = new Object();
obj._proto_ = Car.prototype;
// 执行构造函数, 绑定 this
Car.call(obj);
2
3
4
5
我们注意到比较关键的地方是,它调用了 Car 对象的构造函数,并通过 call 将 obj 的 this 绑定到了 Car 对象上
这一步操作将 Car 对象上的属性,继承到了 obj 上
手写 new:
/*
* 1.创建一个空对象
* 2.链接到原型
* 3.绑定this值
* 4.返回新对象
*/
function createNew() {
let obj = {}; // 1.创建一个空对象
// 把类数组对象转为数组对象,shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
let constructor = [].shift.call(arguments);
obj.__proto__ = constructor.prototype; // 2.链接到原型
// 3.绑定this值 使用apply,将构造函数中的this指向新对象,这样新对象就可以访问构造函数中的属性和方法
let result = constructor.apply(obj, arguments); //
return typeof result === 'object' ? result : obj; // 4.返回新对象
}
function People(name, age) {
this.name = name;
this.age = age;
}
let peo = createNew(People, 'Bob', 22);
console.log(peo.name);
console.log(peo.age);
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
# 3. Object.create()
Object.create()方法接受两个参数: Object.create(obj,propertiesObject) ;
obj:一个对象,应该是新创建的对象的原型。
propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与 Object.defineProperties()的第二个参数一样)。注意:该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。
创建一个干净的空对象:
Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法
手写 create 方法
Object.myCreate(Car) {
var F = function() {};
F.prototype = Car;
var newObj = new F()
return newObj;
}
2
3
4
5
6
newObj.__proto__ == F.prototype == Car
可以看出,在 Object.create 的内部,并没有去调用 Car 构造函数,而是调用了创建新对象的构造函数,因此 Car 上的属性不会继承到 Object.create 创建的实例中
# 4. 区别
- {} 字面量和 new Object()区别
new Object()可以传参,new Object()没有传入值和{}是一样的
- new Object()和 Object.create()区别
new object(), 原型 Object.prototype
Object.create 可以指定原型,创建的新对象的__proto__
指向传入的参数
# js 对象问题汇总
# for of 怎么遍历对象?
- 使用 Object.keys
const obj = {
a: 1,
b: 2,
c: 3,
};
for (let i of Object.keys(obj)) {
console.log(i);
// 1
// 2
// 3
}
2
3
4
5
6
7
8
9
10
11
12
- 实现 iterator 接口
const obj = {
e: 5,
f: 6,
};
newObj[Symbol.iterator] = function*() {
let keys = Object.keys(this);
for (let i = 0, l = keys.length; i < l; i++) {
yield {
key: keys[i],
value: this[keys[i]],
};
}
};
for (let { key, value } of newObj) {
console.log(key, value);
}
// 输出结果
// e 5
// f 6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21