属性的简写

es6允许对属性进行简写,可以直接使用变量,变量名直接成为了属性名。

var a = "hello";
var b = {a};
//等同于
var b = {
  a:a
}

属性里的方法也可以简写

var a = {
  b(){...}
}
//等同于
var a = {
  b: function(){
     ...
  }
}

在CommonJS模块输出变量时,也就是node模块导出时,这种简写就显得十分方便,我们直接导致一个对象,对象里面使用简写。

var a = {};
var b = {};

module.exports = {a,b}

而属性的赋值器,也采用了这种写法

var a = {
  b: "hello",
  get b() {
    return this.b;
  },
  set b(value) {
    this.b = value;
  }
}

如果是一个Generator函数,简写的时候需要在前面加上*符号

var a = {
  *b() {
    yield "hello";  
  }
}

属性名表达式

es6支持属性名可以使用表达式生成,原来都是固定了,必须为string,现在还可以使用计算生成

var a = {
  ["b"+"c"]: true,
};

a.bc; //true

通过将计算的代码放入方括号中,便可以使用表达式了,为此,我们甚至可以使用变量。

var bc = "d";
var a = {
  [bc]: true,
};

a.d; //true

使用这种方式,我们还可以自定义方法名。

var a = {
  ["h"+"ello"](){
    return "hello";
  },
};

a.hello(); //"hello"

但是需要注意:属性名表达式不能和简写形式同时使用,否则会报错。

var a = "b";
var b = "d";
var c = {[a]};//报错

//正确写法
var c = {
  [a]:b
};
c.b; //"d"

如果表达式最后返回的是一个object对象,它其实会被转为字符串[object Object],所以需要避免这种情况。

var a = {
  name:"a"
};
var b = {
  name:"b"
};
var c = {
  [a]:a,
  [b]:b,
}

console.log(c); //Object { "[object Object]": {…} }

你会发现只有一个[object Object],原因是相同的key值,后面的会覆盖前面的。

方法的name属性

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

var a = {
  b(){}
};

a.b.name; //"b"

Object.is()

在es5中,判断两个值是否相等,只有两种方式:=====,但是这两种方式都有缺点,前者他会自动转换数据类型,后者两个NaN判断为不相等,+0和-0却又相等。

为了更明确的判断,es6增加了这个方法,他的行为与===全等运算符相同,但是可以正确判断NaN和+0-0。

Object.is("a","a");  //true

Object.is({},{});  //false

Object.is(NaN,NaN);  //true

Object.is(+0,-0);  //false

Object.assign()

Object.assign用于将源对象所有可枚举的属性复制到目标对象,所以他是一个浅克隆,如果源对象的属性是一个引用类型,复制的其实是这个对象的引用。

如果源对象的引用类型发生改变,复制的值也会发生改变。

Object.assign方法可以有多个源对象,所以,如果有相同的属性,后面的会替换前面的。

var a = {name:"a"};
var b = {name:"b",age:20};

Object.assign(a,b); 
//{name:"b",age:20}

Object.assign的第一个参数,是目标对象,后面的参数为源对象,源对象可以有多个,通过逗号隔开。

如果只有一个参数,也就是目标对象参数,那么Object.assign会直接返回这个目标对象。

默认格式转换

如果参数不是一个对象,会被默认转换为一个对象,但是undefined和null无法转为对象,所以如果他们作为目标对象,是会报错的,如果是作为源对象,它其实会被忽略。

Object(转换的值);

处理数组

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

原因是因为数组被转换为对象了,数组的下标就是对象的属性名,于是相同的属性名被后来的替换了。

Object.assign()的常见用途

  1. 为对象添加属性
class Point {
  constructor(x,y) {
    Object.assign(this,{x,y});
  }
}

这种用法也常用在给vue的this上下文添加属性或替换属性的值。

  1. 为对象添加方法

以前都是通过prototype原型链添加,每次都要书写这个属性,太麻烦了,使用Object.assign可以减少书写重复代码

Object.assign(obj.prototype,{a(){...}},{b(){...}})
  1. 浅克隆对象
var a = {name:"a"};

Object.assign({},a);

如果需要保持继承对象的原型链,那么就需要这样写

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

Object.getPrototypeOf方法返回指定对象的原型,Object.create创建一个新对象,第一个参数是原型,也可以是null,这里使用了获取到的原型,然后再进行复制属性。

  1. 合并多个对象
const merge = (...sources) => {
  return Object.assign({},...sources);
}
  1. 为属性指定默认值
const a = {
  b:1,
  c:2
};

function processContent(options) {
  return Object.assign({},a,options);
}

这样,我们默认设置的值和传入的options参数会进行一个合并操作,如果没有传入对应的属性,使用的就是默认的a预设值。

当然这种方法也有缺陷,就是所有的值都必须是简单值,不能是对象,如果是对象,当存在相同的key时,替换的是整个值,而不是对象里面的相同项

var a = {
  b:1,
  c: {
    name:"a",
    type:"xxx"
  }
}

Object.assign({},a,{c:{ccc:""}}); 
//{b:1,c:{ccc:""}}

属性的可枚举性

对象中每一个属性都具有一个描述对象Descriptor,这个描述对象类似于属性的配置选项,用于控制改属性的行为。

如何获取一个属性的描述对象?

var a = {name:"a"};
Object.getOwnPropertyDescriptor(a,"name");

//{ 
//  value: "a", 
//  writable: true, 
//  enumerable: true, 
//  configurable: true 
//}

其中enumerable就是控制属性是否可枚举,默认是true,当我们设置为false时,部分方法就无法遍历出该属性

Object.defineProperties(a,{
  name: {
    enumerable: false
  }
})

当属性被设置为无法枚举后,下面这些方法就无法获取到对应的值!

  1. for...in循环,只能遍历自身和继承的可枚举属性
  2. Object.keys(),返回对象自身可枚举的属性键名数组
  3. JSON.stringify(),只串行话对象自身可枚举的属性。
  4. Object.assign(),只复制对象自身的可枚举属性。

上面4中方法,只有for...in可以遍历继承的属性,而enumerable的存在,本身就是为了让某些属性可以规避for...in循环,比如原型的toString方法就是通过这种方式,不会被for...in遍历出来的。

Object.getOwnPropertyDescriptor(Object.prototype,'toString').enumerable;
//false

另外,es6规定,class的原型链的方法都是不可枚举的。

总的来说,原型链的继承会使得属性变得复杂,但事实上我们一般情况下只关系对象自身的属性,所以推荐是尽量不要用for..in循环,而是改用Object.keys()代替。

属性的遍历

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

以上5中方法,属性的顺序都按照以下规则:

  1. 首先是属性名为数值的,0,1,2这种数值,从小到大排序
  2. 其次按照属性名为字符的属性,按照生成的时间顺序排序
  3. 再就是Symbol属性,按照生成的时间排序

__proto__

该属性是一个浏览器广泛支持的一个属性,但是并不是es6规定的一个属性,也只有浏览器需要部署这个属性,其他环境则不用,该属性可以用来读取和设置对象的prototype对象,目前ie11及其他浏览器都支持该属性。

但是并不建议使用,如果你要设置一个prototype对象,使用Object.setPrototypeOf(),读取使用Object.getPrototypeOf(),创建使用Object.create()来进行代替。

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

Object.getPrototypeOf()和Object.setPrototypeOf()

该方法和Object.setPrototypeOf()配合使用,get用于获取对象的prototype原型,set则用于设置对象的prototype原型。

function Rectangle() {
  ...
}

var a = new Rectangle();

Object.getPrototypeOf(a) === Rectangle.prototype; //true

//修改
Object.setPrototypeOf(a,Object.prototype);
Object.getPrototypeOf(a) === Rectangle.prototype; //false

如果获取的参数他不是一个对象,会被默认转换为对象。

Object.getPrototypeOf(1);
//等同于
Object.getPrototypeOf(Number(1));

如果是undecided和null,他们无法转换为对象,所以会报错。

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

Object.keys()

es6引入了这个方法,它会返回一个对象所有的key键名的数组,不包含继承的所有可遍历的属性键值。

const a = {a:1,b:2};
Object.keys(a); //["a","b"];

es7中还将Object.values(),Object.entries()加入,和keys一起作为遍历一个对象的补充手段,配置for...of使用

const a = {a:1,b:2};
let {keys} = Object;

for(let key of keys(a)) {
  console.log(key);
}

//a
//b

Object.values()

该方法返回一个数组,成员就是参数对象的所有可遍历的键值,不包含继承。

const a = {a:1,b:2};
Object.values(a); //[1,2];

这个数组的顺序,和属性的遍历讲的顺序是一样的,先是数字的key从小到大,然后是字符的key,按照创建顺序,然后是Symbol,这里Symbol是被忽略的。

const a = {a:1,2:"r",1:"f"};
Object.values(a); //["f","r",1];

当我们使用Object.create()去创建一个对象的时候,需要注意一下。该方法第一个参数是原型,可以为null,第二个参数,是添加到创建的新对象上的可枚举属性,但是这个枚举是需要自己设置的,他其实就是一个描述对象的集合,默认的enumerable属性都是false

var a = Object.create({},{
  p: {
    value:42
  }
});

console.log(a); // {p:42}
Object.values(a); // []

values得到的是空数组,因为enumerable是false,虽然p属性已经是a的自身属性了,但是因为不可枚举,values就无法获取到。

所以我们要手动设置

var a = Object.create({},{
  p: {
    value:42,
    enumerable:true
  }
});

console.log(a); // {p:42}
Object.values(a); // [42]

keys,values,entries都会对非对象的参数进行对象转换,但是undefined和null都是无法转换的,所以使用这些方法转换他们时会报错。

Object.entries()

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

var a = {b:1,c:2};

Object.entries(a); //[["b",1],["c",2]]

这个方法除了返回的值不一样,其他和values都一样。

Object.entries()的基本用途就是遍历对象的属性,另一个用处就是将对象转为Map对象。

var a = {b:1,c:2};

var map = new Map(Object.entries(a));
//Map {b:1,c:2}

对象的扩展运算符

解构赋值

const {a,b,...c} = {a:1,b:2,c:3,d:4,e:5};
a //1
b //2
c // {c:3,d:4,e:5}

c会将后面所有可遍历的,但尚未被读取的属性都赋值到自己。

因为解构,所以扩展运算符要写在最后面,并且等号右边不是undefined或者null,否则就会报错。

解构赋值,他也是浅复制,所以,如果值是对象,解构赋值复制的只是引用,而不是副本。

另外使用扩展运算符解构赋值也不会继承原型的属性。

var a = Object.create({x:1,y:2});
a.z = 3;

let {x,...{y,z}} = a;
x //1
y //undefined
z //3

x因为没有使用扩展运算符,就是单纯的解构赋值,它是可以读取到prototype的,所以x有值,后面使用了扩展运算符,再又进行了一次解构,y对应的是原型的y,但是扩展运算符不会继承原型属性,所以拿不到,于是成了undefined,z是因为后来的设置的,z不是原型而是自身,所以有值。

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

function a({b,c,...d}) {

}

b和c用于解构,d则通过扩展运算符接收多余的参数,以便进行其他操作。

扩展运算符

扩展运算符用于取出参数对象的所有可遍历属性,并且其复制到当前对象中

var a = {b:1,c:2};
let n = {...a};  //{b:1,c:2}

这种方式等同于Object.assign({},a)

这种方式只是复制了对象的自身属性,如果要完整的克隆一个对象,就需要复制对象的原型,可以这样做。

var a = {b:1,c:2};

Object.assign(Object.getPrototypeOf(a),a);

扩展运算符可用于两个或者对个对象合并

let ab = {...a,...b};

原则依旧是后面的覆盖前面的

let ab = {...a,x:1,y:2};

如果a里面有x或者y,会被后面的覆盖,所以这种特性也可以用来继承一个对象属性,只修改几个属性。

var a = {b:1,c:2,d:3};
var b = {...a,b:4};  //{b:4,c:2,d:3}

和数组的扩展运算符一样,对象的扩展运算符也可以在后面接运算使用

var a = ...(x>1?b:c)

如果后面接的是一个空对象,则无任何效果,如果是undefined或者null,则会被忽略,不会报错。

var a = {...{},b:1};  //{b:1}

var b = {...undefined,...null}; //{}

如果扩展运算符的参数对象中有取值的get函数,在运算时会被运行,因为扩展也是一次取值。

//这个时候不会抛出错误,因为x没有被执行
let aWithXGetter = {
  ...a,
  get x(){
    throw new Error("not throw yet")
  }
}

//这个时候会抛出错误,因为x被执行了
let aWithXGetter = {
  ...a,
  ...get x(){
    throw new Error("not throw yet")
  }
}

Object.getOwnPropertyDescriptors()

es5中Object.getOwnPropertyDescriptors用于返回某个对象属性的描述对象,在es7中,Object.getOwnPropertyDescriptors用于返回指定对象所有的自身属性(非继承属性)的描述对象。

es5中Object.getOwnPropertyDescriptors接收两个参数,第一个是对象,第二个是要获取描述对象的属性名

var a = {b:1,c:2};
Object.getOwnPropertyDescriptors(a,"b");
//{...}

但是现在es6就不一样了,他只接收一个参数,返回这个参数对象的所有属性的描述对象

var a = {b:1,c:2};
Object.getOwnPropertyDescriptors(a)

// {b:{....},c:{...}}

该方法的引入主要是为了解决Object.assign()无法正确复制get属性和set属性的问题。

const a = {
  set foo(value) {
    ...
  }
}

const b = Object.assign({},a);
console.log(b); //{foo:undefined}

Object.getOwnPropertyDescriptors(b);
// {
//  foo:{
//    configurable: true
//    enumerable: true
//    value: undefined
//    writable: true
//  }
}

你会发现没有set方法了。

因为Object.assign只会复制属性,并不会复制赋值的方法或者取值的方法,所以如果要正确的复制这个对象,需要Object.getOwnPropertyDescriptors和Object.defineProperties配合使用

const b = Object.defineProperties({},Object.getOwnPropertyDescriptors(a))

getOwnPropertyDescriptors可以获取到所有的描述,通过defineProperties重新定义一个对象。

getOwnPropertyDescriptors的另一个用处就是配合Object.create方法,将对象的属性,克隆到一个新对象上,这属于浅克隆

const clone = Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj))

Null传导运算符

在编程中,如果我们要读取对象中某个属性,往往需要判断对象是否存在。比如:

const firstName = (message && message.body && message.body.user && message.body.user.firstName) || "default";

这样层层判断非常麻烦,所以现在有个提案,引入Null传导运算符,简化上面的写法

const firstName = message?.body?.user?.firstName || "default";

上面代码中有很多个?.运算符,只要其中一个返回undefined或者null,整个就不继续运算了,直接返回undefined。

他有四种用法:

  1. obj?.prop 读取对象属性
  2. obj?.[expr] 同上
  3. func?.(...args) 函数或者对象方法调用
  4. new C?.(...args) 构造函数的调用

例子:

//如果a存在,就运行a.b.c().d
a?.b.c().d;  

//如果a不存在,下面代码无效果
a?.b = 42;
delete a?.b

//函数
function c(value) {
  console.log(value);
}

c?.(1);  //1

函数因为有参数,后面就接一个括号,括号里面放参数。并且运行

分类: ES6 标签: 对象javascriptes6Object

评论

暂无评论数据

暂无评论数据

目录