《JavaScript模式》读书笔记html
模式是针对广泛问题的解决方案。更进一步地说,模式是解决一类特定问题的模版。node
在软件开发过程当中,模式是指一个通用问题的解决方案。 一个模式不只仅是一个能够用来复制粘贴的代码解决方案,更多地是提供一个更好的实践经验、有用的抽象化表示和解决一类问题的模版。关键词: 表示、解决 程序员
学习模式的好处:
web
三种类型的模式:正则表达式
对象有两种类型设计模式
全局变量的问题: 1. 全局变量会在整个JavaScript应用或web页面内共享。 他们生存在同一个命名空间内,总有可能发生命名冲突。尤为是一个应用程序中两个独立的部分(如两个js文件)定义了同名的全局变量,但却有不一样目的时。 而咱们只要减小全局变量的数目,就能够减小发生冲突的可能性。数组
使用var 定义的全局变量不能够删除; 不使用var的全局变量能够删除。说明,使用var 定义就是一个变量, 不使用var定义就是一个对象的属性。
浏览器
2. 单一var模式缓存
即便用一个 var 声明多个变量。安全
优点: 声明为局部变量、 更少的代码、 更简洁的操做。
考虑下面的代码:
log(c); var c = 1000; log(c);
第一个log输出 undefined; 第二个log输出 1000;
上面的代码等同于:
var c; log(c); c = 1000; log(c);
由于开始c只是声明了,没有初始化。在 c = 1000;才初始化。
for循环主要用于两种状况,遍历 数组 和 类数组对象(HTML容器对象)的。
数组就是Array对象, var arr = [1, 2, 3, 4]; 这就是一个数组。
而HTML容器对象是DOM方法返回的对象,如: document.getElementByName() document.getElementsByClassName() document.getElementsByTagName()都是 dom方法返回的HTML容器对象。
HTML容器对象的麻烦之处在于: 他们都是document(HTML页面)下活动的查询。也就是说每次再访问任何容器的长度时,都是在查询活动的DOM,而且DOM是很是耗时的。
因而,下面的循环是不可取得:
for (var i = 0; i < myArray.length; i++) { // 对myArray进行操做 }
若是myArray是一个数组还好,每次读取它的长度耗时并非很严重。
可是若是myArray是一个HTML容器对象(类数组对象),那么操做DOM就会很是耗时。
好的方法:
for (var i = 0, len = myArray.length; i < len; i++ ) { // 对myArray进行操做 }
这种方法里,咱们现将myArray.length缓存了起来,这样在后续的循环过程当中, 就不会每次循环都读取length,进而减小了大量的DOM访问,这样的方法在safari中速度提升两倍,在IE7中速度提升170倍。
刚刚提到,for循环主要是用于遍历数组和类数组对象的(通通与数组有关),而for-in循环主要是用于遍历一个普通对象的属性的(与数组无关)。
虽然,有时候混用是可行的,可是这样咱们的代码逻辑就会大大下降。
在使用 for 循环时,咱们最好使用 hasOwnProperty()方法。
增长构造函数的原型属性是一个加强功能性的强大方法,可是这样会严重影响可维护性,由于你的同伴每每但愿内置的JavaScript的方法使用是一致的,而不指望您增长本身的方法。
可是若是咱们发现 将来的ECMASCRIPT版本将某一方法做为统一的实现,只是如今还有一些不支持时,咱们就能够本身添加构造函数上的方法。 另外,若是其余地方已经支持了此方法,您这不支持,也能够添加。
为原型添加自定义的方法:
if (typeof Object.prototype.myMethod !== "function") {
Object.prototype.myMethod = function () {
// implementation...
};
}
7. 避免使用隐式类型转换
咱们应当尽可能采用 == 而不是 === 来判断是否相等。
咱们认为使用 == 来判断是否相等就是反模式(可能产生的问题比解决的问题要多), 而 === 是一种更为标准的模式。
其实这个仍是很好理解的,由于大多数时候,咱们不多见到使用eval()方法的状况。
而且记住:eval()是一个魔鬼
为何? 由于eval()能够将任意字符串看成一个JavaScript代码来执行。
大多数状况下,parseInt顾名思义就是将接收的参数解析出整数, 通常,咱们可能给传递一个字符串,可是,最佳实践是传递第二个参数表示把这个字符串中的数字当作几进制。ES3和ES5的表现是不一致的。
下面两个函数一个是函数声明的方式定义,另外一个是匿名函数的形式定义,注意空格的区别所在:
function addTwoNumber() { return 10 + 20; } var addTwoNumber = function () { return 10 + 20; }
这即是最好的空格的保留习惯 --- 在函数声明时,函数名和圆括号之间没有空格; 在匿名函数时, function 与 ()之间是有一个空格的。
举例来讲, 若是有一个名为reverse()的函数, 该函数能够将字符串翻转过来,该函数有一个字符串参数并返回另外一个字符串,那么能够按照以下的方式来记录文档:
/** * 翻转一个字符串 * * @param {String} 输入须要翻转的字符串 * @return {String} 翻转后的字符串 */ var reverse = function () { //... return ouput;
};
12. 注意一个变量若是没有使用var,那么它将成为全局的,对于函数也是同样的,一个函数在定义时没有使用var,那么它也将是全局的,由于函数名就是一个指针,指针就是变量。
1. 相对于使用构造函数建立对象,咱们更倾向于使用对象字面量的方式建立对象。
缘由有二:
(1). 字面量表示法的显著优势在于它仅须要输入更少更短的字符,这是一种优美的对象建立方式
(2). 与使用object构造函数相对, 使用字面量的另外一个缘由在于它并无做用域的解析。由于可能以一样的名字建立了一个局部构造函数,解析器须要从Object()的位置开始一直向上查找做用域链,直到发现全局object构造函数,可是对象字面量的方法就不会有这样的解析过程。
2. 相对于使用 new Array() 方式建立数组,咱们更倾向于使用数组字面量的方式建立数组。
JavaScript中的数组也是对象, 虽然能够经过 new Array() 来建立,但这不是咱们所推荐的。
缘由有二:
(1). 数组字面量表示法简单、明确、优美。
(2). 使用 new Array() 会出现陷阱 --- 如 var arr = new Array(3); 这里的3是数组的长度, 而这个数组中却什么都没有。 又如使用 var arr = new Array (3.14); 那么就会报错,由于3.14不是合法的长度。
注: 通常状况下, 为了将数组和通常的对象区分开,咱们能够检测它是否具备length属性和slice()方法,有的就是数组,不然是对象。另外ES5中出现了Array.isArray(arr)方法,若是arr是数组,则返回true,不然返回false。
上面的说法是错的! 由于不只仅 对象中的Array又length属性, 对象中的 Function也是具备length属性的。Function的length属性时这个函数指望接收的参数的个数!!!
3. 相对于使用 new RegExp() 构造函数的方法建立正则表达式, 更但愿是正则表示式字面量的方法。
如 var re = /ad\\f/gm; 是字面量的方法。 var re = new RegExp("ad\\\\f","gm"); 是构造函数的方式。
缘由有二(实则是一):
(1). 使用字面量的方式看上去就很简洁。
(2). 能够看到若是想要匹配 \ , 在字面量中须要使用 \ 来转义,可是在构造函数中须要用四个才能达到相同的效果。
4. 对于String、Number、Boolean,咱们尽可能使用简单形式,而不要用包装对象。
var a = "zzw"; 就是简单的形式,而 var a = new String("zzw"); 就是复杂的使用了包装对象的形式。 二者的区别有二:
1. 前者使用typeof判断获得的是 string ,然后者使用 typeof 判断获得的是 obect。
2. 二者均可以只用方法,只是前者可用的方法存在的声明周期更短, 然后者会始终存在。
5. 错误对象
try { 什么 throw { name: "myError", message: "some error!", } } catch (e) { alert(e.name); }
如上所示: 若是出现了错误,咱们在throw 就抛出来这个错误对象, 而后再catch。
注意:一个程序中最好要有 try 和 catch 的语句!
总结:
在通常状况下, 除了Date()构造函数之外,不多须要其余的内置构造函数,而是使用字面量的方法去建立。
a 函数是对象中的第一等公民。
b 函数能够提供做用域。
(a)函数固然是对象,由于咱们能够用new Function()来建立对象,其次函数有本身的属性(name)和方法(toString),最后函数能够做为参数向其余的函数传递。注:虽然函数能够用构造函数的方法建立,可是不推荐,由于咱们得传递字符串,这和eval()同样糟糕。
(b)在js中,只有函数能够提供做用域,而其余如if、while等有{}的都不能提供,由于在js中,没有块级做用域这一说。
(a) 函数声明
function add () { return 1+2; }
注意:函数声明能够将函数的定义提高到其所在做用域的顶部。
(b) 命名函数表达式
var add = function addSomething() { return 1+2; }
注意:前者和后者是能够相同也能够不一样的, 若是调用其name属性能够获得addSomething而不是add,另一个好处是用于求阶乘的时候(参看《JavaScript函数之美》),命名函数表达式无提高效果。
(c) 匿名函数表达式
var add = function () { return 1+2; }
注意: 无提高效果。且函数.name是空的字符串。
(a) 函数声明只能出如今“程序代码”中, 这表示它仅能在其余函数体的内部或全局空间中。
正确区分下面的几种形式是颇有必要的。
callMe(function () { // 刚刚说了,函数声明只能出如今“程序代码”中,显然这里是匿名函数表达式。 }); callMe(function add() { // 一样,函数声明不可能做为一个参数,这里是命名函数表达式 });
var myObject = {
say: function () {
// 显然这里是匿名函数表达式
}
}
(b) 在不使用函数声明的时候,咱们推荐使用匿名函数表达式。
var foo = function bar() {};
虽然这个在语法上没有什么问题,可是表达起来不够方便,且在IE中可能会出现问题。
什么使回调函数?当把一个函数的引用 看成一个参数传递给另外一个函数时,另外一个函数在执行过程当中可能会执行这个函数,那么这个函数就成为了回调函数,又称回调。
看下面的这个例子(很是重要!)
大意: 先找到所需的nodes,而后隐藏这些nodes。 咱们能够用两个独立的函数完成这个任务 --- 这样代码可重用!
var findNodes = function () { var nodes = [], found, status = true; for (var i = 0; i < 1000; i++) { // 复杂的逻辑 if (status) { nodes.push(found); } } return nodes; }; var hide = function (nodes) { var i = 0, max = nodes.length; for (var i = 0; i < max; i++) { nodes[i].style.display = "none"; } } hide(findNodes());
存在的问题:1. 虽然这是两个函数,能够保证代码的重用,可是咱们发现每一个函数都要进行一个循环 --- 很消耗性能。
2. 注意: findNodes()并非回调函数,由于咱们能够看到 传入的是一个结果,而不是函数的引用。
由于屡次循环消耗性能,咱们能够将隐藏的步骤放在findNodes里啊, 问题是: 若是这样,咱们就不能重用代码了。
解决方法: 将hide函数做为回调函数传入findNodes中。以下:
var findNodes = function (callback) { var i = 1000, nodes = [], found; if (typeof callback !== "function") { callback = false; } while (i) { i -= 1; // 复杂的逻辑 if (callback) { callback(found); } nodes.push(); } return nodes; }; //回调函数 var hide = function (node) { node.style.display = "none"; } findNodes(hide);
这样问题就解决了 --- 两个函数既能够重用, 还无需屡次循环消耗性能。
虽然在不少状况下这种方法都是简单且有效的,可是也常常存在一些场景,其回调并非一次性的匿名函数或者全局函数(其中若是有this就会指向全局),而是对象的方法(那么它的this就会指向对象),这时若是做为回调函数被应用时,由于函数的特殊之处(函数时存在堆里的),这时this就会指向全局, 不会指向对象,就会出错。 方法: 使用call或者apply进行绑定this到对象上。
另外异步事件监听器也是回调函数的模式。
document.addEventListener("click", console.log, false);
其中console.log就是回调函数。
另外超时也是回调函数的模式。
var add = function () { return 1+3; }; setTimeout(add, 100);
其中setTImeout的第一个参数传递的是函数的引用(不带圆括号),若是带上圆括号,传递的就是一个结果了。
(注意: 咱们知道传递回调函数的引用的目的是在这个函数中调用这个回调函数,因此能够猜想setTimeout这个函数的内部必定有调用add这个函数的语句!)
库中的回调模式
若是咱们但愿函数的重用,咱们就要考虑回调模式。
什么是自定义函数?
函数能够动态定义(即函数声明的方式),也能够分配给变量(即匿名函数表达式的方式)。 若是建立了一个新函数而且将其分配给保存了另外函数的同一个变量,那么就以一个新函数覆盖了旧函数,这一切发生在旧函数体的内部。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>callback</title> </head> <body> <script> var selfDefine = function () { console.log("good"); selfDefine = function () { console.log("better"); } } selfDefine(); //good selfDefine(); //better selfDefine(); //better selfDefine(); //better selfDefine(); //better </script> </body> </html>
这就是自定义函数, 咱们能够看到虽然五次调用了一样的函数,但第一次的却不同,这是由于在第一次调用函数的时候, 咱们将这个函数名(指针)指向了一个新的函数,因为没有使用var, 因此这个函数就是全局的,便覆盖了第一次的函数。
可是若是咱们使用var,那么这个就不会覆盖了,也就不是自定义函数了,
var selfDefine = function () { console.log("good"); var selfDefine = function () { console.log("better"); } } selfDefine(); //good selfDefine(); //good selfDefine(); //good selfDefine(); //good selfDefine(); //good
这是由于咱们每次再调用的时候,在函数内部的同名函数做为外部函数的私有函数,根据做用域链的规则,显然咱们是不能访问到里面的函数的。
优势: 当您的函数有一些初始化工做要作,而且只须要执行一次,那么这种模式就很是有用。 由于并无理由去执行本能够避免的重复工做,即该函数的一些部分可能并再也不须要(很是好)。
即时函数模式(Immediate Function Pattern)是一种能够支持在定义函数后当即执行该函数的语法。
思考: 既然你这个函数在定义的时候就执行了,那么你直接写成一堆语句不就好了,干吗非要写成即时函数的形式呢?
回答: 说得好! 即时函数的惟一目的就是模仿块级做用域!
我: 嗯? 也不彻底对,后面我来告诉你!
(function () { alert("good"); }());
这就是一个即时函数。下面的替代方法也是很常见的。
(function () { alert("good"); })();
其实即便函数的目的不只仅是模仿块级做用域!由于即便函数也能够传递参数啊!以下:
(function (who, when) { // 能够看到和变量相接的字符串咱们都使用一个空格来空开,这一点意识到是很是重要的。 console.log("I met " + who + " on " + when); }("zzw", "now")); // I met zzw on now
通常来讲,不该该传递过多的参数到即时函数中,由于这样将迅速成为一种阅读负担!
即便函数的返回值:
var str = (function (who, when) { // 能够看到和变量相接的字符串咱们都使用一个空格来空开,这一点意识到是很是重要的。 return "I met " + who + " on " + when; }("zzw", "now")); console.log(str); // I met zzw on now
咱们能够看到这里即时函数的返回值赋值给了 str ,很是有效。
在即时函数中定义的变量将会是用于自调用函数的局部变量,而且不会担忧全局空间被临时变量所污染。
注意: 即时函数的其余名称包括 “自调用”以及“自执行”函数,由于该函数在定义以后当即执行。
可使用下面的模版来定义一个功能,让咱们称之为module1:
// 文件module.js中定义的模块 module1 (function () { //模块1中的全部代码... }());
遵循这个模版,能够编码其余的模块,而后,当将这些代码发布到在线站点时,能够决定哪些功能应用于黄金时间,而且使用构建脚本将对应文件合并。
注意: 好比在开发一个较大的网站时,咱们每每须要引入一些通用的js文件,这时咱们就能够将这些通用的文件使用即时函数封装起来,这同时也遵循咱们以前所讲的尽可能减小全局变量的原则。
这种模式使用带有init()方法的对象,该方法在建立对象以后将会当即执行,init函数须要负责全部的初始化任务。
下面是一个即时对象模式的实例:
({ // 在这里能够定义设定值 // 又名配置常数 maxWidth: 600, maxHeight: 800, // 还能够定义一些实用的方法 gimmeMax: function () { return this.maxWidth + "x" + this.maxHeight; }, // 初始化 init: function () { console.log(this.gimmeMax()); // 更多初始化任务 } }).init();
这里是使用对象字面量建立了一个对象,而后用括号包裹起来是由于这样就肯定是一个对象了,而不是相似与if和while的代码块, 对象一旦建立就会初始化。
配置对象模式是一种提供更简洁的API的方法,尤为是在创建一个库或者是任何将被其余程序使用的代码的状况。
举例说明:
想象一下, 若是正在编写一个名为addPerson()的函数, 该函数接收人员的名和姓参数,而且将这我的添加到列表中,可使用:
function addPerson(first, last) { //... }
后来,了解到实际上还要存储人员的出生日期、以及可选的性别和住址等信息,所以,能够修改函数并添加新的函数信息(将可选参数放在末尾),
function addPerson(first, last, dob, gender, address) {};
在这一点上, 该函数的参数列表就变得有点长,而后,知道须要添加一个用户名,而且这是绝对必要的而非可选的,如今的函数调用者必须传递参数,并且可选参数也要传递,同时还要注意不要混淆了参数的顺序。
addPerson("zzw", "wadf", new Date(), null, null, "batman");
使用者须要传递大量的参数并非很方便,一个更好地办法就是仅仅使用一个参数对象来替代全部的参数,让咱们称该参数为conf, 也就是配置的意思。
addPerson(conf);
而后,该函数的使用者能够这么作:
var conf = { username: "batman", first: "Bruce", last: "Wary" }; addPerson(conf);
这就是配置对象了,配置对象的优势在于:
而配置对象的不利之处在于:
须要注意的地方:
注:这一部分一直不是很理解,但愿之后真正须要用到的时候能够边学边用。
前言:
JavaScript是一种简洁明了的语言,其中并无在其余语言中常用的一些特殊的语法结构,好比命名空间(namespace)、模块(module)、包(package)、私有属性(private property)、以及静态成员等语法。
本章节会经过一些常见的模式来实现、替换那些语法特征,或者仅仅以不一样于那些语法特征的方式来思考问题。
JavaScript并无内置命名空间,命名空间(namespace)有助于减小程序中所须要的全局变量的数量。 它的用途和使用即时函数有类似之处,都是为了减小全局变量的数量, 由此带来的好处就是有助于避免命名冲突或着过长的名字前缀。
补充:显然,过长的名字前缀也是一种避免冲突的方法,可是这种方法并很差。
使用js模仿命名空间仍是很简单的。
以下面的例子:
// 警告:反模式 // 构造函数 function Parent() {}; function Child() {}; // 一个变量 var some_var = 1; // 一些对象 var module1 = {}; module1.data = { a: 1, b: 2 }; var module2 = {};
上面的代码不是不能够这么写,可是坏处在于: 该模块的变量和引入的其余模块的变量颇有可能会冲突,或者全局变量愈来愈多的时候,自身也会引起冲突。
能够经过为应用建立一个全局对象这种方式来重构上面的代码,好比建立全局对象MYAPP,而后改变全部的函数和变量以使其成为您的全局对象的属性。
// 全局变量,最好只有一个 var MYAPP = {}; // 构造函数 MYAPP.Parent = function () { }; MYAPP.Child = function () { }; // 一个变量 MYAPP.some_var = {}; // 一个容器对象 MYAPP.modules = {}; // 嵌套对象 MYAPP.modules.module1 = {}; MYAPP.modules.module1.data = { a: 1, b: 2 }; MYAPP.modules.module2 = {};
这就是 模仿命名空间 的模式,这里在这个项目上,只有一个全局变量MYAPP,所有大写是由于 一般程序员都会根据公约以所有大写的方式来命名全局变量,故全局变量是很是引人注目的。(别忘了,通常状况下一个常量也是使用这种方式来命名的)
主要使用场景: 在引入第三方库的时候,好比js和窗口widget的冲突,强烈推荐使用这种方式。
缺点(从强哥的APP.js就能够看出来): 1. 须要输入更多的字符,每一个变量和函数都要加前缀,整体上增长了须要下载的代码量。 2. 仅有一个全局实例意味着任何部分的代码均可以修改全局实例。 3. 长嵌套名字意味着更长(更慢)的属性解析查询时间。
这一部分不作过多的介绍,只说重点的部分。
咱们知道:
因为程序复杂性的增长、代码的某些部分被分隔成了不一样的文件,以及使用添加包含语句等多个因素,仅假设您的代码是第一个定义某个命名空间或者它内部的一个属性(也许实际上不是的),这种作法就会致使覆盖以前的命名空间,或者本身的命名空间被覆盖 --- 总之,这都是不安全的。 所以,在添加一个属性或者建立一个命名空间以前,最好是先检查它是否已经存在,以下所示:
// 不安全的代码 --- 可能会有冲突 var MYAPP = {}; // 更好的代码风格1 if (typeof MYAPP === "undefined") { var MYAPP = {}; } // 更好的代码风格2 if (typeof MYAPP !== "object") { var MYAPP = {}; } // 或者使用更短的语句 var MYAPP = MYAPP || {};
这都只是咱们给的名称, 私有成员通常指对象的属性在外部不能访问,特权方法通常指对象的方法能够访问对象的属性(有特权)。
Javascript Page 97