用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 相关文章推荐
些很实用且必用的小脚本代码
Jun 26 Javascript
JavaScript高级程序设计(第3版)学习笔记3 js简单数据类型
Oct 11 Javascript
jQuery实现响应浏览器缩放大小并改变背景颜色
Oct 31 Javascript
Jquery中Event对象属性小结
Feb 27 Javascript
js实现完全自定义可带多级目录的网页鼠标右键菜单方法
Feb 28 Javascript
jQuery使用$.ajax进行即时验证实例详解
Dec 11 Javascript
利用策略模式与装饰模式扩展JavaScript表单验证功能
Feb 14 Javascript
微信小程序canvas实现刮刮乐效果
Jul 09 Javascript
jQuery中ajax请求后台返回json数据并渲染HTML的方法
Aug 08 jQuery
最简单的JS实现json转csv的方法
Jan 10 Javascript
Swiper实现导航栏滚动效果
Oct 16 Javascript
javascript实现数字时钟效果
Feb 06 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
php后台多用户权限组思路与实现程序代码分享
2012/02/13 PHP
浅析php面向对象public private protected 访问修饰符
2013/06/30 PHP
利用PHP获取网站访客的所在地位置
2017/01/18 PHP
PHP Trait功能与用法实例分析
2020/06/03 PHP
同一页面多个商品倒计时JS 基于面向对象的javascript
2012/02/16 Javascript
jquery插件实现鼠标经过图片右侧显示大图的效果(类似淘宝)
2013/02/04 Javascript
jquery实现按Enter键触发事件示例
2013/09/10 Javascript
javascript跨域原因以及解决方案分享
2015/04/08 Javascript
简述AngularJS相关的一些编程思想
2015/06/23 Javascript
JavaScript 字符串常用操作小结(非常实用)
2016/11/30 Javascript
BootStrap实现响应式布局导航栏折叠隐藏效果(在小屏幕、手机屏幕浏览时自动折叠隐藏)
2016/11/30 Javascript
解决URL地址中的中文乱码问题的办法
2017/02/10 Javascript
10道典型的JavaScript面试题
2017/03/22 Javascript
微信小程序 sha1 实现密码加密实例详解
2017/07/06 Javascript
npm配置国内镜像资源+淘宝镜像的方法
2018/09/07 Javascript
详解JS实现简单的时分秒倒计时代码
2019/04/25 Javascript
vxe-table vue table 表格组件功能
2019/05/26 Javascript
通过说明与示例了解js五种设计模式
2019/06/17 Javascript
jQuery表单校验插件validator使用方法详解
2020/02/18 jQuery
js实现轮播图效果 纯js实现图片自动切换
2020/08/09 Javascript
[56:45]DOTA2上海特级锦标赛D组小组赛#1 EG VS COL第一局
2016/02/28 DOTA
利用Python生成文件md5校验值函数的方法
2017/01/10 Python
python numpy数组的索引和切片的操作方法
2018/10/20 Python
python 爬虫百度地图的信息界面的实现方法
2019/10/27 Python
Jupyter notebook如何修改平台字体
2020/05/13 Python
Python正则表达式如何匹配中文
2020/05/27 Python
HTML5操作WebSQL数据库的实例代码
2017/08/26 HTML / CSS
世界首屈一指的在线男士内衣权威:HisRoom
2017/08/05 全球购物
如果Session Bean得Remove方法一直都不被调用会怎么样
2012/07/14 面试题
大学生优秀的自我评价分享
2013/10/22 职场文书
优秀实习自我鉴定
2013/12/04 职场文书
《掌声》教学反思
2014/02/23 职场文书
企业理念标语
2014/06/09 职场文书
不尊敬老师检讨书范文
2014/11/19 职场文书
给老婆的检讨书(搞笑版)
2015/05/06 职场文书
运动会通讯稿100字
2015/07/20 职场文书