真的了解JS的面向对象吗?
js的几种数据类型:number, string, boolean, object, undefined五种数据类型
js的常见内置对象类:Date, Array, Math,Number,Boolean, String, RegExp,Function,Object。
js的两个类型判断方法:typeof、instanceof
typeof:一元运算符,eg: typeof(1)将返回number.它返回的是顶级的几种数据类型
instanceof:二元运算符 eg: myObj instanceOf MyObj返回的是一个boolean值。应用数据类型包括自己定义的类。判断变量是不是此类的实例,它和typeof都只能应用于js内置及自定义的数据类型,不包括DOM对像,例如window,document.
我们平台声明的数据类型也就是number,string,boolean,object undefined及其object的子类型。声明的类型也就拥有所属类型的本性方法,常用的有string类型的substring,indexOf,length方法。主要的是要掌握function定义出的类型。
我们利用function可以声明一个函数,也可以做为创建类的关键字。当作为函数关键词使用时,没有什么好说的。但是当做为创建类的关键词时,我们需要知道js的一些特征东西。(使用function做为函数关键词还是类关键词,因自己的目的而定)。当我们使用函数关键词时js面向对象的特征基本用不到,所以就不多说了。现在就说一下function做为类关键词,我们需要常用的一些js特征,以适应我们的面向对象的三大特性:封装、继承、多态。
1、封装性:
类型于其它的面向对象语言一样,用大括号来限定变量的作用域。这样就有了局部变量。如果声明在最外面,那就是全局变量。那么现在还少的是什么?就是成员变量。幸好js支持闭包特性,例如:
- function Person(){
- var name="张三";
- var sex ="男";
- return {
- "getName":function(){
- return name;
- },
- "getSex":function(){
- return sex;
- },
- "setName":function(_name){
- name=_name;
- },
- "setSex":function(_sex){
- sex = _sex;
- },
- "aliasName":name;
- };
- }
- var person = new Person();
- alert(person.name);//undefined
- alert(person.getName());//张三
- person.setName("李国");
- alert(person.getName());//李国
这样就有了私有成员变量。公有成员变量,就是可以直接访问的,像aliasName属性。这是很基础的。我们都知道,在像Java语言里,有this关键字
来代表对像本身。而恰恰js也有这个this.但是这个this和其它面向对向语言的this是有区别的,这里先不说区别在哪。
我们先利用this实现一下类:
- function Person(){
- var name="王五";
- var sex ="男";
- this.aliasName="123";
- this.setName=function( _name){
- name=_name;
- };
- this.getName=function(){
- return name;
- };
- this.setSex=function( _sex){
- sex=_sex;
- };
- this.getSex=function(){
- return sex;
- }
- }
- //测试
- var person = new Person();
- alert(person.name);//undefined
- alert(person.getName());//张三
- person.setName("李国");
- alert(person.getName());//李国
- person.aliasName="nnd";
- alert(person.aliasName);
下面我们来看一下 person = new Person()执行过程:person=new Object()-->把Person类型的this绑定到this-->person有什么,person就是有什么。
我们想一下,这里也是利用的闭包特性。因person的相关方法,引用了外部变量,当person不被回收时,外部变量也不会被回收,但是只能通过person的方法才能访问的到。被包住了。但是,这一种方式,要比第一个实现起来要灵活多了。这里就要说一下,this为什么和其它语言的this有区别,想一下,js的function即可以以函数用也可以做类用,当一个function含有this,但被直接调用了,像Person().这时js就会把相关的方法属性给了window(this没有被指向一个对象,会默认指向window),执行过程:this-->window.这样的话,就有一定的凶险性了,因为这个类里的所有东西都是全局的了。比如:
- Person();
- alert(window.getName());
到现在为止,创建封闭性的对象算是说完了。目前我们看它像是一个类,但还有一点就是每new Person(),不但属性产生副本,方法也会产生副本。
属性产生副本,这个是应该的,但是方法产生副本就没有必要了吧?js function 类型的数据(切记),它提供了prototype这个属性,即原型。
这么写Person:
- <span style="white-space:pre"> </span>function Person(){
- var name="王五";
- var sex ="男";
- this.aliasName="123";
- this.self = Person;
- this.self.prototype.setName=function( _name){
- name=_name;
- };
- this.self.prototype.getName=function(){
- return name;
- };
- this.self.prototype.setSex=function( _sex){
- sex=_sex;
- };
- this.self.prototype.getSex=function(){
- return sex;
- }
- }
创建的所有Person就会有一个方法副本了。为了证明正确性,可以分别:
- <span style="white-space:pre"> </span><span style="white-space:pre"> </span>var person = new Person();
- var person1 = new Person();
- alert(person.getName===person1.getName);
看一下效果就可以了。原型为什么能提供这个效果?
下面,就说一下原型prototype及其相关应用吧
原型是Js中非常重要的概念,每个函数(在Js里面函数也是对象)都有一个叫prototype即原型)的属性,不过在一般情况下它的值都是null,但它他有一项非常重要的功能就是所以实例都会共享它里面的属性和方法(这就是Js里面实现继承的基础)!举个例子:
- <span style="white-space:pre"> </span>function auth(){
- <span style="white-space:pre"> </span>alert(this.name);
- <span style="white-space:pre"> </span>//此处一定要加this关键字
- <span style="white-space:pre"> </span>}
- <span style="white-space:pre"> </span>auth.prototype.name='shiran';//这句可以放到对象定义之后,但必须在被调用之前
- <span style="white-space:pre"> </span>new auth();//这里一定要用new
这里需要注意三点:
第一、name前面一定要加关键字this,不然的话就会得不到任何,因为如果你不加this,他就不会到原型中查找(加上this是属性它会在上下文查找)。
第二、如果实例对象在对象中找不到属性或方法就会到对象的prototype中去查找,所以如果想在函数被调用的时候调用对象的属性或方法,就必须把调用语句放在prototype定义之后(在这里,就是new auth必须到auth.prototype.name之后)!
第三、只有实例对象才会到原型中查找,因为对于原对象来说prototype是他的属性必需通过prototype才能访问(在这里,要用new auth()生成一个实例,而不能用auth)!
原型对于对象的实例来说是共享的,这既给程序带来方便,同时也会让人感到迷惑,出现好多让人意想不到的结果!
- <span style="white-space:pre"> </span>auth=function(){ };
- <span style="white-space:pre"> </span>auth.prototype={
- <span style="white-space:pre"> </span>name:[],
- <span style="white-space:pre"> </span>getNameLen:function(){
- <span style="white-space:pre"> </span>alert(this.name.length);
- <span style="white-space:pre"> </span>},
- <span style="white-space:pre"> </span>setName:function(n){
- <span style="white-space:pre"> </span>this.name.push(n);
- <span style="white-space:pre"> </span>}
- <span style="white-space:pre"> </span>}
- <span style="white-space:pre"> </span>var lwx=new auth();
- <span style="white-space:pre"> </span>lwx.setName('lwx');
- <span style="white-space:pre"> </span>lwx.getNameLen();
- <span style="white-space:pre"> </span>var shiran=new auth();
- <span style="white-space:pre"> </span>shiran.setName('shiran');
- <span style="white-space:pre"> </span>shiran.getNameLen();
第二次弹出的对话框显示name的长度已经是2,为什么呢?这就是原型的共享引起的,因为变量lwx和shiran都是auth对象,而且name属性是在auth对象的原型中定义的,所以lwx和shiran实例之间共享name这个属性!可是这并不是我们想要看到的结果,因为我们希望,每个实例之间是相互隔离的。
这里我们可以把name属性从原型中去掉,放在auth对象的定义中即:
- <span style="white-space:pre"> </span>auth=function(){
- <span style="white-space:pre"> </span>this.name=[];//切记,一定要在前面加上this关键词
- <span style="white-space:pre"> </span>};
这样一来,每个auth的实例都会拥有自己的name属性!所以推荐大家,以后在定义对象的时候:把属性放到定义里,而把对象的方法放到原型里!
有了原型,也其实就有了类方法和类属性,完整的类有了,封装成员也有了。
2、继承性:
继承,我们需要熟悉js中function的两个函数,它们是call及apply下面,我们先介绍一下它们,知道它们了,继承也就差不多了。
JavaScript中有一个call和apply方法,其作用基本相同,但也有略微的区别。先来看看JS手册中对call的解释:
call 方法
调用一个对象的一个方法,以另一个对象替换当前对象。
call([thisObj[,arg1[, arg2[, [,.argN]]]]])
参数
thisObj
可选项。将被用作当前对象的对象。
arg1, arg2, , argN
可选项。将被传递方法参数序列。
说明:
call 方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。如果没有提供 thisObj参数,那么 Global对象被用作 thisObj。说明白一点其实就是更改对象的内部指针,即改变对象的this指向的内容。这在面向对象的js编程过程中有时是很有用的。
引用网上一个代码段,运行后自然就明白其道理。
Js代码:
- <input type="text" id="myText" value="input text">
- <script>
- function Obj(){this.value="对象!";}
- var value="global变量";
- function Fun1(){alert(this.value);}
- window.Fun1(); //global变量
- Fun1.call(window); //global变量
- Fun1.call(document.getElementById('myText')); //input text
- Fun1.call(new Obj()); //对象! </script>
- <input type="text" id="myText" value="input text">
- <script>
- function Obj(){this.value="对象!";}
- var value="global变量";
- function Fun1(){alert(this.value);}
- window.Fun1(); //global变量
- Fun1.call(window); //global变量
- Fun1.call(document.getElementById('myText')); //input text
- Fun1.call(new Obj()); //对象!
- </script>
call函数和apply方法的第一个参数都是要传入给当前对象的对象,及函数内部的this。后面的参数都是传递给当前对象的参数。
Js代码:
- <script>
- var func=new function(){this.a="func"}
- var myfunc=function(x){
- var a="myfunc";
- alert(this.a);
- alert(x);
- }
- myfunc.call(func,"var");
- </script>
- <script>
- var func=new function(){this.a="func"}
- var myfunc=function(x){
- var a="myfunc";
- alert(this.a);
- alert(x);
- }
- myfunc.call(func,"var");
- </script>
可见分别弹出了func和var。到这里就对call的每个参数的意义有所了解了。
对于apply和call两者在作用上是相同的,但两者在参数上有区别的。对于第一个参数意义都一样,但对第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。如func.call(func1,var1,var2,var3)对应的apply写法为:func.apply(func1,[var1,var2,var3])
同时使用apply的好处是可以直接将当前函数的arguments对象作为apply的第二个参数传入。我们用call,写一个继承的例子,像:
- function Animal(){
- this.name=“gg”;
- this.getName=function(){alert(this.name)};
- };
- function Person(){
- Animal.call(this);//构造父类
- this.aliasName="";
- }
- function init(){
- var p = new Person();
- p.getName();
- }
- 再写一个apply函数的:
- function Animal(name,sex){
- this.name="gg";
- this.getName=function(){alert(this.name+name+sex)};
- };
- function Person(name,sex){
- Animal.apply(this,[name,sex]);//构造父类,参数是数组
- this.aliasName="";
- }
- function init(){
- var p = new Person("wsk",'man');
- p.getName();
- }
3、多态性:
多态性,对于js来讲更简单了,因为,它是一个弱类型的语言,可以对一个变量付任何类型的变量。虽然不完全符合,但也能凑合用了。
js面向对象编程,还有相当长的考验,希望能给刚入门的朋友提供一个帮助。