js单例模式

6 篇文章 0 订阅

实现单例模式

核心原理

  1. 使用 new 操作符,会隐式返回 this
  2. 使用闭包,保存了实例变量

方式 1 需要显式调用

var Singleton = function(name) {
  this.name = name;
  // new操作符调用 隐式返回this
};
Singleton.prototype.getName = function() {
  console.log(this.name);
};
// 自执行函数 保存了instance的引用
Singleton.getInstance = (function() {
  var instance = null;
  return function(name) {
    if (!instance) {
      instance = new Singleton(name);
    }
    return instance;
  };
})();

// 测试一下
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
console.log(a === b); // true

方式 2 隐式调用之代理实现

var CreateDiv = function(html) {
  this.html = html;
  this.init();
  // new操作符调用 隐式返回this
};
CreateDiv.prototype.init = function() {
  var div = document.createElement('div');
  div.innerHTML = this.html;
  document.body.appendChild(div);
};

// 返回div实例
var ProxySingletonCreateDiv = (function() {
  var instance;
  return function(html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  };
})();

// 测试一下
var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
console.log(a === b);

JavaScript 中的单例模式

书中说:“单例模式的核心是确保只有一个实例,并提供全局访问”。全部变量符合这个定义,但是又说全局变量不是单例模式,为什么呢?

因为全局变量虽然提供全局访问,但是全局变量可以创建多个实例,而且随时可能被覆写。
全局变量很容易产生冲突,可以使用命名空间或闭包减少全局变量的数量

动态创建命名空间

这个代码挺巧妙的,有 lodash 的风格,短小精悍~

// 分配内存地址0x001 指向{}
var MyApp = {};
// 以 MyApp.namespace('dom.style') 为例
MyApp.namespace = function(name) {
  // 转为数组 parts = ['dom', 'style']
  var parts = name.split('.');

  // 保存当前命名空间的引用 current指向内存地址0x123
  var current = MyApp;

  for (var i in parts) {
    // 当前命名空间不存在则增加 因为是引用 所以MyApp同步修改
    if (!current[parts[i]]) {
      // 第一次循环 current[dom] = {}(分配地址0x002) 同时 MyApp=current={'dom': {}}
      // 第二次循环 地址0x002 增加style MyApp和current同步修改
      current[parts[i]] = {};
    }
    // 这里是核心 每次遍历后 修改当前命名空间 用于下次循环
    // current 指向地址0x002
    current = current[parts[i]];
  }
};

MyApp.namespace('event');
MyApp.namespace('dom.style');
console.dir(MyApp);

// 上述代码等价于:
var MyApp = {
  event: {},
  dom: {
    style: {},
  },
};

在这里插入图片描述

闭包封装私有变量

这里使用闭包创建了私有变量,外部只能通过暴露的方法获取。

var user = (function() {
  var __name = 'sven';
  return {
    getUserInfo: function() {
      return __name;
    },
  };
})();

typescript 的 private

私有变量,也就是其他语言 private 标识符修饰的变量,这就想到了 typescript 中的 private,它是怎么实现的,编译后的代码是什么?

class User {
  private name = 'sven';
  getName() {
    return name;
  }
}

const sven = new User();
console.log(sven.name); // Property 'name' is private and only accessible within class 'User'.

按照 private 的理解,我以为 typescript 编译后会有什么黑科技,来实现变量私有化,比如加个下划线啥的,结果并不是,没啥变化,只是编译时限制了类型,编译后就移除了。。并没有私有化,以下就是 babel 编译后的代码

'use strict';

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true,
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

class User {
  constructor() {
    _defineProperty(this, 'name', 'sven');
  }

  getName() {
    return name;
  }
}

const sven = new User();
console.log(sven.name); // sven 果然啥也没做 就是编译为普通的js代码

变量私有化的其他方式

私有化的核心是隐藏对象的 key。es6 新增了原始数据类型 Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。还是以 User 为例

// nameKey定义在内部,对外不暴露
const nameKey = Symbol('name');
class User {
  constructor() {
    this[nameKey] = 'sven';
  }

  getName() {
    return this[nameKey];
  }
}

const sven = new User();
console.log(sven.name); // undefined
console.log(sven.getName()); // sven

// 虽然这样可以访问,但是nameKey类似密码机制,虽然用密码可以获取,但是你得先拿到密码
console.log(sven[nameKey]); // sven

惰性单例

可复用单例的实现

var getSingle = function(fn) {
  var result;
  return function() {
    return result || (result = fn.apply(this, arguments));
  };
};

场景 1 创建唯一的 dom 元素

var createLoginLayer = function() {
  var div = document.createElement('div');
  div.innerHTML = '我是登录浮窗';
  return div;
};

// 本次执行只是获取了一个创建div单例的函数
var createSingleLoginLayer = getSingle(createLoginLayer);
// 第一次执行 执行fn 创建div 保存到result
var loginLayer1 = createSingleLoginLayer();
// 第二次执行 得到result 也就是上次创建的div
var loginLayer2 = createSingleLoginLayer();
console.log(loginLayer1 === loginLayer2); // true



// 分析下执行过程
var getSingle = function(fn) {
  // 单例标识
  var result;
  return function() {
    // 这里代码比较精简 转化一下
    // return result || (result = fn.apply(this, arguments));
    if (!result) {
      // 用当前函数上下文执行fn 获取实例或标识 赋值给result
      result = fn.apply(this, arguments);
    }
    return result;
  };
};

场景 2 模拟 jQuery one 事件

var bindEvent = getSingle(function() {
  document.getElementById('div1').onclick = function() {
    alert('click');
  };
  return true;
});

// 第一次执行 result为false 此时执行回调函数 result 改为ture
bindEvent();
// 第二次执行 result为true 什么也不做 返回 true
bindEvent();
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hyduan200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值