object

作者: YJ_1101 | 来源:发表于2018-10-10 15:48 被阅读0次

    属性的见解表示法

    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) // Object {x: 1, y: 2}
    

    除了属性简写,方法也可以简写。

    const o = {
      method() {
        return "Hello!";
      }
    };
    
    // 等同于
    
    const o = {
      method: function() {
        return "Hello!";
      }
    };
    

    属性名表达式

    JavaScript 定义对象的属性,有两种方法。

    // 方法一
    obj.foo = true;
    
    // 方法二
    obj['a' + 'bc'] = 123;
    

    ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

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

    方法的name属性

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

    const person = {
      sayName() {
        console.log('hello!');
      },
    };
    
    person.sayName.name   // "sayName"
    

    Objects.is()

    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
    });
    

    Object.assgin()

    Object.assgin方法用来将对象(source)的所有可枚举属性复制到目标对象(target)。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。

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

    其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。因为只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。

    //有参数不为对象的情况
    const target = { a: 0 };
    const source1 = [1, 2];
    const source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    console.log(target) // { '0': 1, '1': 2, a: 0, c: 3 }
    
    Object(true) // {[[PrimitiveValue]]: true}
    Object(10)  //  {[[PrimitiveValue]]: 10}
    Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
    

    如果既有数组又有字符串,则后面的会覆盖前面的值

    const v1 = ['h', 'e', 'l', 'l', 'o'];
    const v2 = true;
    const v3 = 10;
    const v4 = 'abc';
    
    const obj = Object.assign({}, v1, v2, v3, v4);
    console.log(obj); // { '0': 'a', '1': 'b', '2': 'c', '3': 'l', '4': 'o' }
    

    如果只有一个参数,Object.assign会直接返回该参数。

    const target = { a: 0 };
    
    Object.assign(target);
    console.log(target) // { a: 0 }
    

    如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

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

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

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

    Object.assign(undefined) // TypeError: Cannot convert undefined or null to object
    Object.assign(null) // TypeError: Cannot convert undefined or null to object
    

    Object.assign只复制自身属性,不可枚举的属性(enumerable)和继承的属性不会被复制。

    Object.assign({b: 'c'},
      Object.defineProperty({}, 'invisible', {
        enumerable: false,
        value: 'hello'
      })
    )
    // { b: 'c' }
    

    上面的代码,Object.assign要复制的对象只有一个不可枚举的属性invisible,这个属性并没有被复制进去。
    属性名未Symbol值得属性,也会被Object.assign复制。

    Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
    // { a: 'b', Symbol(c): 'd' }
    
    常见用途

    (1)为对象添加属性

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

    上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

    (2)为对象添加方法

    Object.assign(SomeClass.prototype, {
      someMethod(arg1, arg2) {
        ···
      },
      anotherMethod() {
        ···
      }
    });
    
    // 等同于下面的写法
    SomeClass.prototype.someMethod = function (arg1, arg2) {
      ···
    };
    SomeClass.prototype.anotherMethod = function () {
      ···
    };
    

    上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。

    (3)克隆对象

    function clone(origin) {
      return Object.assign({}, origin);
    }
    

    上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。

    不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

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

    (4)合并多个对象

    将多个对象合并到某个对象。

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

    如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。

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

    (5)为属性指定默认值

    const DEFAULTS = {
      logLevel: 0,
      outputFormat: 'html'
    };
    
    function processContent(options) {
      options = Object.assign({}, DEFAULTS, options);
      console.log(options);
      // ...
    }
    

    上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。

    注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。

    const DEFAULTS = {
      url: {
        host: 'example.com',
        port: 7070
      },
    };
    
    processContent({ url: {port: 8000} })
    // {
    //   url: {port: 8000}
    // }
    

    上面代码的原意是将url.port改成 8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。

    属性的可枚举性

    可枚举性

    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。

    let obj = { foo: 123 };
    Object.getOwnPropertyDescriptor(obj, 'foo')
    //  {
    //    value: 123, // value—表示属性的值
    //    writable: true, // writable—表示属性的可书写性
    //    enumerable: true, // enumerable—表示属性是否可枚举。
    //    configurable: true // configurable—表示属性是否可编辑,默认值为true,编辑内容包括(1)能否使用delete删除,(2)能否修改属性特性,(3)能否修改访问器属性
    //  }
    

    如果描述对象的enumerable属性为false,就表示某些操作会忽略当前属性。

    ES5有3个操作会忽略enumerable为false的属性。

    for...in循环:只遍历对象自身的和继承的可枚举的属性。
    Object.keys():返回对象自身的所有可枚举的属性的键名。
    JSON.stringify():只串行化对象自身的可枚举的属性。

    ES6有1个操作会忽略enumerable为false的属性。

    Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

    只有for...in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for...in操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的toString方法,以及数组的length属性,就通过“可枚举性”,从而避免被for...in遍历到。所以,尽量不要用for...in循环,而用Object.keys()代替。

    属性的遍历

    ES6 一共有 5 种方法可以遍历对象的属性。

    (1)for...in

    for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

    (2)Object.keys(obj)

    Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

    (3)Object.getOwnPropertyNames(obj)

    Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

    (4)Object.getOwnPropertySymbols(obj)

    Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

    (5)Reflect.ownKeys(obj)

    Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

    以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

    首先遍历所有数值键,按照数值升序排列。
    其次遍历所有字符串键,按照加入时间升序排列。
    最后遍历所有 Symbol 键,按照加入时间升序排列。

    Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
    // ['2', '10', 'b', 'a', Symbol()]
    

    上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2和10,其次是字符串属性b和a,最后是 Symbol 属性。

    __ proto__属性

    // es5 的写法
    const obj = {
      method: function() { ... }
    };
    obj.__proto__ = someOtherObj;
    
    // es6 的写法
    var obj = Object.create(someOtherObj);
    obj.method = function() { ... };
    

    无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
    实现上, __ proto __ 调用的是Object.prototype.__ proto __,具体实现如下。

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

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

    Object.setPrototypeOf()

    Object.setPrototypeOf方法的作用与__ proto __相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

    // 格式
    Object.setPrototypeOf(object, prototype)
    
    // 用法
    const o = Object.setPrototypeOf({}, null);
    

    下面是一个例子。

    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
    

    上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。

    如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。

    Object.setPrototypeOf(1, {}) === 1 // true
    Object.setPrototypeOf('foo', {}) === 'foo' // true
    Object.setPrototypeOf(true, {}) === true // true
    

    由于undefined和null无法转为对象,所以如果第一个参数是undefined或null,就会报错。

    Object.setPrototypeOf(undefined, {})
    // TypeError: Object.setPrototypeOf called on null or undefined
    
    Object.setPrototypeOf(null, {})
    // TypeError: Object.setPrototypeOf called on null or undefined
    

    Object.getPrototypeOf()

    该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

    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
    

    Object.keys()

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

    var obj = { foo: 'bar', baz: 42 };
    Object.keys(obj)
    // ["foo", "baz"]
    

    ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,作为遍历一个对象的补充手段,供for...of循环使用。

    let {keys, values, entries} = Object;
    let obj = { a: 1, b: 2, c: 3 };
    
    for (let key of keys(obj)) {
      console.log(key); // 'a', 'b', 'c'
    }
    
    for (let value of values(obj)) {
      console.log(value); // 1, 2, 3
    }
    
    for (let [key, value] of entries(obj)) {
      console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
    }
    

    Object.values()

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

    const obj = { foo: 'bar', baz: 42 };
    Object.values(obj)
    // ["bar", 42]
    

    Object.values只返回对象自身的可遍历属性。

    const obj = Object.create({}, {p: {value: 42}});
    Object.values(obj) // []
    

    上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的,因为p的属性描述对象的enumerable默认是false,Object.values不会返回这个属性。只要把enumerable改成true,Object.values就会返回属性p的值。

    const obj = Object.create({}, {p:
      {
        value: 42,
        enumerable: true
      }
    });
    
    Object.values(obj) // [42]
    

    Object.values会过滤属性名为 Symbol 值的属性。

    Object.values({ [Symbol()]: 123, foo: 'abc' });
    // ['abc']
    

    如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

    Object.values('foo')
    // ['f', 'o', 'o']
    

    Object.entries()

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

    const obj = { foo: 'bar', baz: 42 };
    Object.entries(obj)
    // [ ["foo", "bar"], ["baz", 42] ]
    

    除了返回值不一样,该方法的行为与Object.values基本一致。

    对象的扩展运算符

    对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

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

    这等同于使用Object.assign方法。

    let aClone = { ...a };
    // 等同于
    let aClone = Object.assign({}, a);
    

    相关文章

      网友评论

          本文标题:object

          本文链接:https://www.haomeiwen.com/subject/sseynftx.html