JavaScript - 对象(Object)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_44198965/article/details/101388724

前言

这里就不再赘述对象(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区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。

猜你喜欢

转载自blog.csdn.net/weixin_44198965/article/details/101388724