JavaScript之——面向对象

时间:2021-10-04 16:19:35

概述:

        说起面向对象,我们最熟悉的一般是C#和JAVA,然而,关于JavaScript是不是一个面向对象的语言各有各的理解,刚才查了百科,JavaScript属于一个基于原型的面向对象,也就是JavaScript算是一个基于对象的编程语言,但是个人理解上,面向对象是一种编程思想,关于语言是不是面向对象的不必过于纠结,用的多了,自然有所理解,很多人包括我在内,其实基本上在用着JAVA这个面向对象的语言编着面向过程的程序;刚学习了JavaScript的面向对象编程思路,在这里整理一下自己的思路,是对自己学习的一个记录也是帮助初学者做一个入门认识。

最初程序

        起初我们创建对象的方式如下代码所示:

var box = new Object(); //创建一个Object 对象
box.name = 'Lee'; //创建一个name 属性并赋值
box.age = 100; //创建一个age 属性并赋值
box.run = function () { //创建一个run()方法并返回值
return this.name + this.age + '运行中...';
};
alert(box.run()); //输出属性和方法的值

       特点分析:创建一个全局的对象,分别做对象的属性和方法的初始化。

       缺点分析:没有封装性,如果再创建一个一样的对象,需要再重新写一遍代码。代码量大,复用性差。

工厂模式

        基于上边的缺点分析我们将上边的代码封装到一个函数中,来集中做对象的创建和初始化工作。代码如下:

function createObject(name, age) { //集中实例化的函数
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box1 = createObject('Lee', 100); //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
alert(box1.run());
alert(box2.run()); //保持独立

       特点分析:通过一个函数将对象创建和初始化过程封装起来,最后将该对象返回。称这种方式为工厂模式。

       缺点分析:对象实例无法区分,因为函数中返回的对象一溜都是Object类型。

构造函数

        利用构造函数也可以用来创造特定的对象,代码如下:

function Box(name, age) { //构造函数模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '运行中...';
};
}
var box1 = new Box('Lee', 100); //new Box()即可
var box2 = new Box('Jack', 200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的识别他从属于Box

       特点分析:通过构造函数不再在函数中new对象,并最后将对象返回,而是,直接在外部通过new构造函数生成对象,同时自动返回对象。

       工厂模式和构造函数方式的区别:对比JAVA的编程过程可以理解,工厂模式纯粹就是一个方法,在该方法中创建一个对象,并最后将对象返回,而构造函数是一个特殊的方法,调用该方法会自动返回一个对象,返回对象的类型就是该方法名对应的类型。

       缺点分析:上边的方法看起来已经算是完美了,但是,有个缺点就是函数中的run方法不是共享的,也就是说每new一个对象就会创建新的run方法,而run方法对于每一个对象又是相同的,这样就造成了不必要的内存浪费。如果可以将run方法共享出来就可以避免这种问题,函数的prototype(原型)属性可以帮我们做到,下面我们认识一下函数的prototype(原型)属性。

原型(prototype)

        首先原型也是一个对象,该对象随着函数的创建而创建,其特点为,可以让所有通过该函数创建的对象实例共享它里面的属性和方法。访问过程如下图所示:

JavaScript之——面向对象

        在我们new一个实例时,该实例会有一个__proto__属性,该属性执行原型的constructor属性,这样就可以访问到原型里面的属性和方法了,每一个实例都是如此。

组合构造函数+原型模式

       由此,利用原来的构造函数,再加上原型模式就可以解决上边的问题了。代码如下:

function Box(name, age) { //不共享的使用构造函数
this.name = name;
this.age = age;
this. family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = { //共享的使用原型模式
constructor : Box,
run : function () {
return this.name + this.age + this.family;
}
};

       有关代码中的constructor : Box,是为了让实例的constructor强制指向它的对象所属,否则创建的实例不是指向它的对象所属的,也就是box的constructor不指向Box,而是指向Object。

       特点分析:这种方法将不需要共享的属性放在构造函数里初始化,将需要共享的方法放在原型里初始化。

       缺点分析:其实这种方法算是一种比较好的方法了,但是还存在的问题在于,原型*享的方法无论是否调用到都会初始化,如果该方法比较大的话,也会造成不必要的内存浪费,同时由于构造和原型在形式上是分离的,如果封装到一起看起来就更直观和方便。

动态原型:

       针对上边的问题,利用下边的代码,动态的调用原型中的方法。

function Box(name ,age) { //将所有信息封装到函数体内
this.name = name;
this.age = age;
if (typeof this.run != 'function') { //仅在第一次调用的初始化
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
}
var box = new Box('Lee', 100);
alert(box.run());

继承

       在JAVA的面向对象编程中,实现继承的方式有接口实现和继承两种方式,但是在JavaScript中是不支持接口实现的,在这里继承的实现是依靠原型链实现的。

基本继承:

       先看代码

function Box() { //Box 构造
this.name = 'Lee';
}
function Desk() { //Desk 构造
this.age = 100;
}
Desk.prototype = new Box(); //Desc 继承了Box,通过原型,形成链条
var desk = new Desk();
alert(desk.age);
alert(desk.name); //得到被继承的属性

       从代码中我们可以看出,在JavaScript中的继承实现的过程就是,将父对象的实例放到了子对象的原型中实现的。通过上边的知识我们知道,函数原型中的内容是被函数的实例所共享的,所以就实现了子类型访问父类型属性和方法的过程。

       缺点分析:这种方式无法实现子类型向父类型传参。

原型链+借用构造函数

       为了解决上边的问题,我们可以借助借用构造函数也称对象冒充的方法实现,代码如下:

function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
Box.prototype.run = function () {
return this.name + this.age;
};
function Desk(age) {
Box.call(this, age); //对象冒充
}
Desk.prototype = new Box(); //原型链继承
var desk = new Desk(100);
alert(desk.run());

       特点分析:从上边的代码可以看出,通过Desk.prototype = new Box(),将Box继承了下来,同时在newDesk(参数) 的过程中传入参数,调用了Box.call(参数) 方法,将参数传递给了Box的构造函数。

总结

       上边的介绍也只是利用面向对象的思想指导JavaScript的运用,真正在实际开发中还需要多思考最适合运用的方法,这些也是自己初步的一个认识,也算是给各位初学者提供一个思路,希望在以后能写出自己的面向对象的代码,同时也祝福各位!