JavaScript学习--Item37 面向对象高级程序设计

Posted 官小飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript学习--Item37 面向对象高级程序设计相关的知识,希望对你有一定的参考价值。

1. JS是基于原型的程序

建立一个简单的面向对象的类。有属性,有方法。

function Aaa(){
    this.name = ‘小明‘;
}
Aaa.prototype.showName = function(){
    alert( this.name );
};

var a1 = new Aaa();
a1.showName();

在JS的自身的对象中,也是new一个对象,然后调用方法,比如:

var arr = new Array();
arr.push();
arr.sort();

在JS源码 : 系统对象也是基于原型的程序。比如:

function Array(){
    this.lenglth = 0;
}
Array.prototype.push = function(){};
Array.prototype.sort = function(){};

尽量不要去修改或者添加系统对象下面的方法和属性,这样会改变系统的方法。

var arr = [1,2,3];
Array.prototype.push = function(){}
arr.push(4,5,6);
alert( arr );

这样修改后,push方法就失效了,覆盖了JS本省的push方法。

那么如何自己重写Array的push方法呢?

var arr = [1,2,3];
Array.prototype.push = function(){
    //this : 1,2,3
    //arguments : 4,5,6

    for(var i=0;i<arguments.length;i++){
        this[this.length] = arguments[i]
    }

    return this.length;
};

arr.push(4,5,6);

alert( arr );*/

//pop shift unshift splice sort

2.JS中的包装对象

我们知道js的基本数据类型有 string, number, boolean, null, undefined, object.但是当你看到下面的代码时,你发现了什么?

var str = ‘hello‘;

alert( typeof str );//String

str.charAt(0);
str.indexOf(‘e‘);

除了对象可以调用方法,为什么基本数据类型也可以?这是因为在js中,string, number, boolean都有自己的包装对象。String Number Boolean

var str = new String(‘hello‘);//通过构造函数构造的数据类型就是对象了,可以直接调用方法了

//alert( typeof str );

alert(str.charAt(1));

String.prototype.charAt = function(){};

那基本类型调用方法时,发生了什么呢?

var str = ‘hello‘;
str.charAt(0);  //基本类型会找到对应的包装对象类型,然后包装对象把所有的属性和方法给了基本类型,然后包装对象消失,包装类型就和快递员一样。

比如要实现字符串的一个lastValue方法:

var str = ‘hello‘;

String.prototype.lastValue = function(){
    return this.charAt(this.length-1);
};

alert( str.lastValue() ); //o

str通过原型继承,使本身具有了lastValue()方法,所以可以直接调用,我们再来看个例子:

var str = ‘hello‘;

str.number = 10;

alert( str.number );  //undefined

这里为什么是undefined,因为这里包装类型并没有把number共享给str,只是自己有,而复制完消失,str.name没有接受到值,变成undefined。

3. JS中的原型链

在js中,实例对象与原型之间的连接,叫做原型链,原型链之间的连接叫__proto__( 隐式连接 )

原型链的最外层是Object。

function Aaa(){

}
Aaa.prototype.num = 10;
var a1 = new Aaa();
alert(a1.num);

比如我们一般的定义一个对象Aaa,并在原型上定义了一个属性num,新new的a1是怎么访问到原型上的属性的呢?

这里就是a1有一个__proto__属性,指向Aaa.prototype原型对象,顾可以访问到。

假如这样呢?

function Aaa(){
    this.num = 20;
}
Aaa.prototype.num = 10;
var a1 = new Aaa();
alert(a1.num);//20

这里有两个num,弹出的是哪一个呢?这就是原型链的规则,先从本身对象找,本身对象没有到原型对象上找,。。。。。一直找下去,何处是尽头?知道找到object的原型,如果没有就报错,因为object的原型是null,这个原型连接起来的链就是一条原型链。看例子

function Aaa(){
    //this.num = 20;
}
//Aaa.prototype.num = 10;
Object.prototype.num = 30;

var a1 = new Aaa();
alert(a1.num);

4. 面向对象的一些常用属性和方法

  • hasOwnProperty() : 看是不是对象自身下面的属性
var arr = [];
arr.num = 10;
Array.prototype.num2 = 20;

//alert(  arr.hasOwnProperty(‘num‘)  );  //true

alert(  arr.hasOwnProperty(‘num2‘)  );  //false
  • constructor : 查看对象的构造函数
    • 每个原型都会自动添加constructor属性
    • For in 的时候有些属性是找不到的
    • 避免修改construtor属性
function Aaa(){
}

var a1 = new Aaa();

alert( a1.constructor );  //Aaa

var arr = [];
alert( arr.constructor == Array );  //true

每个对象的原型的构造函数都自动指向本身,可以修改为别的,但是不建议修改:

function Aaa(){
}
//Aaa.prototype.constructor = Aaa;   //每一个函数都会有的,都是自动生成的

//Aaa.prototype.constructor = Array;

var a1 = new Aaa();
alert( a1.hasOwnProperty == Object.prototype.hasOwnProperty );  //true

说明hasOwnProperty 属性是通过原型链,在Object.prototype原型上的。

有时候我们会不经意的就把原型修改了,这个时候我们就要把原型修正一下:

function Aaa(){
}

Aaa.prototype.name = ‘小明‘;//采用这种原型继承,constructor没有改变
Aaa.prototype.age = 20;

Aaa.prototype = {//采用json格式的对象,这就是相当于一个Object对象赋值,所以constructor变为了Object。
    constructor : Aaa,
    name : ‘小明‘,
    age : 20
};

var a1 = new Aaa();
alert( a1.constructor );

采用for in循环,是循环不到原型上constructor属性的。

function Aaa(){
}

Aaa.prototype.name = 10;
Aaa.prototype.constructor = Aaa;

for( var attr in Aaa.prototype ){
    alert(attr);//只有name
}
  • instanceof : 运算符。对象与构造函数在原型链上是否有关系【对象是否是构造函数的一个实例】
function Aaa(){
}

var a1 = new Aaa();

//alert( a1 instanceof Object );  //true


var arr = [];

alert( arr instanceof Array );//true;
  • toString() : 系统对象下面都是自带的 , 自己写的对象都是通过原型链找object下面的。主要做一些解析,主要用于Array、Boolean、Date、Object、Number等对象
var arr = [];
alert( arr.toString == Object.prototype.toString ); //false*/

/*function Aaa(){
}
var a1 = new Aaa();
alert( a1.toString == Object.prototype.toString );  //true*/
- 把对象转成字符串
/*var arr = [1,2,3];

    Array.prototype.toString = function(){
        return this.join(‘+‘);
    };

    alert( arr.toString() );  //‘1+2+3‘*/
  • 做进制转换,默认10进制
//var num = 255;
    //alert( num.toString(16) );  //‘ff‘
  • 最重要一点是做类型判断
//利用toString做类型的判断 : 

/*var arr = [];

alert( Object.prototype.toString.call(arr) == ‘[object Array]‘ ); */ 
//‘[object Array]‘

类型判断的常用方法:三种typeof是不行的,根本区分不开。假如判断一个数据类型是数组?

alert( arr.constructor == Array );  

alert( arr instanceof Array );  

alert( Object.prototype.toString.call(arr) == ‘[object Array]‘ ); 

但是在一种很特殊的情况下,就只能用第三种,在iframe中创建一个数组,前两种方法就会失效,但是在大多数情况下,还是可以的。

window.onload = function(){

    var oF = document.createElement(‘iframe‘);
    document.body.appendChild( oF );

    var ifArray = window.frames[0].Array;

    var arr = new ifArray();

    //alert( arr.constructor == Array );  //false

    //alert( arr instanceof Array );  //false

    alert( Object.prototype.toString.call(arr) == ‘[object Array]‘ );  //true   
};

5. JS对象的继承

什么是继承?
在原有对象的基础上,略作修改,得到一个新的对象不影响原有对象的功能。

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针_proto_

1.原型继承

实现原型链有一种基本模式,其代码大致如下:

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); //true

以上代码定义了两个类型:SuperType 和SubType。每个类型分别有一个属性和一个方法。它们的主要区别是SubType 继承了SuperType,而继承是通过创建SuperType 的实例,并将该实例赋给SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType 的实例中的所有属性和方法,现在也存在于SubType.prototype 中了。在确立了继承关系之后,我们给SubType.prototype 添加了一个方法,这样就在继承了SuperType 的属性和方法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图

技术分享

我们没有使用SubType 默认提供的原型,而是给它换了一个新原型;这个新原型就是SuperType 的实例。于是,新原型不仅具有作为一个SuperType 的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向了SuperType 的原型。

我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的,上面例子展示的原型链中还应该包括另外一个继承层次。

技术分享

可以通过两种方式来确定原型和实例之间的关系instanceof操作符,isPrototypeOf()方法。

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

原型继承注意点:

(1)子类型有时候需要重写父类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎
样,给原型添加方法的代码一定要放在替换原型的语句之后。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
//添加新方法,一定放在原型替换之后,不然会被覆盖掉的哦。
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
    //重写超类型中的方法
    SubType.prototype.getSuperValue = function (){
        return false;
    };
    var instance = new SubType();
    alert(instance.getSuperValue()); //false

(2)通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链,直接被json取代。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
    },
    someOtherMethod : function (){
        return false;
    }
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!

原型链的问题

原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原来实例的属性也就顺理成章地变成了现在的原型属性了。引用类型共享了数据。。。

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承了SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

这个例子中的SuperType 构造函数定义了一个colors 属性,该属性包含一个数组(引用类型值)。SuperType 的每个实例都会有各自包含自己数组的colors 属性。当SubType 通过原型链继承了SuperType 之后,SubType.prototype 就变成了SuperType 的一个实例,因此它也拥有了一个它自己的colors 属性——就跟专门创建了一个SubType.prototype.colors 属性一样。但结果是什么呢?结果是SubType 的所有实例都会共享这一个colors 属性。而我们对instance1.colors 的修改能够通过instance2.colors 反映出来,就已经充分证实了这一点。

原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

2.构造函数继承(类式继承)

这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    //继承了SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"

传递参数

相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函
数传递参数。

function SuperType(name){
    this.name = name;
}
function SubType(){
    //继承了SuperType,同时还传递了参数
    SuperType.call(this, "Nicholas");
    //实例属性
    this.age = 29;
}

var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29

在SubType 构造函数内部调用SuperType 构造函数时,实际上是为SubType 的实例设置了name 属性。为了确保SuperType 构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。

借用构造函数的问题

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了,而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

3. 组合继承

将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name, age){
    //继承属性
    SuperType.call(this, name);
    this.age = age;
}

//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

4.拓展:拷贝继承

先理解一下对象的拷贝,对象的引用是地址引用,双向改变。而基本数据类型是按值传递,不影响原来的数值。

var a = {
    name : ‘小明‘
};

var b = a;

b.name = ‘小强‘;

alert( a.name );//小强

采用拷贝继承的方式,将方法一个个拷贝过去就不会发生双向继承了。

var a = {
    name : ‘小明‘
};

//var b = a;
var b = {};
extend( b , a );    
b.name = ‘小强‘;
alert( a.name );    

function extend(obj1,obj2){
    for(var attr in obj2){
        obj1[attr] = obj2[attr];
    }
}

通过这个对象的复制之后,就可以实现拷贝继承了:

//继承 : 子类不影响父类,子类可以继承父类的一些功能 ( 代码复用 )

//属性的继承 : 调用父类的构造函数 call

//方法的继承 : for in :  拷贝继承 (jquery也是采用拷贝继承extend)

function CreatePerson(name,sex){   //父类
    this.name = name;
    this.sex = sex;
}
CreatePerson.prototype.showName = function(){
    alert( this.name );
};

var p1 = new CreatePerson(‘小明‘,‘男‘);
//p1.showName();


function CreateStar(name,sex,job){  //子类

    CreatePerson.call(this,name,sex);

    this.job = job;

}

//CreateStar.prototype = CreatePerson.prototype;

extend( CreateStar.prototype , CreatePerson.prototype );

CreateStar.prototype.showJob = function(){
};

var p2 = new CreateStar(‘黄晓明‘,‘男‘,‘演员‘);

p2.showName();


function extend(obj1,obj2){
    for(var attr in obj2){
        obj1[attr] = obj2[attr];
    }
}

6.实例:用继承实现拖拽

<!DOCTYPE html>
<html>
<head>
    <title>拖拽继承</title>
    <style type="text/css">
        #div1{width: 100px;height: 




以上是关于JavaScript学习--Item37 面向对象高级程序设计的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript Item37—— 面向对象高级程序设计

javascript模拟面向对象一种比较好的写法

OOCSS入门学习

JavaScript学习--Item11 arguments对象

JavaScript学习--Item11 arguments对象

JavaScript学习--Item9 call(),apply(),bind()与回调