我正在参加「掘金·启航计划」
这些内容会在本篇出现:
- Object.assign和解构的区别
- 如何获取对象中的所有key
omit.js
源码非常短,直接看实现:
function omit(obj, fields) {
const shallowCopy = Object.assign({}, obj);
for (let i = 0; i < fields.length; i += 1) {
const key = fields[i];
delete shallowCopy[key];
}
return shallowCopy;
}
简单易懂:
- 通过
Object.assign对对象进行拷贝 - 遍历需要删掉的 key list
- 通过 delete 运算符删掉当前遍历中的key
- 返回拷贝+删除后的对象
值得一提的是,git记录里这里曾经用过
…obj而不是Object.assign,这两个最常用的首层深拷贝的方法在大多数点上都一致,唯一的区别在于Object.assign会使用setter而解构不会。 举例来说:
Object.assign({set a(v){this.b=v}, b:2}, {a:4});
// {b: 4}
{…{set a(v){this.b=v}, b:2}, …{a:4}};
// {a: 4, b: 2}
lodash
lodash里的omit方法实现用了许多工具方法,并且相比起omitjs,它支持更多的入参类型。
从测试用例可以看出,甚至还包括第二个参数是function的选项。为了直取重点,这里会忽略掉一部分参数处理的逻辑,单纯考虑第二个参数为
string[]的场景。
在4.17.15版本的lodash中,omit方法由 flatRest 方法包裹,这个方法会把超出2个的参数合并为2个,例如:
const origin = { a: 1, b: "2", c: 3 };
const result = omit(origin, “a”, “b”, [“c”, [“d”]]);
// omit方法实际接受到的参数为:
// { a: 1, b: "2", c: 3 } 和 [ 'a', 'b', 'c', [ 'd' ] ]
正如上文所说,这里不展开讲具体的实现逻辑,我们直接进入function内部,为每一行代码加上注释
function omit(object, paths) {
var result = {};
// 虽然这里没有判断undefined,但在copyObject的时候也做了容错
if (object == null) {
return result;
}
var isDeep = false;
// 把['a', 'b', 'c']变成[[ 'a' ], [ 'b' ], [ 'c' ]]
// arrayMap 相当于 map,只是用while写的循环
paths = arrayMap(paths, function (path) {
path = castPath(path, object);
isDeep || (isDeep = path.length > 1);
return path;
});
// 将object深拷贝给result
copyObject(object, getAllKeysIn(object), result);
// 只有一层的情况下,这里不走,--忽略开始--
if (isDeep) {
result = baseClone(
result,
CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG,
customOmitClone
);
}
// --忽略结束--,当前result为object的深拷贝
var length = paths.length;
while (length—) {
// 通过修改result的方式,去掉path里对应的属性
baseUnset(result, paths[length]);
}
// 返回处理完成的对象
return result;
}
浅看下来,可以看到关键在于
copyObject(object, getAllKeysIn(object), result);
和
var length = paths.length;
while (length—) {
// 通过修改result的方式,去掉path里对应的属性
baseUnset(result, paths[length]);
}
这两段代码,我们进入去看。
1. copyObject(object, getAllKeysIn(object), result)
1.1 getAllKeysIn(object)
首先看 getAllKeysIn(object) 是啥,从字面上猜测是获取所有的key,实际看也确实如此,这个方法合并了keysIn(创建一个 object 自身 和 继承的可枚举属性名为数组) 和 getSymbolsIn (内部方法,获取object 自身 和 继承的可枚举Symbol属性名为数组)。
keysIn的核心逻辑为:
var result = [];
// for...in: 按照原型链遍历对象上所有可枚举且key值不为Symbol的属性
for (var key in object) {
// 忽略掉constructor
if (
key == "constructor" &&
(isProto || !hasOwnProperty.call(object, key))
) {
continue
}
result.push(key);
}
return result;
getSymbolsIn在环境合适的情况下即为getOwnPropertySymbols。
1.2 copy
经过上面的处理,可以知道,对于 { a: 1, b: '2', c: 3 } [ 'a', 'c' ] 这个例子来说,我们的入参为copyObject({ a: 1, b: '2', c: 3 }, [ 'a', 'b', 'c' ], {}(result的引用))。
那么继续对copyObject的方法进行简化:
function copyObject(source, props, object, customizer) {
var isNew = !object; // var isNew = false
object || (object = {}); // 无操作
var index = -1,
length = props.length; // length = 3
// 遍历props,也即是[ 'a', 'b', 'c' ]
while (++index < length) {
var key = props[index];
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined; // 相当于 var newValue = undefined
if (newValue === undefined) {
// 走这里,newValue等于原对象key的value
newValue = source[key];
}
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
// 走这里, 对于普通的key assignValue的核心代码为
// object[key] = value
// 在传入的原对象上赋值
assignValue(object, key, newValue);
}
}
return object;
}
走过一遍代码可以看出,本质就是 新对象[key] = 原本对象[key],并没有判断是否是引用之类的,所以等同于omitjs里面的Object.assign和解构,或者说是 Object.assign和解构 等同于它,毕竟这是7年前的代码,那时候还没有es2015。
baseUnset(result, paths[length]);
核心代码相当于delete object[key]
delete object[toKey(last(path))]
拆到这里,可以看到虽然lodash虽然有许多边界条件判断容错和代码模拟,但是本质上来说跟omitjs是一样的,只是写法不同。而我的心情也有点复杂,毕竟两者代码的阅读难度完全不是一个等级的,但是学习嘛,不学到怎么知道这里学过了呢...
参考文档: javascript - Object spread vs. Object.assign - Stack Overflow