原型链上的属性是否可以被修改?

559 阅读2分钟

背景

近几天在读《你不知道的 Javascript》,读到一个之前理解有点片面的知识点:原型链上的属性是否可以被修改?

我之前的理解是:会在对象本地创建这个属性并覆盖原型链上的属性。 读到书中相关章节发现,这个理解不够全面,还需要考虑原型链上属性的设置,具体整理如下:

修改原型链属性的几种情况

  1. 原型链属性 writable = true

    这种情况是最常见的,也是我之前理解的那样。看示例:

    const foo = {
      count: 1,
    }
    const far = Object.create(foo)
    
    console.log(far.count)  // 1
    console.log(Object.getOwnPropertyNames(far))  // []
    far.count = 2
    console.log(foo.count)  // 1
    console.log(far.count)  // 2
    console.log(Object.getOwnPropertyNames(far))  // [ 'count' ]
    

    总结一下:读的时候,读取的原型链上的属性;修改的时候会在本对象上定义一个同名属性,并且屏蔽掉原型链上的属性。

  2. 原型链属性 writable = false

    这种情况,大多数人估计也没碰到(我就是),直接看示例:

    const foo = {
      count: 1,
    }
    // 这里修改了 writable 配置
    Object.defineProperty(foo, 'count', {
      writable: false,
    })
    const far = Object.create(foo)
    
    console.log(far.count)  // 1
    console.log(Object.getOwnPropertyNames(far))  // []
    far.count = 2
    console.log(foo.count)  // 1
    console.log(far.count)  // 1
    console.log(Object.getOwnPropertyNames(far))  // []
    

    可以看到,修改是没有生效的!

    ⚠️ 另外请注意,如果在严格模式 'use strict'; 下,上面的代码还会报错:TypeError: Cannot assign to read only property 'count' of object '#<Object>’.

    那有解吗?我就是需要在新对象中有一个可修改的同名属性可以吗?答案是可以,使用Object.definePropertyfar 对象上面定一个 count 属性即可。

    💡 上述特性确实令人奇怪,我的理解是 Javascript 语言是在使用过程中不断修订完善,所以会出现一些有点不太符合预期的特性。
  3. 原型链属性是一个 setter

    这种情况也是很少碰到,设置了 settergetter 可能原型对象上并没有这个属性,但是可以读取和设置。看示例:

    const foo = {
      count: 1,
      set rule(val) {
        console.log(`set rule ${val}`)
      }
    }
    Object.defineProperty(foo, 'count', {
      writable: false,
    })
    const far = Object.create(foo)
    
    far.rule = 'limit 1'  // set rule limit 1
    console.log(far.rule) // undefined
    

    上面这个只设置了 setter 没有设置 getter ,可以看出来,并没有在本对象上面定义同名属性去覆盖。

    最后,我再补充说明一下,上述三种情况,属性都是基本类型,如果这个属性是对象会怎么样?

    其实对象属性本身存的是一个引用,可以看成指针,指针存储的就是一个地址,类比一下:c 语言里面就是长整形。修改引用本身就是修改一个长整形。

    修改这个对象的属性呢?那就看这个对象的这个属性的配置了,依照上面的三种情况判断即可。