博主入职两个月了,越来越感受到打好基础对于前端工程师的重要性,在向着狂拽酷炫的框架&构建工具狂飚之前,必须有一个坚实的基础打底,才不至于轻易翻车。所以博主最近一直在恶补《JS高级程序设计》,发现了很多第一次读时忽略的、有趣的地方。不愧是经典,常读常新呀!
PART 1 最熟悉的陌生人——动态脚本
对于一些刚接触前端不久的同学来说,“动态脚本”可能是一个有些陌生的字眼,我也是偶然看高程,才想起同桌的你……不,才注意到这个技术。但事实上,这是每个前端每天都会用到的技术,也是从jQuery到各种狂拽酷炫的前端框架得以运行的基础技术。那它到底是谁呢?
来看一行代码:
<script src="jquery.js"></script>
眼熟吧?
写过吧?
恭喜你,你已经是会使用动态脚本技术的前端攻城狮了!
……纳尼?
没错!不要觉得这行代码司空见惯,其实仔细研究下,还颇有点因缺斯艇呢~
记住一点,网页归根到底就是一个HTML页面,除此之外别无他物。其他如JS、CSS、图片等,都是页面上的资源,从属于这个页面。JS代码只有作为script类型的DOM节点的内部文本的形式被添加到网页上,在浏览器解析DOM结构解析到script节点时,才会被执行。动态脚本,顾名思义,是“动态”的JS代码——“动态”的意思是,不是预先写在HTML页面上的,而是在页面解析的过程中被添加上去的。
动态添加脚本有两种方法:一是DOM操作,像插入其他类型的DOM节点一样插入一个script节点到页面,其内部的JS代码会被立即执行;二是如jQuery那个例子所示,从某个路径加载js文件到页面上来,可以理解为文件中的代码被复制粘贴到这个script标签中了,这些代码会在加载完成后被执行。
举个栗子:
<!-- 出于简略,只写body的代码 -->
<body>
<!-- 静态脚本 -->
<script>
console.log(1);
var scriptDOM = document.createElement('script');
scriptDOM.innerHTML = 'console.log(2);'; // 动态脚本的内容
document.body.appendChild(scriptDOM); // 动态插入script DOM节点
console.log(3);
</script>
</body>
这个页面上有一段静态脚本,其在执行时会向body插入一个script子节点。用浏览器打开这个页面,console会顺序打印出“1 2 3”这三个数字。查看页面,变成了这样:
<!-- 出于简略,只写body的代码 -->
<body>
<!-- 静态脚本 -->
<script>
console.log(1);
var scriptDOM = document.createElement('script');
scriptDOM.innerHTML = 'console.log(2);'; // 动态脚本的内容
document.body.appendChild(scriptDOM); // 动态插入script DOM节点
console.log(3);
</script>
<!-- 动态插入的的脚本 -->
<script>console.log(2);</script>
</body>
这个最终页面有趣的地方在于,包裹“console.log(2);”的script标签明明排在原来的标签的后面,但2却比3先打印出来。事实上,往任何地方插入动态脚本,其中的代码都会在插入后立刻执行。通过src属性引入的JS代码也是如此,一旦加载完成就立刻执行。
这是脚本语言灵活性的绝佳体现呀!想想编译型语言如C++、Java,所有代码必须预先编译好才能执行,无法做到像JS这样,不用编译不说,还可以在原本的代码执行到一半时,忽然插进来一堆新代码并且立即得到执行;就好比原本宴会邀请到了99个宾客,饭吃到一半,忽然来了个不速之客,还是个自来熟,坐下就开始与众人谈笑风生、觥筹交错,于是东道主JavaScript顺手就把宾客人数改成了100,毫无违和感;若是换成C++或Java做东,只会在宴会开始后把所有入口全部封死,不再允许任何人进入。
PART 2 动态脚本的应用:JSONP
“跨域”是前端面试中几乎必考的问题,而JSONP是一个比较简单好使的解决方案。
JSONP诞生的背景是,“跨域”只是AJAX所受的安全策略限制,只要域名、协议、端口这3项有1项不一致,浏览器就禁止发送AJAX请求。而像<script><img><link>这类标签的src属性不受此限,可以填写任意域名的地址(想想jQuery的CDN地址,还有一堆图床网站……)。于是聪明的前端攻城狮们想到了利用动态脚本来获取json数据的套路,名曰JSON Padding,意思是“JSON填充”。
那具体怎么个做法呢?分三步
步骤1
让服务器端略改下代码:当一个get请求的查询参数里有callback一项时,譬如一个URL长这样:
http://someurl.com/data?callback=handler
那么服务器就不要直接把JSON文件作为响应内容了,而是返回一个动态生成的JS文件,其中的代码是
handler({JSON数据});
其实就是一个handler函数的调用,同时把JSON作为参数“填充”进去。
步骤2
回到前端。定义好负责处理JSON数据的handler函数:
function handler(json) {
// 处理json数据
}
步骤3
在需要跨域获取数据时,向页面插入一个script DOM元素:
var script= document.createElement('script');
script.src = 'http://someurl.com/data?callback=handler';
document.body.appendChild(script);
这个script元素的src属性值带有一个callback查询参数,于是服务器会返回步骤1中的代码,这段代码被下载完成后立刻执行,handler恰好是已经定义好的函数,而梦寐以求的跨域JSON数据就这样作为handler的实参被传到了当前页面上!
最后是两点注意事项:
1. 用这种方法可以请求任何类型的数据,不限制为JSON,只是因为JSON最常用,所以命名为JSONP。
2. JSONP是为了帮AJAX绕开跨域限制而使用的一种技巧,它本身跟AJAX没有半毛钱关系,在不支持AJAX的浏览器上照样可以用JSONP技术。