JS笔记《Object对象》

125 阅读8分钟

概述

  • JS所有其他的对象都继承自Object对象,即那些对象都是Object的实例。

Object()

  • Object()方法用于将任意值转为对象。
Object(1)     // Number {1}
Object(true)  // Boolean {true}
Object("abc") // String {'abc'}

Object(null)      // {}
Object(undefined) // {}

var arr = [];
Object(arr) === arr;  // true
var obj = {};
Object(obj) === obj;  // true

如果参数是原始值(number/boolean/string),则会将其转为对应的包装对象的实例。
如果参数是引用值(object/array),则总是会返回该对象,不用转换。

判断数据是否为对象

function isObj(value){
  return value === Object(value);
}

isObj([])  // true   传入的是对象,直接返回该对象,本来就是一个对象,所以严格相等
isObj(1)   // false  传入的是原始值,会被转为对象,所以不严格相等

构造函数

  • Object可以当作构造函数使用,使用new命令。可通过它来创建一个新对象。
  • 通过字面量方式创建的对象等同于使用构造函数创建的对象,详见《数据类型—对象》一章。
var obj = new Object();
var obj = {};
// 这两种相同,只是后者更简便



var o = {a: 1};
var o2 = new Object(o);
o === o2  // true

var n = new Object(1);
n    // Number {1}

如果参数是原始值(number/boolean/string),则会将其转为对应的包装对象的实例。
如果参数是引用值(object/array),则总是会返回该对象,不用转换。

属性描述对象

  • JS提供了一个内部数据结构用来描述对象的属性,控制它的行为。比如该属性是否可写、可遍历等。每个属性都有自己对应的属性描述对象。
  • 属性描述对象的各个属性称为元属性,因为它们可以看作是控制对象属性的属性。元属性如下:

value 属性值

  • 该属性的属性值,默认为undefined
var obj = {};
obj.p = 123;

Object.getOwnPropertyDescriptor(obj, 'p').value  // 123  读取 p 的 value

Object.defineProperty(obj, 'p', {value: 234});   // 重新设置 p 的 value
obj.p  // 234

writable 可被改变

  • boolean值,表示该属性值是否可被改变,默认为true
var obj = {}
Object.defineProperty(obj, 'a', {
  value: 123,
  writable: false
});
obj.a  // 123
obj.a = 0   
obj.a  // 123  未改变

严格模式下给writablefalse的属性赋值会报错。

enumerable 可遍历

  • boolean值,表示该属性是否可遍历,默认为true。如果为false则会导致for in循环Object.keys()、JSON.stringify跳过该属性。
var obj = Object.defineProperty({}, 'a', {
  value: 123,
  enumerable: false
});
obj.a  // 123

for (const key in obj) {
  console.log(key)    // 空
}

Object.keys(obj)    // []  
JSON.stringify(obj) // '{}'

Object.getOwnPropertyNames(obj)  // ['a'] 不管属性是否可遍历都可以获取

configurable 可配置性

  • boolean值,表示该属性的可配置性(是否可以修改元属性),默认为true。如果为false,则writableenumerableconfigurable都不能被修改。
var obj = Object.defineProperty({}, 'a', {
    value: 123,
    writable: false,
    enumerable: false,
    configurable: false
});
obj.a  // 123

Object.defineProperty(obj, 'a', {writable: true})     // Uncaught TypeError: Cannot redefine property: a
// writable元属性只有从false改为true才会报错,从true改为false则允许

Object.defineProperty(obj, 'a', {enumerable: true})   // Uncaught TypeError: Cannot redefine property: a

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

修改value属性的情况比较特殊。只要writableconfigurable有一个为true,就允许改动value
configurable决定了目标属性是否可以被删除(delete)。

set get 存取器

  • 除了直接定义value外,属性还可以用存取器定义getter/setter,一旦定义了存取器,那么存取的时候都将执行对应的函数。
// 写法一
var obj = Object.defineProperty({}, 'a', {
  get: function(){
    return 'getter'
  },
  set: function(value){
    console.log('setter' + value);
  }
});
obj.a      // 'getter'   读取时执行的是get函数返回值
obj.a = 1  // 'setter1'  赋值时执行的是get函数


// 写法二(推荐)
var obj = {
  get p() {
    return 'getter';
  },
  set p(value) {
    console.log('setter: ' + value);
  }
};

写法一属性aconfigurableenumerable都为false,从而导致属性a不可遍历。
写法二属性aconfigurableenumerable都为true,因此属性a可遍历。

静态方法

Object.keys(obj)

  • 用来遍历对象的属性,返回一个数组。该数组的成员都是该对象自身的(不包括继承的)所有可遍历属性名
var obj = {
  a: 1,
  b: 2,
}
Object.keys(obj);   //  ['a', 'b']

Object.getOwnPropertyNames(obj)

  • 用来遍历对象的属性,返回一个数组。该数组的成员都是该对象自身的(不包括继承的)所有属性名(包括不可遍历的)。
var obj = {
  a: 1,
  b: 2,
}
Object.getOwnPropertyNames(obj);  //  ['a', 'b']

Object.getOwnPropertyDescriptor(obj, prop)

    • 获取对象某个属性的属性描述对象,只能获取对象自身属性,不能获取继承的属性。
var obj = {a: 1};
Object.getOwnPropertyDescriptor(obj, 'a')   
// {value: 1, writable: true, enumerable: true, configurable: true}

Object.getOwnPropertyDescriptors(obj)

  • 获取对象所有属性的属性描述对象,只能获取对象自身属性,不能获取继承的属性。
var obj2 = {a: 1, b: 1}
Object.getOwnPropertyDescriptors(obj)
// {
//   a: {value: 1, writable: true, enumerable: true, configurable: true}
//   b: {value: 1, writable: true, enumerable: true, configurable: true}
// }

Object.defineProperty(obj, prop, propDescObj)

  • 通过属性描述对象定义或修改一个属性,返回修改后的对象。
var obj = Object.defineProperty({}, 'p', {
  value: 123,
  writable: true,
  enumerable: true,
  configurable: true
});

obj.p // 123

Object.defineProperties(obj, {prop: {propDescObj}})

  • 通过属性描述对象定义或修改多个属性,返回修改后的对象。
var obj = Object.defineProperties({}, {
  a: {
    value: 1,
    writable: true,
  },
  b: {
    value: 2,
    enumerable: false
  },
  c: {
    get: function(){
      return 3
    }
  }
})
obj    // {a: 1, b: 2}
obj.c  // 3

上述两个方法注意项:

  1. 如果要定义或修改的属性已存在,则相当于更新该属性的属性描述对象。
  2. 定义了get()set()时,不能将writable属性设为true或者同时定义value属性,否则会报错。
  3. writableconfigurableenumerable这三个属性的默认值都为false

Object.preventExtensions(obj)

  • 冻结!使一个对象无法再添加新的属性。
var obj = {};
Object.preventExtensions(obj);

Object.defineProperty(obj, 'p', {
  value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.

obj.p = 1;   // 这种赋值不会报错,但也赋值不上
obj.p        // undefined 

可修改冻结前的原有属性,除非原属性的writablefalse

Object.isExtensible()

  • 是否可以为一个对象添加属性,返回boolean。使用了Object.preventExtensions()方法返回false,未使用返回true
var obj = {}
Object.isExtensible(obj); // true 可以添加属性

Object.preventExtensions(obj);
Object.isExtensible(obj); // false 不可以添加属性

Object.seal()

  • 冻结!使一个对象既无法添加新属性也无法删除旧属性,本质是把属性描述对象的configurable属性设为false
var obj = {a: 1};
Object.seal(obj);

delete obj.a  // false 无法删除
obj.a         // 1

obj.b = 2
obj.b   // undefined  无法添加新属性

Object.isSealed(obj)

  • 检查一个对象是否使用了Object.seal()
var obj = {a: 1};
Object.isSealed(obj);  // false

Object.seal(obj);
Object.isSealed(obj);  // true
Object.isExtensible(obj); // false 不可以给对象添加新属性

可修改冻结前的原有属性,除非原属性的writablefalse

Object.freeze(obj)

  • 冻结!使一个对象无法添加新属性、无法删除旧属性、无法修改属性值,变成了一个常量
var obj = {a: 1};
Object.freeze(obj);

obj.a = 2;
obj.a   // 1  无法修改
obj.b = 2;
obj.b   // undefined 无法添加
delete obj.a;  // false 无法删除
obj     // {a: 1}

修改、添加、删除操作在严格模式下会报错。

Object.isFrozen(obj)

  • 检查一个对象是否使用了Object.isFrozen()
var obj = {
  p: 'hello'
};

Object.freeze(obj);

Object.isFrozen(obj) // true  已被冻结
Object.isSealed(obj) // true  已被冻结
Object.isExtensible(obj) // false  不可以添加新属性

实例方法

  • 除了静态方法还有许多方法定义在Object.prototype对象上,它们称为实例方法,所有Object的实例对象都继承了这些方法。

Object.prototype.valueOf()

  • 返回一个对象的,默认情况下返回对象本身。
var obj = new Object();
obj.valueOf() === obj;    // true
  • valueOf()方法的主要用途是JS自动类型转换时会默认调用。
var obj = {};
1 + obj  // 1[object Object]

// 重写valueOf
Object.prototype.valueOf = function(){
   return 2;
}
var obj2 = {};
1 + obj2 // 3

Object.prototype.toString()

  • 返回一个对象的字符串形式,默认情况下返回类型字符串。
 var obj = {};
 obj.toString()  // '[object Object]'
  • 通过自定义toString方法,可以让对象在自动类型转换时得到想要的字符串。
Object.prototype.toString = function(){
  return 'hello world!'
}

var obj = {};
obj.toString()  // 'hello world!'
  • 数组、字符串、函数、Date都分别重写了Object.prototype.toString
[1,2,3].toString()  // '1,2,3'
'123'.toString()    // '123'
(function test(){return 1}).toString()  // '(function test(){return 1}).toString()' 
                                        // 首尾添加()是为了变成表达式
(new Date()).toString()  // 'Sat Feb 17 2024 19:45:19 GMT+0800 (中国标准时间)'

toString()的应用

  • 此方法会返回对象的类型字符串,因此可以用来判断一个值的数据类型。但由于实例对象有可能会重写此方法,所以通过call方法直接调用。
Object.prototype.toString.call(2)         // "[object Number]"
Object.prototype.toString.call('')        // "[object String]"
Object.prototype.toString.call(true)      // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null)      // "[object Null]"
Object.prototype.toString.call(Math)      // "[object Math]"
Object.prototype.toString.call({})        // "[object Object]"
Object.prototype.toString.call([])        // "[object Array]"
  • 封装一个判断值的数据类型的方法。
var getValueType = function (value){
  var str = Object.prototype.toString.call(value);
  return str.match(/\[object (.*?)\]/)[1].toLowerCase();
};

getValueType(1);  // 'number'
getValueType({}); // 'object'
getValueType([]); // 'array'
getValueType(function(){})  // 'function'

Object.prototype.toLocaleString() (不完整)

  • 返回一个值的类型字符串,与toString()返回结果相同。
var obj = {};
obj.toString(obj) // "[object Object]"
obj.toLocaleString(obj) // "[object Object]"
  • 主要作用是留出一个接口,让各种不同的对象实现自己版本的toLocaleString(),用来返回针对某些地域的特定的值。
  • 目前有三个对象重写了toLocaleString方法:Array、Number、Date
var date = new Date();
date.toString()        // 'Sat Feb 17 2024 20:03:23 GMT+0800 (中国标准时间)'
date.toLocaleString()  // '2024/2/17 20:03:23'

Object.prototype.hasOwnProperty(prop)

  • 返回一个boolean,判断该实例对象自身(不包括继承的)是否有该属性
var obj = {a: 1};

obj.hasOwnProperty('a') // true
obj.hasOwnProperty('b') // false
obj.hasOwnProperty('toString')  // false  继承自Object.prototype

Object.prototype.propertyIsEnumerable()

  • 返回一个boolean,判断某个属性是否可遍历。只能判断对象自身属性,继承的属性一律返回false
var obj = Object.defineProperty({}, 'a', {
    value: 1,
    enumerable: false
})

obj.propertyIsEnumerable('a')        // false
obj.propertyIsEnumerable('toString') // false 继承的属性

冻结方法的局限

  • 局限一:可以通过改变原型对象来为对象增加属性,其中一种解决方案是将原型也给冻结。
var obj = new Object();
Object.preventExtensions(obj);

var proto = Object.getPrototypeOf(obj);
proto.a = 1;
obj.a   // 1 原型上新增了属性

// 冻结原型
Object.preventExtensions(proto);
proto.b = 1;
proto.b  // undefined  无法新增
  • 局限二:如果属性值是对象,那么只能冻结对象的内存地址,而不能冻结对象本身的内容。
var obj = {
    arr: [1,2,3]
}
Object.freeze(obj);

obj.arr.push(4);
obj.arr // [1, 2, 3, 4] 添加成功,无法冻结对象本身内容