分类 javascript 下的文章

[].slice.call(arguments)

我们知道,Array.prototype.slice.call(arguments)能将具有length属性的对象转成数组,除了IE下的节点集合(因为ie下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换)
如:

1 var a={length:2,0:'first',1:'second'};
2 Array.prototype.slice.call(a);//  ["first", "second"]
3  
4 var a={length:2};
5 Array.prototype.slice.call(a);//  [undefined, undefined]

可能刚开始学习js的童鞋并不是很能理解这句为什么能实现这样的功能。比如我就是一个,所以,来探究一下。

首先,slice有两个用法,一个是String.slice,一个是Array.slice,第一个返回的是字符串,第二个返回的是数组,这里我们看第2个。

Array.prototype.slice.call(arguments)能够将arguments转成数组,那么就是arguments.toArray().slice();到这里,是不是就可以说Array.prototype.slice.call(arguments)的过程就是先将传入进来的第一个参数转为数组,再调用slice?

再看call的用法,如下例子

1 var a = function(){
2      console.log(this);    // 'littledu'
3      console.log(typeof this);      //  Object
4      console.log(this instanceof String);    // true
5 }
6 a.call('littledu');

可以看出,call了后,就把当前函数推入所传参数的作用域中去了,不知道这样说对不对,但反正this就指向了所传进去的对象就肯定的了。
到这里,基本就差不多了,我们可以大胆猜一下slice的内部实现,如下

1 Array.prototype.slice = function(start,end){
2      var result = new Array();
3      start = start || 0;
4      end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
5      for(var i = start; i < end; i++){
6           result.push(this[i]);
7      }
8      return result;
9 }

大概就是这样吧,理解就行,不深究。

最后,附个转成数组的通用函数

 1 var toArray = function(s){
 2     try{
 3         return Array.prototype.slice.call(s);
 4     } catch(e){
 5             var arr = [];
 6             for(var i = 0,len = s.length; i < len; i++){
 7                 //arr.push(s[i]);
                   arr[i] = s[i];  //据说这样比push快
 8             }
 9              return arr;
10     }
11 }

CORS-AJAX POST跨域解决方案

前言

跨域是我在日常面试中经常会问到的问题,这词在前端界出现的频率不低,主要原因还是由于安全限制(同源策略,即JavaScript或Cookie只能访问同域下的内容),因为我们在日常的项目开发时会不可避免的需要进行跨域操作,所以跨域能力也算是前端工程师的基本功之一。

和大多数跨域的解决方案一样,JSONP也是我的选择,可是某天PM的需求变了,某功能需要改成支持POST,因为传输的数据量比较大,GET形式搞不定。所以折腾了下闻名已久的CORS(跨域资源共享,Cross-Origin
Resource Sharing),这边文章也就是折腾期间的小记与总结。

概述

  1. CORS能做什么:

    正常使用AJAX会需要正常考虑跨域问题,所以伟大的程序员们又折腾出了一系列跨域问题的解决方案,如JSONP、flash、ifame、xhr2等等。
    本文介绍的CORS就是一套AJAX跨域问题的解决方案。

  2. CORS的原理:

    CORS定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX
    请求。实现此功能非常简单,只需由服务器发送一个响应标头即可。

    CORS浏览器支持情况如下图:

06082033-9233ced009a644f9af909baca57a72b7.png

06082103-f46e697e71884240b7200f8713697fbc.png

喜闻乐见、普大喜奔的支持情况,尤其是在移动终端上,除了opera Mini;PC上的现代浏览器都能友好的支持,除了IE9-,不过前端工程师对这种情况早应该习惯了...

CORS启航

  假设我们页面或者应用已在 http://www.test1.com 上了,而我们打算从 http://www.test2.com 请求提取数据。一般情况下,如果我们直接使用 AJAX 来请求将会失败,浏览器也会返回“源不匹配”的错误,"跨域"也就以此由来。
  利用 CORS,http://www.test2.com 只需添加一个标头,就可以允许来自 http://www.test1.com 的请求,下图是我在PHP中的 hander() 设置,“*”号表示允许任何域向我们的服务端提交请求:

06082159-b1a102a3ce0e49e7841f76675236e408.png

  也可以设置指定的域名,如域名 http://www.test2.com ,那么就允许来自这个域名的请求:

06082227-10f22c4f75094faeb78255509199aed5.png

  当前我设置的header为“”,任意一个请求过来之后服务端我们都可以进行处理&响应,那么在调试工具中可以看到其头信息设置,其中见红框中有一项信息是“Access-Control-Allow-Origin: ”,表示我们已经启用CORS,如下图。
  

PS:由于demo都在我厂的两台测试机间完成,外网也不能访问,所以在这就不提供demo了,见谅

06082242-c70a37f237ed48c4a60d33fccfd467fb.png

  简单的一个header设置,一个支持跨域&POST请求的server就完成了:)

  当然,如果没有开启CORS必定失败的啦,如下图:

06110611-953e174761e545ad94da63fceee9afb4.jpg

问题&小结

  • 刚刚说到的兼容性。CORS是W3C中一项较新的方案,所以部分浏览器还没有对其进行支持或者完美支持,详情可移http://www.w3.org/TR/cors/
  • 安全问题。CORS提供了一种跨域请求方案,但没有为安全访问提供足够的保障机制,如果你需要信息的绝对安全,不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障,比如OAuth2。

自认为的cors使用场景:

  1. cors在移动终端支持的不错,可以考虑在移动端全面尝试;
  2. PC上有不兼容和没有完美支持,所以小心踩坑。当然浏览器兼容就是个伪命题,说不准某个浏览器的某个版本就完美兼容了,说不准就有点小坑,尼玛伤不起!~
  3. jsonp是get形式,承载的信息量有限,所以信息量较大时CORS是不二选择;
  4. 配合新的JSAPI(fileapi、xhr2等)一起使用,实现强大的新体验功能。

第六章:依赖管理《Developing JavaScript Web Applications》学习笔记

为什么要依赖管理

没有依赖管理,代码会变得混乱不堪,如下:

<script src="jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="jquery.ui.js" type="text/javascript" charset="utf-8"></script>
<script src="application.utils.js" type="text/javascript" charset="utf-8"></script>
<script src="application.js" type="text/javascript" charset="utf-8"></script>
<script src="models/asset.js" type="text/javascript" charset="utf-8"></script>
<script src="models/activity.js" type="text/javascript" charset="utf-8"></script>
......

依赖管理系统除了能解决实际的编程复杂度和可维护性的问题,还能解决性能方面的问题。浏览器需要针对每个JavaScript 文件都发起一个HTTP 请求,尽管可以将这些请求放入异步队列,但大量的HTTP 连接总会造成性能的下降,每个连接都包含额外的HTTP 头信息、Cookie,并都要做TCP 的三次握手。

- 阅读剩余部分 -

第五章:视图和模板《Developing JavaScript Web Applications》学习笔记

模板

jQuery.tmpl是jQuery官方指定的jQuery模板库,这个库有一个主要的函数jQuery.tmpl(),可以给它传入一个模板和一些数据,函数会返回渲染好的元素节点,可以将渲染的结果追加至页面里。如果数据是数组的话,对于数组中的每个数据项都会生成渲染好的模板,否则,将只会渲染一个模板:

var object = {
url: "http://example.com",
getName: function(){ return "Trevor"; }
};
var template = '<li><a href="${url}">${getName()}</a></li>';
var element = jQuery.tmpl(template, object);
// 得到的结果: <li><a href="http://example.com">Trevor</a></li>
$("body").append(element);

这里你可以看到我们使用${} 语法来书写插见的变量。不管括号中的变量名是什么,都会根据传入jQuery.tmpl() 的对象来计算得出要填充的文本,不考虑它是一种属性还是一个函数。
然而模板的功能要比这种纯粹的插值替换强大很多。很多模板库都具有一些高级功能,诸如条件流(conditional flow)和迭代。你可以通过使用if 和else 语句来实现条件流,就像用纯粹的JavaScript 写出的代码一样。惟一和JavaScript 语法不同的地方是,这里需将关键字用双括号括起来,以便模板引擎能正确识别它们:

{{if url}}
${url}
{{/if}}

遍历是所有模板类库都提供的基础功能。使用模板类库的{{each}} 关键字可以遍历任何JavaScript 类型,包括Object 和Array。你可以使用$value 变量来访问当前正被遍历的值

var object = {
foo: "bar",
messages: ["Hi there", "Foo bar"]
};

然后使用接下来的模板来遍历这个message 数组,显示每条消息。此外,数组元素的索引也可以使用$index 变量来输出:

<ul>
{{each messages}}
<li>${$index + 1}: <em>${$value}</em></li>
{{/each}}
</ul>

模板Helpers

有时在视图内部使用“通用helper 函数”(generic helper function)是非常好用的,比如格式化一个日期或数字。将它抽象出来,并用命名空间进行管理,而不是直接将函数掺杂进视图中,这样才能保持逻辑和视图之间的解耦。例如:

// helper.js
var helper = {};
helper.autoLink = function(data){
var re = /((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/g;
return(data.replace(re, '<a target="_blank" href="$1">$1</a> ') );
};
// template.html
<div>
${ helper.autoLink(this.data) }
</div>

这里还有一个额外的好处,autoLink() 函数是通用的,在应用的任何地方都可以重用它。

模板存储

说到模板存储,有这样一些内容需要考虑:

  1. 在JavaScript 中以行内形式存储。---违背了MVC 架构的原则
  2. 在自定义 script 标签里以行内形式存储。--推荐这种方式,浏览器不必对它们进行渲染,而仅将它们解析为内容文本
  3. 远程加载。--影像UI 的渲染
  4. 在 HTML 中以行内形式存储。--增加了初始页面的体积

其中一些非常适合在MVC 架构中使用。作者推荐使用第2种方式,即在自定义script标签里以行内形式存储模板。例如:

<script type="text/x-jquery-tmpl" id="someTemplate">
<span>${getName()}</span>
</script>
<script>
var data = {
getName: function(){ return "Bob" }
};
var element = $("#someTemplate").tmpl(data);
element.appendTo($("body"));
</script>

绑定

我们希望,当模型记录创建、更新或销毁时,都会触发change 事件,并重新渲染视图。在下面的例子中,我们创建了一个基础的
User 类,新建了事件绑定和触发,最后监听了change 事件,当触发change 事件时重新渲染视图:

<script>
var User = function(name){
this.name = name;
};
User.records = []
User.bind = function(ev, callback) {
var calls = this._callbacks || (this._callbacks = {});
(this._callbacks[ev] || (this._callbacks[ev] = [])).push(callback);
};
User.trigger = function(ev) {
var list, calls, i, l;
if (!(calls = this._callbacks)) return this;
if (!(list = this._callbacks[ev])) return this;
jQuery.each(list, function(){ this() })
};
User.create = function(name){
this.records.push(new this(name));
this.trigger("change")
};
jQuery(function($){
User.bind("change", function(){
var template = $("#userTmpl").tmpl(User.records);
$("#users").empty();
$("#users").append(template);
});
}):
</script>
<script id="userTmpl" type="text/x-jquery-tmpl">
<li>${name}</li>
</script>
<ul id="users">
</ul>

现在无论何时修改User 的记录,User 的模型的change 事件都会被触发,调用我们模板的回调函数并重绘用户列表。这很有帮助,因为我们只需关注创建和更新用户的记录,而不必担心视图的更新,视图的更新是自动的。比如,我们创建一个新的User :

User.create("Sam Seaborn");

第四章:控制器和状态《Developing JavaScript Web Applications》学习笔记

模块模式

模块模式是用来封装逻辑并避免全局命名空间污染的好方法。使用匿名函数可以做到这一点,匿名函数也是JavaScript 中被证明最优秀的特性之一。通常是创建一个匿名函数并立即执行它。在匿名函数里的逻辑都在闭包里运行,为应用中的变量提供了局部作用域和私有的运行环境:

(function(){
/* ... */
})();

在执行这个匿名函数之前,我们用一对括号() 将它包起来。这样才能让JavaScript 解释器正确地将这段代码解析为一个语句。

全局导入和导出

可以将页面的window 导入我们的模块,直接给它定义属性,通过这种方式可以暴露全局变量:

var exports=this;
(function($){
    exports.Foo = "wem";
})(jQuery);

这里我们使用的变量名叫exports,用它来暴露全局变量,这样代码看起来更干净易读,可以直接看出模块创建了哪些全局变量。

一个完整的控制器

(function($, exports){
      var mod = {};
  mod.create = function(includes){
    var result = function(){
      this.init.apply(this, arguments);
    };
    
    result.fn = result.prototype;
    result.fn.init = function(){};
    
    result.proxy = function(func){ return $.proxy(func, this); };
    result.fn.proxy = result.proxy;

    result.include = function(ob){ $.extend(this.fn, ob); }; 
    result.extend = function(ob){  $.extend(this, ob); };
    if (includes) result.include(includes);
    
    return result;
  };
  
  exports.Controller = mod;
})(jQuery, window);

var exports = this;

$(function($){
  exports.SearchView = Controller.create({
    elements: {
      &quot;input[type=search]&quot;: &quot;searchInput&quot;,
      &quot;form&quot;: &quot;searchForm&quot;
    },
    
    init: function(element){
      this.el = $(element);
      this.refreshElements();
      this.searchForm.submit(this.proxy(this.search));
    },
    
    search: function(){
      alert(&quot;Searching: &quot; + this.searchInput.val());
      return false;
    },
    
    // Private
    
    $: function(selector){
      return $(selector, this.el);
    },
    
    refreshElements: function(){
      for (var key in this.elements) {
        this[this.elements[key]] = this.$(key);
      }
    }
  });
  
  new SearchView(&quot;#users&quot;);
});</code></pre>

See the Pen CaEbA by buer (@buer) on CodePen.

状态机

考虑这样一个场景,应用中存在一些视图,它们的显示是相互独立的,比如一个视图用来显示联系人,另一个视图用来编辑联系人。这两个视图一定是互斥的关系,其中一个显示时另一个一定是隐藏的。这个场景就非常适合引入状态机,因为它能确保每个时刻只有一种视图是激活的。的确,如果我们想添加一些新视图,比如一个承载设置操作的视图,用状态机来处理这种场景绰绰有余。

一个完整的状态机

 var Events = {
      bind: function(){
        if ( !this.o ) this.o = $({});
        this.o.bind.apply(this.o, arguments);
      },
  trigger: function(){
    if ( !this.o ) this.o = $({});
    this.o.trigger.apply(this.o, arguments);
  }
};

var StateMachine = function(){};
StateMachine.fn  = StateMachine.prototype;
$.extend(StateMachine.fn, Events);

StateMachine.fn.add = function(controller){
  this.bind(&quot;change&quot;, function(e, current){
    if (controller == current)
      controller.activate();
    else
      controller.deactivate();
  });
  
  controller.active = $.proxy(function(){
    this.trigger(&quot;change&quot;, controller);
  }, this);
};

var con1 = {
  activate: function(){ 
    console.log(&quot;controller 1 activated&quot;);
  },
  deactivate: function(){ 
    console.log(&quot;controller 1 deactivated&quot;);
  }
};

var con2 = {
  activate: function(){ 
    console.log(&quot;controller 2 activated&quot;);
  },
  deactivate: function(){ 
    console.log(&quot;controller 2 deactivated&quot;);
  }
};
 
var con3= {
  activate: function(){ 
    console.log(&quot;controller 3 activated&quot;);
  },
  deactivate: function(){ 
    console.log(&quot;controller 3 deactivated&quot;);
  }
};

var sm = new StateMachine;
sm.add(con1);
sm.add(con2);
sm.add(con3);

con2.active();

See the Pen ljyLq by buer (@buer) on CodePen.

URL 中的hash

定位当前页面所用的URL(基于URL)是不能更改的,如果改变则会引起页面的刷新,这是我们要避免发生的。幸好我们有一些解决办法。操作URL 的一种传统办法是改变它的hash。hash 不会发送给服务器,因此更改hash 不会造成页面的刷新。比如,这个
URL 是Twitter 的页面,它的hash 值就是#!/maccman:

http://twitter.com/#!/maccman

可以通过location 对象来读取或修改页面hash :

// 设置hash
window.location.hash = "foo";
assertEqual( window.location.hash , "#foo" );
// 去掉“#”
var hashValue = window.location.hash.slice(1);
assertEqual( hashValue, "foo" );

如果URL 中没有hash,location.hash 则返回空字符串。否则,location.hash 和URL的hash 部分相等,带有# 前缀。
太过频繁地设置hash 也会影响性能,特别是在移动终端里的浏览器中。因此,如果你对hash 的改动太频繁,就要注意限制这种改动,否则在移动终端里可能会造成页面的频繁滚动。

检测hash 的变化

以往检测hash 变化的方法是通过轮询的计时器来监听,这种方法非常原始。现在情形有所改观,现代浏览器都支持hashchange 事件。这是一个window 的事件,如果想检测hash 的改变就需要绑定这个监听:

window.addEventListener("hashchange", function(){ /* ... */ }, false);

使用jQuery 的代码:

$(window).bind("hashchange", function(event){
// hash 发生改变,更改状态
});

当触发hashchange 事件时,我们需要确定应用的当前状态。这个事件的浏览器兼容性非
常不错,主流浏览器的最新版本都支持这个事件:

  • IE >= 8。
  • Firefox >= 3.6。
  • Chrome。
  • Safari >= 5。
  • Opera >= 10.6。