学习第十三天(2019-11-26)

第二十二章 高级技巧

一、高级函数

1、安全的类型检测

由于typeof会出现无法预知的行为,instanceof在多个全局作用域中并不能正确工作,所以调用Object原生的toString方法,会返回[Object NativeConstructorName]格式字符串。每个类内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名。
原生数组的构造函数名与全局作用域无关,因此使用toString方法能保证返回一致的值,为此可以创建如下函数:
1 function isArray(value){
2     return Object.prototype.toString.call(value) == "[object Array]";
3 }

也可以基于这一思路测试某个值是不是原生函数或正则表达式:(这一技巧广泛用来检测原生JSON对象)

1 //判断是否原生函数
2 function isFunction(value){
3     return Object.prototype.toString.call(value) == "[object Function]";
4 }
5 //判断是否原生函数
6 function isFunction(value){
7     return Object.prototype.toString.call(value) == "[object RegExp]";
8 }
注:对于IE中以COM对象形式实现的任何函数,isFunction都将返回false,因为他们并不是js原生函数,Object的toString方法不能检测非原生构造函数的函数名。
 
2、作用域安全的构造函数
对于构造函数,使用了new操作符,则首先创建一个新的对象,将this指针指向新创建的对象,如果没有使用new操作符,则this指针会指向window对象,作用域安全的构造函数例子:
 1 function Person(name,age,job){
 2     if(this instanceof Person){          //判断this是否是正确的类型
 3         this.name = name;
 4         this.age = age;
 5         this.job = job;
 6     }else{
 7         return new Person(name,age,job);
 8     }
 9 }
10  
11 var per1 = Person("Nicholas",29,"Software Engineer");
12 alert(window.name);         //""
13 alert(per1.name);           //"Nicholas"
14  
15 var per2 = new Person("Shelby",34,"Ergonomist");
16 alert(per2.name);           //"Shelby"

多个程序员在一个页面上写JavaScript代码的环境中,推荐使用作用域安全的构造函数作为最佳实践。

3、惰性载入函数

对于多分支的if语句,有些时候并不需要每次都查询if语句,对于此情况,引入了惰性载入技巧,惰性载入表示函数分支只会执行一次,有两种方法可以实行该技巧:

方法1、在函数被调用时处理函数,在第一次调用时该函数会被覆盖为另一个按合适方式执行的函数。

 1 function createXHR(){
 2     if(typeof XMLHttpRequest != "undefined"){
 3         createXHR = function(){                             //将原函数覆盖
 4             return new XMLHttpRequest();
 5         };
 6     }else if(typeof ActiveXObject != "undefined"){
 7         createXHR = function(){                             //将原函数覆盖
 8             if(typeof arguments.callee.activeXString != "string"){
 9                 var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len;
10                 for(i=0,len=versions.length;i < len;i++){
11                     try{
12                         new ActiveXObject(versions[i]);
13                         arguments.callee.activeXString = versions[i];
14                         break;
15                     }catch(ex){
16                         //跳过
17                     }
18                 }
19             }
20             return new ActiveXObject(arguments.callee.activeXString);
21         };
22     }else{
23         createXHR = function(){                           //将原函数覆盖
24             throw new Error("No XHR object available.");
25         };
26     }
27     return createXHR();
28 }

方法2、在声明函数时指定适当的函数,这样第一次调用函数不损失性能,在代码首次加载时会损失性能

 1 var createXHR = (function(){
 2     if(typeof XMLHttpRequest != "undefined"){
 3         return function(){
 4             return new XMLHttpRequest();
 5         };
 6     }else if(typeof ActiveXObject != "undefined"){
 7         return function(){
 8             if(typeof arguments.callee.activeXString != "string"){
 9                 var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len;
10                 for(i=0,len=versions.length;i < len;i++){
11                     try{
12                         new ActiveXObject(versions[i]);
13                         arguments.callee.activeXString = versions[i];
14                         break;
15                     }catch(ex){
16                         //跳过
17                     }
18                 }
19             }
20             return new ActiveXObject(arguments.callee.activeXString);
21         };
22     }else{
23         return function(){
24             throw new Error("No XHR object available.");
25         };
26     }
27 })();

4、函数绑定

函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用。

 1 var handler = {
 2     message : "Event handled",
 3  
 4     handleClick : function(event){
 5         alert(this.message);
 6     }
 7 };
 8  
 9 var btn = document.getElementById("my-btn");
10 EventUtil.addHandler(btn,"click",handler.handleClick);      //按钮按下显示undefined,不显示Event handled

上述代码的问题在于没有保存handler.handleClick的环境,所以this最后指向了DOM按钮,而非handler(IE8中this指向window),可以使用闭包解决问题:

 1 var handler = {
 2     message : "Event handled",
 3  
 4     handleClick : function(event){
 5         alert(this.message);
 6     }
 7 };
 8  
 9 var btn = document.getElementById("my-btn");
10 EventUtil.addHandler(btn,"click",function(event){
11     handler.handleClick(event);   //按钮按下显示Event handled
12 });

由于闭包难以理解和调试,大部分js库为此实现了可以将函数绑定到指定环境的函数,一般叫做bind(),简单的bind函数,接收一个函数和环境,返回一个给定函数中调用给定函数的函数,并且将所有参数原封不动传过去。

1 //bind函数解决方案
2 function bind(fn,context){
3     return function(){
4         return fn.apply(context,arguments);
5     };
6 }

5、函数柯里化

函数柯里化用于创建已经设置好了一个或多个参数的函数。基本方法和函数绑定一样:使用闭包返回一个函数。区别在于:函数被调用时返回的函数还需要设置一些传入参数。

 

二、防篡改对象

1、不可扩展对象

使用Object.preventExtensions()方法,不能再给对象添加属性和方法;
1 var person = { name : "name"};
2 Object.preventExtensions(person);
3  
4 person.age = 28;
5 alert(person.age);          //undefined
6 //Object.isExtensible()可检测对象是否可扩展
2、密封对象
密封对象不可扩展,而且已有成员的[[Configurable]]特性将被设置为false。意味着不能修改或删除属性和方法
使用Object.seal()方法密封对象,使用Object.isSealed()方法确定对象是否密封。
1 var person = { name : "name" };
2 alert(Object.isExtensible(person));             //true
3 alert(Object.isSealed(person));                 //false
4  
5 Object.seal(person);
6 alert(Object.isExtensible(person));             //false
7 alert(Object.isSealed(person));                 //true
3、冻结对象
最严格防篡改级别。冻结对象既不可扩展,又是密封,对象的数据属性的[[Writable]]特性会被设置为false。如果定义了[[Set]]函数,访问器属性仍然可写。
使用Object.freeze()方法冻结对象,Object.isFrozen()方法检测对象是否冻结。
 
三、高级定时器
 JavaScript是单线程的,定时器仅仅只是计划代码在未来某个时间执行,执行时间并不确定。意思就是,js是单线程的,所有需要处理的代码都要排到执行队列中去,而定时器,只是在我们设定的时间之后将代码添加到队列中去,并不一定马上执行代码。
1、重复定时器
   setInterval定时器的功能缺陷:可能在代码再次被添加到队列中时,之前的代码还没有完成执行,结果导致代码运行好几次。js引擎足够聪明,能避免这个问题,在使用setInterval时,仅当没有定时器的任何其他代码实例时,才将定时器代码加入到队列。不过,还是有问题:某些间隔会被跳过,多个定时器的代码执行之间的间隔可能比预期小。
使用setTimeout方法(超时调用,执行一次后再定义一个,模拟循环)来定义重复定时器可以避免这些缺点。
2、函数节流
函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定时间间隔之后运行代码。当第二次调用函数时,会清除前一次的定时器并设置另一个,如果前一个已经执行过了,此操作无意义。然而,若前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的在于只有执行函数的请求停止了一段时间后才执行。
 
四、自定义事件与拖放

   JavaScript中经常以事件的形式应用观察者模式。虽然事件常常和DOM一起使用,但也可以通过实现自定义事件在自己的代码中应用。使用自定义事件有助于将不同部分的代码相互之间解耦,让维护更加容易,并减少引入错误的机会。

   拖放对于桌面和Web应用都是一个非常流行的用户界面范例,它能够让用户非常方便地以一种直观的方式重新排列或者配置东西。在JavaScrip中可以使用鼠标事件和一些简单的计算来实现这种功能类型。将拖放行为和自定义事件结合起来可以创建一个可重复使用的框架,它能应用于各种不同的情况下。 

 

第二十三章 离线应用与客户端存储

  开发离线Web应用需要几个步骤。首先是确保应用知道设备是否能上网,以便下一步执行正确的操作。然后,应用还必须能访问一定的资源(图像、JavaScript、CSS 等),只有这样才能正常工作。 最后,必须有一块本地空间用于保存数据,无论能否上网都不妨碍读写。HTML5及其相关的API让开发离线应用成为现实。 

一、离线检测

HTML5定义了navigator.onLine属性,true表示能上网。

还定义了两个事件:online(离线变在线触发),offline(在线变离线触发),这两个事件在window对象上触发。
 
二、应用缓存
HTML5的应用缓存(application cache),或者简称为appcache,是专门为开发离Web应用而设计的。Appcache就是从浏览器的缓存中分出来的一块缓存区。要想在这个缓存中保存数据,可以使用一个描述文件(manifest file),列出要下载和缓存的资源。
 
三、数据存储
1、Cookie
  HTTP Cookie,通常叫做cookie,cookie在性质上是绑定在特定域名下的。当设定了一个 cookie后,再给创建它的域名发送请求时, 都会包含这个cookie。这个限制确保了储存在cookie中的信息只能让批准的接受者访问,而无法被其他域访问。 
a、cookie的构成:
cookie由浏览器保存的以下几块信息构成
    名称:唯一确定cookie的名称,不缺分大小写,实践中最好看成区分大小写。
    值:储存在cookie中的字符串值,必须被URI编码
    域:对于哪个域有效
    路径:对于域中的那个路径,应该向服务器发送cookie
    失效时间:cookie应该被删除的时间
    安全标志:指定后,cookie只有在使用ssl连接时才发送到服务器
b、JavaScript中的cookie
基本的cookie操作:读,写,删,在CookieUtil对象表示:
 1 var socket = new WebSocket("ws://www.example.com/server.php");
 2 var CookieUtil = {
 3     get:function(name){
 4         var cookieName = encodeURIComponent(name) + "=" ,
 5             cookieStart = document.cookie.indexOf(cookieName),
 6             cookieValue = null;
 7 
 8         if(cookieStart > -1){
 9             var cookieEnd = document.cookie.indexOf(";",cookieStart);
10             if(cookieEnd == -1){
11                 cookieEnd = document.cookie.length;
12             }
13             cookieValue = decodeURIComponent(document.cookie.substring(cookieStart+cookieName.length,cookieEnd));
14         }
15 
16         return cookieValue;
17     },
18     set:function(name,value,expires,path,domain,secure){
19         var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value);
20 
21         if(expires instanceof Date){
22             cookieText += "; expires=" + expires.toGMTString();
23         }
24         if(path){
25             cookieText += "; path=" + path;
26         }
27         if(domain){
28             cookieText =+ "; domain=" + domain;
29         }
30         if(secure){
31             cookieText += "; secure=" + secure;
32         }
33         document.cookie = cookieText;
34     },
35     unset:function(name,path,domain,secure){
36         this.set(name,"",new Date(0),path,domain,secure);
37     }
38 };

使用上述方法的例子:

 1 //设置cookie
 2 CookieUtil.set("name","Nicholas");
 3 CookieUtil.set("book","Professional JavaScript");
 4 
 5 //读取
 6 alert(CookieUtil.get("name"));  //"Nicholas"
 7 alert(CookieUtil.get("book"));  //Professional JavaScript
 8 
 9 //删除
10 CookieUtil.unset("name");
11 CookieUtil.unset("book");
12 
13 //设置cookie,包括它的路径、域、失效日期
14 CookieUtil.set("name","Nicholas","/books/projs","www.wrox.com",new Date("January 1,2010"));
15 
16 //删除刚设置的cookie
17 CookieUtil.unset("name","/books/projs","www.wrox.com");
18 
19 //设置安全cookie
20 CookieUtil.set("name","Nicholas",null,null,null,true);
为了绕开浏览器的单域名下cookie数量限制,还可以使用子cookie。

子 cookie 是存放在单个 cookie 中的更小段的数据。也就是使用cookie值来存储多个名称值对 儿。子cookie常见的的格式如下所示: 
    name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5

 注:使用cookie时尽量少存储信息,不存敏感信息,因为cookie数据并非存储在一个安全环境中。

2、Web存储机制  
Web Storage目的是克服由cookie带来的一些限制。主要目的:
    a、提供一种cookie之外存储会话数据的途径;
    b、提供一种存储大量可以跨会话存在的数据的机制
Web Storage定义了两种用于存储数据的对象:sessionStorage和localStorage。前者严格用于在一个浏览器会话中存储数据,因为数据在浏览器关闭后会立即删除;后者用于跨会话持久化数据并遵循跨域安全策略。 
 
3、IndexedDB
  IndexedDB是一种类似SQL数据库的结构化数据存储机制。但它的数据不是保存在表中,而是保存在对象存储空间中。创建对象存储空间时,需要定义一个键,然后就可以添加数据。它的操作是异步的。
a、open方法,存在就打开,不存在就创建并打开;
b、可以使用游标在对象存储空间中查询特定的对象,在对象存储空间调用openCursor方法可创建游标。
c、键范围,通过游标查找方式有限,键范围为游标增添了灵活性
d、索引是为了提高查询速度而基于特定的属性创建的。 
有了以上这些选择,就可以在客户端机器上使用 JavaScript 存储大量数据了。但必须小心,不要在客户端存储敏感数据,因为数据缓存不会加密。
posted @ 2019-11-26 23:38  xiongbing  阅读(156)  评论(0编辑  收藏  举报