JavaScript——我理解的OOP

此篇文章是看完《 JavaScript程序设计》—— 第五章 函数这一篇有感而发,觉得自己作为新手,而JavaScript又作为自己学的第一门语言,对原型链、构造器、this等特性概念的理解掌握对面向对象这一编程思想的形成有重要意义,所以我会把自己的不那么准确但尽量准确的理解写一写。

作为属性的函数

唔,就从5.5.2 作为属性的函数这里说起吧~我会经常引用书里的原话,尽量去理解作者的含义。

由于函数也是值,所以可以作为对象的属性。把函数放在对象内部有两个主要理由第一个理由是把许多相关函数放在一组。把许多函数组合到单个对象中,有助于组织和理解大型程序。人类不希望去尝试理解一个拥有数百个甚至数千个函数的系统,如果一个系统只有数十个软件组成部分,那我们理解起来会容易很多。例如,在一个游戏程序中,我们会很自然地为玩家、地貌、物理属性、消息传递、装备、图像等分别创建出子系统,每个都是一个很大的对象。

将函数作为属性的第二个理由是让程序从面向过程转向面向对象。例如,我们不一定要将函数看作对形状执行操作,将函数存储为形状的属性。将函数放在对象的内部,可以让人们专注于这些函数,让函数扮演对象行为的角色。

面向对象编程这词儿相信很多人听说过,啥是对象?!我没有对象啊!乱说啥?好了,那我们就用JavaScript生成一个对象不就好了嘛~
        var firstGirlfriend = {
            // 属性
            sex:"woman",
            behaviorA:function () {return ("漂亮")},
            behaviorB:function () {return ("前凸后翘")},
            skill:{first:"做家务",second:"按摩",thirdly:"PAPAPA"}
            // 方法
            behaviorC:function () {
                return ("我会:"+this.skill.first+" | "+this.skill.second+" | ")
            },
            test:function () {return this;}
        };
        alert(firstGirlfriend.behaviorC())                 // "我会:做家务 | 按摩 | "
        alert(firstGirlfriend.test()===firstGirlfriend)    // true
YEAH!!我有对象啦,嗯哼一看,卧槽广大群众义愤填膺了,TM的有妹子就不错了还做家务!按摩!!最后那是神马?哦,没看见。咳咳,都说了自定义的妹子嘛~肯定得全能一些嘛~实际情况是,那些做家务、按摩这些技能树肯定是我来点啦,妹子只负责貌美如花就OK了~blablabla~
这里先给个结论,不是很准确,关于this我会另开一篇文章谈我的理解:
  • 在最外层代码中,this引用的是全局对象。
  • 在函数内,this引用根据函数调用方式不同而有所不同。
  • 接收方对象:通过点运算符或中括号运算符调用对象的方法时,在运算符左侧所指定的对象。

var obj = {
    x:3,
    doit:function () {alert("method is called."+this.x);}
};
obj.doit();            // 对象obj时接收方对象。doit是方法
obj["doit"]();         // 同上

回到咱们的"妹子"那儿,我想知道妹子有有什么技能,所以我用一个方法(behaviorC())让她自己说出来,这时谁是接收方呢?没错,是妹子firstGirlfriend,妹子自己说自己会什么嘛,当然是她自己了,test()也证明了

方法中,this.skill.first,先确定this引用的对象,然后读取属性值,最后成为全局函数alert的传入值,被弹出。

好了大概知道this是什么我们就要继续了。

想一想古代的皇帝,三宫六妾的,想想现在的一夫一妻制,哇哈哈,崩溃ing。那怎么办,我想要更多的妹子啊:)(程序媛看到这里不要拉黑我,我只是想想而已),难道一个一个码吗?当然不会,所以我们写一个函数来生成妹子
        var GirlfriendPlant = function (s) {
            return {
                sex:s,
                behaviorA:function () {return ("漂亮")},
                behaviorB:function () {return ("前凸后翘")},
                skill:{first:"做家务",second:"按摩",thirdly:"PAPAPA"},
                // 方法
                behaviorC:function () {
                    return ("我会:"+this.skill.first+" | "+this.skill.second+" | ")
                },
                test:function () {return this;}
            };
        };
           
        var girl_a = GirlfriendPlant("woman");
        var girl_b = GirlfriendPlant("man");
        alert(girl_a.behaviorB());            // "前凸后翘"
        alert(girl_b.sex+" | "+girl_b.behaviorC("man | 我会:做家务 | 按摩 | "));  // :)

瞧,是不是不用一个一个的写啦?而且我还高度定制了一些"功能",比如......咳咳,我是异性恋,但是男生力气大做家务也快不是?
但是!还是有不足的地方,每个妹子出生就自带这些技能和属性了,可是有些妹子是不愿学习某些技能的,比如一些方法,一些属性也不想表露出来,怎么办?

这段代码看上去没问题,却有一个严重缺陷,每次创建一个对象,都另行创建了额外的属性、方法。在创建多个对象时,会浪费大量的内存来保存这些函数方法的冗余副本,这是很糟糕的事情,因为内存资源是有限的。当脚本耗尽内存就会崩溃。但是,我们还有一个方法来解决它们!
        // 工厂设置制造车间,protoMGP对象代表一个技能属性坑齐全但是未命名未定制的妹子
        var protoMGP = {
            sex:undefined,
            behaviorA:function () {return ("漂亮")},
            behaviorB:function () {return ("前凸后翘")},
            skill:{first:undefined,second:undefined,thirdly:undefined},
            // 方法
            behaviorC:function () {
                alert("我会:"+this.skill.first+" | "+this.skill.second+" | "+this.skill.thirdly);
            }
        };
        // 工厂参数输入车间
        var middleGirlPlant = function (sex,f,s,t) {
            // 获得车间制造的妹子(对象),注意获得的只是粗胚,为设置参数,但已经留好坑了
            var girlObj = Object.create(protoMGP);        
            // 开始定制妹子                
            girlObj.sex = sex;                            
            girlObj.skill.first = f;
            girlObj.skill.second = s;
            girlObj.skill.thirdly = t;
            // 返回定制好的妹子(对象)
            return girlObj;
        };  
        //现在开始制造妹子
        
        var gA = middleGirlPlant("woman","洗衣","做饭","LOL");
        console.log(gA.skill.first+" | "+gA.skill.second+" | "+gA.skill.thirdly);
        // "洗衣 | 做饭 | LOL"
        
        var gB = middleGirlPlant("man","Java","C","JavaScript");
        console.log(gB.skill.first+" | "+gB.skill.second+" | "+gB.skill.thirdly);            
        // "Java | C | JavaScript"

看,我们的妹子不但满足自定义属性方法,并且这些属性方法并不保存在每个妹子对象上,而是在她们的工厂中,只要需要随时可以调用它们。(好吧这里实在抽象不起来了)

每个通过middleGirlPlant创建的对象都有自己的sexskillbehaviorA()behaviorB()behaviorC()属性,方法,这些属性方法是由每个对象里的prototype引用的对象提供的,每个对象的隐藏链接都指向唯一共享原型,其中包含了上面所述的那些属性方法。不过还有一个小小缺陷。我们使用了两个全局变量middleGirlPlantprotoMGP。如果有一个就更好了,这样我们的原型作为函数的一个属性(对象)。接下来,就是引出new这个构造器了。

JavaScript中的每个函数对象都自动包含一个prototype属性,prototype是函数两个预定义属性中的第二个,第一个length。只要函数一经定义,它的prototype属性就会被初始化为一个全新对象。(这个全新对象有自己的一个属性,叫做constructor)。
        // 创建构造函数highGirlfactory
        function highGirlfactory(s) {
            this.sex = s;
            var test = "哇哈哈,我有女朋友啦!!!!";
            return test;
        };
        // 构造函数的prototype(原型)
        highGirlfactory.prototype.behaviorA = function (a,b,c) {
            this.first = a;
            this.second = b;
            this.thirdly = c;
        };
        // 构造函数的prototype(原型)
        highGirlfactory.prototype.behaviorB = function () {
            console.log("我会:"+this.first+this.second+this.thirdly);
        };

        // GirlA对象
        var GirlA = new highGirlfactory("woman");
        GirlA.behaviorA("Ax","Ay","Az");
        GirlA.behaviorB();                        // "我会:AxAyAz"
        // GirlB对象
        var GirlB = new highGirlfactory("woman");
        GirlB.sex = "SEX";
        console.log(GirlB.sex)                    // "SEX"
        // ---
        var Test = highGirlfactory();
        alert(Test);                              // "哇哈哈,我有女朋友啦!!!!"
        // 原型链
        alert(highGirlfactory.prototype.constructor===highGirlfactory)    // true
        alert(GirlA.__proto__===highGirlfactory.prototype)                // true

图片描述

就不废话了.....当你使用new操作符,就无需明确链接(Object.create)原型,也无需返回新创建的对象。当你在函数调用之前加上了new时,会发生什么?引自《JavaScript程序设计》《JavaScript编程全解》

  • 调用构造函数new表达式的值是(被生成的)对象的引用。通过new表达式调用的构造函数内的this引用,引用了(被新生成的)对象。

见人说人话,见鬼说鬼话:)。【这里要注意的是,构造函数也是函数!所以它也可当普通函数使用哟(看最后代码)】。我们调用new表达式后,会隐式生成一个新对象,但是对象不是赋值的,是引用的,所以说完就是生成了一个新对象的引用,代码中就是var GirlA = new highGirlfactory("woman")将这个引用赋值给了变量GirlA。然后!构造函数里的(this引用)引用了新对象,嗯这样断句应该没错@_@,这里要提到一个之前没说清楚的知识,this这个功能全称叫this引用,我们为了形象点说指向。意思就是this指向的是新对象,接收方对象就是那个新生成的对象。so.......

  • 这里先抄点板书,所有的函数(对象)都支持一种成为原型链的功能。使用原型链有两个前提:
1. 所有的函数(对象)都具有名为prototype的属性(这个属性引用的对象成为prototype对象)。
2. 所有的对象都含有一个(隐藏的)链接,用以指向在对象生成过程中所使用的构造函数(Function对象)的prototype对象。

  • 满足上面后,便有了我们的原型链。其中对象对属性的读取(以及对方法的调用)是按照以下顺序查找的:
  • 对象自身的属性
  • 隐式链接所引用的对象(构造函数prototype对象)的属性
  • 上面的对象的隐式链接所引用的对象的属性
  • 反复按第三项的规则查找直至全部查找完毕,终点是Object.prototype对象。
所以,我在 GirlB.sex = "SEX";这里重新创建一个键值对,在console.log(GirlB.sex)时,由于自身属性就存在这个键值对,不会在搜索到原型里的sex属性。

关于Object.prototype我理解不是很深,到时也会在研究一下。


  • 对于原型链(_proto_)这个玩意儿,他就是那个神秘的隐式链接,在new生成的新对象中,里面的_proto_引用的对象就是原型对象。

    GirlA.__proto__===highGirlfactory.prototype

  • 构造函数的prototype引用的对象里还有一个constructor属性,这个东东引用的是构造函数,没错就是自己找自己。书中是这么写的:

    highGirlfactory.prototype.constructor===highGirlfactory

可以通过使用对象的constructor属性来从对象处获取其构造函数。如果能获知对象的构造函数,也就能够知道该对象的原型继承情况了,于是便可以了解这个对象的一部分操作。
constructor属性不是对象的之前属性,而是通过原型链查找到的属性。

嗯,不懂~,先暂时理解为获取构造函数吧(弄懂回来补充)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值