【解决方案】你必须要知道的~前端九种跨域方式实现原理(完整版)

前言

前后端数据交互经常会碰到请求跨域,什么是跨域,以及有哪几种跨域方式,这些问题通常出现在Web开发中,当浏览器执行脚本发起请求到不同的域名、协议或端口时,出于安全考虑,浏览器会限制这种跨源HTTP请求。以下是九种常见的跨域解决方案及其实现原理:

一、什么是跨域?

1.什么是同源策略及其限制内容?

同源策略是浏览器实施的一项核心安全机制,它确保了网页只能访问与自身具有相同协议、域名和端口号的资源。这种策略是防止跨站脚本(XSS)和跨站请求伪造(CSRF)等网络攻击的关键。即使两个域名映射到同一个IP地址,只要它们的协议、域名或端口不完全匹配,浏览器就会将它们视为不同的源,从而限制它们之间的交互。

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

但是有三个标签是允许跨域加载资源:

  • <img>
  • <link>
  • <script>

2.常见跨域场景

当网页尝试访问与自身不属于同一协议、子域名、主域名或端口的资源时,这种行为被称为“跨域”。简而言之,如果两个URL在协议、子域名、主域名或端口中的任何一个方面存在差异,它们就被视为属于不同的域,而从一个域向另一个域发起的资源请求就是跨域请求。常见跨域场景如下图所示:

特别说明两点:

第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。

第二:跨域识别仅基于URL的协议、域名和端口,与IP地址无关。

这里你或许有个疑问:请求跨域了,那么请求服务器有没有收到?

       跨域问题并不阻止请求的发送,实际上请求可以成功地从客户端发送到服务器,并且服务器也能正常处理这些请求并返回数据。问题在于,浏览器出于安全考虑,会拦截来自不同源的响应数据,防止用户访问。这解释了为什么使用表单提交可以实现跨域请求,而Ajax请求则不行。表单提交不涉及获取新的响应内容,因此不会触发浏览器的跨域限制。然而,Ajax请求能够接收响应数据,浏览器认为这可能带来安全风险,因此会阻止这些数据的读取。这也意味着,尽管存在跨域限制,但它们并不能完全防止CSRF攻击,因为请求本身仍然可以被发送出去。

二、跨域解决方案

1.jsonp
1) JSONP原理

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

2) JSONP和AJAX对比

JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

3) JSONP优缺点

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

4) JSONP的实现流程

        定义一个回调函数,比如命名为show,这个函数将接收服务器返回的数据。然后,创建一个<script>元素,其src属性设置为需要请求的跨域API地址,并附加一个查询参数callback=show来告知服务器使用这个函数名来包装响应数据。服务器在接收到这个特殊的请求后,会将返回的数据格式化为show('返回的数据')的形式。客户端浏览器接收到这个响应后,会自动执行这个格式化的JavaScript代码,从而调用show函数并处理数据。如果存在多个JSONP请求可能使用相同的回调函数名,开发者需要自定义一个机制来避免函数名冲突。

// index.html
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script')
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    params = { ...params, callback } // wd=b&callback=show
    let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${arrs.join('&')}`
    document.body.appendChild(script)
  })
}
jsonp({
  url: 'http://localhost:3000/say',
  params: { wd: 'hello word' },
  callback: 'show'
}).then(data => {
  console.log(data)
})

上面这段代码相当于向http://localhost:3000/say?wd=helloword&callback=show这个地址请求数据,然后后台返回show('hello word'),最后会运行show()这个函数,打印出'我不爱你'

// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
  let { wd, callback } = req.query
  console.log(wd) 
  console.log(callback) 
  res.end(`${callback}('世界')`)
})
app.listen(3000)
5) jQuery的jsonp形式

JSONP都是GET和异步请求的,不存在其他的请求方式和同步请求,且jQuery默认就会给JSONP的请求清除缓存。

$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",//可以省略
jsonpCallback:"show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
jsonp:"callback",//->把传递函数名的那个形参callback,可省略
success:function (data){
console.log(data);}
});
2.cors

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现

浏览器将自动处理CORS协议,其核心在于服务器端的配置。一旦服务器端配置了Access-Control-Allow-Origin头,便激活了CORS机制,允许指定或所有域名进行资源访问。

尽管CORS的设置与前端代码无关,但这种机制区分了两种类型的请求:简单请求和复杂请求。简单请求无需额外的预检流程,而复杂请求则需要浏览器先发送一个OPTIONS预检请求,以确认服务器是否允许跨域访问。

1) 简单请求

只要同时满足以下两大条件,就属于简单请求

条件1:使用下列方法之一:

GET

HEAD

POST

条件2:Content-Type 的值仅限于下列三者之一:

text/plain

multipart/form-data

application/x-www-form-urlencoded

请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

2) 复杂请求

如果一个请求不满足CORS的简单请求条件,它就被视为复杂请求。对于复杂请求,CORS协议会在实际通信之前发起一个额外的HTTP查询请求,也就是一个OPTIONS请求,作为“预检”流程。这个预检请求的目的是确定服务器是否允许即将到来的跨域请求。

我们用PUT向后台请求时,属于复杂请求,后台需做如下配置:

// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS请求不做任何处理
if (req.method === 'OPTIONS') {
  res.end() 
}
// 定义后台返回的内容
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.end('我不爱你')
})

 接下来我们看下一个完整复杂请求的例子,并且介绍下CORS请求相关的字段

// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.response)
      //得到响应头,后台需设置Access-Control-Expose-Headers
      console.log(xhr.getResponseHeader('name'))
    }
  }
}
xhr.send()
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
//server2.js
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] //设置白名单
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS请求不做任何处理
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end('我不爱你')
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('我不爱你')
})
app.use(express.static(__dirname))
app.listen(4000)

上述代码由http://localhost:3000/index.html向http://localhost:4000/跨域请求,正如我们上面所说的,后端是实现 CORS 通信的关键。

3.postMessage

postMessage API是HTML5规范中的一部分,属于少数能够实现跨域通信的Web属性。它主要用于以下场景的数据交换:

  • 在主页面与其打开的新标签或窗口之间传输数据。
  • 在多个浏览器窗口之间实现消息的传递。
  • 允许页面与其嵌入的iframe进行消息交流,即便它们来自不同的源。

postMessage提供了一个跨域通信的桥梁,使得不同域的页面能够安全地交换信息。

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递

otherWindow.postMessage(message, targetOrigin, [transfer]);

message: 将要发送到其他 window的数据。

targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。

transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

接下来我们看个例子: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“我爱你”,然后后者传回"我不爱你"。

// a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
  //内嵌在http://localhost:3000/a.html
    <script>
      function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
        window.onmessage = function(e) { //接受返回数据
          console.log(e.data) //我不爱你
        }
      }
    </script>

// b.html
  window.onmessage = function(e) {
    console.log(e.data) //我爱你
    e.source.postMessage('我不爱你', e.origin)
 }
4.websocket

WebSocket是HTML5引入的一种持久连接协议,它允许在浏览器和服务器之间建立一个全双工通信渠道,这不仅实现了数据的实时交换,也可以作为解决跨域问题的一种手段。WebSocket与HTTP一样,都是应用层的协议,都运行在TCP之上。不同之处在于WebSocket支持服务器和客户端之间的双向数据流通。在建立WebSocket连接时,会使用HTTP协议,但一旦连接建立,随后的通信就不再依赖HTTP。

原生的WebSocket API可能使用起来较为复杂,而Socket.IO提供了一个更易于使用的封装版本,它简化了WebSocket接口,使得接口更加简洁和灵活。此外,Socket.IO还能为那些不支持WebSocket的旧版浏览器提供兼容性支持。

我们先来看个例子:本地文件socket.html向localhost:3000发生数据和接受数据

// socket.html
<script>
    let socket = new WebSocket('ws://localhost:3000');
    socket.onopen = function () {
      socket.send('我爱你');//向服务器发送数据
    }
    socket.onmessage = function (e) {
      console.log(e.data);//接收服务器返回的数据
    }
</script>
server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('地球999')
  });
})
5. Node中间件代理(两次跨域)

代理服务器的工作原理基于一个事实:服务器间通信不受同源策略的限制。其工作流程包括四个主要步骤:

  1. 接收来自客户端的请求。
  2. 将该请求转发至目标服务器。
  3. 接收目标服务器的响应数据。
  4. 将这些数据转发回客户端。

      我们先来看个例子:本地文件index.html文件,通过代理服务器http://localhost:3000向目标服务器http://localhost:4000请求数据。

// index.html(http://127.0.0.1:5500)
 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script>
      $.ajax({
        url: 'http://localhost:3000',
        type: 'post',
        data: { name: 'xiamen', password: '123456' },
        contentType: 'application/json;charset=utf-8',
        success: function(result) {
          console.log(result) // {"title":"fontend","password":"123456"}
        },
        error: function(msg) {
          console.log(msg)
        }
      })
     </script>
// server1.js 代理服务器(http://localhost:3000)
const http = require('http')
// 第一步:接受客户端请求
const server = http.createServer((request, response) => {
  // 代理服务器,直接和浏览器直接交互,需要设置CORS 的首部字段
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': 'Content-Type'
  })
  // 第二步:将请求转发给服务器
  const proxyRequest = http
    .request(
      {
        host: '127.0.0.1',
        port: 4000,
        url: '/',
        method: request.method,
        headers: request.headers
      },
      serverResponse => {
        // 第三步:收到服务器的响应
        var body = ''
        serverResponse.on('data', chunk => {
          body += chunk
        })
        serverResponse.on('end', () => {
          console.log('The data is ' + body)
          // 第四步:将响应结果转发给浏览器
          response.end(body)
        })
      }
    )
    .end()
})
server.listen(3000, () => {
  console.log('The proxyServer is running at http://localhost:3000')
})
// server2.js(http://localhost:4000)
const http = require('http')
const data = { title: 'fontend', password: '123456' }
const server = http.createServer((request, response) => {
  if (request.url === '/') {
    response.end(JSON.stringify(data))
  }
})
server.listen(4000, () => {
  console.log('The server is running at http://localhost:4000')
})

上述代码经过两次跨域,值得注意的是浏览器向代理服务器发送请求,也遵循同源策略,最后在index.html文件打印出{"title":"fontend","password":"123456"}

6.nginx反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

通过设置一个nginx服务器作为转发请求的中介,可以实现一种高效的跨域解决方案。这种方法简单易行,只需对nginx配置文件进行调整,无需更改任何前端或后端代码,就能解决跨域问题,同时保持对所有浏览器的兼容性和session支持,且对服务器性能无负面影响。

具体操作是,在nginx中配置一个与domain1相同域名但不同端口的代理服务器,该服务器将请求转发至domain2的接口。在此过程中,还可以更新cookie中的domain信息,使得cookie能够在当前域中被正确写入,从而实现跨域登录。

先下载nginx,然后将nginx目录下的nginx.conf修改如下:

// proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

最后通过命令行nginx -s reload启动nginx

// index.html
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

// server.js
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));
    // 向前台写cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取
    });
    res.write(JSON.stringify(params));
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
7.window.name + iframe

window.name的特性在于,其值能够在不同页面间(包括跨域页面)保持不变,即使页面重新加载。此外,window.name能够存储非常大的数据量,支持高达2MB的数据。

其中a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000

// a.html(http://localhost:3000/b.html)
  <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
  <script>
    let first = true
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    function load() {
      if(first){
      // 第1次onload(跨域页)成功后,切换到同域代理页面
        let iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:3000/b.html';
        first = false;
      }else{
      // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
        console.log(iframe.contentWindow.name);
      }
    }
  </script>

b.html为中间代理页,与a.html同域,内容为空。
 // c.html(http://localhost:4000/c.html)
  <script>
    window.name = '地球ss'  
  </script>

总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

8.location.hash + iframe

实现原理: a.html欲与c.html跨域相互通信,通过中间页b.html来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

实现过程如下:a.html首先向c.html发送一个hash值。c.html接收到这个hash值之后,接着将这个值传递给b.html。最终,b.html将获取到的结果通过更新a.html的hash值来反馈给a.html。在这个过程中,a.html和b.html属于同一域,即本地服务器的3000端口;而c.html则位于不同的域,即4000端口。

// a.html
  <iframe src="http://localhost:4000/c.html#helloword"></iframe>
  <script>
    window.onhashchange = function () { //检测hash的变化
      console.log(location.hash);
    }
  </script>
// b.html
  <script>
    window.parent.parent.location.hash = location.hash 
    //b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面
  </script>
// c.html
 console.log(location.hash);
  let iframe = document.createElement('iframe');
  iframe.src = 'http://localhost:3000/b.html#hellowordsss';
  document.body.appendChild(iframe);
9.document.domain + iframe

这种方法仅适用于具有相同二级域名的场合,例如a.test.com和b.test.com。只需在页面上设置document.domain='test.com',明确两个页面共享相同的主域,即可实现跨域通信。

其工作原理是:通过JavaScript在两个页面上均设置document.domain属性为共同的主域,从而使得它们在浏览器中被视为同一域的一部分。

我们看个例子:页面a.html获取页面b.html中a的值

// a.html
<body>
 helloa
  <iframe src="http://baidu:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'baidu'
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>
// b.html
<body>
   hellob
   <script>
     document.domain = 'baidu'
     var a = 100;
   </script>
</body>

三、总结

CORS提供了一种全面的解决方案,允许执行所有类型的HTTP请求,从而解决了跨域问题。相比之下,JSONP虽然只能处理GET请求,但它的优势在于兼容性,能够支持较旧的浏览器,并且能够向那些尚未实现CORS的服务器请求数据。

无论是使用Node.js作为中间件代理,还是通过nginx设置反向代理,它们的核心原理都是绕过浏览器的同源策略限制,允许服务器间的通信不受此策略约束。

在实际开发中,CORS和nginx反向代理是两种常用的解决跨域问题的方法。CORS通过服务器端设置来允许特定域名的访问,而nginx反向代理则通过配置服务器转发请求和响应,简化了跨域请求的处理。这两种方法因其稳定性和灵活性,被广泛应用于现代Web开发中。

结语

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/749830.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis数据库(六):主从复制和缓存穿透及雪崩

目录 一、Redis主从复制 1.1 概念 1.2 主从复制的作用 1.3 实现一主二从 1.4 哨兵模式 1.4.1 哨兵的作用 1.4.2 哨兵模式的优缺点 二、Redis缓存穿透和雪崩 2.1 缓存穿透——查不到 2.1.1 缓存穿透解决办法 2.2 缓存击穿 - 量太大&#xff0c;缓存过期 2.2.1 缓存…

拍照就用华为Pura 70系列,后置真实感人像轻松出片!

平时喜欢用手机记录生活的人是不是总有个烦恼&#xff0c;想要拍出媲美单反的完美人像&#xff0c;又怕照片失真&#xff0c;经过近期对手机摄影的探索&#xff0c;我发现了华为Pura70系列的真实感人像之美&#xff0c;它给予每个热爱生活的人直面镜头的自信&#xff0c;记录真…

毕业季留念,就该这样记录下来

毕业季来啦&#xff01;这个季节总是充满了不舍和期待&#xff0c;就像夏天里的冰淇淋&#xff0c;甜蜜中带着一丝丝凉意。在这个特别的时刻&#xff0c;我想和大家分享一款陪伴我记录青春点滴的神器——nova 12 Ultra 手机。 要说自拍&#xff0c;我可是个“资深玩家”。以前…

以算筑基,以智赋能 | Gooxi受邀出席2024中国智算中心全栈技术大会

6月25日&#xff0c;2024中国智算中心全栈技术大会暨展览会、第5届中国数据中心绿色能源大会暨第10届中国&#xff08;上海&#xff09;国际数据中心产业展览会在上海新国际博览中心隆重召开。Gooxi受邀参与并携最新服务器产品以及解决方案亮相展会&#xff0c;吸引众多行业领袖…

基于MATLAB仿真设计无线充电系统

通过学习无线充电相关课程知识&#xff0c;通过课程设计无线充电系统&#xff0c;将所学习的WPT&#xff0c;DC-DC&#xff0c;APFC进行整合得到整个无线充电系统&#xff0c;通过进行仿真研究其系统特性&#xff0c;完成我们预期系统功能和指标。 以功率器件为基本元件&#x…

【人工智能学习之图像操作(二)】

【人工智能学习之图像操作&#xff08;二&#xff09;】 图像上的运算图像混合按位运算 图像的几何变换仿射变换透视变换膨胀操作腐蚀操作开操作闭操作梯度操作礼帽操作黑帽操作 图像上的运算 图像上的算术运算&#xff0c;加法&#xff0c;减法&#xff0c;图像混合等。 加减…

Profibus协议转Modbus协议网关模块在船舶中的应用

一、背景 在当今数字化快速发展的时代&#xff0c;船舶作为重要的交通工具之一&#xff0c;也在不断追赶着科技的步伐&#xff0c;实现自身的智能化升级。而在这个过程中&#xff0c;Profibus转Modbus网关&#xff08;XD-MDPB100&#xff09;作为关键的一环&#xff0c;扮演着…

05 Shell编程之免交互

目录 5.1 Here Document 免交互 5.1.1 Here Document 概述 5.1.2 Here Document 免交互 1. 通过read命令接收输入并打印 5.1.3 Here Document变量设定 5.1.4 Here Document 格式控制 (1)关闭变量替换的功能。 (2)去掉每行之前的TAB字符。 5.1.5 Here Document 多行注释…

前端写代码真的有必要封装太好么?

前言 封装、代码复用、设计模式…… 这些都是方法&#xff0c;业务才是目的。技术始终是为业务服务的。能够满足业务需求&#xff0c;并且用起来舒服的&#xff0c;都是好方法。 不存在一套适用于所有项目的最佳代码组织方法&#xff0c;你需要结合业务&#xff0c;去不断地…

cad报错:由于找不到vcruntime140.dll无法继续执行代码

在现代的工程设计中&#xff0c;计算机辅助设计&#xff08;CAD&#xff09;软件已经成为了工程师们不可或缺的工具。然而&#xff0c;在使用CAD软件的过程中&#xff0c;有时我们会遇到一些问题&#xff0c;其中之一就是“找不到vcruntime140.dll”的错误提示。本文将详细介绍…

鸿蒙期末项目(2)

主界面 主界面和商店详情界面参考如下设计图&#xff08;灵感严重匮乏&#xff09; 简单起见&#xff0c;将整个app分为4个布局&#xff0c;分别是主界面、搜索界面、购物车界面&#xff0c;以及个人界面。 所以在app中也需要使用tab组件进行分割&#xff0c;且需要通过tabBa…

安装Flask

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 大多数Python包都使用pip实用工具安装&#xff0c;使用Virtualenv创建虚拟环境时会自动安装pip。激活虚拟环境后&#xff0c;pip 所在的路径会被添加…

离散傅里叶变化

傅里叶变换 对傅里叶变换了解不是很清楚的朋友推荐一下这个帖子&#xff0c;讲得很详细 傅里叶变换 源码 先看源码链接 #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "open…

FuTalk设计周刊-Vol.026

&#x1f525;&#x1f525;AI漫谈 热点捕手&#x1f525;&#x1f525; 1、Hotshot-XL AI文本转GIF Hotshot-XL 是一种 AI 文本转 GIF 模型&#xff0c;经过训练可与Stable Diffusion XL一起使用。能够使用任何现有或新微调的 SDXL 模型制作 GIF。 网页体验 网页http://htt…

git 初基本使用-----------笔记(结合idea)

Git命令 下载git 打开Git官网&#xff08;git-scm.com&#xff09;&#xff0c;根据自己电脑的操作系统选择相应的Git版本&#xff0c;点击“Download”。 基本的git命令使用 可以在项目文件下右击“Git Bash Here” &#xff0c;也可以命令终端下cd到指定目录执行初始化命令…

聚类算法(3)---K-means 算法

本篇文章是博主在人工智能等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对人工智能等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在AI学习笔记&#…

软件测试的目的和原则介绍,软件测试外包公司推荐

在当今信息技术迅速发展的时代&#xff0c;软件产品的质量和安全性对用户至关重要。而软件测试作为保障软件产品质量的关键一环&#xff0c;具有不可或缺的作用。 软件测试的目的是为了发现和解决软件产品中的缺陷和问题&#xff0c;确保软件的稳定和可靠性。软件测试帮助找出…

表格截图怎么转换成表格?6个软件帮助你快速进行表格转换

表格截图怎么转换成表格&#xff1f;6个软件帮助你快速进行表格转换 将表格截图转换为可编辑的表格文件是处理数据时常见的需求&#xff0c;特别是在需要分析或编辑图像中包含的信息时。以下是几款帮助你快速进行表格转换的软件和工具&#xff0c;它们提供了不同的功能和适用场…

揭秘!这款电路设计工具让学校师生都爱不释手——SmartEDA的魔力何在?

随着科技的飞速发展&#xff0c;电子设计已成为学校师生们不可或缺的技能之一。而在众多的电路设计工具中&#xff0c;有一款名为SmartEDA的工具&#xff0c;凭借其强大的功能和友好的用户体验&#xff0c;迅速赢得了广大师生的青睐。今天&#xff0c;就让我们一起探索SmartEDA…

游泳耳机入耳式好还是骨传导好?游泳教练力荐实力卓绝的四大热款

作为一名长期致力于游泳爱好者健康与运动体验提升的专业教练&#xff0c;我深知在水中听音乐的魅力&#xff0c;同时也深知选择正确的耳机对于水上运动的重要性。近年来&#xff0c;市场上的游泳耳机类型日益丰富&#xff0c;其中入耳式和骨传导两大主流各有千秋。今天&#xf…