对象的拓展

对象的拓展

对象属性的简洁表示法

//几个例子
function f(x,y){
    return {x,y}
}
f(1,2)//Objext {x:1,y:2}

module.exports = { getItem, setItem, clear };

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

set wheels(value) {
    if (value < this._wheels) {
        throw new Error('数值太小了!');
    }
    this._wheels = value;
    }
}
cart.wheels//4
set.wheels = 1//Error'数值太小了!'

属性名表达式

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

上面代码中,[keyA]和[keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而 myObject 最后只有一个[object Object]属性。

方法的 name 属性

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

const person = {
sayName() {
  console.log('hello!');
    }
  };
person.sayName.name   // "sayName"

如果对象的方法使用了取值函数和存值函数,则 name 属性不是在改方法上面,而是在该方法的描述对象的 get set 属性上面

const  obj = {
    get foo(){},
    set foo(x){}
}
const descriptor = Object.getOwnPropertyDescriptor(obj,"foo");
//getOwnPropertyDescriptor方法返回指定对象上一个自有属性对应的属性描述符。
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"

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

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

Object.is()

ES5 用 == 和=== 两个运算符比较两个值是否相等,但都有缺点。

第一个会自动转换数据类型,后面的 NaN 不等于自身。以及+0 等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。

ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is 就是部署这个算法的新方法。它用来比较两个值是否严格相等,与===的不同之处只有+0 不等于-0 NaN 等于自身。

ES5 可以通过下面的代码,部署 Object.is。

  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()方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象上

其中第一个参数是目标对象,后面的参数都是源对象

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

Object.assign 拷贝的属性是有限制的,之拷贝源对象的自身属性,不拷贝继承属性,也不拷贝不可枚举的属性

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

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

Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }

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

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

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

源对象 obj1 的 a 属性是对象,目标对象拷贝的是对象的引用。对象的任何变化都会反映到目标对象上

对于嵌套的对象,一旦遇到同名属性,Object.assign 的处理方法是替换

const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }

也就是说对象的复制最多到一层

Object.assign 的常见用途

为对象添加方法
Object.assign(SomeClass.prototype,{
    someMethod(arg1,arg2){
        //...
    }
})
SomeClass.prototype.someMethod = function (arg1, arg2) {
  //···
};
克隆对象
function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}//保持继承链
合并多个对象
const merge =
(target, ...sources) => Object.assign(target, ...sources);
//or
const merge =
(...sources) => Object.assign({}, ...sources);
为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
  };

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

DEFAULTS 对象是默认值,options 是用户提供的参数,processContent 将 DEFAULTS 和 options 合并成一个新对象,如果两者有同名属性,则 options 的值将覆盖 DEFAULTS 的属性值。当然,属性都是简单类型数据,不然将有可能发生前面所说的属性替换。

属性的可枚举性 属性的遍历

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

枚举

引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉 for…in 操作

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

enumerable 属性成为可枚举性,设为 false 表示某些操作会忽略此属性。目前有 4 种。

for..in 循环只遍历对象自身和继承的可枚举的属性

Object.keys() 只遍历自身的可枚举属性键名

JSON.stringfy()只串行化自身的可枚举属性

Object.assign() 之拷贝对象自身的可枚举属性 继承的和不可枚举的都会被忽略

另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。

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

遍历

ES6 一共 5 中方法遍历对象的属性

for…in

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

Object.keys(obj)

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

Object.getOwnPropertyNames(obj)

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

Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性的键名。

Reflect.ownKeys(obj)

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

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

首先遍历所有数值键,按照数值升序排列。

其次遍历所有字符串键,按照加入时间升序排列。

最后遍历所有 Symbol 键,按照加入时间升序排列。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

上面代码中,Reflect.ownKeys 方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性 2 和 10,其次是字符串属性 b 和 a,最后是 Symbol 属性。

Object.getOwnPropetryDescriptions()

Object.getOwnPropertyDescriptors 返回指定对象的所有自身属性的描述对象

该方法的引用目的是为了解决 Object.assign()无法正确拷贝 get 属性和 set 属性的问题

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

proto

_proto__,用来读取或设置当前对象的[[prototype]]对象。

//es6
const obj = {
    method:function(){...}
};
obj.__proto__ = someOtherObj;

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

只有浏览器必须部署这个属性。其它环境不一定需要部署。

let shape = function () {};
var p = {
    a: function () {
        console.log('a');
    }
};

let circle = new shape();
circle.__proto__ = p;//原型对象被改变了
circle.a(); //  a
console.log(shape.prototype === circle.__proto__);//false

最好使用 Object.create(),Object.setPrototypeOf(),Object.getPrototypeOf()来代替

Object.setPrototypeOf()

用来设置一个对象的 prototype 对象,返回参数对象本身,是 ES6 正式推荐的设置原型对象的方法

//es6
const o = Object.setPrototypeOf({}, null);
//es5
function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

举个栗子

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
//上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
Object.getPrototypeOf()

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

function Rectangle() {
  // ...
}

const rec = new Rectangle();

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

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

super 关键字

this 关键字总是指向函数所在的当前对象,ES6 新增 super,指向当前的原型对象

const proto = {
  foo: 'hello'
};

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

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

super 关键字表示原型对象时,只能用在对象的方法之中。

const obj = {
  foo: super.foo
}

// 报错,super用在一个函数里面,赋值给foo属性
const obj = {
  foo: () => super.foo
}

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

JavaScript 引擎内部,super.foo 等同于 Object.getPrototypeOf(this).foo(属性)或 Object.getPrototypeOf(this).foo.call(this)(方法)。

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

与前面数组的拓展类似

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}