阅读视图

发现新文章,点击刷新页面。

jsonp解决前端跨域问题

各位大佬我们今天聊聊前端热门八股-跨域问题

那什么叫从一个域到另一个域呢?

在 Web 开发中,"域"(或"源")是指一个特定的协议、域名和端口的组合。浏览器的同源策略限制了从一个源加载的文档或脚本与另一个源的资源进行交互。理解域的概念对于理解跨域资源共享(CORS)非常重要。

域的组成

一个域由以下三个部分组成:

  • 协议:如 http 或 https。

  •  域名:如 example.com。

  • 端口:如 80(HTTP 的默认端口)或 443(HTTPS 的默认端口)。

比如掘金的域

image.png

同源策略

同源策略是浏览器的一种安全机制,限制了从一个源加载的文档或脚本与另一个源的资源进行交互。只有当协议、域名和端口都相同时,两个 URL 才被认为是同源的。

但是为什么浏览器要阻止这种访问资源的行为,我们来说几个常见的跨域安全问题

1. 防止跨站请求伪造(CSRF)

  • 跨站请求伪造是一种攻击方式,攻击者诱导用户的浏览器在用户不知情的情况下执行不当的操作。例如,用户登录到银行网站后,攻击者可能会诱导用户访问一个恶意网站,该网站会在用户不知情的情况下向银行网站发送请求,执行转账等操作。

2. 防止跨站脚本攻击(XSS)

  • 跨站脚本攻击允许攻击者在其他网站的上下文中执行恶意脚本。通过限制跨域请求,浏览器可以减少攻击者在用户访问的其他网站上执行恶意脚本的机会。

3. 保护用户隐私

  • 浏览器的同源策略保护用户的敏感信息不被恶意网站访问。例如,用户的会话信息、登录状态和其他敏感数据通常存储在 cookies 中,限制跨域请求可以防止这些信息被不可信的来源访问。

4. 防止数据泄露

  • 如果没有同源策略,恶意网站可以轻松地从其他网站获取数据,可能导致数据泄露。例如,攻击者可以从用户访问的其他网站窃取个人信息、交易记录等。

5. 确保数据完整性

  • 同源策略确保数据的完整性,防止恶意网站在用户不知情的情况下修改或操纵数据。

跨域请求

当一个网页尝试从不同的源请求资源时,就会发生跨域请求。例如:

我们用fetch模拟一下跨域访问

首先初始化一个后端项目,并且运行在3000端口

//http 服务启动
// commonjs模块规范node早期 引入http模块
const http=require('http');
const server=http.createServer((req,res)=>{
    //异步回调 
    //当请求来到服务器后,该函数会被执行 req请求对象被解析,res响应对象被创建 http结束

    //发送响应体
    res.end('hello world');
});
    console.log('服务已启动,端口号:3000');
});

我们使用nodemon热更新让后端跑在3000端口,使用live'server启动前端在5500端口我们前端用fecth请求3000端口会发现报错了,浏览器报了一个跨域访问出错,所以即使在同一个局域网,端口号不同的情况下,这算一个跨域请求

我们今天用jsonp来实现这跨域资源访问

 <ul id="list"></ul>
<script src=''http://localhost:3000''></script>
<script>
function callback(data) {
 list.innerHTML = data.map(user => `<li>${user.id+user.name}</li>`).join('');
}
</script>
                      

后端

//http 服务启动
// commonjs模块规范node早期 引入http模块
const http=require('http');
const user=[
    {
    id:1,
    name:'张三'
    },
    {
    id:2,
    name:'李四'
    }]
const server=http.createServer((req,res)=>{
    //异步回调 
    //当请求来到服务器后,该函数会被执行 req请求对象被解析,res响应对象被创建 http结束

    //发送响应体
    res.end('callback('+JSON.stringify(user)+')');
});

server.listen(3000,()=>{
    console.log('服务已启动,端口号:3000');
});

首先我们把script的源设置在与后端同一个端口,这里小编设置在3000端口,我们把后端返回的数据使用js的api挂载在ul标签上,这段代码的script标签从3000端口加载资源,看似浏览器能正常解析后端返回的js数据但是

各位大佬先别运行这段代码,我们先来想想这段代码的致命问题是什么,不错script标签阻塞

image.png

image.png 我们在浏览器启动前端会发现控制台报了一个callback没定义的问题,其实这段代码按顺序执行的时候,浏览器先解析了上面的script标签,从3000端口加载资源,但是此时的前端执行了返回的callback函数,<script src="http://localhost:3000"> 直接加载了这个脚本,而没有定义 callback 函数,导致浏览器无法正确执行返回的 JavaScript 代码。所以我们引出今天的跨域解决方案jsonp

我们把回调函数,与跨域的src封装在jsonp函数中这样只要改动函数中的src便能正确的处理后端返回的数据

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>
<body>
    <ul id="list"></ul>
    <script>
            

        let list = document.getElementById('list');
        // function callback(data) {
        //     list.innerHTML = data.map(user => `<li>${user.id+user.name}</li>`).join('');
        // }
        // fetch('http://127.0.0.1:3000').then(res => res.json())
        // fetch('http://localhost:3000')
        let jsonp=(url,callback)=>{
            let oScript=document.createElement('script')
            oScript.src=url          
            document.body.appendChild(oScript)
            window.callback=callback
        }
        jsonp('http://localhost:3000',(user)=>{ list.innerHTML = user.map(user => `<li>${user.id+user.name}</li>`).join('');})
    </script>
</body>
</html>

我们把后端callback函数挂载到window上

  • 动态创建 

  • 浏览器允许从不同源加载脚本文件,因此可以利用 

  • 回调函数:

  • 服务器返回的数据被包装在一个回调函数中。客户端在请求时指定回调函数的名称,服务器将数据作为参数传递给这个回调函数。

  • 数据传输:

  • 服务器返回的响应是一个 JavaScript 文件,其中包含对回调函数的调用,并将数据作为参数传递。

后端

//http 服务启动
// commonjs模块规范node早期 引入http模块
const http=require('http');
const user=[
    {
    id:1,
    name:'张三'
    },
    {
    id:2,
    name:'李四'
    }]
const server=http.createServer((req,res)=>{
    //异步回调 
    //当请求来到服务器后,该函数会被执行 req请求对象被解析,res响应对象被创建 http结束

    //发送响应体
    res.end('callback('+JSON.stringify(user)+')');
});

server.listen(3000,()=>{
    console.log('服务已启动,端口号:3000');
});

我们重新打开浏览器我们发现后端返回的数据正确的被浏览器解析到页面上,这样我们便使用jsonp实现了简单的跨域资源访问

vue hooks编程

我们来想想这段效果怎么实现,一个鼠标在窗口移动,页面上不断实时更新x与y的值

很简单的一段逻辑,相信各位大佬一看就会,不就用两个响应式数据,如然后监听mousemove吗,各位大佬且别急,听小编慢慢到来

我们来看看最简单传统的写法

<template>
    <div>
        <p >x:{{ mousePos.x }}</p>
        <p>y:{{ mousePos.y }}</p>
    </div>
</template>

<script setup>
const mousePos=reactive({x:0,y:0})
function handleMouseMove(e) {
  mousePos.x=e.clientX
  mousePos.y=e.clientY
  console.log("haizai")

}
 window.addEventListener('mousemove', handleMouseMove)
</script>

我们先不探讨这段代码存在的问题,我们先来看看这个需求,添加一个按钮,实现上述组件的v-if功能 我们来看看根组件的代码

<template>
  <div>
<MousePos v-if="showMouse" />
<button @click="toggle">切换</button>
  </div>
</template>

<script setup>
import {
  ref
} from 'vue'
import MousePos from './components/MousePos.vue'
const showMouse=ref(true)
const toggle=()=>{
  showMouse.value=!showMouse.value
}
</script>

看起来我们好像完成了这个需求,但是上述代码有致命问题,各位大佬先仔细想想,我们先探讨用到vue中的狗钩子函数进行上述需求的完善

我们来看看更新后的代码

<template>
    <div>
        <p >x:{{ mousePos.x }}</p>
        <p>y:{{ mousePos.y }}</p>
    </div>
</template>

<script setup>
import { 
  reactive,
  onMounted,
  onUnmounted
} from 'vue'
// const x=ref(0)
// const y=ref(0)
const mousePos=reactive({x:0,y:0})
function handleMouseMove(e) {
  mousePos.x=e.clientX
  mousePos.y=e.clientY
  console.log("我还在")

}
onMounted(()=>{
  window.addEventListener('mousemove', handleMouseMove)
})

</script>

<style  scoped>

</style>

onMounted钩子函数在挂载前设置了一个监听器我们一样实现了这个功能,但是各位大佬应该很敏感的反映到,这里的致命问题--内存泄漏

我们来看看什么是常见的内存泄漏

  1. 全局变量:未被清理的全局变量会一直占用内存。
  2. 事件监听器:添加了事件监听器但在不再需要时没有移除它们。
  3. 定时器:设置的 setInterval 或 setTimeout 没有被清除。
  4. 闭包:闭包可能会意外地保持对大对象的引用,阻止垃圾回收机制回收这些对象。
  5. Vue 组件中的响应式数据:组件卸载后未正确清理响应式数据或侦听器。

如果我们把代码运行就会发现即使点击了切换组件,即把组件利用v-if销毁,控制台也一直在鼠标移动的时候输出我还在,所有我们发现即使组件销毁了这个定时器依旧没有在内存中删掉,一直浪费性能,所以我们重新来修改修改我们的代码

<template>
    <div>
        <p >x:{{ mousePos.x }}</p>
        <p>y:{{ mousePos.y }}</p>
    </div>
</template>

<script setup>
import { 
  reactive,
  onMounted,
  onUnmounted
} from 'vue'
// const x=ref(0)
// const y=ref(0)
const mousePos=reactive({x:0,y:0})
function handleMouseMove(e) {
  mousePos.x=e.clientX
  mousePos.y=e.clientY
  console.log("haizai")

}
onMounted(()=>{
  window.addEventListener('mousemove', handleMouseMove)
})
onUnmounted(()=>{
  window.removeEventListener('mousemove', handleMouseMove)
})
</script>

<style  scoped>

</style>

我们上述完成了一个小小的响应式功能,并且利用组建的生命周期钩子函数在组件销毁时自动删除了这个定时器,但是各位大佬再详细我们还能优化吗

我们今天重点说说这个思想-响应式数据+ui=组件

很明显上述的响应式数据与鼠标移动的组件放在一起,如果我们采用这个思想 把ui与数据分离,这样我们的数据业务不仅能更好的维护,而且更加符合es6模块化思想,当我们做大型项目时,协同性也会更优秀

image.png 我们先来src创建一个hooks目录。里面封装我们的响应式业务逻辑,我们来看看这段代码

import { ref,onMounted ,onUnmounted} from 'vue'
export function useMouse() {
 alert("hello")
}
export function useMousePosition() {
  const x=ref(0)
  const y=ref(0)
  function update(e){
    x.value=e.pageX
    y.value=e.pageY;
  }
  onMounted(()=>{
    window.addEventListener('mousemove',update)
  })
  onUnmounted(()=>{
    window.removeEventListener('mousemove',update)
  })
  return {x,y};
}


我们用export导出这个模块函数,并且请记住return这个响应式数据我们来看看鼠标移动这个组件代码

<template>
    <div>
        <p>x: {{ mousePos.x }}</p>
        <p>y: {{ mousePos.y }}</p>
    </div>
</template>

<script setup>
import { useMousePosition,useMouse } from '../hooks/useMouse.js';

// 调用 useMouse 并接收整个响应式对象
const mousePos = useMousePosition();


// 如果你需要分别访问 x 和 y,可以继续使用 mousePos.x 和 mousePos.y
// const { x, y } = mousePos; // 这样做仍然会保持响应性,因为 mousePos 是响应式的
</script>

<style scoped>
/* 添加必要的样式 */
</style>

我们把业务逻辑封装在一个模块,并且导出,这个模块还具有其他功能,这样我们只要在想用的组件中利用es6的解构便能轻松实现逻辑的复用与方便的维护

❌