JavaScript中使用fetch的相对路径

62

今天我在JavaScript中遇到了相对路径的一个问题,感到很惊讶。我将情况归结如下:

假设你有这样的目录结构:

app/
   | 
   +--app.html
   +--js/
        |
        +--app.js
        +--data.json

我的app.html只是运行js/app.js

<!DOCTYPE html>
<title>app.html</title>
<body>
<script src=js/app.js></script>
</body>

app.js 加载 JSON 文件并将其粘贴到 body 开始位置:

// js/app.js
fetch('js/data.json') // <-- this path surprises me
  .then(response => response.json())
  .then(data => app.data = data)

数据是有效的 JSON,只是一个字符串:

"Hello World"

这是一个相当简单的fetch用法示例,但我惊讶地发现,我传递给fetch的URL必须相对于app.html而不是相对于app.js。我希望这个路径可以工作,因为data.jsonapp.js在同一个目录(js/)中:

fetch('data.json') // nope

这是为什么的解释呢?


2
JS 可以来自于无法指向其文件夹的位置,因为 SOP 的限制,所以它始终是相对于页面的。 - dandavis
1
请注意,这与JavaScript实际上没有太大关系;这是Web浏览器解释HTTP请求路径的方式。主页面为所有内容定义了URL上下文:脚本引用、图像、样式表和XHRs。 - Pointy
5
CSS路径并不是这样工作的,它们是相对于样式表源目录的位置 —— 如果你在css/styles.css中有类似 body { background-image: url(pic.gif) } 的代码,则浏览器会在css/文件夹中寻找pic.gif,而不是根目录下。这是我对JS期望的行为,但显然事实并非如此。 - user2467065
3个回答

89
当你使用 fetch('data.json') 时,实际上是在请求 http://yourdomain.com/data.json,因为它相对于发出请求的页面。你应该以正斜杠开头,这将表明路径相对于域根:fetch('/js/data.json')。或者完全指定你的域:fetch('http://yourdomain.com/js/data.json')

3
这个问题问的是域名还是真正的起源,它包含了使用HTTP端口的完全限定域名(FQDN),例如http://yourdomain.com:8080/data.json? - Randy
似乎Chrome不再在没有服务器的情况下获取相同目录。 - user985399
3
Chrome中的行为是始终相对于根目录,即使这不是所需的。 - John

9
一个简单的理解为什么它必须是这种情况的方法是考虑如果我们在app/js/helper/logfetch.js中编写一个帮助函数,会发生什么:
// app/js/helper/logfetch.js
function logFetch(resource) {
    console.log('Fetching', resource);
    return fetch(resource);
}

现在,考虑一下如果我们使用来自app/js/app.js 的logFetch会发生什么:

// app/js/app.js
fetch('data.json');    // if this is relative to js/, then ...
logFetch('data.json'); // should this be relative to js/ or js/helper/?

我们可能希望这两个调用返回相同的结果 - 但是如果fetch是相对于包含文件的位置,那么logFetch将请求`js/helper/data.json`而不是与fetch一致的内容。
如果fetch可以感知它被调用的位置,那么要实现像logFetch这样的帮助库,JavaScript需要整套新的调用者位置感知功能。
相比之下,相对于HTML文件执行fetch会提供更多的一致性。
CSS的工作方式不同,因为它没有方法调用的复杂性:不能创建"帮助 CSS 模块"来转换其他 CSS 模块,所以相对路径的想法在概念上更加清晰。

2
这应该是被接受的答案。OP要求“为什么会这样”的解释,而这个答案真正解释了“为什么”。 - Shiva Prasad

3

这并不是一个确切的建议,因为它依赖于许多东西,不能保证在任何地方都能正常工作,也不能保证将来仍然有效,但在我需要的地方它能够运行,希望对你有所帮助。

const getRunningScript = () => {
    return decodeURI(new Error().stack.match(/([^ \n\(@])*([a-z]*:\/\/\/?)*?[a-z0-9\/\\]*\.js/ig)[0])
}

fetch(getRunningScript() + "/../config.json")
  .then(req => req.json())
  .then(config => {
    // code
  })


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接