• 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", "miracle@cnblogs.com");
    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", "miracle@sina.com");
    setSubjectAndFrom.apply(myEmailMessage, [ "New subject", "miracle@sina.com" ]);
    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("miracle@cnblogs.com", "miracle.he@cnblogs.com", "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: 'miracle@cnblogs.com',
        to: 'miracle.he@cnblogs.com',
        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的性能调优以及测试框架的搭建。

  • 相关阅读:
    WebAPI中路由参数中包含字符-点“.”
    Web API 授权筛选器
    WebApi
    C#视频拍照、视频录制项目示例
    WPF 获取鼠标屏幕位置、窗口位置、控件位置
    C#中字符串转换为计算公式
    ffmpeg开发文档
    .net core控制台应用程序初识
    网络书籍
    ffmpeg命令参数详解
  • 原文地址:https://www.cnblogs.com/hmiinyu/p/2515198.html
Copyright © 2020-2023  润新知