js-design's People
js-design's Issues
模式讲解
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。应用:显示登录浮窗
var Singleton = function(name) {
this.name = name;
this.instance = null;
}
Singleton.prototype.sayName = funciton() {
}
Singleton.getInstance = function() {
if (!this.instance) {
return new Singleton();
}
return this.instance
}
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a === b) // true
透明的单例模式
实现一个透明的单例类,用户从这个类中创建对象的时候,可以像使用其他类一样
var CreateDiv = (function() {
var instance;
var CreateDiv = function() {
if (instance) {
return instance;
}
this.html = html;
this.init();
return instance = this;
}
CreateDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
return CreateDiv
})();
用代理实现单例模式
var CreateDiv = function() {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function() {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
return CreateDiv
var ProxySingleton = (function(html) {
var istance;
if (!instance) {
instance = new CreateDiv(html);
}
return instance
})()
创建方式:
- 命名空间
var MyApp = {
};
MyApp.namespace = function(name) {
var parts = name.split('.');
var current = MyApp;
for (var i = 0, len = parts.length; i < len; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {}
}
current = current[parts[i]];
}
}
MyApp.namespace('event');
MyApp.namespace('dom.style');
// MyApp = {
// event: {},
// dom: {
// style: {}
// }
// }
- 闭包方式
惰性单例
采用事件机制,来创建,但是不符合单一职责原则,如果要创建其他标签,就得重写
var createDiv = (function() {
var div = null;
return function() {
if (!div) {
div = document.createElement('div');
div.innerHTML = 'div';
div.style.display = 'none';
document.appendChild(div);
return div;
}
return div;
}
})();
document.querySelector('btn').onclick = function() {
var player = createDiv();
player.style.display = 'block'
}
通用的惰性单例
var getSingle = function(fn) {
var result;
return result || result = fn.apply(this, arguments)
}
策略模式
定义一系列算法,并将它们封装成策略类,算法被封装在策略类内部的方法里,使它们可以相互替换。在客户对Context发起请求时,Context总是将请求委托给这些策略对象中间的某一个计算
var performanceS = function() {
}
performanceS.prototype.calculate = function(salary) {
return salary*4
}
var performanceA = function() {
}
performanceA.prototype.calculate = function(salary) {
return salary*4
}
var Bonus = function() {
this.salary = null;
this.strategy = null;
}
Bonus.prototype.setSalary = function(salary) {
this.salary = salary;
}
Bonus.prototype.setStrategy = function(strategy) {
this.strategy = strategy;
}
Bonus.prototype.getBonus = function() {
return this.strategy.calculate(this.salary);
}
优化方案:
体现了多态的特性
var strategies = {
S: function(salary) {
return salary*4
},
A: function(salary) {
return salary*3
}
}
var calculateBonus = function(level, salary) {
return strategies[level](salary)
}
应用:
- 实现动画
- 表单检验
优点:
- 策略模式利用组合、委托和多态等技术和**,可以有效地避免多重条件选择语句。
- 策略模式提供了对开发-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换、理解、扩展
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 策略模式中利用组合和委托来让context拥有执行算法的能力,这也是继承的一种更轻便的方案。
缺点:
- 使用策略模式会在程序中增加许多策略类或者策略对象
- 必须了解所有的strategy
代理模式
当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
保护代理
代理B帮助代理A过滤掉一些请求
虚拟代理
把一些开销很大的对象,延迟到真正需要它的时候才去创建。
var myImage = (function() {
var img = new Image();
document.appendChild(img)
return {
setSrc: function(src) {
img.src = src;
}
}
})()
var proxyImage = (function() {
var img = new Image();
img.onload = function() {
myImage.setSrc(this.src);
}
return {
setSrc: function(src) {
myImage.setSrc = 'loading.gif';
img.src = src;
}
}
})()
proxyImage.setSrc('real-img.jpg')
好处
- 用户不清楚代理和本体的区别,用户可以放心的请求代理,他只关心是否能得到想要的效果
- 在任何地方本体都可以替换成代理
缓存代理
// 缓存代理,通过传入方法,也可以为各种计算进行代理
var mult = function() {
var result = 1;
for (var i = 0, len = arguments.length; i < len; i++) {
result *= arguments[i];
}
return;
}
var proxyMult = (function() {
var cache = {};
return function() {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = mult.apply(this, arguments);
}
})();
proxyMult(2, 3, 4);
还能用于ajax异步请求数据,比如:分页。
其它代理
- 防火墙代理
- 远程代理
- 保护代理
- 智能引用
- 写时复制代理
迭代器模式
提供一个方法有序的访问一个聚合对象的每一个元素,又不用关心对象的内部构造
实现一个迭代器
var each = function(arr, callback) {
for (var i = 0, len = arr.length; i < len; i++) {
callback.call(arr[i], i, arr[i]);
}
}
内部迭代器
上述的each函数,就是一个内部迭代器,each函数内部已经定义好了规则
外部跌代器
显示地跌代下一个元素
function Iterator(arr) {
let index = 0;
function next() {
index++;
}
function isDone() {
return index >= arr.length;
}
function getCurrent() {
return arr[index];
}
return {
next,
isDone,
getCurrent
}
}
let iterator1 = Iterator([1, 2, 3]);
let iterator2 = Iterator([1, 2, 3]);
function compare(iterator1, iterator2) {
while (!iterator1.isDone() && !iterator2.isDone()) {
if (iterator1.getCurrent() !== iterator2.getCurrent()) {
throw new Error('iterator1 !== iterator2');
}
iterator1.next();
iterator2.next();
}
}
跌代类数组对象和字面量对象
区分数组
倒序迭代器
迭代器提供有序访问,所以顺序是可以设定的
中止迭代器
用break来中止迭代器
发布-订阅模式
又叫观察者模式,定义了一对多的依赖关系
作用:
- 大量用于异步编程
- 取代对象硬编码的通知机制,不再显示调用接口
实现
- 自定义事件
- 首先指定发布者
- 发布者用一个列表缓存回调函数来通知订阅者
- 发布消息时,遍历缓存列表(此列表可为数组也可为对象,因为有时可能需要key)
- 通用发布-订阅模式的实现
优点
- 时间解耦
- 对象解耦
缺点
- 模块之间用了太多的全局发布-订阅模式来通信,那模块之间的联系就隐藏到了背后,难以追踪。
- 创建订阅者需要消耗一定的时间和内存
必须先订阅再发布吗?
不是的,如qq离线消息。建立一个离线存放事件的堆栈,当事件发布的时候,如果此时还没有订阅者来订阅这个事件,暂时把发布事件的动作包裹在一个函数里,这些包装函数将被存入堆栈,等到有对象来订阅时,遍历堆栈执行包装函数。
避免全局事件的命名冲突
给Event对象提供创建命名空间的功能
命令模式
命令模式中的命令指的是一个执行某些特定事情的指令
应用场景
有时候需要向某些对象发送请求,但是不知道请求的接受者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合来设计程序,是的请求发送者和请求接收者能够消除彼此之间的耦合关系。
代码实例
var MenuBar = {
refresh: function() {
console.log('refresh')
}
}
var SubBar = {
add: function() {
console.log('add')
}
}
function RefreshMenuBarCommand(receiver) {
this.receiver = receiver;
}
RefreshMenuBarCommand.prototype.execute = function() {
this.receiver.refresh()
}
function AddMenuBarCommand(receiver) {
this.receiver = receiver;
}
AddMenuBarCommand.prototype.execute = function() {
this.receiver.add()
}
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addMenuBarCommand = new AddMenuBarCommand(SubBar);
function setCommand(button, command) {
button.onclick = function() {
command.execute();
}
}
setCommand(document.querySelector('.btn-1'), refreshMenuBarCommand)
撤销功能
提供一个undo方法,如小球移动,每次移动记录上一次位置,这样在undo方法中就能回到上一次位置。
重做功能
提供一个队列,将每次命令入队列,重做时出队
命令队列
组合模式
举例:宏命令对象和普通命令对象,采用深度优先遍历这些命令并execute
用途:
- 表示属性结构
- 利用对象多态性统一对待组合对象和单个对象
注意:
- 组合模式不是父子关系
- 对叶对象的操作一致性
- 双向映射关系
- 用职责连模式提高组合模式性能
好处:
- 透明,不用去估计树中组合对象和叶对象的区别
- 表示对象的部分-整体层次
模板方法模式
需要继承实现的模式,通过封装变化提高系统的扩展性
享元模式
用于性能优化,节省内存
关键点
把握好内部状态和外部状态来避免多次建立对象。
共享是重点,用时间来换空间
职责链模式
定义:
使多个对象都能处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。
Function.prototype.after = function(fn) {
var self = this;
return function() {
console.log(self)
console.log(fn)
var ret = self.apply(this, arguments);
if (ret === 'nextSuccessor') {
return fn.apply(this, arguments);
}
return ret;
}
}
function func1() {
console.log('1');
return 'nextSuccessor';
}
function func2() {
console.log('2');
return 'nextSuccessor';
}
function func3() {
console.log('3');
return 'nextSuccessor';
}
function func4() {
console.log('3')
}
func1.after(func2).after(func3).after(func4)();
基础知识
对模式的误解
- 很多类图看起来相似,要区分应结合相应的使用情景
面向对象的js
- js没有提供传统面向对象语言的类式继承,而是通过原型委托的形式实现对象与对象之间的继承
动态类型语言与鸭子类型
- 静态类型语言与动态类型语言的区别
- 静态类型语言: 在编译时就已确定变量类型,就能检查出类型不匹配的错误
- 动态类型语言:在被赋予值后才能确定变量类型,缺点:无法保证变量类型
- 鸭子类型
- 不用借助超类型的帮助,就能实现面向接口编程
- 一个对象只要拥有pop,push方法,就能被当成栈来使用,所以它实际是什么不重要,重要的是它能否实现某个接口方法
多态
- 概念:同一个操作作用在不同的对象时,可以产生不同的解释和执行结果
- 代码举例
var Duck = function () {
this.name = 'duck';
}
var Chicken = function () {
this.name = 'chicken';
}
function makeSound(animal) {
if (animal instanceof Duck) {
console.log(animal.name + ' duck')
}else if (animal instanceof Chicken) {
console.log(animal.name + ' chicken')
}
}
module.exports = {
makeSound: makeSound,
Duck: Duck,
Chicken: Chicken
}
- 背后的**就是将“不变的事物”和“可变的事物”区分开,遵循开放封闭的原则
对象的多态
var Duck = function () {
this.sound = function () {
console.log('duck')
}
};
var Chicken = function () {
this.sound = function () {
console.log('chicken')
}
};
function mekeSound (animal) {
animal.sound();
}
mekeSound(new Duck())
- 发出声音就是不变的事物,可以发出什么声音就是可变的事物
var Dog = function () {
this.sound = function () {
console.log('dog')
}
};
makeSound(new Dog());
- 使用继承实现多态
var Animal = function () {// 基类
this.sound = function () {
console.log(this.name)
}
}
var Duck = function () {
this.name = 'Duck'
};
var Chicken = function () {
this.name = 'chicken'
};
Duck.prototype = new Animal();
Chicken.prototype = new Animal();
var AnimalSound = function () {
this.makeSound = function (animal) {// 这儿的传参就能传不同实例
animal.sound();
}
}
var duck = new Duck();
var chicken = new Chicken();
var animalSound = new AnimalSound();
animalSound.makeSound(duck);
animalSound.makeSound(chicken);
- 应用, 地图api,show表示展示地图
var googleMap = {
show: function () {
console.log('google map');
}
};
var baiduMap = {
show: function () {
console.log('baidu map');
}
};
var renderMap = function (type) {
if (type === 'google') {
googleMap.show();
}else if (type === 'baidu') {
baiduMap.show();
}
}
renderMap('baidu');
// 但是这里type区分,明显感觉鸡肋,当多加一个sosomap的时候,需要再添加type
// 改进版:
var googleMap = {
show: function () {
console.log('google map');
}
};
var baiduMap = {
show: function () {
console.log('baidu map');
}
};
var sosoMap = {
show: function () {
console.log('soso map');
}
}
var renderMap = function (map) {
if (map.show instanceof Function) {
map.show();
}
}
renderMap(sosoMap)
// 在实际中不会是只有show方法的,这时候就需要适配器模式
封装
- 封装不只是数据层面的封装,应该理解为任何形式的封装,隐藏数据,隐藏细节,隐藏设计细节,隐藏对象的类型
- 有些语言有private,public关键字,而js只能模拟出这些情况,利用变量依赖的作用域来实现封装特性
var person = (function () {
let name = 'james';
return {
sayName: function () {
console.log(name);
}
}
})()
person.sayName(); // james
console.log(person.name);// undefined
原型模式和基于原型继承的js对象系统
- 使用克隆的原型模式
不关心对象的具体类型,找到一个对象,然后通过克隆来创建一个一摸一样的对象。但是这样,尽管功能都有,但是内部构造是不一样的,所以也不能称为一摸一样的对象
function Plane () {
this.blood = 'blood'
}
Object.create = Object.create || function (obj) {
var F = function () {};
F.prototype = obj;
return new F();
}
原型编码范型基本规则
- 所有数据都是对象
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
- 对象会记住它的原型
- 如果对象无法访问某个请求,就把这个请求委托给它自己的原型
js中的原型继承
- 一切皆为对象,除了基本类型,但是基本类型数据也可以通过“包装类”的方式变成对象类型
- 根对象是
Object.prototype
对象(null)
var obj1 = new Object();
var obj2 = {};
console.log(Object.getPrototypeOf(obj1) === Object.prototype);
console.log(Object.getPrototypeOf(obj2) === Object.prototype);
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
但是js并不关心克隆的细节,这是内部负责实现的
如何用new运算符从构造器中得到一个对象
function Person () {
this.name = 'lejunjie';
}
Person.prototype.sayName = function () {
console.log(this.name);
}
var person = new Person();
person.sayName(); // 'lejunjie'
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
console.log(person.__proto__ === Person.prototype); // true
在这里Person并不是类,而是一个构造器,当使用new的时候,实际也只是先克隆Obejct.prototype
4. 用__proto__来理解new运算
function Person (name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function createPerson () {
var obj = new Object();
var Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments); // 也就是执行Person,改变obj的name
return typeof ret === 'object' ? ret : obj;
}
var person = createPerson(Person, 'lejunjie');
person.sayName();
es6带来的class语法,背后仍是通过原型机制来创建对象
this, call, apply, bind
this
理解:this总是指向一个对象,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的
指向:
- 作为对象的方法被调用
如:
var obj = {
a: 1,
getA: function () {
console.log(this === obj);// true
console.log(this.a);// 1
}
}
obj.getA();
- 作为普通函数调用
如:
global.name = 'junjie';// node 环境
var myObject = {
name: 'le',
getName: function () {
return this.name;
}
}
var getName = myObject.getName;// 将一个函数赋值给一个变量,这时就相当于重新定义一个函数,函数作用域为全局作用域
console.log(getName())// junjie
另一种
window.id = 'div1';
document.getElementById('myDiv').onclick = function () {
console.log(this.id);// myDiv
var callback = function () {
console.log(this.id);// div1
}
}
//解决方案
window.id = 'div1';
document.getElementById('myDiv').onclick = function () {
var that = this;// 保存div节点的引用
var callback = function () {
console.log(this.id)// div1
}
}
注意:
在ECMAScript5的strict模式下,这种情况的this已经被规定不会指向全局对象,而是undefined
3. 构造器函数调用
- 如果return一个非对象或者非数组,则返回该构造器的实例
function Base () {
this.name = 'base';
return 'another'
}
Base.prototype.sayName = function () {
console.log(this.name)
}
var base = new Base();
console.log(base)
- 如果return 对象或者数组,则返回该对象或者该数组
function Base () {
this.name = 'base';
return {
name: 'another'
}
}
Base.prototype.sayName = function () {
console.log(this.name)
}
var base = new Base();
console.log(base)
- Function.prototype.call, Function.prototype.apply
动态改变this指向。
丢失的this
像这样把一个函数赋值给另一个函数,this指向是动态的,所以会改变
var obj = {
name: 'username',
sayName: function () {
console.log(this.name);
}
}
var sayName = obj.sayName;
sayName()
call和apply的区别
- 参数不同
第一个参数为this指向,之后的参数,call传入以逗号隔开的参数,apply传入数组 - 在严格模式下
函数体内的this是undefined
bind
返回一个改变了this指向的函数
function func () {
console.log(this.name)
}
var anotherFunc = func.bind({name: 'first'})
anotherFunc()// first
bind函数的实现:
注意
多次bind无效
Function.prototype.bind = function () {
var context = [].shift.call(arguments);
var args = [].slice.call(arguments);
var self = this;
return function () {
console.log(this);
console.log(context)
self.apply(context, args.concat([].slice.call(arguments)));
}
}
function say () {
console.log(this.name);
console.log(arguments)
}
say.bind({name: 'first'}, '1').bind({name: 'second'}, '2')('3');
你想多次bind?那么按照这样来把
let context = null
Function.prototype.bind = function () {
context = [].shift.call(arguments);
var args = [].slice.call(arguments);
var self = this;
return function () {
console.log(this);
console.log(context)
self.apply(context, args.concat([].slice.call(arguments)));
}
}
function say () {
console.log(this.name);
console.log(arguments)
}
say.bind({name: 'first'}, '1').bind({name: 'second'}, '2')();
闭包
概念: 指有权访问另一个函数作用域中的变量的函数
变量的作用域
所谓作用域,即指变量的作用范围。创建函数会创建函数作用域,搜索变量时外部无妨直接访问该函数内部的变量。在内部可以访问函数外部,与函数同级的变量
变量的生命周期
- 全局变量永久存在
- 无闭包情况,退出函数即销毁
- 有闭包情况,在另一个函数内部定义的函数会被包含函数的活动对象添加到它的作用域链中,直到匿名函数被销毁后,才会被销毁
应用场景
- 封装私有变量
- 解决for循环弹出索引的问题,如:
1.
var nodes = document.querySelectorAll("div");
for (var i = 0; i < nodes.length; i++) {
(function (i) {
nodes[i].onclick = function () {
console.log(i)
}
})(i)
}
2.
var arr = [1,2,3], funcs = [];
for (var i = 0, len = arr.length; i < len; i++) {
funcs[i] = ((i) => {
return () => {console.log(i)}
})(i)
}
- 延续局部变量的寿命
var report = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
}
}()
闭包与内存管理
- 把变量放在闭包中和放在全局作用域中,对内存方面的影响是一致的,并不能说成是内存泄露。
- 使用闭包容易形成循环引用。BOM和DOM对象时使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略
高阶函数
函数作为参数传递
- 回调函数
- Array.prototype.sort
函数作为返回值输出
- 判断数据的类型
var isType = function(type) {
return function(obj) {
return Object.prototype.toString.call(obj) === '[object ' + type + ']' ;
}
}
- getSingle
var getSingle = function(fn) {
var ret;
return function() {
return ret || (ret = fn.apply(this, arguments));
}
}
- 高阶函数实现AOP(面向切面编程)
作用:把一些跟核心业务逻辑模块无关的功能抽离出来,包括日志统计,安全控制,异常处理等
Function.prototype.before = function(beforeFn){
var self = this;
return function(arguments) {
beforeFn.apply(this, arguments);
self.apply(this, arguments);
}
}
Function.prototype.after = function(afterFn) {
var self = this;
return function() {
var ret = self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
}
}
function func() {
console.log('2')
}
func = func.before(() => {
console.log(1)
}).after(() => {
console.log(3)
})
func()
- 函数柯里化
Function.prototype.bind = function() {
let self = this;
let context = [].shift.call(arguments);
return function() {
self.call(context);
}
}
function func () {
console.log(this.name);
}
var func2 = func.bind({name:2});
func2()
- 函数节流
场景:频繁被调用,如resize,mousemove
原理:确保某时间内只调用一次
- 分时函数
适用于dom插入,分时一次性插入
- 惰性加载函数
// 惰性判断
var addEvent = function(elem, type, func) {
if (window.addEventListener) {
addEvent = function() {
elem.addEventListener(type, func, false);
}
} else if (window.attachEvent) {
addEvent = function() {
elem.attachEvent('on' + type, func);
}
}
addEvent()
}
// 每次判断
var addEvent = function(elem, type, func) {
if (window.addEventListener) {
return elem.addEventListener(type, func, false);
}else if (window.attachEvent) {
return elem.attachEvent('on' + type, func);
}
}
// 立即得到
var addEvent = (function(elem, type, func) {
if (window.addEventListener) {
return function() {
elem.addEventListener(type, func, false);
}
} else if (window.attachEvent) {
return function() {
elem.attachEvent('on' + type, func);
}
}
addEvent()
})()
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.