Javascript高级技术篇(2): 深入理解面向对象

我们常说Javascript是一种面向对象的语言,那也就是说具有面向对象的一些基本特性。比如包含对象、类、属性、方法以及构造函数等基本元素,很多人在想:JS类到底是什么玩意?其实很简单,就是一个function,正所谓"简单就是美"嘛。在自定义类的同时,我们也回顾一下JS基本的类:Math,Array,Object以及String等。

//定义JS类的两种方式(注意这里是大写开头)

function EmailMessage() { 

}

var EmailMessage = function() { 

}

有类就有对象存在,同时构造函数也应运而生。常常在构造函数中使用this.**来访问属于当前对象的属性或方法。对于由同一个类生成的多个对象之间是松耦合的,相互独立。

//当创建对象时会触发构造函数(这里无参)

var EmailMessage = function() {

    alert("New message created.");

}

var myMessage = new EmailMessage(); // 输出 "New message created."

var anotherMessage = new EmailMessage(); // 输出 "New message created."

当你想传递参数给对象时,就会使用带参构造函数。

//带参构造函数

var EmailMessage = function(message) {

    alert(message);

}

// 输出 "Return to sender"

var myMessage = new EmailMessage("Return to sender");

刚才讲过,可以使用this来访问属性,其实在JS中还有一种方式来添加属性: 利用prototype。不仅能添加属性,还能添加方法。

//使用this来访问当前对象的属性和方法

var EmailMessage = function(subject) {

    this.subject = subject; 

    this.send = function() {

        alert("Message '" + this.subject + "' sent!");

    }

}

var myMessage = new EmailMessage("Check this out...");

var anotherMessage = new EmailMessage("Have you seen this before?");

//输出属性和方法

alert(myMessage.subject); // 输出 "Check this out..."

alert(anotherMessage.subject); // 输出 "Have you seen this before?"

myMessage.send();// 输出 "Message 'Check this out...' sent!"

anotherMessage.send();// 输出 "Message 'Have you seen this before?' sent!"

利用prototype来添加属性和方法(主要适合于对已有类进行功能扩展)。

var EmailMessage = function(subject) {

    this.subject = subject; 

}

//使用prototype来添加方法

EmailMessage.prototype.send = function() {

    alert("Message '" + this.subject + "' sent!");

}

var myMessage = new EmailMessage("Check this out...");

var anotherMessage = new EmailMessage("Have you seen this before?");

//输出属性和方法

alert(myMessage.subject); // 输出 "Check this out..."

alert(anotherMessage.subject); // 输出 "Have you seen this before?"

myMessage.send();// 输出 "Message 'Check this out...' sent!"

anotherMessage.send();// 输出 "Message 'Have you seen this before?' sent!"

通常有朋友在讲:要是想JS类只有一个对象存在时,即常说的单例模式如何实现呢?其实JS的内置类就包含了很多单例,如:Math等。在这里,我给出两种实现方式。

var User = function() {

    this.username = "";

    this.password = "";

    this.login = function() {

        return true;

    }

}

// 创建User类的对象并存储为相同的对象实例,而原始的类将被移除(可以看成起了个别名而已)

User = new User();

// 使用单例对象来访问对应的方法(类似于C#的静态方法)

User.login();

另外一种实现方式就是"自我初始化",即在类声明时就立即执行,而该类就只包含一个对象。

var Inbox = new function() {

    this.messageCount = 0;

    this.refresh = function() {

        return true;

    }

}();

//声明就立即执行

Inbox.refresh();

接下来,我谈一下在已有类的基础上添加新的功能以实现扩展,我们称之为"继承"。注意以下代码的高亮片段。

var EmailMessage = function(subject) {

    this.subject = subject;

    this.send = function() {

        alert("Message '" + this.subject + "' sent!");

    }

}

// 创建一个新的空类

var EventInvitation = function() {};

// 继承已有类EmailMessage的属性和方法

EventInvitation.prototype = new EmailMessage(); // EventInvitation将构造函数设置为自身

EventInvitation.prototype.constructor = EventInvitation; // 重置设置已有的属性subject

EventInvitation.prototype.subject = "You are cordially invited to...";

// 创建EventInvitation的对象

var myEventInvitation = new EventInvitation();

// 输出 "Message 'You are cordially invited to...' sent!"

myEventInvitation.send();

在继承已有类的同时,子类便获取了父类的所有属性和方法,自身只需要定义额外与自己相关的属性和方法。我们来解释一下封装与多态的概念:所有封装,即每个类只关注与自身相关的属性和方法。所谓多态,即子类在包含父类同名属性或方法时,而又不想继承来自父类的同名元素,则可以重新定义该元素(如subject)。

var EmailMessage = function(subject) {

    this.subject = subject;

    this.send = function() {

        alert("Email message sent!");

    }

}

// 继承EmailMessage

var EventInvitation = function() {};

EventInvitation.prototype = new EmailMessage("You are cordially invited to...");

EventInvitation.prototype.constructor = EventInvitation;

// 重写send方法

EventInvitation.prototype.send = function() { alert("Event invitation sent!"); } var myEmailMessage = new EmailMessage("A new email coming your way.");

var myEventInvitation = new EventInvitation();

myEmailMessage.send(); // 输出 "Email message sent!"

myEventInvitation.send(); // 输出 "Event invitation sent!"

现在假设子类需要重写父类的同时,又需要调用父类的方法,我们来看怎么实现。

var EmailMessage = function(subject) {

    this.subject = subject;

    this.send = function() {

        alert("Email message sent!");

    }

}

// 继承EmailMessage

var EventInvitation = function() {};

EventInvitation.prototype = new EmailMessage("You are cordially invited to...");

EventInvitation.constructor.prototype = EventInvitation;

// 重写send方法

EventInvitation.prototype.send = function() {

    alert("Event invitation sent!");

    // 使用this.constructor.prototype来指向父类并执行同名方法

    this.constructor.prototype.send.call(this); }

var myEmailMessage = new EmailMessage("A new email coming your way.");

var myEventInvitation = new EventInvitation();

myEmailMessage.send();// 输出 "Email message sent!"

myEventInvitation.send();// 输出 "Event invitation sent!"、"Email message sent!"

从以上的讲解中,我们不难发现this的大量应用,可能很多朋友也知道this代表当前执行的对象实例。我这里详细解释一下this的普遍意义和用法。给一段很简单的代码,大家试着想一想结果是什么?主要是搞清楚此时this到底代表什么?

var showSubject = function() {

    alert(this.subject);

}

showSubject();// 输出 "undefined"

刚才提过,this代表当前执行的实例对象,可是现在并没有像之前的代码先定义一个类,然后定义对象,在使用this代表这个定义的对象。准确的解释应该是: this代表其所在的作用域内类自身或正在执行的对象,若this超出类的作用域则代表全局对象window对象。故此时应该输出undefined。接下来我们把这个function移植到已有类EmailMessage上。

var showSubject = function() {

    alert(this.subject);

}

showSubject();// 输出"undefined"

this.subject = "Global subject";// 设置全局属性

showSubject();// 输出 "Global subject"

// 定义EmailMessage类

var EmailMessage = function(subject) {

    this.subject = subject;

}

// 将showSubject添加到EmailMessage,注意这里showSubject不含()

EmailMessage.prototype.showSubject = showSubject;

var myEmailMessage = new EmailMessage("I am the subject.");

myEmailMessage.showSubject();// 输出 "I am the subject.",因为现在this变成myEmailMessage

showSubject();// 输出"Global subject",因为此时this仍然为window

EmailMessage.prototype.outputSubject = function() { //现在为EmailMessage添加新方法outputSubject来调用showSubject

    showSubject();

}

myEmailMessage.outputSubject();// 输出 "Global subject.",因为尽管添加了新方法,但this仍然是window

如果希望能强制切换当前引用的对象this,有两种方法: call、apply。两者的区别很小,前者传递的是参数列表,后者传递的是参数数组。

var showSubject = function() {

    alert(this.subject);

}

var setSubjectAndFrom = function(subject, from) {

    this.subject = subject;

    this.from = from;

}

//this代表全局对象window

showSubject(); // 输出"undefined"

setSubjectAndFrom("Global subject", "[email protected]");

showSubject(); // Outputs "Global subject"

//定义EmailMessage类

var EmailMessage = function() {

    this.subject = "";

    this.from = "";

};

var myEmailMessage = new EmailMessage();

//call或apply将this从全局对象window切换到myEmailMessage

setSubjectAndFrom.call(myEmailMessage, "New subject", "[email protected]"); setSubjectAndFrom.apply(myEmailMessage, [ "New subject", "[email protected]" ]); showSubject.call(myEmailMessage);// 输出"New subject"

到此为止,我们所定义的属性和方法,在类外部都能访问。如果我们希望能将一些属性和方法仅供类内部使用,即所谓的private变量,我们改如何实现呢?

var EmailMessage = function(subject) {

    // 公有的属性和方法

    this.subject = subject;

    this.send = function() {

        alert("Message sent!");

    }

    // 私有的属性和方法(使用var而不是this)

    var messageHeaders = "";

    var addEncryption = function() {

        return true;

    }

    // 特权的属性和方法(对外开放读取接口但不能修改,类似于只读)

    var messageSize = 1024;

    this.getMessageSize = function() {

        alert(messageSize);

    }

}

接下来我们(在类外)开始使用这些变量,看看他们的表现如何?

var myEmailMessage = new EmailMessage("Save these dates...");

alert(myEmailMessage.subject); // 输出 "Save these dates..."

myEmailMessage.send(); // 输出 "Message sent!"

// 输出"undefined"因为messageHeaders是私有属性

alert(myEmailMessage.messageHeaders);

// addEncryption()是私有方法,外部不能访问因此抛出异常

try {

    myEmailMessage.addEncryption();

} catch (e) {

    alert("Method does not exist publicly!");

}

// 输出"undefined"因为messageSize是私有属性

alert(myEmailMessage.messageSize);

// 输出"1024",特权属性可通过方法在外部访问

myEmailMessage.getMessageSize();

通过以上的学习,大家对面向对象的基础知识点已经有所了解了把。接下来,我在最后简单聊一下关于"对象字面量(Object Literal)"的知识。常听别人谈起这个概念,那对象字面量到底是什么呢?简单的说:就是将一系列属性和方法组合起来的集合体,可以用来创建单一对象(Singleton),创建类,设置函数输入参数等。下面我来一一讲解。首先,对象字面量是一个变量,然后将所有的属性和方法以"键值对"的方式全部包含在{}中,这跟后面的系列JSON数据组织格式很相似。

var earth = {

    name: "Terra Firma", // 字符串

    planet: true, // 布尔变量

    moons: 1, // 整数

    diameter: 12756.36, // 小数

    oceans: ["Atlantic", "Pacific", "Indian", "Arctic", "Antarctic"], // 数组

    poles: { // 嵌套对象字面量

        north: "Arctic",

        south: "Antarctic"

    },

    setDiameter: function(diameter) { // 函数

        this.diameter = diameter; // 此时this代表earth

    }

}

// 注意:此处不再声明

alert(earth.diameter); // 输出 "12756.36"

earth.setDiameter(12756.37);

alert(earth.diameter); // 输出 "12756.37"

同样对象字面量也能创建类(将对象字面量映射到类的prototype上)。

var EmailMessage = function() {};

EmailMessage.prototype = {

    subject: "",

    from: "",

    send: function() {

        alert("Message sent!");

    }

}

var myEmailMessage = new EmailMessage();

myEmailMessage.subject = "Come over for a party.."

myEmailMessage.send(); // 输出"Message sent!"

那对象字面量咋作为函数的输入参数呢?很简单,当我们有时传递的输入参数过多时,而这些参数之间又彼此关联时,我们不妨用对象字面量来作为输入参数。

// 使用多个输入参数

var sendEmail = function(to, from, subject, body) {

    alert("Message '" + subject + "' from '" + from + "' sent to '" + to + "'!");

}

// 调用必须按照顺序

sendEmail("[email protected]", "[email protected]", "Dinner this week?", ?

"Do you want to come over for dinner this week? Let me know.");

// 用对象字面量来作为输入参数(将4个合成为1个)

var sendEmail = function(message) {

    alert("Message '" + message.subject + "' from '" + message.from + "' sent to '" + message.to + "'!");

}

// 此时调用不再区分顺序,只要将对象字面量的属性赋值即可

sendEmail({

    from: '[email protected]',

    to: '[email protected]',

    subject: 'Dinner this week?',

    body: 'Do you want to come over for dinner this week? Let me know.'

});

是不是觉得调用起来更加灵活和方便呢?再次回到上面的话题:变量作用域,其实这个在大家日常的实际项目经验中应该更加引以重视?我们说了window在全局作用域中均有效,但是我们的JS程序如果过多或不当使用全局变量,导致全局作用域内存在很多全局变量,将使应用程序的安全性面了巨大挑战,对于有些有心机的黑客来说,随意让别人获取全局变量并做恶意的修改,将导致应用程序的崩溃。那如何才能有效避免呢?我的建议:可以采用私有变量对我们的私密数据加以保护,其次还可以采用命名空间来建立模块层次以达到合理的保护。

// 这里MyCompany已经成为一个Singleton

var MyCompany = new function(){

    this.MyClient = { 

        WebMail: function() {

            alert("Creating WebMail application...");

        }

    };

}(); 

// 输出 "Creating WebMail application..."

var myWebMail = new MyCompany.MyClient.WebMail();

到此为止,关于JS面向对象的知识点就介绍到这里,以后的系列还将讲解Javascript的性能调优以及测试框架的搭建。

你可能感兴趣的:(JavaScript)