zoukankan      html  css  js  c++  java
  • 从码农到设计者,从单例模式入手设计代码

    首先先复习一下内存

    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

    模板和实例

    <div id="myDiv"></div>
    
    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);
    

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

  • 相关阅读:
    [LeetCode]Contains Duplicate
    C++基础之泛型算法
    KMP算法
    [LeetCode]Shortest Palindrome
    [LeetCode]House Robber
    Palindrome Linked List leetcode
    Rotate Array leetcode
    Rotate Image LeetCode
    Rotate List leetcode
    Reorder List leetcode
  • 原文地址:https://www.cnblogs.com/liangyin/p/7764266.html
Copyright © 2011-2022 走看看