JavaScript之基础对象

163 阅读5分钟

在JavaScript类型中 string 、number、undefined、null、boolean、Symbol 、object 中。object 最为特殊,它是一个概括性的类型【包含null(这个原因是因为typeof 读取二进制表达造成的现象),Array、Function 等】,对于 object 的定义内部存储key-value的方式组织的【内部的 value 是对应变量或结构化类型的存储地址,也就是引用】。也可以说 object 是存储了字符串类型的key(属性名)【用以区别不同的属性】,以及属性名对应值所在的存储地址(引用)。

为什么 typeof null 得到的结果是 'object' ? 注:原理是这样的,不同的对象在底层都表示为二进制,在JavaScript 中二进制前三位都为0 的话会被判断为object 类型,null 的二进制表示是全0,自然前三位也是0,所以执行typeof 时会返回“object”。

JavaScript 内置对象包括 Object、String、Number、Boolean、Array、Date、RegExp 等。其中 Array 数组是结构化的数据类型。索引的类型限制是可转化成 number 的字符或者整形数字。如果是非整形数字,那么转化后会是变成属性而不处理成数字类型的索引【变成属性存储,length 不会被修改】,如果是字符串并且可以转换成整形数字的,则可修改数组中对应索引的值,如果当前没有值则会改变数组长度。如果 [] 操作符内部读取到的是引用类型则会根据 toPrimitive【区别是有的对象调用的是 valueOf 还是toString】 规则转化成 string 。

引用类型 => string => number 特殊的数组:


let arr = [];

arr[1] = 123;
console.log(arr, arr.length,); // [ <1 empty item>, 123 ] 2

// 增加浮点索引
arr[1.01] = 1.01;  

console.log(arr, arr.length,); // [ <1 empty item>, 123, '1.01': 1.01 ] 2

arr['abc'] = 'abc';

console.log(arr,arr.length,); // [ <1 empty item>, 123, '1.01': 1.01, abc: 'abc' ] 2

// 增加一个数字类型下标
arr['2'] = 123; 
console.log(arr, arr.length,); // [ <1 empty item>, 123, 123, '1.01': 1.01, abc: 'abc' ] 3

// 引用类型索引
let a = [3];

arr[a] = 'this index is other Array value';
console.log(arr, arr.length);
// [
//   <1 empty item>,
//   123,
//   123,
//   'this index is other Array value',
//   '1.01': 1.01,
//   abc: 'abc'
// ] 4

对象的复制,如果是 JSON 安全的,可以通过

let copyData = JSON.parse(JSON.stringify(data));

console.log(data===copyData, copyData); 
// false { a: 123, b: 'xxx', c: [ 1, 2, 3 ] }

对象的不变性

对于对象本身或者对象属性,JavaScript 提供一些能力支持实现不变性【不可以被改变】。对于对象的本身定义来说这种不变性都是浅层的,因为对象的内容存储的都是引用,js 提供的能力都只支持当前层次的限定,如果需要对对象进行深度的不可变限定,需要递归的针对每一层进行不可变限定【深层的不可变性限定,可能造成其他共享的引用类型对象被额外限制】。主要方法如下:

一、属性修饰符限定对象不变

对象属性有四个 value、writeable、configurable、enumerable 属性描述符。可以通过 Object.getOwnPropertyDescriptors api 查询。

let abj = {
  a: 123,
};

console.log(Object.getOwnPropertyDescriptors(abj, 'a'));
// {
//   a: { 
// 				value: 123, 
//				writable: true, 
//        enumerable: true, 
//        configurable: true 
// 		}
// }

enumerable 设置成 false 则不支持在迭代器中枚举该属性 for .. in 中迭代时会过滤掉 enumerable === false 的属性,操作符 . 和 [ ] 都是可以访问到属性的只是不支持枚举。 writeable 用于限定修改,writeable === false 时,不允许修改属性值的内容,严格模式下修改会抛出 TypeError。

configurable 用于限定对属性的是否支持配置,只要 configurable === true 就可以用 Object.defineProperty() 来进行修改属性修饰符和删除属性【delete】。 如果 configurable 值为false 则不可以在修改属性修饰符,可以赋值、取值,但是不能进行删除操作使用 delete 和 Object.defineProperty 均会沉默【silently error】 严格模式会抛错 TypeError 。所以使用 Object.defineProperty() 修改 configurable 为false 是个单向操作。

因此只要设置 writeable === false、configurable === false 。就能将对象的属性变成不可变形,对对象上的所有属性进行修饰,则会得到一个真实意义上的常量对象【因为是浅层的不可变性,如果属性是引用类型的,下一层开始则管不住了】。

二、禁止扩展,禁止增加其他属性

Object.preventExtentions(obj) 用于限制对象增加其他属性,使用 Object.preventExtentions 限制扩展后,增加属性默认失败,严格模式会抛出 TypeError 错误。

let obj1 = {
  name: 'xxx'
};

Object.preventExtensions(obj1);
obj1.a = 111;

console.log(obj1) // { name: 'xxx' }

三、Object.seal 封存对象

Object.seal 主要用来封存一个对象,已封存的对象禁止对属性进行扩展,已有属性仅支持修改禁止删除和修改属性修饰符。调用Object.preventExtentions 的基础上,将每个属性的 configurable 设置为 false 来禁止删除和禁止修改属性修饰符。注意:seal 禁止的是属性维度的变化,属性值的修改不受影响。

四、Object.freeze 冻结对象

Object.freeze 针对对象最高层的进行不可变形,仍然是浅层的不可变形。冻结的对象不可扩展属性、不可删除属性、不能修改属性内容【对象最高层面的,深层的不受影响】。实际上是在 Object.seal 的基础上,将每个属性 writeable 设置为 false ,来禁止对属性内容的修改。

对象的遍历

遍历的核心是迭代器,常见的是对数组进行遍历。Array 是对象的子类型,对象本身也支持遍历。for .. in 是遍历的是属性名,会把原型链上的所有可枚举的属性都遍历出来。

let obj11 = {
  a: 'a1',
  b: 'b1',
  c: 'c1',
};

Object.defineProperty(obj11, 'a', {
  enumerable: false
})

obj11.__proto__.aa = 234;

for ( let key in obj11 ) {
  console.log(key, obj11[key]);
}

// b b1
// c c1
// aa 234

也可以借助迭代器自定义迭代的内容和顺序(后续专门搞一篇文章)。