此篇文章是看完《 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
创建的对象都有自己的sex
、skill
、behaviorA()
、behaviorB()
、behaviorC()
属性,方法,这些属性方法是由每个对象里的prototype
引用的对象提供的,每个对象的隐藏链接都指向唯一共享原型,其中包含了上面所述的那些属性方法。不过还有一个小小缺陷。我们使用了两个全局变量middleGirlPlant
、protoMGP
。如果有一个就更好了,这样我们的原型作为函数的一个属性(对象)。接下来,就是引出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
属性不是对象的之前属性,而是通过原型链查找到的属性。嗯,不懂~,先暂时理解为获取构造函数吧(弄懂回来补充)