一、对象

1. 语法

对象可以通过两种方式进行定义:

  • 字面量的方式

    1. let myObj = {
    2. name:'joly'
    3. }
  • 构造形式

    1. let myObj = new Object();
    2. 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;

  1. <a name="t09ql"></a>
  2. #### 3 - 属性的 Getter 和 Setter
  3. **a - [[Get]]操作:当获取一个属性的属性值时,会实现对象的[[Get]]操作**
  4. - 第一步:先在对象中查看当前属性对应的属性值,如果有则返回属性值
  5. - 第二步:如果对象中没有对应的属性名,则查找对象的原型链,如果查到则返回属性值
  6. - 第三步:如果无论如何都没有找到该属性,则返回 undefined
  7. **b - [[Put]]操作:当给一个属性设置属性值时,会触发对象的[[Put]]操作**
  8. - 判断属性存不存在,在当前对象中没有找到属性名则会在原型链中查找
  9. - 如果属性存在,则判断当前属性是否为访问描述符,如果是,则调用setter
  10. - 属性的数据描述符中的writable是否为false,是则失败
  11. - 如果都不是,则设置属性值为该值
  12. <a name="ZiuSu"></a>
  13. #### 3 - 复制对象
  14. 当复制一个对象时,需要先判断是 浅复制 还是 深复制,最根本的区别在于是复制了一个对象的实体还是引用。
  15. - **浅拷贝**:自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。
  16. - **深拷贝**:将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
  17. **a - 有哪些操作属于浅拷贝**
  18. - = 操作符直接赋值
  19. - Object.assign():Object.assign()方法实质上就是用了 = 操作,所以不会赋值源对象的一些属性操作符
  20. 使用Object.assign() 要注意地方:
  21. - 只会拷贝原对象可以遍历的属性值
  22. - 可以拷贝对象的symbol属性值
  23. - 不会拷贝对象的继承属性
  24. ```javascript
  25. let arr1 = ['骑车','打球']
  26. let objectA = {
  27. name:'Lily'
  28. }
  29. let objectB = {
  30. age:12,
  31. hobby:arr1,
  32. friend:{
  33. name:'Summer',
  34. age:15
  35. }
  36. }
  37. let copyA = Object.assign({},objectA);
  38. let copyB = Object.assign({},objectB);
  39. objectA.name = 'Lily2'
  40. objectB.age = 14;
  41. arr1.push("学习");
  42. objectB.friend.name = 'Summer2';
  43. console.log(copyA);
  44. // 输出结果:{ name: 'Lily' }
  45. console.log(copyB);
  46. // 输出结果:
  47. { age: 12,
  48. hobby: [ '骑车', '打球', '学习' ],
  49. friend: { name: 'Summer2', age: 15 }
  50. }
  • 展开运算符

说明:修改了原对象的引用属性的属性值后,复制的对象也会发生改变

  1. let objectC = {
  2. name:'Lily',
  3. age:12,
  4. friend:{
  5. name:'Summer',
  6. age:15
  7. }
  8. }
  9. let copyC = {...objectC};
  10. console.log(copyC); // { name: 'Lily', age: 12, friend: { name: 'Summer', age: 15 } }
  11. objectC.friend.name = 'Summer2';
  12. console.log(copyC);// { name: 'Lily', age: 12, friend: { name: 'Summer2', age: 15 } }
  • concat

    1. let arrayB = [1,2,3,[1,2,3],4,5];
    2. let copyArrayB = arrayB.concat(6,7);
    3. console.log(copyArrayB); // [ 1, 2, 3, [ 1, 2, 3 ], 4, 5, 6, 7 ]
    4. arrayB[3].push('number');
    5. console.log(copyArrayB); // [ 1, 2, 3, [ 1, 2, 3, 'number' ], 4, 5, 6, 7 ]
  • slice

    1. let arrayA = [1,2,3,[1,2,3],4,5];
    2. let copyArrA = arrayA.slice(1);
    3. console.log(copyArrA); // [ 2, 3, [ 1, 2, 3 ], 4, 5 ]
    4. arrayA[3].push('number');
    5. console.log(copyArrA);// [ 2, 3, [ 1, 2, 3, 'number' ], 4, 5 ]
  • 手动实现一个浅拷贝

    1. // 手动实现一个浅拷贝
    2. const shadowClone = (target) => {
    3. if (typeof target === 'object' && target !== null){
    4. const targetClone = Array.isArray(target) ? [] : {} ;
    5. for (let prop in target){
    6. if (target.hasOwnProperty(prop)){
    7. targetClone[prop] = target[prop];
    8. }
    9. }
    10. return targetClone;
    11. }else {
    12. return target;
    13. }
    14. }

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; }

  1. <a name="pwsZJ"></a>
  2. #### 4 - 冻结对象
  3. ```javascript
  4. // 测试对象
  5. let Lily = {
  6. name:'Lily',
  7. age:12,
  8. friends:{
  9. name:'July',
  10. age:16
  11. }
  12. }
  • 常量属性:常量属性即当前属性值不能修改,

    1. // 设置常量属性
    2. Object.defineProperty(Lily , 'name' , {
    3. writable:false,
    4. configurable:false,
    5. enumerable:true
    6. })
  • 禁止对象扩展(Object.preventExtensions()):在当前对象上新增其他属性值无效

    1. Object.preventExtensions(Lily);
    2. Lily.sex = "女";
    3. console.log(Lily);
  • 密封对象(Object.seal(obj)):禁止对象扩展且禁止对象属性配置,但是可以修改属性的值

    1. Object.seal(Lily);
    2. Object.defineProperty(Lily , 'age', {
    3. writable:false,
    4. configurable:false,
    5. enumerable:true,
    6. value:13
    7. })
    8. console.log(Object.getOwnPropertyDescriptor(Lily, 'age'));
  • 完全冻结对象(Object.freeze()):在密封对象的基础上,禁止修改属性值

    1. Object.freeze(Lily);
    2. Lily.age = 14
    3. 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 })

  1. **a - 存在性**<br />两者的区别在于是否查找**原型链**
  2. - in 操作符:检查属性是否存在于对象及其原型链中
  3. ```javascript
  4. console.log('age' in Lily) // true
  5. console.log('sex' in Lily) // false
  6. console.log('test' in Lily) // true
  7. console.log('age' in July) // true
  8. console.log('sex' in July) // true
  • myObject.hasOwnProperty() 函数:只会检查属性是否存在于对象本身中

    1. console.log(Lily.hasOwnProperty('age')); // true
    2. console.log(Lily.hasOwnProperty('sex')); // false
    3. console.log(July.hasOwnProperty('age')); // false
    4. console.log(July.hasOwnProperty('sex')); // true

    b - 可遍历性

  • myObject.propertyIsEnumerable() 函数:判断某个属性是否可遍历,返回布尔值

    1. console.log(Lily.propertyIsEnumerable('age')); // true
    2. console.log(Lily.propertyIsEnumerable('test')); // false

    c - 获取属性
    两者的区别在于是否 过滤不可遍历的属性,三者都可以获取到Symbol属性,都不可以获取到原型链上的属性

  • Object.keys():获取到所有可遍历的属性值

  • Object.getOwnPropertyNames() 函数:获取到所有的属性值,无论当前属性是否可遍历
  • Reflect.ownKeys():获取到所有的属性,无论当前属性是否可遍历
    1. console.log(Object.keys(Lily)); // [ 'name', 'age', 'friends', 'sym' ]
    2. console.log(Object.getOwnPropertyNames(Lily)); // [ 'name', 'age', 'friends', 'sym', 'test' ]
    3. console.log(Reflect.ownKeys(Lily)); // [ 'name', 'age', 'friends', 'sym', 'test' ]
    4. console.log(Object.keys(July)); // [ 'sex' ]
    5. console.log(Object.getOwnPropertyNames(July)); // [ 'sex', 'test2' ]
    6. 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 } ```

5. 对象总结

对象.xmind