首页 > 代码库 > 重新认识JavaScript里的创建对象(一)

重新认识JavaScript里的创建对象(一)

一、序

      面向对象有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。ECMA-262把对象定义为“无序属性的集合,其属性可以包含基本值、对象或者函数”。

使用Object来创建对象,如下代码所示:

        var animal = new Object();
        animal.name = "小猫";
        animal.color = "黑色";
        animal.print = function () {
            console.log(this.name + "的颜色是:" + this.color);
        }

使用对象字面量来创建对象,如下代码所示:

 var animal = {
            name: "小猫",
            color: "黑色",
            print: function () {
                console.log(this.name + "的颜色是:" + this.color);
            }
        };

通过上面两种方式,虽然我创建了对象,但是并没用满足对象的封装特性,我并不能复用这短代码,只能粘贴复制来创建新的对象,这并不是我想要的结果,前辈们就从面向对象的设计模式

的思想中提炼出一些新的方法。我们要站在巨人的肩膀上才能看得更远。

二、工厂模式

      这种模式抽象了创建具体对象的过程。ECMAScript无法创建类,所以采用函数来封装以特定接口创建对象的细节。如下面代码所示:

        ///提供构造一个动物对象方法
        function createAnimal(name, color) {
            var obj = new Object();
            obj.name = name;
            obj.color = color;
            obj.print = function () {
                console.log(name + "的颜色是:" + color);
            }
            return obj; //返回对象
        }
        //实例化一个对象
        var animal1 = createAnimal("小猫", "黑色");
        animal1.print();

这种模式,虽然做到了代码可以复用的效果,但是却没有解决对象识别的问题,如果我们能够像申明Object,Array这样的原生构造函数就好了,所以就有了构造函数的衍生;

三、构造函数

     熟悉C#、Java等语言的朋友,就知道一般来说我们都有无参构造函数、有参构造函数。像Array,就属于一种无参构造函数。先来看下面一段代码

        function Animal(name, color) {
            this.name = name;
            this.color = color;
            this.print = function () {
                console.log(this.name + "的颜色是:" + this.color);
            }
        }

        var animal1 = new Animal("小猫", "黑色");
        animal1.print();

我们这采用了有参构造函数,和工厂模式比较起来,构造函数并没有显式的创建对象,直接将属性和方法赋给了this对象,没有return语句。通过instanceof方法我们可以得出构造函数

可以将它的实例化标识为一种特定的类型;

        console.log(animal1 instanceof Object);//true
        console.log(animal1 instanceof Animal);//true

使用构造函数的主要问题,就是每个方法都要在每一个实例上重新创建一遍。ECMAScript中函数是对象,也就是说每定义一个函数,也就是实例化了一个对象,所以上面对象中print函数就实例化了2次。

这样做是没有必要的,所以我们可以通过把函数定义转移到构造函数外部来解决这个问题。

        function Animal(name, color) {
            this.name = name;
            this.color = color;
            this.print = print
        }

        function print() {
            console.log(this.name + "的颜色是:" + this.color);
        }

但是这样做新问题又来了,在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实了。于是让我们这个自定义的引用类型就丝毫没有封装性可言了。接下来就有了原形模式来

解决这个问题了。

四、原型模式

      我们创建每个函数都有一个prototype(原型)属性,这个属性是个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如下面的代码所示:

        function Animal() {

        }

        Animal.prototype.name = "小猫";
        Animal.prototype.color = "黑色";
        Animal.prototype.print = function () {
            console.log(this.name + "的颜色是:" + this.color);
        }

        var animal = new Animal();
        animal.print();

如果我们要改变这个对象中的属性的话,就直接对其属性重新赋值:

        var animal = new Animal();
        animal.name = "小花猫";
        animal.print();

但是一般来说,我们都喜欢在创建对象的时候就对其赋值,所以我就结合有参构造函数来重新改造下上面的代码

        function Animal(name, color) {
            this.name = name;
            this.color = color;
        }
        Animal.prototype.print = function () {
            console.log(this.name + "的颜色是:" + this.color);
        }

        var animal = new Animal("小黑猫", "黑色");
        animal.print();

但是这样做,如果参数过于多就会显得很长,有时候传参的时候就会容易出错,根据我几年的C#代码经验,针对过于长的参数,我一般喜欢把参数分装成一个对象,作为参数,

如下代码所示:

        function Animal(setting) {
            this.name = setting.name;
            this.color = setting.color;
        }
        Animal.prototype.print = function () {
            console.log(this.name + "的颜色是:" + this.color);
        }

        var setting = {
            name: "小猫",
            color:"黑色"
        };

        var animal = new Animal(setting);
        animal.print();

这样做封装就看起来简洁了许多,个人所爱吧。

五、结语

     本来是计划早睡早起身体好的,现在又00:30了,又得晚睡早起了,今天就先温故知新到这,加油吧,老男孩!晚安!

重新认识JavaScript里的创建对象(一)