当前位置: 首页 > news >正文

JavaScript中类的学习

一、JavaScript中的类

1.什么是类

        类描述了一种代码的组织结构形式,不同的语言中对其实现形式各有差异。JavaScript中的类Class实际是一种描述对象之间引用关系的语法糖。

        在Class语法糖出现之前,我们想重用一个功能模块,通常是用一个函数来进行封装:

//声明一个函数,用于功能的复用
function Animal(name) {this.name = name;
}
//可以在函数的原型上进行属性的添加,便于复用
Animal.prototype.walk = function () {return 5555
};
//通过new操作符创建一个新的函数
//new的过程发生了什么:
// 1.创建一个新的对象
// 2.将构造函数的作用域赋值给这个新对象,从而this指向了这个新对象
// 3.执行构造函数中的代码,为这个新对象添加属性
// 4.返回新对象
const dog = new Animal("小狗");//
//通过new创建了一个新对象
console.log(dog);//Animal { name: '小狗' }
//改对象拥有其构造函数上的属性
console.log(dog.walk());//5555
//通过原型可以查看原型上绑定的属性,当你获取一个属性而对象本身没有时,会一层层的查找原型上是否存在该属性,最后找到Object对象本身,没有则返回undefined,有则返回该属性
console.log(Animal.prototype);//Animal { walk: [Function] }
//引用对象的隐式原型指向构造函数的显示原型
console.log(dog.__proto__===Animal.prototype);//true

        为了更好的做成一个单独的模块,还有使用IIFE(立即执行函数)形式来增强模块的:

//立即执行函数
//立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行
//立即执行函数的组成:定义一个函数;将整个函数包裹在一对括号中,将函数声明转换成表达式;在函数尾部加上一对括号,让函数立即被执行
//作用:页面加载完成后只执行一次的设置函数;将设置函数中的变量包裹在局部作用域中,不会泄漏成全局变量
var Animal = (function () {function Animal(name) {this.name = name;console.log(this.name);}Animal.prototype.walk = function (data) {console.log(data);};console.log(666);return Animal;
})();
Animal('我是name')
Animal.prototype.walk('执行方法')
//依次输出为 
//666
//我是name
//执行方法

        由于这是一种很常见的需求,所以ECMAScript规范中加入了Class语法,简化了之前的使用形式:

//通过class关键字声明一个Animal类
//constructor构造函数用于将new的实例对象指回构造函数本身
class Animal {constructor(name) {this.name = name;}walk() {console.log('执行了');}
}
//通过new关键字基于类生成实例对象
let dog = new Animal('小狗')
console.log(Animal);//[Function: Animal]
console.log(dog);//Animal { name: '小狗' }
dog.walk()//执行了

2.静态属性

        到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 static定义 origin,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 origin前面加上类名。 如同在实例属性上使用 this.前缀来访问属性一样,这里我们使用 Grid.来访问静态属性。

class Grid {//用于定义静态属性static origin = { x: 0, y: 0 };calculateDistanceFromOrigin(point) {let xDist = (point.x - Grid.origin.x);let yDist = (point.y - Grid.origin.y);console.log(this.scale);//声明实例对象时所传递的值return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;}constructor(scale) {this.scale = scale;}
}let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scaleconsole.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));

3.存取器

        通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

        下面来看如何把一个简单的类改写成使用 get和 set。 首先,我们从一个没有使用存取器的例子开始。

class Employee {fullName;
}let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {console.log(employee.fullName);
}

        我们可以随意的设置 fullName,这是非常方便的,但是这也可能会带来麻烦。

        下面这个版本里,我们先检查用户密码是否正确,然后再允许其修改员工信息。 我们把对 fullName的直接访问改成了可以检查密码的 set方法。 我们也加了一个 get方法,让上面的例子仍然可以工作。

let passcode = "secret passcode";class Employee {_fullName=5;get fullName() {return this._fullName;}set fullName(newName) {if (passcode && passcode == "secret passcode") {this._fullName = newName;}else {console.log("验证失败");}}
}let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {console.log(employee.fullName);
}

4.继承与多态

        使用extends关键字可以很方便的实现类之间的继承。

class Animal {move(distanceInMeters) {console.log(`Animal moved ${distanceInMeters}m.`);}
}class Dog extends Animal {bark() {console.log('Woof! Woof!');}
}const dog = new Dog();
dog.bark();//Woof! Woof!
dog.move(10);//Animal moved 10m.
dog.bark();//Woof! Woof!

        这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里, Dog是一个 派生类,它派生自 Animal 基类,通过 extends关键字。 派生类通常被称作 子类,基类通常被称作 超类。

        因为 Dog继承了 Animal的功能,因此我们可以创建一个 Dog的实例,它能够 bark()和 move()。

        下面我们来看个更加复杂的例子:

//新建Animal类
class Animal {//用于将实例对象的this指向构造函数本身constructor(theName) { this.name = theName; }move(distanceInMeters) {console.log(`${this.name} moved ${distanceInMeters}m.`);}
}
//新建Snake类继承于Animal类
class Snake extends Animal {//super关键字,代表父类对象,super(name)表示执行父类constructor(name) { super(name); }move(distanceInMeters = 5) {console.log("Slithering...");//触发父类方法super.move(distanceInMeters);}
}
//新建Horse类继承于Animal类
class Horse extends Animal {//super关键字,代表父类对象,super(name)表示执行父类constructor(name) { super(name); }move(distanceInMeters = 45) {console.log("Galloping...");//触发父类方法super.move(distanceInMeters);}
}
let sam = new Snake("Sammy the Python");
let tom = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
// 输出为
// Slithering...
// Sammy the Python moved 5m.   
// Galloping...
// Tommy the Palomino moved 34m.

        这个例子展示了一些上面没有提到的特性。 这一次,我们使用 extends关键字创建了 Animal的两个子类: Horse和 Snake。

        与前一个例子的不同点是,派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,可以用使用super关键字调用Animal中的方法。

        这个例子演示了如何在子类里可以重写父类的方法。 Snake类和 Horse类都创建了 move方法,它们重写了从 Animal继承来的 move方法,使得 move方法根据不同的类而具有不同的功能。 注意,即使 tom被声明为 Animal类型,但因为它的值是 Horse,调用 tom.move(34)时,它会调用 Horse里重写的方法。在继承链的不同层次中move方法被多次定义,当调用方法时会自动选择合适的定义,这就是多态的一种实现。

5.类的本质

        文章的开头说过Class只是一种描述对象之间引用关系的语法糖。现在我们就来看看语法糖背后的本质是什么。

class Animal {constructor(name) {this.name = name;}print() {console.log(this.name);}
}const animal = new Animal('dog');
animal.print(); // dog

        这里我们创建了一个Animal的实例,可以看到实例可以使用print中的方法。实现这一切的前提需要搞清楚new操作符到底做了什么,下面是一个new操作符的模拟实现:

//模拟实现new操作符
//创建一个Person函数,声明属性与方法
function Person(name) {this.name = name;
}
Person.prototype.sayName = function () {console.log(this.name);
}//创建一个createPerson函数用于模拟new Person实现逻辑
function createPerson() {// 1 创建一个新的对象var o = {};// 2 获取构造函数,以便实现作用域的绑定//arguments的作用:当在js中调用一个函数的时候,我们经常会给这个函数传递一些参数,js把传入到这个函数的全部参数存储在arguments中。// Arguments是一个类数组对象,对象中的每一项分别对应传入的参数console.log(arguments);//[Arguments] { '0': [Function: Person], '1': 'ydb' }//因为arguments不是数组对象,是一个类数组对象,所以不能调用数组的方法,比如prototype属性不能使用console.log(arguments.prototype);//undefined//所以为了实现后续new实现对象之前的相关绑定需要使用prototype进行绑定,所以需要将arguments转换成数组对象// console.log([].shift.call(arguments));//[Function: Person]//通过[].shift.call(arguments)将arguments从类数组对象,转成数组对象//原理: //[].slice.call( arguments )相当于Array.prototype.slice.call( arguments )//在这里,slice()是数组上的一个方法,它不改变原数组,而是从原数组中返回指定的元素,也就是一个新的数组,当没有传入任何参数的时候也就是会返回整个数组(复制原数组,不改变原数组);然后是 call 函数,它和 apply 函数一样,他们两个都是改变函数的 this 指向,区别就是参数不一样,他们的第一个参数都是一个对象或者是 “this”,而 apply 的第二个参数是一个数组,call 后面可以继续跟多个参数,也就是 apply ( this,[ ] );call ( this , n1,n2,n3....)// 所以重点就是:// 因为slice内部实现是使用的this为代表调用对象,那么当[ ].slice.call() 传入 arguments 对象的时候,通过 call 函数改变原来 slice 方法的 this 指向, 使其指向 arguments,并对 arguments 进行复制操作,然后返回一个新数组。所以就达到了把 arguments 类数组转为数组的目的!// 当然,[ ].shift.call( arguments ) 也是如此,shift () 方法为删除数组的第一项,并返回删除项,所以这句我们可以理解为 “ 删除arguments的第一项并返回,也就是拿到 arguments 的第一项 ”。var _constructor = [].shift.call(arguments);//获取到传入的Person函数console.log(_constructor);//[Function: Person]// 3 由于通过new操作创建的对象实例内部的不可访问的属性[[Prototype]](有些浏览器里面为__proto__)//指向的是构造函数的原型对象的,所以这里实现手动绑定。// 实例对象的隐式原型指向构造函数的现实原型o.__proto__ = _constructor.prototype;// 4.作用域的绑定使用apply改变this的指向//作为参数传递console.log(arguments,'arguments');//[Arguments] { '0': 'ydb' } argumentsconsole.log(o,'o')_constructor.apply(o, arguments);console.log(o,'o');//Person { name: 'ydb' } oreturn o;
}
var person1 = createPerson(Person, 'ydb');
person1.sayName();
console.log(person1);//Person { name: 'ydb' }

        注意这里说的构造函数并不是指的是"constructor",而是指的是Person函数,因为每一个函数的原型对象上有一个constructor属性指向函数本身:

console.log(Person.prototype.constructor === Person); // true

        到这里可以很清楚的知道之所以person1可以使用sayName方法,完全是因为这句代码:

o.__proto__ = _constructor.prototype;

        它们实现了同一个对象的引用,这个对象在这里指的是Person的原型对象,也就是Person.prototype。并不是Person把原型对象复制给了o对象上,它们之前只是一种对象引用的关系。

        再来看看继承是如何实现的:

class Animal {//将new的实例对象指向构造函数本身constructor(name) {this.name = name;}print() {console.log(this.name);}
}class Dog extends Animal {//将new的实例对象指向构造函数本身constructor(name) {super(name);}print() {console.log("dog");}walk() {console.log(`${this.name} is a dog`);}
}const dog = new Dog('二哈');
dog.print(); // dog
dog.walk(); // 二哈 is a dog

        转换一下:

var __extends = (this && this.__extends) || (function () {var extendStatics = function (d, b) {extendStatics = Object.setPrototypeOf ||({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };return extendStatics(d, b);};return function (d, b) {extendStatics(d, b);function __() { this.constructor = d; }d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());};
})();
var Animal = /** @class */ (function () {function Animal(name) {this.name = name;}Animal.prototype.print = function () {console.log(this.name);};return Animal;
}());
var Dog = /** @class */ (function (_super) {__extends(Dog, _super);function Dog(name) {return _super.call(this, name) || this;}Dog.prototype.print = function () {console.log("dog");};Dog.prototype.walk = function () {console.log(this.name + " is a dog");};return Dog;
}(Animal));
var dog = new Dog('二哈');
dog.print(); // dog
dog.walk(); // 二哈 is a dog

        可以看出通过__extends方法实现了某些对象对其他对象的引用,达到功能模块复用的目的,并没有什么魔法。

        所以到现在你应该明白了,JavaScript中其实并没有类这个概念的,只不过是语法糖隐藏了内部实现,方便开发人员的实现对对象之间引用这种功能。JavaScript中也没有所谓的“原型继承”,本质都是对象之间的引用。

相关文章:

JavaScript中类的学习

一、JavaScript中的类 1.什么是类 类描述了一种代码的组织结构形式,不同的语言中对其实现形式各有差异。JavaScript中的类Class实际是一种描述对象之间引用关系的语法糖。 在Class语法糖出现之前,我们想重用一个功能模块,通常是用一个函数来…...

1600*A. Linova and Kingdom(DFS优先队列贪心)

Problem - 1336A - Codeforces Linova and Kingdom - 洛谷 解析: 开始认为分情况讨论 k 小于等于叶子结点和大于叶子结点的情况,然后选择深度最深的叶子结点和子孙数量最小的结点,但是发现如果把某一个非叶子结点选取,那么其子孙…...

gitlab git lfs的替代软件整理汇总及分析

文章目录 前言替代软件分析git-annexgit-fatgit-symgit-meida 总结 前言 git-lfs科普 Git LFS(Large File Storage)是一个Git扩展,用于管理大型文件。Git LFS通过将大型文件存储在Git仓库之外,从而加快了Git操作的速度。它使用指…...

IDEA 2023.2.2图文安装教程及下载

IDE 系列的第二个年度更新现已发布,涵盖 IntelliJ IDEA、WebStorm、PyCharm、DataGrip、GoLand、DataSpell 以及 All Products Pack 订阅中包含的其他工具。该版本还包括多项用户体验增强功能,例如 Search Everywhere(随处搜索)中…...

第六届“中国法研杯”司法人工智能挑战赛

解锁司法科技的未来 “中国法研杯”司法人工智能挑战赛(Legal AI Challenge,简称LAIC),是面向法院侧人工智能应用领域唯一权威比赛,大赛愿景是在拥有全球最大规模司法数据的中国,实现法律界、学术界、产业界…...

Springcloud中间件-----分布式搜索引擎 Elasticsearch

该笔记是根据黑马程序员的课来自己写了一遍的,b站有对应教程和资料 第一部分 第二部分 第三部分 预计看完跟着练习5小时足够 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能&#xff…...

基于深度学习的目标检测和语义分割:机器视觉中的最新进展

基于深度学习的目标检测和语义分割是机器视觉领域的两个重要任务,它们在图像处理、自动驾驶、医学影像分析和智能视频监控等应用中发挥着关键作用。以下是这两个领域的最新进展: 目标检测(Object Detection): 一阶段检…...

微信小程序报错request:fail -2:net::ERR_FAILED(生成中间证书)

微信小程序报错request:fail -2:net::ERR_FAILED-生成中间证书 前言一、检查网站ssl证书二、生成证书方法1.获取中间证书手动合并1.进入网站:[https://www.myssl.cn/tools/downloadchain.html](https://www.myssl.cn/tools/downloadchain.html)2.点击下一步3.手动合…...

Ubuntu更改时区

sudo apt install tzdata 进行安装时区,有很多时区可供选择。 然后执行:tzselect rootd75c94dcd226:/# date 2023年 10月 11日 星期三 06:25:12 UTC rootd75c94dcd226:/# tzselect Please identify a location so that time zone rules can be set correctly. Ple…...

0144 文件管理

目录 4.文件管理 4.1文件系统基础 4.2目录 4.3文件系统 部分习题 4.文件管理 4.1文件系统基础 4.2目录 4.3文件系统 部分习题 1.UNIX操作系统忠,输入/输出设备视为() A.普通文件 B.目录文件 C.索引文件 D.特殊文…...

python psutil库之——获取网络信息(网络接口信息、网络配置信息、以太网接口、ip信息、ip地址信息)

文章目录 使用Python psutil库获取网络信息安装psutil库获取网络连接信息查看所有网络连接过滤特定状态的连接 获取网络接口信息获取网络IO统计信息实例1实例2 总结 使用Python psutil库获取网络信息 Python的psutil库是一个跨平台库,能够方便地获取系统使用情况和…...

uniapp上echarts地图钻取

1: 预期效果 通过切换地图 , 实现地图的钻取效果 2: 实现原理以及核心方法/参数 一开始是想利用更换地图数据的形式进行地图钻取 , 这就意味着我们需要准备全国30多个省份的地图数据 , 由于一开始考虑需要适配小程序端 , 如此多的地图文件增加了程序的体积 , 如果使用接口调…...

scratch保护环境 2023年5月中国电子学会图形化编程 少儿编程 scratch编程等级考试一级真题和答案解析

目录 scratch保护环境 一、题目要求 1、准备工作 2、功能实现 二、案例分析...

RPC分布式网络通信框架项目

文章目录 对比单机聊天服务器、集群聊天服务器以及分布式聊天服务器RPC通信原理使用Protobuf做数据的序列化,相比较于json,有哪些优点?环境配置使用项目代码工程目录vscode远程开发Linux项目muduo网络库编程示例CMake构建项目集成编译环境Lin…...

Navicat如何连接远程服务器的MySQL

参考:https://blog.csdn.net/a648119398/article/details/122420906 1.Navicat for Mysql 2.腾讯云轻量级服务器一台(Centos 7) 3.Mysql 8.0.24(远程服务器内安装的) 4.Xshell7(连接操作远程服务器) 一、修…...

【计算机网络笔记】计算机网络的结构

系列文章目录 什么是计算机网络? 什么是网络协议? 文章目录 系列文章目录网络边缘接入网络数字用户线路 (DSL)电缆网络典型家庭网络的接入机构(企业)接入网络 (Ethernet)无线接入网络 网络核心Internet结构最后 计算机网络的结构…...

排序算法-插入排序法(InsertSort)

排序算法-插入排序法(InsertSort) 1、说明 插入排序法是将数组中的元素逐一与已排序好的数据进行比较,先将前两个元素排序好,再将第三个元素插入适当的位置,也就是说这三个元素仍然是已排序好的,接着将第…...

RuntimeError: “slow_conv2d_cpu“ not implemented for ‘Half‘

RuntimeError: “slow_conv2d_cpu” not implemented for ‘Half’ 背景 测试语音识别模型whisper时,出现上述错误!! 测试代码如下: import whispermodel whisper.load_model("base") # print(model)# load audio an…...

前端 | 前端工程化

文章目录 前端工程化1. Vue项目创建2. Vue项目目录结构3. vue项目开发 前端工程化 1. Vue项目创建 安装插件vue-cli npm install -g vue/cli命令行创建 Vue 项目 vue create vue-project(项目名称)图形化界面创建 VUe 项目 vue ui图形化界面如下: 选择功能&…...

学信息系统项目管理师第4版系列24_整合管理

1. PMBOK 1.1. 自1987年以来,PMBOK-直是基于过程的项目管理标准的重要代表 1.1.1. 基于过程的方法是项目管理的基石 1.2. 从2021年开始,第7版PMBOK采用了基于原则的标准,其中包含了 12个项目管理基本原则,这些基本原则为有效的…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

HTML 列表、表格、表单

1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...

测试markdown--肇兴

day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则&#xf…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

iview框架主题色的应用

1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...

论文阅读:Matting by Generation

今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...

【iOS】 Block再学习

iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...