JavaScript设计模式(一)
什么是设计模式呢? 就是指对于类似的问题,我们可以用大致相同的思想、方法去解决之,而这种通用的思想、方法就是设计模式。学习设计模式可以帮助我们在遇到问题时迅速地搜索出一种清晰的思路来实现之。
第一部分: 面向对象的JavaScript
1. JavaScript是动态类型语言。
静态类型语言即强迫规定程序员在使用某个变量时先定义它的类型。而动态类型语言是在程序运行的时候,才会具有某个类型,不需要严格定义。显然,静态类型语言要求更严格,而动态类型语言却无法保证变量的类型。JavaScript就是这样的动态类型语言,它的好处是我们可以花更多的时间关注在逻辑上,而不是变量的定义上,代码会更加简洁。 静、动我们就可以理解为变量类型的静与动。
2. 面向接口编程和鸭子模型
JavaScript王国需要100只鸭子来组成合唱团,但最后只能找到99只, 而正巧发现一只鸡的叫声也是嘎嘎嘎,于是我们就把鸡也拉近了鸭子合唱团。 这就是说,我们只关注对象的行为,而不关注对象本身。
基于这种思想,比如一个对象若有push和pop方法,就可以把它当作栈来使用等等,这种思想的编程就是面向接口编程。
3.多态
多态的实际含义是: 同一个操作作用在不同的对象上面,可以产生不同的解释和不同的执行结果。 如下所示:
var makeSound = function(animal) {
if (animal instanceof Duck) {
console.log("嘎嘎嘎");
}
if (animal instanceof Chicken) {
console.log("咯咯咯");
}
} var Duck = function() {};
var Chicken = function() {}; makeSound(new Duck());
makeSound(new Chicken());
可以看出makeSound函数对于不同的输入就有不同的输出,这就是多态。
但是如果再添加一只狗呢? 我们不仅要在创建一个狗的构造函数,还要改变makeSound函数,即这样的可扩展性是十分糟糕的。
解决方法: 多态背后最重要的思想是将“做什么”和“谁去做以及怎么样去做”分离开来,也就是将不变的事物和可变的事物分离开来。
如下所示:
var makeSound = function(animal) {
animal.sound(); // 做什么
} var Duck = function() {}; // 谁去做
Duck.prototype.sound = function() {
console.log("嘎嘎嘎"); // 怎么做
}; var Chicken = function() {};
Chicken.prototype.sound = function() {
console.log("咯咯咯");
}; var Dog = function() {};
Dog.prototype.sound = function() {
console.log("汪汪汪");
}; makeSound(new Duck());
makeSound(new Chicken());
可以看出在上面的例子中不变的部分就是animal.sound(),我们将之分离出来(做什么)。 然后再将谁去做,怎么做分离出来,这样函数的可扩展性就非常好了。
4. 封装
封装的思想在于隐藏内部的实现、 提高代码的可重用性、 封装变化。
5.原型模式
在以类为中心的面向对象变成语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建。
但是在原型编程的思想中,类不是必须的,对象未必必须从类中创建而来,一个对象是通过克隆另外一个对象而得到的。
第二部分:this、 call、 apply
我在《JavaScript函数之美~》中详尽的介绍了this的用法。这里还是要提及一些重点。
我们知道:this总是指向一个对象,也许是window对象,也许是调用它所在的方法的对象,也有可能是新创建的一个对象,具体执行的对象是运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
情况一:作为对象的方法调用,this指向的是该对象。
情况二:作为普通函数调用,而非对象的方法,this指向的全局对象window。
情况三:构造器调用,那么就指向这个新创建的函数。
情况四:Function.prototype.call或Function.prototype.apply调用。
注意点一: 在一个对象中,如果存在回调函数,我们还想this存在,该怎么办?
可以把 this 在对象的环境中赋值给 that,然后使用that,这时就有正确的指向了。
注意点二: 构造器调用的过程中,如果最后返回了对象,那么this就会指向这个返回的对象。如下:
var MyClass = function() {
this.name = "zzw";
return {
name: "htt"
};
}
var person = new MyClass();
console.log(person.name); // htt
如果说这里构造函数并没有返回一个对象,那么最终的结果一定是zzw,但是如果返回了对象,那么这个this指向的一定是这个返回的对象。
注意点三: docuemnt.getElementById()方法需要用到this
var getId = function(id) {
return document.getElementById(id);
}
getId("div").style.color = "red";
这样我们就可以成功获取到id为div的元素。但是:
var getId = document.getElementById;
getId("div").style.color = "red";
这样就会报错。
这时因为用getId来引用document.getElementById()之后,再调用getId,此时就变成额普通函数调用,内部的this就指向了window而不是document。(许多引擎的document.getElementById方法的内部实现需要用到this)。
在JavaScript版本的设计模式中,call和apply方法都是很常用的,能熟练应用这两个方法是我们真正成为一名JavaScript程序员的重要一步。
Function.prototype.apply 和 Function.prototype.call 显然都是被一个函数(Function)调用的。
其中apply接受两个参数,第一个参数指定了函数体内this对象的指向(之前说过,this总是指向一个对象),第二个参数是一个到右下标的集合,这个集合可以是数组,也可以是类数组,apply把这个集合中的元素作为参数传递给被调用的函数。 而call也接收两个参数,同样的,第一个参数指定了函数体内的this对象的指向,第二个参数是这个函数需要接受的参数(没有call和apply函数也要接收参数啊!)。举例如下:
var myObject = {
c:
}; var anotherObject = {
c:
} var c = ; function outputNum(a, b) {
var c = ;
console.log([a, b, this.c]);
} function outputNumSecond(a, b) {
c = ;
console.log([a, b, this.c]);
}
outputNum(, ); // [1, 2, 233]
outputNum.apply(null, [,]); // [1, 2, 233]
outputNum.apply(window, [,]); // [1, 2, 233]
outputNum.call(null, , ); // [1, 2, 233]
outputNum.call(window, , ); // [1, 2, 233]
outputNum.apply(myObject, [, ]); // [1, 2, 66]
outputNum.apply(anotherObject, [, ]); // [1, 2, 88]
outputNumSecond.apply(null, [, ]); // [1, 2, 10]
如果第一个参数是null,那么this就会指向默认的宿主对象,在浏览器中就是window。 但是在严格模式下, 函数体内的this还是为null。
常见错误
document.getElementById("div").onclick = function() {
alert(this.id); // div
var func = function() {
alert(this.id); //undefined
}
func();
}
这是因为func函数中的this指向的是window,而window是没有id的。 如果想要得到正确的结果,我们只需要使用apply或call改变this的指向。
document.getElementById("div").onclick = function() {
alert(this.id); // div
var func = function() {
alert(this.id); // 两次都是div
}
func.call(this);
func.apply(this);
}
这样,我们就可以得到想要的结果了。
第三部分:闭包和高阶函数
因为在JavaScript版本的设计模式中,许多模式都可以使用闭包和高阶函数来实现,所以这里重点强调闭包和高阶函数的使用。
一:闭包
闭包对于JavaScript程序员来说,是一个难点但又是必须征服的知识点。下面介绍一些与闭包相关的知识点和闭包的应用
1. 变量的作用域
变量的作用域,就是指变量的有效范围,我们最常谈到的就是在函数中声明的变量作用域。 在JavaScript中,函数可以用来创造函数作用域,此时的函数像一层半透明的玻璃,在函数里面可以看到外面的变量,在函数外面却无法看到函数里面的变量。
2.变量的声明周期
除了变量的作用域外,另一个跟闭包有关的就是变量的声明周期,对于全局变量而言,它的声明周期当然是永久的,除非我们主动销毁这个全局变量,而对于在函数内使用var关键字声明的局部变量而言,当退出函数时,这些局部变量就失去了他们的价值,他们都会随着函数调用的结束而被销毁。
举例如下:
var func = function() {
var a = ;
console.log(a); //
}
func();
函数func在被调用之后, a 就没有存在的价值了,所以a会随着函数调用的结束而被销毁。
var func = function() {
var a = ;
return function() {
a++;
console.log(a);
}
}
var f = func(); // func函数被调用,并将返回的匿名函数赋值给f。
f(); //
f(); //
f(); //
f(); //
在这里我们可以看出,虽然func()已经被调用,将道理其中的变量a也应该被销毁了,但是我们后面调用f函数时,发现还是可以引用上a的,说明a并没有被销毁,也就是说,闭包的存在使得局部变量在调用结束之后有了不被销毁的理由!!!
实用的例子!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>this</title>
</head>
<body>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<script>
var divs = document.querySelectorAll(".div"),
len = divs.length,
i;
for (i = ; i < len; i++ ) {
divs[i].onclick = function() {
console.log(i); // 全部是5
}
}
</script>
</body>
</html>
上述代码中,我的本意是当我点击不同的div时,得到其相应的index,但是最后发现我们得到的永远都是5, 这是因为最后点击时,for循环早已结束,而这时的i就已经变成 5 了。
解决方法: 形成闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>this</title>
</head>
<body>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<div class="div">我是一个div</div>
<script>
var divs = document.querySelectorAll(".div"),
len = divs.length,
i;
for (i = ; i < len; i++ ) {
(function(i) {
divs[i].onclick = function() {
console.log(i); // 分别得到 0 1 2 3 4
}
})(i)
}
</script>
</body>
</html>
这时我们发现得到的就都是相应的index值了。
原理: 每次循环,都形成了一个匿名函数, 给匿名函数传递参数i,并立即调用,这样就形成了块级作用域,而里面的onclick后面的函数就是闭包了,i值就是这个块级作用域的私有变量。
第一个i是传递进去的参数,这样才能分别保留每一个i值,必须要有; 最后的i是调用函数, 也必须有。
3. 闭包的更多用途
(1). 封装变量
(2). 延续局部变量的寿命
二、 高阶函数
高阶函数是指至少满足下列条件之一的函数:
函数可以作为参数被传递
函数可以作为返回值输出
在JS中的函数显然是满足高阶函数的特点的。
1. 函数作为参数被传递
回调函数
在ajax异步请求中,回调函数的使用就非常的频繁,不做过多说明
还有就是sort方法。
console.log([,,,].sort(function(a, b){
return a - b;
})); //[3,15,33,58]
2. 函数作为返回值输出
3. 高阶函数的其他应用 --- 函数柯里化
即currying,又称为部分求值。 一个currying的函数会首先接收一些参数,在接收了参数之后并不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来,等到真正需要求值的时候,之前传入的所有参数就会被一次性用于求值。举例如下:
var monthlyCost = ; var cost = function(money) {
monthlyCost += money;
}; cost(); // 第一天的开销
cost(); // 第二天的开销
cost(); // 第三天的开销
// cost(700); // 第三十天开销
alert( monthlyCost );
这里它每天都记录花了多少钱,并计算一共花了多少,但是我们其实并不太关心截至今日一共花了多少,而是关心30天一共花了多少, 所以每天都进行一次计算是浪费的。
如果我们可以在每个月的前29天只是保存当天的开销,知道第30天才开始计算一共的开销不就可以减少计算量了吗? 这样的思路就是currying的实现思路。
var currying = function( fn ) {
var args = []; return function() {
if ( arguments.length === ) {
return fn.apply( this,args );
} else {
[].push.apply( args, arguments );
return arguments.callee;
} }
}; var cost = (function() {
var money = ; return function() {
for ( var i = , l = arguments.length; i < l; i++ ) {
money += arguments[i];
}
return money;
}
})(); var cost = currying( cost );
cost();
cost();
cost(); alert(cost());
上面的过程即完成了一个currying函数的编写,当调用cost()时,如果明确带上了一些参数,表示此时并不进行真正的求值计算,而是把这些参数保存起来, 此时让cost函数返回另一个函数,只有不带参数的形式执行cost()时,才利用前面保存的所有参数,真正开始进行求值计算。
JavaScript设计模式(一)的更多相关文章
- 《JavaScript设计模式 张》整理
最近在研读另外一本关于设计模式的书<JavaScript设计模式>,这本书中描述了更多的设计模式. 一.创建型设计模式 包括简单工厂.工厂方法.抽象工厂.建造者.原型和单例模式. 1)简单 ...
- 《JavaScript设计模式与开发实践》整理
最近在研读一本书<JavaScript设计模式与开发实践>,进阶用的. 一.高阶函数 高阶函数是指至少满足下列条件之一的函数. 1. 函数可以作为参数被传递. 2. 函数可以作为返回值输出 ...
- Javascript设计模式学习一
学习Javascript设计模式之前,需要先了解一些相关知识,面向对象的基础知识.this等重要概念,以及掌握一些函数式编程的技巧. Js多态 多态的思想:实际上是把“做什么”和“谁去做”分离开来.例 ...
- javascript设计模式实践之职责链--具有百叶窗切换图片效果的JQuery插件(三)
在上一篇<javascript设计模式实践之模板方法--具有百叶窗切换图片效果的JQuery插件(二)>里,通过采用模板方法模式完成了切换效果对象的构建编写. 接下来就是完成各效果对象的调 ...
- javascript设计模式实践之模板方法--具有百叶窗切换图片效果的JQuery插件(二)
在上一篇<javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)>里,通过采用迭代器模式完成了各初始化函数的定义和调用. 接下来就要完成各个切换效果的编 ...
- javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)
类似于幻灯片的切换效果,有时需要在网页中完成一些图片的自动切换效果,比如广告,宣传,产品介绍之类的,那么单纯的切就没意思了,需要在切换的时候通过一些效果使得切换生动些. 比较常用之一的就是窗帘切换了. ...
- 常用的Javascript设计模式
<parctical common lisp>的作者曾说,如果你需要一种模式,那一定是哪里出了问题.他所说的问题是指因为语言的天生缺陷,不得不去寻求和总结一种通用的解决方案. 不管是弱类型 ...
- Javascript设计模式(摘译)
说明: 未完成...更新中.... 一.javascipt设计模式分类 设计模式分类有很多标准,最流行的三种如下 1) creational -- 主要关注对象创建 Creational des ...
- JavaScript设计模式学习笔记
1 JavaScript设计模式深入分析 私有属性和方法:函数有作用域,在函数内用var 关键字声明的变量在外部无法访问,私有属性和方法本质就是你希望在对象外部无法访问的变量. 特权属性和方法:创建属 ...
- JavaScript设计模式:读书笔记(未完)
该篇随我读书的进度持续更新阅读书目:<JavaScript设计模式> 2016/3/30 2016/3/31 2016/4/8 2016/3/30: 模式是一种可复用的解决方案,可用于解决 ...
随机推荐
- 自定义beans.xml文件实现Spring框架
经过一天的补习,学习文件加载,java反射,JDom等知识,到了晚上终于能够搭出一个基于配置文件的简单spring框架实现! 首先我们先看看这个问题: 下面是两副图左边是项目结构图,右边是UML图: ...
- ZSTU4269 买iphone 2017-03-22 14:31 73人阅读 评论(0) 收藏
4269: 买iphone Time Limit: 3 Sec Memory Limit: 128 MB Submit: 1710 Solved: 316 Description 自从上次仓鼠中了 ...
- Android-解析JSON数据(JSON对象/JSON数组)
在上一篇博客中,Android-封装JSON数据(JSON对象/JSON数组),讲解到Android真实开发中更多的是去解析JSON数据(JSON对象/JSON数组) 封装JSON的数据是在服务器端进 ...
- Centos 下部署tomcat多实例
基础环境及JDK就不多说了,下面的目录结构以如下为准: 根目录-apps 根目录-apps--tomcat 根目录-apps--ins1 根目录-apps--ins2 ================ ...
- .NET框架源码解读之准备CLR源码阅读环境
微软发布了CLR 2.0的源码,这个源码是可以直接在freebsd和windows环境下编译及运行的,请在微软shared source cli(http://www.microsoft.com/en ...
- LRUCache c#
LRUCache是Least Recently Used 近期最少使用算法的缓存,是android提供的一个缓存工具类.可以以两种排序方式来输出缓存,一种是按插入顺序输出,一种是按最近最少方式输出,最 ...
- update sharepoint 2013 cu error
1. 安装过程合理: A. 可以同时在管理中心.两台前端.搜索服务器上安装重新发布的SP1补丁包(所提供的链接) B. 等待所有SP1补丁包安装完成,依次在管理中心.两台前端.搜索服务器上运行配置向导 ...
- Syncthing源码解析 - 源码目录说明!
Syncthing是一个免费开源的p2p软件,Go语言编写的! 官网:https://syncthing.net/ 源码:https://github.com/syncthing/syncthing/ ...
- 使用java中for循环,循环打印出五角星--
//5.0 输出五角星 int touHigh = 6; int jianHigh = 25 ; int kuang =50; for (int ...
- 关于OI中简单的常数优化
有些东西借鉴了这里qwq 1.IO(istream/ostream) 输入输出优化 之后能,在赛场上常见的几种输入输出: 输入: $1.cin$ 呵呵,不说什么了,慢的要死.大概$1e8$个数要读1分 ...