Object常用方法

525 阅读12分钟

Object.keys

  • 返回一个由一个给定对象的自身可枚举属性组成的数组,所有元素为字符串的数组
  • 数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。
  • 如果对象的键-值都不可枚举,那么将返回由键组成的数组。
    var arr = ['a', 'b', 'c'];
    console.log(Object.keys(arr)); // console: ['0', '1', '2']
    
    var obj = { 0: 'a', 1: 'b', 2: 'c' };
    console.log(Object.keys(obj)); // console: ['0', '1', '2']
    
    var anObj = { 100: 'a', 2: 'b', 7: 'c' };
    console.log(Object.keys(anObj)); // console: ['2', '7', '100']
    
    var myObj = Object.create({}, {
      getFoo: {
        value: function () { return this.foo; }
      } 
    });
    myObj.foo = 1;
    console.log(Object.keys(myObj)); // console: ['foo']
    在ES5里,如果此方法的参数不是对象(而是一个原始值),那么它会抛出 TypeError。在ES2015中,非对象的参数将被强制转换为一个对象。
    
    Object.keys("foo");
    // TypeError: "foo" is not an object (ES5 code)
     
    Object.keys("foo");
    // ["0", "1", "2"]               

Object.defineProperty()

Object.defineProperty(obj, prop, descriptor):

  • 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

     obj:要在其上定义属性的对象。
     prop:要定义或修改的属性名称。
     descriptor:将被定义或修改的属性描述符
    
  • 返回值:被传递给函数的对象。

  • 通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。

  • 默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。

举例:首先定义一个对象

const obj = {
    firstName: 'A',
    lastName: 'B'
}

给这个对象添加一个fullName属性,fullName的值为obj.firstName-obj.lastName

Object.defineProperty(obj, 'fullName', {
    // 访问描述符
    // 当读取对象此属性值时自动调用, 将函数返回的值作为属性值, this为obj
    get () {
        return this.firstName + '-' + this.lastName
    },
    // 当修改了对象的当前属性值时自动调用, 监视当前属性值的变化, 修改相关的属性, this为obj
    set (value) {
        const names = value.split('-')
        this.firstName = names[0]
        this.lastName = names[1]
    }
})
console.log(obj.fullName) // A-B

属性描述符

  • 对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。
  • 数据描述符:是一个具有值的属性,该值可能是可写的,也可能不是可写的。
  • 存取描述符:是由getter-setter函数对描述的属性。
  • 描述符必须是这两种形式之一;不能同时是两者。

数据描述符和存取描述符均具有以下可选键值

  • configurable:当且仅当该属性的 configurabletrue 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false
  • enumerable:当且仅当该属性的 enumerabletrue 时,该属性才能够出现在对象的枚举属性中。默认为 false

数据描述符同时具有以下可选键值:

  • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
  • writable:当且仅当该属性的 writabletrue 时,value 才能被赋值运算符改变。默认为 false

存取描述符同时具有以下可选键值:

  • get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。 当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入 this 对象 (由于继承关系,这里的 this 并不一定是定义该属性的对象)。默认为 undefined
  • set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。 当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined

描述符可同时具有的键值

  • 如果一个描述符不具有value,writable,getset 任意一个关键字,那么它将被认为是一个数据描述符。
  • 如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。

创建属性

  • 如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。
  • 当描述符中省略某些字段时,这些字段将使用它们的默认值。
  • 拥有布尔值的字段的默认值都是falsevaluegetset 字段的默认值为 undefined
  • 一个没有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数据描述符
    var o = {}; // 创建一个新对象
     
    // 在对象中添加一个属性与数据描述符的示例
    Object.defineProperty(o, "a", {
      value : 37,
      writable : true,
      enumerable : true,
      configurable : true
    });
     
    // 对象o拥有了属性a,值为37
     
    // 在对象中添加一个属性与存取描述符的示例
    var bValue;
    Object.defineProperty(o, "b", {
      get : function(){
        return bValue;
      },
      set : function(newValue){
        bValue = newValue;
      },
      enumerable : true,
      configurable : true
    });
     
    o.b = 38;
    // 对象o拥有了属性b,值为38
     
    // o.b的值现在总是与bValue相同,除非重新定义o.b
     
    // 数据描述符和存取描述符不能混合使用
    Object.defineProperty(o, "conflict", {
      value: 0x9f91102, 
      get: function() { 
        return 0xdeadbeef; 
      } 
    });
    // throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors

修改属性

  • 如果属性已经存在,Object.defineProperty() 将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
  • 如果旧描述符将其 configurable 属性设置为 false,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writable 为 false)。
  • 当属性不可配置时,不能在数据和访问器属性类型之间切换。
  • 当试图改变不可配置属性(除了valuewritable 属性之外)的值时会抛出TypeError,除非当前值和新值相同。
  • Writable 属性:当writable属性设置为false时,该属性被称为“不可写”。它不能被重新分配。

如示例所示,试图写入非可写属性不会改变它,也不会引发错误。

var o = {}; // Creates a new object
 
Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});
 
console.log(o.a); // 37
o.a = 25; // 不会报错,但是修改不会生效
console.log(o.a); // logs 37
 
// strict mode 严格模式会报错
(function() {
  'use strict';
  var o = {};
  Object.defineProperty(o, 'b', {
    value: 2,
    writable: false
  });
  o.b = 3; // throws TypeError: "b" is read-only
  return o.b; // returns 2 without the line above
}());

Enumerable 特性

enumerable定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

var o = {};
Object.defineProperty(o, "a", { value : 1, enumerable:true });
Object.defineProperty(o, "b", { value : 2, enumerable:false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable defaults to false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则这个属性的enumerable为true
 
for (var i in o) {    
  console.log(i);  
}
// 打印 'a' 和 'd' (in undefined order)
 
Object.keys(o); // ["a", "d"]
 
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false

Configurable 特性:

  • configurable 特性表示对象的属性是否可以被删除,以及除valuewritable特性外的其他特性是否可以被修改。
  • 如果 o.aconfigurable属性为 true,则不会抛出任何错误,并且该属性将在最后被删除。
    var o = {}
    Object.defineProperty(o, 'a', {
        get: function () {
            return 1
        },
        configurable: false
    })
    
    Object.defineProperty(o, 'a', {configurable: true}) // throws a TypeError
    Object.defineProperty(o, 'a', {enumerable: true}) // throws a TypeError
    Object.defineProperty(o, 'a', {value: 12}) // throws a TypeError
    Object.defineProperty(o, 'a', { // throws a TypeError (set was undefined previously) 
        set: function () {
        }
    })
    Object.defineProperty(o, 'a', { // throws a TypeError (even though the new get does exactly the same thing) 
        get: function () {
            return 1
        }
    })
    
    console.log(o.a) // 1
    delete o.a // false
    console.log(o.a) // 1

添加多个属性和默认值:考虑特性被赋予的默认特性值非常重要,通常,使用点运算符和Object.defineProperty()为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示。

var o = {};
 
o.a = 1;
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : true,
  configurable : true,
  enumerable : true
});
 
 
// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : false,
  configurable : false,
  enumerable : false
});

一般的 SettersGetters:下面的例子展示了如何实现一个自存档对象。 当设置 temperature 属性时,archive 数组会获取日志条目。

function Archiver() {
  var temperature = null;
  var archive = [];
 
  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });
 
  this.getArchive = function() { return archive; };
}
 
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
或

var pattern = {
    get: function () {
        return 'I alway return this string,whatever you have assigned';
    },
    set: function () {
        this.myname = 'this is my name string';
    }
};
 
 
function TestDefineSetAndGet() {
    Object.defineProperty(this, 'myproperty', pattern);
}
 
 
var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';
 
 
console.log(instance.myproperty); // 'I alway return this string,whatever you have assigned'
console.log(instance.myname); // 'this is my name string'

继承属性:如果访问者的属性是被继承的,它的 get 和set 方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。

function myclass() {
}
 
var value;
Object.defineProperty(myclass.prototype, "x", {
  get() {
    return value;
  },
  set(x) {
    value = x;
  }
});
 
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1

这可以通过将值存储在另一个属性中解决。在 get 和 set 方法中,this 指向某个被访问和修改属性的对象。

function myclass() {
}
 
Object.defineProperty(myclass.prototype, "x", {
  get() {
    return this.stored_x;
  },
  set(x) {
    this.stored_x = x;
  }
});
 
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined

不像访问者属性,值属性始终在对象自身上设置,而不是一个原型。然而,如果一个不可写的属性被继承,它仍然可以防止修改对象的属性。

function myclass() {
}
 
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
  writable: false,
  value: 1
});
 
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1

Object.create

  • 创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

  • 语法:Object.create(proto, propertiesObject)

      proto:新创建对象的原型对象。
      propertiesObject:可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)
      对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
      如果propertiesObject参数是 null 或非原始包装对象,则抛出一个 TypeError 异常。
    
  • 返回值:一个新对象,带着指定的原型对象和属性。

  • 1.方法内部定义一个新的空对象 obj

  • 2.将 obj._proto__的对象指向传入的参数 proto

  • 3.返回一个新的对象

Object.create() 和 new Object() 的不同

new Object()方式创建

var newObj = {
    name: 'fx',
    why: {
        day: 1
    }
}
var b = new Object(newObj)
b.name = 'bfx'
b.why = {
    bday: 'b'
}
console.log('b:', b)
console.log('newObj:', newObj)

输出如下

Object.create()方式创建

var newObj = {
    name: 'fx',
    why: {
        day: 1
    }
}
var b = Object.create(newObj)
console.log(b)
b.name = 'bfx'
b.why = {
    bday: 'b'
}
console.log('b:', b)
console.log('newObj:', newObj)

输出如下

例:

// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
o = Object.create({}, { p: { value: 42 } })
 
// 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的:
o.p = 24 
console.log(o.p) // 42
 
o.q = 12
for (var prop in o) {
   console.log(prop)
}
//"q"
 
delete o.p // false
delete o.q // true
 
//创建一个可写的,可枚举的,可配置的属性p
o2 = Object.create({}, {
  p: {
    value: 42, 
    writable: true,
    enumerable: true,
    configurable: true 
  } 
});
用 Object.create实现类式继承

// Shape - 父类(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}
 
// 父类的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};
 
// Rectangle - 子类(subclass)
function Rectangle() {
  Shape.call(this); // call super constructor.
}
 
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
 
var rect = new Rectangle();
 
console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?', rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
如果你希望能继承到多个对象,则可以使用混入的方式。

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}
 
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
 
MyClass.prototype.myMethod = function() {
     // do a thing
};
Object.assign 会把  OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。

var o;
 
// 创建一个原型为null的空对象
o = Object.create(null);
 
 
o = {};
// 以字面量方式创建的空对象就相当于:
o = Object.create(Object.prototype);
 
 
o = Object.create(Object.prototype, {
  // foo会成为所创建对象的数据属性
  foo: { 
    writable:true,
    configurable:true,
    value: "hello" 
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});
 
 
function Constructor(){}
o = new Constructor();
// 上面的一句就相当于:
o = Object.create(Constructor.prototype);
// 当然,如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码

propertyIsEnumerable (属性是否可枚举)

  • 返回一个布尔值,表示指定的属性是否可枚举。
  • 语法 obj.propertyIsEnumerable(prop)
  • 每个对象都有一个 propertyIsEnumerable 方法。此方法可以确定对象中指定的属性是否可以被 for...in 循环枚举,但是通过原型链继承的属性除外。如果对象没有指定的属性,则此方法返回 false
    var o = {};
    var a = [];
    o.prop = 'is enumerable';
    a[0] = 'is enumerable';
    
    o.propertyIsEnumerable('prop'); // 返回 true
    a.propertyIsEnumerable(0);      // 返回 true
    ===
    var a = ['is enumerable'];
    
    a.propertyIsEnumerable(0);        // 返回 true
    a.propertyIsEnumerable('length'); // 返回 false
    
    Math.propertyIsEnumerable('random'); // 返回 false
    this.propertyIsEnumerable('Math');   // 返回 false

自身属性和继承属性

var a = [];
a.propertyIsEnumerable('constructor'); // 返回 false

function firstConstructor() {
  this.property = 'is not enumerable';
}

firstConstructor.prototype.firstMethod = function() {};

function secondConstructor() {
  this.method = function method() { return 'is enumerable'; };
}

secondConstructor.prototype = new firstConstructor;
secondConstructor.prototype.constructor = secondConstructor;

var o = new secondConstructor();
o.arbitraryProperty = 'is enumerable';

o.propertyIsEnumerable('arbitraryProperty'); // 返回 true
o.propertyIsEnumerable('method');            // 返回 true
o.propertyIsEnumerable('property');          // 返回 false

o.property = 'is enumerable';

o.propertyIsEnumerable('property');          // 返回 true

// 之所以这些会返回 false,是因为,在原型链上 propertyIsEnumerable 不被考虑
// (尽管最后两个在 for-in 循环中可以被循环出来)。
o.propertyIsEnumerable('prototype');   // 返回 false (根据 JS 1.8.1/FF3.6)
o.propertyIsEnumerable('constructor'); // 返回 false
o.propertyIsEnumerable('firstMethod'); // 返回 false

hasOwnProperty

  • 返回一个布尔值,表示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
  • obj.hasOwnProperty(prop)
  • prop 要检测的属性的 String 字符串形式表示的名称,或者 Symbol
  • 所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

即使属性的值是 null 或 undefined,只要属性存在,hasOwnProperty 依旧会返回 true。

o = new Object();
o.propOne = null;
o.hasOwnProperty('propOne'); // 返回 true
o.propTwo = undefined;  
o.hasOwnProperty('propTwo'); // 返回 true

自身属性与继承属性

o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop');             // 返回 true
o.hasOwnProperty('toString');         // 返回 false
o.hasOwnProperty('hasOwnProperty');   // 返回 false

遍历一个对象的所有自身属性

var buz = {
  fog: 'stack'
};

for (var name in buz) {
  if (buz.hasOwnProperty(name)) {
    console.log('this is fog (' + 
      name + ') for sure. Value: ' + buz[name]);
  }
  else {
    console.log(name); // toString or something else
  }
}

in

  • 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
  • prop in object
    var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
    0 in trees        // 返回true
    3 in trees        // 返回true
    6 in trees        // 返回false
    "bay" in trees    // 返回false (必须使用索引号,而不是数组元素的值)
    "length" in trees // 返回true (length是一个数组属性)
    "PI" in Math          // 返回true
    "toString" in {}; // 返回true

in右操作数必须是一个对象值。例如,你可以指定使用String构造函数创建的字符串,但不能指定字符串文字。

var color1 = new String("green");
"length" in color1 // 返回true
var color2 = "coral";
"length" in color2 // 报错(color2不是对象)

Object.prototype.toString()

  • 语法:obj.toString()
  • 返回值:一个表示该对象的字符串。
  • 每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用
  • 默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中 type 是对象的类型。
  • toString() 调用 null 返回 [object Null]undefined 返回 [object Undefined]
    var o = new Object();
    o.toString(); // returns [object Object]

使用 toString() 检测对象类型

var toString = Object.prototype.toString;

toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]