一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

原生 javascript 面向对象分析及思考

时间:2015-12-05 编辑:简简单单 来源:一聚教程网

面向对象语言有三个特征:  封装,继承,多态。而js初学者一般会觉得js同其他类C语言一样,有类似于Class这样的关键字可以让我们在js中更好的进行面向对象的操作。可事实并非如此。

严格地说,js不是一门面向对象的语言,但是我们可以利用js里面的一些高级特性来进行OOP编程。

 
----封装

在js中,如何来创建一个对象呢?这非常简单,我们只需要new一个已封装好的函数(就是类C语言中的类),就可以实例化一个对象了。

那我们首先来构造这么一个"类",在构造之前必须知道一个类需要有"变量"和"方法",接着我们就来构造这个"类":

function Parent(){
    this.name = "Parent";
    this.sayName = function(){
        console.log(this.name);
    }
}
var p1 = new Parent();
p1.sayName();

怎么样,很简单吧?这样就封装好了一个"类"了。但是这个类看起来很笨重,因为名字是固定的,所以我们需要进行修改并扩展。

function Parent(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}
var p1 = new Parent("yxy");
p1.sayName(); //控制台打印yxy

这样我们封装的"类"就变得有变量,有方法,有外部参数,可复用。看上去已经非常完美了?

试着这样想想。每次创建一个对象,都会创建一个变量,同样也都会创建一个方法。而这个方法对所有对象都只是同一个方法效果,为什么你还要去对这个方法创建多次呢?学过java或c++的人可能会想,你怎么知道这个方法是被创建了多次,而不是引用的同一个呢?嗯?我们来做个测试。

function Parent(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}
var p1 = new Parent("yxy");
var p2 = new Parent("danshengou");
console.log(p1.sayName == p2.sayName); //false

利用"=="可以看到两个方法是不同的,那就是被创建了多次。所以当我们创建了多个对象后,每个对象的每个方法都是不同的!这显然会大大消耗内存,不利于web开发。那如何解决呢?

前面的文章中我提到的js中的"类"都是带双引号的,原因很简单,js不支持类(在ES6的规范中就可以支持了),但为了方便,我们可以称之为"伪类"。但在我们之前的例子中都还不能说得上是伪类!因为完全就没有方法的复用,不是吗?接下来我们会引入一个概念性很强的一个术语:"原型"。

原型,prototype,每一个函数都有一个原型属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。确实是非常不好理解。简单的说,原型属性就是通过调用构造函数而创建的那个对象实例的原型对象。如果你还不清楚,那我推荐你好好地看看<<javascript高级程序设计>>一书的第六章。

在这里先记住为什么我们要在封装中使用原型。如果你还对原型的概念模糊不清,不急,先理解使用它的好处。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。好像清晰多了?那我们来改装一下前面提到的那个例子。

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}
var p1 = new Parent("yxy");
p1.sayName(); //调用原型中的sayName方法,打印出yxy
var p2 = new Parent("danshengou");
console.log(p1.sayName == p2.sayName); //true
console.log(p1.sayName === p2.sayName); //true

在这个例子中,我们用原型模式构造了一个函数。我们实例化了两个对象,用"=="和"==="比较了它的值和地址,发现都是一样的。看来原型真的是很好用啊,大大节省了我们的内存空间。你可能会问,为什么不把变量也放到原型中呢?因为原型是所有对象所共享的,每个对象的属性必须是该对象自己持有的,如果放到原型中,那这些对象便没有任何区别了。这就是我们所谓的"组合继承"。

到目前为止,我们就成功地创建了一个比较完美的js"伪类"了。而且你应该对原型有了很大的了解。

----继承

在你了解了整个封装过程后,你对js可能很失望了,既然有这样复杂的封装,那继承肯定也很难了。

可是,继承的语法其实很简单。

我们先来看看一个继承:

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}

function Child(name,position){
    this.position = position;     
    Parent.call(this,name);      
}
Child.prototype = new Parent();
Child.prototype.sayPos = function(){
    console.log(this.position);
}

var p1 = new Child("yxy","student");
p1.sayName();
p1.sayPos();

有两个地方很吸引我们眼球。

1.Parent.call(this,name)
2.Child.prototype = new Parent();

细讲可能容易绕晕,我从继承的概念入手,首先,子对象继承的是父对象的变量和方法。那我们可以很清晰地从代码作用范围看到,Parent.call(this,name)在构造函数中,显然是在继承变量。而Child.prototype = new Parent(),这个Parent对象不带参数地创建,显然是在继承原型。这样说是不是好理解多了?

Child.prototype = new Parent(),这条语句很简单,我不再去细究它。这里重点要讲的是Parent.call(this,name)这个东西。

Parent.call(this,name)还有另外几种写法:

1.Parent.call(this,arguments[0]);
2.Parent.apply(this,[name]);
3.Parent.apply(this,arguments);

虽然是四个写法都不同,但是效果都是一样的 : 将父类构造函数的上下文引用到子类的构造函数上下文中。

call和apply,都可以用来代替另一个对象调用一个方法。两者可将一个函数的对象上下文从初始的上下文改变为由this指定的新对象。又有点绕?简单地说,就是this对象在当前的上下文中执行了一次Parent函数,并且把参数(this后面那个参数)传递给Parent函数。

我们都知道函数怎么暴露自己内部的属性,就是把它执行一次,就可以在它的父级作用域中访问到。那理解这个apply,call的作用机理就很简单了。

继承语法很简单,但是其继承机制还是需要花时间去理解的。

终于我们把有关js面向对象的步骤给很详细的做了一次,可能大家觉得自己终于可以开始模拟一些有难度的面向对象的实例了。这一次我并不会反对大家,但是你难道没看出来少了什么吗?我来运行一下代码:

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}

function Child(name,position){
    this.position = position;
    //Parent.call(this,name);
    Parent.apply(this,[name]);
}
Child.prototype = new Parent();
Child.prototype.sayPos = function(){
    console.log(this.position);
}

var p1 = new Child("yxy","student");
console.log(p1.name); //yxy
console.log(p1.position); //student

嗯?我明明想的是将变量私有化啊,可是为什么能访问到呢?细心地人可能早就在继承那个部分的时候就想到了,函数只要一执行,我的变量,方法都会暴露在外面了!其实这根本就不是封装啊!只是装而已。

没错,js并没有private,public这些关键字来定义属性和方法的私有和公有性质。可能你会很失望很失望,前面做了那么多,得来的是一个半瘸子的面向对象。其实不光你这么想,很多开发人员也觉得,因此他们用复杂的名字来命名一些变量,以保证其不会那么容易被访问到。这不失为一种办法,但是难道就没有别的办法?

仔细想想,还有什么办法能将模拟private,public这样的操作呢?我给出一个例子和一个概念,然后大家可以在这上开始自己对js真正面向对象的思考。

概念 : "模块模式"

代码 :

var Parent = function(name,publicAge){
    var myName = name;
    return {
        age : publicAge,
        sayName : function(){
            console.log(myName);
        }
    };
};

var p1 = Parent("yxy","20");
p1.sayName(); //yxy
console.log(p1.age); //20
console.log(p1.myName); //undefined

当你能理解封装中访问等级的时候,javascript真正的思考才刚刚开始



JavaScript 中面向对象的思想真的很重要吗?

具体来说的话,怎样用面向对象的 JavaScript 构造一个网站的前端架构?

最近很多人都提到 JavaScript 的面向对象编程,网络上介绍了很多是想 JavaScript 实现面向对象编程的方法和技巧,自己也掌握了解一些,但是每每到实际开发的时候面向对象js开发却很少用的到,可能是自己的对真正的面向对象思想还不是很熟悉,也获取自己对一些 OO 的设计模式不是很了解。

很想知道 JavaScript 面向对象开发思想在做前端发开时是否真的很重要,如何具体的将其用于开发之中。


面向对象的概念出来很久了,任何一门语言都是可以去面向对象的。   为什么要用面向对象,原因很多,但就前端开发来说我个人觉得有两个优点。   第一,是将松散的JS代码进行整合,便于后期的维护  第二,是让我们的代码适应更多的业务逻辑。   这两点,是我在日常工作中深有体会的。   言归正传,在前端开发过程中,我们所接触到最多的事物其实就是各种业务,在我看来,每一个业务都是一个对象,而这些业务必然是由一个个小的功能所组成的,   因此我所理解,面向对象,就是针对这个业务,来构造对应的状态以及操作。而在这个大的前提下可以继续对下面的小的功能再次细分与构造。   为了达到这样的目的,我们通常采用了封装,继承以及多态这样的方法。   最后,应用环节上,就可以利用OO来实现获取的状态和方法从而实现业务。   (题外话,因此面向对象和面向过程,有的时候也是交替存在的甚至是相辅相成的)   ===============补充下================  上面??铝撕芏啵?卑椎乃担?褪墙?阍谇岸肆煊蛩?龅降奈侍猓ù蟛糠挚赡苁且滴瘢??ü?庾埃?坛校?嗵?壤嗨频姆椒??谐橄蟮氖迪郑?谑褂霉?讨校?ü?虻サ氖道??涂梢越?滴穸韵蟾秤瓒杂Φ淖刺?头椒ù佣?饔谩?nbsp;  而面向过程,实际上就是将你的业务逻辑按照先后顺寻进行整理,将每一个小的逻辑认为是一个模块,并套用面向对象的方法。   这就是我所理解的,如何具体的将其应用到开发这个问题的答案。


我觉得在前端面向对象不那么重要,不是不重要,是【不那么重要】,或者说没必要【刻意】追求面向对象面向对象的最大的特征无非是“继承、多态、封装、组合”,我们看一个面向对象的语言或者框架,第一反应就是继承、多态怎么实现的但在是在前端环境下,继承和多态的效益真的够高吗?回到问题的本源,继承和多态的出发点都是“分离关注点”。使程序以最小的代价适应“关注点”的变化。但前端环境和后端不同的一点是,后端程序只有“数据+行为”,关注点不多而且容易预测。而前端是“数据+行为+展现+交互”,多出来的“展现+交互”决定了前端的关注点多且无从预测,除非人为限制关注点,让UI和交互套在一个相对死的范围内面向对象做得很彻底的UI框架基本都有几个共同的特点:“丑,慢,大”,结合以上分析应该不难明白个人认为对前端来说,做好代码的分层、解耦、结构化极有必要,但做这些事和追求面向对象没有必然关系。

热门栏目