JavaScript创建对象的7大模式

时光总嘲笑我的痴心妄想 提交于 2019-12-15 10:47:59

在JavaScript中,创建对象有7大模式,分别是工厂模式、构造函数模式、原型模式、组合使用构造函数模式和原型模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式。下面针对这7种模式展开讲解。

工厂模式

工厂模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但没有解决对象识别的问题。

 //1.工厂模式
 function  createPerson(name,age,career) {
     let o = new Object();
     o.name = name;
     o.age = age;
     o.career= career;

     o.sayName = function(){
         console.log(this.name);
     }

     return o;
 }
 let person1 = createPerson("Febby",18,"student");
 let person2 = createPerson("Jack",22,"teacher");

 console.log(person1.sayName());  //Febby
 console.log(person2.sayName());  //Jack

在这里插入图片描述

构造函数模式

在这个例子中,Person()函数取代了createPerson()函数,Person()函数相较于createPerson()函数有以下不同之处:

  • 没有显式地创建对象
  • 直接将属性和方法赋给了 this 对象
  • 没有 return 语句
 //2.构造函数模式
 //注意:构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
function Person(name,age,career) {
     this.name = name;
     this.age = age;
     this.career = career;
     this.sayAge = function () {
         console.log(this.age);
     };
}


let person1 = new Person("Febby",16,"student");
let person2 = new Person("Jack",40,"teacher");

console.log(person1.sayAge()); //16
console.log(person2.sayAge());  //40

//person1 和 person2 分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person.
console.log(person1.constructor === Person); //true
console.log(person2.constructor === Person); //true

//instanceof操作符检测对象类型更可靠 
//person1 和 person2  之所以同时是Object实例,是因为所有对象都继承自Object
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person);  //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person);  //true

在这里插入图片描述
要创建Person的新实例,必须使用new操作符,这种方式调用构造函数会经过4个步骤,分别是:

  1. 创建一个新对象
  2. 将构造函数的作用赋给新对象(this)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

person1 和 person2 分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person.
person1 和 person2 之所以同时是Object实例,是因为所有对象都继承自Object.

原型模式

创建的每一个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处就是可以让所以的对象实例共享它包含的属性和方法。

//3.原型模式
function Person() {

}
Person.prototype.name = "Febby";
Person.prototype.age = 18;
Person.prototype.career = "student";
Person.prototype.sayCareer = function () {
    console.log(this.career);
}

let person1 = new Person();
person1.sayCareer();  //student

let person2 = new Person();
person2.sayCareer(); //student

console.log(person1.sayCareer() === person2.sayCareer()); //true

在这里插入图片描述
可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。 如果在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那就会在实例中创建该属性,该属性将会屏蔽原型中的那个属性。

//3.原型模式
function Person() {

}
Person.prototype.name = "Febby";
Person.prototype.age = 18;
Person.prototype.career = "student";
Person.prototype.sayCareer = function () {
    console.log(this.career);
}

let person1 = new Person();
let person2 = new Person();

person1.name = "Rose";

console.log(person1.name); //Rose 来自实例
console.log(person2.name); //Febby  来自原型

//可以使用 delete 操作符删除该同名属性,从而能够重新访问原型中的属性。
delete  person1.name;
console.log(person1.name); //Febby 来自原型

在这里插入图片描述
添加同名属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性置为null,也只会影响实例属性,而不会恢复其指向原型的链接。

可以使用 delete 操作符删除该同名属性,从而能够重新访问原型中的属性。

还有一种更简单的原型语法
更常见的做法是使用一个包含所有属性和方法的对象字面量来重写整个原型对象。

//使用对象字面量
function Person() {

}
Person.prototype = {
    name : "Febby",
    age : 18,
    career : "student",
    sayCareer : function () {
        console.log(this.career);
    }
};

let person = new Person();

在上面代码中,我们将 Person.prototype 设置为等于一个以对象字面量形式创建的对象,最终结果相同。 但是,constructor属性不再指向Person
这是因为,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。而在这里使用的对象字面量语法,本质上完全重写了默认的prototype对象,因此 constructor属性也就变成新对象的 constructor 属性(指向 Object构造函数),不再指向Person函数。

直白地说就是,Person.prototype 本质上是 Object(原型)对象,函数的本身也是对象,这样一来就同时创建了 Person.prototype 的 prototype 对象,这个 prototype 对象自动获得 constructor属性。constructor属性也就变成新对象的 constructor 属性(指向 Object构造函数

此时尽管instanceof 操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了。

console.log(person instanceof Object); //true
console.log(person instanceof Person); //true
console.log(person.constructor == Person);  //false
console.log(person.constructor == Object);  //true

用instanceof 操作符测试Object 和 Person仍然返回true,但是constructor属性则等于Object而不等于Person了。我们可以特意将constructor属性设置回适当的值。

//使用对象字面量
function Person() {

}
Person.prototype = {
    constructor : Person, //特意将constructor设置回适当的值
    name : "Febby",
    age : 18,
    career : "student",
    sayCareer : function () {
        console.log(this.career);
    }
};

let person = new Person();

组合使用构造函数模式和原型模式

组合使用构造函数模式和原型模式是自定义类型的最常见的方式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每一个实例都会有自己的一份实例属性的副本,但又同时共享着对方法的引用,可以最大限度地节省了内存。这种混成模式还支持向构造函数传递参数

//4.组合使用构造函数模式和原型模式
function Person(name,age,career) {
    //实例属性
    this.name = name;
    this.age = age;
    this.career = career;
    this.friends =  ["Jack","Rose","Bob"];
}

//共享属性和方法
Person.prototype = {
    constructor:Person,
    sayName : function () {
        console.log(this.name);
    }
}

let person1 = new Person("Febby",16,"student");  //Febby
let person2 = new Person("Lily",45,"teacher");   //Lily

person1.friends.push("Tom");

console.log(person1.friends);  //"Jack、Rose、Bob、Tom"
console.log(person2.friends);  //"Jack、Rose、Bob"

console.log(person1.friends === person2.friends); //false
console.log(person1.sayName() === person2.sayName());  //true

动态原型模式

动态原型模式把所有信息都封装在构造函数中,通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。也就是说,可以用过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

 //5.动态原型模式
function Person(name,age,career) {
    this.name = name;
    this.age = age;
    this.career = career;

    //初始化
    if(typeof this.sayAge != "function"){
        Person.prototype.sayAge = function () {
            console.log(this.age);
        }
    };
}

let person1 = new Person("Febby",16,"student");
person1.sayAge();  //16

注意,这里只有当sayAge()方法不存在的情况下,才会将它添加到属性中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经初始化完成。但是要记住,对原型所做的修改能够立即在所有实例中得到反映。

寄生构造函数模式

寄生构造函数模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。

//6.寄生构造函数模式
function Person(name,age,career) {
    let o = new Object();
    o.name = name;
    o.age = age;
    o.career = career;
    o.sayCareer = function () {
        console.log(this.career);
    };
    return o;
}

let person = new Person("Febby",16,"student");
person.sayCareer();  //student

除了使用 new操作符 并把使用的包装的函数叫做 构造函数 外,这个模式跟工厂模式是一模一样的。
关于寄生构造函数模式,需要明确的是,返回的对象与构造函数或者与构造函数的原型属性之间是没有关系的。也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么区别。为此,不能依赖 instanceof 操作符来确定对象类型。

稳妥构造函数模式

道格拉斯.克罗克福德发明了JS中的稳妥对象。所谓稳妥对象,就是指没有公共属性,而且其方法也不引用 this 的对象。 稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:

  1. 新创建对象的实例方法不引用 this
  2. 不使用 new 操作符调用构造函数

根据这两点要求,可以改写前面的 Person 函数。

 //7.稳妥构造函数模式
 function Person(name,age,career) {

     //创建要返回的对象
     let o = new Object();

     //可以在这里自定义私有变量的函数

     //添加方法
     o.sayCareer = function () {
         console.log(career);
     };

     return o;
 }
 let perosn = Person("Febby",16,"student");
 perosn.sayCareer();  //student

在这里插入图片描述
注意,在这种模式创建的对象中,除了使用 sayCareer() 方法之外,没有其他方法访问 career 的值。变量 person 保存的是一个稳妥对象,除了调用 sayCareer() 方法外,没有别的方法可以访问数据成员了。即使有其他代码会给这个对象添加方法或者数据成员,但也不可能有别的办法访问传入到构造函数的原始数据。这就是稳妥构造函数模式的安全性。

以上就是JavaScript创建对象的7大模式,希望能够帮助到大家!

参考来源:《JavaScript高级程序设计》

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!