对象扩展

属性相关

简洁表示

  • 可直接写入变量和函数,作为属性和方法
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
// 属性简写
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}

// 方法简写,注意,只有方法的简写方式才会被js引擎确认对定义的是对象的方法
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
// 以下两种方式均为定义一个方法然后赋给foo属性
const obj = {
foo:() => super.foo
}
const obj = {
foo:function(){
return super.foo
}
}
  • 属性表达式(ES6新增属性定义方式),不仅可以定义的时候用,还可以用来访问属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let proKey = 'foo';

    let obj = {
    [proKey]: true, // foo
    ['a'+'b']: 123 // ab
    };
    console.log(obj) // {foo:true,ab:123}
    console.log(obj[proKey]) // true 注意这种访问元素方式
    console.log(obj['a'+'b']) // 123

    // 注意:
    const keyA = {a:1}

    const myObject = {
    [keyA]:'valueA',
    }
    console.log(myObject) // {'[object Object]':'valueA'}
    /*
    如果属性表达式是一个对象,则会自动转为字符串[object Object]
    */

name

类似于函数方法,对象的属性方法也会返回函数名,但是存在以下特殊情况。

  • 如果对象的方法使用了取值和存值函数,name属性不是在该方法上,而是该方法的属性描述对象的get和set属性,返回值是方法名前加上get和set。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const obj = {
    get foo() {},
    set foo(x) {}
    };

    obj.foo.name
    // TypeError: Cannot read property 'name' of undefined

    const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

    descriptor.get.name // "get foo"
    descriptor.set.name // "set foo"
  • bind方法创造的函数,name属性返回bound加上原函数的名字;

  • Function构造函数创造的函数,name属性返回anonymous

1
2
3
4
5
6
(new Function()).name // "anonymous"

var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
  • 如果对象的方法是一个Symbol值,那么name属性返回的是这个Symbol值的描述
    1
    2
    3
    4
    5
    6
    7
    8
    const key1 = Symbol('description');
    const key2 = Symbol();
    let obj = {
    [key1]() {},
    [key2]() {},
    };
    obj[key1].name // "[description]"
    obj[key2].name // ""

枚举加遍历

可枚举性

1
2
3
4
5
6
7
8
9
10
11
对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
/*
{
value: 123,
writable: true,
enumerable: true,还有相关,暂不作细究
configurable: true
}
*/

遍历

  • for..in,循环遍历对象自身的和继承的可枚举属性(不包含Symbol)

  • Object.keys(obj),返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键名

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

  • Object.getOwnProppertySymbols(obj)返回一个数组,包含对象自身的所有Symbol键名

  • Reflect.ownKeys(obj)返回一个数组,包含对象自身的所有键名,不管键名是Symbol或字符串,也不管是否可枚举

  • 遍历规则如下

    • 首先遍历所有数值键,按照数值升序排列。
    • 其次遍历所有字符串键,按照加入时间升序排列。
    • 最后遍历所有 Symbol 键,按照加入时间升序排列。
      1
      2
      Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
      // ['2', '10', 'b', 'a', Symbol()]

super

指向当前对象的原型对象!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
super.foo == Object.getPrototypeOf(this).foo(属性) == Object.getPrototypeOf(this).foo.call(this)(方法)
*/
const proto = {
x:'hello',
foo(){
console.log(this.x)
}
}

const obj = {
x:'world',
foo(){
super.foo();
}
}

Object.setPrototypeOf(obj,proto);

obj.foo() // world
/*
解释:obj继承proto,所以super.foo为父对象proto.foo,而这个函数普通函数,this绑定的是当前执行的对象,所以为obj,x则为world
*/

扩展运算符

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

  • 类似于数组,准确的说,扩展运算符运用于对象,然后数组是特殊的对象,所以采用扩展运算符。

  • 用法与数组一样

  • 如果…后面不是对象,则会自动将其转化为对象。但是由于有的对象没有自身属性,返回一个空对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 等同于 {...Object(1)},会自动包装为Number{1}
    {...1} // {}

    // 等同于 {...Object(true)}
    {...true} // {}

    // 等同于 {...Object(undefined)}
    {...undefined} // {}

    // 等同于 {...Object(null)}
    {...null} // {}
  • ...相当于Object.assign()方法

对象的新增方法

Object.is()

比较两个值是否严格相等,按照===规则基本一致。

1
2
3
4
5
6
7
8
9
10
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
!!!重点与===不相等的是:
+0 === -0 //true
NaN === NaN // false

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

Object.assign()

注意点:

  • 由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。(这是作为首参的情况)

    1
    2
    Object.assign(undefined) // 报错
    Object.assign(null) // 报错
  • 当作为二参的情况下,因为是二参,不能转化为对象的则跳过。

    1
    2
    3
    let obj = {a: 1};
    Object.assign(obj, undefined) === obj // true
    Object.assign(obj, null) === obj // true
  • 其他类型的值(数值、字符串和布尔值)不在首参数,也不会报错。除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

    1
    2
    3
    4
    5
    const v1 = 'abc';
    const v2 = true;
    const v3 = 10;
    const obj = Object.assign({}, v1, v2, v3);
    console.log(obj); // { "0": "a", "1": "b", "2": "c" }
  • 注意拷贝的属性有限制,只拷贝源对象的自身属性,也不拷贝不可枚举的属性

  • 该方法发生的是浅拷贝,不过一些函数库提供其定制版本,可以得到浅拷贝的合并。

  • 遇同名属性则替换

  • 数组的处理

    1
    2
    3
    Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]
    Object.assign把数组视为属性名为 012 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1
  • 取值函数的处理

    1
    2
    3
    4
    5
    6
    7
    const source = {
    get foo() {return 1}
    };
    const target = {};
    Object.assign(target,source)
    // {foo:1}
    // source对象的foo属性是一个取值函数,Object.assign不会赋值该函数,而是拿到值以后,将该值赋值过去。

Object.create

create是创建的意思,而new Object也是创建的意思,之间的关联如下:

1
2
3
4
5
6
7
8
function Car(){
this.color = "red";
}
Car.prototype.sayHi = function(){
console.log('你好')
}
var car = new Car()
var car2 = Object.create(Car);

new Object()?

1
2
3
4
5
6
7
8
// 一、创建了一个空对象
var obj = {};
// 二、将空对象的__proto__成员指向了Car函数的原型属性,该原型属性是一个原型对象,也就意味着obj的__proto__上拥有了Car.prototype中的属性和方法。
obj.__proto__ = Car.prototype
// 三、将Car函数中的this指针指向obj,obj有了Car构造函数中的属性或方法,然后Car函数无返回值或返回的不是对象,直接返回Car函数中的对象。
Car.call(obj)

// 总结:当创建一个新的对象的的时候,new会自动将后面Object的prototype赋值给obj.__proto__!!即使创建的是一个空对象:new Object(),也会自动绑定Object的prototype原型对象

Object.create?

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
// 一、语法
Object.create(proto(原型对象),[propertiesObject](属性的配置))
// 二、其内部过程如下:
Object.create = function(o){
var F = function (){};
F.prototype = o;
return new F();
}
/*
流程如下:Object.create内部创建了一个新对象,假设为newObj,默认情况下newObj.__proto__ == F.prototype == o,以为o是参数,二参数是原型对象。
更多举例如下:
*/
function Animal(name) {
this.name = name;
}
var animal = new Animal("test")
// Animal.prototype.name = "ksksk"

var createAnimal1 = Object.create(Animal.prototype)
var createAnimal2 = Object.create(animal)
var createAnimal3 = Object.create(null)

console.log(createAnimal1.name) // undefined,如果加上上面注释的那个,则会输出ksksk
console.log(createAnimal2.name) // test
console.log(createAnimal3.name) // undefined

var obj = Object.create({},{
"a":{value:1,configurable:false,enumerable:true,writable:true},
"b":{value:2,configurable:false,enumerable:true,writable:true},
"c":{value:3,configurable:false,enumerable:true,writable:true},
})
console.log(obj.a) // 1
console.log(obj.b) // 2
console.log(obj.c) // 3
// 第二个参数是一个对象的形式配置的,key是属性的名称、value是一个对象,而该对象是对属性的描述
  • value:当前属性的属性值
  • configurable:能否使用delete、能否需改属性特性、或能否修改访问器属性,false为不可重新定义,默认值为true
  • enumerable:可枚举型
  • writable:对象属性是否可修改,false为不可修改,默认值为true
    设置不可修改后,可以理解为常量,不能对属性值进行修改

区别?

  • new操作符会将构造函数的prototype也就是指定的原型对象赋值给新对象的__proto__
  • Object.create将参数proto也就是指定的原型对象赋值给新对象的__proto__
  • Object.create(null)会创建空对象,但是new创建出的空对象会绑定Objectprototype原型对象,但是Object.create(null)是没有任何属性的

Object.get/setPrototypeOf()

__proto__

__proto__用来读取或设置当前对象的prototype对象。是一个内部属性,而非正式对外的API。只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为该属性是不存在的。因此,最好使用Object.setPrototypeOf()、Object.getPrototypeOf()、Object.create()代替。

Object.setPrototypeOf()

设置一个对象的prototype对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//  格式
Object.setPrototypeOf(object, prototype)
// 用法
var o = Object.setPrototypeOf({}, null);
//该方法等同于下面的函数。
function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
//下面是一个例子。
let proto = {};
let obj = { x: 10 };
// 给对象obj设置原型为proto
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40

注意点:

  • 如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以该操作不会产生任何效果。

    1
    2
    3
    Object.setPrototypeOf(1, {}) === 1 // true
    Object.setPrototypeOf('foo', {}) === 'foo' // true
    Object.setPrototypeOf(true, {}) === true // true
  • 但undefiend和null无法转为对象

    1
    2
    3
    4
    5
    Object.setPrototypeOf(undefined, {})
    // TypeError: Object.setPrototypeOf called on null or undefined

    Object.setPrototypeOf(null, {})
    // TypeError: Object.setPrototypeOf called on null or undefined

Object.getPrototypeOf()

读取一个对象的prototype对象。

1
2
3
4
5
6
7
8
9
10
11
Object.getPrototypeOf(obj);
//下面是一个例子。
function Rectangle() {
}
var rec = new Rectangle();
// rec.__proto__ == Rectangle.prototype
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

Object.getOwnPropertyDescriptors()

ES5是返回某个对象属性的描述对象,而ES6是返回指定对象所有自身属性(非继承属性)的描述对象。

实现拷贝get属性和set属性

因为Object.assign拷贝一个属性的值,而不是拷贝它背后赋值方法或取值方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
const source = {
set foo(value) {
console.log(value);
}
};

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }

浅拷贝对象(配合Object.create)

1
2
3
4
5
6
7
8
9
10
// 拷贝其原型对象和该对象属性的描述对象(descriptor)
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));

// 或者

const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);

实现继承

因为ES6尽量少用__proto__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 前后对比
// ES5
const obj = {
__proto__: prot,
foo: 123,
};

//
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
})
);

Object.keys()

Object.values()

Object.entries()

Object.fromEntries()