查看原文
其他

JavaScript 创建对象之单例、工厂、构造函数模式

iceman_dev 计算机与网络安全 2022-06-01

信息安全公益宣传,信息安全知识启蒙。

加微信群回复公众号:微信群;QQ群:16004488

加微信群或QQ群可免费索取:学习教程


01单例模式


首先看一个问题,我们要在程序中描述两个人,这两个人都有姓名和年龄,可能刚刚开始学习js的时候会写成这样:

var name1 = 'iceman'; var age1 = 25; var name2 = 'mengzhe'; var age2 = 26;

以上的写法的确是描述两个人,每个人都有姓名和年龄,但是每个人的姓名和年龄并没有放在一起,也就是说每个人的年龄和姓名并没有对应起来。这时候我们就引出对象的概念:把描述同一个事物(同一个对象)的属性和方法放在同一个内存空间下面,起到了分组的作用,这样不同事物之间的属性即使属性名相同相互也不会发生冲突。

var person1 = {    name:'iceman',    age:25}; var person2 = {    name:'mengzhe',    age:26};

以上这种写法可以认为是一种分组编写代码的模式,通过如此分组之后,每一个人的姓名和年龄都在同一块内存空间下,也就是每个人的姓名和年龄都对应起来了。我们也把这种分组编写代码的模式称之为单例模式(在《JavaScript高级程序设计》中也称之为对象字面量模式),在单例模式中,把person1和person2叫做命名空间。


注意:JavaScript中的单例模式就是这样写的,和其他语言的单例模式有很大的区别,很多Java程序员可能觉得这不是单例模式,不过这就是语言的区别。


单例模式是一种项目开发中经常使用的模式,因为项目中我们可以使用单例模式来进行模块化开发,对一个相对来说比较大的项目中,需要多人协作开发,所以一般情况下会根据当前项目的需求,划分几个功能模块,每个人负责一部分,同时开发,最后把每个人的代码进行合并,比如:


公共模块:提供公共方法

var utils = {  select:function () {      //...  } };


页面选项卡模块:实现选项卡切换

var tabModule = {  change:function () {      utils.select(); // 在自己的命名空间下调用其他命名空间的方法  } };


搜索模块:实现搜索内容变化的处理

var searchModule  = {  change:function () {      this.clickEvent(); // 在自己的命名空间下调用自己命名空间的方法  },  clickEvent:function () {      //...  } };

以上在例子中,在选项卡模块(tabModule)和和搜索模块(searchModule)中都有change方法,如果没有分模块来编写的话会造成命名的冲突,按照JavaScript语言的解析规则,后声明的方法会覆盖前面声明的方法。而通过模块划分之后,这两个模块下的change方法归属于各自的模块,调用的时候都是调用自己模块下的方法(tabModule.change()、searchModule.change()),不会有冲突。


02工厂模式


回顾单例模式:

var person1 = {    name:'iceman',    age:25,    writeJs:function () {        console.log(this.name + 'write js');    } }; person1.writeJs();

单例模式解决了分组的问题,让每个对象有了自己独立的命名空间,但是不能批量生产,每一个新的对象都要重新写一份一模一样的代码。这时候就有了工厂模式,即:把实现同一事情的相同代码,放到一个函数中,以后如果再想实现这个功能,就不需要重新编写这些代码了,只要执行当前的函数即可,这就是函数的封装,体现了高内聚、低耦合的思想:减少页面的中的冗余代码,提高代码的重复利用率:

function createPerson(name, age) {    var obj = {};    obj.name = name;    obj.age = age;    obj.writeJs = function () {        console.log(this.name + 'write js');    }    return obj; } var p1 = createPerson('mengzhe' , 26); p1.writeJs(); var p2 = createPerson('iceman' , 25); p2.writeJs();

顺便讲一下重载:在Java、C#等强类型的面向对象编程语言中,有函数重载的概念,但是在JavaScript中不存在重载,如果方法名一样的话,后面的会把前面的覆盖掉,最后只保留一个方法的定义,不过我们可以根据传递的参数不一样,实现模拟重载的功能:

function sum(num) {    if (typeof num === 'undefined') {        return 0;    }    return num; } sum(100); sum();

03构造函数模式

function CreateJsPerson(name, age) {    this.name = name;    this.age = age;    this.writeJs = function () {        console.log(this.name + 'write js');    }    // 浏览器再把创建的实例默认的进行返回 } var p1 = new CreateJsPerson('iceman' , 25); p1.writeJs(); var p2 = new CreateJsPerson('mengzhe' , 26); p2.writeJs();

注意:上面是new CreateJsPerson('iceman' , 25)这样,使用了new创建了对象,这和普通调用函数的方式有区别的:

var res = CreateJsPerson('xx' , 7);

这样没有用new而直接调用函数的方式,不是构造函数而是普通函数执行,由于没有写return,所以res=undefined,并且CreateJsPerson这个方法中的this是window。


构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。


构造函数模式和普通函数的模式的区别:


执行的时候


  • 普通函数执行:CreateJsPerson()

  • 构造函数执行:new CreateJsPerson(),通过new执行后,CreateJsPerson就是一个类了,而函数的返回值就是CreateJsPerson这个类的一个实例。


在函数代码执行的时候


  • 相同:都是形成一个私有的作用域,然后经历:形参赋值 --> 预解释 --> 代码从上到下执行(类和普通的函数一样执行,它也有普通函数的一面)。

  • 不同:① 在构造函数的代码执行之前,不用自己再手动的创建对象,浏览器会默认的创建一个对象数据类型的值(这个对象类型的值其实就是当前类的一个实例);② 接下来代码从上到下执行,以当前实例为执行的主体(this代表的就是当前的实例),然后分别的把属性名和属性值赋值给当前的实例;③ 最后,浏览器会默认的把创建的实例返回。


注意点:


  • JavaScript中所有的类都是函数数据类型的,它通过new执行变成了一个类,但是它本身也是一个普通的函数;

  • JavaScript中所有的实例都是对象数据类型的;

  • 在构造函数模式中,类中(函数体中)出现的 this.xx = xx中的this是当前类的一个实例;

  • p1和p2都是CreatePerson这个类的实例,所以都拥有writeJs这个方法,但是不同实例之间的方法是不一样的,在类中给实例增加的属性(this.xxx=xxx)属于当前实例的私有的属性,实例和实例之间是单独的个体,所以私有的属性之间是不相等的

console.log(p1.writeJs === p2.writeJs); // --> false

04构造函数模式知识的扩展


4.1、类中出现的this

function Fn() {    this.x = 100;    this.getX = function () {        // this-> 需要看getX执行的时候才知道        console.log(this.x);    }; } var f1 = new Fn; f1.getX(); // 100  方法中的this是f1 var xx = f1.getX; xx(); // undefined  方法中的this是window

在构造函数模式中创建对象new Fn(),如果Fn不需要传递参数的话,后面的小括号可以省略,即:new Fn


this的问题:在类中出现的this.xxx=xxx中的this都是当前类的实例,而类中某一个方法中的this需要看方法执行的时候,前面 是否有“."才能知道this是谁


4.2、私有变量

function Fn() {    var num = 10;    this.x = 100; // f1.x = 100    this.getX = function () { // f1.getX=function        console.log(this.x);    }; } var f1 = new Fn; console.log(f1.num); // -> undefined

类有普通函数的一面,当函数执行的时候,var num其实只是当前形成的私有作用域中的私有变量而已,它和f1这个实例没有任何的关系,只有this.xxx=xxx才相当于给f1这个实例增加私有的属性和方法,才和f1有关系。


4.3、类中的return

function Fn() {    this.x = 100;    this.getX = function () {        console.log(this.x);    }; //        return 100;    return {name:'iceman'}; } var f1 = new Fn; console.log(f1);

在构造函数模式中,浏览器会默认的把我们的实例返回(返回的是一个对象数据类型的值),如果我们自己手动写了return返回:


返回的是一个基本数据类型的值:当前实例的值是不变的,例如:return 100,此时的f1还是当前Fn类的实例;

返回的是一个引用数据类型的值:当前的实例会被自己返回的实例给替换掉,例如:return {name:'icmean'},此时的f1就不是Fn的实例,而是返回的这个对象;


4.4、检测实例是否属于类

function Fn() {    this.x = 100;    this.getX = function () {        console.log(this.x);    }; } var f1 = new Fn; console.log(f1 instanceof Fn); // --> true console.log(f1 instanceof Array); // --> false console.log(f1 instanceof Object); // --> true

检测某一个实例是否属于某一个类,使用的是instanceof,因为typeof只能检测基本数据类型的变量,有自己的局限性,不能细分Object下的对象、数组、正则...


所有的实例都是对象数据类型,而每一个对象数据类型的变量都是Object这个内置类的一个实例,所以f1也是Object的实例。


4.5、检测私有属性

function Fn() {    this.x = 100;    this.getX = function () {        console.log(this.x);    }; } var f1 = new Fn; var f2 = new Fn;

f1和f2都是Fn这个类的实例,都拥有x和geX两个属性,但是这两个属性是各自的私有的属性,所以:

console.log(f1.getX === f2.getX); // --> false

in:检测某一个属性是否属于这个对象,attr in object,不管是私有的属性,还是公有的属性,只要存在,用in来检测都是true

console.log('getX' in f1); // --> true 是它的一个属性

hasOwnProperty:用来检测某一个属性是否为这个对象的私有属性,这个方法只能检测私有的属性

console.log(f1.hasOwnProperty('getX')); // --> true   'getX'是f1的私有属性

思考:检测某一个属性是否为该对象的公有属性

function hasPubProperty(obj, attr) {    // 首先保证是它的一个属性,并且还不是私有的属性,那么只能是公有的属性了    return (attr in obj) && !obj.hasOwnProperty(attr); } console.log(hasPubProperty(f1, 'getX'));

05this


以上出现了很多次this,这里介绍下this。


在JavaScript中主要研究的都是函数中this,但是并不是只有函数中才有this,全局的this是window。


JavaScript中的this代表的是当前行为执行的主体,JavaScript中的context代表的是当前行为执行的环境。


首先明确:this是谁,和函数在哪里定义,在哪里执行都没有任何的关系,那么如何区分this呢?


  • 函数执行,首先看函数名前面是否有".",有点话,"."前面是谁,this就是谁,没有的话,this就是window;

  • 自执行函数中的this永远是window;

  • 给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this是当前的元素。

function fn() {    console.log(this); } var obj = {    fn:fn }; fn(); // this -> window obj.fn(); // this -> obj document.getElementById('div1').onclick = fn; // fn中的this是#div1 document.getElementById('div1').onclick = function () {    // this -> #div1    fn(); // this -> window };

练习题:

function sum() {    // this -> window    fn();// this -> window } sum(); var oo = {    sum:function () {        // this -> oo        fn(); // this -> window    } }; oo.sum();

▼ 点击阅读原文,查看更多精彩文章。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存