用AJAX返回HTML片段中的JavaScript脚本


Posted in Javascript onJanuary 04, 2010

这是AJAX开发中很常见的问题,如果你不是一直在用JavaScript框架做开发,相信你早就发现这个问题了。本文分析了两个解决办法,其中一个是讲解jQuery框架的实现。

一、 问题描述
下面举个简单的例子,演示问题所在。在下面的例子中,假设变量responseText就是AJAX加载的HTML片段数据,其中包含脚本弹出一条消息,用innerHTML方法插入ID为ajaxData的DIV中,你可能期望看到弹出那个消息框,结果你发现没有,问题就是这样。

<div id="ajaxData"></div> 
<script type="text/javascript"> 
var responseText = '<p>这是一个段落</p><script>alert("这是AJAX加载回来的脚本片段")</script>'; 
document.getElementById('ajaxData').innerHTML = responseText; 
</script>

二、两种解决办法
1、 利用JavaScript的eval方法执行脚本。

本方法的具体实现思路是把xmlHttp.responseText中的脚本都抽取出来,不管AJAX加载的HTML包含多少个脚本块,我们对找出来的脚本块都调用eval方法执行它即可。下面提供一个封装好的函数:

function executeScript(html) 
{ 
var reg = /<script[^>]*>([^\x00]+)$/i; 
//对整段HTML片段按<\/script>拆分 
var htmlBlock = html.split("<\/script>"); 
for (var i in htmlBlock) 
{ 
var blocks;//匹配正则表达式的内容数组,blocks[1]就是真正的一段脚本内容,因为前面reg定义我们用了括号进行了捕获分组 
if (blocks = htmlBlock[i].match(reg)) 
{ 
//清除可能存在的注释标记,对于注释结尾-->可以忽略处理,eval一样能正常工作 
var code = blocks[1].replace(/<!--/, ''); 
try 
{ 
eval(code) //执行脚本 
} 
catch (e) 
{ 
} 
} 
} 
}

本方法的使用如下,对HTML用innerHTML方法添加到DOM,紧跟着调用executeScript方法执行脚本块:
document.getElementById("div1").innerHTML = xmlHttp.responseText; 
executeScript(xmlHttp.responseText);

显然这个方法还是存在缺陷的,如果xmlHttp.responseText包含像这样的外部脚本调用:
<script type="text/javascript" src="/js/common.js"></script>,executeScript方法不能再深入执行这个外部加载的脚本。

2、 学习并使用jQuery框架的实现

jQuery对于AJAX加载HTML,是最终在执行html(value)方法时把整个xmlHttp.responseText数据转换成DOM,然后利用DOM相关操作方法来找出里面的脚本,最后再把这些脚本插入到head中。具体原理也不好说,先举个最简单的例子,然后再分析一下大致思路。先看例子:

$.get('ajax.aspx', function(data) 
{ 
$('#div1').html(data); 
});

现在假设上面ajax.aspx页面返回的是HTML片段,而且包含一个或多个脚本块,甚至外部脚本引用。div1是AJAX请求发起页的一个DIV标签的ID,整句代码实现的结果是加载ajax.aspx中的HTML填充到一个ID为div1的DIV标签中。

在匿名回调函数中通过typeof(data)可以发现data还是原始的字符串,即等同于xmlHttp.responseText,通过代码执行跟踪发现,对AJAX加载脚本片段的执行处理不在jQuery的AJAX模块代码中,而是在html(value)方法,即把一段包含脚本块的HTML字符串插入DOM时,由它负责抽出脚本进行调用处理。而html(value)方法其实又是调用了append(value)方法……,整个过程大概调用了以下方法,箭头代表调用这些方法的先后顺序:

html -> append -> domManip -> clean -> evalScript -> globalEval

其中clean方法特别关键,这个方法也是jQuery比较重要的方法,其中也涉及修复HTML错误(标签没有结束,表格结构调整等方法)处理脚本。而脚本的抽出也是在这里进行的。看看相关源代码(jQuery1.3.2):

if (fragment) 
{ 
for (var i = 0; ret[i]; i++) 
{ 
if (jQuery.nodeName(ret[i], "script") && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript")) 
{ 
scripts.push(ret[i].parentNode ? ret[i].parentNode.removeChild(ret[i]) : ret[i]); 
} 
else 
{ 
if (ret[i].nodeType === 1) 
ret.splice.apply(ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script")))); 
fragment.appendChild(ret[i]); 
} 
} 
return scripts; 
}

另外,在evalScript方法中我们还发现如下代码,这里是“同”步加载像<script type="text/javascript" src="/js/common.js"></script>这样的外部脚本,解决executeScript方法存在的一个缺陷:
if (elem.src) 
jQuery.ajax( 
{ 
url: elem.src, 
async: false, 
dataType: "script" 
});

同时也发现如下代码,这段代码是把xmlHttp.responseText中的脚本删除,因为在这个方法中,jQuery是准备把抽取的脚本放入head区,所以删除可以避免最终的HTML出现重复的脚本块:
if (elem.parentNode)

最后,在globalEval方法中,发现head.removeChild( script );方法,就是把脚本插入head后马上又移除脚本标签,这也是避免因为重复执行html(value)方法在head区生成重复的脚本块。这个移除是不影响脚本执行的,同是也是不会清除脚本块中的相关变量值。显然,如果你想看看html(data)最终的执行结果,比如抽取后插入到head的脚本块是什么,你可以先临时注释这一行代码。
Javascript 相关文章推荐
3款实用的在线JS代码工具(国外)
Mar 15 Javascript
文本框input聚焦失焦样式实现代码
Oct 12 Javascript
JavaScript调用堆栈及setTimeout使用方法深入剖析
Feb 16 Javascript
JS注册/移除事件处理程序(ExtJS应用程序设计实战)
May 07 Javascript
JS 新增Cookie 取cookie值 删除cookie 举例详解
Oct 10 Javascript
jQuery内容折叠效果插件用法实例分析(附demo源码)
Apr 28 Javascript
移动端使用localStorage缓存Js和css文的方法(web开发)
Sep 20 Javascript
Vue-component全局注册实例
Sep 06 Javascript
微信小程序五子棋游戏的悔棋实现方法【附demo源码下载】
Feb 20 Javascript
Webpack 4如何动态切割JS注入文件名详解
Jul 09 Javascript
vue+element表格导出为Excel文件
Sep 26 Javascript
解决Layui数据表格显示无数据提示的问题
Nov 14 Javascript
Javascript解决常见浏览器兼容问题的12种方法
Jan 04 #Javascript
javascript 模拟点击广告
Jan 02 #Javascript
javascript 多种搜索引擎集成的页面实现代码
Jan 02 #Javascript
让firefox支持IE的一些方法的javascript扩展函数代码
Jan 02 #Javascript
javascript getElementsByClassName 和js取地址栏参数
Jan 02 #Javascript
firefox插件Firebug的使用教程
Jan 02 #Javascript
firefox firebug中文入门教程 脚本之家新年特别版
Jan 02 #Javascript
You might like
星际争霸中的热键
2020/03/04 星际争霸
PHP数组的交集array_intersect(),array_intersect_assoc(),array_inter_key()函数的小问题
2011/05/29 PHP
如何判断php数组的维度
2013/06/10 PHP
PHP实现QQ、微信和支付宝三合一收款码实例代码
2018/02/19 PHP
PHP中PCRE正则解析代码详解
2019/04/26 PHP
小议Function.apply()之二------利用Apply的参数数组化来提高 JavaScript程序性能
2006/11/30 Javascript
一起来写段JS drag拖动代码
2010/12/09 Javascript
javascript中负数算术右移、逻辑右移的奥秘探索
2013/10/17 Javascript
jquery学习总结(超级详细)
2014/09/04 Javascript
javascript检查浏览器是否已经启用XX功能
2015/07/10 Javascript
在js里怎么实现Xcode里的callFuncN方法(详解)
2016/11/05 Javascript
详解jQuery的表单验证插件--Validation
2016/12/21 Javascript
Webpack执行命令参数详解
2017/06/17 Javascript
vue最简单的前后端交互示例详解
2018/10/11 Javascript
JavaScript数组去重实现方法小结
2020/01/17 Javascript
原生JS生成指定位数的验证码
2020/10/28 Javascript
[51:15]2014 DOTA2国际邀请赛中国区预选赛 Orenda VS LGD-GAMING
2014/05/22 DOTA
[02:41]辉夜杯现场一家三口 “我爸玩风行 我玩血魔”
2015/12/27 DOTA
[46:55]LGD vs Liquid 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/19 DOTA
Python用zip函数同时遍历多个迭代器示例详解
2016/11/14 Python
python使用logging模块发送邮件代码示例
2018/01/18 Python
python后端接收前端回传的文件方法
2019/01/02 Python
Python3.6中Twisted模块安装的问题与解决
2019/04/15 Python
opencv转换颜色空间更改图片背景
2019/08/20 Python
使用浏览器访问python写的服务器程序
2019/10/10 Python
用python实现一个简单的验证码
2020/12/09 Python
HTML5的一个显示电池状态的API简介
2015/06/18 HTML / CSS
canvas离屏技术与放大镜实现代码示例
2018/08/31 HTML / CSS
全球最大的网上自行车商店:Chain Reaction Cycles
2016/12/02 全球购物
Ray-Ban雷朋奥地利官网:全球领先的太阳眼镜品牌
2020/10/12 全球购物
请解释流与文件有什么不同
2016/07/29 面试题
广告业务员岗位职责
2014/02/06 职场文书
《沉香救母》教学反思
2014/04/19 职场文书
张家口市高新区党工委群众路线教育实践活动整改方案
2014/10/25 职场文书
幼儿园元旦主持词
2015/07/06 职场文书
MySQL创建管理KEY分区
2022/04/13 MySQL