程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • HTML

  • CSS

  • JavaScript

    • 简介和用法
    • 变量和数据类型
    • 运算符
    • 数据类型转换
    • 选择结构
    • 循环结构
    • 数组
    • 数组遍历
    • 函数
    • Debug调试
    • DOM
    • 事件处理
    • BOM(浏览器对象模型)
    • 自定义对象
      • 1. 简介
      • 2. 创建对象
        • 2.1 使用 Object
        • 2.2 使用构造函数
        • 2.3 使用对象字面量
        • 2.4 定义属性的注意点
      • 3. this 关键字
        • 3.1 全局上下文中的 this
        • 3.2 函数中的 this
        • 3.3 对象方法中的 this
        • 3.4 构造函数中的 this
        • 3.5 箭头函数中的 this
        • 3.7 事件处理函数中的 this
        • 3.8 call、apply 和 bind 方法中的 this
        • 3.9 总结
      • 4. 引用数据类型
        • 4.1 内存分配
        • 4.2 作为函数参数
        • 4.3 引用数据类型的常见类型
        • 4.4 引用数据类型的特点
      • 5. 闭包
        • 5.1 闭包的理解
        • 5.2 闭包的用途
        • 5.3 闭包示例
        • 5.4 闭包的作用域链
        • 5.5 闭包的常见应用
        • 5.6 闭包问题解决方案示例
        • 问题描述
        • 解决方案
      • 6. JSON
        • 6.1 JSON简介
        • 6.2 JSON 的用法
        • 6.3 JSON 转换
    • 原型 (Prototype)
    • 内置对象
    • 客户端存储
    • 模块加载方案
  • 前端三剑客
  • JavaScript
scholar
2024-07-19
目录

自定义对象

# 四、自定义对象

# 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();        // 调用方法
1
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 岁。
1
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 岁。
1
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 对象
1

# 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"
1
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"
1
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"
1
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>
1
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
    6
  • apply 方法

    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
    6
  • bind 方法

    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 修改的影响
1
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 受函数内部修改的影响
1
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
1
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
1
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>
1
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

# 解决方案

  1. 在函数外部定义事件处理函数

    将事件处理函数在循环外部定义,并传入当前循环变量作为参数。

    <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
  2. 使用 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
  3. 使用自定义属性存储变量

    将每次循环的变量值存储在元素的自定义属性中,然后在事件处理函数中读取该属性。

    <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
};
1
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
    3
  • JSON 对象的集合

    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
编辑此页 (opens new window)
上次更新: 2025/01/25, 22:32:05
BOM(浏览器对象模型)
原型 (Prototype)

← BOM(浏览器对象模型) 原型 (Prototype)→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式