程序员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(浏览器对象模型)
    • 自定义对象
    • 原型 (Prototype)
      • 1. 构造函数与原型
      • 2. _proto_
      • 3. 对象的类型
        • 3.1 判断数据的类型
        • 3.2 获取对象的类型
      • 4 constructor 属性
      • 5. 原型链
      • 6. call 和 apply
        • 6.1 作用
        • 6.2 语法
        • 6.3 区别
      • 7. 继承
        • 7.1 对象冒充继承(构造继承)
        • 7.2 原型链继承
        • 7.3 组合继承
    • 内置对象
    • 客户端存储
    • 模块加载方案
  • 前端三剑客
  • JavaScript
scholar
2024-07-19
目录

原型 (Prototype)

# 原型 (Prototype)

在 JavaScript 中,原型是一种用于实现对象继承和共享数据的机制。每个 JavaScript 对象(除了 null)都与另一个对象相关联,这个对象就是原型,每个对象都从原型“继承”属性和方法。

  • prototype 是构造函数的一个属性,其属性值为对象,称为原型对象。
  • 可以通过 prototype 来添加新的属性和方法,此时所有由该构造函数创建的对象都会具有这些属性和方法。
  • 由该构造函数创建的对象会默认链接到该原型对象上。

# 1. 构造函数与原型

构造函数用于创建对象。每个构造函数都有一个 prototype 属性,这个属性指向一个对象,这个对象包含了由构造函数创建的所有实例共享的属性和方法。

语法:

  • 添加属性:构造函数.prototype.属性名 = 值;
  • 添加方法:构造函数.prototype.方法名 = function() { 方法体 };
// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 向原型添加方法
Person.prototype.sayHello = function() {
    console.log(`你好,我叫 ${this.name},我 ${this.age} 岁。`);
};

// 创建对象
var person1 = new Person("张三", 25);
var person2 = new Person("李四", 30);

person1.sayHello(); // 输出:你好,我叫 张三,我 25 岁。
person2.sayHello(); // 输出:你好,我叫 李四,我 30 岁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

访问对象属性的查找顺序

当访问一个对象的属性时,JavaScript 引擎会按照以下顺序查找:

  1. 首先在当前对象中查找对应的实例属性。
  2. 如果没有找到,就会到该对象关联的构造函数的 prototype 属性中查找,即到原型对象中查找。
  3. 如果在原型对象中也没有找到,则继续沿着原型链向上查找,直到找到顶层的 Object.prototype。
  4. 如果最终仍未找到,则返回 undefined。

原型的作用

  • 对象间共享数据:通过在原型中定义属性和方法,可以使多个对象实例共享这些属性和方法,减少内存消耗。
  • 为类(系统内置或自定义)增加新的属性和方法:可以动态地向原型添加方法,并且新增内容对于当前页面中已经创建的对象也有效。

# 2. proto

  • prototype 是构造函数的属性,是站在构造函数的角度来讨论其原型属性。
  • __proto__ 是对象的属性,是站在对象的角度来讨论其原型对象。__proto__ 属性指向构造函数的 prototype 对象。
  • 注:由于 __proto__ 是非标准属性,因此一般不建议使用。

示例:

console.log(person1.__proto__ === Person.prototype); // 输出:true
console.log(Person.prototype.constructor === Person); // 输出:true
console.log(Object.getPrototypeOf(person1) === Person.prototype); // 输出:true
1
2
3

# 3. 对象的类型

# 3.1 判断数据的类型

  • 使用 typeof 可以判断任意变量的类型,但判断对象的类型时总是返回 object。
  • 使用 instanceof 判断对象是否为某种类型,需要指定判断的目标数据类型,无法获取对象的类型名称。
console.log(typeof person1); // 输出:object
console.log(person1 instanceof Person); // 输出:true
console.log(person1 instanceof Object); // 输出:true
1
2
3

# 3.2 获取对象的类型

每个 JavaScript 对象(除了 null 和 undefined)都有一个 constructor 属性,指向它的构造函数。通过 constructor 属性可以获取构造函数,从而获取对象的类型名称。

// 定义一个构造函数 Student
function Student() {}
// 使用构造函数创建一个对象 stu
var stu = new Student();
// 通过对象的 constructor 属性获取构造函数,并通过构造函数的 name 属性获取类型名称
console.log(stu.constructor.name); // 输出:Student
1
2
3
4
5
6

兼容性问题

需要注意的是,在代码压缩或混淆的情况下,构造函数的名称可能会被改变,因此依赖 constructor.name 获取对象类型的方式可能会失效。

# 4 constructor 属性

  • 每个对象都有一个 constructor 属性,该属性指向其构造函数。
  • 对象的 constructor 属性是其原型对象提供的,因为每个对象都链接到其原型对象上。
// 定义一个构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 创建一个对象实例
var person1 = new Person("张三", 25);

// 使用 constructor 属性
console.log(person1.constructor); // 输出:Person
console.log(person1.constructor === Person); // 输出:true
1
2
3
4
5
6
7
8
9
10
11
12

# 5. 原型链

  • 任何对象都有其原型对象,原型对象也有原型对象,对象的原型对象一直往上找,直到 null 为止。这一过程称为原型链。
  • 在这一过程中,有一个 Object 类型的对象,它就是 Object.prototype,位于原型链的顶层。

原型链结构

这个图示展示了 JavaScript 中对象的原型链结构和 __proto__、prototype、constructor 之间的关系。以下是对图中内容的解释:

  1. 构造函数 Object:

    • JavaScript 中的所有对象都是通过构造函数创建的。图中表示了构造函数 Object。
    • Object 构造函数有一个 prototype 属性,指向 Object.prototype 对象。
  2. Object.prototype:

    • Object.prototype 是一个对象,包含了构造函数 Object 的原型属性和方法。
    • Object.prototype 具有一个 constructor 属性,指向 Object 构造函数本身。这意味着通过 Object.prototype 创建的所有对象都共享 Object 构造函数。
    • Object.prototype 还有一个 __proto__ 属性,指向它的原型。由于 Object.prototype 是原型链的顶端,其 __proto__ 属性指向 null。
  3. 创建了对象 o1:

    • 使用 Object 构造函数创建一个对象 o1。
    • 对象 o1 具有一个 __proto__ 属性,指向它的原型对象 Object.prototype。

解释图中每个元素

  • Object:构造函数,用于创建对象。
  • prototype:构造函数的属性,指向构造函数的原型对象。
  • proto:对象的属性,指向对象的原型。
  • constructor:原型对象的属性,指向构造函数本身。

示例代码

// 构造函数
function Person(name) {
    this.name = name;
}

// 创建对象
var person1 = new Person('张三');

// 原型链关系
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 当创建 person1 对象时,person1.__proto__ 指向 Person.prototype。
  • Person.prototype.constructor 指向 Person 构造函数。
  • Person.prototype.__proto__ 指向 Object.prototype,这表示 Person.prototype 的原型是 Object.prototype。
  • Object.prototype.__proto__ 指向 null,表示原型链的终点。

总结

  • 原型链是 JavaScript 中实现继承和共享数据的机制。
  • 通过原型链,对象可以继承和共享原型上的属性和方法。
  • 通过 constructor 属性可以获取对象的构造函数,通过 __proto__ 属性可以访问对象的原型,理解原型链的结构有助于深入理解 JavaScript 的继承机制。

# 6. call 和 apply

# 6.1 作用

call 和 apply 方法都是 JavaScript 中用于改变函数调用上下文(this 指向)的方法。通过这两个方法,我们可以以某个对象的身份来调用另一个对象的方法,从而改变 this 的指向。

# 6.2 语法

  • call:方法.call(对象, 参数1, 参数2...)
  • apply:方法.apply(对象, [参数数组])

# 6.3 区别

  • call 是逐个传参,后面参数可以有多个,逗号隔开。
  • apply 是以数组形式传参,后面参数只能有一个,会自动拆分为多个元素传入。
// 定义一个函数
function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}

// 创建一个对象
var person = { name: "张三" };

// 使用 call 方法调用 greet 函数,将 this 指向 person 对象
greet.call(person, "你好", "!"); // 输出:你好, 张三!

// 使用 apply 方法调用 greet 函数,将 this 指向 person 对象
greet.apply(person, ["你好", "!"]); // 输出:你好, 张三!
1
2
3
4
5
6
7
8
9
10
11
12
13

适用场景

  • call 方法:适用于参数数量已知且较少的情况,可以逐个列出所有参数。
  • apply 方法:适用于参数数量不确定或参数以数组形式存在的情况,方便传递参数数组。

# 7. 继承

# 7.1 对象冒充继承(构造继承)

使用 call,以对象冒充的形式调用父类的构造函数,相当于是复制父类的实例属性给子类,但只能继承父类构造函数中的属性和方法,无法继承原型中的属性和方法。

// 父类构造函数
function Parent(name) {
    this.name = name;
}

// 子类构造函数
function Child(name, age) {
    // 通过 Parent.call 调用父类构造函数,继承父类属性
    Parent.call(this, name);
    this.age = age;
}

var child = new Child("李四", 10);
console.log(child.name); // 输出:李四
console.log(child.age); // 输出:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7.2 原型链继承

使用 prototype,将父类的实例作为子类的原型。通过这种方式,子类可以继承父类原型中的方法。

// 父类构造函数
function Parent(name) {
    this.name = name;
}

// 在父类的原型上添加方法
Parent.prototype.sayHello = function() {
    console.log(`你好,我叫 ${this.name}`);
};

// 子类构造函数
function Child(name, age) {
    // 调用父类构造函数
    this.name = name;
    this.age = age;
}

// 将父类的实例作为子类的原型
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child = new Child("李四", 10);
child.sayHello(); // 输出:你好,我叫 李四
console.log(child.age); // 输出:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 7.3 组合继承

组合对象冒充继承和原型链继承的优点,既可以继承父类构造函数中的属性和方法,也可以继承原型中的属性和方法。

// 父类构造函数
function Parent(name) {
    this.name = name;
    this.colors = ["红色", "蓝色", "绿色"];
}

// 在父类的原型上添加方法
Parent.prototype.sayHello = function() {
    console.log(`你好,我叫 ${this.name}`);
};

// 子类构造函数
function Child(name, age) {
    // 通过 Parent.call 调用父类构造函数,继承父类属性
    Parent.call(this, name);
    this.age = age;
}

// 将父类的实例作为子类的原型
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child("李四", 10);
child1.colors.push("黄色");
console.log(child1.colors); // 输出:[ '红色', '蓝色', '绿色', '黄色' ]
child1.sayHello(); // 输出:你好,我叫 李四

var child2 = new Child("王五", 20);
console.log(child2.colors); // 输出:[ '红色', '蓝色', '绿色' ]
child2.sayHello(); // 输出:你好,我叫 王五
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
编辑此页 (opens new window)
上次更新: 2025/01/25, 22:32:05
自定义对象
内置对象

← 自定义对象 内置对象→

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