JavaScript 系列博客(四)
JavaScript 系列博客之(四)
前言
本篇介绍 JavaScript 中的对象。在第一篇博客中已经说到 JavaScript 是一种‘’对象模型‘’语言。所以可以这样说,对象是 JavaScript 语言的核心概念,也是最重要的数据类型。
概述
生成方法
在 JavaScript 中声称对象相当方便,直接定义一个空字典就 ok。想要添加属性或者方法的话可以在定义结束之后动态添加。注意:对象时无序的复合数据集合。
上面代码中,大括号就可以直接定义一个对象,被赋值给变量 a,所以 a 就指向一个对象。该对象为一个空对象,但是会有一些默认的方法,像 constructor 是构造方法,想要动态的添加属性和方法就是这个方法的功劳。
在这里添加了一个属性为name,那么 name 是键名(成员名称),字符串 musibii 是键值(成员的值)。键名与键值之间用冒号分隔。如果再添加一个属性,那么属性之间使用逗号分隔。
具体生成方法
// 1.单一对象
var obj = {
// 属性
name: 'Zero',
// 方法
teach: function () {
console.log("教学");
}
};
obj.name | obj.teach()
// 2.构造函数
function Person(name) { // 类似于python中的类一样来使用
// this代表Person构造函数实例化出的所有具体对象中的某一个
this.name = name;
this.teach = function () {
console.log(this.name + "正在教学");
}
}
// ①通过构造函数实例化出具体对象
// ②通过对象.语法调用属性与方法
var p1 = new Person("张三");
p1.name // 张三, this指向p1对象
var p2 = new Person("李四");
p2.teach // 李四正在教学, this指向p2对象
// 3.ES6类语法
class Student {
// 需要构造器(构造函数)来完成对象的声明与初始化
constructor (name) {
// 属性在构造器中声明并完成初始化
this.name = name;
}
// 类中规定普通方法
study () {
console.log(this.name + "正在学习");
}
// 类方法
static fn() {
console.log("我是类方法")
}
}
// 类中的普通方法由类的具体实例化对象来调用
// 类中的类方法由类直接来调用(这类型的方法大多是功能性方法,不需要对象的存在)
键名
对象的所有键名都是字符串(ES6又引入了 Symbol 值也可以作为键名:还没了解过),所以加不加引号都可以。如果键名是数值,会被自动转为字符串。如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。
对象的每一个键名又称为‘’属性‘’,它的键值可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为方法,调用方法和函数一样。
特别的如果属性的值指向的还是一个对象,那么就行成了链式引用。对象的属性之间用逗号分隔,最后一个属性后面可以加逗号,也可以不加。
对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有的引用。和 JavaScript 中的基本数据类型不一样,复合数据类型是传址传递。
a 和 b指向同一个对象,因此为其中任何一个变量添加属性,另一个变脸都可以读写该属性。如果取消某一个对象的引用,不会影响到其他变量。
这种引用只局限于对象,在之前的博客也提到,两个变量指向同一个原始类型(基本数据类型)的值,那么变量只是对值得拷贝(传值传递)。
属性的操作
属性的读取
读取对象的属性,有两种方法,一种是使用点运算符;另一种是使用方括号运算符。(在 python 中,字典只能通过方括号取值;对象只可以通过点运算符取值。不过可以通过自定义字典类改写 getattr 魔术方法改变。)
注意:如果使用方括号运算符,键名必须放在引号里面,否则会被当做变量处理。
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo]// 2
上面代码中,引用对象obj 的 foo 属性时,如果使用点运算符,foo 就是字符串;如果使用方括号运算符,但是不使用引号,那么 foo 就是一个变量,指向字符串 bar。
方括号运算符内部还可以使用表达式:
obj['hello' + 'world']
obj[3 + 3]
数字键可以不加引号,因为会自动转为字符串。
var obj = {
0.7: 'hello world'
};
obj['0.7'] // 'Hello World'
obj[0.7] // 'Hello World'
上面代码对象的数字键0.7加不加引号都可以,因为会自动转为字符串。
var obj = {
123: 'Hello musibii'
};
obj.123 // 报错
obj[123] // 'Hello musibii'
如果对数值键名123使用点运算符,会报错,使用方括号运算符才是正确的方式。
属性的赋值
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
JavaScript 允许属性的后绑定,也就是说可以在任意时刻新增属性,没必要在定义对象的时候就把属性全都定义好。
属性的查看
查看一个对象的所有属性使用 Object.keys 方法。
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj); // [key1, key2]
属性的删除
delete 命令用于删除对象的属性,删除成功后返回 true。
var obj = {
p: 1;
}
delete obj.p; // true
obj.p // undefined
Object.keys(obj) // []
上述代码中,delete 命令删除对象 obj 的 p 属性。删除后,再读取 p 属性就会返回 undefined,而且 Object.keys 方法的返回值也不再包括该属性。
注意:删除一个不存在的属性,delete 不会报错而是返回 true。因此不能根据 delete 命令的结果认为某个属性的存在。(那么到底哪种方式才可以证明某个属性的存在与否)
如果删除属性时返回 false那就说明该属性存在,但是不可以删除。
var obj = Object.defineProperty({}, 'p', {
value: 'musibii',
configurable: false
});
obj.p // 'musibii'
delete obj.p // false
上述代码中,通过 Object 的defineProperty方法给对象 obj创建了一个属性,属性的configurable(可配置) 的值为 false,这样的一个属性就是不可以删除的。
另外需要注意的是,delete 命令只能删除对象本身的属性,无法删除继承的属性。
可以看出虽然 delete 命令返回 true,但是删除的属性依然存在。但是如果通过 proto 删除的话就可以删除。
判断属性的存在
in 运算符用于检出对象是否包含某个属性(注意,检查的是键名,不是键值)。如果包含就返回 true,否则就返回 false。它的左边是一个字符串,表示属性名,右边则是一个对象。
var obj = {p: 1};
'p' in obj //true
'toString' in obj // true
拿上面删除的 constructor 来说:
in 运算符的一个问题是,它不能识别哪些属性时对象自身的,哪些属性是继承的。就像上面,对象 obj 本身并没有 toString 属性,但是 in 运算符会返回 true,因为这个属性是继承的。
这时可以通过对象的 hasOwnProperty 方法判断,是否为对象自身属性
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')); // fasle
}
属性遍历
for...in 循环用来遍历一个对象的所有属性。
for...in 循环有两个注意点;
- 它遍历的是对象所有可遍历的属性,会跳过不可遍历的属性;
- 它不仅遍历对象自身的属性,还遍历继承的属性。
如果继承的属性是可遍历的,那么就会被 for...in 循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用 for...in 的时候,应该结合使用 hasOwnProperty 方法,在循环内部判断一下,某个属性是否为对象自身的属性。
var person = {name: '老张'};
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
} // name
with 语句
with 语句的格式如下:
with (对象) {
语句;
}
它的作用是操作同一个对象的多个属性时,提供一些书写的方便。
注意:如果 with 区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。
var obj = {};
with (obj) {
p1 = 4,
p2 = 5
}
obj.p1 // undefined
p1 // 4
上面代码中,对象 obj 并没有 p1属性,对 p1赋值等于创造了一个全局变量 p1.正确的写法应该是,先定义对象 obj 的属性 p1,然后在 with 区块内操作它。
这是因为 with 区块没有改变作用域,它的内部依然是当前作用域。这造成了with 语句的一个很大的弊病,就是绑定对象不明确。
with (obj) {
console.log(x);
}
单纯从上面的代码块,根本无法判断 x 到底是全局变量,还是对象 obj 的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用 with 语句,可以考虑用一个临时变量代替 with。
with (obj1.obj2.obj3) {
console.log(p1 + p2);
}
// 可以写为
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);