# js对象
以前的对象遍历是没有特定的顺序的。
枚举顺序:ES6 规定了自有属性的枚举顺序,并且会将同一类别的属性整合到一块,具体的排列规则如下所列。
1)首先遍历数字类型或数字字符串的属性,按大小升序排列。
2)接着遍历字符串类型的属性,按添加时间的先后顺序排列。
3)最后遍历符号类型的属性,也按添加顺序排列。
var obj = {
c: 1,
1: 2,
a: 3,
"0": 4,
[Symbol("x")]: 5,
[Symbol("y")]: 6
};
var properties = [];
for(var key in obj) {
if(obj.hasOwnProperty(key)) { //过滤掉继承属性
properties.push(key);
}
}
console.log(properties); //["0", "1", "c", "a"]
JSON.stringify(obj); //{"0":4,"1":2,"c":1,"a":3}
Object.getOwnPropertyNames(obj); //["0", "1", "c", "a"]
Object.keys(obj); //["0", "1", "c", "a"]
Object.getOwnPropertySymbols(obj); //[Symbol(x), Symbol(y)]
Object.assign({}, obj); //{0: 4, 1: 2, c: 1, a: 3, Symbol(x): 5, Symbol(y): 6}
# 关于对象的遍历
- object for-in 循环循环自身和原型上的属性,不包括Symbol,如果该属性设置了不可遍历,则会忽略
- 使用 obj3=Object.create(obj1,obj2) ,obj1是在原型上,obj2是添加到obj3中,obj2设置的值 默认不支持遍历 的除非手动enumerable:true
let obj=Object.create({a:1},{b:{value:17},c:{value:20,enumerable:true}})
for(let i in obj){
console.log(i)
}
//只有 c 和 a
如果原型上的a设置了enumerable:false,依然是可以遍历的!因为第二个参数之后才符合Object.definePerporty规则
let obj=Object.create({a:{value:1000,enumerable:false}},{b:{value:17},c:{value:20,enumerable:true}})
for(let i in obj){
console.log(i)
}
// c
// a
console.warn(obj['a'])//{value: 1000, enumerable: false}
consolo.warn(obj['c'])//20
- Object.entries(obj),Object.keys(obj),Object.values(obj)只会给出对应的obj 自身的可遍历属性
- Object.getOwnPropertyDescriptor(obj,'x'),查出自身的属性的具体信息:value,
let obj=Object.create({a:1},{b:{value:17},c:{value:20,enumerable:true}})
console.log(Object.getOwnPropertyDescriptor(obj,'b'))
console.log(Object.getOwnPropertyNames(obj))
//{value: 17, writable: false, enumerable: false, configurable: false}
// ['b', 'c']
- Object.getOwnPropertyNames(obj) :只获取对象自身的属性的key值,包括可遍历的和不可遍历的属性
遍历关于symbol
如果响应的属性key是Symbol设置的,那么是无法通过以上方法遍历的 ,可借助Object.getOwnPropertySymbols获取属性是symbol的,
也可以使用Object.getOwnPropertyDescriptors() 不过只能查看无法遍历到symbol类型
let obj={name:'jspang',skill:'web'};
//let obj={name:'jspang',skill:'web',c:{value:20,enumerable:false}};
// 这种写法会认为c有两个参数而已,Object.keys依然能遍历到c
Object.defineProperty(obj, "c", {
value: "c++",
//是否为枚举属性
enumerable: false
});
let age=Symbol();
let t1 =Symbol('t1')
obj[age]=18;
obj[t1]='t111'
obj.sex="man"
console.log(Object.getOwnPropertyNames(obj))
//['name', 'skill', 'c', 'sex']
console.log(Object.keys(obj))//['name', 'skill', 'sex']
console.log(obj.c)//c++
console.log(obj[age])//18
console.log(Object.getOwnPropertySymbols(obj))// [Symbol(), Symbol(t1)]
Object.getOwnPropertySymbols(obj).forEach(el=>{
console.log(obj[el])
//18
// t111
})
console.log(Object.getOwnPropertyDescriptors(obj))// 见下图
for (let i in Object.getOwnPropertyDescriptors(obj)){
console.log(i)
// 不包括Symbol
}
console.log(age in obj)//true
# 属性枚举顺序
- for-in 循环、Object.keys()、Object.getOwnPropertyNames()、Object.getOwnProperty- Symbols()以及 Object.assign()在属性枚举顺序方面有很大区别。
- for-in 循环和 Object.keys() 的枚举顺序是不确定的。
- Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键。在对象字面量中 定义的键以它们逗号分隔的顺序插入。
let k1 = Symbol('k1'),
k2 = Symbol('k2');
let o = {
1: 1,
first: 'first',
[k1]: 'sym2',
second: 'second',
0: 0
};
o[k2] = 'sym2';
o[3] = 3;
o.third = 'third';
o[2] = 2;
console.log(Object.getOwnPropertyNames(o)); // ["0", "1", "2", "3", "first", "second", "third"]
console.log(Object.getOwnPropertySymbols(o)); // [Symbol(k1), Symbol(k2)]
# 对象new操作符的原理
- 创建一个新的空对象
- 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)
- 执行构造函数内部的代码:给对象添加属性和方法
- 如果构造函数返回 一个对象 ,则返回该对象;否则返回刚创建的对象(
如果return 1等数字/字符串/布尔值也是返回刚创建的对象)
function Foo() {
this.user = 'Lucas';
const o = {};
return o;
}
const instance = new Foo();
console.log(instance.user); // undefined
function Foo() {
this.user = 'Lucas';
return 1;
}
const instance = new Foo();
console.log(instance.user); // Lucas
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
var lily = new Person("Lily");
// 上述这段代码在运行时,JavaScript 引擎通过将Person的原型对象prototype赋值给实例对象lily的__proto__属性
// 实现了lily对Person的继承,即执行了以下代码:
// 实际上 JavaScript 引擎执行了以下代码
var lily = {};
lily.__proto__ = Person.prototype;
Person.call(lily, "Lily");
TIP
new对象:
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Alice", 23);
new一个对象的四个过程:
1、创建一个空对象
var obj = new Object();
2、设置原型链,将obj的__proto__成员指向了Person函数对象的prototype成员对象
obj.__proto__ = Person.prototype;
3、让Person中的this指向obj,并执行Person的函数体
var result = Person.call(obj);
4、将初始化完毕的新对象地址,保存到等号左边的变量中
function A(name,age){
console.log(this)
this.name=name
console.log(this)
this.age=age
console.log(this)
//return this //默认有这个操作,不管写不写
}
let b1=new A('111',19)
//{}
// {name: "111"}
//{name: "111", age: 19}
let b2=A('222',20)
//Window
//Window
//Window
console.log(name)
//'222'
Object 构造函数创建一个对象包装器。
剩余/扩展属性
它将自己提供的对象的枚举属性复制到一个新的对象上。 使用比Object.assign()更短的语法,可以轻松克隆(不包括原型)或合并对象。
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }
请注意,Object.assign()会触发setter,而展开操作符则不会。
# 对象的方法
# Object.fromEntries 键值对数组转对象
console.warn(Object.fromEntries([
['foo', 'bar'],
['baz', 42]
]))
# Object.assign()
通过复制一个或多个对象来创建一个新的对象。
Object.assign(target, ...sources)
返回值 目标对象。
function A(){
this.f1=10
this.f2=20
}
A.prototype.f1=1000
A.prototype.f3=3000
let childa=new A()
const object2 = Object.assign({c: 4, d: 5}, childa);
console.log(object2);//Object {c: 4, d: 5, f1: 10, f2: 20}
var obj1 = { name: "strick" },
obj2 = Object.create(obj1); //name 是继承属性
obj2.age = 28; //age 是不可枚举的属性
Object.defineProperty(obj2, "age", {
enumerable: false
});
obj2.school = "university"; //school 是可枚举的自有属性
Object.assign({}, obj2); //{school: "university"}
var obj1 = { [Symbol("name")]: "strick" },
obj2 = Object.assign({}, obj1);
console.log(obj2); //{Symbol(name): "strick"}
var obj = Object.assign({}, 1, "a", true, undefined, null);
console.log(obj); //{0: "a"}
提示
- 只能复制
可枚举的自有属性(定义在对象中),无法复制继承属性(定义在对象原型中)和不可枚举的属性。Symbol 类型的属性也能被复制。 - 如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地
覆盖早先的属性。 - Object.assign 方法只会
拷贝源对象自身的并且可枚举的属性到目标对象。 - 它分配属性,而不仅仅是复制或定义新的属性。针对深拷贝,需要使用其他方法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
- 当源对象的位置是基本类型的值时,它们会被包装成对象,再进行合并。但由于undefined 和 null 没有包装对象,并且数字和布尔值的包装对象又没有可枚举的属性(不能使用for循环之类的方法),因此只有字符串的包装对象才能不被忽略,最终以数组的形式复制到目标对象中,
- 源对象的访问器属性会变成目标对象的数据属性。代码如下所示,obj 对象包含一个名为 name 的访问器属性,在把它与空对象合并后,目标对象会有一个名为 name 的数据属性,值就是访问器属性中 get()方法的返回值。
var obj = {
get name() {
return "strick";
}
};
Object.assign({}, obj); //{name: "strick"}
const object1 = {
a: 1,
b: 2,
c: 3
};
function A(){
this.f1=10
this.f2=20
}
A.prototype.f1=1000
A.prototype.f3=3000
let childa=new A()
//e的这种写法并不会像Object.create的第二个参数那样,虽然声明不可枚举,但是无效,反而认为是个属性
const object3 = Object.assign(childa,{c: 4, d: 5,e:{value:99,enumerable:false}});
console.log(object3);
const object1 = {
a: 1,
b: 2,
c: 3
};
function A(){
this.f1=10
this.f2=20
}
A.prototype.f1=1000
A.prototype.f3=3000
let childa=new A()
//
let oc = Object.create({},{bb:{value:17},cc:{value:20,enumerable:true},dd:{value:20,enumerable:false}})
const object3 = Object.assign(childa,oc);
console.log(object3);
// {f1: 10, f2: 20, cc: 20}
dest = {
set a(val) {
console.log(`Invoked dest setter with param ${val}`);
}
};
src = {
get a() {
console.log('Invoked src getter');
return 'foo';
}
};
Object.assign(dest, src);
// Invoked src getter
// Invoked dest setter with param foo
//调用src的获取方法,调用dest的设置方法并传入foo参数
// 因为这里的设置函数并不执行赋值操作,所以实际上并没有把值转移过来
// 所以不能在两个对象间转移获取函数和设置函数。
console.log(dest); // { set a(val) {...} }
console.log(dest.a)//undefind
可以通过目标对象上的设置函数观察到覆盖的过程
dest = {
set id(x) {
console.log(x);
}
};
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
// first
// second
// third
# Object.create(proto[, propertiesObject])
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
- propertiesObject:对应于 Object.defineProperties() 的第二个参数
let obj=Object.create({a:1},{b:{value:17},c:{value:20,enumerable:true}})
console.log(obj)
//{c: 20, b: 17,[[prototype]]:{
// a:1
//}}
for(let i in obj){
console.log(i)
}
//c
//a
//这种方式默认生成的对象是不可遍历的,除非enumerable设为true。
# new Object和Object.create区别
- new Object:通过构造函数的对象, 对象添加的属性 obj 是在自身实例下
- Object.create:Object.create(proto[, propertiesObject]) :创建的新对象继承一个对象。 添加的属性 proto 是在原型下,添加的属性 propertiesObject 才在自身实例下 第二个参数创建非空对象的属性描述符默认是为 false 的,不可写,不可枚举,不可配置
// 一个原型 对象
let prototype = {
getName: function () {
return this.first + " " + this.last;
},
say: function () {
console.log("hello")
}
}
// 基于原型创建x
let x = Object.create(prototype);
x.first = "A";
x.last = "B";
console.log(x.getName());//A B
x.say();//hello
console.log(x.__proto__ === prototype)//true
# Object.getOwnPropertySymbols()
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
Object.getOwnPropertySymbols(obj)
# Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性并返回这个对象。 Object.defineProperty(obj, prop, descriptor)
已单独提出专题.跳转
# Object.defineProperties()
直接在一个对象上定义新的属性或修改现有属性,并返回该对象。 给对象添加多个属性并分别指定它们的配置。
Object.defineProperties(obj, props)
var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
});
# Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。( 自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
Object.getOwnPropertyDescriptor(obj, prop)
参数
obj 需要查找的目标对象
prop 目标对象内属性名称(String类型)
let obj1={k:1};
console.log(Object.getOwnPropertyDescriptor(obj1,'k'))
{value: 1, writable: true, enumerable: true, configurable: true}
# Object.getOwnPropertyDescriptors
Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
let s1 = Symbol('foo'),
s2 = Symbol('bar');
let o = {
[s1]: 'foo val',
[s2]: 'bar val',
baz: 'baz val',
qux: 'qux val'
};
Object.defineProperty(o,'foo',{
value: "fooooo",
//是否为枚举属性
enumerable: false
})
console.log(Object.getOwnPropertyDescriptors(o));
//{baz: {…}, qux: {…}, foo: {…}, Symbol(foo): {…}, Symbol(bar): {…}}
# Object.getOwnPropertyNames(obj)
参数
obj 一个对象,其自身的可枚举和不可枚举属性的名称被返回。
返回值 在给定对象上找到的自身属性对应的字符串数组。
function F(){
}
F.prototype.k=900;
let obj=new F();
obj.s=1;
Object.defineProperty(obj,'j',{
value:9
})
console.log(Object.getOwnPropertyNames(obj))// ["s", "j"]
//只返回自身,不返回原型链上的名称
# Object.entries(obj)
提示
返回对象的键值对组成的数组,不会遍历原型上的 ,与for in不同
function F(){}
F.prototype.k=1;
F.prototype.t=8;
let a=new F();
a.p=9;
a.o=1000;
for(let i in a){
console.log(a[i])
}//9 1000 1 0
// for (let i of a){
// console.log(i)
// }
//对象不经过手动添加不能迭代,不能用for of
console.log(Object.entries(a))//[['p',9],['o',1000]]
# Object.keys(obj)
返回key组成的对象
# Object.values(obj)
返回value组成的对象
# Object.is(a,b)
只要a,b一模一样即可 包括NaN和NaN都是正确的
# obj.hasOwnProperty(prop)
prop: 要检测的属性 字符串 名称或者 Symbol。
返回值: 用来判断某个对象是否含有指定的属性的 Boolean。
TIP
所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
function A(){
this.age=20
this.eat=function(){
console.log(1)
}
}
A.prototype.full=function(){
console.log(2)
}
let a=new A()
console.log(a.hasOwnProperty('eat'))//true
console.log(a.hasOwnProperty('full'))//false
console.log(a.age)//20
class B{
// name="tr" /*webpack工程化中会报错,单独写的没报错*/
like(){
console.log(3)
console.log(this.age)
}
}
let b=new B()
b.bl=function(){
console.log(4)
}
console.log(b.hasOwnProperty('like'))//false
b.bl()//4
let c=new B();
//c.bl() TypeError: c.bl is not a function
对比 Object.seal():用Object.seal()密封的对象可以改变它们现有的属性。使用Object.freeze() 冻结的对象中现有属性是不可变的。
# Object.freeze
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。 但是默认不能冻结深层次对象。
let obj = {
prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// expected output: 42
//删除某个属性无效
delete obj.a
console.log(obj)//{prop: 42}
//可置空
obj = null
console.log(obj)//null
//浅冻结
obj1 = {
internal: {}
};
Object.freeze(obj1);
obj1.internal.a = 'aValue';
console.log(obj1.internal.a )// 'aValue'
// 深冻结函数.
function deepFreeze(obj) {
// 取回定义在obj上的属性名
var propNames = Object.getOwnPropertyNames(obj);
// 在冻结自身之前冻结属性
propNames.forEach(function(name) {
var prop = obj[name];
// 如果prop是个对象,冻结它
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// 冻结自身(no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined\
# Object.freeze 面试题
const user = {
name: 'Joe',
age: 25,
pet: {
type: 'dog',
name: 'Buttercup'
}
};
Object.freeze(user);
user.pet.name = 'Daffodil';
console.log(user.pet.name);
//Daffodil
Object.freeze 将对对象执行浅层冻结,但不会保护深层属性不被突变。在此示例中,我们将无法更改user.age,但对 user.pet.name 进行更改不会有问题。如果我们认为需要保护某个对象以免其被“彻底破坏”,则可以递归应用 Object.freeze 或使用现有的“deep freeze”库。
# Object.seal
对象封存,防止新属性添加或者存在出现被移除,原先的对象还是可以修改的。
const person ={name:"aaa"}
Object.seal(person)
person.name="bbb"
person.age=30;
delete person.name
console.log(person)//{name: "bbb"}
# 对象的一些有关面试题
- 属性名皆为字符串,所以覆盖了
let a={}, //数组也可
b="0",
c=0;
a[b]="skt"
a[c]="IG"
console.log(a[b])//IG
let a1={};
a1[undefined]="IG"
a1['undefined']="skt"
console.log(a1[undefined])//skt 运行转成字符串比较,实际存储则不是
let a1={};
let s={k:1}
a1[s]=1000
console.log(a1)//{[object Object]: 1000}
# Object.entries与Array.entries对比
- 调用方式与所属对象
Object.entries()是 Object 的静态方法,需传入对象作为参数:
const obj = { a: 1, b: 2 };
const entries = Object.entries(obj); // [ ['a', 1], ['b', 2] ]
Array.prototype.entries()是数组实例的方法,需通过数组调用:
const arr = ['a', 'b'];
const iterator = arr.entries(); // 返回迭代器
- 返回类型
Object.entries() :
返回一个二维数组,包含对象的可枚举属性键值对。
// 示例输出:
[ ['key1', value1], ['key2', value2], ... ]
Array.prototype.entries():返回一个迭代器对象,可通过 for...of 或 next() 遍历,生成 [index, value] 形式的数组。
// 示例迭代结果:
for (const [index, value] of arr.entries()) {
console.log(index, value); // 0 'a', 1 'b'
}
- 处理的数据结构
Object.entries()处理对象的自身可枚举属性,包括普通对象、数组(视为键值对集合)等。
const arr = [1, , 3];
arr.foo = 'bar';
console.log(Object.entries(arr));
// 输出:[ ['0', 1], ['2', 3], ['foo', 'bar'] ]
Array.prototype.entries() 处理数组的所有索引(包括稀疏位置),生成 [index, value]。空位的 value 为 undefined。
const sparseArr = [1, , 3];
const iterator = sparseArr.entries();
console.log([...iterator]);
// 输出:[ [0, 1], [1, undefined], [2, 3] ]
- 稀疏数组处理
Object.entries():忽略未赋值的稀疏索引,仅包含实际存在的属性。
Array.prototype.entries(): 遍历所有索引(从 0 到 length-1),未赋值的索引对应 undefined。
- 使用场景
Object.entries() 常用于对象转数组结构后的遍历或操作:
// 遍历对象属性
for (const [key, value] of Object.entries(obj)) { ... }
// 转为 Map
const map = new Map(Object.entries(obj));
Array.prototype.entries()适用于需要同时操作数组索引和值的场景:
// 获取索引和值
for (const [index, value] of arr.entries()) { ... }
# 总结
| 特性 | Object.entries() | Array.prototype.entries() |
|---|---|---|
| 调用方式 | Object.entries(obj) | arr.entries() |
| 返回类型 | 二维数组 | 迭代器对象 |
| 处理目标 | 对象的可枚举属性 | 数组的所有索引(包括稀疏位置) |
| 稀疏数据 | 忽略未赋值的属性 | 包含空位,值为 undefined |
| 典型场景 | 对象转数组、遍历属性、转为 Map | 同时操作数组的索引和值 |