第九章 对象的扩展

9.1 属性的简洁表示法

ES6 允许直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁。

const foo = 'bar';
const baz = {foo};
baz // {foo: 'bar'}

//等同于

const baz = {foo:foo};

上面代码表明,ES6允许在对象之中,直接写变量。这时,属性名为变量名,属性值为变量的值。

比如:

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: function()...
    hello() { console.log('我的名字是:', this.name); }
}

这种写法用于函数的返回值,将会非常方便。

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

getPoint();

9.2 属性名表达式

JS中定义对象的方法

// 直接使用标识符作为属性名

obj.foo = true;

//使用表达式作为属性名

obj['a' + 'bc'] = 123;

//使用字面量方式定义对象

var obj = {
    foo: true,
    abc: 123
}

ES6允许字面量定义对象时,使用表达式作为对象的属性名

let propKey = 'foo';
let obj = {
    [propKey]: true,
    ['a' + 'bc']: 123
}

来看另一个例子:

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

9.3 方法的 name 属性

const person = {
    sayName() {
       console.log('hello!')
    }
}

person.sayName.name  // 'sayName'

9.4 Object.is()

ES5 比较两个值是否相等:== 和 ===

他们都有缺点:== 会自动转换数据类型,=== 中 NaN不等于自身,以及 +0 等于 -0。
JS缺乏一种运算,在所有环境中,只要两个值是一样的,他们就应该相等。

ES6提出了 'same-value-equality'(同值相等)算法,Object.is就是这个新方法,用来比较两个值是否严格相等,与严格比较运算符 === 的行为基本一致。

Object.is('foo','foo')
// true
Object.is({}, {})
// false

不同之处在于,一是 +0不等于-0,二是NaN等于自身

+0 === -0  // true
NaN === NaN // false

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

ES5中,可以通过下面的代码部署 Object.is()

Object.defineProperty(Object, 'is', {
    value: function(x, y){
      if ( x === y ){
           // 针对 +0 不等于 -0 的情况
           return x !== 0 || 1/x === 1/y
      }
      //针对NaN的情况
      return x !== x && y !== y;
    },
    configurable: true,
    enumerable: false,
    writable: true
})

9.5 Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)

const target = { a: 1};

const source1 = { b: 2};
const source2 = { c: 3};

Object.assign(target, source1, source2);  // { a: 1,b: 2,c: 3}

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

Object.assign(2)  // Number(2)

由于 undefined和null无法转为对象,所以如果他们作为参数,就会报错。

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

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

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

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

注意点:
(1) 浅拷贝
Object.assign方法实行的是浅拷贝

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

obj1.a.b = 2;
obj2.a.b  // 2

(2) 同名属性的替换

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

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

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

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

用途:
(1) 为对象添加属性

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

(2) 为对象添加方法

Object.assign(SomeClass.prototype, {
    someMethod(arg1, arg2){
       ...
    },
    anotherMethod() {
       ···
    }   
})

等同于下面的方法

SomeClass.prototype.someMethod = function(arg1, arg2){
    
}

(3) 克隆对象

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

(4) 合并多个对象

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

9.6 属性的可枚举性和遍历

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

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }
  • for...in循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
    -JSON.stringify():只串行化对象自身的可枚举的属性。
  • Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

(2) 属性的遍历

9.7 Object.getOwnPropertyDescriptors()

9.8 proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()

JS语言的对象继承是通过原型链实现的,ES6提供了更多原型对象的操作方法

(1) proto属性
用来读取或设置当前对象的 prototype 对象。

//ES6的写法
const obj = {
    method: function(){

    }
}
obj._proto_ = someOtherObj;

// es5的写法

var obj = Object.create(someOtherObj);
obj.method = function(){ ... }

具体的实现原理:

Object.defineProperty(Object.prototype, '_proto_', {
    get() {
       let _thisObj = Object(this);
       return Object.getPrototypeOf(__thisObj);
    },
    set(proto){
       if ( this === undefined || this === null ){
          throw new TypeError();
       }
       if ( !isObject(this) ){
           return undefined;
       }
       if ( !isObject(proto) ){
            return undefined;
       }
       let status = Reflect.setPrototypeOf(this, proto);
       if (!status) {
         throw new TypeError();
       }
    }
})

function isObject(value) {
  return Object(value) === value;
}

(2) Object.setPrototypeOf()
用来设置一个对象的prototype对象,返回参数对象本身。它是ES6正式推荐的设置原型对象的方法

let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40

(3) Object.getPrototypeOf()
用于读取一个对象的原型对象

Object.getPrototypeOf(obj);

function Rectangle() {
  // ...
}

const rec = new Rectangle();

Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

9.9 super关键字

我们知道,this关键字总是指向函数所在的当前对象,ES6又新增了另一个类似的关键字super,指向当前对象的原型对象。

const proto = {
    foo: 'hello'
}

const obj = {
    find() {
        return super.foo
    }
}

Object.setPrototypeOf(obj, proto);
obj.find()  // 'hello'

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

//报错
const obj = {
    foo: super.foo
}

//报错
const obj = {
    foo: () => super.foo
}

//报错
const obj = {
  foo: function () {
    return super.foo
  }
}

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

(1) Object.keys():成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

var obj = {
    foo: 'bar',
    baz: 42
}
Object.keys(obj)

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

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

自己实现Object.entries方法,非常简单:

function entries(obj){
    let arr = [];
    for ( let key of Object.keys(obj) ){
        arr.push([key, obj[key]])
    }
    return arr;
}

9.11 对象的扩展运算符

(1) 解构赋值

对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

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

x: // 1
y: // 2
z: // { a: 3, b: 4}

需要注意两点:
(一) 解构赋值右边必须是一个对象,要不会报错

let { x, y, ..z } = null    //报错
let { x, y, ..z } = undefined    //报错

(二) 解构赋值必须是最后一个参数

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

(2) 扩展运算符

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

n // { a: 3, b: 4 }

9.12 Null传导运算符

如果要读取对象内部的某个属性,往往需要判断该对象是否存在。比如,要读取message.body.user.firstName,安全的写法:

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

先在,有了一个提案,引入了 'Null 传导运算符'

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

推荐阅读更多精彩内容