ES6学习笔记8 对象的扩展

属性的简洁表示法

ES6允许直接写入变量和函数作为对象的属性和方法

var foo = 'bar';
var obj = {foo};
obj   //{foo:"bar"}
var o = {
    method() {
        return "hello!";
    }
};

//等同于

var o = {
    method: function() {
        return "hello!";
    }
};

这种写法用于函数的返回值将会非常方便

function getPoint(){
    var x = 1;
    var y = 2;
    return {x,y};
}

getPoint();  //{x:1,y:2}

属性的赋值器(setter)和取值器(getter)也可以采用这种写法

var cart = {
    _wheels: 4,
    get wheels () {
        return this._wheels;
    },
    set wheels (value) {
        this._wheels = value; 
    }
}

如果某个方法的值是一个Generator函数,则其前面需要加上星号

const obj = {
  * m() {
    yield 'hello world';
  }
};

属性名表达式

ES6允许以字面量形式定义变量时,用表达式作为对象属性名或方法名,即把表达式放在方括号里

let propkey = "foo";

let obj = {
    [propkey] : true,

    ['a' + 'bc'] : 123,

    ['h' + 'ello'] () {
        return 'hi';
    }
};

但属性名表达式和简洁表示法不能同时使用,否则会报错

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

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

方法的name属性

方法的name属性返回方法名

const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"

若对象的方法使用取值函数(getter)和存值函数(setter),则name属性不在该方法上,而是在该方法属性描述符对象(descriptor)的getset属性上,,返回值是方法名前加上get或set

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

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

const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

Object.is()

该方法用于比较两个值是否是相等的,用来弥补===运算符对于两个NaN不等,以及+0-0相等的情况

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()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

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

如果只有一个参数,Object.assign会直接返回,如果该参数不是对象,则会自动转换为对象。若参数是nullundefined,且其为第一个参数,则会报错。

其他类型的值(数值、字符串、布尔值)不在首参数也不会报错,但是除了字符串会以数组形式复制到目标对象外,其他值都不会产生效果。

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" }

上述只有字符串才被复制的原因在于,只有字符串包装对象具有可枚举属性,而Object.assign方法只复制源对象自身的可枚举属性(不复制继承属性和不可枚举属性),属性名为Symbol值的属性也会被复制

Object.assign方法复制的对象属性是浅复制,若源对象属性是对象,则目标对象得到的是这个对象的引用

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

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

Object.assign也可以用来处理数组,但是会把数组视为对象,即索引为属性名

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

Object.assign的用途很多,可以为对象添加属性和方法、克隆对象、合并对象、为属性指定默认值

//为对象添加属性和方法

var obj = {};
var x = 1,
    y = 2;

Object.assign(obj, {x,y}, { someMethod () {return 'abc';} });
//上述利用了简洁表示法,为obj对象添加了x,y属性和someMethod方法
//克隆对象
var objProto = Object.getPrototypeOf(obj);
var clone = Object.assign(Object.create(objProto),obj);
//上述先获取对象原型,再复制属性,保证了原对象的继承属性也被复制
//合并对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
//为对象指定默认属性
var Default = {
    abc: 0,
    text: 'html'
};

function(options){
    options = Object.assign({}, Default, options);
    //....
}
//上述代码将默认属性放置在Default对象中,用户输入的属性在options对象中
//如果两者有同名属性,则options中属性会覆盖Default中的属性

属性的可枚举性和遍历

若某个属性的描述符对象中enumerable属性为false,则表示该属性不可被枚举。(关于描述符对象https://blog.csdn.net/zjw_python/article/details/79526443)。

目前有4个操作会忽略enumerablefalse的属性

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

另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。因此尽量不要使用for...in循环,而用Object.keys()代替

ES6一共有5种方法可以遍历对象的属性:
(1)for...in
遍历对象自身和继承的所有可枚举属性(不包含Symbol属性)
(2)Object.keys(obj)
遍历对象自身所有可枚举属性的键名(不包含Symbol属性)
(3)Object.getOwnPropertyNames(obj)
返回一个数组,包含对象自身的所有属性的键名(不包含Symbol属性)
(4)Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身所有Symbol属性的键名
(5)Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有属性,不管是否可枚举或者是Symbol属性


Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptor方法会返回某个对象属性的描述对象(descriptor)。ES2017 引入了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: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。这是因为Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法

const source = {
  set foo(value) {
    console.log(value);
  }
};

const target1 = {};
Object.assign(target1, source);

Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
//   writable: true,
//   enumerable: true,
//   configurable: true }

这时,Object.getOwnPropertyDescriptors方法配合Object.defineProperties,就可以实现正确拷贝了

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

除此之外,还可以配合Object.create方法,克隆对象属性到一个新对象

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

另外,Object.getOwnPropertyDescriptors方法可以实现一个对象继承另一个对象

//以前的写法
const obj = {
  __proto__: prot,
  foo: 123,
};

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

__proto__属性、Object.getPrototypeOf()、Object.setPrototypeOf()

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

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

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

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

//格式
Object.setPrototypeOf(object, prototype);
//示例
const obj = Object.setPrototypeOf({}, null);

第一个参数若不是对象,则自动会转换为对象。若第一个参数为nullundefined,就会报错

Object.getPrototypeOf()用于获取对象的原型

Object.getPrototypeOf(obj);

super关键字

this关键字总是指向函数所在的当前对象,ES6又新增了另一个类似的关键字super,指向当前对象的原型对象

const proto = {
  foo: 'hello'
};

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

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

值得注意的是,super关键字表示原型对象时,只能用在对象的方法之中,且必须使用对象方法的简写形式才可以让JavaScript 引擎确认,定义的是对象的方法,否则会报错。

// 报错
const obj = {
  foo: super.foo
}

// 报错
const obj = {
  foo: () => super.foo
}

// 报错
const obj = {
  foo: function () {
    return super.foo
  }
}

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

Object.keys()返回一个数组,包含对象自身所有可枚举属性的键名。Object.values()则是返回对象自身所有可遍历对象的键值。类似的Object.entries()返回键值对。

var obj = {foo:'bar', num: 42};

Object.keys(obj);    //["foo" , "num"];
Object.values(obj);   //["bar", 42];
Object.entries(obj);  //[ ["foo","bar"], ["num", 42] ]

对象的扩展运算符

对象的扩展运算符用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。

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

在解构赋值中,扩展运算符必须是最后一个参数,否则会报错。解构赋值的拷贝是浅拷贝,若属性值是一个对象,而只是拷贝的这个对象的引用。

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

扩展运算符可用于合并两个对象,与Object.assign方法类似,后面的属性会覆盖前面的属性

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

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

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

Null传导运算符

Null传导运算符简化了读取对象内部某个属性时,对该对象是否存在的判断。例如对message.body.user.firstName读取,可使用

const firstName = message?.body?.user?.firstName || 'default';

上述代码中,只要有一个?.运算符返回nullundefined,则就不载向下运算,而是返回undefined

该运算符有4种用法

  • obj?.prop:读取对象属性
  • obj?.[expr]:读取对象属性
  • func?.(...args):函数或方法的调用
  • new C?.(...args):构造函数的调用

猜你喜欢

转载自blog.csdn.net/zjw_python/article/details/80923968