对象 - 你不知道的 JS

  1. 内置“对象”
  2. 对象的属性
    1. 属性与函数
    2. 属性描述符
      1. writable
      2. configurable
      3. enumerable
    3. 维持对象现状
      1. 禁止扩展
      2. 更进一步 - 密封
      3. 更更进一步

些内置对象从表现形式来说很像其他语言中的类型(type)或者类(class),比如 Java 中的 String 类。

内置“对象”

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

这些内置对象从表现形式来说,很像 Java 中的 String 类。
然而,在 JS 中,它们实际上只是内置 ** 函数 **。
这些内置函数可以当作构造函数,由 new 操作符使用,用来创建对应的基本数据类型的新对象。

一段代码:

var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true

原始值 “I am a string” 并不是一个对象,它只是一个字面量,并且是一个不可变的值。
如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为 String 对象。
幸好,在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要显式创建一个对象。比如:

var strPrimitive = "I am a string";
console.log( strPrimitive.length ); // 13
console.log( strPrimitive.charAt( 3 ) ); // "m"

数值类型和布尔类型也是如此

2..toFixed(2) => "2.00"
3.1315926.toFixed(4) => "3.1416"
  • Date 只有构造形式,没有字面量形式。
  • Object,Array,Function,RegExp 无论是字面量形式还是构造形式,都是对象。
  • Error 对象一般是抛出异常时被自动创建

对象的属性

当我们说对象存储着 “属性” 时,似乎在暗示这些属性被存储在对象内部,但是这只是它的表现形式。
在引擎内部,这些属性的值的存储方式是多种多样的,一般并不会直接存在对象容器内部。
存储在对象容器内部的是这些属性的名称,它们就像 ** 指针 **(从技术角度来说就是引用)一样,指向这些值真正的存储位置。

属性与函数

由于函数很容易被认为是属于某个对象,在其他语言比如 Java 中,属于对象(也被称为“类”)的函数通常
被称为 “方法”,因此把“属性访问” 说成是 “方法访问” 也就不奇怪了。

从技术角度,JavaScript 的函数永远不会 “属于” 一个对象。有些函数具有 this 引用,有时候这些 this 确实会指向调用位置的对象引用。但是这种用法从本质上来说并没有把一个函数变成一个“方法”,因为 this 是在运行时根据调用位置动态绑定的,所以函数和对象的关系最多也只能说是间接关系。

通过对象的属性访问方式而返回的函数和其他函数没有任何区别(除了可能发生的隐式绑定 this,就像我们刚才提到的)。

## 数组的属性

如果你试图向数组添加一个属性,但是属性名 “看起来” 像一个数字,那它会变成
一个数值下标(因此会修改数组的元素而不是添加一个属性):

let a = [1,3];
a["1"] = 666;
a => [1,666]

属性描述符

ES5 开始,所有的属性具备属性描述符。

var myObject = {
a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
输出
{
    value: 2,
    writable: true,
    enumerable: true,
    configurable: true
}

在创建普通属性时属性描述符会使用默认值,我们也可以使用 Object.defineProperty(..) 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置

writable

var myObject = {};
Object.defineProperty( myObject, "a", {
    value: 2,
    writable: false, // 不可写!
    configurable: true,
    enumerable: true
} );
myObject.a = 3;
myObject.a; // 2

如图,我们对于属性值的修改失败了,并且是静默的。(严格模式才会报错)

简单来说,你可以把 writable:false 看作是属性不可改变,相当于你定义了一个空操作 setter

configurable

属性是可配置的,才可以使用 defineProperty(..) 方法来修改属性描述符。
细节,一旦可配置设为 false,由于你不能再使用 defineProperty(..),所以这是个单向操作。delete 无法删除这个属性。

 let obj = {
    a: 2
}

Object.defineProperty(obj, 'a', {
    configurable: false
})

console.log(obj.a);
delete obj.a  // delete 无法删除
console.log(obj.a);

// 报错:TypeError: Cannot redefine property: a
Object.defineProperty(obj, 'a', {
    configurable: true
})

enumerable

这个描述符控制的是属性是否会出现在对象的属性枚举时。for in 就是属性枚举。

维持对象现状

禁止扩展

禁止一个对象添加新属性并且保留已有属性 Object.preventExtensions(对象)

let obj = {
    a: 9
}
Object.preventExtensions(obj);
obj.b = 23;
obj.b // undefined

非严格模式,静默失败,严格模式,TypeError

更进一步 - 密封

Object.seal(obj)

密封后,无法删除属性,无法重新设置属性描述符

let obj2 = {
    a: 123,
}

Object.seal(obj2)

obj2.b = 456;
console.log('obj2.b:', obj2.b);
delete obj2.a;  // 密封后,不能删除已有属性
console.log('obj2.a:', obj2.a);
Object.defineProperty(obj2, "a", {
    writable: false
})
// 密封后,不能重新配置
console.log(Object.getOwnPropertyDescriptor(obj2, "a"));

更更进一步

Object.freeze(obj)

在密封的基础上,对所有对象的属性标记为 writable:false
这个方法是应用在对象上的级别最高的不可变性,它会进制对于对象本身及其任意直接属性的修改(当然,这个对象引用的其他对象不受影响)。
所以,这个冻结是“浅冻结”。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间