第九章 对象的扩展

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';
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容