ES6学习5(对象的拓展)

属性的简洁表示法

ES6允许在对象之中,只写属性名,不写属性值。这时,属性值等于属性名所代表的变量。

var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同于
var baz = {foo: foo};


function f(x, y) {
  return {x, y};
}
// 等同于
function f(x, y) {
  return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}

var o = {
  method() {
    return "Hello!";
  }
};
// 等同于
var o = {
  method: function() {
    return "Hello!";
  }
};

属性名表达式

定义对象属性时可以使用表达式来代表属性名,甚至方法名:

let propKey = 'foo';
let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

let obj1 = {
  ['h'+'ello']() {
    return 'hi';
  }
};
obj1.hello() // hi

Object.is()

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

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

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

在ES5中可以给Object对象添加这么一个方法来解决+-0和NaN的问题:

Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // 针对+0 不等于 -0的情况
      return x !== 0 || 1 / x === 1 / y;
    }
    // 针对NaN的情况
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});

Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
第一个是目标,后面的都是源。同名属性会被覆盖。

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}

如果传入的参数不是对象,则会被先被转换为对象,undefined和null无法转成对象,如果它们是目标则会报错,是源会被跳过。
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。对于包装对象,布尔值和数字的包装对象都没有可枚举的属性,只有字符串有:

Object(true) // {[[PrimitiveValue]]: true}
Object(10)  //  {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

所以:

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

要注意的是:
这个方法执行浅拷贝,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
遇到同名的属性会被直接替换,如果是属性对象也会直接替换,不会进一步把这个对象里面的属性做添加处理。
Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。get和set这种访问器属性是不会被复制的。

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 }

应用

比如你想为某类对象添加默认值:

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

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

属性的可枚举性

我们都知道,对象的每个属性都有一个描述对象用来控制该属性的行为,其中enumerable描述这个属性是否是可枚举的。

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

在ES5中,有3个操作会考虑到enumerable:

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

在ES6中,又新增了Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
另外,ES6规定,所有Class的原型的方法都是不可枚举的。

属性的遍历

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或字符串,也不管是否可枚举。

关于遍历出的属性的顺序:

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。

proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()

这几个属性都与对象原型的设置有关。

proto属性

这个属性并不是一个对外开放的API并不建议使用,虽然使用它可以非常方便的指定和修改一个对象的原型:

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

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

Object.setPrototypeOf()

用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。

let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40

Object.getPrototypeOf()

用于读取一个对象的prototype对象。

function Rectangle() {
}

var rec = new Rectangle();

Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

Object.values(),Object.entries()

与Object.keys()相对应,在ES7中还可能加入Object.values(),Object.entries()。

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

console.log(Object.values({ [Symbol()]: 123, foo: 'abc', a:{b:'hahaha'}}));
//["abc",{b:"hahaha"}]

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

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

对象的扩展运算符

ES7有一个提案,将Rest运算符(解构赋值)/扩展运算符(…)引入对象。Babel转码器已经支持这项功能。

解构赋值

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

由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
解构赋值必须是最后一个参数,否则会报错。
注意,解构赋值的拷贝是浅拷贝。
解构赋值不会拷贝继承自原型对象的属性。

扩展运算符

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

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
var a = {q:1,r:3};
var b = {c:2,v:4};
let ab = { ...a, ...b };
console.log(ab);
// 等同于
//let ab = Object.assign({}, a, b);

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

get与set

复习一下,在对象中,get和set是属性不是方法:

var test = {
  _age:0,
  get age() {
    return this._age;
  },
  set age(value) {
    if (value > 100) 
      this._age = new Date().getFullYear() - value;
    else 
      this._age = value;
  }
};
test.age = 1994;
console.log(test.age);

Object.getOwnPropertyDescriptors()

我们知道ES5有一个Object.getOwnPropertyDescriptor方法,返回某个对象属性的描述对象(descriptor)。
ES7中有一个提案,提出了Object.getOwnPropertyDescriptors(),没错,只多一个s。返回指定对象所有自身属性(非继承属性)的描述对象。

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

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

这样的格式正好适合Object.defineProperties方法来定义对象的属性,所以如果你要拷贝带有存取描述符get和set的对象时,可以使用Object.getOwnPropertyDescriptors()方法来替代Object.assign()方法。

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

当然这还是浅拷贝。
也可以配合使用Object.create方法来复制对象。
继承对象:

var superClass = {name:'LYM'};
const child = Object.create(
  superClass,
  Object.getOwnPropertyDescriptors({
    age: 23,
  })
);
console.log(Object.getOwnPropertyDescriptors(child));
// { age:
//    { value: 23,
//      writable: true,
//      enumerable: true,
//      configurable: true } }

为啥没有name属性?这个方法不遍历父类的属性哦。
使用这个方法也可以完成对象的混入,把多个对象混到一个里,就是读取每个源对象的所有属性放到一起。

let mix = (object) => ({
  with: (...mixins) => mixins.reduce(
    (c, mixin) => Object.create(
      c, Object.getOwnPropertyDescriptors(mixin)
    ), object)
});

// multiple mixins example
let a = {a: 'a'};
let b = {b: 'b'};
let c = {c: 'c'};
let d = mix(c).with(a, b);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值