JavaScript中建立对象的几种方式

前言

通常状况下,JS中建立对象的方式能够用构造函数Object或者对象字面量的方式,但须要建立几个具备相同属性或方法的对象时,就得写大量的冗余代码。故而出现了下述几种建立对象的方法。设计模式

1、工厂模式

工厂模式是一种常见的设计模式。这种模式把对象的建立过程抽象出来并封装成一个函数。须要使用同类型的对象时,能够不断地调用此函数。例子以下:浏览器

function createPerson (name, age) {
    var o = new Object();
    o.name = name;
    o.sayName = function(){
        console.log(this.name)
    }
    return o;
}
复制代码

工厂模式解决了建立多个相同类型对象的问题,即减小了代码的冗余,但没有解决对象识别的问题。bash

2、构造函数模式

JavaScript中的构造函数其实就是普通的函数,但构造函数的目的通常用来建立对象。按照约定俗成的惯例,构造函数应该以一个大写字母开头。例子以下:函数

function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码

此例子与工厂模式的区别有:学习

  • 没有显式地建立一个对象;
  • 直接把方法和属性值赋给this;
  • 没有return语句;
  • 建立对象时须要使用new关键字。

此外,构造函数模式还解决了对象识别的问题:ui

xiaoming instanceof Person // true
xiaohong instanceof Person // true
复制代码

特别地,由于构造函数也是函数,在不用new操做符调用时,函数内部的this对象实际上是指向了全局对象(浏览器环境是window对象),具体缘由请学习this的原理。this

构造函数并非没有缺点,如上面例子的sayName方法,在每一个实例上都从新建立了一遍:spa

xiaoming.sayName === xiaohong.sayBane // false
复制代码

像sayName这种函数都是为了完成相同的任务(打印实例的名字),大可抽象出来做为一个共享的函数prototype

function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName(){
    console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName();  //小明
xiaohong.sayName();  //小红
复制代码

但这种方式使得sayName函数变为了全局函数,而且只合适被Person new出来的实例调用。故而带来下面几个问题:设计

  • 此全局函数会让其余的开发人员感到疑惑;
  • 使得全局做用域变得名副其实(全局做用域的属性和函数应该适合在任何地方调用);
  • 若构造函数的实例须要不少方法时,会暴露太多函数到全局做用域,毫无封装性可言。

3、原型模式

利用JS的原型对象和原型链,能够解决构造函数模式中带来的那几个问题:

function Person(){};
Person.prototype = {
    constructor: Person,
    name: '小明',
    age: 18,
    sayName: function () {
        console.log(this.name)
    }
}

var xiaoming = new Person();
var xiaohong = new Person();
xiaoming.sayName(); //小明
xiaohong.sayName(); //小明
复制代码

上面的例子能够看到,虽然解决了构造函数模式存在的问题,但缺点也是大大的:

  • 因忽略了构造函数的传参,每一个实例的属性都是相同的,缺少灵活性;
  • 对于引用类型的属性值,一个实例对其修改会反映到全部实例。
Person.prototype.friends = ['张三', '李四'];
xiaoming.friends === xiaohong.friends; //true
xiaoming.frinds.push('王五');
xiaohong.friends; // ['张三', '李四', '王五'];
复制代码

4、组合使用构造函数模式和原型模式(推荐)

组合使用构造函数模式和原型模式是ES5中建立对象最经常使用的模式,此方式用构造函数定义实例的公共属性,使用原型模式定义实例的公共方法,最大限度下降了内存:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.friends = ['张三', '李四'];
};
Person.prototype.sayName = function () {
    console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码

另外,对于引用类型的共享的属性值,实例之间不会互相影响:

xiaoming.friends ==== xiaohong.friends; //false
xiaoming.frinds.push('王五');
xiaohong.friends; // ['张三', '李四'];
复制代码

5、动态原型模式

动态原型模式就是把全部信息封装在构造函数里:

function Person(name, age){
    this.name = name;
    this.age = age;
    if(typeof this.sayName !== 'function'){
        Person.prototype.sayName = function () {
            console.log(this.name);
        }
    }
};
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码

动态原型模式同事保持了构造函数模式和原型模式的优势,且封装性比构造函数和原型组合使用的方式更好。但要注意在构造函数执行后不能重写原型对象,不然会切断现有实例和新原型的关系。由于该模式中原型对象上的自定义方法是在构造函数中挂载的。

红皮书还介绍了寄生构造函数模式和稳妥构造函数模式,但实践中很是少用。有兴趣的同窗能够查阅。

相关文章
相关标签/搜索