对象的扩展

本文介绍了ES6中对象的一些新特性,包括属性的简洁表示方式、属性名表达式、方法的name属性、Object.is()和Object.assign()方法的使用、属性的可枚举性、属性遍历方法、_proto_属性及Object.setPrototypeOf()方法、Object.keys()等方法、扩展运算符的应用以及Object.getOwnPropertyDescriptors()方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章编写参考 阮一峰《ECMAScript 6 入门》


1.属性的简介表示方式

ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

var name = "Blue";
var Person = {
    name
}
//等同于
var Person = {
    name: name
}

上面代码表明,ES6允许在对象中,直接写入变量。这时【属性名为变量名,属性值为变量值】

let fun = (x, y) => ({ x, y })

//等同于

var fun = function fun(x, y) {
  return { x: x, y: y };
};
fun(1,2);
//{ x: 1, y: 2 }

除了属性可以简写,方法也可以简写

let obj = {
    fun() {
        callme('Blue')
    }
}

//等同于

var obj = {
    fun: function fun() {
        callme('Blue');
    }
};

结合属性和矢量的简写,我们看看下面这一个例子

let gender = '男';
let Me = {
    name: "Blue",
    gender,
    sayHi() {
        console.log('I am ', this.name);
    }
}

有了对象属性的简写,那么在函数中要返回多个值得时候又有了一种简洁的方式

let getPoint = () => {
    let x = 1;
    let y = 2;
    return { x, y }
}

2.属性名表达式

为对象定义属性有两种方式,一种是直接【用标识名作为属性名】,第二种是用【表达式作为属性名】,这时表达式放在方括号内

obj.name = "Blue";

obj['gen' + 'der'] = 'Man'

但是如果只用字面量方式定义对象(使用大括号),在ES5中只有使用标识符的形式定义属性

var obj = {
  foo: true,
  abc: 123
};

而ES6则允许字面量定义对象时,用表达式的形式作为对象的属性名,即把表达式放在方括号内

let name = 'Blue';
let obj = {
    name,
    ['gen' + 'der']: 'Man',
    ['say' + 'Hi']() {
        console.log(this.name, this['gen' + 'der']);
    }
}

【注意】属性名表达式与简洁表示法,不能同时使用,会报错。

// 报错
var foo = 'bar';
var bar = 'abc';
var baz = { [foo] };

// 正确
var foo = 'bar';
var baz = { [foo]: 'abc'};

3.方法的name属性

函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

const Person = {
    sayName() {
        console.log("Blue");
    }
}
Person.sayName.name;    //sayName

如果对象的方法使用了取值函数或者存值函数,那么name属性不是在该方法上面,而是该方法的属性的描述对象中的get和set属性上面,返回值的前面加上了get和set

const obj = {
    get foo() {
        return 'Blue';
    },
    set foo(name1) {
        this.foo = name1;
    }
}
obj.foo.name;
// TypeError: Cannot read property 'name' of undefined

const descript = Object.getOwnPropertyDescriptor(obj, 'foo');
descript.get.name;  // "get foo"
descript.set.name;  // "set foo"

有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous。

(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"

如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。

const key1 = Symbol('Blue');
const key2 = Symbol('Crazy');
const obj = {
    [key1]() { },
    [key2]() { }
}
obj[key1].name; //[Blue]
obj[key2].name; //[Crazy]

4.Object.is( )

ES5比较两个值是否相等只有两种运算符:相等运算符(==)和严格相等运算符(===)。他们都有缺点,自动转换数据类型,严格相等运算符NaN不等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

【Object.is】用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

5.Object.assign( )

5.1基本用法
Object.assign( )用于合并对象,将源对象所有的可枚举的属性,【复制】到目标对象

var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target;
//{ a: 1, b: 2, c: 3 }

Object.assign( )的第一个参数是目标对象,后面的参数全部是源对象。

【注意】根据后面对象合并到目标对象可以很明显的看出,如果目标对象和源对象有同名属性,则目标对象的属性会被源对象的属性所覆盖。

var target = { a: 1, b: 1 };

var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

【如果只有一个参数,那么Object.assign( )会直接返回该对象】

var target = { a: 1 };
Object.assign(target);
target; //{ a: 1 }

【如果该参数不是对象,则会先转换成对象,然后进行返回】

let obj = Object.assign(2);
typeof obj; //object

由于undefined和null无法转成对象,所以如果它们作为参数,就会报错

Object.assign(undefined) // 报错
Object.assign(null) // 报错

如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,【如果undefined和null不在首参数,就不会报错】。

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

其他类型的值不在首参数也不会报错,但是除了字符串会以数组的形式,拷贝入目标对象,其他值都不会产生任何效果。

var v1 = 'abc';
var v2 = true;
var v3 = 10;

var obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

上面代码v1是字符串,会被转换成类数组对象进行拷贝。

Object.assign( )只拷源对象的自身属性,不拷贝继承的属性,也不拷贝不可枚举的属性

Object.assign({ b: 'c' }, Object.defineProperty({}, "invisable", {
    enumerable: false,
    value: 'hello'
}))
//{ b: 'c' }

属性名为 Symbol 值的属性,也会被Object.assign拷贝。

 Object.assign({ a: "b" }, { [Symbol("Blue")]: 'Crazy' });
 //{a: "b", Symbol(Blue): "Crazy"}

【注意】Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

5.2 常见用途

5.2.1 为对象添加属性

class Person {
    constructor(name, age) {
        Object.assign(this, { name, age });
    }
}

上面代码中通过Object.assign( )向Person类对象添加了name和age两个属性。

5.2.2 为对象添加方法

class Person {
    constructor(name, age) {
        Object.assign(this, {
            sayHi() {

            },
            sayHello() {

            }
        })
    }
}

与添加属性类似,上面代码将sayHi和sayHello两个方法添加到Person类的实例中。

5.2.3 克隆对象

let cloneObj = origion => Object.assign({}, origion);
cloneObj({ a: 'Blue', b: "Crazy" });
//{ a: 'Blue', b: 'Crazy' }

上面代码将一个对象拷贝到一个空对象,实现了对象的拷贝。但是这样的克隆只能克隆源对象的自身的可枚举属性,不能克隆继承属性。

5.2.4 合并多个对象

const merge =
  (target, ...sources) => Object.assign(target, ...sources);

利用扩展运算符和函数参数的结合很容易写出上面代码中的合并对象代码。

5.2.5 为属性指定默认值

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

6.属性的可枚举性

对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象

let obj = { foo: "123" };
Object.getOwnPropertyDescriptor(obj, 'foo');
/*
    { value: '123',
  writable: true,
  enumerable: true,
  configurable: true }
*/

描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性。writable属性称为“可写性”,configurable称为可配置性。

ES5 有三个操作会忽略enumerable为false的属性。

  • for…in循环:只遍历对象自身的和继承的可枚举的属性
  • Object.keys():返回对象自身的所有可枚举的属性的键名
  • JSON.stringify():只串行化对象自身的可枚举的属性

ES6 新增了一个操作Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

【ES6规定,所有Class的原型的方法都是不可枚举的】

总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,【尽量不要用for…in循环,而用Object.keys()代替】。


7.属性的遍历

ES6一共提供了5种方法可以遍历对象的属性。

7.1 for…in
for…in遍历对象自身和继承的可枚举的属性(不含Symbol属性)。

7.2 Object.keys(obj)
Object.keys(obj)返回一个数组,包含自生所有的可枚举的属性(不含Symbol属性)。

7.3 Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames(obj)返回一个数组,包含自身所有的属性(不包含Symbol属性,但是包括不可枚举属性)

7.4 Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols(obj)返回一个数组,包含对象自身的所有 Symbol 属性。

7.5 Reflect.ownKeys(obj)
Reflect.ownKeys(obj)返回对象的所有属性,不管是否是Symbol属性还是是否可枚举。


8._proto_属性,Object.setPrototypeOf(),Object.getPrototypeOf()

8.1 _proto_属性
_proto_属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。

// es6的写法
var obj = {
  method: function() { ... }
};
obj.__proto__ = someOtherObj;

// es5的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

8.2 Object.setPrototypeOf( )
Object.setPrototypeOf方法的作用与_proto_相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

// 格式
Object.setPrototypeOf(object, prototype)

// 用法
var o = Object.setPrototypeOf({}, null);

该方法如同下面的函数

function (obj,proto){
    obj.__proto__ = proto;
    return obj;
}

8.3 Object.getPrototypeOf( )

该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

Object.getPrototypeOf(obj);

下面是一个配套使用的例子

function Person() { };
var p = new Person();
Object.getPrototypeOf(p) === Person.prototype;
//true

Object.setPrototypeOf(p, Object.prototype);
Object.getPrototypeOf(p) === Person.prototype;
//false

Object.getPrototypeOf( )和Object.setPrototypeOf( )要求的参数都是对象,如果参数不是对象会被默认转换成object,如果转换失败则报错。


9.Object.keys(),Object.values(),Object.entries()

在数组的扩展中,数组实例拥有这个三个方法,其实在对象上功能类似。

9.1 Object.keys( )

Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

9.2 Object.values( )

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

var obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

9.3 Object.entries( )

Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

var obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

10.对象的扩展运算符

数组拥有扩展运算符,其实对象也拥有。

10.1 解构赋值

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

扩展运算符用于对象的解构赋值时,所有未匹配的属性都会复制到一个对象中。

跟数组一样,对象的解构赋值也必须是最后一个参数,否则是会报错的。

let { ...x, y, z } = obj; // 错误
let { x, ...y, ...z } = obj; // 错误

【注意】解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个【值的引用】,而不是这个值的副本。

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

解构赋值不会拷贝继承自原型对象的属性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined

10.2 扩展运算符

扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

上面的行为和Object.assign( )有些相似了。

let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。

// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

上面代码中,写法一的_proto_属性在非浏览器的环境不一定部署,因此推荐使用写法二。

【扩展运算符用于合并两个对象】

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。

const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};

【如果扩展运算符后面是一个空对象,则没有任何效果。】

{...{}, a: 1}
// { a: 1 }

【如果扩展运算符的参数是null或undefined,这两个值会被忽略,不会报错。】

let emptyObject = { ...null, ...undefined }; // 不报错

扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。

//'并不会抛出错误,因为 x 属性只是被定义,但没执行
let aWithXGetter = {
  ...a,
  get x() {
    throws new Error('not thrown yet');
  }
};

// 会抛出错误,因为 x 属性被执行了
let runtimeError = {
  ...a,
  ...{
    get x() {
      throws new Error('thrown now');
    }
  }
};

11.Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

上面代码中,Object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值