JavaScript理解对象Object

对象定义为一组属性得无序集合。
意味着对象就是一组没有特定顺序得值。对象得每个属性或方法都有一个名称来标识,这个名称映射到一个值。
对象中得this指向对象本身

  • 属性的类型
    • ES使用一些内部特性来描述属性的特征。这些特征是由为JS实现引擎的规范定义的。因此,开发者不能在JS中直接访问这些特性。
    • 为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来。[[Enumerable]]
    • 属性分为:数据属性和访问器属性
  1. 数据属性
    • 数据属性包含一个保存数据值的位置。值会从这个位置读取、写入。数据属性有4个特性描述它们的行为。
      • [[Configureable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,为true。
      • [[Enumerable]]:表示是否可以通过for-in循环返回。默认为true。
      • [[Writable]]:表示属性的值是否可以修改。默认为true。
      • [[Value]]:包含属性实际的值。读取和写入属性值的位置。默认为undefined。
  let person = {};
  Object.defineProperty(person, 'name', {
    Configurable: false,
    Value: '云'
  })

  person.name; // 云
  delete person.name; 
  person.name; // 云
  • 将Configurable设置为false,意味着这个属性不能从对象上删除。非严格模式下对这个属性调用delete没有效果。严格模式下调用会抛出错误。一旦一个属性被定义为不可配置之后,就不能再变回可配置的了。再次调用Object.defineProperty()并修改任何非writable属性会导致错误。
  • 虽然可以对同一个属性多次调用Object.defineProperty(),但在把configurable设置为false之后就会受限制了.
  • 在调用Object.defineProperty()时,configurable、enumerable、writable的值如果不指定,则默认为false。多数情况下,可能都不需要Object.defineProperty()提供强大的设置。
  1. 访问器属性
  • 访问器属性不包含数据值。相反它们包含一个获取getter函数和一个设置函数setter函数,不过这两个函数不是必需的。
  • 在读取访问器属性时,会调用获取函数,这个函数返回一个有效值。
  • 在写入访问器属性时,会调用设置函数,这个函数决定对数据做出什么修改。
  • 访问器属性有4个特性描述它们的行为:
    • [[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
    • [[Enumerable]]:表示属性是否可以通过for-in循环返回。默认为true。
    • [[Get]]:获取函数,在读取属性时调用。默认值为undefined。
    • [[Set]]:设置函数,在写入属性时调用。默认值为undefined。
  • 访问器属性是不能直接定义的,必须使用Object.defineProperty()。
  // 定义一个对象,包含伪私有成员year_和公共成员edition
  let book = {
    year_: 2017,
    edition: 1
  };
  Object.defineProperty(book, 'year', {
    get() {
      return this.year_;
    },
    set(newValue) {
      if (newValue > 2017) {
        this.year_ = newValue;
        this.edition +=newValue - 2017;
      }
    }
  });

  book.year = 2018;
  book.edition; // 2
  • 获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。在严格模式下,尝试写入只定义了获取函数的属性会抛出错误。类似地,只有一个设置函数的属性是不能读取的,非严格模式下会返回undefined,严格模式下下会抛出错误。

  • 定义多个属性

    • ES提供了Object.defineProperties()方法。这个方法可以通过多个描述符一次性定义多个属性。它接收两个参数:要为之添加或修改的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应。
      let book = {}
      Object.defineProperties(book, {
        year_: {
          value: 2017
        },
        edition: {
          value: 1
        },
        year: {
          get() {
            return this.year_;
          },
          set(newValue) {
            if (newValue > 2017) {
              this.year_ = newValue;
              this.edition += newValue - 2017;
            }
          }
        }
      });
    
    • 这段代码跟上一节实例中的一样。唯一的区别是所有属性都是同时定义的,并且数据属性的configurable、enumerable、writable特性都是false。
  • 读取属性的特性

    • 使用Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
      • 接收2个参数:属性所在的对象和要取得其描述符的属性名。
      • 返回值是一个对象,对于访问器属性包含configurable、enumerable、get、set属性。对于数据属性包含configurable、enumerable、writable、value属性。
    // 承接上例
    let descriptor = Object.getOwnPropertyDescriptor(book, 'year_');
    descriptor.value; // 2017
    descriptor.configurable; // false
    typeof descriptor.get; // undefined
    
    let descriptor = Object.getOwnPropertyDescriptor(book, "year"); 
    console.log(descriptor.value);          // undefined 
    console.log(descriptor.enumerable);     // false 
    console.log(typeof descriptor.get);     // "function"
    
    • ES2017新增了Object.getOwnPropertyDescriptors()静态方法。这个方法实际上会在每个属性上调用Object.getOwnPropertyDescriptot()并在一个新对象中返回它们。
      // 承接book对象
      Object.getOwnPropertyDescriptors(book);
      // {
        // edition: {
          // configurable: false,
          // enumerable: false,
          // value: 1,
          // writable: false
        },
        //   year: { 
        //     configurable: false, 
        //     enumerable: false, 
        //     get: f(), 
        //     set: f(newValue), 
        //   }, 
        //   year_: { 
          //     configurable: false, 
          //     enumerable: false, 
          //     value: 2017, 
          //     writable: false 
          //   }
      // }
    
  • 合并对象

    • 开发者经常觉得合并(merge)两个对象很有用。更具体地说,就是把源对象所有的本地属性一起复制到目标对象上。有时候这种操作也被称为混入(mixin),因此目标对象通过混入的属性得到增强。

    • ES6为合并对象提供了Object.assign()方法。

      • 接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中的可枚举(Object.propertyIsEnumerable()返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象。
      • 以字符串和符号为键的属性会被复制。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值。
      let dest, src, result;
    
      /**
       * 简单复制
       */
      dest = {};
      src = {id: 'src'};
    
      result = Object.assign(dest, src);
    
      // Object.assign()修改目标对象
      // 也会返回修改后的目标对象
      dest === result; // true
      dest !== src; // true
      result; // {id: 'src'}
      dest; // {id: 'src'}
    
      /**
       * 多个源对象
       */
       dest = {}; 
       result = Object.assign(dest, { a: 'foo' }, { b: 'bar' }); 
       console.log(result); // { a: foo, b: bar }
    
      /**
       * 获取函数与设置函数
       */
      dest = {
        set a(val) {
          console.log(`dest setter with ${val}`);
        }
      };
      src = {
        get a() {
          console.log('src getter');
          return 'foo';
        }
      };
    
      Object.assign(dest, src);
    
      // 调用src的获取方法
      // 调用dest的设置方法并传入参数'foo'
      // 因为这里的设置函数不能执行赋值操作
      // 所以实际上并没有把值转移过来
      console.log(dest); // {set a(val) {...}}
    
    • Object.assign()实际上对每个源对象执行的是浅拷贝。
      • 如果多个源对象都有相同的属性,则使用最后一个复制的值。
      • 从源对象访问器属性取得的值,比如获取函数[[Get]],会作为一个静态值赋给目标对象。换句话说,不能在两个对象间转移获取函数和设置函数。
      let dest, src, result;
    
      // 覆盖属性
      dest = { id: 'dest' };
      
      result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' });
    
      // Object.assign会覆盖重复的属性
      result; // {id: 'src2', a: 'foo', b: 'bar'}
    
      // 可以通过目标对象上的设置函数观察到覆盖的过程:
      dest = {
        set id(x) {
          console.log(x);
        }
      };
    
      Object.assign(dest, {id: 'first'}, {id: 'second'}, {id: 'third'});
      // first
      // second
      // third
    
      // 对象引用
      dest = {};
      src = { a: {}};
    
      // 浅拷贝意味着只会复制对象的引用
      Object.assign(dest, src);
      dest; // {a: {}}
      dest.a === src.a; // true
    
    • 如果赋值期间出错,则操作会中止并退出,同时抛出错误。Object.assign()没有回滚之前赋值的概念,因此它是一个尽力而为、可能只会完成部分复制的方法。
  • 对象标识符及相等判定

    • ES6之前,有些特殊情况即使是===操作符也无能为力:
      // ===符合预期的情况
      true === 1 // false
      {} === {} // false
      "2" === 2 // false
    
      // 在不同js引擎中表现不同,但仍被认为相等
      +0 === -0 // true
      +0 === 0 // true
      -0 === 0 // true
    
      // 使用isNaN()来确定NaN的相等性
      NaN === NaN // false
      isNaN(NaN) // true
    
    • ES6新增了Object.is(),这个方法与===很像,但同时也考虑到了上述边界情形。这个方法必须接收2个参数:
      console.log(Object.is(true, 1));  // false 
      console.log(Object.is({}, {}));   // false 
      console.log(Object.is("2", 2));   // false
    
      // 正确的0、-0、+0相等/不相等判定
      Object.is(+0, -0); // false
      Object.is(+0, 0); // true
      Object.is(-0, 0); // false
    
      // 正确的NaN相等判定
      Object.is(NaN, NaN); // true
    
      // 要检查超过2个值,递归地利用相等性传递即可:
      function recursivelyCheckEqual(x, ...rest) {
        return Object.is(x, rest[0]) && (rest.length < 2 || recursivelyCheckEqual(...rest));
      }
    
  • 增强的对象语法

    • ES6新增了一些操作对象的语法糖。
    1. 属性值简写
      let name = 'Matt';
    
      let person = {
        name
      };
    
      // 代码压缩程序会在不同作用域间保留属性名,以防止找不到引用
      function makePerson(name) {
        return {
          name
        };
      }
    
      let person = makePerson('Matt');
      person.name; // Matt
    
    1. 可计算属性
    • 在引入可计算属性之前,如果想使用变量的值作为属性,那么必须先声明对象,然后使用正括号语法来添加属性。换句话说,不能在对象字面量中直接动态命名属性。
    • 有了可计算属性,就可以在对象字面量中完成动态属性赋值。中括号包围的对象属性键告诉运行时将其作为js表达式而不是字符串来求值:
      const nameKey = 'name';
      const ageKey = 'age';
      const jobKey = 'job';
    
      let person = {};
      person[nameKey] = 'Matt';
      person[ageKey] = '27';
      person[jobKey] = 'software engineer';
    
      // 可计算属性
      let person2 = {
        [nameKey]: 'Matt',
        [ageKey]: 27,
        [jobKey]: 'Software engineer'
      };
    
      // 因为被当作JS表达式求值,所以可计算属性本身可以是复杂的表达式,在实例化时再求值:
      let uniqueToken = 0;
    
      function getUniqueKey(key) {
        return `${key}_${uniqueToken++}`;
      }
    
      let person3 = {
        [getUniqueKey(nameKey)]: 'Matt',
        [getUniqueKey(ageKey)]: 27,
        [getUniqueKey(jobKey)]: 'Software engineer'
      }
      person3; // {name_0: 'Matt', age_1: 27, job_2: 'Software engineer'}
    
    
    • 可计算属性表达式中抛出任何错误都会中断对象创建。如果计算属性的表达式有副作用,那就要小心了,因为如果表达式抛出错误,那么之前完成的计算是不能回滚的。
    1. 简写方法名
    • 在给对象定义方法时,通常都要写一个方法名、冒号,然后再引用一个匿名函数表达式。
    • 新的简写方法的语法遵循同样的模式,但开发者要放弃给函数表达式命名(不过给作为方法的函数命名通常没什么用)。相应地,这样也可以明显缩短方法声明。
      let person = {
        sayName: function(name) {
          // ...
        }
      }
    
      // 简写方法名
      let person = {
        sayName(name) {
          // ...
        }
      }
    
    • 简写方法名对获取函数和设置函数也是适用的。
    • 简写方法名与可计算属性键相互兼容:
      const methodKey = 'sayName';
    
      let person = {
        [methodKey](name) {
          console.log(`My name is ${name}`)
        }
      }
      person.sayName('Matt'); // My name is Matt
    
    
  • 对象解构

    • ES6新增对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地说就是,对象解构就是使用与对象匹配的结构来实现对象属性赋值。
      let personName, personAge;
    
      let person = {
        name: 'Matt',
        age: 27
      };
    
      let { name, age} = person; // age Matt; age: 27
    
    • 解构赋值不一定与对象的属性匹配。赋值的时候可以忽略某些属性,而如果引用的属性不存在,则该变量的值就是undefined。

    • 也可以在解构赋值的同时定义默认值,这适用于前面刚提到的引用的属性不存在于源对象中的情况。

    • 解构在内部使用函数ToObject()(不能在运行时环境中直接访问)把源数据结构转换成对象。这意味着在对象解构上下文中,原始值会被当成对象。也意味着null和undefined不能被解构,否则会抛出错误。

      let { length } = 'foobar'; // 6
    
      let { constructor: c} = 4;
      c === Number; // true
    
      let { _ } = null; // TypeError
    
      let { _ } = undefined; // TypeError
    
    • 解构并不要求变量必须在解构表达式中声明。不过,如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号:
      let personName, personAge;
    
      let person = {
        name: 'Matt',
        age: 27
      };
    
      (let { name: personName, age: personAge} = person);
    
      console.log(personName, personAge); // Matt, 27
    
    
    1. 嵌套解构
      1. 解构对于引用嵌套的属性或赋值目标没有限制。可以通过解构来复制对象属性。
      2. 在外层属性没有定义的情况下不能使用嵌套解构。无论源对象还是目标对象。
    2. 部分解构
      1. 需要注意的是,涉及多个属性的解构赋值是一个输出无关的顺序化操作。
      2. 如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分。
    3. 参数上下文匹配
      1. 在函数参数列表中也可以进行解构赋值。
      2. 对参数的解构赋值不会影响arguments对象,但可以在函数签名中声明在函数体内使用局部变量。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值