Javascript创建对象

创建对象

ECMAScript 定义类或对象

使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象。使用 Object 构造函数或者字面量可以用来创建单个对象,但是这种行为有个明显的缺点, 创建多个对象会导致重复代码

原始的创建对象方式

let ferrari = new Object();

ferrari.color = 'red';
ferrari.showColor = function() {
    console.log(this.color);
}

上面的方法可以创建一个 ferrali 对象, 但是如果有多个对象将会有大量的重复代码, 比如 showColor 方法, 为了解决这个问题我们使用一种变体: 工厂模式

工厂模式

封装上例的方法:

let createFerrari = function() {
    let ferrari = new Object();

    ferrari.color = 'red';
    ferrari.showColor = function() {
        console.log(this.color);
    }
    return ferrari;
}

let ferrari1 = createFerrari(),
    ferrari2 = createFerrari();

我们使用 createFerrari() 封装上面的方法从某种程度上减少了代码量, 但创建出的对象属性值完全一样, 可以为函数传参数将其变的灵活一些

let createFerrari = function(color) {
    let ferrari = new Object();

    ferrari.color = color;
    ferrari.showColor = function() {
        console.log(this.color);
    }
    return ferrari;
}

let ferrari1 = createFerrari('red'),
    ferrari2 = createFerrari('blue');

上述代码每次创建对象的时候都要重新创建一个 showColor 方法, 其实我们希望这些实例能够共享一个方法

ferrari1.showColor === ferrari2.showColor;  // false

为此我们可以在工厂函数外面定义方法, 然后引入到工厂函数内部

let showColor = function() {
    console.log(this.color);
};

let createFerrari = function(color) {
    let ferrari = new Object();

    ferrari.color = color;
    ferrari.showColor = showColor;
    return ferrari;
}

let ferrari1 = createFerrari('red'),
    ferrari2 = createFerrari('blue');

ferrari1.showColor === ferrari2.showColor;  // true

这段代码解决了创建对象时重复创建方法的问题, 但是创建实例的时候没有 new, 而且这个 showColor() 不太像是对象的方法, 它被定义到外面去了, instanceof 无法检测对象实例, 这些问题导致了 构造函数 的出现

构造函数

创建构造函数首先要有个名字, 大写字母开头, 并不强制, 这只是一个习惯, 用于区分其他的函数, 某种程度上 构造函数工厂函数 很类似

let CreateFerrari = function(color) {
    this.color = color;
    this.showColor = function() {
        console.log(this.color);
    }
}

在构造函数中并没有显示创建对象, 使用 this 关键字, 创建实例的时候使用了 new 运算符, 其实在函数调用的时候其内部会创建两个特殊的对象 thisarguments, 在使用 new 运算符的时候会创建一个对象, 由 this 指向它, 默认情况构造函数会把 this return 出来, 如果指出了 return 的值情况将会有所不一样, 若 return 的是基本类型的值将无视这个 return , 还是继续将 this return, 如过 return 的是 引用类型的值将把该值返回出来, 另外 构造函数 也能在没有 new 的情况下调用, 返回值和使用 new 的时候不一样, 同样 构造函数 也是在内部定义的方法, 那么实例并没有共享该方法

原型方式

javascript 中的函数都有一个默认的属性 prototyoe(Function.prototype出来), prototype 可以看作创建对象索依赖的原型

let CreateFerrari = function() {};

CreateFerrari.prototype.color = 'red';
CreateFerrari.prototype.showColor = function() {
    console.log(this.color);
}

let ferrari1 = new CreateFerrari(),
    ferrari2 = new CreateFerrari();

这段代码把方法和属性定义在构造器的 prototype 上, 被 new 出来的实例都有一个指针 __proto__ 指向构造器的 prototype, 前面的问题到这里得到了一定程度的解决, 但是原型模式又暴露了新的问题, 比如构造器没有参数, 必须在对象创建后才能改变属性的默认值, 属性指向引用类型的值时将会造成问题

let CreateFerrari = function() {};

CreateFerrari.prototype.options = {
    speed: '330km/h',
    color: 'red'
};
CreateFerrari.prototype.showColor = function() {
    console.log(this.color);
}

let ferrari1 = new CreateFerrari(),
    ferrari2 = new CreateFerrari();

ferrari1.options.price = '$3000';
console.log(ferrari2.options.price);    // $3000

混合构造函数和原型

这种方式可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函数定义对象的所有非共享属性,用原型方式定义对象的共享属性, 这有可保证共享属性只被创建一次, 实例可以拥有自己的属性

let CreateFerrari = function(options) {
    this.options = options;
};

CreateFerrari.prototype.showColor = function() {
    console.log(this.color);
};

这种方式是比较完美的创建对象的方式, 是目前 ECMAScript 中使用非常广泛的一种方式, 仍有更好些的解决方案

动态原型方式

动态原型方法的基本想法与混合的构造函数和原型的方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义, 区别是在给对象赋方法的位置

let CreateFerrari = function(options) {
    this.options = options;
    if(typeof this.showColor !== 'function') {
        CreateFerrari.prototype.showColor = function() {
            console.log(this.color);
        }
    }
};

保证了 showColor() 只被添加一次

其他一些另类的方式

当上述方式无法满足需求的时候可以考虑如下方案

寄生构造函数方式

这种方式通常是在不能应用前一种方式时的变通方法。它的目的是创建假构造函数,只返回另一种对象的新实例。

let CreateFerrari = function() {
    let tmp = new Object();
        tmp.color = 'red';
        tmp.showColor = function() {
            console.log(this.color);
        }
    return tmp;
};

let ferrari = new CreateFerrari();

与经典构造函数不同,这种方式使用 new 运算符,使它看起来像真正的构造函数, 其实是使用了 new 运算符来调用了工厂模式, 这种模式可以在特殊的情况下用来为对象创建构造函数, 考虑如下demo

let OtherArray = function() {
    let arr = new Array();
    arr.push(...arguments);
    arr.toMinusString = function() {
        return this.join('-');
    }
    return arr;
};

let arr = new OtherArray(1, 2, 3);
arr.toMinusString();    //1-2-3;

该方式返回的对象和构造器的原型之间没有任何联系, 也不能使用 instanceof 来检测类型, 也可以通过 es6 的 classextends 实现, 详情请见 更多信息

稳妥构造函数

稳妥对象是指没有公共属性, 而且其方法也不引用 this 的对象, 可以在不能使用 thisnew 的环境下使用, demo 如下:

let Ferrari = function(color) {
    let obj = new Object();
    // 这里还能设置一些私有属性和私有方法
    obj.showColor = function() {
        console.log(color);
    }
    return obj;
}
let ferrari = new Ferrari('red');
    ferrari.showColor();    // red

在这段代码中 ferrari 变量中保存了一个稳妥对象, 除了调用 showColor 之外再也没有办法访问到 color 变量;

小结

  • 混合构造函数和原型 方法适合大多数创建对象的情况, 比较推荐使用
  • 动态原型方式 把所有信息封装在构造函数, 仅在必要的情况下初始化原型, 保持了同时使用构造函数和原型的优点