Object对象

142 阅读10分钟

属性的类型

1、数据属性

要修改属性的默认特性,就必须使用 Object.defineProperty() 方法

Configurable: 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认true

Enumerable: 表示属性是否可以通过 for-in 循环返回。默认true

Writable: 表示属性的值是否可以被修改。默认true

Value: 包含属性实际的值,默认undefined

2、访问器属性

访问器属性是不能直接定义的,必须使用 Object.defineProperty()

Configurable: 同上

Enumerable: 同上

Get: 获取函数,在读取属性时调用。默认值为 undefined。

Set: 设置函数,在写入属性时调用。默认值为 undefined 。

3、属性枚举顺序

for-in 循环、 Object.keys() 、 Object.getOwnPropertyNames() 、 Object.getOwnProperty- Symbols() 以及 Object.assign() 在属性枚举顺序方面有很大区别。 for-in 循环和 Object.keys() 的枚举顺序是不确定的,取决于 JavaScript 引擎,可能因浏览器而异。

Object.getOwnPropertyNames() 、 Object.getOwnPropertySymbols() 和 Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键。在对象字面量中定义的键以它们逗号分隔的顺序插入。

定义对象的两种方式

function Preson={};

Preson.prototype={
 buy:function(){}
}
Preson.prototype.buy=function(){};

Object.prototype

属性表示 Object 的原型对象

Object.defineProperty(obj, prop, descriptor)

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

/*
参数说明: 
	obj: 要定义属性的对象
	prop: 要定义或修改的属性的名称或 Symbol
	descriptor: 要定义或修改的属性描述符
*/
Object.defineProperty(this,"属性名",{
  get:function(){},//默认undefined,属性的getter函数,获取属性值
  set:function(){},//默认undefined,属性的setter函数,设置属性值
  wriable //默认false,是否可被赋值于value
  enuerable //默认false,是否可枚举
  configurable //默认false,是否可被修改或者删除
  value //默认undefined,该属性对应的值
})

Object.defineProperties(obj, props)

方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。 可以通过多个描述符一次性定义多个属性。


props:{
  get 
  set 
  wriable //是否可写
  enuerable //是否可枚举
  configurable //是否可配置
  value
}

Object.create()

可以通过 Object.create() 来创建一个新对象,同时为其指定原型

let biped = {
numLegs: 2
};
let person = Object.create(biped);
person.name = 'Matt';
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true

Object.entries()

Object.freeze()

Object.getOwnPropertyDescriptor()

//ES5 的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象
Object.getOwnPropertyDescriptor(obj, prop)

let obj = {
  foo: 123,
  get bar() { return 'abc' }
};
let result = Object.getOwnPropertyDescriptor(obj,'foo')
console.log(JSON.stringify(result))// {"value":123,"writable":true,"enumerable":true,"configurable":true}

Object.getOwnPropertyNames()

返回所有实例属性,无论是否可以枚举

function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
	console.log(this.name);
};
let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // "[constructor,name,age,job,sayName]"

Object.getOwnPropertySymbols()

以符号为键的属性没有名称的概念。因此, Object.getOwnPropertySymbols() 方法就出现了,这个方法与 Object.getOwnPropertyNames() 类似,只是针对符号而已。

let k1 = Symbol('k1'),
k2 = Symbol('k2');
let o = {
[k1]: 'k1',
[k2]: 'k2'
};
console.log(Object.getOwnPropertySymbols(o));
// [Symbol(k1), Symbol(k2)]

obj.hasOwnProperty(prop)

Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。

//判断自身属性是否存在
var o = new Object();
o.prop = 'exists';

function changeO() {
  o.newprop = o.prop;
  delete o.prop;
}

o.hasOwnProperty('prop');  // true
changeO();
o.hasOwnProperty('prop');  // false

JavaScript 并没有保护 hasOwnProperty 属性名,因此,可能存在于一个包含此属性名的对象,有必要使用一个可扩展的hasOwnProperty方法来获取正确的结果

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // 始终返回 false

// 如果担心这种情况,可以直接使用原型链上真正的 hasOwnProperty 方法
// 使用另一个对象的`hasOwnProperty` 并且call
({}).hasOwnProperty.call(foo, 'bar'); // true

// 也可以使用 Object 原型上的 hasOwnProperty 属性
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
Object.isExtensible()
Object.isFrozen()
Object.isSealed()

Object.keys()

方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致

Object.keys(obj)


// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

// array like object with random key ordering
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
  getFoo: {
    value: function () { return this.foo; }
  } 
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']

==ES6语法==

Object.is()

#方法判断两个值是否是相同的值。
//与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
Object.is(value1, value2);

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true


//Object.is()的实现代码
Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // 针对+0 不等于 -0的情况
      return x !== 0 || 1 / x === 1 / y;
    }
    // 针对NaN的情况
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});

Object.assign()

//浅拷贝
//1.为对象添加属性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

//2.为对象添加方法
Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

//3.克隆对象
function clone(origin) {
  return Object.assign({}, origin);
}

//4.合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
//注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

//5.为属性指定默认值
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

Object.getOwnPropertyDescriptors()

//ES2017引入
//返回指定对象所有自身属性(非继承属性)的描述对象。
let obj = {
  foo: 123,
  get bar() { return 'abc' }
};
let result = Object.getOwnPropertyDescriptors(obj)
/*{"foo":{
	"value":123,
	"writable":true,
	"enumerable":true,
	"configurable":true
  },
  "bar":{
  	"get": [Function: get bar],
    "set": undefined,
  	"enumerable":true,
  	"configurable":true
  	}
  }*/



//Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,实现深拷贝
const source = {
  set foo(value) {
    console.log(value);
  }
};

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
//   set: [Function: set foo],
//   enumerable: true,
//   configurable: true }



//Object.getOwnPropertyDescriptors()方法的另一个用处,是配合Object.create()方法,将对象属性克隆到一个新对象。这属于浅拷贝。
const clone = Object.create(Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj));

// 或者

const shallowClone = (obj) => Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);


//可以实现一个对象继承另一个对象
//es5 写法
const obj = {
  __proto__: prot,
  foo: 123,
};

//es6 写法
const obj = Object.create(prot);
obj.foo = 123;

// 或者

const obj = Object.assign(
  Object.create(prot),
  {
    foo: 123,
  }
);


//Object.getOwnPropertyDescriptors写法
const obj = Object.create(
  prot,
  Object.getOwnPropertyDescriptors({
    foo: 123,
  })
);

Object.setPrototypeOf()

//用来设置一个对象的原型对象(prototype),返回参数对象本身
Object.setPrototypeOf(obj, prototype)
参数描述:
	obj: 要设置其原型的对象。
	prototype: 该对象的新原型(一个对象 或 null).
    
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40

Object.getPrototypeOf()

//返回指定对象的原型(内部[[Prototype]]属性的值)
Object.getPrototypeOf(object)
参数:
	object:要返回其原型的对象
    
// 等同于 Object.getPrototypeOf(Number(1))
Object.getPrototypeOf(1)
// Number {[[PrimitiveValue]]: 0}

// 等同于 Object.getPrototypeOf(String('foo'))
Object.getPrototypeOf('foo')
// String {length: 0, [[PrimitiveValue]]: ""}

// 等同于 Object.getPrototypeOf(Boolean(true))
Object.getPrototypeOf(true)
// Boolean {[[PrimitiveValue]]: false}

Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true


//如果参数是undefined或null,它们无法转为对象,所以会报错。
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object

Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object

*对象迭代

Object.values()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

let obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

Object.entries()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

let obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

Object.fromEntries()

//用于将一个键值对数组转为对象。
Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }


//特别适合将 Map 结构转为对象。
// 例一
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }


//该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

属性名表达式

//定义对象的属性
let obj = {
    ['a'+'bc']:123
}
console.log(obj['abc']) //123

对象的属性赋值器(setter、getter)

const cart = {
  _wheels: 4,

  get wheels () {
    return this._wheels;
  },

  set wheels (value) {
    if (value < this._wheels) {
      throw new Error('数值太小了!');
    }
    this._wheels = value;
  }
}

name属性

//函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"


//如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"


//有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous。
(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"

//如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

可枚举性

//对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo'){
    value: 123,
    writable: true,
    enumerable: true,
    configurable: true
}
//描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略enumerable为false的属性。

for...in循环:     只遍历对象自身的和继承的可枚举的属性。
Object.keys():   返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

Object遍历

//1.for...in...
//循环遍历对象自身的和继承的可枚举属性(不含Symbol属性).
for(const key in obj){
    console.log(`key=${key},value=${obj[key]}`)
}

//2.Object.keys()
//返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性).
Object.keys(obj).forEach(key => {
    console.log(key);
})

//3.Object.getOwnPropertyNames()
//返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
Object.getOwnPropertyNames(obj).forEach(key => {
    console.log(key);
})

//4.Object.getOwnPropertySymbols(obj)
返回一个数组,包含对象自身的所有 Symbol 属性的键名。

//5.Reflect.ownKeys()
//返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举. 
Reflect.ownKeys(obj).forEach(key => {
	console.log(key);
});

super关键字

const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

//super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。

解构赋值

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
//变量z是解构赋值所在的对象。它获取等号右边的所有尚未读取的键(a和b),将它们连同值一起拷贝过来。




//解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2


//扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined


//遇到相同属性名,重命名
const obj = {name:'ace',age:18}
const name = 'tom'
const {name:objname = 'jack'} = obj
console.log(objname);//ace

扩展运算符

//用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }


//扩展运算符也可以用于数组
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}


//如果扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)}
{...1} // {}


//如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
let obj = {...'abc'}
console.log(obj) //{0: "a", 1: "b", 2: "c"}


//自动转换
// 等同于 {...Object(true)}
{...true} // {}

// 等同于 {...Object(undefined)}
{...undefined} // {}

// 等同于 {...Object(null)}
{...null} // {}




//对象的扩展运算符等同于使用Object.assign()方法。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

//对象深拷贝
// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)
//写法一的__proto__属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。

//扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);



//修改原对象属性值
let previousVersion = {name:'snail',age:18}
let newVersion = {
  ...previousVersion,
  name: 'New Name' 
};
console.log(newVersion) //{name: "New Name", age: 18}


//扩展运算符后面可以跟表达式
const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};


//扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。
let a = {
  get x() {
    return '中国'
  }
}

let aWithXGetter = { ...a };
console.log(aWithXGetter) //{x: "中国"}

链判断运算符

//ES2020引入

const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value

//使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。


//链判断运算符有三种用法。

obj?.prop // 对象属性
obj?.[expr] // 同上
func?.(...args) // 函数或对象方法的调用

Null 判断运算符

//ES2020引入
// ?? 它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。

const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

//默认值只有在属性值为null或undefined时,才会生效。


//跟链判断运算符?.配合使用,为null或undefined的值设置默认值。
const animationDuration = response.settings?.animationDuration ?? 300;
//response.settings如果是null或undefined,就会返回默认值300。

//判断函数参数是否赋值
function Component(props) {
  const enable = props.enabled ?? true;
  // …
}
//等同于
function Component(props) {
  const { enabled: enable = true} = props;
  // …
}