博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从码农到设计者,从单例模式入手设计代码
阅读量:6526 次
发布时间:2019-06-24

本文共 6161 字,大约阅读时间需要 20 分钟。

首先先复习一下内存

var str1 = "abc";var str2 = "abc";console.log(str1 == str2)console.log(str1 === str2)// 上面的代码实际上是执行了这个操作// var str = String("abc")
// 那么如果var str1 = String("abc");var str2 = new String("abc");console.log(str1 == str2)console.log(str1 === str2)// 我们可以在控制台输出一下 str1 和 str2,一看就知道为什么不一样了。

我们可以得到结论:

  • == 比较的是变量(对象)的值
  • === 比较的是变量(对象)的地址
  • 以后不管什么编程语言,只要看到 new 关键字,一定是在堆中开辟一块新内存

浏览器解析 HTML

模板和实例

var myDiv = document.getElementById("myDiv");
  • 浏览器把 HTML 一对对标签解析下来后,全部存放到内存空间,并互相指向,形成所谓的 DOM 树
  • 可以用typeof mydiv的方式查看
  • 如果是 Object 那就一定是存放在堆空间的,其他存放在常量池。
  • 这个 myDiv 实际上是 div 标签的实例
  • 可以用 myDiv.constructor 的方法看这个实例到底是哪个实例类

那么能不能用 new HTMLDivElement() new 一个新的 HTMLDiv 类呢?

不管对错,先猜猜看。

实际操作一遍发现浏览器报错了,浏览器不允许你私自 new 一个 HTMLDiv 类

那么该如何 new 出一个 HTMLDiv 类呢?

--------------------------------------我是分割线--------------------------------------

浏览器提供了这么一个方法 document.createElement("div"),通过这种方式,它能够在内存中创建一个 DOM 对象,并且是 HTMLDivElement 的对象。

var myDiv1 = document.createElement("div");

这是一个典型的工厂模式。

我们可以发现 myDiv.constructormyDiv1.constructor 一模一样,这就说明,这两个是同样一个模板下所产生的不同的实例

但是问题来了,我希望我的模板下有且只有一个实例,节约内存,例如 body 标签,全局唯一,这个时候该怎么设计?

--------------------------------------我是引导君--------------------------------------

JavaScript 有一个特性,就是动态对象可以随意复制其行为和属性。

大家来说出自己的理解。

不要着急,我先继续往下讲。

var obj = {};// 这就是一个简单的单例,同时也是 Object 的一个实例,我们可以在这里面扩展任何我们想要的属性// 例如 var Obj = {name: "abc",age: 1}

这段代码在我们项目代码里非常普遍,但是这样的实例,也是单例模式,有个不好的地方,那就是,这个单例根本无法扩展,而且使用起来也非常不安全,因为我们可以随时改变这个里面的内容。

--------------------------------------我是不正经的正题君--------------------------------------

那么,单例模式在 JavaScript 中该如何设计呢?

(function(){})()// 熟悉我的写法的人肯定知道// 这段代码是创建一个匿名函数,并且立即调用// 那么这么写到底有什么用呢?

这种写法是有用的,它帮我实现了一个闭包,这段代码的 {} 中帮我实现了一个闭包临时作用域。

var SingleTest = (function(){    // 这个 return 的 function 就是刚刚说的模板类    return function(){        console.log("进入构造器函数");    }})()// 这个时候我们就可以var i1 = new SingleTest();var i2 = new SingleTest();console.log(i1===i2) // false

大家注意,有基础的应该都知道,函数在 JavaScript 里面有两种使用方式,一种是函数调用(小写),另一种是构造器(大写),行业潜规则。

但是我希望不管我怎么 new ,我都想使用内存中的同一块地址,也就是i1===i2,那么该怎么做呢?

var SingleTest = (function(){    var _instance = null;    return function(){        console.log("进入构造器函数");        if (!_instance) {            console.log("第一次 new,局部变量(实例)_instance 为 null");            _instance = this;            return _instance;        } else {            console.log("不是第一次 new,局部变量(实例)_instance 不为 null,直接返回");            return _instance;        }    }})()

大家先理解理解这段代码。

this 代表的是当前创建的这块内存空间的引用,所以定义的属性或者方法都可以用 this 来操作。

这个时候 console.log(i1===i2) 看看会发生什么。

--------------------------------------我是不正经的参数君--------------------------------------

如果我要给这个单例传一个参数,我们要访问实例里面的 name 属性怎么办?

var SingleTest = (function(){    var _instance = null;    return function(ops){        // 我们经常会这么做        // 通过这种方式我们可以过滤掉不传参数带来的空引用问题        ops = ops || {};        if (!_instance) {            _instance = this;            // 通过 for 循环,遍历迭代我们的参数            for (var prop in ops) {                _instance[prop] = ops[prop];            }            return _instance;        } else {            for (var prop in ops) {                _instance[prop] = ops[prop];            }            return _instance;        }    }})()var i1 = new SingleTest({name:"zhangsan"});var i2 = new SingleTest({name:"lisi"});console.log(i1.name);// 那么输出的值是多少呢?// 很明显上面写了两个 for 循环,我们代码里也经常有这种情况发生,这个时候该怎么优化?
var SingleTest = (function(){    var _instance = null;    var _default = {}    // 封装 for 循环    function _init(ops) {        for (var prop in ops) {            this[prop] = ops[prop];        }    }    return function(ops=_default){// es6支持        // ops = ops || {}; 这种方式已经 out 了        if (!_instance) {            _instance = this;            _init.call(_instance, ops);        } else {            _init.call(_instance, ops);        }        return _instance;    }})()var i1 = new SingleTest({name:"zhangsan"});var i2 = new SingleTest({name:"lisi"});console.log(i1.name);

这个代码已经优化度很高了,但是还可以进行优化,比如说,如果我这里面不止 _init 方法,还有其他方法,例如,function _method1(){} function _method2(){}等,在函数体内进行调用的时候,你会发现,这么设计并不是一个好主意。

那么,有多个方法的时候该如何进行优化呢?

--------------------------------------我是正经的优化君--------------------------------------

简单来说,就是将这些方法加到原型链中。

var SingleTest = (function(){    var _instance = null;    var _default = {}    function SingleInstance(ops=_default){        if (!_instance) {            _instance = this;            this._init(ops);        } else {            _instance._init(ops);        }        return _instance;    }    // 将方法加到原型中去    // _的意思是,私有属性或方法,行业规则。    SingleInstance.prototype._init = function(ops) {        for (var prop in ops) {            this[prop] = ops[prop];        }    }    return SingleInstance;})()var i1 = new SingleTest({name:"zhangsan"});var i2 = new SingleTest({name:"lisi"});console.log(i1.name);

你会发现,这样做代码量并没有减少多少,但是优点是,在写入其他方法的时候,可以用 this 直接相互调用已存在的方法。

还有一个优点是,如果后期想 new 出不同的实例,直接对 _instance 做处理就好了,因为我已经把这个单例打包成了闭包,不会影响外面的调用者。

但是这样还有一个 bug !

那就是如果有些人不上规矩,想直接调用 SingleTest({name:"mazi"}) ,这个时候你会发现,控制台报错了。
那么该如何优化,让这种调用也兼容呢?

--------------------------------------我是万恶的bug君--------------------------------------

原因就是,这样做是直接调用这个函数堆栈,这就意味着,当前函数的作用域并不是堆里面的 this

不要问我函数堆栈是什么,这个不是主题,简单来说就是把函数拿到栈里面去执行。

奔主题。

var SingleTest = (function(){    var _instance = null;    var _default = {}    function SingleInstance(ops=_default){        // instanceof 是表示 this 是不是 SingleInstance 的实例        if (this instanceof SingleInstance) {            if (!_instance) {                _instance = this;                this._init(ops);            } else {                _instance._init(ops);            }        } else {            if (!_instance) {                _instance = new SingleInstance();                _instance._init(ops);            } else {                _instance._init(ops);            }        }        return _instance;    }    SingleInstance.prototype._init = function(ops) {        for (var prop in ops) {            this[prop] = ops[prop];        }    }    return SingleInstance;})()var i0 = SingleTest({name:"wangwu"})var i1 = new SingleTest({name:"zhangsan"});var i2 = new SingleTest({name:"lisi"});console.log(i0 === i1);console.log(i0 === i2);

至此,这个单例已经优化完毕。

或许还可以继续优化,但是这个不重要了,讲到这足够了。
我想说的是,你们不要记代码,没用的,试着去理解我的思路,思路是通用的。

转载地址:http://kxnbo.baihongyu.com/

你可能感兴趣的文章
如何设定VDP同时备份的任务数?
查看>>
ipsec的***在企业网中的经典应用
查看>>
过来人谈《去360还是留在百度?》
查看>>
mysql备份工具innobackupex,xtrabackup-2.1安装,参数详解
查看>>
【复制】slave筛选复制之二(create/drop table语句)
查看>>
Movie Store OpenCart 自适应主题模板 ABC-0249
查看>>
mytop-MySQL监控工具
查看>>
RedHat linux YUM本地制作源
查看>>
apache端口占用问题
查看>>
本地Office Project计划表同步到SharePoint2013任务列表的权限问题
查看>>
Windows2008 R2 GAC权限问题
查看>>
洛谷——P1469 找筷子
查看>>
几句话就能让你明白:网络地址转换(NAT)
查看>>
springboot项目自定义注解实现的多数据源切换
查看>>
如何用javascript正则表达式验证身份证号码是否合法
查看>>
ccf 201803-1 跳一跳(Python实现)
查看>>
特此说明
查看>>
使用flume替代原有的scribe服务
查看>>
用脚本来定制ESXI安装镜像
查看>>
微软企业级加解密解决方案MBAM架构
查看>>