首页 > jquery的getjson的作用域问题

jquery的getjson的作用域问题

用getjson读本地文件,获取数据后怎么赋给外面用啊,现在只能在回调函数里获取到数据?


由于异步调用的原因,通常回调函数中的赋值操作都是在异步方法在的变量使用之后进行的 所以总是发现取不到回调函数中的值。
一个简单的思路就是,回调函数中的值使用外部可访问的方式暂存,完了以后通知外部程序去取。


真正的问题不在于“怎么赋给外面用”,而是“何时外面才能用”。为了理解这一点,你得明白什么是 async programming(异步编程),看这个虚构例子:

javascriptvar outside               // 0. 我在外面

$.getJSON('/some/api/endpoint', function(response) {
    outside = response    // 1. 你当然可以把响应数据赋值给外面的代码
})

console.log(outside)      // 2. 但是你没法保证接下来的代码能正确运行

// 因为以上代码的运行顺序是 0 -> 2 -> 1

02,代码是自上而下同步运行的,然而 1(注意:1$.getJSON 的回调函数而不是 $.getJSON 方法本身)是在之后运行的,并且确切的时间无法预知,它取决于对于 '/some/api/endpoint' 的请求何时返回。所以上面的虚拟例子从语法上来说没有任何问题,你只是无法保证它能运行出期望的结果罢了(在现实中由于 event loop 的机制,1 总是晚于 2 运行的)。

So,我们不知道何时异步回调会执行(只知道它晚于同步代码),那我们怎么办?幸运的是我们知道:“反正它会运行的”(除非请求失败,然而那属于错误处理或异常处理的部分,此处我们只谈 happy path),所以我们就告诉它:“当它运行时该做什么”就是了。所以之前的例子可以改写一下:

javascriptvar outside

$.getJSON('/some/api/endpoint', function(response) {
    outside = response    // 你还是可以把响应数据赋值给外面的代码
    console.log(outside)  // 然后再执行相应的逻辑,因为这里面是同步的,你能保证顺序
})

当然这个例子太简单了,以至于 outside 这个变量根本就是个摆设(who cares),领会精神就好。当然在实际中,由于代码组织的关系,我们不总是能把逻辑上的顺序按照线性的方式自上而下的写代码,不过没关系只要你心中始终拎清楚现在正在写的代码将在何时运行就好。举例而言,稍微改动一下上面的例子:

javascriptvar outside

// 我不知道什么时候,但终将在某一时刻我要打印出 outside 的值
// 至于这个值是什么就看到时候是谁传 response 进来了
// 注意:这也同时暗示着,此函数的调用者一定要传递一个 response 进来,否则接下来的代码无意义
function printOutside(response) {
    outside = response
    console.log(outside)
}

// ...

// 接着在未来的某个时候,某个请求调用了 printOutside 函数,outside 被顺利赋值并打印
$.getJSON('/some/api/endpoint', printOutside)

实际上,最后的 $.getJSON 写在 printOutside 的前面或后面都无所谓,它总是能正确运行的,因为无论写前写后,同步代码的解释执行都会先于 printOutside 被调用。而作为函数声明 $.getJSON 也总是能找到正确的 printOutside 函数(hoisting)。

这就是你想知道的“怎样给外面用”的最核心的知识,我尽可能通俗的讲解了。不过现实中会有非常多复杂的情形,比如最常见的:“异步回调里面再异步请求回调再异步请求回调再……”,也就是俗称的“回调地狱”——就需要更复杂一些的手段来让它们乖乖听话。这些进阶的知识其实并不难,只是在我之前描述的概念之上更进一步罢了。

那么最后我在给你点明一下如何在这个概念上进阶一步。想想看如果你是 jQuery 的作者(或者其他类似异步请求函数的作者),你要如何定义 $.getJSON 这个函数?重点是:你如何赋予使用者自由定义回调函数,然后你能确保它正确运行?先自己想想,然后看下面的伪代码:

javascript// 这里,我要定义这个 getJSON;本质上,它是对一个 xhr 请求的封装
$.prototype.getJSON = function(path, callback) {
    // 使用者需要告诉我两个条件:1,请求的 path;2,请求返回后要执行的 callback

    // 接着我开始封装 xhr 请求,具体的代码请参考 javascript 的 API 手册
    // 比如这里:https://developer.mozilla.org/en-US/docs/AJAX/Getting_Started
    var xhr = new XMLHttpRequest()    // 初始化一个 xhr 对象
    xhr.onreadystatechange = function() {
        // xhr 请求发出后会有多种状态变化(根据请求的发送及接受情况),这个函数用于处理各种状况
        // 因此真实的代码这里会比较复杂,然而最终一切 OK 的话我们能获得正确的 response
        // 要注意的是,此函数必须在 send 之前定义,否则 send 完之后 xhr 对象并不知道该继续做什么
        // 这也就暗示着,onreadystatechange 其实是 send 在某一时刻调用的
        // 这与前面的 printOutside 的例子有异曲同工之妙


        // 拿到 response 之后我们开始调用 callback,并把 response 传递进去
        callback(xhr.responseText)
    }
    xhr.open('GET', path)     // 配置 xhr 的请求参数
    xhr.send()                // 发送真正的请求
}

真正的 jQuery 自然要比这个伪代码例子复杂多了(就连 xhr 的封装都不是在这里做的,我没记错的话 jQuery 还有更底层的函数,$.ajax?),它要处理的问题可不仅仅是调用 callback 这么简单,不过一旦你从作者角度明确了 callback 是如何被调用的,那么从使用者的角度来考量该如何利用 callback 就变得自然而然毫无窒碍了,这也就是常说的“知其然并知其所以然”。

有心的话,再研究一个举一反三的小题目吧(不难),研究明白了就能再上一个台阶。题目如下:

我们已经了解了这样的写法和用法:

javascript$.getJSON('some/api/endpoint', function(response) {
    // do something...
})

那么这样的写法和用法又是怎么实现的呢?

javascript$.getJSON('some/api/endpoint'[, maybeSomeAdditionalParams])
.success(function(response) {
    // do something when request is OK
})
.fail(function(error) {
    // do something when it is NOT OK
})

一旦你掌握了这类写法是如何定义的,你就 get 到了新的姿势来处理可能存在的异步回调嵌套问题,并且这也是接下来你去学习更先进的异步编程范式的基础(比如 promises 等)。GL!


可以贴上代码看下么

【热门文章】
【热门文章】