JavaScript的对象是JS中唯一一种复杂类型,它可以存其他七种原始值(数字类型Number、字符串类型String、布尔类型BooleanUndefined类型、Null类型、符号类型SymbolBigInt类型)

重点内容提要

  • 声明对象的两种语法
  • 如何删除对象的属性
  • 如何查看对象的属性
  • 如何修改或增加对象的属性
  • 'name' in objobj.hasOwnProperty('name')的区别

声明对象的两三种语法

  • 对象字面量:let obj = {'属性名':属性值,...},工作只用一般这个
  • new构造函数:let obj = new object{'属性名':属性值}
  • ES5方法:var obj = Object.create({...})

删除对象的属性

  • delete obj.xxx
  • delete obj['xxx']
  • obj.xxx = undefined属性值改为了undefined,而并非将该属性完全删除

当运行'xxx' in obj 值为 false 时,可确认已删除xxx属性

查看对象的属性

  • 查看自身独有属性:Object.keys(obj)
  • 查看自身独有加共有属性:console.dir(obj)
  • 两种方法单个属性属性:obj['key'] 或 obj.key,(注意key是字符串)

修改或增加对象的属性

  • 声明时赋值:let obj = {kkk: 'vvv'}kkk 是字符串
  • 直接赋值(点运算符):obj.kkk = 'vvv'kkk 是字符串
  • 直接赋值(方括号运算符):obj['kkk'] = 'vvv'
  • 间接赋值(到变量):let key = 'kkk'; obj[key] = 'vvv'
  • 批量赋值:Object.assign(obj, {age: 18, gender: 'man'})

'name' in objobj.hasOwnProperty('name') 的区别

'name' in obj:无法知道这个属性是对象自身的还是共有的,当自身没有但共有属性里面有这个属性时,也同样返回true。

obj.hasOwnProperty('name'):可以很清楚的知道这个对象的某一个属性是自身的,不会去找共有属性里面

obj.hasOwnProperty('name'):当自身没有,但共有属性里面有这个属性,返回false,即使改变了原型,除非命名了一个和共有属性名字相同的属性。


正文开始


对象的定义

  • 无序的数据集合
  • 键值对的集合

对象的写法

1
2
3
4
var obj = { 'name':'frank', 'age':18 } //工作常用写法,对象字面量
var obj = new Object({'name':'frank'}) //正规写法没人写,new构造函数
console.log({ 'name':'frank', 'age':18 }) //匿名对象
var obj = Object.create({...}) //ES5方法

细节

  • key(键名)是字符串,不是标识符(不以数字开头的)
  • 可以包含任意字符串,即使是空字符串、包含空格、中文、Emoji的字符串皆可,但没人这么用,此时引号不能省略
  • Chrome的控制台返回值有欺骗性,用Object.keys(obj)来查看正确的键名
  • 除了字符串,symbol也能做属性名,但不常用

    1
    2
    
    let obj = { 'name family':'frank', 'age':18 }
    let obj2{ '':1 } //fine~
    
  • 引号可以省略,省略后只能写*标识符*(符合标识符规则的字符)

  • 就算引号省略,键名也还是字符串

属性名

每个key(键)都是对象的属性名(property)

属性值

每个value都是对象的属性值

不规范的属性名

所有属性名会自动变成字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let obj = {
    1:'a',
    3.2:'b',
    1e2:true,
    1e-2:false,
    .234:true,
    0xFF:true
};//< Object { 1: "a", 100: true, 255: true, "3.2": "b", "0.01": false, "0.234": true }

Object.keys(obj); //< Array(6) [ "1", "100", "255", "3.2", "0.01", "0.234" ]

细节

  • 加引号是最安全的键值写法,否则,键名就不一定符合预期
  • Object.keys(obj)可以得到obj的所有key

*变量的值*做属性名

如何用变量做属性名

  • 之前都是用*常量*做属性名
  • 想以变量的值作为属性名,以[]将变量括起来,这是ES6的新增语法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    let  a = 'xxx'
    var obj = {
    a : 1111,
    'a' : 2222,
    [a] : 3333
    }
    
    // ES6之前的做法
    var obj = {}
    obj[a] = 111
    

另一些例子

1
2
3
4
5
6
7
let p1 = 'name'
let obj = {p1:'frankFang'} //这样写,属性名为'p1'
let obj = {[p1]:'frankFang'} //这样写,属性名为'name'
var obj = {
    [1+2+3+4]:'十'
}
// obj {10:"十"} 这里的10是字符串的'10'

对比

  • 不加[]的属性名会自动变成字符串
  • 加了[]则会被当做变量并先求值,再作为字符串,成为属性名
  • 值如果不是字符串,则会自动变成字符串

对象的*隐藏*属性

对象obj,点开控制台看到有个属性__proto__,控制台骗你,__proto__并非指向Object

  • JS中每一个对象都有一个隐藏属性
  • 这个隐藏属性存储着其共有属性组成的对象地址
  • 这个*共有属性组成的对象*叫做原型
  • 也就是说,隐藏属性存储着原型的地址
  • 共有属性省内存

代码示例

1
2
var obj = {}
obj.toString() //不报错
  • 不报错是因为obj的隐藏属性对应的对象上有toString
  • 原型即共有属性
  • 内存举例:#409存着toString()valueOf()hasOwnProperty()
  • 定义一个对象obj2,地址#901,里面有__proto__属性指向#409
  • 这个对象就是原型,而#409是它的地址,#409所代表的这块内存所表示的对象是原型
  • 它是obj的原型,也是任何一个新对象的原型

one more thing…

  • 除了字符串,symbol也能做属性名

    1
    2
    
    let a = Symbol()
    let obj = { [a]: 'Hello' }
    

目前(2020)不常用,在学「迭代」的时候会用到


增删改查

「增删改查」对象的属性


增加属性(写入属性)

直接赋值

1
2
3
let obj = {name: 'frank'} //name自动变为字符串
obj.name = 'frankfang' //name自动变为字符串
obj['name'] = 'fangfang'

obj[name] = 'frank 错误,因为name 值不确定,不信,再看

1
2
obj['na' + 'me'] = 'frank'
let key  = 'name'; obj[key] = 'frank'

let key = 'name'; obj.key = 'frank' 错误,因为obj.key等价于obj['key']

增加属性(写入属性),即属性设置又称为属性赋值

增加属性和改变属性的方法一样,其中,点运算符和方括号运算符这两种方法,与属性查询相同

1
2
o.p = 'abc';
o['p'] = 'abc';

批量赋值

1
2
let obj = {}
Object.assign(obj,{age: 30, gender: 'oldman'})

增加共有属性

无法通过自身,来增加共有属性

1
2
3
let obj = {},obj2{} //共有toString
obj.toString = 'xxx' //只会在改obj的自身叫toString的属性
obj2.toString //还是在原型链上

硬要增加原型链上的属性

1
2
obj.__proto__.toString = 'xxx' //不推荐用__proto__
Object.prototype.toString = 'xxx'

删除属性

delete obj.xxxdelete obj['xxx']

  • 即可删除objxxx属性
  • 请区分「属性值为undefined」和「不含属性名」
  • delete obj['xxx']注意[]里的引号"",要求是一个字符串的形式

不含属性名

1
2
let obj = {}
'xxx' in obj === false //验证是否删除属性

含属性名,但值为undefined

1
2
3
4
5
let obj = {}
obj.xxx = undefined
'xxx' in obj && obj.xxx === undefined //true
'x' in obj //false
'x' in obj2//true

注意obj.xxx === undefined

  • 所以obj.xxx === undefined不能断定xxx是否为obj的属性

    1
    2
    3
    4
    5
    
    var obj = {}
    var obj2 = {xxx: undefined}
    /* 判断 */
    obj.xxx === undefined //true
    obj2.xxx === undefined //true
    

严谨:没有有但undefined是不一样的

1
2
3
var obj = {name: 'frank', age: 18}
obj.name = undefined //仅删掉属性值,未删掉属性名
delete obj.name //删掉属性名,属性值自然就被同时删掉
  • 就算你删掉属性后再同样地删一次,JS也不报错
  • 所以要验证'xxx' in obj,注意属性名一定要加引号

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    var obj = {name: 'frank', age: 18}
    obj.name = undefined
    
    'name' in obj //true
    'name' in obj && obj.name === undefined //true
    
    delete obj.name
    delete obj['name']
    
    //验证是否还存在属性'name'
    'name' in obj === false //true
    'age' in obj === false //false
    
  • 只能用'xxx' in obj 查看属性名是否还在对象中:true表示在,false表示不在

  • 'xxx' in obj && obj.xxx === undefined返回true,表示属性xxx还在obj中,而且属性xxx的值是undefined

  • obj.xxx === undefined不能断定'xxx' 是否为obj的属性。

    1
    2
    3
    4
    5
    6
    
    let obj = {}
    let obj2 = {xxx: undefined}
    obj.xxx === undefined //true
    obj2.xxx === undefined //true
    obj.xxx === null //false null空占位符
    obj2.xxx === null //false null空占位符
    

小结

判断对象是否含有某个属性名

'xxx' in obj

判断对象的某个属性的属性值是否为空,预期值true

'xxx' in obj && obj.xxx === undefined

delete操作符用于删除对象的属性

那怎么删掉对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var obj  = {}
window.obj //{} 自动变成window的属性
obj === window.obj //true
delete obj //
obj //{} 还在

var o = {
    a : 1
};
console.log(o.a);//1
console.log('a' in o);//true
console.log(delete o.a);//true
console.log(o.a);//undefined
console.log('a' in o);//false

delete 操作符 MDN


改变属性(值)

原来没有某个属性,就是增加;有某个属性,就是修改

修改属性的值,即赋值

增加或修改属性(写入属性)

直接赋值 增加或修改

原来有某个属性,就是修改,确切地分为覆盖掉原来所有的属性值即修改属性值,还是增加新属性,方法一样

1
2
3
4
// 开发不用var 再次赋值不报错,会覆盖掉原来的所有属性,所以用let
let obj = {name: 'frank'} //name自动变为字符串,声明时赋值
obj.name = 'frankFang' //name自动变为字符串,直接赋值
obj['name'] = 'fangFang' //直接赋值

obj[name] = 'frank' 错误,因为name 值不确定,不信看

1
2
3
4
5
6
7
8
9
let obj = {}
obj[name] = "fang"
name //""
Object.keys(obj) //Array[""]
console.dir(obj) //{"":"fang",...}
obj.hasOwnProperty("") //true
Object.values(obj) //["fang"}
Object.entries(obj)
obj[''] //"fang"

obj[]里是通过运算后得到的属性

1
2
obj['na' + 'me'] = 'frank' //经过拼接的字符串作为属性
let key  = 'name'; obj[key] = 'frank' //声明变量,变量的值为属性值

let key = 'name'; obj.key = 'frank' 错误,因为obj.key等价于obj['key']

批量赋值,批量增加属性(ES6)

1
2
let obj = {}
Object.assign(obj,{age: 30, gender: 'oldMan'})

assign就是赋值的意思

增加或修改共有属性

无法通过自身,来增加或修改共有属性

1
2
3
let obj = {},obj2 = {} //共有toString
obj.toString = 'xxx' //只会在改obj的自身叫toString的属性
obj2.toString //还是在原型链上
  • JS设计成读的时候,回去看对象隐藏属性发准考的原型;写的时候,不会改掉原型,只能写到自身的属性上
  • 修改单个不同对象的共有属性不会互相覆盖

硬要增加或修改原型链上的属性

1
2
3
4
5
6
7
obj.__protp__.toString = 'xxx' //不推荐用__proto__

obj.__proto__ === window.Object.prototype //true 同一个地址

//硬要增加或修改原型链上的属性,就用
Object.prototype.toStirng = 'xxx'
window.Object.prototype.toStirng = 'xxx'

谨慎修改原型,可能引起很多问题,这就是JS脆弱的地方,可以随时修改任意对象的原型属性,毁掉整个对象体系

1
2
3
4
var obj = {name: "fang", age:false}
console.dir(obj)
obj.__proto__ = null
console.dir(obj) //得到一个没有原型的“纯”对象,但没一点儿卵用

修改隐藏属性

不推荐使用__proto__,性能低

1
2
3
4
5
6
7
8
9
let obj = {name:'frank'}
let obj2 = {name:'fang'}
let common = {kind:'human'}

/* 不推荐 */
obj.__proto__ = common
obj2.__proto__ = common
console.dir(obj)
console.dir(obj2)

在原型中加了一个节点,原型后面还有原型,即原型链

推荐使用Object.create(common),(ES6)新方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let common = {kind:'human'}
/* 推荐 */
let obj = Object.create(common)
obj.name = 'frank'
let obj2 = Object.create(common)
obj2.name = 'Fang'
console.dir(obj)
console.dir(obj2)
console.dir({obj,obj2})
console.dir([obj,obj2])

Object.create(common) MDN

1
2
3
4
5
6
var common = {'国籍':'中国', hairColor: 'gold'}
var person = Object.create(common)
console.dir(person)
var person = Object.create(common,{
    name:{value:'frank'} //很麻烦
})

一创建时,就告指定原型

规范:要改就一开始就该,别后来再改


查看属性

分查看所有属性,和查看单个属性

查看所有属性(读取属性)

仅查看自身独有属性
  • Object.keys(obj)
  • 返回一个包含所有属性名字符串的数组
查看独有 + 共有 的所有属性
  • console.dir(obj)
  • 一目录的形式打出来,但chrome控制台瞎返回了一个Object,点开可以看到自身 + 共有 的所有属性__proto__
  • 或者自己依次用Object.keys()在控制台打印出obj.__proto__,这种方法不推荐,不规范
判断一个属性是自身的独有的,还是共有的:obj.hasOwnProperty(‘propertyName’)
  • 'toString' in obj //true是不会区分属性是自身的,还是共有的
  • 'xxx' in obj && obj.xxx === undefined返回true,表示属性xxx还在obj中,而且属性xxx的值是undefined
  • obj.xxx === undefined不能断定'xxx' 是否为obj的属性
  • 'xxx' in obj 只用来查看属性名是否还在对象中:true表示在,false表示不在
  • 只能用obj.hasOwnProperty('toString'),返回false说明不是共有属性
仅查看所有属性的值
  • Object.values(obj)
  • 返回一个包含所有属性值字符串的数组
既要看名,又要看值
  • Object.entries(obj)
  • 返回一个数组,这个数组包含两个数组
  • 一个数组包含所有属性名字符串
  • 另一个包含所有属性值字符串

补充插入

原型

每个对象都有原型

  • 原型里存着对象的共有属性
  • 比如obj的原型就是一个对象
  • obj.__proto__存着这个对象的地址
  • 这个对象里有toString/constructor/valueOf等属性

对象的原型也是一个对象

  • 所以对象的原型也有原型
  • obj = {}的原型即为所有对象的原型
  • 这个原型包含所有对象的共有属性,是对象的根
  • 这个原型也有原型,是null

    1
    2
    3
    
    console.log(obj.__proto__)
    console.log(obj.__proto__.__proto__) //null
    '__proto__' in obj.__proto__ //true
    

把根对象的原型人为地指定为空值null

原型之属性继承

对象都和某一个对象相关联,这个对象即原型,每一个对象都从原型继承属性。

所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过Object.prototype获得对原型对象的引用

1
2
var obj = {};
console.log(obj.__proto__ === Object.prototype);//true

对象本身具有的属性叫自有属性(own property),从原型对象继承而来的属性叫继承属性

1
2
3
4
5
6
7
var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
//继承自原型对象o的属性a
console.log(obj.a);//1
//自有属性b
console.log(obj.b);//2

in操作符可以判断属性在不在该对象上,但无法区别自有还是继承属性

1
2
3
4
5
6
var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
console.log('a' in obj);//true
console.log('b' in obj);//true
console.log('b' in o);//false

通过hasOwnProperty()方法可以确定该属性是自有属性还是继承属性

1
2
3
4
5
var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
console.log(obj.hasOwnProperty('a'));//false
console.log(obj.hasOwnProperty('b'));//true

补充插入完毕


查看单个属性

两种方法查看单个属性

属性查询一般有两种方法,包括点运算符和方括号运算符

1
2
3
4
5
var o = {
  p: 'Hello World'
};
o.p // "Hello World"
o['p'] // "Hello World"
  • 中括号语法:obj['key'],最明确
  • 点语法:obj.key,易被误导成key不是字符串,其实是字符串
  • 坑新人语法:obj[key],其变量key值一般不为字符串'key'

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    var obj = {name: 'frankfang', age: 30}
    obj['name'] //'frankfang'
    obj.name //'frankfang'
    obj[name] //undefined
    
    name //""
    window.name = 'age'
    obj[name] //18
    
    obj['na'+'me'] //'frankfang',在对象字面量内部对属性名使用表达式,则需要使用ES6的可计算属性名
    
    obj[console.log('name')] //undefined,打印出的是name
    
    console.log('name') //返回的都是undefined,打印出的是name
    
    obj[console.log('name')] //相当于obj['undefined'],打印出的是name
    

如果要在对象字面量内部对属性名使用表达式,则需要使用ES6的可计算属性名

1
2
3
4
5
var a = 1;
var person = {
    [a + 3]: 'abc'
};
person[4];//'abc'
优先使用中括号语法
  • 点语法会误导,让人误以为.key不是字符串
  • 等能够确定不会混淆两种语法,再改用点语法
  • 比如用点语法时,就加注释

重要知识点

  • 读对象的属性时, 如果使用 [ ] 语法,那么 JS 会先求 [ ] 中表达式的值

注意区分表达式是变量还是常量。

  • 如果使用点语法,那么点后面一定是 string 常量。

易混淆的

  • obj.name等价于obj['name'](注意这里的[]中是字符串'name'
  • obj.name不等价于obj[name](注意这里的[]中是变量name

简单说来,.name是字符串'name',而不是变量

再举一例

1
2
3
4
5
6
7
8
let obj = {name:'fang'}
let name = 'frankFang'
/* 注意以下 */
obj[name] === obj['frankFang'] //true
obj[name] === obj['name'] //false
obj[name] === obj.name //false
obj['name'] === obj['frankFang'] //false
obj['name'] === obj.name //true

obj[name]等价于obj['frankfang'],而不等价于obj['name']

而不是obj['name']obj.name

面试就看分清变量name和常量字符串'name'

记清obj.nameobj['name']obj[name]三者的区别

  • 必须搞清的面试题

    1
    2
    3
    4
    5
    6
    7
    8
    
    let list = ['name','age','gender']
    let person  = {
    name: 'xiaofang', age: 30, gender: 'femaleMaybe'
    }
    for (let i = 0; i < list.length; i++){
    let name = list[i]
    console.log(person__???__)
    }//打印person的所有属性
    

填空(坑)

区分.name'name'为什么这么重要

  • vue的时候就知道厉害了

总结

1
2
3
4
5
6
let obj =  {name: 'fang'}
/* 只能删属性 */
delete obj['name']
'name' in obj  //false 没办法判断是自身/共有属性
obj.hasOwnProperty('name') //false 有可能这个属性是在原型上的,或者根本没有这个属性
obj = null //交给垃圾回收机制

1
2
3
4
5
6
7
let obj =  {name: 'fang'}
/**/
Object.keys(obj)
console.dir(obj)
obj['name']
obj.name //记住这里的name是字符串
obj[name] //记住这里的name是变量

  • 改自身obj['name'] = 'jack'
  • 批量改自身Object.assign(obj,{age:18,...}),先赋值空对象
  • 改共有属性obj.__proto__['toString'] = 'xxx',不推荐
  • 改共有属性Object.prototype['toString'] = 'xxx'
  • 改原型obj.__proto__ = common,不推荐
  • 改原型let obj = Object.create(common)
  • 所有__proto__的代码都是墙裂不推荐写的,不可用于业务代码中,仅供学习测试

增(改),属于写

基本同上:已有属性则改;没有属性则增

查属于读,查的时候会看原型链

增改时不会看原型链,增改都是自身

1
2
3
4
5
/* 区分Object.create() 和new Object()*/
var obj1 = Object.create({name:'frank'}) //增改原型属性
console.dir(obj1)
var obj2 = new Object({name:'frank'}) //增改自身属性
console.dir(obj2)

原型是个对象,共有属性是原型对象的所有属性;原型包含共有属性,共有属性依附于原型对象上


·未完待续·

参考文章

JS 对象.pdf

入门《网道 JavaScript 教程》

相关文章