乐趣区

关于javascript:从mename-forceddd-开始

me.name = 'forceddd' 的赋值语句在 JavaScript 中是随处可见的。然而,咱们真的理解这句代码做了什么事件吗?是创立了一个新属性吗?是批改了原有属性的值吗?这次操作是胜利还是失败了呢?这只是一行简略的赋值语句,但如果咱们认真思考的话,就会发现这种细节其实也没有那么简略。

万物皆可分类探讨。首先分为两类状况:对象 me 上已有 name 属性和没有 name 属性。

当然,咱们都晓得,JavaScript 中存在着原型链机制,所以当对象 me 中不存在 name 属性时,也能够分为两种状况:me 的原型链上存在 name 属性与不存在 name 属性。也就是能够分为三类来探讨。

  1. me 中已存在 name 属性

    const me = {name: 'me',};
    me.name = 'forceddd';
    
    console.log(me.name); //forceddd

    在这种状况下显然咱们的目标是从新设置 name 属性的值,后果仿佛也是不言而喻的,me.name 被批改成了 'forceddd'

    然而不要遗记了,一个对象的属性也是领有它本人的属性的,其中就包含是否是只读的(writable),(通常用于形容一个对象的某个属性的各种个性的对象被称为 属性描述符 或者是 属性形容对象 ),所以当 name 属性的可写性(writable)为 false 时,后果会是什么样的呢?不仅如此,name 属性定义了 getter 或者 setter 函数,成为一个 拜访描述符 的时候,后果又会是怎么样的呢?

    • name 属性的 writablefalse

      const me = {name: 'me',};
      Object.defineProperty(me, 'name', {
          writable: false,
          value: me.name,
      });
      
      me.name = 'forceddd';
      // name 的属性值仍是 'me'
      console.log(me.name); //me

      因为 name 属性是只读的的,所以赋值操作 me.name = 'forceddd' 失败了,这恒河狸。然而要留神的是,这种操作失败是静默的,如果咱们在操作之后没有校验 name 值的话,是很难发现的。应用严格模式能够把这种静默失败变成显式的TypeError

      'use strict';// 应用严格模式
      const me = {name: 'me',};
      
      Object.defineProperty(me, 'name', {
          writable: false,
          value: me.name,
      });
      me.name = 'forceddd';
      //TypeError: Cannot assign to read only property 'name' of object '#<Object>'
    • name 属性是拜访描述符(定义了 getter 或者 setter)时

      const me = {
          _name: 'me',
          get name() {return this._name;},
          set name(v) {console.log('调用 setter,设置 name');
              this._name = v;
          },
      };
      
      me.name = 'forceddd';// 调用 setter,设置 name
      
      console.log(me.name);//forceddd

      此时,name 属性存在 setter 函数,在进行赋值操作时,就会调用 setter 函数。一般来说,咱们在定义拜访描述符时,getter setter 都是 成对呈现 的,然而只定义其中一个也是没有任何问题的,只是这样的话可能会呈现一些咱们不冀望的状况,比方,当 name 只定义了一个 getter 函数时,不存在 setter 函数,赋值操作便没有意义了。

      非严格模式 下,因为没有 setter 函数,所以赋值静默失败了。

      const me = {
          _name: 'me',
          get name() {return this._name;},
      };
      
      me.name = 'forceddd';
      
      console.log(me.name); //me

      严格模式 下,对没有 setter 函数的拜访描述符进行赋值操作,会呈现一个TypeError,也是十分河狸的。

      'use strict';
      const me = {
          _name: 'me',
          get name() {return this._name;},
      };
      
      me.name = 'forceddd';
      //TypeError: Cannot set property name of #<Object> which has only a getter

    总结一下,当 me 中存在 name 属性时,进行赋值操作时,存在三种状况:

    1. 当属性是一个 拜访描述符 时,如果存在 setter,则调用 setter;如果不存在 setter,在非严格模式下会静默失败,在严格模式下会产生一个TypeError
    2. 当属性的属性描述符中可写性(writable)为 false 时,在非严格模式下会静默失败,在严格模式下会产生一个TypeError
    3. 当属性不属于下面两种状况时,将值设为该属性的值,赋值胜利。
  2. me 及其原型链上均不存在 name 属性

    此时,会在对象 me 上新建一个属性 name,值为 'forceddd'。这是也是咱们常常应用的一种形式。

    const human = {};
    const me = {};
    Object.setPrototypeOf(me, human);
    me.name = 'forceddd';
    
    console.log({me, human}); //{me: { name: 'forceddd'}, human: {}}

    咱们应用这种形式创立的属性当然是能够通过赋值的形式批改的,这也就是代表着此时这个属性的属性描述符中的 writabletrue。事实上,此时属性的 可配置性 可枚举性 也都为 true,这和通过 defineProperty 增加属性的默认值是不同的,defineProperty 办法默认增加的属性描述符中 writableconfigurableenumerable 默认值都为 false。这也是值得注意的一点。

    console.log(Object.getOwnPropertyDescriptor(me, 'name'));
    // {
    //   value: 'forceddd',
    //   writable: true,
    //   enumerable: true,
    //   configurable: true
    // }

    通过 defineProperty 办法增加属性

    Object.defineProperty(me, 'prop', { value: 'forceddd'});
    console.log(Object.getOwnPropertyDescriptor(me, 'prop'));
    // {
    //   value: 'forceddd',
    //   writable: false,
    //   enumerable: false,
    //   configurable: false
    // }
  3. me 中不存在 name 属性,而其原型链上存在 name 属性

    此时,me 的原型对象 human 上存在 name 属性,很多时候,咱们应用 me.name = 'forceddd' 之类的赋值语句,只是为了批改对象 me,而不想牵涉到其余对象。然而在 JS 中, 拜访对象属性时,如果该属性在对象上不存在,便会去原型链上查找,所以对象的原型链是很难绕开的。

    const human = {};
    const me = {};
    Object.setPrototypeOf(me, human);

    如前文所述,name 属性在 human 对象上也是有三种状况:

    • name 属性的属性描述符中 writablefalse

      // 设置 human 对象的 name 属性
      Object.defineProperty(human, 'name', {
          writable: false,
          value: 'human',
      });
      me.name = 'forceddd';
      console.log(me);//{}

      此时,当咱们进行了赋值操作后,查看 me 对象,会发现它并没有 name 属性。WTF?很难了解是吧,因为 human 中的 name 属性是只读的,所以将 human 对象为原型的对象就不能通过 = 赋值操作新增 name 属性了。

      其实这是为了 模拟类的继承行为,如果父类中的 name 属性是只读的,那么继承它的子类中的 name 属性天然应该也是只读的。又因为在 JS 中时通过原型链来模仿的类的继承行为,所以就导致了这一看起来很奇怪的景象。

      同样地,如果是在 严格模式 下,不会是静默失败了,而是会产生一个TypeError

    • name 属性是一个 拜访描述符

      Object.defineProperty(human, 'name', {set() {console.log('调用了 human 的 setter');
          }
      });
      me.name = 'forceddd';//'调用了 human 的 setter'
      console.log(me);//{}

      此时,同样不会在 me 对象上创立 name 属性,而是会调用 humanname 属性的 setter 函数。相似地,当只存在 getter 不存在 setter 时, 严格模式 下会产生 TypeError 非严格模式 下会静默失败。

    • name 属性不是之前两种状况

      这种状况最简略,也是最合乎咱们料想的,会在 me 对象上创立一个 name 属性。

    总结一下:当 me 中不存在 name 属性,而其原型链上存在 name 属性时,有三种状况:

    1. 如果该属性在原型链上是一个 拜访描述符 并且存在 setter,则会调用这个 setter;如果只存在 getter,则非严格模式下会静默失败,严格模式下呈现TypeError
    2. 如果该属性在原型链是是一个 writablefalse 的只读属性,在非严格模式下会静默失败,严格模式下呈现TypeError
    3. 如果该属性在原型链上不是前两种状况,只是一个 writabletrue 的一般属性,则会在 me 对象上创立该属性。

如果你肯定、必须要批改或者设置 mename 属性的话,能够应用 defineProperty 办法来绕过 = 赋值的这些限度。

Object.defineProperty(human, 'name', {set() {console.log('调用了 human 的 setter');
        // me.name = 'forceddd';
    },
});
Object.defineProperty(me, 'name', {
    value: 'forceddd',
    enumerable: true,
    writable: true,
    configurable: true,
});
console.log(me); //{name: 'forceddd'}

文字上的形容总是没有那么直观,最初总结为一张图,便于了解。

退出移动版