前端面试题总结-补01

36 minute read

前端面试题第二波 前端面试题总结

计算机网络总结

常见代码题

实习相关

主要记录一下当面试官问道暑期实习的相关问题时,该怎么回答。

介绍一下实习期间的工作

(2018年美团实习)实习期间主要负责的是B端的各类工作,包括给统计每天各城市、各商家销售情况的统计图表类项目,主要的工作内容是页面和前端接口的编写;以及还有实习最后阶段开发用于内部负责数据分析工作同事使用的实现类似自定义页面的系统。

实习期间遇到的主要问题

主要问题是新框架的适应问题,实习期间用到了一种接口描述语言“thrift”(Thrift包含一套完整的栈来创建客户端和服务端程序。顶层部分是由Thrift定义生成的代码。而服务则由这个文件客户端和处理器代码生成。在生成的代码里会创建不同于内建类型的数据结构,并将其作为结果发送。协议和传输层是运行时库的一部分。有了Thrift,就可以定义一个服务或改变通讯和传输协议,而无需重新编译代码。除了客户端部分之外,Thrift还包括服务器基础设施来集成协议和传输,如阻塞、非阻塞及多线程服务器。栈中作为I/O基础的部分对于不同的语言则有不同的实现),还有就是开发过程中会用到一些小组内部重构或整合的UI库,随着react版本的迭代,很多库都变得老化臃肿,所以开发过程中还需要不断地踩坑填坑。(待补充,一两个react组建的坑)

实习期间最大的收获是什么

实习期间最大的收获首先是认识了很多不错的同事,同事会进行code view,所以经过实习之后,会更注意代码规范。

introduce your self

Hello thank you for giving me the opportunity for this interview, my name is yangyukun and I will graduate from Wuhan Institute of Technology this year. I have a two-month internship as a front-end engineer in Meituan, and have been involved in a lot of to-B projects. I’ve always been interested in programming and enjoy everything that I do. I know that xx is a technology-driven company, which I am very interested in, so Good luck today.

Javascript

三种web会话管理

cnblogs.com/lyzg HTTP是无状态的,一次请求结束,连接断开,下次服务器在收到请求,它知道从哪个客户端地址发过来的,但无法精确到用户,但是对于应用来讲,我们靠的是用户来管理,而不是客户端。所以对我们的应用而言,它是需要有状态管理的,以便服务端能够准确的知道http请求是哪个用户发起的,从而判断他是否有权限继续这个请求。

常见的三种web会话管理方式:

  • 基于server端session的管理方式

    服务端session简单的理解就是服务端在用户第一次访问应用(往往就是登录的时候)时创建一个凭证为session,每个session有一个唯一的sessionId,然后这个sessionId可以在cookie中携带,已达到作为会话管理的功能;另外这个session往往有一个失效时间,对应的便是登录失效的时间,这个时间一般都设定为最后一次登录往后延迟xx小时。

    这个过程的流程图: session流程图 由于 sessionId是随机的,所以可以保证一定的安全性,但也有被劫持的危险,但这种方式将会话信息存储在web服务器里面,所以在用户同时在线量比较多时,这些会话信息会占据比较多的内存;同时也存在如何在多个服务器间共享的问题,以及跨域等等问题。

  • cookie-based的管理方式

    cookie-based和下面的token-based相对于session都是实现了服务端的无状态化,彻底移除了服务端对会话的管理的逻辑;流程图如下 cookie-based流程图

  • 与token-based的管理方式 token-based流程图

    JavaScript内存泄露

    原文链接

  • 什么是内存储泄露?本质上,内存泄漏可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。

  • JavaScript语言的内存泄露,JavaScript 是一种垃圾回收语言。垃圾回收语言通过周期性地检查先前分配的内存是否可达,帮助开发者管理内存。换言之,垃圾回收语言减轻了“内存仍可用”及“内存仍可达”的问题。两者的区别是微妙而重要的:仅有开发者了解哪些内存在将来仍会使用,而不可达内存通过算法确定和标记,适时被操作系统回收。

  • 例子
    1. 全局变量,当在函数内部没有使用var或let、const申明变量时,会默认生成一个全局对象;还有当函数调用自己时,函数中的this指向最近一父级作用域层,此时往往就是window。
    2. 计时器和回调函数
    3. 闭包,以下为代码示例
      var theThing = null;
      var replaceThing = function () {
          var originalThing = theThing;
          var unused = function () {
              if (originalThing)
              console.log("hi");
          };
          theThing = {
              longStr: new Array(1000000).join('*'),
              someMethod: function () {
              console.log(someMessage);
              }
          };
      };
      setInterval(replaceThing, 1000);
    

    代码片段做了一件事情:每次调用 replaceThing ,theThing 得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量 unused 是一个引用 originalThing 的闭包(先前的 replaceThing 又调用了 theThing )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod 可以通过 theThing 使用,someMethod 与 unused 分享闭包作用域,尽管 unused 从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄漏。在 replaceThing 的最后添加 originalThing = null 可以解决问题。

前端页面优化

  • 资源压缩合并,合并图片,css、js文件压缩
  • 利用浏览器缓存,分为强缓存和协商缓存
    1. 强缓存不用请求服务器,直接使用本地的缓存。强缓存是利用 http 响应头中的Expires(绝对时间)或Cache-Control(服务器返回的相对时间,每次都会更新)实现的。
    2. 协商缓存:浏览器发现本地有资源的副本,但是不太确定要不要使用,于是去问问服务器。当浏览器对某个资源的请求没有命中强缓存(也就是说超出时间了),就会发一个请求到服务器,验证协商缓存是否命中。有两对header分别为Last-ModifiedIf-Modified-SinceETagIf-None-Match
  • 使用cdn,CDN服务,其实就是把页面静态内容或静态化的动态内容缓存到不同地区很多台专门的缓存服务器上(如图片缓存服务器,静态HTML缓存服务器、文件缓存服务器等), 然后根据用户线路所在的地区通过CND服务商的智能DNS自动选择一个最近的缓存服务器让用户访问,如果静态缓存服务器中没有请求的内容,才向动态服务器发出请求,以此提高速度,这种方案对页面静态内容效果非常好,所以Web设计中页面静态化是很有必要的。
  • DNS预解析,通过 DNS 预解析来告诉浏览器未来我们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。
  • 关键渲染路径优化,延迟加载JavaScript(设置defer或async)、确保将任何非必需的 CSS 都标记为非关键资源(例如打印和其他媒体查询media=”print”)、尽早在 HTML 文档内指定所有 CSS 资源,以便浏览器尽早发现<link>标记并尽早发出 CSS 请求、避免使用 CSS import

还有一个高并发优化掘金

React & Redux & webpack

说一下react-redux

https://jin-yang.github.io/post/react-redux-introduce.html React-Redux 将所有组件分成两大类:UI 组件 (Presentational Component) 和容器组件 (Container Component)。

UI 组件有以下几个特征。

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态 (即不使用this.state这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 Redux 的 API 下面就是一个 UI 组件的例子。
    const Title = value => <h1>{value}</h1>;
    

    因为不含有状态,UI 组件又称为”纯组件”,即它纯函数一样,纯粹由参数决定它的值。

容器组件的特征恰恰相反。

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 Redux 的 API 总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。

React优化

  • 在React应用中,使用压缩过的生产版本,最好在开发应用时使用开发模式,部署应用时换为生产模式。
  • 合理使用shouldComponentUpdate
  • 避免使用值可能会突变的属性或状态,使用concat来追加数组或es6的展开语法。使用Object.assign来重写会突变的对象。(因为浅比较时,比较的是引用,所以如果不这样,可能会导致不会更新的bug)

    React事件机制

    https://zhuanlan.zhihu.com/p/35468208

React事件机制分为两部分:1、事件注册 2、事件分发

  • 事件注册:React在组件加载(mount)和更新(update)时,其中的ReactDOMComponent会对传入的事件属性进行处理,对相关事件进行注册和存储。document中注册的事件不处理具体的事件,仅对事件进行分发。ReactBrowserEventEmitter作为事件注册入口,担负着事件注册和事件触发。注册事件的回调函数由EventPluginHub来统一管理,根据事件的类型(type)和组件标识(_rootNodeID)为key唯一标识事件并进行存储。
  • 事件执行,事件执行时,document上绑定事件ReactEventListener.dispatchEvent会对事件进行分发,根据之前存储的类型(type)和组件标识(_rootNodeID)找到触发事件的组件。ReactEventEmitter利用EventPluginHub中注入(inject)的plugins(例如:SimpleEventPlugin、EnterLeaveEventPlugin)会将原生的DOM事件转化成合成的事件,然后批量执行存储的回调函,回调函数的执行分为两步,第一步是将所有的合成事件放到事件队列里面,第二步是逐个执行。需要注意的是,浏览器原生会为每个事件的每个listener创建一个事件对象,可以从这个事件对象获取到事件的引用。这会造成高额的内存分配,React在启动时就会为每种对象分配内存池,用到某一个事件对象时就可以从这个内存池进行复用,节省内存。

    redux和组件之间的联系

    Redux基础:

    首先看一下redux的工作流程: redux工作流程

  • Action,首先用户发出action,action把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store
  • Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state
  • Store 就是把Action和Reducers联系到一起的对象。Store 有以下职责:
    1. 维持应用的 state;
    2. 提供 getState() 方法获取 state;
    3. 提供 dispatch(action) 方法更新 state;
    4. 通过 subscribe(listener) 注册监听器;
    5. 通过 subscribe(listener) 返回的函数注销监听器。
  • State,Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。state 和 view 一一对应,一个 State 对应一个 View,只要 State 相同,View 就相同;反之亦然。

虚拟dom对比dom

首先,了解浏览器的工作流;

Diff算法

from zhihu.com

  • treeDiff

React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。 diff-01

  • element diff

当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。

INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。

MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。

REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。

  • 对于key的用法,如果没有key的话,根据diff算法,则当对比不对时,会直接删除旧的,追加新的 diff-02 而当为每个节点设置了唯一的key之后,则可以通过判断不同节点的key值,将节点不断后移即可。(注意是后移,因为在新集合之中,相对老集合处于后面位置的话,移动是不影响前面的,所以放一边,是一种优化) diff-03

redux中间件

中间件是允许我们扩展redux应用的功能,位于dispatch和reducers之间,意味着我们可以在数据流到达reducer之前修改我们的dispatch操作,或者在dispatch执行的过程中运行代码。

const customMiddleware = store => next => action => {
  ...
}

中间件是一个柯里化函数(一个函数返回另一个函数)。middleware接收一个store(redux的store),然后返回一个函数接收next函数(当已分配的任务被中间件完成时,调用next函数。它给我们的reducer或其他中间件发送actions),再返回另一个函数接收action函数(我们正在dispatch的action)

redux-thunk & redux-saga

from medium.com@shoshanarosenfield

我们为什么要用像redux-thunk这样的中间件?因为redux的store只支持同步的数据流;这时候中间件设计出来允许异步数据,解释所有的分发(dispatch),最后返回一个纯对象,恢复redux同步数据流,redux中间件可以解决很多关键的异步需求。

  • Redux-thunk

在redux上下文中,redux-thunk中间件允许你写一个action creators,返回一个函数,而不是通常的action对象,然后可以使用thunk来延迟action的dispatch,直到异步函数完成(例如,等到axios请求收到数据)。

Redux-thunk逐步过程的概括:

检查传进来的action,如果是普通的action对象,啥也不做;如果是函数,redux-thunk则会调用(invoke)它,并传入store的dispatch,getState等方法作为参数;在函数运行完后,thunk会dispatch action,相应的更新state。

Redux-thunk包含两部分,一个是thunk生成器,即一个生成thunk的action生成器;再一个是thunk本身,一个返回自thunk生成器并接收dispatch和setState作为参数的函数(thunk本身是个函数)

  • Redux-saga

Redux-saga旨在于使应用的副作用(像异步fetch数据)更好的处理,以及使执行更高效。其思想是,使saga分隔出来成为处理应用副作用的单独的线程;

与redux-thunk不同的是,可以使用普通的redux action从主应用程序来启动、暂停和取消一个的saga线程。和redux-thunk一样,saga也可以访问完整的redux应用状态(state),还可以调度(dispatch)redux的action。

Generators。redux-saga为完成这种功能,使用了ES6的新特性——Generator。Generator函数可以退出,并在随后重新进入的函数

总结起来,thunk和saga谁赢了?都没有,下面是两者实现同一功能的代码比较

Redux-thunk:redux-thunk Redux-saga:真的是用的generator函数呢 redux-saga

Saga的好处就是可以避免回调地狱,意味着可以避免传入函数,并在内部调用他们。此外,还可以更容易的测试异步数据流。调用和put方法返回js对象,因此简单的等价比较就可以测试saga函数返回的每一个值。redux-thunk返回promise,这很难测试。saga一票,函数结构好,容易测试

Thunk的优势就是简单,易于初学者,因为他所有的逻辑都包含在函数中。

webpack打包的过程

@taobaofed.org@medium.com

流程总览可以参考淘宝前端的图

webpack-process

webpack与其他工具像gulp和grunt有什么主要的不同

以下关于webpack的问题都来自于github.com

webpack是模块打包,这个工具为开发人员提供了分离模块的控制,允许他们根据特定的情况调整构建,并提供无法开箱即用的解决方案。

与Grunt比较,webpack给现代前端项目提供更加灵活和高级的功能。其一个核心功能就是可以使用特定的loaders(加载器)和plugins(插件)进行扩展。本质上,它用于将JavaScript文件与依赖项绑定到文件中,对于有大量非代码资源(图片、字体、css等等)的复杂JavaScript应用,webpack有更大的优势。

性能相比,Gulp和Grunt会查看与你的配置匹配的文件的已定义路径,而Webpack则分析整个项目。它检查所有依赖项,使用loaders(加载器)处理它们,并生成一个绑定的JS文件。也就是webpack会查看所有的依赖,而不仅仅只是当前需要打包的文件。

什么是webpack里的bundle

bundle是由webpack生成的输出文件。包含应用中所有用到的模块。bundle生成过程由webpack配置文件控制。

解决webpack打包文件体积过大

jianshu.com

  • 优化依赖引入
- import _ from 'lodash';
+ import _ from 'loadsh/core';
  • 提取第三方库
{
  entry: {
   bundle: 'app'
    vendor: ['react']
  }

  plugins: {
    new webpack.optimize.CommonsChunkPlugin('vendor',  'vendor.js')
  }
}
  • 代码压缩

webpack 自带了一个压缩插件 UglifyJsPlugin,只需要在配置文件中引入即可。

{
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
}
  • 代码分离

webpack.js.org。 以下是最基本的代码分离的操作 webpack-cs

  1. 防止重复

splitChunks插件可以帮助我们将公共依赖项提取到一个现有的条目块或一个全新的块中。让我们使用它来消除与前一个lodash依赖项的重复

webpack.config.js

  const path = require('path');

  module.exports = {
    mode: 'development',
    entry: {
      index: './src/index.js',
      another: './src/another-module.js'
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
+   optimization: {
+     splitChunks: {
+       chunks: 'all'
+     }
+   }
  };
  1. 动态引入

webpack.js.org

react生命周期

  1. componentWillMount()
  2. render(),在componentWillMount()方法之后以及在componentWillReceive(nextProps, nextState)方法之后
  3. componentDidMount()
  4. componentWillReceiveProps(nextProps)
  5. shouldComponentUpdate(nextProps, nextState) shouldComponentUpdate() 返回 false,这就意味着 componentWillUpdate()render()componentDidUpdate() 将不再执行
  6. componentWillUpdate(nextProps, nextState),不可以调用setState()
  7. componentDidUpdate(prevProps, prevState)
  8. componentWillUnmount()

react context

context被翻译为上下文,当在组件树中不想通过props或者state的方式来传递数据时,可以使用context来实现跨层级组件传递,有很多react组件都是通过context来完成自己的功能,比如react-redux的<Provider />,就是通过context提供一个全局态的store

当使用propsstate时,数据是自顶向下流,也就是一层一层的传递,而context则可以跨越组件进行数据传递(其实也就导致了不稳定,所以官方并不推荐在稳定版本的react中使用)

如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider),通常是一个父节点,另外是一个Context的消费者(Consumer),通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式(想想redux怎么用的)

react/redux设计模式

react/redux构架中观察到的最重要的一种结构模式是中介者模式,他允许我们公开一个统一的接口,系统不同部分可以通过该接口进行通讯。

react/redux这个组合中严格贯彻了中介者模式的思想,redux为单一state管理提供了解决方法。单一对象在处理复杂数据变化的时候可以协调指挥,不至于交叉变化引用。中介者可以处理多个委托人的请求,处理一个委托人的请求也不在话下。而且在增加委托者的时候对系统中其他委托者是没有影响的,他们都是独立存在的,只和中介者发生通讯联系。如果用单元测试的方法来测试也很容易。

mvvm设计模式

最常见的客户端架构有三种:

  • MVC: Model-View-Controller

View通过Controller来和Model联系,Controller是View和Model的协调者,View和Model不直接联系,基本联系都是单向的。

用户User通过控制器Controller来操作模板Model从而达到视图View的变化。

  • MVP: Model-View-Presenter

是从MVC模式演变而来的,都是通过Controller/Presenter负责逻辑的处理+Model提供数据+View负责显示。在MVP中,Presenter完全把View和Model进行了分离,主要的程序逻辑在Presenter里实现。并且,Presenter和View是没有直接关联的,是通过定义好的接口进行交互,从而使得在变更View的时候可以保持Presenter不变。

  • MVVM: Model-View-ViewModel

MVVM是把MVC里的Controller和MVP里的Presenter改成了ViewModel。Model+View+ViewModel。View的变化会自动更新到ViewModel,ViewModel的变化也会自动同步到View上显示。这种自动同步是因为ViewModel中的属性实现了Observer,当属性变更时都能触发对应的操作。

ES6

阮一峰,只记录一下难点。

Proxy & Reflect

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。

super、Extends关键字

Js ES6中的”Super“和”Extends以及原文链接https://medium.com/@anurag.majumdar

fetch与传统Ajax

首先了解到Ajax的本质是使用XMLHttpRequest对象来请求数据,fetch则是全局window的一个方法,而且它使用ES6 Promise来处理回调和结果,所以也需要浏览器支持ES6。

Fetch和Ajax主要有两种方式的不同;其中第一个是从Fetch返回的 Promise不会拒绝HTTP错误状态, 即使响应是一个 HTTP 404 或 500,相反,它会正常解决 (其中ok状态设置为false),。仅在网络故障时或任何阻止请求完成时,它才会拒绝。如果有需求一般都可以自行封装。

第二个是Fetch在默认情况下在服务端不会发送或接收任何cookie,如果站点依赖于维护一个用户会话,则可能导致未经认证的请求。如果需要可以在参数中加上credentials

fetch(url, {
  credentials: same-origin', // credentials: ’include'
})

Promise封装Ajax请求

let ajax = (obj) => {
    return new Promise((resolve, reject) => {
        let method = obj.method || 'GET';
        let xhr = null;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else {
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
        xhr.onReadyStateChange = () => {
            if (xhr.readyState == 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                    resolve(xhr.responseText);
                } else {
                    reject(xhr.statusText);
                }
            }
        }
        if (method == 'POST') {
            xhr.open('POST', obj.url, true);
            xhr.responseType = "json";
            xhr.setRequestHeader("Accept", "application/json");
            xhr.send(obj.data);
        } else {
            let query = '';
            for (let key in obj.data) {
                query += '&' + encodeURIComponent(key) + "=" + encodeURIComponent(obj.data[key]);
            }
            query.substring(1);
            xhr.open('GET', obj.url + '?' + query, true);
            xhr.send();
        }
    })
}

计算机相关

Linux常用命令

  • ps -aux:查看执行中的程序的一个指令
  • kill:用来杀进程的,需配合 ps 这个指令,如kill 100
  • netstat -tln:查看端口使用情况
  • netstat -tln | grep 8083 :只查看端口8083的使用情况

    mvc、mvvm

    https://draveness.me/mvx

  • mvc:mvc分别为modelviewcontroller,视图:管理作为位图展示到屏幕上的图形和文字输出;控制器:翻译用户的输入并依照用户的输入操作模型和视图;模型:管理应用的行为和数据,响应数据请求(经常来自视图)和更新状态的指令(经常来自控制器);

    观察者模式

    juejin.im

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

翻译过来就是观察者模式是一种软件设计模式的一个对象,称为主体,维护一个依赖列表,称为观察员,当任何状态发生改变自动通知它们,通常是通过调用一个方法。

用图来表示 观察者模式

发布订阅模式

订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。 发布订阅模式 与观察者模式相比,最大的区别是调度的地方。虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.