对象
对象则用来存储键值对和更复杂的实体。
一个属性就是一个键值对(“key: value
”),其中键(key
)是一个字符串(也叫做属性名),值(value
)可以是任何值。
创建对象
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法
众所周知,变量名不能是编程语言的某个保留字,如 “for”、“let”、“return” 等……
但对象的属性名并不受此限制:
let user = {
name: "Ann", // 键 "name",值 "John"
age: 18, // 键 "age",值 18
"like music": true, // 多词属性名必须加引号
0: "test", // 等同于 "0": "test"
let: 1,
for: 2,
return: 666,
class: "one"
}
点符号访问属性: obj.property
点符号要求 key
是有效的变量标识符。这意味着:不包含空格,不以数字开头,也不包含特殊字符(允许使用 $ 和 _)。
console.log(user.name) // John
console.log(user.let + user.for + user.return) // 669
console.log(user.classs) // one
// 写入isAdmin属性
user.isAdmin = true
console.log(user)
// 移除isAdmin属性
delete user.isAdmin
console.log(user)
方括号[]
访问属性:obj["property"]
。方括号允许从变量中获取键,例如 obj[key]
。
let user = {}
user.like music = true // 报错
user.0 = "test" // 报错
对于多词属性,点符号访问会报错。
JavaScript 理解不了。它认为我们在处理 user.like
,然后在遇到意外的 music
时给出了语法错误。
多词属性需要使用方括号[]
访问。方括号[]
可用于任意字符串。
// 设置
user["like music"] = true
user[0] = "test"
user["0"] = "test"
let key = "circle"
user[key] = "圈圈" // 等同于 user["circle"] = "圈圈"
// 读取
console.log(user["like music"])
查是否存在给定键的属性,使用“in
” 操作符: "key" in obj
请注意,in
的左边必须是 属性名。通常是一个带引号的字符串。
相比于其他语言,JavaScript 的对象有一个需要注意的特性:能够被访问任何属性。即使属性不存在也不会报错!
读取不存在的属性只会得到 undefined
。
但是,也有可能这个属性的值就是 undefined
。
let user = {
age: undefined,
}
// 判断某个属性是否存在
console.log(user.name === undefined) // true, 表示没有这个属性,判断正确
console.log(user.age=== undefined) // true, 表示没有这个属性,判断错误
实际上,对象user
里面存在age
属性,只是age
属性的值为 undefined
。
因此,为了精确判断对象里是否存在某个属性,建议使用in
:
console.log("name" in user) // false,不存在属性name
console.log("age" in user) // true, 存在属性age
遍历对象:"key" in obj
for(let key in user) {
console.log('key:' , key)
console.log('value:', user[key])
}
对象方法,this
作为对象属性的函数被称为 方法。
- 存储在对象属性中的函数被称为“方法”。
- 方法允许对象进行像
object.doSomething()
这样的“操作”。 - 方法可以将对象引用为
this
。
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
console.log(`${this.name} say hello!`) // this指向user对象
};
user.sayHi(); // Hello!
- “
this
” 不受限制
在 JavaScript 中,this
关键字与其他大多数编程语言中的不同。JavaScript 中的this
可以用于任何函数,即使它不是对象的方法。
this
的值是在代码运行时计算出来的,它取决于代码上下文。
例如,这里相同的函数被分配给两个不同的对象,在调用中有着不同的 “this” 值:
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name ); // 不会报错
}
// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;
// 这两个调用有不同的 this 值
// 函数内部的 "this" 是“点符号前面”的那个对象
user.f(); // John(this == user)
admin.f(); // Admin(this == admin)
admin['f'](); // Admin(使用点符号或方括号语法来访问这个方法,都没有关系。)
这个规则很简单:如果 obj.f()
被调用了,则 this
在 f
函数调用期间是 obj
。所以在上面的例子中 this
先是 user
,之后是 admin
。
- 在没有对象的情况下调用:
this == undefined
function sayHi() {
alert(this);
}
sayHi(); // undefined
this
的值是在程序运行时得到的。
- 一个函数在声明时,可能就使用了
this
,但是这个this
只有在函数被调用时才会有值。 - 可以在对象之间复制函数。
- 以“方法”的语法调用函数时:
object.method()
,调用过程中的this
值是object
。
请注意箭头函数有些特别:它们没有this
。在箭头函数内部访问到的this
都是从外部获取的。
构造函数
常规的 {...}
语法允许创建一个对象。
需要创建多个类似对象的时候,可以使用构造函数和 “new
” 操作符来实现。
构造函数在技术上是常规函数。不过有两个约定:
- 它们的命名以大写字母开头。
- 它们只能由 “
new
” 操作符来执行。
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数被使用 new
操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并分配给 this。
- 函数体执行。通常它会修改 this,为其添加新的属性。
- 返回 this 的值。
换句话说,new User(...)
做的就是类似的事情:
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
所以 new User("Jack")
的结果是相同的对象:
let user = {
name: "Jack",
isAdmin: false
};
现在,如果我们想创建其他用户,我们可以调用 new User("Ann")
,new User("Alice")
等。比每次都使用字面量创建要短得多,而且更易于阅读。
这是构造器的主要目的 —— 实现可重用的对象创建代码。
构造器中的return
通常,构造器没有 return
语句。它们的任务是将所有必要的东西写入 this
,并自动转换为结果。
但是,如果这有一个 return
语句,那么规则就简单了:
- 如果
return
返回的是一个对象,则返回这个对象,而不是this
。 - 如果
return
返回的是一个原始类型,则忽略。
换句话说,带有对象的return
返回该对象,在所有其他情况下返回this
。
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- 返回这个对象,这里return通过返回一个对象,覆盖了this
}
console.log( new BigUser().name ); // Godzilla,得到了那个对象
function SmallUser() {
this.name = "John";
return; // <-- 返回this
}
console.log( new SmallUser().name ); // John
构造器中的方法
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
// 相当于以下代码:
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
- 构造函数,或简称构造器,就是常规函数,但大家对于构造器有个共同的约定,就是其命名首字母要大写。
- 构造函数只能使用
new
来调用。这样的调用意味着在开始时创建了空的this
,并在最后返回填充了值的this
。
练习
- 检查空对象
写一个isEmpty(obj)
函数,当对象没有属性的时候返回true
,否则返回false
。
function isEmpty(obj) {
for (let key in obj) {
// 如果进到循环里面,说明有属性。
return false;
}
return true;
}
let user= {}
console.log(isEmpty(user))
- 创建一个构造函数 Calculator,它创建的对象中有三个方法:
read()
使用prompt
请求两个值并把它们记录在对象的属性中。sum()
返回这些属性的总和。mul()
返回这些属性的乘积。
function Calculator() {
this.read = function() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
};
this.sum = function() {
return this.a + this.b;
};
this.mul = function() {
return this.a * this.b;
};
}
let calculator = new Calculator();
calculator.read();
console.log( "Sum=" + calculator.sum() );
console.log( "Mul=" + calculator.mul() );
- 创建一个构造函数
Accumulator(startingValue)
。
- 将“当前 value”存储在属性
value
中。起始值被设置到构造器startingValue
的参数。 read()
方法应该使用prompt
来读取一个新的数字,并将其添加到value
中。
换句话说,value
属性是所有用户输入值与初始值startingValue
的总和。
function Accumulator(startingValue) {
this.value = startingValue;
this.read = function() {
this.value += +prompt('How much to add?', 0);
};
}
let accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
console.log('accumulator.value:', accumulator.value);