JS对象-潜在知识点总结

前言

平常使用Object的频率非常高,但是使用它自带的一些API时,会有一些潜在的知识点,这些知识点可能会是影响bug产生的原因,接下来我将一一总结。

Object.keys、Object.values、Object.entries

在使用Object.keysObject.valuesObject.entries 遍历对象自身属性的时候,原形上的属性不会被遍历出来。

js 复制代码
const obj = Object.create({
    // 原型链不会被遍历
    age: 21
}, {
    name: {
        value: '1in',
        enumerable: true,
    }
});

Object.keys(obj); // ["1in"]

这三个函数的筛选逻辑本质上是一致的,都是调用了一个叫做 EnumerableOwnProperties() 的内部方法,只不过输出的数据不同,一个是所有的键,一个是所有的值,最后一个是键值。

我们从这个内部方法的名字就可以看出,它只会遍历可枚举的属性,所以当对象的属性修饰符enumerable为false时,其属性是不会出现在遍历的结果中的

js 复制代码
const obj = Object.create(null, {
    name: {
        value: '1in',
        enumerable: true,
    },
    age: {
        value: '21',
        // 不可枚举
        enumerable: false,
    }
});

Object.keys(obj); // ["1in"]

注意!!!我们都知道,Object的key值可以是string类型或者是symbol类型,这三种方法的遍历是不会遍历出key值为symbol类型的属性的。

js 复制代码
const obj = {
    name: '1in',
    // Symbol 不输出
    [Symbol('age')]: 21,
};

Object.keys(obj); // ["1in"]

所以,我们可以总结出 Object.keys/values/entries 只会遍历出对象自身的、可枚举的、以字符串类型为键的属性

遍历对象自身所有属性

经过上边的知识点,我们知道Object.keys/values/entries 只会遍历出对象自身的、可枚举的、以字符串类型为键的属性

所以,我们现在需要遍历出自身的所有数据,用什么方法呢?

Object.getOwnPropertyNames 可以用来获取其中的字符串键。Object.getOwnPropertySymbols 用来获取其中的 Symbol 键。

把他们两个结合起来,不就遍历出对象的所有属性了吗。

js 复制代码
var obj = Object.create(null, {
    [Symbol('name')]: {
        value: '1in',
        writable: false,
        enumerable: true,
        configurable: true,
    },
    age: {
        value: '21',
        writable: true,
        enumerable: true,
        configurable: true,
    },
});
console.log([
    ...Object.getOwnPropertyNames(obj),
    ...Object.getOwnPropertySymbols(obj),
]); // ["age", Symbol(name)]

但是,这样总感觉怪怪的。

因为,Object.getOwnPropertyNames 是 ES5 引入的,当时还没有 Symbol 类型,因此它只会返回一个字符串数组。ES6 引入 Symbol 之后,如果要求 Object.getOwnPropertyNames 也返回 Symbol 类型的话,那么恐怕很多代码都会出错。所以为了向后兼容的考量,又引入了一个 Object.getOwnPropertySymbols 专门返回 Symbol 类型的键。

当然,我们也有一个方法可以直接返回对象的key值为string类型或者symbol类型的方法。

ES6 引入了一个 Reflect.ownKeys 函数。

js 复制代码
//接着上边的代码
console.log(Reflect.ownKeys(obj)); // ["age", Symbol(name)]

与此效果差不多的还有 Object.getOwnPropertyDescriptors,但是它还会额外返回每一个键属性修饰符。

js 复制代码
// {
//   age: { value: '21', writable: true, enumerable: true, configurable: true },
//   [Symbol(name)]: { value: '1in', writable: false, enumerable: true, configurable: true }
// }
console.log(Object.getOwnPropertyDescriptors(obj));

遍历对象及它的原型链上的可枚举属性

能够实现遍历原型链的现成方法,目前只有 for...in 一种。

但是,它只能遍历可枚举、字符串键的属性。

js 复制代码
var obj = Object.create(
    Object.create(null, {
        // 原型链上的属性会被遍历
        name: {
            value: '1in',
            writable: true,
            enumerable: true,
            configurable: true,
        },
    }), {
        // Symbol 不会被遍历
        [Symbol('age')]: {
            value: '21',
            writable: false,
            enumerable: true,
            configurable: true,
        },
        school: {
            value: 'cdut',
            writable: true,
            enumerable: true,
            configurable: true,
        },
        // 不可枚举的属性不会被遍历
        company: {
            value: 'alibaba',
            writable: true,
            enumerable: false,
            configurable: true,
        },
    }
);

for (let key in obj) {
    console.log(key); // 1in cdut
}

如果想把 Symbol 包括进来,甚至和那些不可枚举的属性,只能自己实现。下面就是一种未经过优化的代码,仅代表其可能性。

js 复制代码
function getExtendedKeys(obj) {
    const visitedKeys = new Set();
    let current = obj;
    
    // 向上遍历原型链
    while (current) {
        // 遍历当前属性
        const keys = Reflect.ownKeys(current);
        keys.forEach(key => {
            visitedKeys.add(key);
        });

        current = Object.getPrototypeOf(current);
    }

    return Array.from(visitedKeys);
}

原理是原型链遍历和属性遍历。

对象合并

一般的,我们合并两个对象时,会采用Object.assign()Object Spread(对象展开) 这两种方式。 Object Spread(对象展开)大家可能一时反应不过来,下边是我们使用的方法。

js 复制代码
//Object Spread(对象展开)
const a={
    name:"1in"
}
const b={
    age:21
}

const c={
    ...a,
    ...b
}

然而事实上,它们的原理完全不同。

Object.assign 以 set 的方式赋值属性,而 Object Spread 以 defineProperty 的方式定义属性

js 复制代码
function assign(dest, src) {
    for (let key in src) {
        // 跳过非自身属性
        if (!src.hasOwnProperty(key)) continue;
        // set
        dest[key] = src[key];
    }
}

function spread(dest, src) {
    for (let key in src) {
        // 跳过非自身属性
        if (!src.hasOwnProperty(key)) continue;
        // defineProperty
        Object.defineProperty(dest, key, {
            value: src[key],
            writable: true,
            enumerable: true,
            configurable: true,
        })
    }
}

Object.assign() 将源对象的可枚举属性都取出来,直接赋值给目标对象;Object Spread 语法也是将源对象的可枚举属性都取出来,不过是在目标对象上定义一个数据属性。

Object.assign() 可能会将数据赋值到目标对象的原型上,如果原型上有这个 key 的存取器属性的话;

Object Spread 抛弃了源对象属性的描述符,无论它是数据属性还是存取器属性,无论是可配置的还是不可配置的,也无论是可枚举的还是不可枚举的,最终都转换为目标对象上的一个可枚举、可配置、可写的数据属性

js 复制代码
var _name = null;

var dest = Object.create({
    set name(n){
        _name = n;
    },
    get name(){
        return _name;
    },

});

Object.assign(dest, {
    name: '1in'
});
console.log(dest.name); // "bar"
// Object.assign 赋值到了对象的原型上而非对象本身
console.log(Object.getOwnPropertyDescriptor(dest, 'name')); // undefined
js 复制代码
var source = Object.create(null, {
    name: {
        get() {
            return '1in';
        },
        set(){},
        enumerable: true,
        configurable: false,
    },
});

const dest = { ...source };
// Object Spread 在目标对象上定义可配置的数据属性
console.log(Object.getOwnPropertyDescriptor(dest, 'name')); // { value: '1in', writable: true, enumerable: true, configurable: true }

所以object.assign会有以下报错场景:

  1. 如果目标对象现存属性是只读的,Object.assign 可能会失败。
  2. 如果目标对象现存属性是不可配置的,或者对象不可扩展,那么 Object.assign 可能会失败。

如果来源对象有问题的话(比如只有 set 没有 get),两种语法都会有共同的报错情景。

这两种方法都是操作批量属性的,如果其中某一属性合并失败,那么之前已经合并的属性会保留,不会回滚,因此,合并失败是可能产生未知的对象污染的

相关推荐
树上有只程序猿13 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187301 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758101 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周1 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队2 小时前
Vue自定义指令最佳实践教程
前端·vue.js