前言
这里就不再赘述对象(Object)与面向对象(OOP)概念,请参考 这篇文章,时间有限敬请谅解。
创建
简单说对象(Object) 就是由一组 键值对(key-value) 组成,是一种无序的复合数据集合,创建对象的几种方式:
// new 关键字
var obj = new Object()
obj.x = 1
obj.y = 0
// 花括号(不囊括属性/值)
var obj = {}
obj.x = 1
obj.y = 0
// 花括号(常见)
var obj = {
x: 1,
y: 0
}
第三种为常见创建对象写法,我们参照这种写法来谈一谈,首先大括号中定义了一个对象,并赋值为变量 obj ,该对象内部包含两个键值对(成员),其中 第一对(x:1) 的键名是 x ,键值是 1 ,键名与键值之间使用冒号(:)隔开,第二对亦是如此。
键名 / 键值
每个对象的键名都是 字符串(String) 类型,所以引号加不加无所谓:
var obj = {
'x': 1,
"y": 0
}
如果你键名不加引号,就表示同意 JS 为你自动转为字符串,再来看一下键名为数字会是怎样:
// 键名为数字(不允许obj.number)
var obj = {
1: 'a',
3.2: 'b',
1e2: true
}
// 尝试访问
console.log(obj['1'])//a
console.log(obj["3.2"])//b
console.log(obj['1e2'])//undefined
// undefined ?
// 因为当键名为数字时会自动转为字符串(1e2=100)
console.log(obj["100"])//true
以上代码,再次证明 JS 会自动将数值转为字符串,但要记住,这条规定是有条件的,请看下面。
如果键名不符合标识名的条件(如第一个字符为数字或者含有空格特殊字符或运算符),且也不是数字,则必须加上引号:
// Error
var obj = {
66error: false
}
// !Error
var obj = {
'66error': true
}
console.log(obj['66error'])//true
由于键名不符合条件,所以请务必加上引号,否则会抛出错误。
对象的每一个键名又称为属性(property),它的键值可以是任何数据类型,如果一个属性的值为函数(function),通常把这个属性称为方法(method),它可以像函数那样调用:
var obj = {
fun: function (even){
return even
}
}
console.log(obj.fun(9))//9
很好理解,对象 obj 的 fun 属性指向一个函数(匿名),当访问它时触发。
如果对象属性的值还是一个对象(嵌套),这就形成了链式引用:
// 对象嵌套
var obj = {
new_obj: {a: '访问到我了!'}
}
// 链式引用
console.log(obj.new_obj.a)//访问到我了!
可以看到,对象 obj 的属性 new_obj 指向另一个对象,其属性有 a ,所以我们可以链式访问(依次访问)。
对象的属性之间用逗号(,)分隔,如果其属性不是最后一个,那么属性后面必须加逗号,如果是最后一个,加不加无所谓:
// Error( x 不是最后一个属性)
var obj = {
x: 1
y: 2
}
// !Error
var obj = {// 不加也行
x: 1,
y: 2
}
var obj = {// 加也行
x: 1,
y: 2,
}
第一种(Error)触犯了规则,抛出错误,而后两种(!Error)遵守了规则,正确解析。
属性可以动态创建与移除,在对象声明时就指定属性与值不是必须的:
// 创建对象(null)
var obj = {}
// 动态添加属性/值(也可以使用方法)
obj.a = 1
obj.b = 0
// 访问
console.log(obj.a,obj.b)//1 0
// 动态移除属性(同样可以使用方法)
delete obj.a
// 尝试访问 a
console.log(obj.a)//undefined
首先创建了一个空对象 obj ,然后为其动态添加属性与值 a / b ,访问正确,最后动态移除属性与值 a ,由于不存在了,则返回 undefined 。
对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址,修改其中一个变量,会影响到其他所有变量:
// o1 / o2 指向同一个内存地址(对象)
var o1 = {}
var o2 = o1
// 添加属性
o1.a = 1
// o1 / o2 尝试访问
console.log(o1.a)//1
console.log(o2.a)//1
由于 o1 / o2 指向同一个内存地址,也就是指向同一个对象,所以当为 o1 添加属性后,o2 也会获得。
此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量(取消了指向,内存地址还是原来的东西):
// 对象
var o1 = {}
var o2 = o1
// o1 改变指向
o1 = true
// 尝试访问 o2
console.log(o2)//{}
当 o1 改变指向后(指向true内存地址),原对象所在的内存地址依然未改变,所以 o2 可以访问到。
需要注意的是,这种引用只局限于对象(Object),如果两个变量指向同一个原始类型的值,那么变量这时都是值的拷贝:
// 变量指向原始类型的值
var x = true
var y = x
// 改变 x 的值
x = false
// 尝试访问 x / y
console.log(x)//false
console.log(y)//true
当改变 x 的值后,y 的值不变的原因,就是因为 x / y 并不是指向同一个内存地址,它们之间是拷贝关系,而非引用关系。
属性的赋值
点运算符(.)和方括号运算符([ ]),不仅可以用来读取值,还可以用来赋值:
// 创建对象(null)
var obj = {}
// 添加属性
obj.a = true
obj['b'] = false
// 尝试访问
console.log(obj.a,obj.b)//true false
上述代码,分别使用点运算符和方括号运算符对属性赋值,很好理解。
JS 允许属性的后绑定,也就是说你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性:
// one
var obj1 = {
a: true,
b: false
}
// two
var obj2 = {}
obj2.a = true
obj2['b'] = false
// 验证
console.log(obj1.a,obj1.b)//true false
console.log(obj2.a,obj2.b)//true false
可以看到,正如题目所说,obj1 于 obj2 二者是等价的,但推荐第一种写法(one),因为这样代码更加清晰。
查看一个对象本身的所有属性,可以使用内置 Object.keys 方法:
// Object
var obj1 = {
a: true,
b: false
}
// 枚举所有属性
var s = Object.keys(obj1)
// 查看
console.log(s)//(2) ["a", "b"]
可以看到,已经枚举出了 obj1 的所有属性,跟我们预期的结果相同。
属性的删除
delete 命令用于删除对象的属性,删除成功后返回 true:
// create Object
var obj = {
x: 1,
y: 0
}
// delete(true)
if(delete obj.x){
console.log(`恭喜您,成功删除!`)
}
// console
console.log(obj.x,obj.y)//undefined 0
首先创建一个新对象 obj ,赋予该对象 x / y 两个属性,然后通过 delete 命令删除 x 属性,返回 true ,所以打印出 “恭喜您…”,最后尝试访问 x / y ,结果正是我们预期中的。
注意,删除一个不存在的属性,delete 不仅不报错,而且还会返回 true:
var obj = {
x: 1,
y: 0
}
if(delete obj.error){
console.log(`您删除了一个不存在的属性!`)
}
//您删除了一个不存在的属性!
可以看到,控制台成功打印出了提示信息,正如上面所说。
只有一种情况 delete 命令会返回 false ,那就是该属性存在但不得删除,这里用 “对象冻结” 来谈:
如果你听到对象冻结没有勾起记忆(或是没学过),请点击这篇文章。
// Object
var obj = {
x: 1,
y: 0
}/
// 冻结对象(Object.freeze()方法)
Object.freeze(obj)
// 尝试删除其属性
if(delete obj.x){
console.log(`true`)
}else{
console.log(`false`)
}
// false
可以看到,当我冻结(无法添加或删除属性) obj 后, delete 删除操作是无法完成,所以返回 false 是毫无疑问的,另外还有一种情况就是使用 Object.defineProperty 方法同样可以让 delete 命令返回 false,这里不在赘述。
delete 命令无法删除对象继承(prototype),这是肯定的,例如尝试删除对象的 valueOf 方法:
如果你不懂原型和继承,请点击这篇文章。
// 新建一个对象(Object)
var obj = {}
// 查看 obj 有哪些继承方法( prototype 展开后你就会看到一些方法)
console.dir(obj)
// 尝试删除 valueOf 属性与方法
var flag = delete obj.valueOf
console.log(flag)//true
// 再次尝试访问 valueOf
console.log(obj.valueOf)//ƒ valueOf() { [native code] }
首先,新建了一个空对象 obj ,因为每一个对象都在它的 原型(prototype) 下挂载着一些 JS 为我们写好的方法,所以当你创建对象时就会自动继承这些方法,那么理所当然 obj 同样拥有 valueOf 方法,接下来尝试删除该方法,delete 却意外给出了反馈说成功了,但事实上并未成功删除 obj 对象的 valueOf 方法,最后一段代码很好的证明了删除失败(成功的话无法访问)。
检测对象的属性是否存在
通过 in 运算符可以很方便的检查对象是否包含某个属性,如果包含返回 true,反之返回 false:
// 新建对象
var obj = {
x: 1,
y: 0
}
// 检查 obj 是否包含 x 属性
if('x' in obj){
console.log(`包含!`)
}
// 结果:包含!
// 故意检查一个不包含的属性 a
console.log('a' in obj)//false
可以看到,对象 obj 包含 x 属性,所以返回 true 打印 “包含!”,而最后故意检查一个 obj 不包含的属性,理所当然为 false。
in 运算符有一个缺陷,就是它不能识别哪些属性是对象自身携带还是继承过来的,但可以使用 Object.hasOwnProperty 方法来判断属性是否为继承的:
// 新建对象
var obj = {
x: 1,
y: 0
}
// in 运算符存在的问题
console.log('toString' in obj)//true
// 解决:检查 x 是否是继承过来的属性(非继承则返回true,反之false)
console.log(obj.hasOwnProperty('x'))//true
console.log(obj.hasOwnProperty('toString'))//false
可以看到,x 属性是非继承的,所以返回 true,而 toString 则是继承过来的,所以返回 false。
遍历对象的属性
有时我们希望把一个对象的所有属性都遍历一遍,for…in 循环可以用来遍历一个对象的全部属性:
// 新建对象
var obj = {
x: 1,
y: 2,
z: 3
}
// for...in 遍历
for(let i in obj){
console.log(`属性:${i}`)
console.log(`属性值:${obj[i]}`)
}
// 结果
属性:x
属性值:1
属性:y
属性值:2
属性:z
属性值:3
很好理解,它遍历的是对象所有 “可遍历(enumerable)” 的属性,它会跳过 “不可遍历” 的属性,但要注意,它不会遍历继承来的属性,如 valueOf、toString等,因为它们是 “不可遍历” 的属性。
表达式还是语句问题
对象采用大括号({})表示,这导致了一个问题:如果行首是一个大括号,它到底是表达式还是语句?
{ a:1 }
JS 引擎读到上面的代码,会发现两种含义,第一种可能就是认为这是一个表达式,表示一个包含 a 属性的对象;第二种可能就是认为这是一个语句,表示一个代码区块,里面有一个标签 a ,它指向表达式 1。
为了避免,JS 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块:
// console.log 证明
{ console.log(`我是代码块中的语句!`) }//我是代码块中的语句!
// let 证明
{
let a = 0
}
console.log(a)//a is not defined
只有代码块才会执行 console.log 语句,故为代码块,下面的 a 更好的证明了它在独立的代码块。
如果要解释为对象,最好在大括号前加上圆括号,因为圆括号的里面,只能是表达式,所以确保大括号能解释为对象:
({ a:1 }) //{a: 1}
with语句
该语句的作用是操作同一个对象的多个属性时,提供一些书写的方便:
// with 语法
with(对象){
语句
}
var obj = {x: 1, y: 0}
with(obj){
x = true
y = false
}
console.log(obj.x)//true
console.log(obj.y)//false
with 语句是运行缓慢的代码块,尤其是在已设置了属性值时,大多数情况下,如果可能,最好避免使用它。
注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。