Fork me on GitHub

对像-操作方法

对像-操作方法

对象冻结

  • Object.freeze(obj)

冻结对象,使其不能修改、删除、新增属性

1
2
3
4
5
6
7
8
9
10
11
const o1={x:10,y:10}
o1.x=11
//o1
//{x: 11, y: 10}
Object.freeze(o1)
const o2=o1;
o2.x=22
//o1
//{x: 11, y: 10}
//o2
//{x: 11, y: 10}

冻结不是深度冻结,如果属性值还是对象的话,还是可以修修改的

1
2
3
4
5
const o3={x:{y:1}}
Object.freeze(o3)
o3.x.y=2
//o3
//{x:{y:2}}

对象遍历

  • Object.entries()

将对象变成数组,对象属性变成子数组
返回一个给定对象自身可枚举属性的键值对数组,数组每个元素是一个包含键和值的数组

1
2
3
4
5
6
7
8
9
let obj = {x:1,y:2}
let arr = Object.entries(obj)
//arr
//[['x',1],['y',2]]
for(let [x, y] of arr){
console.log(x,y)
}
//x 1
//y 2
  • Object.keys()

    返回对象自身可枚举属性属性名组成的数组,不包含 Symbol 类型的属性

  • for… in

    遍历返回对象的自身和原型链上的可枚举属性(不包含 Symbol 属性)

  • Object.getOwnPropertyNames()

    返回对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值属性)组成的数组

  • Object.getOwnPropertySymbols(obj)

    返回对象的所有 Symbol 类型属性的 key 组成数组

  • Reflect.ownKeys()

    返回对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举.

判断对象是否含有某属性

  • key in obj:检测指定对象(obj)原型链上是否有对应的属性值。
  • obj.hasOwnProperty(key)判断 key 是不是 obj 的私有属性。
    在 ES5 里,如果此方法的参数不是对象(而是一个原始值),那么它会抛出 TypeError。在 ES2015 中,非对象的参数将被强制转换为一个对象。

对象浅拷贝

  • Object.assign({}, obj)

    将 obj 拷贝到空对象,返回目标对象
    Object.assign(target, ...source)
    方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象。会修改原对象。

  • 扩展运算符

    1
    let newObj={...obj}
  • Array.prototype.concat()

  • Array.prototype.slice()

    注意:
    Array 的 slice 和 concat 方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

  • 手写

1
2
3
4
5
6
7
8
9
10
function shadowClone(obj) {
//保证新对象与原对象是相同的构造函数的实例
let newObj = new obj.constructor;
for (let key in obj) {
if (obj.hasOwnProperty(key)) { //只拷贝私有属性,不拷贝原型链上的属性
newObj[key] = obj[key]
}
}
return newObj
}

对象深拷贝

  • JSON.parse(JSON.stringify(obj))

缺点:

  • 时间对象拷贝后不是时间对象而是字符串
    date
  • 正则表达式、Error 对象拷贝后变成空对象
    date
  • 函数、undefined 拷贝后会丢失
    date
  • NaNInfinity-Infinity,拷贝后会变成 null
    date
  • 只能序列化对象的可枚举的自有属性,如果 obj 中的对象是由构造函数生成的,则使用 JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的 constructor。
  • 如果对象中存在循环引用的情况也无法正确实现深拷贝。

总结
JSON.parse(JSON.stringify(X)),其中 X 只能是 Number, String, Boolean, Array, 扁平对象,即那些能够被 JSON 直接表示的数据结构

JSON.stringify()的其他用法

  • 判断数组是否包含某对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let data = [
    {name:'John'},
    {name:'Mary'},
    {name:'Lucky'},
    ],
    val = {name:'Mary'};
    console.log(JSON.stringify(data).indexOf(JSON.stringify(val)) !== -1);//true
    console.log(data.indexOf(val) !== -1)//false
    //因为两个对象是引用值不同,所以需要序列化一下转成字符串
  • 判断对象/数组是否相等

    1
    2
    let a=[1,2,3],b=[1,2,3];
    JSON.Stringify(a)===JSON.stringify(b);//true

与 toString()的区别

1
2
3
let arr = [1,2,3];
console.log(JSON.stringify(arr));//'[1,2,3]'
console.log(arr.toString());//'1,2,3'
  • 手写深拷贝

    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 isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
    }
    function deepClone(obj) {
    //递归终止条件
    if (obj === null) return null;
    if (!isObject(obj)) return obj;
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) {
    return new Date(obj)
    }
    //测试了一下,貌似还是拷贝了引用地址,未找到替换方法
    if (typeof obj === 'function') {
    return new Function("return " + JSON.stringify(obj))();
    }
    if (obj instanceof TypeError) {
    return new TypeError(obj)
    }
    //保证新对象与原对象是相同的构造函数的实例,比如原对象是[]
    let newObj = new obj.constructor;
    for (let key in obj) {
    if (obj.hasOwnProperty(key)) { //只拷贝私有属性,不拷贝原型链上的属性
    newObj[key] = deepClone(obj[key])
    }
    }
    return newObj
    }

问题
如果存在循环引用递归终止条件就无法执行,陷入死循环。

解决循环引用问题

  • 修改

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
29
30
31
32
const deepClone = (value, hash = new WeakMap) => {
if (value == null) {
//排除null 和 undefine的情况,直接返回
return value;
}
if (typeof value !== "object") {
//基本数据类型和函数的情况直接返回即可
return value;
}
if (value instanceof RegExp) {
//正则的情况,返回新的正则即可
return new RegExp(value);
}
if (value instanceof Date) {
return new Date(value);
}
//处理对象或者数组的情况,new 创建新的空对象或数组
let instance = new value.constructor();
if (hash.has(value)) {
//在hash 中查询一下是否存在过,如果存在就把以前拷贝的返回
return hash.get(value);
}
hash.set(value, instance); //没有存过就放进去
for (let key in value) {
if (value.hasOwnProperty(key)) {
//排除原型链上的属性或方法
instance[key] = deepClone(value[key], hash);
//将hash继续传递下去,保证每次能拿到以前拷贝的结果
}
}
return instance;
}

更完善的深拷贝

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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}

function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
return Object.prototype.toString.call(target);
}

function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}

function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}

function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}

function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}

function clone(target, map = new WeakMap()) {

// 克隆原始类型
if (!isObject(target)) {
return target;
}

// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}

// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);

// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}

// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}

// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});

return cloneTarget;
}

module.exports = {
clone
};
-------------本文结束感谢阅读-------------