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),两种语法都会有共同的报错情景。

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

相关推荐
王解32 分钟前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录32 分钟前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁36 分钟前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂41 分钟前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑2136 小时前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程