接上篇:[ES6]Day07—构造函数与原型、继承
对象(object)是 JavaScript 最重要的数据结构。ES6 对object数据结构进行了重大升级
const foo = 'bar';
const obj = {foo}; // {foo: "bar"}
// 等同于
const obj = {foo: foo}; // {foo: "bar"}
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
1.定义对象
let birth = '2000/01/01';
const Person = {
name: '张三',
birth, //等同于birth: birth
hello() { console.log('我的名字是', this.name); } // 等同于hello: function ()...
};
2.导出变量
let ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
JavaScript 定义对象的属性,有两种方法。
obj.foo = true;
obj['a' + 'bc'] = 123;
但是,如果使用字面量(使用大括号)定义对象,在ES5中只能使用标识符定义属性。
ES6中允许字面量定义对象时,使用表达式作为对象的属性名,把表达式放在方括号[]
内。
let objKey='isOK';
let obj={
[objKey]:true,
['a'+'bc']:123
};
表达式还可以用于定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
1、属性的可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor(object,prop)
方法:获取该属性的描述对象
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略enumerable为false的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys()
:返回对象自身的所有可枚举的属性的键名。
JSON.stringify()
:只串行化对象自身的可枚举的属性。
Object.assign()
: ES6 新增,忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
其中,只有for...in
会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。
另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in
循环,而用Object.keys()
代替。
ES6 一共有 5 种方法可以遍历对象的属性。
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
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.keys(obj)
用于获取对象自身(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名,返回一个由属性名组成的数组。for...in
var fruit = {
name : '苹果',
desc : '红富士'
};
console.log('获取对象自身(不含继承的)所有可枚举属性',Object.keys(fruit));
// ["name", "desc"]
Object.defineproperty(obj,prop,describer)
定义对象中的新属性或修改原有的属性。
obj
:必需,目标对象prop
:必需,需定义或修改的属性名describer
:必需,目标属性所拥有的特性其中第三个参数 describer
为一个对象{}
var fruit = {
name : '苹果',
desc : '红富士'
};
Object.defineProperty(fruit,'desc',{
enumerable : false
});
console.log('修改属性描述符为不可枚举',Object.keys(fruit));
// 修改属性描述符为不可枚举 ["name"]
Object.isExtensible(obj)
判断一个对象是否可扩展,返回 true | false
Object.preventExtensions(obj)
阻止对象扩展,阻止后对象
var fruit = {
name : '苹果',
desc : '红富士'
};
console.log('isExtensible',Object.isExtensible(fruit)); // true
Object.preventExtensions(fruit); //{name: "苹果", desc: "红富士"}
console.log('isExtensible',Object.isExtensible(fruit)); // false
Object.defineProperty(fruit,'price',{
value : 6
});
console.log('为对象fruit添加属性',Object.keys(fruit));
Object.isSealed(obj)
判断一个对象是否被密封,返回 true | false
Object.seal()
将对象密封,对象被密封后
Object.isFrozen(obj)
判断一个对象是否被冻结,返回 true | false
Object.freeze()
将对象冻结,对象被冻结后
var fruit = {
name : '苹果',
desc : '红富士'
};
console.log('isFrozen',Object.isFrozen(fruit)); // false
Object.freeze(fruit);
console.log('isFrozen',Object.isFrozen(fruit)); // true
delete(fruit.desc);
console.log('删除属性',fruit); //删除不成功
ES5 比较两个值是否相等,只有两个运算符,可是它们都有缺点:
==
)会自动转换数据类型。===
),NaN
不等于自身,+0
等于-0
。Object.is(value1,value2)
采用ES6 提出“Same-value equality”(同值相等)算法,用来比较两个值是否严格相等,与严格比较运算符(===
)的行为基本一致。不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身。
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(target, source1, source2)
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Object.assign(target, source1, source2)
方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
const obj = {a: 1};
Object.assign(obj) === obj // true
typeof Object.assign(2) // "object"
undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。如果undefined
和null
不在首参数,就不会报错。Object.assign(undefined) // 报错
Object.assign(null) // 报错
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
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(true) // {[[PrimitiveValue]]: true}
Object(10) // {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
Object.assign()
拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。 Object.assign({b: 'c'},
Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
})
)
// { b: 'c' }
上面代码中,Object.assign
要拷贝的对象只有一个不可枚举属性invisible
,这个属性并没有被拷贝进去。
Object.assign
拷贝。Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
Object.assign()
的常见用途
//为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
//为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
//克隆对象
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
//合并多个对象
const merge = (...sources) => Object.assign({}, ...sources);
//为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
Object.getOwnPropertyDescriptor()
:会返回某个对象属性的(descriptor)描述对象(ES5)。
Object.getOwnPropertyDescriptors()
:返回指定对象所有自身属性(非继承属性)的描述对象。(ES2017)
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.getOwnPropertyDescriptors()
方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
Object.getOwnPropertyDescriptors()
方法配合Object.create()
方法,将对象属性克隆到一个新对象,实现浅拷贝。
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj), //获取Object的原型对象
Object.getOwnPropertyDescriptors(obj) //返回 obj 所有自身属性(非继承)的描述对象
);
JavaScript 语言的对象继承是通过原型链实现的。
__proto__
属性(前后各两个下划线):用来读取或设置当前对象的原型对象(prototype)
Object.setPrototypeOf()
(写操作):用来设置一个对象的原型对象(prototype),返回参数对象本身。
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
该方法等同于下面的函数。
function setPrototypeOf(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
Object.getPrototypeOf()
(读操作):用于读取一个对象的原型对象
Object.getPrototypeOf(obj);
function Rectangle() {
// ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
如果参数不是对象,会被自动转为对象。
// 等同于 Object.getPrototypeOf(Number(1))
Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
// 等同于 Object.getPrototypeOf(String('foo'))
Object.getPrototypeOf('foo') // String {length: 0, [[PrimitiveValue]]: ""}
// 等同于 Object.getPrototypeOf(Boolean(true))
Object.getPrototypeOf(true) // Boolean {[[PrimitiveValue]]: false}
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
如果参数是undefined或null,它们无法转为对象,所以会报错。
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object
Object.create()
(生成操作):用于创建一个新对象,使用现有的目标对象来提供新创建的对象的__proto__
,返回一个新对象,带着指定的原型对象和属性。
const person = {
isHuman: false,
printIntroduction: function() {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
例子:Object.create()
Object.keys(obj)
:返回一个成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组
Object.values(obj)
:返回一个成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值的数组
Object.entries(obj)
:返回一个成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
Object.entries方法的另一个用处是,将对象转为真正的Map结构。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
自己实现Object.entries方法,非常简单。
// Generator函数的版本
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
// 非Generator函数的版本
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]]);
}
return arr;
}
Object.fromEntries()
:Object.entries()
的逆操作,用于将一个键值对数组
转为对象
。
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。
// 例一
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }
// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }
该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
参考链接:
阮一峰:ECMAScript 6 入门