1、属性的简洁表示法

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

const foo = 'bar';
const baz = {foo};//等同于 const baz = {foo : "bar"};
baz
//属性简写
function f(x,y) {
return {x,y};
}
//等同于
function f(x, y) {
return {x: x, y: y}
}
f(1,2);//{x:1, y:2}
 //方法简写
const o = {
method() {
return "Hello!"
}
};
//等同于
const o = {
method: function(){
return "Hello!"
}
} ;

实例

 let birth = '2000/01/01';
const Person = {
name: '张三',
birth,//等同于 birth: birth
hello() {
console.log('My name is ' + this.name);
} //等同于 hello: function(){console.log('My name is' + this.name);} };
console.log(Person.birth);//2000/01/01
 Person.hello();//My name is 张三

用于函数的返回值

function getPoint() {
const x = 1;
const y = 10;
return {x,y};
};
getPoint();//{x:1, y:10}

属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。

 const cart = {
_wheels: 4,
get sheels(){
return this._wheels;
},
set wheels (value) {
if(value < this._wheels) {
throw new Error('数值太小了!');
}
this._wheels = value;
}
}

注意,简洁写法的属性名总是字符串,这会导致一些看上去比较奇怪的结果。

const obj = {
class () {}
}; // 等同于 var obj = {
'class': function() {}
};

2、属性名的表达式

JavaScript 定义对象的属性,有两种方法。

 // 方法一
obj.foo = true; // 方法二
obj['a' + 'bc'] = 123;

ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

 let lastWord = 'last word';

 const a = {
'first word': 'hello',
[lastWord]: 'world'
}; a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

表达式还可以用于定义方法名。

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

注意,属性名表达式与简洁表示法,不能同时使用,会报错。

 // 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] }; // 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};

注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[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]属性。

3、方法的name属性

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

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

上面代码中,方法的name属性返回函数名(即方法名)。

如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的getset属性上面,返回值是方法名前加上getset

 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

 (new Function()).name // "anonymous"

 var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"

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

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

4、Object.is()

Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

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

5、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方法的第一个参数是目标对象,后面的参数都是源对象。

注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

 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}

如果只有一个参数,Object.assign会直接返回该参数。

 const obj = {a: 1};
Object.assign(obj) === obj // true

如果该参数不是对象,则会先转成对象,然后返回。

 typeof Object.assign(2) // "object"

由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。

 Object.assign(undefined) // 报错
Object.assign(null) // 报错

如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefinednull不在首参数,就不会报错。

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

上面代码中,v1v2v3分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。

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

上面代码中,布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性[[PrimitiveValue]]上面,这个属性是不会被Object.assign拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。

Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。

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

上面代码中,Object.assign要拷贝的对象只有一个不可枚举属性invisible,这个属性并没有被拷贝进去。

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

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

注意点

(1)浅拷贝

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

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

上面代码中,源对象obj1a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

(2)同名属性的替换

对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

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

上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。这通常不是开发者想要的,需要特别小心。

一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。

(3)数组的处理

Object.assign可以用来处理数组,但是会把数组视为对象。

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

上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1

(4)取值函数的处理

Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

 const source = {
get foo() { return 1 }
};
const target = {}; Object.assign(target, source)
// { foo: 1 }

上面代码中,source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。

常见用途

(1)为对象添加属性

 class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}

上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

(2)为对象添加方法

 Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
}); // 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};

上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。

(3)克隆对象

 function clone(origin) {
return Object.assign({}, origin);
}

上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。

不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

 function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}

(4)合并多个对象

将多个对象合并到某个对象。

 const merge =
(target, ...sources) => Object.assign(target, ...sources);

如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。

 const merge =
(...sources) => Object.assign({}, ...sources);

(5)为属性指定默认值

 const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
}; function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}

上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTSoptions合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。

注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。

 const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
},
}; processContent({ url: {port: 8000} })
// {
// url: {port: 8000}
// }

上面代码的原意是将url.port改成 8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。

6、属性的可枚举性和遍历

可枚举性

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

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

描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略enumerablefalse的属性。

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

这四个操作之中,前三个是 ES5 就有的,最后一个Object.assign()是 ES6 新增的。其中,只有for...in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for...in操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for...in遍历到。

 Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

上面代码中,toStringlength属性的enumerable都是false,因此for...in不会遍历到这两个继承自原型的属性。

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

 Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false

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

属性的遍历

ES6 一共有 5 种方法可以遍历对象的属性。

(1)for...in

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

(2)Object.keys(obj)

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

(3)Object.getOwnPropertyNames(obj)

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

(4)Object.getOwnPropertySymbols(obj)

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

(5)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方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性210,其次是字符串属性ba,最后是 Symbol 属性。

7、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.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以实现正确拷贝。

 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.getOwnPropertyDescriptors方法的另一个用处,是配合Object.create方法,将对象属性克隆到一个新对象。这属于浅拷贝。

 const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)); // 或者 const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);

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

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

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

__proto__属性

__proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。

如果一个对象本身部署了__proto__属性,该属性的值就是对象的原型。

Object.setPrototypeOf()

方法的作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身

 // 格式
Object.setPrototypeOf(object, prototype) // 用法
const o = Object.setPrototypeOf({}, null);

例子

 let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto); proto.y = 20;
proto.z = 40; obj.x //
obj.y //
obj.z //

Object.getPrototypeOf()

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

 Object.getPrototypeOf(obj);
 

9、super关键字

指向当前对象的原型对象

 const proto = {
foo: 'hello'
}; const obj = {
foo: 'world',
find() {
return super.foo;
}
}; Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

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

Object.keys()

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

ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,作为遍历一个对象的补充手段,供for...of循环使用。

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

Object.entries

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

Object.entries方法的另一个用处是,将对象转为真正的Map结构。

const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }

11、对象的扩展运算符

解构赋值

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

解构赋值必须是最后一个参数,否则会报错。

 let { ...x, y, z } = obj; // 句法错误
let { x, ...y, ...z } = obj; // 句法错误

解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。

扩展运算符

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

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

ES6(阮一峰)对象的扩展的更多相关文章

  1. ES6(阮一峰) 数组的扩展

    1.扩展运算符 扩展运算符(spread)是三个点(...).它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列. console.log(1, ...[2, 3, 4], 5) // ...

  2. es6阮一峰读后感

    不经意间看了你一眼(阮一峰的es6读后感)我自己常用的 字符串篇:ES6 为字符串添加了遍历器接口(详见<Iterator>一章),使得字符串可以被for...of循环遍历.只要有遍历器接 ...

  3. ES6 阮一峰阅读学习

    参考: ECMAScript6入门 就是随便看看,了解一下. 一.ECMAScript6简介 1. 什么是ECMAScript6? JavaScript语言的下一代标准.2015年6月发布,正式名称是 ...

  4. ES6 学习笔记之四 对象的扩展

    ES6 为对象字面量添加了几个实用的功能,虽然这几个新功能基本上都是语法糖,但确实方便. 一.属性的简洁表示法 当定义一个对象时,允许直接写入一个变量,作为对象的属性,变量名就是属性名. 例1: , ...

  5. ES6学习笔记(8)----对象的扩展

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ 对象的扩展 1.属性名的简洁表示法 : ES6允许在代码中直接写变量,变量名是属性名,变量值是属 ...

  6. ES6中数组和对象的扩展运算符拷贝问题以及常用的深浅拷贝方法

    在ES6中新增了扩展运算符可以对数组和对象进行操作.有时候会遇到数组和对象的拷贝,可能会用到扩展运算符.那么这个扩展运算符到底是深拷贝还是浅拷贝呢? 一..使用扩展运算符拷贝 首先是下面的代码. le ...

  7. ES6(vue)对象词法扩展

    ES6 允许声明在对象字面量时使用简写语法,来初始化属性变量和函数的定义方法,并且允许在对象属性中进行计算操作: function getCar(make, model, value) { retur ...

  8. Class 学习 (Es6阮一峰)

    es5 构造函数 实例: function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () ...

  9. 《ES6标准入门》(阮一峰)--10.对象的扩展

    1.属性的简洁表示法 ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法.这样的书写更加简洁. const foo = 'bar'; const baz = {foo}; baz // ...

随机推荐

  1. Retrofit总结(原)

    1.Retrofit的作用 A type-safe HTTP client for Android and Java 看到这句话时候在想什么是类型安全? 百度一下:类型安全代码指访问被授权可以访问的内 ...

  2. Android开发中怎样用多进程、用多进程的好处、多进程的缺陷、解决方法(转)

    转自:http://blog.csdn.net/spencer_hale/article/details/54968092 1.怎样用多进程 Android多进程概念:一般情况下,一个应用程序就是一个 ...

  3. java集合类笔试选择题整理含答案

    1.ArrayList list=new ArrayList(20);中的list扩充几次()A. 0B. 1C. 2D. 3答案:A分析:已经指定了长度, 所以不扩容 2.List.Set.Map哪 ...

  4. oracle 数据库插入中文乱码

    一. 查询数据库编码 select userenv('language') from dual; 查询服务器编码 select * from v$nls_parameters; 推出sql查询系统编码 ...

  5. Win10桌面图标显示不正常变成了白色

    开机不知道什么原因,windows 10 桌面图标全部变成了白色,软件是可以点击正常打开使用,但是看着特别不爽.今天就告诉大家一种办法,解决这种问题. 解决步骤 1.在桌面右键新建 "文本文 ...

  6. ContextLoaderListener vs DispatcherServlet

    In XML based Spring MVC configuration, you must have seen two declarations in web.xml file i.e. Cont ...

  7. MySQL图形化管理工具之Navicat安装以及激活

    软件以及激活包下载地址 1. 安装navicat 双击navicat_trial_11.1.20.0.1449226634.exe,一路下一步安装(记住安装目录) 2. 激活 双击PatchNavic ...

  8. BZOJ 1778: [Usaco2010 Hol]Dotp 驱逐猪猡(高斯消元+期望dp)

    传送门 解题思路 设\(f(x)\)表示到\(x\)这个点的期望次数,那么转移方程为\(f(x)=\sum\frac{f(u)*(1 - \frac{p}{q})}{deg(u)}\),其中\(u\) ...

  9. Docker Machine 管理-创建machine(16)

    对于 Docker Machine 来说,术语 Machine 就是运行 docker daemon 的主机.“创建 Machine” 指的就是在 host 上安装和部署 docker.先执行 doc ...

  10. angularjs $index用来取顺序

    $index用来取顺序 null