深入对象
深入对象
对象类型
对象存在的意义
很多时候但用一个变量是无法完整描述一个对象的:
- 学生有学号,姓名,各门课程的成绩等等。
- 某个同学的网页设计分数,由平时成绩,,期中作业成绩,期末考核成绩共同组成。
- 某人的社会关系,包括父亲、母亲、兄弟、配偶等等。
总之很多时候我们是需要一种可以将很多内容塞进一个变量并可以随意读写的机制。这机制在C语言里,用的是结构体,在JavaScript里是对象,在Python里是字典,在Java里就是类。
对象的定义
对象定义有两种很常见的方法
第一种,直接用{}定义:
let user0 = {id:1, name:"庄体育"};
let user1 = Object.create({id:1, name:"庄体育"});
不管是直接用=赋值,还是用Object.create
创建,我们都需要用{}
来定义一个对象。
一个对象会有很多种属性,以用户为例,有一个id
属性和一个name
属性。我们把属性名放前面,把值放在后面,属性和属性之间以逗号分割。这两种定义的方式都定义了id
属性等于1
,name属性等于"庄体育"
。
第二种,用new和构造器来定义:
let User = function(id, name){
this.id = id;
this.name = name;
}
let user2 = new User(1,"庄体育");
从语法上,User
是一个函数,但你应该将其看作一个对象的构造器。
接下来的申明方式跟其他面向对象很类似,通过new
和User
这个构造函数来构造对象。
这种方式会比较难理解,还涉及到this
关键字。不过这种方式在申明上更加符合面向对象的表述方式,也更为严谨,因此在大型框架上都会采用类似的定义。
构造器的返回值
默认情况下构造器不需要返回对象,实际上是隐含地返回this
这个对象。 但你还是可以返回对象,如果你显示给出返回值,此时这个返回值会替代this
被返回:
let User = function(id, name){
this.id = id;
this.name = name;
return {id:1, name:"张三"};
}
let user3 = new User(1,"庄体育");
console.log(user3.id, user3.name); // 1 张三
当然一般很少两者混用,要么用this
,要么直接返回对象。
new.target
在函数内部,可以通过new.target
,判断自己是不是在被new
:
let User = function(id, name){
this.id = id;
this.name = name;
console.log(new.target);
}
User(); // 没有被new,new.target==undefined
new User(); // 被new, new.target==User函数
这可以用来进行一些比较有意思的操作:
let User = function(id, name){
this.id = id;
this.name = name;
if(!new.target){
return new User(id,name);
}
}
// 兼容两种写法
let user1 = new User(1,"庄体育");
let user1 = User(2,"张三");
属性引用
最常用的引用方式是用点.
:
let user0 = {id:1, name:"庄体育"};
console.log(user0.id);
console.log(user0.name);
这种方式最常见,你一定已经用过了。比如:
for( let i=; i=array.length; ++i ){
//....
}
其中的length
就是每个数组都会有的属性。
又比如:
tag.innerHTML = "<h1>Hello World!</h1>";
innerHTML
也是每个元素属性。 另一种引用的方式是用类似数组的下标方式,将属性作为下标。一定要注意,此时下标是字符串:
console.log(user0['id']);
console.log(user0['name']);
思考一下
为什么不能是user0[id]
和user0[name]
,不带引号时他们分别是什么意思?
值得注意的是,定义属性时,你也可以写引号,事实上这才是更严谨的写法:
let user3 = {'id':1, 'name':"庄体育"};
console.log(user3.name);
console.log(user3['name']);
属性的添加和移除
对象的属性可以移除也可以随时添加:
let user4 = {'id':1};
user4.name = "庄体育";
console.log(user4.name);
当然用下标方式也可以:
user4['name'] = "庄体育";
用delete
指令可以将指定的属性删除:
delete user4.name;
console.log(user4.name);
你会得到一个undefined
你可能会想知道对象是否包含某个属性,可以用in
指令来进行判断:
let user5 = {'id':1};
console.log(('name' in user5)) // false
if( !('name' in user5) ){
user5.name = "庄体育";
}
console.log('name' in user5); // true
delete user5.name;
console.log('name' in user); // false
可以看到当user5
里不包含name
属性时,'name' in user5
返回false。 当我们给他加上这个属性后,表达式的值就变成true
了。当我们把这个属性删除之后,表达式的值又变回false。
对象方法
如果你还记得函数的定义方式,有一种是这么写的:
let func = function(){
//....
}
我讲过,这应该这样理解,我们先定义一个函数,这个函数没有名字(匿名函数),然后我们将匿名函数赋值给变量func。 那么你可能会产生疑问,尤其是C学的好的同学,func
到底是一个函数,还是一个变量?
回答是,都是。
对于javascript这类语言,函数并没有什么独特的地位,他也是一个对象,当然也可以被赋值,所以比较准确的说法是,func这个变量指向一个匿名函数。
既然函数可以被赋值给变量,那么他当然也可以被赋值给对象的属性:
let user6 = {
id:1,
name:"庄体育",
sayHello: function(to_who){
console.log("Hello "+to_who);
}
}
user6.sayHello("Tom");
Hello Tom
这个代码里。user6
对象有3个属性,其中id
和name
和之前的属性一样。但多了一个属性sayHello
,这个属性是一个函数(匿名的,和前面的函数赋值变量对比下)。
我们通过user6.sayHello()
就可以调用这个函数。如果说跟其他函数调用有什么不同的话。就是他属于某个属性。
事实上这种用法你们也用过了:
let tag = document.getElementById("output");
函数getElementById
(以及其他三个函数),都是document
这个全局对象的属性。
注意,函数作为一个属性时,我们一般会使用面向对象的术语方法。就像对象的内部变量,我们会使用属性这个术语一样。
this对象
上一节的那种用法,显然不是常见的场景,我们把函数放进对象里,当然是为了函数和对象可以互动,否则一点意义都没有。 以上文的例子来说,就是我们可能更希望Hello 庄体育
,或者像庄体育:Hello Tom
这样,name
属性可以参与到函数中来。那么作为函数的sayHello
要如何访问到他的兄弟属性呢?这里需要this
对象的帮助。
let Person = function(id, name){
this.id = id;
this.name = name;
this.sayHello = function(to_who){
console.log(this.name+":Hello "+to_who);
}
}
let user1 = new Person(1,"庄体育");
let user2 = new Person(2,"张三");
let user3 = new Person(3,"李四");
user1.sayHello("Tom"); // 庄体育:Hello Tom,this==user1
user2.sayHello("Tom"); // 张三:Hello Tom,this==user2
user3.sayHello("Tom"); // 李四:Hello Tom,this==user3
你可以简单认为this
就是.
前面的那个对象,通过这个我们就可以访问到兄弟属性或者兄弟函数了。这看起来简单,却是javascript里非常重要的概念,js就依靠这种看起来有点简陋的办法实现了面向对象的编程范式。
对于不是对象属性的函数,他的this
等于windows
对象,这是BOM的根对象。
对this
对象的理解是JavaScript的入门门槛之一,看下面的例子:
let obj = {
foo: "bar",
func: function(){
let self = this;
console.log("1: this.foo = " + this.foo);
console.log("1: self.foo = " + self.foo);
(function(){
console.log("2: this.foo = " + this.foo);
console.log("2: self.foo = " + self.foo);
})();
}
}
obj.func();
你认为应该输出什么?
点击看答案
第三行console.log("2: this.foo = " + this.foo);
,是我们定义的一个匿名函数,因此其this
指向window
,所以this.foo
等于undefined
,其他行都能输出bar
。
对象属性的遍历
对象的所有属性可以通过for来遍历,这个遍历的方法和很多语言是一样的:
let user7 = {
id:1,
name:"庄体育",
sex:"♂"
}
for( key in user7 ){
console.log(key+":"+user7[key]);
}
要注意,这里遍历得到的,也就是迭代变量key的取值,是属性名,而不是对应的值。 所以你必须通过user7[key]
这种方式来获得对应的值。 现在再回过头去想想前面的问题:
思考一下
为什么不能是user0[id]
和user0[name]
,不带引号时他们分别是什么意思?