JavaScript寻踪OOP之路
上一集中,重点介绍了谁动了你的代码。这里先总结一下:咱们的代码从敲下来到运行出结果,经历了两个阶段:分析期与运行期。在分析期,JavaScript分析器悄悄动了我们的代码;在运行期,JavaScript又按照自己的一套机制进行变量寻找。我们的代码是如何被动了手脚的,相信看官你已经明白。但是前面所聊均是面向过程的,如果说只是简单的面向过程言语,那JavaScript能够有基本的数据类型,基本的执行单元那也差不多了。但是故事并没有在此结束。接下来剧情的发展,那才是造成今天鞋同们困惑的地方,那们还是从故事开始。大伙不要嫌楼主啰嗦(楼主确实是个啰嗦之人),讲这故事是为了让大伙了解当年布大师设计JavaScript的背景,融入布大师的设计思维,你就知道JavaScript为什么会有哪些奇怪的设计。好,故事开始了。
前几集的故事中,咱们提到了布大师只想设计一个简单、满足浏览器进行数据检验的脚本言语。当时的web应用毫无颜值,犹如白纸黑字,顶多再加点图片。所以,你也别期待当时的布大师会想到如UI交互、动画效果等等的设计需求。为此,从一开始布大师设计的JavaScript就是一个过程式的简单的言语,但是布大师也不是个迂腐落后之人。c的升级版c++、让编程界有点疯狂的Java,布大师也不能视而不见,多少受点影响。于是乎,布大师想:我这JavaScript能否也玩点OOP思想呢?布大师这么一想,一堆问题就来了,本来就没打算搞个正式的OOP脚本,也没设计有class、extend,更没有override啥的。但是今天拍脑袋一想要玩OOP,那总得在现有的设计基础上去实现OOP三大思想(封装、继承、多态)吧。那咱们就看看布大师是如何给JavaScript赋予OOP的。
封装
概念,楼主就不说了。但是你看看JavaScript定义的那些数据类型,压根就没class的概念。没有类何来实例,没有实例谈何封装?布大师翻来覆去研究已经定义的数据类型,再对比了c++、java。他发现c++、java每次创建对象都离不开调用构造函数。布大师灵感一来“对!绕过class直接调用构造函数创建对象,刚好function可以作为构造函数”。于是乎,你见到了今天JavaScript是这样创建实现对象的:
/***
*定义构造函数
****/
function clazz(params){ }
/**通过构造函数创建实例**/
var ins=new clazz(1);
好了,创建对象的事情是解决了。但也不是说有了new就万事大吉。码农们都知道,有类得有类的数据成员吧。布大师也知道这个问题,那他是怎么赋予数据成员呢?这家伙又去挖java的idea了,他发现java实例都有this。this这个神器都可以直接访问数据成员。于是乎,他又把this扯进了function里面,用于封装实例的数据成员。这样看上去封装的事情解决得差不多了。
/***
*定义构造函数
****/
function clazz(p1,p2){
this.p1=p1;
this.p2=p2;
this.print=function(){
console.log(this.p1+this.p2);
}
} /**通过构造函数创建实例**/
var ins1=new clazz(1,1);
/***调用实例成员 修改ins1.p1 看看是否影响了ins2.p1****/
ins1.p1=9;
ins1.print(); //结果1+9=10
var ins2=new clazz(3,1);//结果3+1=4
ins2.print();
console.log("结论修改ins1.p1不影响ins2.p1;证明this下的定义是各自储存副本的");
至此,JavaScript即可创建实例,有具有实例成员,看上去没啥问题,满足了OOP的“封装”思想,但是布大师就是大师,他总觉得上面的代码还有点问题。this是属于实例的,这意味着,挂在this下面的成员不是实例共享的(ins1、ins2分别储存了各自的副本)。如上面p1、p2作为数据成员,各自实例分别拥有那是十分合理的。但是print这个函数也搞分别拥有似乎不合理啊。因为函数是基于栈运行的,而栈又是私有的,函数的执行其实就是在私有栈里面跑代码,这里将实例函数定义到this下面有点浪费内存了。布大师又脑洞大开:那我得在function上面再搞点东西来存放实例共享的成员,解决无法共享成员浪费内存的问题。于是乎,他在每个function构造函数里面搞了prototype用于存放实例共享成员,每个实例实际上都拥有prototype定义的成员。
/***
*定义构造函数
****/
function clazz(p1,p2){
this.p1=p1;
this.p2=p2;
this.print=function(){
console.log(this.p1+this.p2);
}
}
/***定义实例共享成员***/
clazz.prototype.p3=function(){
console.log("come on man!");
};
/**通过构造函数创建实例**/
var ins1=new clazz(1,1);
var ins2=new clazz(3,1); console.log(ins1.p3());//结果:come on man!
console.log(ins2.p3());//结果:come on man! /**clazz.prototype.p3 看看是否影响了ins1 ins2**/
clazz.prototype.p3=function(){
console.log("come on girl!");
};
console.log(ins1.p3());//结果:come on girl!
console.log(ins2.p3());//结果:come on girl!
console.log("定义到prototype的成员是各个实例共享的!");
布大师终于围绕着funciton搞掂了OOP的封装思想,对funciton瞬间大爱啊!那接下来的继承,看来布大师还是会围绕function来动脑筋了。看官你听说过“function是JavaScript一等公民”这个说法不?布大师在给JavaScript扯OOP的时候全靠funciton,你说它能不是一等公民嘛?那咱们看看布大师是如何又对function做手脚来实现继承的。
继承
说到继承,那得先有个继承链上的上帝,Java继承链上的上帝是Object。毫无疑问,布大师又将JavaScript的Object封为上帝,JavaScript里面的一切对象实例(引用类型)都是这个上帝的子民。有了上帝那还得有个维系上帝与子民之间关系的纽带啊。布大师又头痛了,我这里没有设计extend啊,怎么建立继承关系呢?他又将目光投到了function身上。话说function都具有创建实例的能力了,那就在它上面再加点料让它具有指向上帝的功能,那既不是解决了问题啊。于是他随意一划,在function上加了个"__proto__"用于指向当前子民的上帝。"__proto__"是加上去了,但是要不要让程序员手动去编写”function.__proto__==上帝“才建立子民与上帝的关系呢?布大师想了想,还是搞个内部自动实现吧,于是乎,你看到了每个对象都默认有个“__proto__”指向上帝Object。“__proto__”有了,同时还有个"prototype",老布看着两玩儿,好像都差不多,也是他又想,既然都看着差不多,那么内部实现就都合并一起吧,所以咱们定义的prototype成员实际上是被合并到了“__proto__”中。“__proto__”是隐形的而prototype则是开放给程序员的。
话说,布大师解决了子民如何找到上帝的问题的,但是这还不够。子民本身有自己的成员,上帝也有上帝的成员。我们在调用子民成员的时候,如何兼顾调用上帝成员的能力呢?那这个调用逻辑得做下处理。通过前面的封装故事,你已经知道了数据被封装到了实例对象[this],我们调用成员的时候都是找实例[this]成员,并没有向那个指向上帝的“__proto__”要数据啊。于是他做下调用逻辑的调整:先找当前实例this上是否存在被调用的成员,没有则在指向上帝的"__proto__"里查找。于是乎继承实现了。这里强调一下:在function.prototype上定义的成员实际上也是被调整到了”__proto__“中,而”__proto__“中又有”__proto__“,这样就形成了所谓的原型链。
看到这里你应该明白了,既然“__proto__”是用于指向父类的,而prototype最终也是和”__proto__“合并一起,如果我们通过修改prototype的指向是不是就实现了对父类的继承呢。正确,这也是布大师所想的,但这么一修改就存在一个问题了,prototype本来是属于某个fuction的,修改后指向了另外的对象,于是乎,布大师又往”__proto__“加了个constructor用于指向归属的function(构造函数),以表明这个”__proto__“的归属,如果我们通过修改prototype指向父类,还得手动将其constructor指向修正回来。于是布大师的继承是这样实现的。
/***定义一个人类构造函数,作为男人、女人的基类***/
function human(sex,name){
this.sex=sex;
this.name=name;
}
/**人都会说话* ***/
human.prototype.say=function(){
console.log("人都会说话!");
console.log("我的性别是:"+this.sex);
console.log("我的名字是:"+this.name);
}
/**
* 定义一个女人构造函数
* ***/
function lady(hobby){
this.hobby=hobby;
}
/**原型继承**/
lady.prototype=human.prototype;
/**记得修改归属**/
lady.prototype.constructor=lady;
var girl = new lady("化妆");
/**女人拥有了人类能说话的能力***/
girl.say();//结果 人都会说话! 我的性别是:undefined 我的名字是:undefined
结果有问题啊!女人是可以说话了,但是人类的性别、名字怎么继承啊?布大师又遇到了恼火的问题:原型继承只能继承prototype上定义的成员,无法继承父类对象上的this成员?他又想点子了。还得拿function开刀。他想了想:构造函数里的this成员变量是实例私有的,this.sex、this.name只归属与human实例的,无法归属于lady实例呢?除非有个偷梁换柱的办法,运行下human函数,运行期间让里面的this换成lady的this。这样借助JavaScript的动态属性,让lady的this偷偷加上sex和name。于是乎他在function上加了两个方法:call、applay。专门干偷梁换柱的事情。上面的代码进一步改良:
/***定义一个人类构造函数,作为男人、女人的基类***/
function human(sex,name){
this.sex=sex;
this.name=name;
}
/**人都会说话* ***/
human.prototype.say=function(){
console.log("人都会说话!");
console.log("我的性别是:"+this.sex);
console.log("我的名字是:"+this.name);
}
/**
* 定义一个女人构造函数
* ***/
function lady(hobby,sex,name){
//依赖javascript的动态属性功能,通过父类构造函数的call方法实现偷梁换柱
human.call(this,sex,name);
this.hobby=hobby;
}
/**原型继承**/
lady.prototype=human.prototype;
/**记得修改归属**/
lady.prototype.constructor=lady;
var girl = new lady("化妆","woman","pretty-mm");
/**女人拥有了人类能说话的能力***/
girl.say();//结果 人都会说话! 我的性别是:woman 我的名字是:pretty-mm
看到这里我觉得看官你应该明白了JavaScript的是怎么玩对象创建,怎么玩继承的了。上述内容也只能说抓住要点进行故事般推理,实际详尽的知识点肯定不是这寥寥几段文字能够说清的,要不然《JavaScript高级程序设计》那书怎么能像枕头那般。楼主只是觉得理解了这些要点,再加以进一步学习,玩转JavaScript的oop肯定不在话下。这段是废话了,面向对象的三大特性,咱们只讲了封装、继承、那多态性呢?JavaScript又如何体现?
多态性
JavaScript实现OOP三大特性,多态性是弱爆的了。布大师实现的封装、继承,虽然确实是非主流,但至少也是实现了。而多态性,布大师就搞得有点潦草了。我估计布大师当时已经有点不耐烦了,所以也没像前面封装、继承那样用心考虑设计,再说了多态性这玩儿层次较高,JavaScript完美实现多态性思想,按当时的需求似乎也有点多余。于是,布大师对多态性的实现,可以说比较简单,那下面就一起聊聊JavaScript的多态性。
由于多态性是个高层次的思想,在说JavaScript多态性之前,楼主得先表达下自己对多态性的理解。楼主认为:多态性就是某个事物、行为呈现的多种状态,在OOP编程里面可以说到处都是多态性的体现。如一个Class可以根据不同的数据产生不同的instance,同一份Class不同的instance那是不是多种状态了?如函数的重写、重载是不是同一个函数可以产生多种不同的行为结果?这又是多态性的体现了。更甚设计模式里面的”工厂模式“,是不是一个工厂去产生不同的实例?还有面向接口编程,一个接口,N份实现......。可以说在OOP编程里面多态无处不在。楼主有时候甚至想:既然都无处不在了,多态性这玩儿还有必要拿出来谈嘛?谁要是知道这个问题的答案,欢迎给楼主答疑。废话了,咱们还是回归布大师是如何给JavaScript弄点多态性的。
话说,多态性最经典的表现就是函数的重写、重载。重写是函数实现的覆盖、重载则是函数签名的多种形式。布大师也明白,JavaScript已经被他赋予动态成员(属性)超级能力,对于重写,通过这个超级能力毫不费劲就实现了,如上面human的say函数,如果女人们想说点女人才说的话,那得重写human的say函数,这个轻松实现:
/***定义一个人类构造函数,作为男人、女人的基类***/
function human(sex,name){
this.sex=sex;
this.name=name;
}
/**人都会说话* ***/
human.prototype.say=function(){
console.log("人都会说话!");
console.log("我的性别是:"+this.sex);
console.log("我的名字是:"+this.name);
}
/**
* 定义一个女人构造函数
* ***/
function lady(hobby,sex,name){
//偷梁换柱
human.call(this,sex,name);
this.hobby=hobby;
}
/**原型继承**/
lady.prototype=human.prototype;
/**记得修改归属**/
lady.prototype.constructor=lady;
/***重写say,让女人们都说女人爱说的话***/
lady.prototype.say=function(){
console.log("我是女人,我当然喜欢化妆啊!");
} var girl = new lady("化妆","woman","pretty-mm");
/**女人拥有了人类能说话的能力***/
girl.say();//结果 我是女人,我当然喜欢化妆啊!
得益于动态成员(属性)的超级能力,JavaScript实现重写,毫不费劲。布大师也可以偷懒一下。但是重载呢?重载呢?重载呢?布大师还是得费脑子了。大师就大师,人家实现重载也是惊艳加省事(当然他这么一搞,咱们写JS的就不省事了)。为了应对一个函数,N个参数的重载实现,布大师又拿function开到,给function加了个arguments用于存放函数的参数,不管你有多少个参数,都可以任意在这个argments里面获取,你也不需要写function(p1,p2,p3.......)这样的代码,你只需要在函数体内根据arguments的长度走不通的逻辑即可。如下
function f(){
var args=arguments;
if(args.length==0){
console.log("当f函数没有传参的逻辑");
}else if(args.length==1){
console.log("当f函数只有一个传参的逻辑");
}else{
console.log("当f函数参数多个的逻辑");
}
}
看,布大师是够省事了,但是苦了咱们写代码的了,要走一大段if...else...。那后来布大师还对此做改进啥的不?抱歉,好像没有了,当然也可能楼主孤陋寡闻,没有了解到的可能。各位看官要是有兴趣,可以进一步研究研究。
写了那么多,看样子对JavaScript寻踪OOP之路也吹得差不多了。以上内容要是有何错误、纰漏,还请客观斧正!
JavaScript寻踪OOP之路的更多相关文章
- JavaScript的OOP编程1
首先要说的是,javascript其实是可以进行OOP编程的,其次javascript的OOP编程实现方式有多种,我写的这一种只是我测试过,可行的一种 version1 // 父类 function ...
- JavaScript 的 OOP 功能解析
根据JavaScript创始人Brandon Eich 自己的说法,JavaScript 最好的语言构造是: 函数是一等公民 (first class functions) 闭包 (closure) ...
- JavaScript面向对象(OOP)
前 言 JRedu 面向对象程序设计(简称OOP)是现在最流行的程序设计方法,这种方法有别于基于过程的程序设计方法.在写面向对象的WEB应用程序方面JavaScript是一种很好的选择.它能支持 ...
- 从前端到全栈:JavaScript逆袭之路
JavaScript如何做到上天入地无所不能?JavaScript真的能一统江湖吗? 背景 近年来,前端技术日新月异,前端已经不仅仅是网页,更多的开始由狭义向广义发展. 先后涌现出了具备后端能力的no ...
- JavaScript原型OOP——你上车了吗?
.title-bar { width: 80%; height: 35px; padding-left: 35px; color: white; line-height: 35px; font-siz ...
- Javascript模块化编程之路——(require.js)
转自:http://www.ruanyifeng.com/blog/2012/10/javascript_module.html Javascript模块化编程(一):模块的写法 随着网站逐渐变成&q ...
- javascript实现OOP编程
1.首先通过一个函数来实现JS的单继承.使用原型方式来实现继承 (function () { $.extend({ oop: { extend: function (child, father) { ...
- JavaScript的进阶之路(七)客户端JavaScript知识点总结
一.客户端JavaScript主要是BOM DOM的操作和js脚本的兼容性.互用性.可访问性.安全性的应用.以及一些框架的引用. 二.BOM:浏览器对象模型 主要介绍window对象 1.定时器:se ...
- JavaScript的进阶之路(六)理解函数
函数:定义一次,多次调用:用于对象的属性则称为对象的方法:在JavaScript中,函数即对象:嵌套的函数形成闭包: 定义函数和简单调用函数: //函数定义 function f1(){ //没有参数 ...
随机推荐
- 四 AndEngine 画线
package com.example.AndEngineExample02; import org.anddev.andengine.engine.Engine;import org.anddev. ...
- Android的两种上下文的区别
1.Activity.this,Activity是间接继承自Context 2.getApplicationContext()返回来的就是Context 3.getBaseContext()返回的也是 ...
- UIScrollView 不能滚动的问题
uiscrollview是开发sdk自带的控件, 在使用的时候,发现滚动不了, 最常山见的原因是 contentSize 这个属性,比uiscrollview的frame要小...所以无需滚动,自然就 ...
- 序列化、反序列化和transient关键字的作用
引言 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口, ...
- 对JAVA集合进行遍历删除时务必要用迭代器
java集合遍历删除的方法: 1.当然这种情况也是容易解决,实现方式就是讲遍历与移除操作分离,即在遍历的过程中,将需要移除的数据存放在另外一个集合当中,遍历结束之后,统一移除. 2.使用Iterato ...
- Garlands
题意: n个数分成m段,每段偶数个数,最小化和最大段的半个区间的数字和. 分析: 先想到了二分,dp求能分成的最小段数. #include <map> #include <set&g ...
- php动态生成一个xml文件供swf调用
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdo ...
- oracle 查看表的相关信息
1.查看当前用户的表 SELECT * FROM user_tables; 2.查看指定用户的表 SELECT * FROM all_tables WHERE owner = 'SYS';
- 高效使用STL
高效使用STL 参考:http://blog.jobbole.com/99115/ 仅仅是个选择的问题,都是STL,可能写出来的效率相差几倍:熟悉以下条款,高效的使用STL: 当对象很大时,建立指针 ...
- Asp.net MVC Bundle 的使用与扩展
一.Asp.net 自带Bundle的使用: 1. 在Globale中注册与配置 BundleConfig.RegisterBundles(BundleTable.Bundles); public c ...