计算属性

在引入计算属性之前,如果想使用变量的值作为属性,就必须先申明对象,然后使用中括号语法来添加。如下:

  1. const nameKey = 'name'
  2. const ageKey = 'age'
  3. const jobKey = 'job'
  4. let person = {}
  5. person[nameKey] = 'zhangsan'
  6. person[ageKey] = 30
  7. person[jobKey] = '游戏达人'
  8. console.log(person)//{name: 'zhangsan', age: 30, job: '游戏达人'}

但有了计算属性后,我们就可以在对象字面量中完成动态属性赋值

  1. const nameKey = 'name'
  2. const ageKey = 'age'
  3. const jobKey = 'job'
  4. let person= {
  5. [nameKey]:'zhangsan',
  6. [ageKey]: 30,
  7. [jobKey]: '游戏达人'
  8. }
  9. console.log(person)//{name: 'zhangsan', age: 30, job: '游戏达人'}

方括号语法

方括号的语法 ,可以用于 设置复杂的属性名和以变量的方式访问属性。如下:

  1. let user = {
  2. name: "John",
  3. age: 30,
  4. job:'软件工程师'
  5. }
  6. // 这将提示有语法错误
  7. user.likes birds = true
  8. // 正确
  9. user["likes birds"] = true;
  10. console.log(user["likes birds"])//true
  11. let key = 'job'
  12. console.log(user.key) //undefined
  13. console.log(user[key]) //软件工程师

点符号要求 key 是有效的变量标识符。这意味着:不包含空格,不以数字开头,也不包含特殊字符(允许使用 $ 和 _)。

属性存在性测试

javascript 中,当对象不存在某个属性,但又被访问时,并不会报错,而是会得到undefined,如下:

  1. let user = {};
  2. console.log( user.noSuchProperty === undefined ); // true 意思是没有这个属性

所以当我们要检查是否存在某个属性时,可以这样来检测。但有一种情况会存在问题,那就是属性值是‘undefined’的情况

  1. let user = {
  2. name:undefined
  3. };
  4. console.log( user.name === undefined );//显示 true,所以属性不存在吗?

这种情况很少发生,因为通常情况下不应该给对象赋值 undefined。我们通常会用 null 来表示未知的或者空的值。
为了解决这种情况,我们可以使用 in 操作符,语法如下:

  1. "key" in object
  2. let user = { name: "John", age: 30 ,job:"undefinde"};
  3. console.log( "age" in user ); // true,user.age 存在
  4. console.log( "blabla" in user ); // false,user.blabla 不存在。
  5. console.log( "job" in user );//true user.job 存在
  6. //做一个比较 这里就是不正确的结果
  7. console.log( user.job === undefined );//显示 true,但这个属性是存在的

属性枚举顺序

Object具有特别的顺序:

  • 非负整数会最先被列出,排序是从小到大的数字顺序
  • 其他属性则按照创建的顺序显示 ```javascript const symbol1 = Symbol(49); const symbol2 = Symbol(41); let codes = { “49”: “Germany”, “41”: “Switzerland”, “name”:’zhangsan’, “age”:30, ‘1.2’:’小数’, “-23”:’负数’, “44”: “Great Britain”, “1”: “USA”, symbol1:49, symbol2:41, };

for(let code in codes) { console.log(code); } // 1,41,44,49,name,age,1.2,-23,symbol1,symbol2

console.log(Object.keys(codes)) //[ “1”, “41”, “44”, “49”, “name”, “age”, “1.2”, “-23”, “symbol1”, “symbol2” ]

  1. - for-in 循环和 Object.keys()的枚举顺序是不确定的,取决于 JavaScript 引擎,可能因浏览器而异(**目前测试到 edge firefox chrome 的输出是一样的 同上面的例子一样**)
  2. - Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign()的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键
  3. <a name="U1ks6"></a>
  4. ### 浅克隆深克隆
  5. ```javascript
  6. let user = {
  7. name: "John",
  8. age: 30
  9. };
  10. let clone = {}; // 新的空对象
  11. // 将 user 中所有的属性拷贝到其中
  12. for (let key in user) {
  13. clone[key] = user[key];
  14. }
  15. // 现在 clone 是带有相同内容的完全独立的对象
  16. clone.name = "Pete"; // 改变了其中的数据
  17. console.log( user.name ); // 原来的对象中的 name 属性依然是 John

我们也可以使用 Object.assign 方法来达成同样的效果。

clone = Object.assign({}, user)//{name: 'John', age: 30}

//如果被拷贝的属性的属性名已经存在,那么它会被覆盖:
clone = Object.assign( user,{age:40})//{name: 'John', age: 40}

以上的两个方式都只是一个浅克隆,当被复制的对象中有对象时,那么只会复制其引用。如下:

let user = {
  name: "John",
  age: 30,
  job:{
  title:'软件工程师',
  company:'xxx科技'
  }
};
let clone = Object.assign({}, user)
console.log(clone)
//age: 30
//job: {title: '软件工程师', company: 'xxx科技'}
//name: "John"

user.name = 'chentao',
user.job.company = 'yyy科技'
console.log(clone)
//修改了user,clone也被改变了
//age: 30
//job: {title: '软件工程师', company: 'yyy科技'}
//name: "John"

如上,当对象中还有对象时,浅克隆的方式复制的是对象的引用。当我们修改user或则clone,就会造成另一个对象也跟着修改了。
为了解决这个问题,我们应该使用一个循环来检查 user[key] 的每个值,如果它是一个对象,那么也复制它的结构。这就是深拷贝,如下的示例:

function cloneDeep(source,clone = {}){
  for(let key in source) {
    if(source[key] instanceof  Object ){
      clone[key] = {}
      cloneDeep(source[key],clone[key])
    }
    else{
      clone[key] = source[key];
    }
  }
  return clone
}

这个方法只是对正常对象的处理,并没有考虑太多其它情况,如数组/Date对象。一般我们会采用现有的实现,例如 lodash 库的 _.cloneDeep(obj)

合并对象

ECMAScript 6 专门为合并对象提供了 Object.assign()方法。这个方法接受一个目标对象和一个或多个源对象。assign()会将源对象中自身可枚举的属性浅复制给目标对象。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标 对象上的[[Set]]设置属性的值。
如下:


/** 
 * 浅复制
 */ 
dest = {}; 
src = { id: 'src',foo:{name:111} }; 
result = Object.assign(dest, src);
console.log(dest.foo === src.foo) //true
/** 
 * 不可枚举的属性不会被合并到目标对象
 */
dest = {}; 
src = { id: 'src',foo:{name:111} }; 
Object.defineProperties(src, { 
 enumerable1: { 
  value:'enumerable1',
   enumerable: false
 }, 
  enumerable2: { 
   value:'enumerable2',
   enumerable: true
 }
})
const result = Object.assign(dest, src)
console.log(result)
//enumerable2: "enumerable2"
//foo: {name: 111}
//id: "src"
/** 
 * 通过get和set来观察合并过程
 */
dest = {
  set foo(value){
    console.log(" invoke set:",value)
  }
}; 
src = {
  get foo(){
     console.log("invoke get:",'bar')
     return 'bar';
  }
}
result = Object.assign(dest,src)
//invoke get: bar
//invoke set: bar

// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(result); // {}