65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (7投票s)

2013年8月16日

CPOL

6分钟阅读

viewsIcon

20856

在 JavaScript 中创建对象

在 JavaScript 中创建对象的方式有很多,毕竟它非常灵活。那么,创建对象的正确方法是什么呢?构造函数模式、原型模式、对象字面量?
它是什么? 

在那之前,让我们先理清一些 JavaScript 的基础知识。
在 JavaScript 中,面向对象编程是否可行?
嗯,它有对象!对象可以包含数据,可以有对这些数据进行操作的方法,甚至可以包含其他对象。它没有类,但有构造函数,没有类继承,但有基于原型的继承。

看起来我们拥有在 JavaScript 中创建对象和面向对象编程所需的所有要素。

我们都知道 JavaScript 有私有变量。在函数内部使用“var”关键字声明的变量存在于函数的范围内,在函数外部无法访问。
如果我们不使用“var”关键字声明变量会怎样?我们可能会稍微谈谈它,可能会涉及“this”,但那可能是另一天的话题了。

回到问题。在 JavaScript 中创建对象/类的正确方法是什么?
让我们从我们已知的东西开始,尝试创建一个名为 Person 的对象。

var Person = {
   firstName : 'John',
   lastName : 'Cody',
   fullName : '',
   message : '',

    createFullName : function () {
        fullName = this.firstName + ' ' + this.lastName;
    },

    changeMessage : function (msg) {
       this.message = msg;
    },

    getMessage : function () {
        this.createFullName();
        return this.message + ' ' + fullName;
    }
}

Person.firstName = 'Eli';
Person.lastName = 'Flowers'
Person.changeMessage('welcome');
var message = Person.getMessage(); // welcome Eli Flowers
alert(message);

这种对象字面量模式。这非常接近我们习惯创建类的方式。
如果你不关心私有性/封装,并且你知道你不会创建该对象的实例,那么这可能是一个不错的选择。私有性有什么是公共性做不到的?但是,这不是类,它是一个对象,不能从中创建实例,也没有封装。

让我们尝试一些不同的方法。 

var Person = function() {
    this.firstName = 'John';
    this.lastName = 'Cody';
    var fullName = '';
    this.message = '';

    var _that = this;

    var createFullName = function () {
        fullName = _that.firstName + ' ' + _that.lastName;
    }

    this.changeMessage = function (msg) {
       this.message = msg;
    }

    this.getMessage = function () {
        createFullName();
        return this.message + ' ' + fullName;
    }

}

var person1 = new Person();
person1.firstName = 'Eli';
person1.lastName = 'Flowers'
person1.changeMessage('welcome');
var message = person1.getMessage(); // welcome Eli Flowers
alert(message);

这是一种构造函数模式。那么,它是类还是对象?嗯,两者皆是。我们可以在需要时直接使用 Person 对象。毕竟它是一个函数。或者使用“new”关键字创建一个该函数的新实例。

使用这种模式需要注意几点。

  1. 每当调用一个函数时,它都会获得一个名为“this”的特殊变量,它指向全局作用域。全局作用域是什么取决于函数本身的作用域。
  2. 每当使用“new”关键字创建函数的实例时,“this”就指向函数本身,并且“new”还会导致函数内部的代码执行。因此,这是一种构造函数模式。
  3. 附加到“this”的任何内容都将是公共的,而用“var”声明的任何内容都是私有的。
  4. 附加到“this”的函数称为特权函数,它可以访问私有变量和函数,以及附加到“this”的其他变量和函数。
  5. 私有函数可以访问其他私有变量和函数。 
  6. 私有函数不能直接访问附加到“this”的变量和函数。实现这一点的方法是创建一个名为“_that”的私有变量,并将其赋值给“this”。
  7. 任何私有变量或函数都可以被所有其他私有函数以及附加到“this”的所有其他函数访问。这是如何实现的,请阅读 JavaScript 中的作用域。
  8. 未用“var”声明或未附加到“this”的变量或函数将被附加到全局作用域,即函数本身定义的作用域。再次阅读有关作用域和闭包的内容。 

这实现了我们想要的大部分功能,但有时“this”和“that”的整个概念可能会变得令人困惑。对于那些想坚持真正的私有性的人来说。

让我们尝试一些更不同的方法。

var Person = function () {

    //private 
    var firstName = 'John';
    var lastName = 'Cody';
    var fullName = '';
    var message = '';


    var createFullName = function () {
        fullName = firstName + ' ' + lastName;
    }

    //public setters 
    var setMessage = function (msg) {
        message = msg;
    }

    var setFirstName = function (fName) {
        firstName = fName;
    }

    var setLastName = function (lName) {
        lastName = lName;
    }

    var getMessage = function () {
        createFullName();
        return message + ' ' + fullName;
    }

    //functions exposed public
    return {
        setFirstName: setFirstName,
        setLastName: setLastName,
        setMessage: setMessage,
        getMessage: getMessage
    };

};


var person1 = new Person();
person1.setFirstName('Eli');
person1.setLastName('Flowers');
person1.setMessage('welcome');
var message = person1.getMessage(); // welcome Eli Flowers
alert(message);

这是揭示模式。感谢 Christian Heilmann。这种模式的问题在于它需要为属性提供“getter”和“setter”。我们大多数人来自传统的 Java 编程,可以理解这一点,并且不觉得它复杂。这也有点像类实现了接口。

这一切都很好,但有一个小问题。每次创建类的实例时,新创建的对象都会获得变量和函数的副本。现在,变量的副本是可以的,我们想要特定于对象的,但是函数呢?它们只是要对数据进行操作。为什么需要它们的副本?

这就是原型发挥作用的地方。在原型上创建的任何内容都将在所有实例之间共享。我们所要做的就是在原型上创建公共函数。

var Person = function () {

    //private 
    var welcomeMessage = 'welcome';
    var fullName = '';
    var firstName = '';
    var lastName = "";
    var createFullName = function () {
	Person.prototype.setFirstName('asdsad'); 
        fullName = firstName + ' ' + lastName;
    };

    //constructor
    var Person = function () { }; //will be created evrytime

    //public 
    Person.prototype = {
        getFullName: function () {
            createFullName();
            return welcomeMessage + ' ' + fullName;
        },
        setFirstName: function (fName) {
            firstName = fName;
        },
        setLastName: function (lName) {
            lastName = lName;
        },
        ChangeMessage: function (mesg) {
            welcomeMessage = mesg;
        }
    }

    return new Person(); // Person; //new Person();
};


var person1 = new Person();
person1.setFirstName ('Eli');
person1.setLastName('Flowers');
person1.ChangeMessage('welcome');
var message = person1.getFullName(); // welcome asdsad Flowers
alert(message);

原型的一个问题是它无法访问私有变量和函数,因此我们引入了闭包,并且还为了那些担心类是如何创建的人而保持代码的组织性,而且它也不会弄乱全局作用域。所有东西仍然在 Person 的作用域内。

另一个问题是,每次创建实例时,都会执行所有代码,包括原型绑定。对我们中的一些人来说,这是一个性能问题。解决此问题的方法是仅在预期的公共函数尚不可用时才绑定原型。

这将导致原型仅在创建第一个实例时绑定一次,之后对于所有其他实例,都会进行检查。不幸的是,这个修复在我们上面的例子中不起作用,因为我们正在重新创建函数以生成闭包来实现类效果。嘿,至少我们节省了一些内存。

另一个问题是私有函数无法直接访问原型函数。

你到底为什么需要私有函数和变量?我明白你的脑海里有封装,想确保属性或内部数据不会被其他程序员意外或故意更改……等等。

记住,你无法将代码编译成二进制文件,你无论如何都无能为力,代码总是可用的,所以如果有人想搞乱它,他们就会搞乱它,无论你是否实现了真正的私有性,无论你是否将其提供给其他团队成员或出售。缩小会起作用吗?很可能。

程序员使用的其他技术之一是命名约定,使用下划线“_”来表示你打算使其私有的任何内容。

(function () {
    var Person = function () {
        this._fullName = '';
        this.welcomeMessage = '';
        this.firstName = '';
        this.lastName = "";
_that = this;


        this._createFullName = function () {
            this.ChangeMessage('Namaste');
            this._fullName = this.firstName + ' ' + this.lastName;
        };
    }

    //Shared Functions for Code optimization
    Person.prototype = {
        constructor: Person,
        getFullName: function () {
            this._createFullName();
            return this.welcomeMessage + ' ' + this._fullName;
        },
        ChangeMessage: function (mesg) {
            this.welcomeMessage = mesg;
        }
    }

    this.Person = Person;
})();

var person1 = new Person();
person1.firstName = 'Eli';
person1.lastName = 'Flowers';
person1.ChangeMessage('Welcome');
var message = person1.getFullName(); // Namaste Eli  Flowers
alert(message);

我并不是说你不应该考虑“私有”之类的东西。你是代码的设计者,你会知道如何工程化以及什么最适合你。根据你的需求,你可以使用任何一种或组合模式。

无论你做什么,有几点需要记住:不要弄乱全局作用域,担心内存泄漏和代码优化,并保持代码的组织性。所以多阅读有关作用域、闭包以及“this”如何行为的内容。

编程愉快。

© . All rights reserved.