查看原文
其他

怎么解决跨域问题?

编程导航和鱼友们 面试鸭 2024-01-21

大家好呀,今天是编程导航 30 天面试题挑战的第十七天,一起来看看今天有哪些优质面试题吧。

后端

题目一

什么是 Java 内部类? 内部类的分类有哪些 ?内部类有哪些优点和应用场景?

官方解析

内部类是定义在另一个类中的类。Java 中内部类主要分为成员内部类、静态内部类、局部内部类和匿名内部类四种。

  1. 成员内部类:定义在另一个类的内部,并且与其它成员变量和方法平级,可以访问外部类的所有成员变量和方法。使用方式:
Outer.Inner inner = new Outer().new Inner()。
  1. 静态内部类:定义在另一个类的内部,但是要用 static 修饰。只能访问外部类的静态成员变量和方法。使用方式:
Outer.Inner inner = new Outer.Inner()。
  1. 局部内部类:定义在方法中,作用域仅限于方法内部。与局部变量类似,不能使用访问控制符修饰。使用方式:在方法中直接实例化。
  2. 匿名内部类:没有名字的内部类。使用方式:new 接口或者抽象类() { } 或 new 父类() { }。

内部类的优点:

  • 可以访问外部类的私有成员变量和方法。
  • 可以隐藏实现细节。
  • 便于编写和维护,提高代码的可读性和可维护性。
  • 内部类可以很好地解决Java中单继承的问题。

内部类的应用场景:

  • 需要访问外部类的私有成员变量和方法。
  • 需要定义一个回调函数或者监听器。
  • 需要实现多重继承。
  • 需要对外部类进行扩展。

鱼友的精彩回答

苏打饼干的回答

顾名思义,内部类是指定义在某一个类中的类,主要分为成员内部类,静态内部类,局部内部类和匿名内部类四种。

创建与获取
/ 1、私有内部类 => 在外部类中编写方法,对外提供内部类对象
// 定义方法(外部类中)
public Inner getInstance(){
 return new Inner();
}
// 使用方法
Outer o = new Outer();
Object i = o.getInstance();

// 2. 非私有内部类 => 直接创建成员内部类
// 外部类名.内部类名 对象名 = new 外部类对象.new 内部类对象;
Outer.Inner oi = new Outer().new Inner();
优点
  1. 可以隐藏实现细节。
  2. 便于编写和维护,提高代码的可读性和可维护性。
  3. 使用内部类解决 Java单继承问题
  4. 可以更换的对外部类进行扩展

注:JDK16 之前成员内部类里不能定义静态变量

爱吃鱼蛋的回答

内部类是指定义在另一个类内部的类,它可以访问外部类的成员变量和方法。

内部类的分类有以下几种:
  • 成员内部类:定义在类内部,但在方法外部的类。它可以访问外部类的所有成员变量和方法

  • 静态内部类:定义在类内部,但使用 static 修饰的类。它只能访问外部类的静态成员变量和方法

  • 局部内部类:定义在方法内部的类。它只能访问方法内部的 final 变量和方法参数;

  • 匿名内部类:没有类名的内部类,通常用于创建只需要使用一次的类。

它们主要有以下区别:
  • 成员内部类和静态内部类都可以定义在外部类中,成员内部类需要外部类实例化才能使用,而静态内部类不需要。
  • 成员内部类和静态内部类都可以定义静态成员,但成员内部类只能在非静态方法中使用,而静态内部类可以在任何地方使用。
  • 局部内部类只能定义在方法中,不能定义静态成员,也不能访问外部类成员。
  • 匿名内部类没有类名,通常用于创建一个实现某个接口或继承某个类的对象,可以访问外部类成员。
内部类的合理使用存在以下优点:
  • 通过内部类变相地实现多继承,使得一个类可以假性继承多个父类;
  • 内部类可以隐藏实现细节,从而简化代码的编写;
  • 内部类可以访问外部类的私有成员变量和方法,从而增加了程序的灵活性和安全性。
内部类常见的使用场景如下:
  • 可用于实现回调函数提供给外部类使用;
  • 通过内部类中定义静态成员变量,利用内部类的加载机制,从而实现单例模式
  • 有多继承需求时可使用内部类实现假性多继承

题目二

覆盖索引和联合索引是什么?讲一下索引的最左前缀匹配原则。

官方解析

覆盖索引联合索引是数据库中常见的两种索引类型。

覆盖索引是指一个包含了所有查询需要的列的索引,查询时可以直接从索引中取到需要的数据,而不需要再回到表中查找,从而可以提高查询效率。

联合索引是指使用多个列组合起来作为一个索引,可以同时查询多个列,以提高查询效率。联合索引可以包含多个列,但是查询时只能使用前缀列进行查询,即只有在查询中使用了联合索引的前几个列,才能利用联合索引进行查询。如果查询中没有使用前缀列,那么联合索引就不能发挥作用,需要使用单独的索引或全表扫描。

最左前缀匹配原则是指如果一个联合索引包含了多个列,那么在查询时只能使用前面的列进行匹配

例如,一个联合索引包含了 A、B、C 三列,那么查询时只能使用 A、AB 或 ABC 进行匹配,而不能只使用 B 或 C 进行匹配。这是因为如果查询时使用的列不是最左前缀列,那么 MySQL 就无法使用索引进行查询,会导致全表扫描,从而降低查询效率。

在实际的应用中,覆盖索引联合索引可以结合使用,以提高查询效率。同时,使用最左前缀匹配原则可以让我们更加合理地设计索引,从而提高查询性能。

鱼友的精彩回答

维萨斯的回答

两个sql回答本题

联合索引 = 聚合索引 = 组合索引

CREATE TABLE test
(
    id        INT,
    age       INT,
    ... (以下省略敲多字段)
    INDEX id_age_index (id, age) // 这个就是联合索引
);

覆盖索引 ( 索引覆盖结果 ) 注意是覆盖!!! index如果是 id_age_index 也是覆盖

SELECT
id ⬅------------
FROM test       | 前提: 给id建了索引!!!
WHERE           | 前提: 给id建了索引!!!
id > 1; ⬅-------
最左前缀匹配

以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。

上面的test
有个(id, age) 这个索引
在查询优化器中,索引要先走 id,再走 age,优先匹配左,并且必须匹配上,才会继续匹配 age

一句话: 脑中模拟B+树 答得HR直发怵

关于索引实现可以看一下往期面试题挑战文章

Day10 MySQL 中的索引是怎么实现的?B+ 树是什么

Ming 的回答

覆盖索引和联合索引是什么?讲一下索引的最左前缀匹配原则

覆盖索引

是指 SQL 中 query 的所有字段,在索引 B+ Tree 的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作

联合索引

通过将多个字段组合成一个索引,该索引就被称为联合索引。

最左前缀匹配原则

使用联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效,这样就无法利用到索引快速查询的特性了。

有效的例子

比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:

where a=1; where a=1 and b=2 and c=3; where a=1 and b=2;

注意,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。即 where b=2 and a=1; 也是符合最左前缀的规则

失效的例子
where b=2; where c=3; where b=2 and c=3;

因为(a, b, c) 联合索引,是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。所以,b 和 c 是全局无序,局部相对有序的,这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。

范围查询例子

Q1

select * from t_table where a > 1 and b = 2

只有 a 字段用到了联合索引进行索引查询,而 b 字段并没有使用到联合索引

Q2

select * from t_table where a >= 1 and b = 2

a 和 b 字段都用到了联合索引进行索引查询。

Q3

SELECT * FROM t_table WHERE a BETWEEN 2 AND 8 AND b = 2

a 和 b 字段都用到了联合索引进行索引查询

Q4

SELECT * FROM t_user WHERE name like 'j%' and age = 22

a 和 b 字段都用到了联合索引进行索引查询

注意:结合 explain 分析

总结

综上所示,联合索引的最左匹配原则,在遇到范围查询(如 **>、<**)的时候,就会停止匹配,也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。注意,对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配,前面我也用了四个例子说明了。

题目三

Spring 如何处理线程并发问题,ThreadLocal 你了解过吗?

官方解析

Spring 框架中处理线程并发问题的方式包括以下几种:

  • 同步关键字 synchronized:使用 synchronized 关键字可以对共享资源进行加锁,从而保证多线程访问时的同步性。但是,synchronized 对性能会产生一定的影响,并且容易导致死锁等问题。
  • Lock 接口:Lock 接口提供了比 synchronized 更加灵活的加锁方式,并且可以防止死锁问题的发生。但是,Lock 接口的使用相对较复杂,需要手动进行加锁和解锁操作。
  • ThreadLocal 类:ThreadLocal 类提供了线程本地变量的功能,可以让每个线程拥有自己的变量副本,从而避免了多个线程之间的共享问题。但是,ThreadLocal 类的使用需要注意内存泄漏问题。

关于 ThreadLocal,它是 Java 中一个非常重要的类,用于实现线程本地存储,即让每个线程都拥有自己的变量副本,从而避免多个线程之间的共享问题。

Spring 框架中,ThreadLocal 类经常用于存储一些与当前线程相关的数据,例如请求上下文、用户信息等。通过将这些数据存储到 ThreadLocal 对象中,可以方便地在整个应用程序中进行访问和传递,同时避免了多个线程之间的共享问题

ThreadLocal 类的使用需要注意内存泄漏问题,因为线程本地变量只有在对应的线程被回收时才会被回收,如果没有及时清理,就可能导致内存泄漏问题。因此,在使用 ThreadLocal 类时,需要注意在不需要存储数据时及时调用 remove() 方法清理 ThreadLocal 对象中的数据。

鱼皮的补充:这题大家可以结合自己的项目去举例并发编程和 ThreadLocal 的应用

鱼友的精彩回答

Starry 的回答

Spring 如何处理线程并发问题?

Spring 中处理线程并发问题的主要方式是使用线程安全的对象并发包中提供的类来避免线程安全问题。

例如,Spring 中的单例 Bean 是线程安全的,因为 Spring 容器在创建单例 Bean 时会确保只有一个实例存在。Spring 还提供了对多线程的支持,例如使用 @Async 注解实现异步方法调用等。

在 Spring 中处理线程并发问题,常用的方法有以下几种:

  • Synchronized 关键字:使用 synchronized 关键字可以锁定某个对象或类,使得多个线程无法同时进入该代码块,从而保证数据的安全性。

  • ReentrantLock:与 synchronized 相比,ReentrantLock 提供了更加灵活的加锁方式,可以控制锁的获取和释放的时机,提供了更多的扩展功能。

  • Atomic 包:Java 的 Atomic 包提供了一些原子性操作,例如 AtomicLong、AtomicInteger 等,可以保证某个操作的原子性。

  • ThreadLocal:ThreadLocal 可以使得每个线程拥有自己的变量副本,避免了多个线程之间共享变量所带来的线程安全问题。

ThreadLocal 你了解过吗?

ThreadLocal 是 Java 中的一个线程局部变量,它可以为每个线程提供一个独立的变量副本,避免了多线程之间的数据共享数据竞争问题

ThreadLocal 可以在每个线程中存储一个对象,并且每个线程只能访问自己的对象,而不会影响其他线程中的对象。

ThreadLocal 主要用于解决线程安全问题,例如在 Web 应用中,可以使用 ThreadLocal 存储用户的会话信息,这样每个线程就可以独立地访问自己的会话信息,避免了多个线程之间的数据共享数据竞争问题。

在 Spring 中,ThreadLocal 通常用于存储和传递一些与线程相关的上下文信息,例如当前登录用户的信息、请求的 IP 地址等。可以将 ThreadLocal 对象定义为一个 Bean,然后在需要使用时注入到其他 Bean 中使用。

Spring 还提供了一些与 ThreadLocal 相关的类和工具,例如 SimpleThreadScope,用于实现线程范围内的 Bean,以及 TaskExecutor,用于在多线程环境中执行任务。

前端

题目一

什么是前端路由?什么时候适合使用前端路由?它有哪些优点和缺点?

官方解析

前端路由是指在前端页面内部实现页面之间的跳转,而不是像传统的网页跳转那样在后端进行页面跳转。前端路由使用浏览器的 history 接口,通过改变浏览器的 URL,来更新页面的视图。

前端路由适合用于单页面应用(SPA)的开发。当一个应用中需要经常切换页面时,使用前端路由可以避免频繁向服务器发起请求,提高页面切换的速度和用户体验。

前端路由的优点:

  • 单页面应用的页面跳转速度快,用户体验好
  • 可以根据不同的 URL 显示不同的页面内容,可以更好地实现前端的页面控制
  • 可以更好地实现前端路由权限控制

前端路由的缺点:

  • 不支持搜索引擎爬虫,对于 SEO 不利
  • 对于复杂的页面控制逻辑和状态管理,需要额外的工作
  • 需要注意浏览器的前进后退等操作对页面的影响

总的来说,前端路由适用于对页面跳转速度和用户体验有要求的单页面应用,但在一些场景下,需要权衡其带来的一些缺点。

鱼友的精彩回答

luckythus 的回答

什么是前端路由?

前端路由是指:在前端中使用JavaScript实现的页面切换路由系统,它可以根据URL的变化,通过修改DOM来实现单页面应用SPA的页面切换效果,无需每次请求页面时都要从服务器获取完整的HTML页面

在传统的Web应用中,每次点击页面链接或刷新页面时,浏览器都会向服务器发送请求,接收服务器返回的HTML页面,而在使用前端路由的SPA单页面应用中,页面的切换是通过JavaScript动态修改DOM内容来实现的,这样可以避免每次都要向服务器请求页面时的开销,从而提高页面的响应速度和用户体验

我是这样理解的(不知道对不对) :传统的Web应用,将所有的 HTML、CSS 和 JavaScript 文件都存放在服务器上,然后网页切换页面时,每次都要发送请求来接收服务器响应回来的HTML页面。而使用前端路由的SPA单页面应用,是第一次加载网页时,发送http请求,请求服务器返回HTML、CSS、JavaScript文件等静态资源,这些文件被下载到浏览器中,并存储在浏览器的缓存中,当用户在SPA单页面应用进行页面切换时,前端路由会根据浏览器中对应URL文件的路径信息,动态地加载相应的JavaScript文件,并执行里面的逻辑来更新切换页面内容。

什么时候适合使用前端路由?

前端路由适合用于构建单页面应用,特别是需要快速响应用户操作,避免不必要的页面刷新应用,例如需要高度交互的应用,如社交网络,音乐播放器

它有哪些优点和缺点?

优点:

  • 快速响应:前端路由进行页面切换时不需要向服务器发送http请求,从而快速响应用户操作,提高加载速度
  • 降低服务器压力:采用前端路由,降低了请求次数,减轻了服务器的负担。
  • 提高应用的交互性:前端路由实现页面无缝切换,避免了传统应用中页面卡顿白屏等现象

缺点:

  • 不利于SEO,由于前端路由是根据文件里面的URL修改DOM来切换,而不是加载新的HTML页面,所以不利于搜索引擎的搜索。
  • 初次加载慢:由于前端路由需要在初次加载时候将所有的静态资源(HTML、CSS、JavaScript)文件都加载到客户端,因此首次加载时间较长,影响用户体验
  • 复杂度高:前端路由需要在客户端处理好切换页面逻辑以及前进后退等操作,增加了应用的复杂度。

鱼皮评论:理解正确

useGieGie 的回答

前端路由是指在Web应用程序中,将URL和对应的视图或组件之间的映射关系交给前端来处理的一种技术。具体来说,前端路由会拦截浏览器的URL请求,然后根据URL中的信息,决定哪些组件或页面需要渲染。

前端路由适合用于单页面应用程序(Single Page Application,SPA)中,因为这种应用程序只有一个HTML页面,所有的内容都是通过JavaScript来动态更新的。在这种情况下,前端路由可以让用户通过浏览器的后退和前进按钮,来切换不同的应用程序状态,而不会重新加载整个页面。

前端路由的优点包括:

  1. 快速响应:前端路由可以快速响应用户的操作,因为不需要向服务器发起新的请求。

  2. 提高用户体验:前端路由可以让用户在不刷新页面的情况下,快速地浏览和切换不同的应用程序状态,提高用户的交互体验。

  3. 更好的性能:前端路由可以减少服务器的负担,因为不需要频繁地向服务器发起请求。同时,前端路由可以利用浏览器的缓存机制,提高页面的加载速度。

  4. 状态管理:前端路由可以帮助开发人员更好地管理应用程序的状态,使得应用程序更加可控。

  5. 提高代码可维护性:前端路由可以将应用程序的不同功能拆分成多个组件,使得代码更加清晰易懂,便于维护和更新。

  6. 可以实现无刷新页面加载:使用前端路由可以在不刷新整个页面的情况下,更新页面部分内容。

前端路由的缺点包括:

  1. 不支持搜索引擎优化:由于前端路由是通过JavaScript来控制页面的显示和隐藏,搜索引擎无法读取页面内容,因此可能会影响SEO。

  2. 增加开发难度:前端路由需要开发人员编写复杂的JavaScript代码,对开发人员的技能要求较高。

  3. 可能会增加页面加载时间:由于前端路由需要在浏览器中加载更多的JavaScript代码,可能会导致页面加载时间变慢。

  4. 前进、后退不够可靠:前端路由通过JavaScript控制页面的显示和隐藏,如果用户手动修改URL,可能会导致前进、后退等浏览器行为失效。

  5. 不支持浏览器回退:由于前端路由是通过JavaScript控制页面的显示和隐藏,无法支持浏览器回退功能。

  6. 可能会增加代码复杂性:使用前端路由需要对路由、状态管理等相关概念有一定的理解,并需要编写较为复杂的JavaScript代码,这可能会增加代码的复杂性。

除此之外,还有以下需要注意的点:

  1. 前端路由需要注意浏览器兼容性。不同浏览器可能会有不同的实现,需要进行兼容性测试和处理。

  2. 前端路由应该合理使用,不要滥用。如果应用程序只有少量页面和交互,使用前端路由可能并不合适,反而会增加代码复杂性和维护难度。

  3. 在使用前端路由时,应该注意安全性。由于前端路由可以通过URL来控制页面的显示和隐藏,如果不进行正确的安全性处理,可能会被恶意攻击者利用来实现跨站脚本攻击(XSS)等安全问题。

  4. 如果需要实现SEO(搜索引擎优化),可以考虑使用服务端渲染(Server-Side Rendering,SSR)等技术来处理。SSR可以让服务器将网页的HTML内容直接输出给浏览器,从而避免了前端路由无法被搜索引擎抓取的问题。

  5. 前端路由可以使用不同的实现方式,例如基于History API的路由和基于Hash的路由。基于History API的路由使用真实的URL,比较直观,但是需要服务器端的支持;基于Hash的路由则可以在不需要服务器端支持的情况下实现,但是URL可能会显得比较丑陋。

  6. 前端路由应该尽可能的简化URL,避免使用过于复杂的URL,以提高用户体验和SEO效果。同时,也需要注意URL的可读性和易记性,以方便用户进行页面导航和分享。

  7. 前端路由可以结合使用其他技术,例如状态管理库(例如Redux)和异步加载(例如按需加载),以提高应用程序的性能和可维护性。

  8. 前端路由也需要考虑性能问题。如果路由配置过多或者页面内容过于复杂,可能会导致页面加载速度变慢。因此,在使用前端路由时需要进行性能优化,例如使用按需加载和代码分割等技术。

  9. 前端路由可以结合使用路由守卫(Route Guard)来实现权限控制和页面跳转等功能。路由守卫可以在路由跳转前或者跳转后执行相应的逻辑,例如检查用户是否有权限访问某个页面、记录用户浏览历史、防止用户在未登录的情况下访问需要登录的页面等。

  10. 前端路由可以使用编程式导航(Programmatic Navigation)来实现页面跳转。编程式导航是指在JavaScript代码中通过调用路由实例的API来进行页面跳转,而不是通过点击链接或者刷新页面来触发路由跳转。编程式导航可以灵活控制路由跳转的时机和方式,从而提高用户体验和页面交互性。

  11. 前端路由还可以结合使用动画效果来实现页面过渡和切换效果,从而提高用户体验。例如,可以在页面切换时添加渐变动画、滑动动画等效果,以增强页面之间的连贯性和视觉效果。

  12. 在使用前端路由时,需要注意内存管理和页面卸载等问题。由于前端路由是单页面应用程序,页面之间的切换是通过JS实现的,因此需要注意内存泄漏和页面卸载等问题,以避免浪费内存和影响应用程序性能。

  13. 前端路由可以使用路由参数(Route Parameters)来传递数据。路由参数是指在URL中使用特定的占位符来表示动态的数据,例如在URL中使用:id来表示某个资源的唯一标识符。路由参数可以在路由配置中定义,并通过路由实例的API来访问和传递数据,以实现动态的页面内容和交互效果。

  14. 前端路由还可以使用路由导航钩子(Route Navigation Hook)来实现页面跳转前和跳转后的逻辑处理。路由导航钩子是指在路由跳转前或者跳转后执行相应的逻辑,例如在跳转前检查用户是否登录、在跳转后记录用户浏览历史等。路由导航钩子可以在路由配置中定义,并通过路由实例的API来访问和执行。

  15. 前端路由还可以使用动态路由(Dynamic Route)来实现动态页面的创建和配置。动态路由是指在路由配置中使用动态的路径来匹配不同的URL,例如在路径中使用:id来匹配不同的资源标识符。动态路由可以实现动态的页面生成和路由配置,从而提高应用程序的灵活性和可扩展性。

  16. 前端路由可以通过异步组件加载(Lazy Loading)来优化应用程序性能。异步组件加载是指在需要使用某个组件时再进行组件的加载和初始化,而不是在应用程序启动时一次性加载所有组件。通过异步组件加载,可以减少应用程序的初始化时间和内存占用,从而提高应用程序的性能和响应速度。

  17. 前端路由还可以结合使用状态管理器(State Management)来实现全局状态的管理和共享。状态管理器是指在应用程序中使用专门的库或框架来管理应用程序的全局状态,例如使用Vuex或Redux来管理应用程序的状态。通过状态管理器,可以实现全局状态的共享和响应式更新,从而提高应用程序的可维护性和可扩展性。

  18. 前端路由还可以使用路由别名(Route Alias)来实现URL的重定向和简化。路由别名是指在路由配置中使用一个路径别名来代替另一个路径,例如使用/alias代替/path/to/some/page。通过路由别名,可以实现URL的简化和重定向,从而提高用户体验和页面交互性。

  19. 前端路由可以使用路由元信息(Route Meta)来实现路由级别的元数据管理。路由元信息是指在路由配置中使用特定的属性来定义路由的元数据,例如页面标题、页面布局、访问权限等。通过路由元信息,可以实现路由级别的元数据管理和访问,从而提高应用程序的灵活性和可维护性。

  20. 前端路由还可以使用嵌套路由(Nested Route)来实现复杂页面的组合和嵌套。嵌套路由是指在路由配置中使用嵌套的路由结构来实现页面的组合和嵌套,例如使用子路由来组合不同的页面组件。通过嵌套路由,可以实现复杂页面的分解和组合,从而提高应用程序的可维护性和可扩展性。

  21. 前端路由还可以使用路由拦截器(Route Interceptor)来实现路由级别的拦截和处理。路由拦截器是指在路由跳转前或者跳转后执行相应的拦截和处理逻辑,例如在跳转前检查用户是否登录、在跳转后记录用户浏览历史等。通过路由拦截器,可以实现路由级别的安全检查和状态管理,从而提高应用程序的可靠性和安全性。

鱼皮评论:👍🏻

题目二

怎么解决跨域问题?

官方解析

跨域问题(Cross-Origin Resource Sharing,CORS)是由于浏览器的同源策略(Same-Origin Policy)导致的。同源策略指的是,如果两个 URL 的协议、主机名和端口号都相同,那么它们就是同源的,否则就是跨域的。当网页发起跨域请求时,浏览器会根据同源策略限制请求。解决跨域问题的方法有以下几种:

JSONP(JSON with Padding)

JSONP 是一种跨域请求数据的方式,它利用了 <script> 标签不受同源策略限制的特性,可以从不同的域名请求数据。实现原理是在服务端生成一个 JavaScript 函数,客户端使用 <script> 标签请求该函数,服务端返回该函数的调用,并将需要传输的数据作为函数参数传入。

CORS(Cross-Origin Resource Sharing)

CORS 是一种通过添加一些 HTTP 头来允许浏览器跨域访问资源的机制,主要是服务端配置。服务端需要在响应头中添加 Access-Control-Allow-Origin 和其他一些参数,指示允许哪些域名进行跨域请求。

反向代理

反向代理是将客户端的请求转发到真正的服务端,从而解决跨域问题。反向代理服务器和真正的服务端在同一个域名下,客户端的请求只需要向反向代理服务器发起,由反向代理服务器将请求转发到真正的服务端,最后将响应返回给客户端。

WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,可以用于跨域通信。由于 WebSocket 协议并不受同源策略的限制,因此可以实现跨域通信。

总的来说,不同的解决方案有各自的优缺点,应根据实际情况选择最适合的方式。例如,如果需要在客户端和服务端之间进行实时通信WebSocket 是最佳选择;如果只需要在客户端发起简单的 GET 请求,可以使用 JSONP 等方式。

鱼皮补充:这题在回答时可以先把解决方案分为两类:服务端解决和客户端解决

鱼友的精彩回答

🤔的回答

解决跨域的方法有以下几种:

1.jsonp:利用script标签不受浏览器跨域的影响,前端通过src属性将回调函数和请求参数拼接在请求url地址后面并发送给后端,后端通过拼接请求数据和回调函数一并返回给前端,前端通过执行回调函数拿到请求数据。所以jsonp解决的跨域问题具有很明显的不足,它只能实现get方法的请求。对于post put delete 等其它请求无法实现。 具体代码实现:

// 前端
<script src="https://xxx.com/getUserInfo?id=212&callback=getUserInfoRes"></script>
const getUserInfoRes = ({userName}) => {
    console.log(userName)
}

// 后端 这里用 express 来模拟
app.get('/getUserInfo', (req, res) => {
    const {id, callback} = req.query
    const res = db.query('...', id, (err, res) => {
        res.send(`callback(res.data)`)
    })
})

2.Nginx代理实现:

{
    listen 80
    
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Headers X-Requested-With;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS
    location / {
        proxy_pass   http://www.domain.com:8080;
    }
}

3.CORS 服务器配置:配置Access-Control-Allow-Origin 为*(所有源)或允许访问的域名/ip地址,Access-Control-Allow-Method 配置请求方法,这是最为常见解决跨域的方法。

4.WebSocket 协议通讯:利用 HTML5 提供的新协议,实现浏览器与服务器的全双工通信,从而解决浏览器的跨域问题。

具体实现:

// 前端
const socket = new WebSocket('http://www.domain.com:8080')
socket.addEventListener('open'function () {
    socket.send('...')
})
socket.addEventListener('message'function(e) {
    console.log(e.data)
})

// 后端 nodejs

const WebSocket = require('ws')
const server =  new WebSocket.Server({post: 8080})
server.addEventListener('connection'function(socket) {
    socket.addEventListener('message'function(res) {
        socket.send(res)
    })
})

mos 的回答

跨域问题(Cross-Origin Resource Sharing,CORS)是由于浏览器的同源策略(Same-Origin Policy)导致的。同源策略指的是,如果两个 URL 的协议、主机名和端口号都相同,那么它们就是同源的,否则就是跨域的。当网页发起跨域请求时,浏览器会根据同源策略限制请求。解决跨域问题的方法有以下几种:

1.JSONP

因为浏览器同源策略的存在,导致存在跨域问题,以下这三个标签加载资源路径是不受束缚的

1. script标签:<script src="加载资源路径"></script>
2. link标签:<link herf="加载资源路径"></link>
3. img标签:<img src="加载资源路径"></img>

而JSONP就是利用了script的src加载不受束缚,从而可以拥有从不同的域拿到数据的能力,但是JSONP需要前端后端配合,才能实现最终的跨域获取数据。

JSONP通俗点说就是:利用script的src去发送请求,将一个方法名callback传给后端,后端拿到这个方法名,将所需数据,通过字符串拼接成新的字符串callback(所需数据),并发送到前端,前端接受到这个字符串之后,就会自动执行方法callback(所需数据)

前端代码

// index.html http://127.0.0.1:5500/index.html

    const jsonp = (url, params, cbName) => {
        return new Promise((resolve,reject) => {
           const script = document.createElement('script')
           window[cbName] = (data) => {
               resolve(data)
               document.body.removeChild(script)
           }
           params = { ...params, callback:cbName }
           const str = Object.keys(params).map(key => `${key}=${params[key]}`)
           script.src = `${url}?${str.join('&')}`
           document.body.appendChild(script)
        })
    }
    
    jsonp('http://127.0.0.1:8000',{ name:'mos', age:18},'callback').then(data => {
        console.log(data) // mos今年18岁了!!!
    })

后端代码

// index.js http://127.0.0.1:8000
const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function(req,res){
    const { query } = urllib.parse(req.url,true);
    if(query && query.callback){
        const { name, age, callback } = query
        const person = `${name}今年${age}岁了!!!`
        const str = `${callback}(${JSON.stringify(person)})` //拼成callback(data)
        res.end(str)
    }else{
        res.end(JSON.stringify('没东西啊你'));
    }
}).listen(port,function(){
    console.log('server is listening on port ' + port);
})

JSONP的缺点就是,需要前后端配合,并且只支持get请求方法

2.WebSocket

WebSocket根本不附属于同源策略,而且它本身就有意被设计成可以跨域的一个手段。由于历史原因,跨域检测一直是由浏览器端来做,但是WebSocket出现以后,对于WebSocket的跨域检测工作就交给了服务端,浏览器仍然会带上一个Origin跨域请求头,服务端则根据这个请求头判断此次跨域WebSocket请求是否合法

前端代码

// index.html http://127.0.0.1:5500/index.html
    function myWebsocket(url,params){
        return new Promise((resolve,reject)=>{
            const socket = new WebSocket(url)
            socket.onopen = () => {
                socket.send(JSON.stringify(params))
            }
            socket.onmessage = (e) => {
                resolve(e.data)
            }
        })
    }
    myWebsocket('ws://127.0.0.1:8000',{ name:'mos', age:18 }).then(data=>){
        console.log(data) // mos今年18岁了!!!
    }

后端代码

// index.js http://127.0.0.1:8000
const Websocket = require('ws');

const port = 8000;
const ws = new Websocket.Server({ port })
ws.on('connection',(obj) =>{
    obj.on('message',(data) =>{
        data = JSON.parse(data.toString())
        const { name, age } = data
        obj.send(`${name}今年${age}岁了!!!`)
    })
})
3.Cors

Cors,全称Cross-Origin Resource Sharing,意思是跨域资源共享,Cors一般是由后端来开启的,一旦开启,前端就可以跨域访问后端。

前端跨域访问到后端,后端开启Cors,发送Access-Control-Allow-Origin:域名字段到前端(其实不止一个),前端浏览器判断Access-Control-Allow-Origin的域名如果跟前端域名一样,浏览器就不会实行跨域拦截,从而解决跨域问题。

前端代码
// index.html http://127.0.0.1:5500/index.html
    // 步骤一:创建异步对象
    var ajax = new XMLHttpRequest();
    // 步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数
    ajax.open('get','http://127.0.0.1:8000?name=林三心&age=23');
    // 步骤三:发送请求
    ajax.send();
    // 步骤四:注册事件 onreadystatechange 状态改变就会调用
    ajax.onreadystatechang = function(){
        if(ajax.readyState == 4 && ajax.status == 200){
            // 步骤五 如果能够进到这个判断 说明数据完美的回来了,并且请求的页面是存在的
            console.log(ajax.responseText);
        }
    }

后端代码

// index.js http://127.0.0.1:8000

const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function(req,res){
    // 开启Cors
    res.writeHead(200,{
        // 设置允许跨域的域名,也可设置*允许所有域名
        'Access-Control-Allow-Origin''http://127.0.0.1.5500',
        // 跨域允许的请求方法,也可以设置*允许所有方法
        "Access-Control-Allow-Methods"'DELETE,PUT,POST,GET,OPIIONS',
        // 允许的header类型
        'Access-Contorl-Allow-Headers''Content-Type'
    })
    const { query : { name, age } } = urllib.parse(req.url, true);
    res.end(`${name}今年${age}岁啦!!!`);
}).listen(port,function(){
    console.log('server is listening on port ' + port);
})

4.反向代理 Node接口代理

同源策略它只是浏览器的一个策略而已,它是不限制后端的,也就是前端-后端会被同源策略限制,但是后端-后端则不会被限制,所以可以通过Node接口代理,先访问已设置Cors的后端1,再让后端1去访问后端2获取数据到后端1,后端1再把数据传到前端

前端代码

// index.html  http://127.0.0.1:5500

//步骤一:创建异步对象
    var ajax = new XMLHttpRequest();
    //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
    ajax.open('get''http://127.0.0.1:8888?name=mos&age=18');
    //步骤三:发送请求
    ajax.send();
    //步骤四:注册事件 onreadystatechange 状态改变就会调用
    ajax.onreadystatechange = function () {
        if (ajax.readyState == 4 && ajax.status == 200) {
            //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
            console.log(ajax.responseText);//输入相应的内容
        }
    }

后端1代码

// index2.js  http://127.0.0.1:8888

const http = require('http');
const urllib = require('url');
const querystring = require('querystring');
const port = 8888;

http.createServer(function (req, res) {
    // 开启Cors
    res.writeHead(200, {
        //设置允许跨域的域名,也可设置*允许所有域名
        'Access-Control-Allow-Origin''http://127.0.0.1:5500',
        //跨域允许的请求方法,也可设置*允许所有方法
        "Access-Control-Allow-Methods""DELETE,PUT,POST,GET,OPTIONS",
        //允许的header类型
        'Access-Control-Allow-Headers''Content-Type'
    })
    const { query } = urllib.parse(req.url, true);
    const { methods = 'GET', headers } = req
    const proxyReq = http.request({
        host: '127.0.0.1',
        port: '8000',
        path: `/?${querystring.stringify(query)}`,
        methods,
        headers
    }, proxyRes => {
        proxyRes.on('data', chunk => {
            console.log(chunk.toString())
            res.end(chunk.toString())
        })
    }).end()
}).listen(port, function () {
    console.log('server is listening on port ' + port);
})

后端2代码

// index.js http://127.0.0.1:8000

const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function (req, res) {
    console.log(888)
    const { query: { name, age } } = urllib.parse(req.url, true);
    res.end(`${name}今年${age}岁啦!!!`)
}).listen(port, function () {
    console.log('server is listening on port ' + port);
})
Nginx

其实Nginx跟Node接口代理是一个道理,只不过Nginx就不需要我们自己去搭建一个中间服务

server{ 
    listen 8888; 
    server_name 127.0.0.1;
    
    location /{
        proxy_pass 127.0.0.1:8000; 
    } 
  }
5.postMessage

场景:http://127.0.0.1:5500/index.html 页面中使用了iframe标签内嵌了http://127.0.0.1:5555/index.html 的页面

虽然这两个页面存在于一个页面中,但是需要iframe标签来嵌套才行,这两个页面之间是无法进行通信的,因为他们端口号不同,根据同源策略,他们之间存在跨域问题

那应该怎么办呢?使用postMessage可以使这两个页面进行通信

// http:127.0.0.1:5500/index.html

<body>
    <iframe src="http://127.0.0.1:5555/index.html" id="frame"></iframe>
</body>
<script>
    document.getElementById('frame').onload = function () {
        this.contentWindow.postMessage({ name:'mos', age:18 }, 'http://127.0.0.1:5555')
        window.onmessage = function (e) {
            console.log(e.data) // mos今年18岁啦!!!
        }
    }
</script>
// http://127.0.0.1:5555/index.html
<script>
        window.onmessage = function (e) {
            const { data: { name, age }, origin } = e
            e.source.postMessage(`${name}今年${age}岁啦!!!`, origin)
        }
</script>

题目三

什么是 Vuex?使用 Vuex 有哪些好处?

官方解析

Vuex是Vue.js框架中用于实现集中式状态管理的插件。它的主要作用是在多个组件之间共享状态,并且提供了一些工具来方便地管理应用程序的状态。

使用Vuex可以将应用程序的状态存储在一个集中的地方,从而使状态管理更加容易、可维护性更高。它还提供了一些工具来简化状态的更改和操作,例如:在组件中使用mutations来修改状态,或者使用actions来异步操作数据。

使用Vuex的好处包括:

  1. 集中化的状态管理:将应用程序的状态集中存储在一个地方,可以方便地进行状态管理和维护。
  2. 易于调试:使用Vuex可以方便地跟踪和记录状态的更改历史,有助于快速诊断和解决问题。
  3. 状态共享:在多个组件之间共享状态,避免了组件之间繁琐的传值,提高了组件之间的解耦性。
  4. 插件化:Vuex提供了插件机制,可以方便地扩展和自定义Vuex的功能。

Vuex的缺点包括:

  1. 增加了学习成本:Vuex相对于直接在组件中管理状态来说,增加了一些学习成本,需要花费时间去学习Vuex的概念和使用方式。
  2. 增加了代码复杂度:在使用Vuex的过程中,需要增加一些额外的代码来管理状态,有时可能会增加代码的复杂度。

鱼皮补充:注意,不是说什么情况下都要用 Vuex,要理解【状态管理】的含义。因为有的时候你自己定义一个全局变量也许就能解决问题

鱼友的精彩回答

mos 的回答

vuex的概念

vuex是一个专为 Vue.js 应用程序开发的状态管理模式,采用集中式存储管理应用的所有组件的状态,解决多组件数据通信。(简单来说就是管理数据的,相当于一个仓库,里面存放着各种需要共享的数据,所有组件都可以拿到里面的数据)

使用Vuex管理数据的好处:

1、能够在vuex中集中管理共享的数据,易于开发和后期维护;

2、能够高效地实现组件之间的数据共享,提高开发效率;

3、存储在vuex的数据都是响应式的,能够实时保持数据与页面的同步;

你还费解吗的回答

简单的介绍

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。简单理解,Vuex 维护了一个对象,该对象存储了 Vue 应用中多个组件所需要共同使用的变量,使得组件可以共享它们,当对象中的变量发生变化时,不同组件中使用变量的地方(视图)也会相应地更新。其中,状态自管理应用包含以下几个部分:

state,驱动应用的数据源;

view,以声明方式将 state 映射到视图;

actions,响应在 view 上的用户输入导致的状态变化。下图为单向数据流示意图:

当应用中遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

优势

响应式:相比于一个简单的全局对象,Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会进行高效的更新,从而确保了单向数据流的简洁性不被破坏。

集中化管理:Vuex 通过把组件的共享状态抽取出来,以全局单例模式管理,这样任何组件都能用一致的方式获取和修改状态,使代码变得更具结构化且易于维护。

插件式扩展:Vuex 允许开发者编写插件来扩展其功能,比如实现日志记录、持久化存储和调试等。也就是说,开发者可以根据应用程序的需求来选择性地扩展其功能。

高度集成:由于 Vuex 是专门为 Vue.js 设计的,因此可以与 Vue.js 高度集成。在使用 Vuex 的同时,还可以利用 Vue.js 的许多特性,如指令、组件、计算属性等来构建更强大的应用程序。

应用场景

如果需要开发大型单页应用或应用里需要维护大量全局的状态,就可以使用 Vuex,否则,一个简单的 store 模式就够了。

用法

Vuex 将全局状态放入 state 对象中,它本身是一颗状态树,组件中使用 store 实例的 state 访问这些状态。

然后用配套的 mutation 方法修改这些状态,并且只能用 mutation 修改状态,在组件中调用 commit 方法提交 mutation。

如果应用中有异步操作或复杂逻辑组合,需要编写 action,执行结束如果有状态修改仍需提交 mutation,组件中通过 dispatch 派发 action。

最后是模块化,通过 modules 选项组织拆分出去的各个子模块,在访问状态(state)时需注意添加子模块的名称,如果子模块有设置 namespace,那么提交 mutation 和派发 action 时还需要额外的命名空间前缀。

星球活动

1.欢迎参与 30 天面试题挑战活动 ,搞定高频面试题,斩杀面试官!

2.欢迎已加入星球的同学 免费申请一年编程导航网站会员

3.欢迎学习 鱼皮最新原创项目教程,手把手教你做出项目、写出高分简历!

加入我们

欢迎加入鱼皮的编程导航知识星球,鱼皮会 1 对 1 回答您的问题、直播带你做出项目、为你定制学习计划和求职指导,还能获取海量编程学习资源,和上万名学编程的同学共享知识、交流进步。

💎 加入星球后,您可以:

1)添加鱼皮本人微信,向他 1 对 1 提问,帮您解决问题、告别迷茫!点击了解详情

2)获取海量编程知识和资源,包括:3000+ 鱼皮的编程答疑和求职指导、原创编程学习路线、几十万字的编程学习知识库、几十 T 编程学习资源、500+ 精华帖等!点击了解详情

3)找鱼皮咨询求职建议和优化简历,次数不限!点击了解详情

4)鱼皮直播从 0 到 1 带大家做出项目,已有 50+ 直播、完结 3 套项目、10+ 项目分享,帮您掌握独立开发项目的能力、丰富简历!点击了解详情

外面一套项目课就上千元了,而星球内所有项目都有指导答疑,轻松解决问题

星球提供的所有服务,都是为了帮您更好地学编程、找到理想的工作。诚挚地欢迎您的加入,这可能是最好的学习机会,也是最值得的一笔投资!

长按扫码领优惠券加入,也可以添加微信 yupi1085 咨询星球(备注“想加星球”):

继续滑动看下一个

怎么解决跨域问题?

编程导航和鱼友们 面试鸭
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存