属性的类型
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;
// …
}