Web前端入门第 67 问:JavaScript 中的面向对象编程
此 对象 非彼对象啊,不要理解错了哦~~
面向对象编程 这个概念在 Java 编程语言中用得比较多,JS 同时支持 面向对象编程 和 函数式编程。
像大名鼎鼎的 React 和 Vue 他们都有两种开发风格,比如:
Vue 中的 组合式API 和 选项式API 也是两种编程模式的代表。
React 中的 函数式组件 和 类组件 就是两种编程模式的代表。
原型链
JS 中的每个对象(null 除外)都有一个隐式原型,可以通过 __proto__ 或者 Object.getPrototypeOf() 访问。
null 虽然用 typeof 检测会获得 Object 类型,但 null 有一点特殊,表示空,什么都没有的意思。
比如:
const a = 'str'
console.log(a.__proto__) // 获得 String.prototype
console.log(Object.getPrototypeOf(a)) // 获得 String.prototype
console.log(a.__proto__ === String.prototype) // true
console.log(Object.getPrototypeOf(a) === String.prototype) // true
这么多年的搬砖经验来看,__proto__ 这个属性能派上用场的场景真的少见~~
构造函数
function 申明的函数都拥有一个显式原型 prototype 属性,如果用 new 关键字调用这个函数,那么此时这个函数就称之为 构造函数。
实例化构造函数的时候,实例化对象的 __proto__ 就指向构造函数的 prototype 属性。
function Person() {}
Person.prototype.name = '前端路引'
const person = new Person()
console.log(person.__proto__ === Person.prototype) // true
console.log(person.name) // 输出:前端路引
编程实践推荐:构造函数声明时,首字母一般大写,而函数声明时首字母一般小写。
继承
继承 这玩意儿可以算作面向对象编程的核心思想,如果编程语言不支持 继承,那面向对象就是一句空话。
JS 中的继承玩法多种多样,掌握一种就可以独步武林~~ 但面试官可是全能高手,一般都会问知道有几种继承方式,他们怎么实现这些问题。
原型链继承
子类通过 prototype 指向父类实例,就是原型链继承,但此种方式继承有一个大缺陷,会共享父类中的引用类型(比如数组、对象)。
function Parent() {
// 父类中申明的属性
this.arr = ['公众号', '前端路引'];
}
// 父类中申明的方法
Parent.prototype.getName = function () {
console.log('前端路引');
}
function Child() {}
// 使用原型链继承父类
Child.prototype = new Parent();
const child1 = new Child();
// 修改 child1 的实例属性
child1.arr.push(1);
child1.getName();
const child2 = new Child();
child2.getName();
console.log(child2.arr); // 输出:['公众号', '前端路引', 1]
可以看到子类都可以调用父类的 getName 方法,但是在 child1 实例修改 arr 属性后,child2 也会受影响,这边是原型链继承中的弊端。
构造函数继承
此继承方式的特点是利用函数的 call 或者 apply 方法,再传入子类的 this 指针实现继承,缺点是无法继承父类上的原型方法。
function Parent(name) {
this.name = name;
this.arr = ['公众号', '前端路引'];
this.test = function () {
console.log('调用父类 test 方法');
}
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name);
}
const child1 = new Child('前端路引');
// 修改 child1 的实例属性
child1.arr.push(1);
console.log(child1.arr); // ['公众号', '前端路引', 1]
child1.test(); // 输出:调用父类 test 方法
const child2 = new Child('前端路引');
console.log(child2.arr); // ['公众号', '前端路引']
child2.getName(); // 报错 TypeError: child2.getName is not a function
此继承方式修复了 原型链继承 中共享 引用类型 问题,但却存在无法继承父类原型链方法的弊端。
组合继承
此继承方式结合了原型链继承和构造函数继承而衍生出的另一种继承方式,同时解决了两种继承方式的弊端。
function Parent(name) {
this.name = name;
this.arr = ['公众号', '前端路引'];
this.test = function () {
console.log('调用父类 test 方法');
}
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name); // 继承属性(第二次调用)
}
Child.prototype = new Parent(); // 继承方法(第一次调用)
const child1 = new Child('前端路引');
// 修改 child1 的实例属性
child1.arr.push(1);
console.log(child1.arr); // ['公众号', '前端路引', 1]
child1.test(); // 输出:调用父类 test 方法
const child2 = new Child('前端路引');
console.log(child2.arr); // ['公众号', '前端路引']
child2.getName(); // 输出:前端路引
组合继承可以拥有父类上的 getName,同时还不会共享父类上的引用类型,但父类构造函数却被调用了两次,存在性能优化上的空间,这也是此种继承方式的弊端。
寄生组合继承
此继承方式通过 Object.create 方法复制父类的原型链,优化父类会被调用两次问题,算是比较完美的一种继承方式,不存在性能浪费。
function Parent(name) {
this.name = name;
this.arr = ['公众号', '前端路引'];
this.test = function () {
console.log('调用父类 test 方法');
}
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name); // 继承属性(第二次调用)
}
Child.prototype = Object.create(Parent.prototype); // 继承原型
Child.prototype.constructor = Child; // 修复子类的 constructor 引用
const child1 = new Child('前端路引');
// 修改 child1 的实例属性
child1.arr.push(1);
console.log(child1.arr); // ['公众号', '前端路引', 1]
child1.test(); // 输出:调用父类 test 方法
const child2 = new Child('前端路引');
console.log(child2.arr); // ['公众号', '前端路引']
child2.getName(); // 输出:前端路引
寄生组合继承重点就是两句代码:
Child.prototype = Object.create(Parent.prototype); // 继承原型
Child.prototype.constructor = Child; // 修复子类的 constructor 引用
由于 Object.create 会复制父类的 constructor 属性,导致子类的 constructor 属性被重写了,所以需要手动修复。
在 ES6 出现之前,这种继承已经是 JS 面向对象编程中的最优解了。
ES6 class 继承
ES6 出现了 class 类关键字,也多了 extends 继承关键字,可以很方便的实现继承。
但其底层实现逻辑还是 寄生组合继承,相当于是提供了一种语法糖,简化了寄生组合继承中的代码。
class Parent {
constructor(name) {
this.name = name;
this.arr = ['公众号', '前端路引'];
}
test () {
console.log('调用父类 test 方法');
}
getName () {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name) {
super(name); // 必须有此行
}
}
const child1 = new Child('前端路引');
// 修改 child1 的实例属性
child1.arr.push(1);
console.log(child1.arr); // ['公众号', '前端路引', 1]
child1.test(); // 输出:调用父类 test 方法
const child2 = new Child('前端路引');
console.log(child2.arr); // ['公众号', '前端路引']
child2.getName(); // 输出:前端路引
如果没有 super() 这行代码,JS 解析器会报错:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
意思就是在父类访问 this 之前,子类中必须调用 super() 方法。
原型链查找规则
有了父子两层继承关系,那肯定就有更多层次的继承关系,比如:
class A {}
class B extends A {}
class C extends B {}
在这种多层级的继承关系中,JS 的原型链查找规则永远都是一层一层往上找,终点是找到 Object.prototype 为止,如果还找不到就报错。
比如:
class A {}
class B extends A {}
class C extends B {}
const child = new C();
console.log(child.toString())
console.log(child.test()) // 报错 TypeError: child.test is not a function
以上代码中 A、B、C 都没有 toString 方法,但是实例 child 却可以调用,原因就是 child 的原型链最终找到了 Object.prototype.toString 方法。
而 test 直到 Object.prototype 为止都没找到,所以最终报错。
可以理解其查找规则是这样的:
实例 (obj) --> 构造函数.prototype --> 父构造函数.prototype --> ... --> Object.prototype --> null
写在最后
虽然个人更喜欢 函数式编程 方式,但面向对象这种写法也必须要掌握,要不然看到面向对象的代码,就玩完了~~
Web前端入门第 67 问:JavaScript 中的面向对象编程的更多相关文章
- 深入理解javascript中实现面向对象编程方法
介绍Javascript中面向对象编程思想之前,需要对以下几个概念有了解: 1. 浅拷贝和深拷贝:程序在运行过程中使用的变量有在栈上的变量和在堆上的变量,在对象或者变量的赋值操作过程中,大多数情况先是 ...
- JavaScript中的面向对象编程,详解原型对象及prototype,constructor,proto,内含面向对象编程详细案例(烟花案例)
面向对象编程: 面向:以什么为主,基于什么模式 对象:由键值对组成,可以用来描述事物,存储数据的一种数据格式 编程:使用代码解决需求 面向过程编程: 按照我们分析好的步骤,按步 ...
- JavaScript 中的面向对象编程
使用JSON 来定义一个对象: <script type="text/javascript">var xiaoming = { name : 'xiaoming', a ...
- 如何理解并学习javascript中的面向对象(OOP) [转]
如果你想让你的javascript代码变得更加优美,性能更加卓越.或者,你想像jQuery的作者一样,写出属于自己优秀的类库(哪怕是基于 jquery的插件).那么,你请务必要学习javascript ...
- web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史
秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知, ...
- 前端开发:面向对象与javascript中的面向对象实现(一)
前端开发:面向对象与javascript中的面向对象实现(一) 前言: 人生在世,这找不到对象是万万不行的.咱们生活中,找不到对象要挨骂,代码里也一样.朋友问我说:“嘿,在干嘛呢......”,我:“ ...
- 前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型
前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型 前言(题外话): 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的 ...
- 前端开发:javascript中的面向对象
前端开发:面向对象与javascript中的面向对象实现(一) 面向对象理解: 面向对象是一种对现实世界理解和抽象的方法,是一种先进的程序设计理念,是一种比较抽象的,多形态的设计模式.我们可以这么理解 ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- JavaScript从初见到热恋之深度讨论JavaScript中的面向对象。
JavaScript中的面向对象.面向对象的三个基本特征:封装.继承.多态. 1.封装 js的封装如下 定义Person类 function Person(name,age,sex) { this.n ...
随机推荐
- linux 上安装portainer.io
inux 上安装portainer.io 1.portainer.io是什么? 2.安装,运行镜像 3.登陆1.portainer.io是什么?Portainer是一个轻量级的Doc ...
- 未给任务“SignFile”的所需参数“CertificateThumbprint”赋值.
问题重现 一个项目发布时错误如下错误: 解决方法 打开项目属性-签名 方式一 [取消勾选]为 ClickOnce 清单签名 - 简单粗暴 方式二 [勾选]为 ClickOnce 清单签名 创建测试证书 ...
- AI时代:大模型开发framework之langchain和huggingface
langchain: 提供了大模型相关应用开发的所有便利. https://python.langchain.com/docs/get_started/introduction Build your ...
- MySQL 中 AUTO_INCREMENT 列达到最大值时会发生什么?
在MySQL中,AUTO_INCREMENT列用于自动生成唯一的数字值,通常用于主键.当AUTO_INCREMENT列达到最大值时,会发生以下几种情况,具体取决于列的数据类型以及MySQL的配置. 对 ...
- Sentinel源码—7.参数限流和注解的实现
大纲 1.参数限流的原理和源码 2.@SentinelResource注解的使用和实现 1.参数限流的原理和源码 (1)参数限流规则ParamFlowRule的配置Demo (2)ParamFlowS ...
- sonarqube+gitlab+jenkins+maven集成搭建(三)
安装JENKINS 关闭防火墙[root@localhost ~]# systemctl stop firewalld[root@localhost ~]# systemctl disable fir ...
- [flask]自定义请求日志
前言 flask默认会在控制台输出非结构化的请求日志,如果要输出json格式的日志,并且要把请求日志写到单独的文件中,可以通过先禁用默认请求日志,然后在钩子函数中自行记录请求的方式来实现. 定义日志器 ...
- 揭秘AI自动化框架Browser-use(终):利用MCP与Spring AI,3行代码复刻Browser-use实现
技术背景与目标 在前几篇文章中,我们深入解析了Browser-use框架的核心机制,包括DOM树遍历与分析.提示词构造.任务分解与规划.以及浏览器操作的函数调用.我们将通过Spring AI和Play ...
- 【记录】SMB|Windows下修改SMB端口并挂载
环境:Window11 使用背景:勒索病毒导致445端口不安全,故而该端口在服务器端被全面禁用了,如需使用SMB服务需要换个SMB服务端口. 方法1:端口转发 win+x点开管理员权限的终端: 输入如 ...
- 遇到的问题之“前端html中div设置边框border属性无效,解决方案”
一.问题 二.解决方案 这里是漏了border-style属性,少了这个属性就不会显示边框了,加上就有边框了,这里是建议三个属性都要有完整 # 边距样式 border-style: inset;# 边 ...