1.属性的简洁表示法
ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
const foo = 'bar';const baz = {foo};baz //{foo:"bar"}//等同于const baz = {foo: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}
除了属性简写,方法也可以简写。
const o = {method(){return "Hello";}};//等同于const o = {method:function(){return "Hello!";}};
CommonJS模块输出一组变量,就非常适合使用简洁写法。
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};
2.属性名表达式
JavaScript 定义对象的属性有两种方法:
obj.foo = true;obj['a' + 'bc'] = 123;
但是,,如果使用字面量方式定义对象(使用大括号),在ES5中只能使用方法一(标识符)定义属性。
var obj = {foo:true,abc:123};
ES6:
let propKey = 'foo';let obj = {[propKey]:true,['a'+'bc']:123};//obj.abc//123let lastWord = 'last word';const a = {'first word':'hello',[lastWord] :'world'};a['first word'] //"hello"a[lastWord] //"world"a['lastWord'] //"world"
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]。
**
const keyA = {a:1};const keyB = {b:2};const myObject = {[keyA]:'valueA',[keyB]:'valueB'};myObject //{[object Object]: "valueB"}[object Object]: "valueB"__proto__: Object
3.方法的name属性
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
const obj = {get foo() {},set foo(x){}};obj.foo.name //Uncaught TypeError: Cannot read property 'name' of undefinedconst 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.属性的可枚举性和遍历
可枚举性
对象的每一个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
let obj = {foo:123};Object.getOwenPropertyDescriptor(obj,'foo')//{value: 123, writable: true, enumerable: true, configurable: true}
描述对象的enumerable属性,称为可枚举性,如果该属性为false,就表示某些操作会忽略当前属性:
- for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。JSON.stringify():只串行化对象自身的可枚举的属性。Object.assign():忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
这四个属性中,前三个是ES5就有的,最后一个Object.assign()是ES6新增的。其中,只有for..in会返回集继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。
ES6规定,所有Class的原型的方法都是不可枚举的。
操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以尽量不要用for...in循环,而用Object.keys()代替。
**
属性的遍历
- 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.ownKey()**返回一个数组,包含对象自身的所有键名,不管键名是Symbol或字符串,也不管是否可枚举。
以上5中方法遍历对象的键名,都遵守同样的属性遍历的次序规则:
- 首先遍历所有数值键,按照数值升、升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有Symbol键,按照加入时间升序排列。
5.super关键字
super关键字指向当前对象的原型对象。
const proto = {foo:'hello'};const obj = {foo:'world',find(){return super.foo;}};Object.setPrototypeOf(boj,proto);obj.find() //"hello"
上面代码中,对象obj.find()方法之中,通过super.foo引用了原型对象proto的foo属性。
注意,super关键字表示原型对象时,只能在对象的方法之中,用在其他地方都会报错,例如:
**
const obj = {foo:super.foo}const obj = {foo:()=>super.foo}const obj = {foo:function(){return super.foo}}
上面三种用法都会报错,因为对于JavaScript引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让JavaScript引擎确认,定义的是对象的方法。
JavaScript引擎内部,super.foo等同于Object.getPrototypeOf(this).foo属性或Object.getPrototypeOf(this).foo.call(this)(方法)。
const proto = {x:'hello',foo(){console.log(this.x);},};const obj = {x:'world',foo(){super.foo();}}Object.setPrototypeOf(obj,proto);obj.foo() //"world"
上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this还是当前对象obj,因此输出的就是world。
6.对象的扩展运算符
解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有的可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let {x,y,...z} = {x:1,y:2,a:3,b:4};x // 1y //2z //{a:3,b:4}
由于解构赋值要求等号右边是一个对象,所以如果等号右边是Undefined或null,就会报错,因为它们无法转为对象。
let {...z} = null;let {...z} = undefined;
解构赋值必须是最后一个参数,否则会报错。
注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数),那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = {a:{b:1}};let {...x} = obj;obj.a.b = 2;x.a.b //2
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性,
let o1 = {a:1};let o2 = {b:2};o2.__proto__ = o1;let {...o3} = o2;o3 //{b:2}o3.a //undefined
o3只复制了o2自身的属性,没有复制它的原型对象o1的属性。
const o = Object.create({x:1,y:2});o.z=3;let {x,...newObj } = o;let {y,z} = newObj;x //1y //definedz //3
上面代码中,变量x是单纯的解构赋值,所以可以读取对象o继承的属性;变量y和z是扩展运算符的解构赋值,所以只能读取对象o自身的属性,所以变量z可以赋值成功。
ES6规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,所以上面代码引入了中间变量newObj,如果写成下面这样会报错:
let {x,...{y,z}} = o;//Uncaught SyntaxError: `...` must be followed by an identifier in declaration contexts
解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。
function baseFunction({a,b}){//...}function wrapperFunction({x,y,...restConfig}){//使用了x和y参数进行操作//其余参数传给原始函数return baseFunction(restConfig);}
扩展运算符
对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z ={a:3,b:4};let n = { ...z};n //{a:3,b:4}
由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = {...['a','b','c']};foo//{0: "a", 1: "b", 2: "c"}
如果扩展运算符后面是一个空对象,则没有任何效果。
如果扩展运算符后面不是个对象,则会自动将其转为对象。
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
{...'hello'}//0: "h"//1: "e"//2: "l"//3: "l"//4: "o"
对象的扩展运算符等同于使用Object.assign()方法。
let aClone = {...a};// =let aClone = Object.assign({},a);
上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法
const clone1 = {__proto__ :Object.getPrototypeOf(obj),...obj};const clone2 = Object.assign(Object.create(Object.getPrototypeOf(obj)),obj};const clone3 = Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj))
上面的写法一的__proto__属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。
扩展运算符可以用于合并两个对象。
let ab = {...a,...b};//=let ab = Object.assign({},a,b);
