自定义对象
# 四、自定义对象
# 1. 简介
对象 是一种能够存储多个值的数据类型,表示一个具有特定特征和行为的实体。
- 特征:对象具有的属性,如学生的姓名、年龄等。
- 行为:对象具有的能力,如学生可以学习、跑步、做自我介绍等。
JavaScript 是基于对象的语言,所有的对象都是 Object
类型的实例。
- 属性:对象的特征,称为属性。
- 方法:对象的行为,称为方法。
# 2. 创建对象
创建对象有三种常见方式:
# 2.1 使用 Object
// 1. 创建对象
var student = new Object();
// 2. 为对象添加属性
student.name = "Tom";
student.age = 20;
// 3. 为对象添加方法
student.introduce = function() {
console.log("我的名字是 " + this.name + ",我今年 " + this.age + " 岁。");
};
// 4. 调用属性和方法
console.log(student.name); // 访问属性
student.introduce(); // 调用方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2 使用构造函数
构造函数是用来创建对象的函数,类似于其他语言中的类。
// 1. 定义构造函数
function Student(name, age) {
this.name = name;
this.age = age;
this.introduce = function() {
console.log("我的名字是 " + this.name + ",我今年 " + this.age + " 岁。");
};
}
// 2. 创建对象
var student1 = new Student("Tom", 20);
var student2 = new Student("Jerry", 22);
// 3. 调用属性和方法
console.log(student1.name); // Tom
student2.introduce(); // 我的名字是 Jerry,我今年 22 岁。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.3 使用对象字面量
对象字面量是定义对象的一种简洁方式,适合在需要创建单个对象时使用。
// 使用对象字面量创建对象
var student = {
name: "Tom",
age: 20,
introduce: function() {
console.log("我的名字是 " + this.name + ",我今年 " + this.age + " 岁。");
}
};
// 调用属性和方法
console.log(student.name); // Tom
student.introduce(); // 我的名字是 Tom,我今年 20 岁。
2
3
4
5
6
7
8
9
10
11
12
# 2.4 定义属性的注意点
1. 普通属性(没有连字符的):
- 这些属性名不包含特殊字符(如连字符、空格),你可以直接使用,不需要加引号。
- 例如:
placeholder: '请输入名称'
或type: 'number'
。
2. 包含连字符的属性:
- 如果属性名包含连字符或其他特殊字符,在 JavaScript 对象中定义时必须加上引号。
- 这是因为在 JavaScript 对象中,连字符会被解释为减法运算符,所以需要引号将其视为一个完整的字符串键。
- 例如:
'prefix-icon': 'el-icon-lock'
。
3. 属性名称和 JavaScript 保留字:
- 如果属性名称与 JavaScript 的保留字或关键字相同,最好也加上引号,以避免可能的解析错误。
- 例如:
'class': 'my-class'
或'default': true
。
# 3. this 关键字
this
关键字在 JavaScript 中是一个重要的概念,它根据上下文的不同而有所变化。
# 3.1 全局上下文中的 this
在全局上下文中,this
关键字引用全局对象。浏览器环境中,this
通常引用 window
对象。
console.log(this); // 在浏览器中,输出 window 对象
# 3.2 函数中的 this
普通函数中的
this
在普通函数中,
this
引用的是调用该函数的对象。如果函数直接在全局作用域中调用,this
将引用全局对象。function showThis() { console.log(this); } showThis(); // 在浏览器中,输出 window 对象
1
2
3
4
5严格模式下的
this
在严格模式下,普通函数中的
this
不再引用全局对象,而是undefined
。'use strict'; function showThis() { console.log(this); } showThis(); // 输出 undefined
1
2
3
4
5
6
7
# 3.3 对象方法中的 this
在对象的方法中,this
引用的是调用该方法的对象。
const person = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
person.greet(); // 输出 "Alice"
2
3
4
5
6
7
8
# 3.4 构造函数中的 this
在构造函数中,this
引用的是将来通过 new
创建出来的实例对象。
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.name); // 输出 "Alice"
2
3
4
5
6
# 3.5 箭头函数中的 this
箭头函数中的 this
继承自其定义时的上下文,而不是调用时的上下文。这意味着箭头函数没有自己的 this
,它的 this
值与包围它的普通函数或全局上下文中的 this
相同。
const person = {
name: 'Alice',
greet: function() {
const arrowFunction = () => {
console.log(this.name);
};
arrowFunction();
}
};
person.greet(); // 输出 "Alice"
2
3
4
5
6
7
8
9
10
11
# 3.7 事件处理函数中的 this
在事件处理函数中,this
引用的是触发事件的元素,即事件源。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件处理函数中的 this</title>
</head>
<body>
<button id="myButton">点击我</button>
<script>
document.getElementById('myButton').onclick = function() {
console.log(this); // 输出按钮元素
};
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.8 call
、apply
和 bind
方法中的 this
call
方法使用
call
方法,可以指定函数调用时的this
。function greet() { console.log(this.name); } const person = { name: 'Alice' }; greet.call(person); // 输出 "Alice"
1
2
3
4
5
6apply
方法apply
方法与call
方法类似,只是apply
接受的是参数数组。function greet(greeting) { console.log(greeting + ', ' + this.name); } const person = { name: 'Alice' }; greet.apply(person, ['Hello']); // 输出 "Hello, Alice"
1
2
3
4
5
6bind
方法bind
方法创建一个新的函数,this
绑定到指定的对象。function greet() { console.log(this.name); } const person = { name: 'Alice' }; const boundGreet = greet.bind(person); boundGreet(); // 输出 "Alice"
1
2
3
4
5
6
7
# 3.9 总结
- 全局上下文:
this
引用全局对象(浏览器中为window
)。 - 普通函数:
this
引用调用该函数的对象;在严格模式下为undefined
。 - 对象方法:
this
引用调用该方法的对象。 - 构造函数:
this
引用将来通过new
创建出来的实例对象。 - 箭头函数:
this
继承自定义时的上下文。 - 事件处理函数:
this
引用触发事件的元素。 call
、apply
和bind
方法:可以显式指定this
。
# 4. 引用数据类型
JavaScript 的数据类型分为两种:
- 基本数据类型:也称为简单数据类型,共 5 种:
string
、number
、boolean
、undefined
、null
。 - 引用数据类型:也称为复杂数据类型,如:
Object
、Array
、自定义的Student
、Person
等。
# 4.1 内存分配
- 栈内存:基本数据类型的变量和引用数据类型的变量的引用存储在栈内存中,存取速度较快。
- 堆内存:引用数据类型的实际数据存储在堆内存中,存取速度较慢。
在创建引用数据类型变量时,首先在栈内存上为其引用分配一块空间,而其具体数据存储在堆内存中,然后由栈上的引用指向堆中的地址。
// 基本数据类型
let a = 10;
let b = a; // 复制值
b = 20;
console.log(a); // 输出 10,a 不受 b 修改的影响
// 引用数据类型
let obj1 = { name: "Alice" };
let obj2 = obj1; // 复制引用
obj2.name = "Bob";
console.log(obj1.name); // 输出 "Bob",obj1 受 obj2 修改的影响
2
3
4
5
6
7
8
9
10
11
# 4.2 作为函数参数
- 基本类型作为函数参数:按值传递。在函数内部修改参数的值,不会影响外部变量。
- 引用类型作为函数参数:按引用传递。在函数内部修改参数的值,会影响外部变量。
// 基本类型作为函数参数
function modifyValue(val) {
val = 20;
}
let num = 10;
modifyValue(num);
console.log(num); // 输出 10,num 不受函数内部修改的影响
// 引用类型作为函数参数
function modifyObject(obj) {
obj.name = "Bob";
}
let person = { name: "Alice" };
modifyObject(person);
console.log(person.name); // 输出 "Bob",person 受函数内部修改的影响
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.3 引用数据类型的常见类型
对象(Object)
let person = { name: "Alice", age: 25, greet: function() { console.log("Hello, my name is " + this.name); } };
1
2
3
4
5
6
7数组(Array)
let fruits = ["apple", "banana", "cherry"];
1函数(Function)
function greet() { console.log("Hello"); }
1
2
3自定义对象
function Person(name, age) { this.name = name; this.age = age; } let person1 = new Person("Alice", 25); let person2 = new Person("Bob", 30);
1
2
3
4
5
6
7
# 4.4 引用数据类型的特点
- 动态性:引用数据类型的大小和结构可以动态改变。
- 共享性:多个引用可以指向同一个对象,通过其中一个引用修改对象会影响所有引用该对象的变量。
- 方法和属性:引用数据类型可以包含方法和属性,提供更丰富的数据和功能。
# 5. 闭包
闭包是 JavaScript 中的一个独特概念,它允许函数访问和操作其外部函数的变量。闭包在许多编程场景中非常有用,特别是当你需要保留某些数据的状态或创建私有变量时。
# 5.1 闭包的理解
- 闭包是定义在另一个函数内部的函数。这个内部函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕。
- 闭包可以读取其他函数内部的变量。当函数返回另一个函数时,被返回的内部函数会将其外部函数的作用域 "关闭"(即闭合),以便以后可以访问这些变量。
- 闭包是在某个作用域内定义的函数,该函数可以访问这个作用域内的所有变量。这包括了在外部函数中声明的变量和参数。
# 5.2 闭包的用途
- 在函数的外部可以读取到函数内部的变量。闭包使得函数内部的变量对外部可见。
- 让变量的值始终保存在内存中(不会被垃圾回收器回收)。由于闭包引用了外部函数的变量,这些变量会在内存中保留,直到闭包本身被销毁。
# 5.3 闭包示例
下面演示闭包的基本用法。我们定义了一个函数 createCounter
,这个函数返回一个内部函数,该内部函数可以访问外部函数的变量 count
。
// 定义外部函数 createCounter
function createCounter() {
var count = 0; // 外部函数中的局部变量
// 返回一个内部函数,该函数可以访问外部函数的变量 count
return function() {
count++; // 内部函数中使用外部函数的变量
return count; // 返回当前计数
};
}
// 创建一个计数器
var counter = createCounter();
// 调用计数器函数,输出结果依次为 1, 2, 3
console.log(counter()); // 输出:1
console.log(counter()); // 输出:2
console.log(counter()); // 输出:3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 当
createCounter
函数被调用时,它会返回一个内部函数。 - 这个内部函数被赋值给变量
counter
,即counter
现在是一个函数。 - 当调用
counter
函数时,它会执行内部函数的代码,并且可以访问createCounter
函数中的count
变量。 - 每次调用
counter
函数时,count
的值都会增加,因为count
是在createCounter
的作用域中定义的。
# 5.4 闭包的作用域链
闭包通过作用域链来访问外部函数的变量。作用域链是由多个执行上下文对象组成的链条,每个执行上下文对象都有一个指向其外部环境的引用。闭包通过作用域链可以访问其外部函数的变量,即使外部函数已经执行完毕。
# 5.5 闭包的常见应用
- 私有变量:通过闭包,可以创建只能通过特定函数访问的变量,实现数据的封装和隐藏。
- 函数工厂:闭包可以用于创建函数工厂,根据输入生成不同功能的函数。
- 回调函数:在异步编程中,闭包常用于保存回调函数的状态。
// 私有变量示例
function createPerson(name) {
var age = 25; // 私有变量
return {
getName: function() {
return name; // 访问外部函数的参数
},
getAge: function() {
return age; // 访问外部函数的变量
},
setAge: function(newAge) {
if (newAge > 0) {
age = newAge; // 修改外部函数的变量
}
}
};
}
var person = createPerson("Alice");
console.log(person.getName()); // 输出:Alice
console.log(person.getAge()); // 输出:25
person.setAge(30);
console.log(person.getAge()); // 输出:30
// 函数工厂示例
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier; // 使用外部函数的参数
};
}
var double = createMultiplier(2);
console.log(double(5)); // 输出:10
var triple = createMultiplier(3);
console.log(triple(5)); // 输出:15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 5.6 闭包问题解决方案示例
如果内部函数使用外部函数的变量,在外部函数执行完成之前变量会有改变时,内部只能获取最后改变的值,无法获取定义时的值,就会产生闭包。
# 问题描述
在以下代码中,使用 var
定义循环变量 i
,并在循环中为每个 li
元素绑定点击事件。但是由于 i
的作用域问题,最终点击每个 li
元素时,输出的都是最后一个 i
的值(即 6),这就产生了闭包问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>闭包问题示例</title>
</head>
<body>
<button onclick="add()">添加元素</button>
<ul id="myul"></ul>
<script>
function add() {
for (var i = 1; i <= 5; i++) {
var li = document.createElement('li');
li.innerText = 'li:' + i;
li.onclick = function() {
console.log('点击了第 ' + i + ' 个 li'); // 输出的 i 总是 6
};
document.getElementById('myul').appendChild(li);
}
}
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 解决方案
在函数外部定义事件处理函数
将事件处理函数在循环外部定义,并传入当前循环变量作为参数。
<script> function handleClick(i) { console.log('点击了第 ' + i + ' 个 li'); } function add() { for (var i = 1; i <= 5; i++) { var li = document.createElement('li'); li.innerText = 'li:' + i; li.onclick = (function(i) { return function() { handleClick(i); }; })(i); // 传入当前 i document.getElementById('myul').appendChild(li); } } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20使用
let
声明变量let
有块级作用域,可以在每次循环中正确捕获变量值。<script> function add() { for (let i = 1; i <= 5; i++) { // 使用 let 声明变量 var li = document.createElement('li'); li.innerText = 'li:' + i; li.onclick = function() { console.log('点击了第 ' + i + ' 个 li'); // 输出正确的 i 值 }; document.getElementById('myul').appendChild(li); } } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14使用自定义属性存储变量
将每次循环的变量值存储在元素的自定义属性中,然后在事件处理函数中读取该属性。
<script> function add() { for (var i = 1; i <= 5; i++) { var li = document.createElement('li'); li.innerText = 'li:' + i; li.setAttribute('data-index', i); // 存储变量值为自定义属性 li.onclick = function() { var index = this.getAttribute('data-index'); // 读取自定义属性 console.log('点击了第 ' + index + ' 个 li'); }; document.getElementById('myul').appendChild(li); } } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
通过这三种方式,可以有效解决闭包问题,确保事件处理函数能够正确访问和操作循环中的变量值。
# 6. JSON
# 6.1 JSON简介
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,用于表示 JavaScript 对象的一种方式。
# 6.2 JSON 的用法
JSON 的语法和对象字面量相似,但属性名必须用双引号括起来。
var person = {
"name": "Tom",
"age": 20
};
2
3
4
# 6.3 JSON 转换
JSON 转换为字符串
var person = { "name": "Tom", "age": 20 }; var str = JSON.stringify(person); console.log(str); // {"name":"Tom","age":20}
1
2
3
4
5
6字符串转换为 JSON
var str = '{"name":"Tom","age":20}'; var obj = JSON.parse(str); console.log(obj); // {name: "Tom", age: 20}
1
2
3JSON 对象的集合
var users = '[{"id":1,"username":"admin","password":"123"},{"id":2,"username":"tom","password":"456"}]'; var objs = JSON.parse(users); console.log(objs); // [{id: 1, username: "admin", password: "123"}, {id: 2, username: "tom", password: "456"}]
1
2
3