一、对象
1. 语法
对象可以通过两种方式进行定义:
字面量的方式
let myObj = {
name:'joly'
}
构造形式
let myObj = new Object();
myObj.name = 'joly'
两者声明出来的对象是一致的,唯一的区别是:字面量可以一次性添加多个属性,而构造函数的形式要单独添加。
2. 类型
对象是JavaScript中的一种基本数据类型,在对象中又有一些子类型:
Array
- Function
- Date
- …
内置对象
内置对象中 String 和 基本数据类型中的 string 是不一样的,文本形式的string不是String,但是在使用过程中如果有需要会被自动转换成String对象
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
……
重点说明:在创建对象时,如果没有特别的额外需要配置的东西,则推荐使用字面量的形式创建对象,如果需要对对象内容进行特殊配置,则使用构造函数的形式创建。
3. 内容
存储在对象内部的是对象属性的名称,这些属性就像是指针,指向属性值真正存储的位置。
1 - 可计算属性名
属性访问(. 操作符访问)
属性访问要求属性名满足标识符的命名规范
- 键访问([]访问)
键访问接受任意UTF-8/Unicode 字符串作为属性名
无论使用哪种访问方式,在对象中,对象的属性名都是String字符串方式,如果属性名不是字符串,在访问时也会先转换成一个字符串。
键访问方式在 [] 之间接受 表达式 作为属性名,即可计算属性名;最常用的使用场景是 Symbol。
2 - 属性描述符
属性描述符(数据描述符)是针对对象中的某个属性的。
a - 包含哪些描述符
- value - 属性值
- writable - 当前属性值是否可修改,默认 true;如果设置为false,则修改属性值无效。
- enumerable - 当前属性是否可枚举,默认 true;如果设置为false后,当前属性为不可枚举,不会出现在for - in 中 和 Obejct.keys()中,但是依旧可以访问该属性。
- configurable - 当前属性是否可配置,默认 true;如果设置为false,则当前属性无法修改特性;单向操作,设置为false后,则不能修改为true。但是设置为false后,依旧可以设置writable由true为false,但是无法由false为true;设置为false后,delete 语句失效。
b - 如何修改一个属性的属性描述符
- Object.defineProperty:可以添加或者修改一个已有属性的特性 ```javascript var myObject = {}; Object.defineProperty(myObject,’a’,{ value:2, writable:true, enumerable:true, configurable:true })
myObject.a = 2;
<a name="t09ql"></a>
#### 3 - 属性的 Getter 和 Setter
**a - [[Get]]操作:当获取一个属性的属性值时,会实现对象的[[Get]]操作**
- 第一步:先在对象中查看当前属性对应的属性值,如果有则返回属性值
- 第二步:如果对象中没有对应的属性名,则查找对象的原型链,如果查到则返回属性值
- 第三步:如果无论如何都没有找到该属性,则返回 undefined
**b - [[Put]]操作:当给一个属性设置属性值时,会触发对象的[[Put]]操作**
- 判断属性存不存在,在当前对象中没有找到属性名则会在原型链中查找
- 如果属性存在,则判断当前属性是否为访问描述符,如果是,则调用setter
- 属性的数据描述符中的writable是否为false,是则失败
- 如果都不是,则设置属性值为该值
<a name="ZiuSu"></a>
#### 3 - 复制对象
当复制一个对象时,需要先判断是 浅复制 还是 深复制,最根本的区别在于是复制了一个对象的实体还是引用。
- **浅拷贝**:自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。
- **深拷贝**:将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
**a - 有哪些操作属于浅拷贝**
- = 操作符直接赋值
- Object.assign():Object.assign()方法实质上就是用了 = 操作,所以不会赋值源对象的一些属性操作符
使用Object.assign() 要注意地方:
- 只会拷贝原对象可以遍历的属性值
- 可以拷贝对象的symbol属性值
- 不会拷贝对象的继承属性
```javascript
let arr1 = ['骑车','打球']
let objectA = {
name:'Lily'
}
let objectB = {
age:12,
hobby:arr1,
friend:{
name:'Summer',
age:15
}
}
let copyA = Object.assign({},objectA);
let copyB = Object.assign({},objectB);
objectA.name = 'Lily2'
objectB.age = 14;
arr1.push("学习");
objectB.friend.name = 'Summer2';
console.log(copyA);
// 输出结果:{ name: 'Lily' }
console.log(copyB);
// 输出结果:
{ age: 12,
hobby: [ '骑车', '打球', '学习' ],
friend: { name: 'Summer2', age: 15 }
}
- 展开运算符
说明:修改了原对象的引用属性的属性值后,复制的对象也会发生改变
let objectC = {
name:'Lily',
age:12,
friend:{
name:'Summer',
age:15
}
}
let copyC = {...objectC};
console.log(copyC); // { name: 'Lily', age: 12, friend: { name: 'Summer', age: 15 } }
objectC.friend.name = 'Summer2';
console.log(copyC);// { name: 'Lily', age: 12, friend: { name: 'Summer2', age: 15 } }
concat
let arrayB = [1,2,3,[1,2,3],4,5];
let copyArrayB = arrayB.concat(6,7);
console.log(copyArrayB); // [ 1, 2, 3, [ 1, 2, 3 ], 4, 5, 6, 7 ]
arrayB[3].push('number');
console.log(copyArrayB); // [ 1, 2, 3, [ 1, 2, 3, 'number' ], 4, 5, 6, 7 ]
slice
let arrayA = [1,2,3,[1,2,3],4,5];
let copyArrA = arrayA.slice(1);
console.log(copyArrA); // [ 2, 3, [ 1, 2, 3 ], 4, 5 ]
arrayA[3].push('number');
console.log(copyArrA);// [ 2, 3, [ 1, 2, 3, 'number' ], 4, 5 ]
手动实现一个浅拷贝
// 手动实现一个浅拷贝
const shadowClone = (target) => {
if (typeof target === 'object' && target !== null){
const targetClone = Array.isArray(target) ? [] : {} ;
for (let prop in target){
if (target.hasOwnProperty(prop)){
targetClone[prop] = target[prop];
}
}
return targetClone;
}else {
return target;
}
}
b - 如何实现一个深拷贝
- JSON序列化:
- 内容JSON安全,没有undefined、function、symbol这几种数据类型,否则就会属性缺失
- 如果属性值有 NAN 、 infinity、-infinity 在序列化之后会被转换成 null
- 无法拷贝不可枚举的属性值
- 无法拷贝原型链上的属性值
- Data 会被序列化为字符串,regExp 会被序列化成null
- 递归实现(完整版递归) ```javascript // 判断当前数据是否需要递归 const isCycleDataType = (obj) => { return (typeof obj === ‘object’ || typeof obj === ‘function’) && (obj !== null) }
function deepClone(origin , hash = new WeakMap()){ if (origin instanceof Date){ return new Date(origin) }
if (origin instanceof RegExp){ return new RegExp(origin) }
// 防止循环递归调用 if (hash.has(origin)){ return hash.get(origin) }
// 复制原型链的内容 let allDesc = Object.getOwnPropertyDescriptors(origin); let cloneObj = Object.create(Object.getPrototypeOf(origin),allDesc); hash.set(origin , cloneObj);
for (let key of Reflect.ownKeys(origin)){ cloneObj[key] = isCycleDataType(origin[key]) ? deepClone(origin[key],hash) : origin[key] }
return cloneObj; }
<a name="pwsZJ"></a>
#### 4 - 冻结对象
```javascript
// 测试对象
let Lily = {
name:'Lily',
age:12,
friends:{
name:'July',
age:16
}
}
常量属性:常量属性即当前属性值不能修改,
// 设置常量属性
Object.defineProperty(Lily , 'name' , {
writable:false,
configurable:false,
enumerable:true
})
禁止对象扩展(Object.preventExtensions()):在当前对象上新增其他属性值无效
Object.preventExtensions(Lily);
Lily.sex = "女";
console.log(Lily);
密封对象(Object.seal(obj)):禁止对象扩展且禁止对象属性配置,但是可以修改属性的值
Object.seal(Lily);
Object.defineProperty(Lily , 'age', {
writable:false,
configurable:false,
enumerable:true,
value:13
})
console.log(Object.getOwnPropertyDescriptor(Lily, 'age'));
完全冻结对象(Object.freeze()):在密封对象的基础上,禁止修改属性值
Object.freeze(Lily);
Lily.age = 14
console.log(Lily);
5 - 查看对象属性的存在性
```javascript let Lily = { name:’Lily’, age:12, friends:{ name:’July’, age:16 }, sym:Symbol(“1”) }
Object.defineProperty(Lily , ‘test’ , { value:’不可枚举’, enumerable:false })
let July = Object.create(Lily) July.sex = ‘boy’ Object.defineProperty(July , ‘test2’ , { value:’不可枚举’, enumerable:false })
**a - 存在性**<br />两者的区别在于是否查找**原型链**
- in 操作符:检查属性是否存在于对象及其原型链中
```javascript
console.log('age' in Lily) // true
console.log('sex' in Lily) // false
console.log('test' in Lily) // true
console.log('age' in July) // true
console.log('sex' in July) // true
myObject.hasOwnProperty() 函数:只会检查属性是否存在于对象本身中
console.log(Lily.hasOwnProperty('age')); // true
console.log(Lily.hasOwnProperty('sex')); // false
console.log(July.hasOwnProperty('age')); // false
console.log(July.hasOwnProperty('sex')); // true
b - 可遍历性
myObject.propertyIsEnumerable() 函数:判断某个属性是否可遍历,返回布尔值
console.log(Lily.propertyIsEnumerable('age')); // true
console.log(Lily.propertyIsEnumerable('test')); // false
c - 获取属性
两者的区别在于是否 过滤不可遍历的属性,三者都可以获取到Symbol属性,都不可以获取到原型链上的属性Object.keys():获取到所有可遍历的属性值
- Object.getOwnPropertyNames() 函数:获取到所有的属性值,无论当前属性是否可遍历
- Reflect.ownKeys():获取到所有的属性,无论当前属性是否可遍历
console.log(Object.keys(Lily)); // [ 'name', 'age', 'friends', 'sym' ]
console.log(Object.getOwnPropertyNames(Lily)); // [ 'name', 'age', 'friends', 'sym', 'test' ]
console.log(Reflect.ownKeys(Lily)); // [ 'name', 'age', 'friends', 'sym', 'test' ]
console.log(Object.keys(July)); // [ 'sex' ]
console.log(Object.getOwnPropertyNames(July)); // [ 'sex', 'test2' ]
console.log(Reflect.ownKeys(July)); // [ 'sex', 'test2' ]
4. 遍历对象
1 - for…in…
for…in 用来遍历所有可枚举的属性,包括原型链上的属性。无法直接获取到对象属性值2 - for…of…
遍历数组的内容,如果对象本身实现了 iterator,则也可以用来遍历对象的属性值。 ```javascript let obj = { name:’Lily’, age:16, sex:’boy’, school:’middle’ }
Object.defineProperty(obj,Symbol.iterator,{ writable:false, configurable:true, enumerable:false, value:function (){ let myObject = this; let keys = Object.keys(myObject); let idx = 0; return{ next(){ return { value:myObject[keys[idx++]], done:idx > keys.length } } } } })
// 手动遍历 let it = objSymbol.iterator; console.log(it.next()) // { value: ‘Lily’, done: false } console.log(it.next()) // { value: 16, done: false } console.log(it.next()) // { value: ‘boy’, done: false } console.log(it.next()) // { value: ‘middle’, done: false } console.log(it.next()) // { value: undefined, done: true }
// 使用 for … of for (let value of obj){ console.log(value); // Lily 16 boy middle } ```