python封装继承多态_浅谈JavaScript的面向对象和它的封装、继承、多态

写在前面

既然是浅谈,就不会从原理上深度分析,只是帮助我们更好地理解...

面向对象与面向过程

面向对象和面向过程是两种不同的编程思想,刚开始接触编程的时候,我们大都是从面向过程起步的,毕竟像我一样,大家接触的第一门计算机语言大概率都是C语言,C语言就是一门典型的面向过程的计算机语言。

面向过程主要是以动词为主,解决问题的方式是按照顺序一步一步调用不同的函数。

面向对象是以名词为主,将问题抽象出具体的对象,而这个对象有自己的属性和方法,在解决问题的时候,是将不同的对象组合在一起使用。

//面向过程装大象

1.开(冰箱)

2.(大象)装进(冰箱)

3.关(冰箱)

//面向对象装大象

1. 冰箱.开门()

2. 冰箱.装进(大象)

3. 冰箱.关门()

从这个例子可以看出,面向对象是以主谓为主,将主谓堪称一个一个的对象,然后对象有自己的属性和方法。

面向对象是以功能来划分问题的,而不是步骤。功能上的统一保证了面向对象设计的可扩展性,解决了代码重用性的问题。

这也是在漫长的程序设计的发展过程中得到的验证结果,面向对象的编程思想较之于面向过程较好一点

封装

面向对象有封装、继承和多态三大特性。

封装:就是把事物封装成类,隐藏事物的属性和方法的实现细节,仅对外公开接口。

在ES5中,并没有class的概念,但是由于js的函数级作用域(函数内部的变量函数外访问不到)。所以我们可以模拟class。在es5中,类其实就是保存了一个函数的变量,这个函数有自己的属性和方法。将属性和方法组成一个类的过程就是封装。

1.通过构造函数添加

JavaScript提供了一个构造函数(Constructor)模式,用来在创建对象时初始化对象。构造函数其实就是普通的函数,只不过有以下的特点

①首字母大写(建议构造函数首字母大写,即使用大驼峰命名,非构造函数首字母小写)

②内部使用this

③使用new生成实例

通过构造函数添加属性和方法实际上也就是通过this添加的属性和方法。因为this总是指向当前对象的,所以通过this添加的属性和方法只在当前对象上添加,是该对象自身拥有的。所以我们实例化一个新对象的时候,this指向的属性和方法都会得到相应的创建,也就是会在内存中复制一份,这样就造成了内存的浪费。

function Cat(name,color){

this.name = name;

this.color = color;

this.eat = (() => {

console.log("fish!")

})

}

//生成实例

var cat1 = new Cat("tom", "gray")

通过this定义的属性和方法,我们实例化对象的时候斗湖重新复制一份

2.通过原型prototype封装

在类上通过this的方式添加属性和方法会导致内存浪费的现象,有什么办法可以让实例化的类所使用的属性和方法 直接使用指针 指向同一个属性和方法。

这就是原型的方法

JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

也就是说,对于那些不变的属性和方法,我们可以直接将其添加在类的prototype对象上。

function Cat(name,color){

this.name = name;

this.color = color;

}

Cat.prototype.type = "英短";

Cat.prototype.eat = ( () => {

alert("fish!")

} )

//生成实例

var cat1 = new Cat('Tom', 'gray');

var cat2 = new Cat('Kobe', 'purple');

console.log(cat1.type); //英短

cat2.eat(); //fish!

这时所有实例的type属性和eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。

但是这样做也有弊端,因为实例化的对象的原型都是指向同一内存地址,改动其中一个对象的属性可能会影响到其他的对象

es6中的类和封装

es6声明一个类

①构造器:构造器内创建自有属性

②方法:声明类实例具有的方法

class Cat {

//等价于Cat构造器

constructor(name) {

this.name = name;

}

//更加简单的声明类的内部函数

//等价于 Cat.prototype.eat

eat() {

console.log("fish!");

}

}

//生成实例

var cat1 = new Cat("tom");

cat1.eat(); //fish!

console.log(cat1 instanceof Cat); //true

console.log(cat1 instanceof Object); //true

console.log(typeof Cat); //function

console.log(typeof Cat.prototype.eat); //function

从上面class声明的Cat为例:Cat类是一个具有构造函数行为的函数,其中内部方法eat实际上就是Cat.prototype.eat()

所以说es6的class封装类,本质上是es5实现方式的语法糖

最主要的区别在于,class类的属性是不可重新赋值和不可枚举的,Cat.prototype就是一个只读属性

class和自定义类型的区别

(1)class的声明不会提升,与let类似

(2)class的声明自动运行于严格模式之下

(3)class声明的方法不可枚举

(4)class的内部方法没有 constructor 属性,无法new

(5)调用class的构造函数必须new

(6)class内部方法不能同名

class类的使用

class作为js中的一级公民,可以被当作值来直接使用

//1.类名作为参数传入函数

function createObj (ClassName) {

return new ClassName()

}

//2.立即执行,实现单例模式

let cat1 = new class{

constructor (name) {

this.name = name

}

eat() {

console.log("fish!")

}

}("tom”)

cat1.eat() //fish!

继承

继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

1.类式继承

所谓的类式继承就是使用的原型的方式,将方法添加在父类的原型上,然后子类的原型是父类的一个实例化对象。

//声明父类

var SuperClass = function(){

let id = 1;

this.name = ['java'];

this.superValue = function() {

console.log('this is superValue!')

}

}

//为父类添加共有方法

SuperClass.prototype.getSuperValue = function () {

return this.superValue();

};

//声明子类

var SubClass = function() {

this.subValue = (() => {

console.log('this is subValue!')

})

}

//继承父类

SubClass.prototype = new SuperClass();

//为子类添加共有方法

SubClass.prototype.getSubValue = function() {

return this.subValue()

}

//生成实例

var sub1 = new SubClass();

var sub2 = new SubClass();

sub1.getSuperValue(); //this is superValue!

sub1.getSubValue(); //this is subValue!

console.log(sub1.id); //undefined

console.log(sub1.name); //["java"]

sub1.name.push("php");

console.log(sub1.name); //["java", "php"]

console.log(sub2.name); //["java", "php"]

其中最核心的是SubClass.prototype = new SuperClass();

类的原型对象prototype对象的作用就是为类的原型添加共有的方法的,但是类不能直接访问这些方法,只有将类实例化之后,新创建的对象复制了父类构造函数的属性和方法,并将原型 proto 指向了父类的原型对象。这样子类就可以访问父类的属性和方法,同时,父类中定义的属性和方法不会被子类继承。

but使用类继承的方法,如果父类的构造函数中有引用数据类型,就会在子类中被所有实例共用,因此一个子类的实例如果更改了这个引用数据类型,就会影响到其他子类的实例。

构造函数继承

为了克服类继承的缺点,才有了构造函数继承,构造函数继承的核心思想就是SuperClass.call(this, id),直接改变this的指向,使通过this创建的属性和方法在子类中复制一份,因为是单独复制的,所以各个实例化的子类互不影响。but会造成内存浪费的问题

//构造函数继承

//声明父类

var SuperClass = function(id){

var name = 'java'

this.languages = ['java', 'php', 'ruby'];

this.id = id

}

//声明子类

var SubClass = function(id){

SuperClass.call(this, id)

}

//生成实例

var sub1 = new SubClass(1);

var sub2 = new SubClass(2);

console.log(sub2.id); // 2

console.log(sub1.name); //undefined

sub1.languages.push("python");

console.log(sub1.languages); // ['java', 'php', 'ruby', 'python']

console.log(sub2.languages); // ['java', 'php', 'ruby']

组合式继承

组合式继承是汲取了两者的优点,既避免了内存浪费,又使得每个实例化的子类互不影响。

//组合式继承

//声明父类

var SuperClass = function(name){

this.languages = ['java', 'php', 'ruby'];

this.name = name;

}

//声明父类原型方法

SuperClass.prototype.showLangs = function () {

console.log(this.languages);

}

//声明子类

var SubClass = function(name){

SuperClass.call(this, name)

}

//子类继承父类(链式继承)

SubClass.prototype = new SuperClass();

//生成实例

var sub1 = new SubClass('python');

var sub2 = new SubClass('go');

sub2.showLangs(); //['java', 'php', 'ruby']

sub1.languages.push(sub1.name);

console.log(sub1.languages);//["java", "php", "ruby", "python"]

console.log(sub2.languages);//['java', 'php', 'ruby']

but警告:组合式继承方法固然好,但是会导致一个问题,父类的构造函数会被创建两次(call()的时候一遍,new的时候又一遍)

寄生组合继承

组合式继承的缺点的关键是 父类的构造函数在类继承和构造函数继承的组合形式被创建了两边,但是在类继承中我们并不需要创建父类的构造函数,我们只要子类继承父类的原型即可。

所以我们先给父类的原型创建一个副本,然后修改子类的 constructor 属性,最后在设置子类的原型就可以了

//原型式继承

//原型式继承其实就是类式继承的封装,实现的功能返回一个实例,该实例的原型继承了传入的o对象

function inheritObject(o) {

//声明一个过渡函数

function F() {}

//过渡对象的原型链继承父对象

F.prototype = o;

//返回一个过渡对象的实例,该实例的原型继承了父对象

return new F();

}

//寄生式继承

//寄生式继承就是对原型继承的第二次封装,使得子类的原型等于父类的原型。并且在第二次封装的过程中对继承的对象进行了扩展

function inheritPrototype(subClass, superClass){

//复制一份父类的原型保存在变量中,使得p的原型等于父类的原型

var p = inheritObject(superClass.prototype);

//修正因为重写子类原型导致子类constructor属性被修改

p.constructor = subClass;

//设置子类的原型

subClass.prototype = p;

}

//定义父类

var SuperClass = function(name) {

this.name = name;

this.languages = ["java", "php", "python"]

}

//定义父类原型方法

SuperClass.prototype.showLangs = function() {

console.log(this.languages);

}

//定义子类

var SubClass = function(name) {

SuperClass.call(this,name)

}

inheritPrototype(SubClass, SuperClass);

var sub1 = new SubClass('go');

es6中的继承

class SuperClass {

constructor(name) {

this.name = name

this.languages = ['java', 'php', 'go'];

}

showLangs() {

console.log(this.languages);

}

}

class SubClass extends SuperClass {

constructor(name) {

super(name)

}

//重写父类中的方法

showLangs() {

this.languages.push(this.name)

console.log(this.languages);

}

}

//生成实例

var sub = new SubClass('韩二虎');

console.log(sub.name); //韩二虎

sub.showLangs(); //["java", "php", "go", "韩二虎"]

多态

多态实际上是不同对象作用与同一操作产生不同的效果。多态的思想实际上是把 “想做什么” 和 “谁去做” 分开。

多态的好处在于,你不必再向对象询问“你是什么类型”后根据得到的答案再去调用对象的某个行为。你尽管去调用这个行为就是了,其他的一切可以由多态来负责。规范来说,多态最根本的作用就是通过吧过程化的条件语句转化为对象的多态性,从而消除这些条件分支语句。

由于JavaScript中提到的关于多态的详细介绍并不多,这里简单的通过一个例子来介绍就好

//非多态

var hobby = function(animal){

if(animal == 'cat'){

cat.eat()

}else if(animal == 'dog'){

dog.eat()

}

}

var cat = {

eat: function() {

alert("fish!")

}

}

var dog = {

eat: function() {

alert("meat!")

}

}

console.log(123);

hobby('cat'); //fish!

hobby('dog'); //meat!

从上面的例子能看到,虽然 hobby 函数目前保持了一定的弹性,但这种弹性很脆弱的,一旦需要替换或者增加成其他的animal,必须改动hobby函数,继续往里面堆砌条件分支语句。我们把程序中相同的部分抽象出来,那就是吃某个东西。然后再重新编程。

//多态

var hobby = function(animal){

if(animal.eat instanceof Function){

animal.eat();

}

}

var cat = {

eat: function() {

alert("fish!")

}

}

var dog = {

eat: function() {

alert("meat!")

}

}

现在来看这段代码中的多态性。当我们向两种 animal 发出 eat 的消息时,会分别调用他们的 eat 方法,就会产生不同的执行结果。对象的多态性提示我们,“做什么” 和 “怎么去做”是可以分开的,这样代码的弹性就增强了很多。即使以后增加了其他的animal,hobby函数仍旧不会做任何改变。

//多态

var hobby = function(animal){

if(animal.eat instanceof Function){

animal.eat();

}

}

var cat = {

eat: function() {

alert("fish!")

}

}

var dog = {

eat: function() {

alert("meat!")

}

}

var aoteman = {

eat: function(){

alert("lil-monster!")

}

}

hobby(cat); //fish!

hobby(dog); //meat!

hobby(aoteman); //lil-monster!

快乐面向对象😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将万亿以下的阿拉伯数字转为中文金额

package test.practice.month3; public class Test005 { //可以不用swich case将123456789转为一二三四五六七八九 //直接用char[] chars {一,二,三,四,五,六,七,八,九}; public static void main(String[] args) { System.out.println(getCMoney(102030405067L)); } private …

8.2 命令历史

2019独角兽企业重金招聘Python工程师标准>>> 命令历史 history //查看之前的命令.bash_history //存放之前敲过的命令,在 /root/ 目录下最大1000条 //默认参数值是1000条变量HISTSIZE/etc/profile中修改 //在其中可编辑HISTSIZE参数HISTTIMEFORMAT"…

使用GCC生成无格式二进制文件(plain binary files)

使用C语言生成一个二进制文件 使用自己喜欢的文本编辑器写一个test.c: int main() { } 再使用如下命令编译: gcc –c test.c ld –o test –Ttext 0x0 –e main test.o objcopy –R .note –R .comment –S –O binary test test.bin 最后生成的二进…

TensorFlow 实例一:线性回归模型

代码 # -- encoding:utf-8 -- """ Create by ibf on 2018/5/6 """import numpy as np import tensorflow as tf# 1. 构造一个数据 np.random.seed(28) N 100 x np.linspace(0, 6, N) np.random.normal(loc0.0, scale2, sizeN) y 14 * x - …

python后端数据发送到前端_Python Django 前后端数据交互 之 后端向前端发送数据...

Django 从后台往前台传递数据时有多种方法可以实现。最简单的后台是这样的:from django.shortcuts import renderdefmain_page(request):return render(request, ‘index.html‘)这个就是返回index.html的内容,但是如果要带一些数据一起传给前台的话&…

Dapper的基本使用

Dapper是.NET下一个micro的ORM,它和Entity Framework或Nhibnate不同,属于轻量级的,并且是半自动的。也就是说实体类都要自己写。它没有复杂的配置文件,一个单文件就可以了。给出官方地址。 http://code.google.com/p/dapper-dot-n…

函数名作为参数传递

假如不知道signal的函数原型,考虑child_handler函数的参数从哪里来? void child_handler(int sig) { if (sig SIGINT) kill(pid_parent, SIGUSR1); } int main(void) { ...... signal(SIGINT, child_handler); ...... } 1、…

易语言神经网络验证码识别_递归神经网络 GRU+CTC+CNN 教会验证码识别

利用 NLP 技术做简单数据可视化分析Chat 简介:用递归神经网络采用端到端识别图片文字,递归神经网络大家最早用 RNN ,缺陷造成梯度消失问题;然后采用了 LSTM,解决 RNN 问题,并且大大提高准确率;现…

GCC 生成的符号表调试信息剖析

GCC把C语言源文件('.c')编译成汇编语言文件('.s'),汇编器把汇编语言文件翻译成目标文件('.o'),最后由链接器链…

《操作系统》OS学习(一):OS相关

清华大学操作系统OS(向勇、陈渝)视频地址:http://www.xuetangx.com/courses/course-v1:TsinghuaX30240243Xsp/about 在ucore实验中,一些基本的常用工具如下: 命令行shell: bash shell -- 有对文件和目录操作的各种命令…

Android4.0蓝牙使能的详细解析

毫无疑问,bluetooth的打开是在Settings中进行的操作。因此,冤有头,债有主,我们来到了Settings.java中,果然发现了相关的代码如下: mBluetoothEnabler new BluetoothEnabler(context, new Switch(context));…

第一次冲刺

本人小组分工角色:产品负责人 本组冲刺订单介绍:经过小组的成员讨论,我们大概确立了一个冲刺的订单是完成一个简易的长沙学院网站项目。 最后完成情况概述:经过大约两周的努力,我们组的编程人员运用python构建了一个简…

移动端导出excel_连载系列【4】Excel开发移动端quot;APPquot;

前三篇文章介绍了百度地图生成器、源代码编辑器、GPS经纬度批量转换工具、源代码编辑器中添加自定义功能按钮和地图控件。这些写好的Java Script代码虽然可以实现所有期望的结果,但毕竟不是一个HTML文件,不便于传播和使用,更无法变成一个类似…

《操作系统》OS学习(二):启动、中断、异常

Bootloader:加载OS。操作系统一开始是放在DISK(硬盘)中,并不是放在内存中。 BIOS:基本I/O处理系统。存放在ROMRead-Only Memory)只读存储中 BIOS(Basic Input/Output System)基本输入输出系统。…

[GCC for C]编译选项---IDE掩盖下的天空

编译选项 ---------IDE掩盖下的天空 /*************************************** * gcc for c language ***************************************/ Single Source to Executable $ gcc helloworld.c [-o howdy] 默认生成的名字a.exe ______________________________________ …

2016级算法第二次上机-F.ModricWang's Number Theory II

891 ModricWangs Number Theory II 思路 使得序列的最大公约数不为1,就是大于等于2,就是找到一个大于等于2的数,它能够整除序列中的所有数。 考虑使得一个数d整除数组中所有数的代价: 如果一个数不能被b整除,那么可以花…

常用css属性集(持续更新…)

禁止换行,超出部分显示…:a. 代码:.hide_word{ max-width: 100px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } b. 效果: 本文转自 bilinyee博客,原文链接: http://blog.51cto.co…

parallels网络初始化失败_33 个神经网络「炼丹」技巧

自然语言处理Andrej Karpathy 是深度学习计算机视觉领域、与领域的研究员。博士期间师从李飞飞。在读博期间,两次在谷歌实习,研究在 Youtube 视频上的大规模特征学习,2015 年在 DeepMind 实习,研究深度强化学习。毕业后&#xff0…

《操作系统》OS学习(三):系统调用

例子 首先看一个标准C库的例子:当我们程序中使用了C库中的printf()函数,实际在底层是在内核态中调用了write()函数。图中右侧则是将程序代码与C库都算到应用程序中,内核提供了一个系统调用接口。 从这个例子我们可以得到以下几点&#xff1a…

cygwin/gcc与MinGW

cygwin/gcc和MinGW都是gcc在windows下的编译环境,但是它们有什么区别?在实际工作中如何选择这两种编译器呢?cygwin/gcc完全可以和在linux下的gcc划等号,这个从boost库的划分中就可以看出来端倪,cygwin下的gcc和linux下的gcc使用的是相同的T…