JavaScript面向对象的理解

前言


1. 本文默认阅读者已有面向对象的开发思想,最好是使用过c++、java,本人Java不太熟悉,所以例子都是用C++来写的。
2. 本人不是专业网站开发人员,接触javascript一年多,自己也编写调试了一些代码,本文完全根据自己经验所写,只希望和朋友们分享。文章难免出错,希望大家指出,以便及时改正。

3. 代码测试环境:google chrome


正文:

所谓对象

为了内容完整,我先说一些面向对象的东西。话说为什么要有面向对象的思想?也就是好好的面向过程的程序设计不用,干嘛搞出个面向对象(OO)?我的理解是为了满足工程开发的需要,增加代码的重复利用率(通过继承等),可以提高开发速度。另外也符合人的思考逻辑,面向过程的代码相对比较难看,很难一眼看出来其中的逻辑,面向对象的代码较好维护。
好了,进入正题。JavaScript的开发方式我认为也只有两种,一是面向过程,二是面向对象。用面向过程就是来一个问题,写一个函数来解决,这就产生了很多的代码,而且你以前也得代码比较难重复利用(也可以重复利用,但是当你代码写多了,你记得清吗),当然对于相似的操作也可以写一个公共函数库(貌似JQuery就是的吧?不对请指出),这比较好容易理解。而面向对象呢?开发的过程中我们会发现,网页元素有很多操作都是相似的,而且网页中有很多模块都是差不多的,这就让人很容易联想到了用面向对象开发。
再来说说提高代码重复利用率的好处。

一、很显然可以加快开发速度。
   二、减少代码量,提高网页加载速度。首先JavaScript是脚本语言。什么是脚本语言呢,脚本语言就是需要有一个翻译器才能执行的。这个翻译器也叫执行器、执行引擎、执行宿主等等。它不是直接生成代码让cpu执行的,而是给执行器看的,执行器看懂了,然后执行器去通过cpu做那些事情。于是脚本和exe相比,速度较慢,因为exe是直接和cpu对话的。批处理bat,vb脚本vbs等都是脚本语言。vbs也可以用于网页哟,它和js有什么不同呢,这里就不说了,不是本文重点。脚本语言的代码的多少直接影响着网页加载速度,所以提高代码重复利用率,可以提高加载速度,有利于改善用户体验。


所谓this

好,都说得差不多了,那么怎么面向对象呢?刚学JS的时候,一个函数就是一个函数。比如

function a()
{alert("呵呵哒");
}
a();
定义完直接调用即可,很简单。但是,继续学,又看到了这样的代码:
function man( name )
{this.name = name;this.sayName = function(){alert(this.Name);}
}
于是乎晕了,哪来的this?又没有定义对象哪来的this?于是乎在网上搜索答案,最后知道了原来JS也可以面向对象。然后网上说用function声明的可以是函数,也可以是对象。这就让人有点乱了。本来脚本语言一般都是弱类型的语言,这个已经让人有点不习惯了,又来了一个既可以是函数又可以是对象的,头都大了。时隔半年后的今天,我在写程序C++的时候,顺便将JS的面向对象又想了一遍。貌似是这样的:
JS的面向对象和C++的面向对象是一样的。在C++里,我们要先声明一个类,然后再在.cpp文件中实现它。而在JavaScript里呢?如果还要声明一个类,然后再定义,那得有多少代码?于是乎JS省去了类的声明这个环节,直接将一个函数看成一个构造函数,在定义的同时也就声明了它即声明和定义是一起的,这和变量的使用也是一样的,如this.name = "呵呵",你只要直接给它赋值它就自动产生了。在JS里,所有的函数可以看成是构造函数 。这样做是为了减少代码量,从而提高网页加载速度。
但是,JS里的函数又和C++里的构造函数有不同的地方:C++构造函数是没有任何返回值的,而JS里可以有。为什么呢,因为为了简化代码,统一使用function来声明变量不是一样吗?何必多一个关键词呢?function本来就是声明函数的,不过要是想产生对象的话,它是作为构造函数来用的。
这样的话,我们再来看看this。我记得当我学this的时候学得是满头雾水。有的说是指向调用者,恩,对的,可是我还是没真正理解调用者是谁,为什么是调用者?网页中那么多DOM对象,有时候使用this的时候生怕使用错了,感觉总是不确定这个this到底指向谁,比如一个按钮的事件响应函数,它被调用时this就指向按钮,那么为什么呢,有什么用呢?有没有一目了然的判断方法?或者说它和C++里面的this是不是一样(编程也要追求融汇贯通,否则学语言就是死记了)?答案是肯定的:
首先你得了解C++里面的this是怎么回事。它是一个编译器给类的非静态成员函数加上去的一个默认的参数,这个类可能产生很多实例,但是所有实例共享这些函数,实例的成员变量一般是不同的,那么这些函数怎么判断谁是谁呢?答案就是this,每个对象调用类的成员函数时,它会带着一个指向自己的一个指针,把它交给类的成员变量,我们知道地址是唯一的,那么类的成员函数就根据这个地址就找到了这个实例所在的地方,从而就能对这个地方的内存进行操作。OK!那么JS也是一样的,它也有一个默认参数this,谁调用它,this就是这个调用者的对象指针(JS里说指针呢不太好,感觉应该说是句柄,更直接点说,这个this就是那个调用者)。这样如果你直接调用一个函数

<pre name="code" class="javascript">function a()
{alert( this );
}
a();

 
结果是很简单啦,因为Window是最高层的对象,那么所谓的全局函数就是Window对象的成员函数,所谓的全局变量就是Window的成员变量啦!说到这,可以看出来Js中的对象是层层嵌套的,也就是C++中的内部类,外部类的关系啦! 

那么何时作为对象,何时作为函数?

答案是:可以作为纯对象,又可以作为纯函数,又可以同时作为对象和函数,具体返回值看你的调用方法。推荐一篇文章的参考链接:http://www.cnblogs.com/andyliu007/archive/2012/07/27/2795415.html。但是推荐归推荐,我对于这篇文章中的观点并不是完全赞同。下面是一段文章的截图:

根据作者的意思,我们可以认为:如果一个函数有返回值,那么以new的方式使用该函数时,得到的返回值与函数的返回值的类型有关,且当函数返回值是基本类型时,得到的返回值为一个object的对象;当函数返回值为一个引用类型的对象时,那么这个对象就是由这个对象的原型决定,至于是什么,并不清楚。那么请看以下代码:

function Test1() 
{this.id = 1;return 1000;
}
var myTest = new Test1();
alert( myTest.id );                                                       //          可访问!
alert( typeof myTest );
结果是



第一张图片的结果说明了返回的对象并不是函数返回值的prototype,而是一个Test1的对象。

如果返回一个对象呢?

function Test1() 
{this.id = 1;return new String("我是返回值");
}
var myTest = new Test1();
alert( myTest.id );										//     无法访问
alert( myTest.legth );
alert( typeof myTest );




说明返回的是一个实质上是String类型而typeof是object的对象,你也可以显式地将new的返回值强制类型转换成String,也一切正常。

那么有没有可能是因为String是内置的类型才可以访问?返回普通的对象也是那样吗?请看下例

function Test1() 
{this.id = 1;return new Test2();
}function Test2()
{this.id = 2;
}
var myTest = new Test1();
alert( myTest.id );
alert( typeof myTest );
结果:



说明:

如果函数返回值是原始类型时,没用,new返回的还是这个函数的对象。

如果函数的返回值是对象时,那么new返回的就是这个对象。

不过你如果没有强制类型转换的话,那么typedef出来的类型将是object。

还有需要注意,原始类型的string和引用类型的String( 首字母大写 )是不一样的,一个是值,一个是类,类可以有很多属性和方法,原始类型没有。


this.name和name的区别

那么既然谈到了this.声明的变量,那么它和不用this.声明的变量有什么区别呢?先看一段C++示例代码:

A.h文件
class A()
{public:A();~A();public:int name ;
}
A.cpp文件
A::A()
{this.name = 0;						//	成员变量,和对象同生命周期int name1 = 1;						//	函数的局部变量,函数执行完,内存就会被其他内容覆盖
}
A::~A()
{
}
JS代码

function A()
{this.name = 0;						//	成员变量,会随对象一直存在name1 = 1;							//	局部变量,会随对象一直存在(为什么这么说?测试出来的)
}

可以看出:
1、JS里的对象没有过多的访问修饰符,只有默认的public,即都可以通过"对象.变量名"的形式在外部访问。
2、JS里的name1有两种解释方法

1)  看成它对应C++构造函数内的局部变量(很多文章都称之为局部变量,如 http://www.jb51.net/article/24101.htm)。这么想的话,那么就有:JS局部变量和C++中的局部变量不同,它和成员变量的生命周期一样。

2)  这里我们它想成它对应C++中用private修饰的变量(私有变量):外部不能通过对象访问,它的生命周期也和对象一样,正好。

我比较偏向 2),因为看成是构造函数的局部变量的话,那么一个类的构造函数是访问不了的,因为JS里根据变量的函数作用域可知,里面的函数可以访问外面函数的变量的,这样才能实现闭包,二者矛盾。所以1)的类比没有2)确切。

总结一下

函数里带this的变量相当于C++中public修饰的变量。

        函数里不带this的变量相当于C++中private修饰的变量。


所谓闭包

那么问题来了,有时候我们要访问变量name1啊,怎么办呢?不能通过"对象.变量名"的形式,因为它不是对象的成员变量。怎么办呢?

方法一:C++中是通过成员函数来读写私有变量的:

A.h文件
class A()
{public:A();~A();public:int name ;private:int name1;public:getName();
}
A.cpp文件
A::A()
{this.name = 0;					//	成员变量,和对象同生命周期this.name1 = 1;
}
A::~A()
{
}
A::getName()
{return this.name1;
}

那么同样,JS中你写一个成员函数来读取或者写入
function A()
{this.name = 0;name1 = 1;this.getName1 = function(){ return name1; }
}
gN = new A();
alert( gN.getName1() );

结果:

方法二:

将函数返回出来

function A()
{this.name = 0;name1 = 1;getName1 = function(){ return name1; }
        return getName1;
}
gN = new A();
alert( gN() );

结果


这种方法涉及到两次返回,不容易理解,但是这种方法在JS里比较有名,叫做闭包。不过我个人推荐用成员函数来返回局部变量(也可以叫做私有成员变量)。因为将一个函数返回出来保存在了"全局变量中,这导致对象始终在内存中"( 引用自 http://www.jb51.net/article/24101.htm )。通过成员函数方法返回的也一样,也是始终存在于内存。

new 和 this

何为new呢?看代码
function A()
{this.name = 1;
}
a = new A();
这个过程发生了什么呢?(还是按C++的过程来模拟、类比,如有错误请指教哈)
1、new一块内存区域。
2、将这块区域的内存的地址传递给构造函数A() ;
3、运行A(),对这块区域进行变量拷贝,再加一个__proto__属性指向基类。

那么这样,this的作用也就一目了然了,就是实例对象的内存地址。故call,apply这两个JS重要而且难懂的函数的第一个参数this就很容易理解了吧。
这里仅仅说一个方面,至于原型链的指向啊都不说了,具体可以看这篇文章:http://blog.csdn.net/zacklin/article/details/7896859。

关于继承

上面说到了call,和apply,下面说一下call方式实现的继承(类式继承)。
建议先看这篇文章http://segmentfault.com/a/1190000002440502,网站页面也很漂亮( 话说国内很多大网站那页面真有点难看 )。
下面是代码
function parent( name )
{
this.name = name;
this.sayName = function()
{
alert(this.name);
}
}
function child( name )
{
parent.call(this,name);
alert(this.sayName);               //    相当于执行了一次this.name = "parent";this.sayName = function(){alert(this.name);}
}
c = new child("child");
c.sayName();
结果:


为什么说"借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起"呢?第一个alert提示说明了child对象中也有一个函数,所以喽,它没有使用parent的函数代码,即没有复用。所以这种继承会浪费内存。但是也不排除比较智能的执行器能够看出来两个函数一样就只保留一份函数也说不定,呵呵,应该不会这么智能吧?

再说说组合式继承

还是参考的这篇文章:http://segmentfault.com/a/1190000002440502。
组合式继承就是 原型继承+类式继承(就是call继承)。上面我们说了,call不仅会拷贝变量,而且会拷贝代码。那么为什么还用call呢?这么用不就行了:
function Parent(age)
{this.name = ['mike','jack','smith'];                    //                     这里面只添加属性this.age = age;
}
Parent.prototype.run = function ()                        //                     方法(成员函数)在这里(原型上)添加
{return this.name  + ' are both' + this.age;
};
function Child(age)
{Parent.call(this,age);                                         //                        call继承属性
}
Child.prototype = new Parent();                           //                      原型链继承方法var test = new Child(21);                                       //                      写new Parent(21)也行alert(test.run());                                                       //                       mike,jack,smith are both21
即只用call来继承属性,用原型来继承方法。也就是假如现在有一个父类A,如果想让它被继承,那么最好将它的属性定义在构造函数里,将它的方法定义在它的prototype里,然后用call实现属性继承,用prototype直接继承A,从而继承方法。

还有寄生式继承:

名字不知道哪来的,反正看不出来什么意思。因为组合式继承有个小问题,就是多一次调用问题。怎了解决呢?只是将组合式继承稍微改动一下,即不直接继承自A,而是直接继承自A.prototype,因为prototype是已经new好的对象,没看出来?这也解释了为什么设置prototype时需要new,这和C++不同,C++只要声明就可以。看下例
Child.prototype = new Parent();                           //                      原型链继承方法
再上代码:
function A( name,age )
{this.name = name;this.age  = age;
}
A.prototype.sayNameAge = function()
{alert(this.name+"   "+this.age);
}function B( name,age )
{A.call( this,name,age );
}
B.prototype = A.prototype;
B.constructor = B;<span style="white-space:pre">												</span>//<span style="white-space:pre">	</span>定位回来,都则就指向A,不知道为啥,求大神指教
b = new B( "我是B",2 );
b.sayNameAge();


结果:

注意:prototype的重定向会导致constructor的变化。所以需要重定向constructor。
感觉写得比那篇文章中的要简单呀,\*_*/。


再推荐一个链接:http://www.w3school.com.cn/js/pro_js_referencetypes.asp

最后

说了那么多,都是语法而已,假如某天语法变了,这些都没用了。但是思路是要有的。
在我看来,JS和C++/Java没太大区别,这也说明了编程语言都是相通的。
第一次写这么长的博文,写得乱,以后会修改,各位且凑合看哈!

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

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

相关文章

【转】C# 彻底搞懂async/await

关键&#xff1a; 异步方法&#xff1a;在执行完成前立即返回调用方法&#xff0c;在调用方法继续执行的过程中完成任务。 async/await 结构可分成三部分&#xff1a; &#xff08;1&#xff09;调用方法&#xff1a;该方法调用异步方法&#xff0c;然后在异步方法执行其任务的…

LeetCode每日打卡 - 汉明距离总和

有点慢&#xff0c;两层循环也可以完成&#xff0c;就是换个方向&#xff0c;外层遍历32大小的bits数组&#xff0c;里层遍历nums的数字每次右移一位&#xff0c;计算方法类似。 class Solution {public int totalHammingDistance(int[] nums) {//int 是4byte &#xff0c; 每…

python编写代码_用 Python 编写干净、可测试、高质量的代码

用 Python 编写干净、可测试、高质量的代码Noah Gift 2010 年 12 月 20 日发布简介 编写软件是人所承担的最复杂的任务之一。AWK 编程语言和 "K and R C" 的作者之一 Brian Kernigan 在 Software Tools 一书中总结了软件开发的真实性质&#xff0c;他说&#xff0c;“…

阿里云服务器被[kthreaddi]挖矿病毒攻击

首先我根本https://blog.csdn.net/weixin_41599103/article/details/115403332这个博客试了下并没有成功&#xff0c;所以应该是被侵入的程序不一样 先去阿里云里看一下详情 明确告诉了是通过docker被攻击了&#xff0c;先将wordpress容器停止并删除容器和镜像 kill掉进行&am…

python canvas画弧度_python画一朵玫瑰给你

听说 python 还能画画&#xff1f;是的&#xff0c;今天亲手画一朵玫瑰给你。turtle 是 python 中一个强大的绘制图像的库&#xff0c;可以用来绘制各种图像&#xff0c;使用起来很方便。但是需要计算各种角度、距离等&#xff0c;所以要想完成一幅复杂的图像还是有一定的难度的…

通俗彻底解读批处理的延迟变量

先说环境变量 一个批处理可以看成是一个程序&#xff0c;它的环境变量就是自己的全局变量。操作系统可以看成是一个大程序&#xff0c;它的环境变量同样也是全局变量&#xff0c;只不过为了和程序的环境变量区别&#xff0c;就叫做全局环境变量了&#xff0c;既然为了区别&…

LeetCode每日打卡 - 4的幂

不使用循环来今天解题 public boolean isPowerOfFour(int n) {return n > 0 && (n & (n-1)) 0 && n % 31;}首先去做了2的幂这道题&#xff0c;并了解了n&(n-1)的过程&#xff0c;然后思考一下这道题:求二进制中1的个数&#xff0c;理解了n&(n-…

【转】探索c#之Async、Await剖析

阅读目录&#xff1a; 基本介绍基本原理剖析内部实现剖析重点注意的地方总结 基本介绍 Async、Await是net4.x新增的异步编程方式&#xff0c;其目的是为了简化异步程序编写&#xff0c;和之前APM方式简单对比如下。 APM方式&#xff0c;BeginGetRequestStream需要传入回调函数…

时间计算题100道_小学数学专项练习:计算题200道,趁早打印给孩子,期末考试拿100分!...

点击上方「升学的秘诀」关注我们&#xff01;获取更多教育经验、方法、学习资料等&#xff0c;每天中午12点与您相约&#xff01;▼说到我们数学学习过程中最重要的是什么&#xff1f;毋庸置疑那就是我们的一个计算能力&#xff0c;计算可以说是贯穿了我们整个数学学习体系&…

幂等性实现 -接口幂等性

接口幂等性 1.什么是幂等性 对于同一笔业务操作&#xff0c;不管调用多少次&#xff0c;得到的结果都是一样的。 也就是方法调用一次和调用多次产生的额外效果是相同的&#xff0c;他就具有幂等性 2.为什么需要幂等性 在系统高并发的环境下&#xff0c;很有可能因为网络&#…

C 怎么读取Cpp文件_python之调用C加速计算(一)

一、前言python语言是目前比较火的语言&#xff0c;很容易上手&#xff0c;对数据处理也比较友好&#xff0c;可以用几行代码就能进行一些简单的数据处理工作。但是对于稍微大型的数值计算&#xff0c;或者一些涉及到大量循环的数值计算python的计算速度有点让人失望。即使是使…

【转】刨根究底字符编码【2.0版】(3):字符编码的由来、演变与ASCII码

为什么需要字符编码 1. 计算机一开始发明出来时是用来解决数字计算问题的&#xff0c;后来人们发现&#xff0c;计算机还可以做更多的事&#xff0c;例如文本处理。 但计算机其实挺“笨”的&#xff0c;它只“认识”010110111000…这样由0和1两个数字组成的二进制数字&#…

JS创建对象的模式介绍

转自http://www.cnblogs.com/asqq/archive/2013/02/01/3194993.html

matplotlib的优点_超详细matplotlib基础介绍!!!

(给Python开发者加星标&#xff0c;提升Python技能)来源&#xff1a;逐梦erhttps://zhumenger.blog.csdn.net/article/details/106530281【导语】&#xff1a;出色的数据可视化&#xff0c;会让你的数据分析等工作锦上添花&#xff0c;让人印(升)象(职)深(加)刻(薪)。matplotli…

【转】WPF PRISM开发入门一( 初始化PRISM WPF程序)

这篇博客将介绍在WPF项目中引入PRISM框架进行开发的一些基础知识。目前最新的PRISM的版本是Prism 6.1.0&#xff0c;可以在Github上获取PRISM的源码。这个系列的博客将选择PRISM 4.1版本来讲解。可以从微软官网上下载到PRISM 4.1相关内容。将下载下来的文件解压开&#xff1a; …

截屏悬浮软件_功能强大,却小巧的录屏软件,不在错过你的王者时刻

看看录屏是一款操作简单。功能强大的录屏软件。他可以设置你录制视频的一个分辨率&#xff0c;帧率以及录制屏幕方向&#xff0c;非常方便&#xff0c;用户将手机摇一摇就可以控制开启和停止录屏&#xff0c;高效录制精彩瞬间&#xff0c;在录制游戏视频的时候也可以做到不掉帧…

公司用的非标普通自动化用单片机还是plc_PLC的介绍

PLC又叫可编程控制器&#xff0c;一开始是替代传统接触器的一个东西。随着人工价格不断的上涨&#xff0c;自动化的设备会越来越普及。自动化不再是大企业才用的起的东西 &#xff0c;各种多元化小型自动化设备进入了普通小企业甚至家庭作坊。PLC其实是单片机开发出来的一种工业…

比较文本差异的工具_Linux 开发的五大必备工具 | Linux 中国

Linux 已经成为工作、娱乐和个人生活等多个领域的支柱&#xff0c;人们已经越来越离不开它。在 Linux 的帮助下&#xff0c;技术的变革速度超出了人们的想象&#xff0c;Linux 开发的速度也以指数规模增长。因此&#xff0c;越来越多的开发者也不断地加入开源和学习 Linux 开发…

【转】C# 动态对象(dynamic)的用法

说到正确用法&#xff0c;那么首先应该指出一个错误用法&#xff1a; 常有人会拿var这个关键字来和dynamic做比较。实际上&#xff0c;var和dynamic完全是两个概念&#xff0c;根本不应该放在一起做比较。var实际上是编译期抛给我们的“语法糖”&#xff0c;一旦被编译&#x…