如果它走起路像鸭子,叫起来也是鸭子,那么它就是鸭子。 ——鸭子模型
从前在Javascript王国里有一个国王,他觉得世界上最美妙的声音就是鸭子的叫声,于是国王召集大臣,要组建一个1000只鸭子组成的合唱团。大臣们找遍了全国终于找到999只鸭子,但是始终还差一只,最后大臣发现一直特别的鸡,它的叫声跟鸭子一模一样,于是这只鸡就成为了合唱团的最后一员。
这个故事告诉我们,国王要听的只是鸭子的叫声,这个声音的主人到底是一个鸡还是要鸭子并不重要。鸭子类型指导我们只关注对象的行为,而不关注对象本身。
ECMA-262把对象定义为:对象是无序属性的集合。属性可以包括基本值,对象或者函数。
通俗点就是说:对象是一个对外封闭的整体,不关注内部细节,外界只需要掌握其属性
或者说是操作方法
就可以了。
比如jquery:大多数时候用好就行了,使用者无需关心怎么实现的。 面向对象不只是局限于编程的思想,而是通用的概念。
主人家里养了两只动物,分别是一只鸭子和一只鸡,当主人向它们发出“叫”的命令时,鸭子会“嘎嘎嘎”叫,而鸡会“咯咯咯”。这两种动物会以自己的方式来发出叫声。它们同样“都是动物”并且可以发出叫声,但根据主人的主指令,它们会发出不同的叫声。
同一操作作用于不同的对象,结果不同。JavaScript的多态实际上是吧做什么和谁去做区分开了。
父类存在时,子类只需要继承了父类的对象,就可以实现一个新的对象。多重继承:继承多个父级的属性。
狭义的属性是静态的。而动态的称为方法。
var a=12;//a是一个变量
var Aaa.a=12//a是一个属性——它依附于对象Aaa
function b(){alert(a)};//这是一个函数
Aaa.b=fuction(){alert(a)}//这是对象Aaa一个方法
【小结】属性本质是变量,区别在于从属关系。方法本质是函数。
首先定义一个show方法
var arr=[1,3123,43,534];
arr.show=function(){
alert(this.length);
}
arr.show();
弹出结果是4,这个show方法里的this就是arr。
this实际上表示当前的方法属于谁。 this:谁发生事件,this就是谁。
function show () {
alert(this);
}
弹出结果是window——说明show函数只是顶层对象window的一个方法。所以上述程序等价于:
window.show=function() {
alert(this);
}
var obj=new Object();
obj.name='欲饮琵琶马上催';
obj.sex='女';
obj.show=function(){
alert('我的博客名是:'+this.name);
}
obj.showSex=function(){
alert('我是'+this.showSex+'的');
}
这个对象就完成了,不但有名字,还有性别,挺好挺好。 事实上我们需要多个对象时,如果每次都这样定义,这样写就及其麻烦。
解决此问题比较好的思路是工厂模式。通过此模式,可以抽象对象的创建过程。 创建一个函数(createPerson),把上面的代码包装起来。
function createPerson(name,sex){
var obj=new Object();
obj.name=name;
obj.sex=sex;
obj.showName=function(){
alert('我的博客名是:'+this.name);
}
obj.showSex=function(){
alert('我是'+this.sex+'的');
};
return obj;//必须返回对象本身
}
此函数接收两个参数,一个是名字,一个是性别 需要创建时,可以写
var person1=createPerson('葡萄美酒夜光杯','男');
var person2=createPerson('欲饮琵琶马上催','女');
p1.showName();
那就可以想调用就调用了。
这样,通过函数,只要创建了对象,把名字,性别等作为参数传递进去,就可以(抽象地)创建一个新对象。 工厂方式特征——原料(obj),流水生产线(构造函数),出厂线(调用)。 p1=new createPerson();然后把构造函数中的obj改为this 工厂模式解决了创建多个相似对象的问题,但是没有解决对象识别问题。
上面说过createPerson其实是个普通函数,但是它是用来创建对象的函数。所以得名构造函数。 接下来解决工厂模式没有new的问题。把这个构造函数改名为Person以示区别。
function Person(name,sex){
this.name=name;
this.sex=sex;
this.showName=function(){
alert('我的博客名是:'+this.name);
}
this.showSex=function(){
alert('我是'+this.sex+'的');
};
}
var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
person2.showName();
效果完全一样。
与createPerson相比,没有了return语句。在this写出来以后,抽象地指向了一个Object()。 比如在javascript中抽象定义"马"这个概念,然后定义“马”应该有名字,有颜色..(属性),还有可以做的事情——比如奔跑…(方法)。而new Horse(…)就是创建了一个Horse的实例,就如同通过告诉告诉计算机“马”是什么东西,然后再告诉计算机“ta是一匹马,白色,名字叫的卢,日行千里”,那计算机很自然根据马的概念解析出“的卢是一匹日行千里的白马”。
每当new一个Person:
现在可以慢慢理解面向对象编程的三个特点了。"的卢"属于世间万物(window),也属于我们所封装的马类对象,继承了它所有父级的特点,具有万物特点比如具有window下的常用属性。也继承了马类的特点(有名字,颜色,还能奔跑),再具体到的卢和其它马的实例相比,能够日行千里。是谓之多态。
构造函数的优势在于有了它标示类型(抽象出一个概念,比如Person,Horse),让计算机较好的理解操作者的意思。只要你对一个函数使用new来调用,它就是构造函数。
然而之前提到浪费资源的问题依然存在。就是声明对象实例时,每个方法都要在实例上重新创建一次。一次,不同实例下的同名方法是是不相等的。多见多个对象等于定义了很多次函数。 解决方案:把方法函数放到全局作用域中。
function Person(name,sex){
this.name=name;
this.sex=sex;
this.showName=showName;
this.showSex=showSex;
};
function showName(){
alert('我的博客名是:'+this.name);
};
function showSex(){
alert('我是'+this.sex+'的');
};
var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
console.log(person2.showName==person1.showName);
这时候返回的就是true了。很悲剧的是,我需要大量方法时,就得创建多个全局函数。封装的特点名不副实。
javascript中,只要是函数,都有一个隐藏的prototype属性。它指向一个对象,这个对象包含了所有实例都可以使用的对象和方法。
因此解决方案是:对象.prototype.新方法=函数
function Person(name,sex){
this.name=name;
this.sex=sex;
}
Person.prototype.showName=function(){
alert('我的博客名是:'+this.name);
};
Person.prototype.showSex=function(){
alert('我是'+this.sex+'的');
};
var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
person2.showSex();
换句话说,原型模式是给一类
(严格来说javascript没有类)的对象
添加方法。我们可以对这个prototype加自定义的方法,实现所有对象共享一个方法。这种构造方法又有构造函数的属性,又有原型称为混合模式。
在js中: “类”:就是构造函数。没有功能,只能用来构造对象。 对象:是一个实例。
对一个实例定义定义了一个的属性名,这个属性会屏蔽掉对象中保存到prototype的同名属性,但不会修改原型对象的同名属性。但如果你用delete方法删除。原型内的属性又可以访问了。
Array.prototype.a=12;
var arr=[1,2,3];
console.log(arr.a);//12
arr.a=4;
console.log(arr.a)//4
delete arr.a;
console.log(arr.a)//12
注意:这里针对的是狭义的属性。而不是方法。
var str='1,2,3';
console.log(str.split(','));//['1','2','3']
str.split=function(n){return n};
console.log(str.split(','));//不会屏蔽!['1','2','3']
delete str.split;
console.log(str.split(','));//['1','2','3']
判断一个属性是属于原型还是实例,用hasOwnPrototy(属性名字符串)的方法。 person方法的最后小结: 看起来更像使用系统属性。减少了重载。但是prototype方法的优先级低于构造函数法。(类似class和行间样式的关系)
也许,你还可以把代码写的更好看一点。
function Person(name,sex){
this.name=name;
this.sex=sex;
};
Person.prototype={
showName:function(){
alert('我的博客名是:'+this.name);
},
showSex:function(){
alert('我是'+this.sex+'的');
}
};
var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
person2.showSex();
但是还不能满足,设想一个场景:
function Person(name,sex){
this.name=name;
this.sex=sex;
};
我想不修改源码,但是要扩展这个Person对象的功能,除了使其拥有showSex方法和showName方法外,还拥有一个新的属性:job。可以这么做: 通过设置prototype,构造一个Info对象。然后,new一个Info实例,把它赋给Person的prototype!
function Info(){
this.job='coder';
}
Info.prototype={
showName:function(){
alert('我的博客名是:'+this.name);
},
showSex:function(){
alert('我是'+this.sex+'的');
}
};
function Person(name,sex){
this.name=name;
this.sex=sex;
};
Person.prototype=new Info();//关键步骤:Person的prototype竟然是Info的一个实例!
var person1=new Person('葡萄美酒夜光杯','男');
person1.showSex();//弹出 男
console.log(person1.sex)//男
console.log(person1.job)//coder
假设上你的js类库里已经存在了这么一个Person对象,已经实现了showSex和showName功能,还有job属性。我想修改它的showSex方法又不让改代码,可以直接把它覆盖掉:
Person.prototype.showSex=function(){
alert('我是'+this.sex+'的'+','+'我的取向是'+this.job)
}
以下举例两个简单的js方法的封装实现。
var arr1=new Array(1,2,3,4);
Array.prototype.sum=function (){
var result=0;
for(var i=0;i<this.length;i++){
result+=this[i];
}
return result;
};
var arr2=new Array(4,5,6,7)
console.log(arr1.sum())//10
console.log(arr2.sum())//22
console.log(arr1.sum==arr2.sum)//true
数组对象原生的方法中没有求和的方法,通过它,扩展了arr的内涵。
String.prototype.trim=function(){
return this.replace(/^\s+|\s+$/g,'');
}
var str=' saf dasafw grfe ';
console.log(str.trim())//'saf dasafw grfe'
当浏览器不支持trim方法时,通过原型扩展了系统方法的功能。大大扩展了系统函数。 有了prototype,实例的被定义来的方法可以放一块。类和构造函数其实是一个东西。 构造函数在调用时需要new。所以构造函数首字母大写。 属性:每个对象不同,但方法所有对象都相同。