1.什么是对象
对象是可以拥有属性和方法的集合。
万物皆对象 == 万物皆可看作对象
2.ES6的扩展
(1)属性和方法的简洁表示
允许直接的写入变量和函数作为对象的属性和方法
//属性的简写 只写属性名,不写属性值,属性值就是属性名所代表的变量
var a="123";
var b={a}; // { a : "123" }
//方法的简写 省略function
var obj = {
a,
greeting(){
console.log("hello");
}
}
//等价于
var obj = {
a:a,
greeting:function(){
console.log("hello");
}
}
方便之处:
- 函数需要返回一个对象 return { x, y }
- CommonJs模块输出 module.exports = { func1, func2, func3... }
- 控制台同时打印多个对象 console,log(obj1,obj2); vs console.log({obj1,obj2})
- 属性的getter和setter
注意:
- 简洁写法中,属性名总是字符串(不与关键字冲突)
var object={
class(){
return "hello"
}
}
//等价于
var object={
"class":function(){
return "hello"
}
}
- 若对象的方法的值是一个Generator函数,则需要在前面加*号,否则报错
var obj = {
*m(){
yield "hello world"
}
}
(2)属性名表达式
定义对象的属性的方法有两种:1.直接使用标识符作为属性名,2使用表达式作为属性名,表达式放在[ ]中。
在使用字面量的方式定义对象时( var obj = {a,b} ),ES5只支持使用第一种方法定义对象属性 ,ES6允许字面量定义对象时使用方法二定义对象属性,即:[ 表达式 ]
var obj = {
['a'+'b']:'123',
['g'+'reet'](){
return 'hello'
}
}
obj.ab;//123
obj.greet();//hello
- 注意:
属性名表达式与简洁表示法不能同时使用,否则报错
//报错
var foo= 'bar';
var bar = 'abc';
var obj = { [foo] }
//就是想得到obj = { 'bar' : 'abc' } 直接obj = { bar } 就可以,如果非要用表达式,如下:
var foo = 'bar';
var obj = { [foo]:'abc' }
如果属性名表达式是一个对象,默认情况下会将对象转为字符串[object object],如果有多个属性名表达式是对象,则后面的会覆盖前面的,最终只剩最后一个属性值
var obj1={a:1};
var obj2={b:2};
var obj = {
[obj1]:'abc',
[obj2]:'xyz'
};
obj;//{ [object object] : 'xyz' }
(3)对象方法的name属性
函数的name属性返回的是函数名,对象的方法也是函数,因此也有name属性,返回函数名。
有几种情况例外:
- 1.bind方法创造的函数,name属性返回 bound 函数名
- 2.Function构造函数创造的函数,name属性返回 anonymous
- 3.对象方法使用了取值函数(getter)和存值函数(setter),则name属性是在该对象的描述对象的get(set)属性上,返回值是 get 方法名 (set 方法名)
var obj = {
get foo(){},
set foo(x){}
}
obj.foo.name; // TypeError:Cannot read property 'name' of undefined
var descriptor = Object.getOwnPropertyDescristor(obj,'foo')
descriptor.get.name // get foo
descriptor.set.name // set foo
- 4.如果对象方法是一个Symbol值,name属性返回的是这个Symbol值的描述
(4)属性的可枚举性与遍历
描述对象
对象的每一个属性都有一个描述对象,用于控制该属性的行为(控制属性的属性)。描述对象包括六个元属性:
value:属性值,默认undefined
writable:是否可以改变 默认true 只要 writable 和 configurable 有一个为 true,就允许改动
enumerable:是否可枚举 默认true
configurable:是否可配置 默认true 即是否可以修改属性描述对象,设为 false将阻止某些操作改写该属性,无法删除该属性
get:取值函数 默认undefined
set:存值函数 默认undefined属性的可枚举性
属性的可枚举性指的就是描述对象的enumerable属性,如果该属性为false,就表示某些操作会忽略当前属性。目前,有四个操作会忽略enumerable为false的属性:
for...in循环:只遍历对象 自身的 和 继承的 可枚举的属性。(ES5)
Object.keys():返回对象 自身的 所有可枚举的属性的键名。(ES5)
JSON.stringify():只串行化对象 自身的 可枚举的属性。(ES5)
Object.assign(): 忽略enumerable为false的属性,只拷贝对象 自身的 可枚举的属性。(ES6)属性的遍历
1.for...in
遍历对象所有的(自身的、继承的)可枚举的属性,不包含Symbol属性
2.Object.keys()
遍历对象自身的所有可枚举的属性,不含Symbol属性,返回一个数组
3.Object.getOwnPropertyNames()
遍历对象自身的所有属性,不含Symbol属性,返回一个数组
4.Object.getOwnPropertySymbols()
遍历对象自身的所有Symbol属性,返回一个数组
5.Reflect.ownKeys()
遍历对象自身的所有属性,返回一个数组
以上方法都遵循的属性遍历次序:
- 首先遍历所有属性名为数值的属性,按照数字排序
- 其次遍历所有属性名为字符串的属性,按照生成时间排序
- 最后遍历所有属性名为Symbol的属性,按照生成时间排序
(5)super关键字
this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
var proto = {
foo: 'hello'
};
varobj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
// 报错
const obj = {
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)(方法)。
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)对象的扩展运算符
解构赋值
对象的解构赋值是用于从一个对象中取值,取出对象中所有可遍历的,但尚未被读取的属性分配到指定对象上,取出的包括所有属性的键和值
var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
注意:
1.要求等号右边是一个对象,如果等号右边是undefined或null,会报错,因为无法转为对象。
2.必须是最后一个参数,否则会报错。
3.浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是副本。
4.扩展运算符的解构赋值,不能复制继承自原型对象的属性。
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...{y,z} = o;
x // 1
y // undefined
z // 3
扩展运算符
数组是特殊的对象,所以数组也可以使用扩展运算符
let foo = { ...['a', 'b', 'c'] };
foo // {0: "a", 1: "b", 2: "c"}
如果扩展运算符后面不是对象,则会自动将其转为对象,
如果扩展运算符后面是一个空对象,则没有任何效果,
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象
{...{}, a: 1} // { a: 1 }
{...true} // {} 等同于 {...Object(true)}
{...undefined} // {} 等同于 {...Object(undefined)}
{...null} // {} 等同于 {...Object(null)}
{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
扩展运算符常用于合并两个对象,等同于使用Object.assign()方法
let a = {a:1,b:2}
let b={c:3,d:4}
let ab = { ...a, ...b }; //{a: 1, b: 2, c: 3, d: 4}
// 等同于
let ab = Object.assign({}, a, b);
扩展运算符也可以用于修改对象的属性,因为用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。(如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值)
(7)链判断运算符与Null判断运算符
链判断运算符(?.)
用于判断对象是否包含某属性或方法,?. 左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
//简化
const firstName = message?.body?.user?.firstName || 'default';
判断对象的方法时,若存在该方法就立即执行该方法,若不存在则返回undefined
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b() //若a.b不是函数,不可调用,报错
a?.()
// 等同于
a == null ? undefined : a() //a不是null或undefined,但也不是函数,报错
使用这个运算符,有几个注意点:
(1)短路机制
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
上面代码中,如果a是undefined或null,那么x不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。
(2)delete 运算符
delete a?.b
// 等同于
a == null ? undefined : delete a.b
上面代码中,如果a是undefined或null,会直接返回undefined,而不会进行delete运算。
(3)括号的影响
如果属性链有圆括号,链判断运算符只对圆括号内部有影响。
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c
上面代码中,?.对圆括号外部没有影响,不管a对象是否存在,圆括号后面的.c总是会执行。
(4)报错场合
以下写法是禁止的,会报错。
// 构造函数
new a?.()
new a?.b()
// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`
// 链判断运算符的左侧是 super
super?.()
super?.foo
// 链运算符用于赋值运算符左侧
a?.b = c
(5)右侧不得为十进制数值
如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
null判断运算符(??)
??的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值,适用于跟链判断运算符?.配合使用,为null或undefined的值设置默认值。
??有一个运算优先级问题,它与&&和||的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
上面四个表达式都会报错,必须加入表明优先级的括号。
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
3.ES6新增方法
(1)Object.is()
ES5中,判断两个值是否相等:== 与 ===,前者会自动转换数据类型,后者+0等于-0,NaN不等于本身。ES6提出 同值相等,在所有环境中,只要两者的值相等就相等。
Object.is()的行为与===基本一致,不同之处是Object.is()判断 +0不等于-0,NaN等于自身,与===正好相反
Object.is('foo', 'foo') // true
Object.is({}, {}) // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
(2)Object.assign()
Object.assign(targetObj,sourceObj1,sourceObj2...),此方法用于将源对象(sourceObj)中的所有自身的、可枚举属性复制到目标对象(targetObj)中。若其中存在同名属性,后面的会覆盖前面的。
let target = { a: 1, b: 1 };
let source1 = { b: 2, c: 2 };
let source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
若只有一个参数,直接返回该参数,若该参数不是对象,则转化成对象返回,若该参数是null、undefined,则报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
若非对象参数出现在源对象位置(非首参数),则都会被转成对象,但除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果,而无法转成对象的就跳过,不会报错。
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
const obj = Object.assign({}, 'abc', 10, true);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
属性名为 Symbol 值的属性,也会被Object.assign拷贝。
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) // { a: 'b', Symbol(c): 'd' }
注意点:
- Object.assign()是浅拷贝,目标对象拷贝的得到的是源对象的引用
- 对于同名属性采取替换,而非添加
- 可以用于处理数组,但会把数组视为对象处理
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
//等价于
Object.assign({ 0:1, 1:2, 2:3 },{ 0:4, 1:5 })
//同名属性会替换,因此结果是[4,5,3]
- 只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
let source = {
get foo() { return 1 }
};
let target = {};
Object.assign(target, source)
// { foo: 1 }
常见用途:
- 为对象添加属性、方法
let obj = { a:1 };
Object.assign( obj, { b:2 } ); // obj{ a:1, b:2 }
- 克隆对象
let b = Object.assign( {}, obj ); // b{ a:1, b:2 }
- 合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources); // 返回target
const merge1 =(...sources) => Object.assign({}, ...sources); // 返回一个新对象
- 为属性指定默认值
//直接在目标对象指定
let obj = { a:2, c:3 }
let b = Object.assign( { a:1,b:2 }, obj );
//单独定义一个默认值对象
let default = { a:1,b:2 }
let b1 = Object.assign({}, default , obj )
(3)Object.getOwnPropertyDescriptors()
ES5 的Object.getOwnPropertyDescriptor(对象属性) 方法会返回某个对象属性的描述对象(descriptor)。
ES2017 引入了Object.getOwnPropertyDescriptors(指定对象) 方法,返回指定对象所有自身属性(非继承属性)的描述对象。
主要用途:
- 解决Object.assign()无法正确拷贝get属性和set属性的问题。
const source = {
set foo(value) {
console.log(value);
}
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined, Object.assign()总是拷贝一个属性的值,而不拷贝它背后的赋值方法或取值方法。
// writable: true,
// enumerable: true,
// configurable: true }
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }
- 配合Object.create()方法,将对象属性克隆到一个新对象(浅拷贝)
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
- 实现一个对象继承另一个对象。
//之前写法,需考虑__proto__部署问题
const obj = {
__proto__: prot,
foo: 123,
};
//Object.getOwnPropertyDescriptors()写法
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
})
);
(4)__proto__属性、Object.setPrototypeOf()、Object.getPrototypeOf()
__proto__属性
__proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。本质上是一个内部属性
标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
Object.setPrototypeOf()
此方法与__proto__属性作用相同,用于设置一个对象的prototype对象,返回参数对象本身
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
//等同于
function setPrototypeOf(obj, proto) {
obj.__proto__ = proto;
return obj;
}
如果第一个参数不是对象,会自动转为对象,如果第一个参数是undefined或null,因无法转为对象,就会报错。
Object.getPrototypeOf()
Object.getPrototypeOf(obj)用于读取一个对象的原型对象
同样的,如果第一个参数不是对象,会自动转为对象,如果第一个参数是undefined或null,因无法转为对象,就会报错。
// 等同于 Object.getPrototypeOf(Number(1))
Object.getPrototypeOf(1)
// 等同于 Object.getPrototypeOf(String('foo'))
Object.getPrototypeOf('foo')
// 等同于 Object.getPrototypeOf(Boolean(true))
Object.getPrototypeOf(true)
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object
(5)Object.values()
遍历对象,返回对象所有自身的、可遍历的属性值组成的数组。
var obj = {a:'hello', b:'world' }
Object.values(obj); // ['hello', 'world' ]
Object.values会过滤属性名为 Symbol 值的属性。
Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc']
如果参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo') // ['f', 'o', 'o']
//等价于
Object.values( { 0:'f', 1:'o', 2:'o'} )
(6)Object.entries()
遍历对象,返回对象所有自身的、可遍历的属性的键值对组成的数组。除返回值不一样,其余行为与Object.values()基本一致。
var obj = {a:'hello', b:'world' }
Object.entries(obj); // [ ['a', 'hello'], ['b', 'world'] ]
除遍历对象之外,Object.entries()另一个常用用途是将对象转为真正的Map结构
var obj = { foo: 'bar', baz: 42 };
var map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
(7)Object.fromEntries()
此方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。作用之一是将map结构转换为对象
var entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries) // { foo: "bar", baz: 42 }
作用之二,配合URLSearchParams对象,将查询字符串转为对象
(URLSearchParams 是一个WebAPI,此接口定义了一些实用的方法来处理 URL 的查询字符串)
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }