对象的扩展以及新增方法

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不等于-0NaN等于自身,与===正好相反

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" }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容