等。此函数必须保持纯净,就是说它在每次调用时必须返回相同的结果。
React 中 refs 干嘛用的?
> Refs 提供了一种访问在render方法中创建的 DOM 节点或者 React 元素的方法。在典型的数据流中,props 是父子组件交互的唯一方式,想要修改子组件,需要使用新的pros重新渲染它。凡事有例外,某些情况下咱们需要在典型数据流外,强制修改子代,这个时候可以使用 Refs。
咱们可以在组件添加一个 ref 属性来使用,该属性的值是一个回调函数,接收作为其第一个参数的底层 DOM 元素或组件的挂载实例。
class UnControlledForm extends Component{
handleSubmit=()=>{
console.log('Input Value:',this.input.value)
}
render(){
return (
)
}
}
请注意,input 元素有一个ref属性,它的值是一个函数。该函数接收输入的实际 DOM 元素,然后将其放在实例上,这样就可以在 handleSubmit 函数内部访问它。
经常被误解的只有在类组件中才能使用 refs,但是refs也可以通过利用 JS 中的闭包与函数组件一起使用。
function CustomForm({handleSubmit}){
let inputElement
return (
)
}
在 React 中如何处理事件
> 为了解决跨浏览器的兼容性问题,SyntheticEvent 实例将被传递给你的事件处理函数,SyntheticEvent是 React 跨浏览器的浏览器原生事件包装器,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault()。
比较有趣的是,React 实际上并不将事件附加到子节点本身。React 使用单个事件侦听器侦听顶层的所有事件。这对性能有好处,也意味着 React 在更新 DOM 时不需要跟踪事件监听器。
state 和 props 区别是啥?
> props和state是普通的 JS 对象。虽然它们都包含影响渲染输出的信息,但是它们在组件方面的功能是不同的。即
- state 是组件自己管理数据,控制自己的状态,可变;
- props 是外部传入的数据参数,不可变;
- 没有state的叫做无状态组件,有state的叫做有状态组件;
- 多用 props,少用 state,也就是多写无状态组件。
有状态和无状态组件的区别。
- 有状态组件:
- 在内存中存储组件状态更改的信息
- 有权更改状态
- 包含过去、现在和可能的未来状态更改的信息
- 无安装组件爱你通知它们关于状态更改的需求,然后它们将props传递给前者
- 无状态组件:
- 计算组件的内部状态
- 无权更改状态
- 没有包含关于状态更改的信息
- 它们从有状态组件接收props,将其视为回调函数
如何创建 refs
> Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
render(){
return
}
}
或者这样用:
class UserForm extends Component{
handleSubmit=()=>{
console.log('Input Value is:',this.input.value)
}
render(){
return (
)
}
}
什么是高阶组件?
> 高阶组件(HOC)是接受一个组件并返回一个新组件的函数。基本上,这是一个模式,是从 React 的组合特性中衍生出来的,称其为纯组件,因为它们可以接受任何动态提供的子组件,但不会修改或复制输入组件中的任何行为。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC 可以用于以下许多用例:
- 代码重用、逻辑和引导抽象
- 渲染劫持
- state 抽象和操作
- props 处理
在构造函数调用 super
并将 props
作为参数传入的作用是啥?
> 在调用 super() 方法之前,子类构造函数无法使用this引用,ES6 子类也是如此。将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。
传递props:
class MyComponent extends React.Component{
constructor(props){
super(props);
console.log(this.props)
}
}
没传递props:
class MyComponent extends React.Component{
constructor(props){
super();
console.log(this.props); // undefined
// 但是 Props 参数仍然可用
console.log(props); // Prints {name:'sudheer',age:30}
}
render(){
// 构造函数外部不受影响
console.log(this.props) // {name:'sudheer',age:30}
}
}
上面示例揭示了一点。props 的行为只有在构造函数中是不同的,在构造函数之外也是一样的。
什么是控制组件?
> 在 HTML 中,表单元素如 、
如何 React.createElement ?
const element = (
Hello, world!
)
上述代码如何使用 React.createElement 来实现:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
讲讲什么是 JSX ?
> 当 Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。很多开发人员就能无意识使用 JSX,因为它已经与 React 结合在一直了。
class MyComponent extends React.Component{
render(){
let props=this.props;
return (
)
}
}
为什么不直接更新 state
呢 ?
> 如果试图直接更新 state ,则不会重新渲染组件。
// 错误
This.state.message = 'Hello world';
需要使用setState()方法来更新 state。它调度对组件state对象的更新。当state改变时,组件通过重新渲染来响应:
// 正确做法
This.setState({message: ‘Hello World’});
React 组件生命周期有哪些不同阶段?
在组件生命周期中有四个不同的阶段:
- Initialization:在这个阶段,组件准备设置初始化状态和默认属性。
- Mounting:react 组件已经准备好挂载到浏览器 DOM 中。这个阶段包括componentWillMount和componentDidMount生命周期方法。
- Updating:在这个阶段,组件以两种方式更新,发送新的 props 和 state 状态。此阶段包括shouldComponentUpdate、componentWillUpdate和componentDidUpdate生命周期方法。
- Unmounting:在这个阶段,组件已经不再被需要了,它从浏览器 DOM 中卸载下来。这个阶段包含 componentWillUnmount 生命周期方法。
除以上四个常用生命周期外,还有一个错误处理的阶段:
Error Handling:在这个阶段,不论在渲染的过程中,还是在生命周期方法中或是在任何子组件的构造函数中发生错误,该组件都会被调用。这个阶段包含了 componentDidCatch 生命周期方法。
React 的生命周期方法有哪些?
- componentWillMount:在渲染之前执行,用于根组件中的 App 级配置。
- componentDidMount:在第一次渲染之后执行,可以在这里做AJAX请求,DOM 的操作或状态更新以及设置事件监听器。
- componentWillReceiveProps:在初始化render的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染
- shouldComponentUpdate:确定是否更新组件。默认情况下,它返回true。如果确定在 state 或 props 更新后组件不需要在重新渲染,则可以返回false,这是一个提高性能的方法。
- componentWillUpdate:在shouldComponentUpdate返回 true 确定要更新组件之前件之前执行。
- componentDidUpdate:它主要用于更新DOM以响应props或state更改。
- componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器。
这三个点(…)在 React 干嘛用的?
> ... 在React(使用JSX)代码中做什么?它叫什么?
这个叫扩展操作符号或者展开操作符,例如,如果this.props包含a:1和b:2,则
等价于下面内容:
扩展符号不仅适用于该用例,而且对于创建具有现有对象的大多数(或全部)属性的新对象非常方便,在更新state 咱们就经常这么做:
this.setState(prevState => {
return {foo: {...prevState.foo, a: "updated"}};
});
使用 React Hooks 好处是啥?
> 首先,Hooks 通常支持提取和重用跨多个组件通用的有状态逻辑,而无需承担高阶组件或渲染 props 的负担。Hooks 可以轻松地操作函数组件的状态,而不需要将它们转换为类组件。
Hooks 在类中不起作用,通过使用它们,咱们可以完全避免使用生命周期方法,例如 componentDidMount、componentDidUpdate、componentWillUnmount。相反,使用像useEffect这样的内置钩子。
什么是 React Hooks?
> Hooks是 React 16.8 中的新添加内容。它们允许在不编写类的情况下使用state和其他 React 特性。使用 Hooks,可以从组件中提取有状态逻辑,这样就可以独立地测试和重用它。Hooks 允许咱们在不改变组件层次结构的情况下重用有状态逻辑,这样在许多组件之间或与社区共享 Hooks 变得很容易。
React 中的 useState()
是什么?
const [count,setCounter]=useState(0);
const [moreStuff,setMoreStuff]=useState(...);
const setCount=()=>{
setCounter(count+1);
setMoreStuff(...);
}
useState 是一个内置的 React Hook。useState(0) 返回一个元组,其中第一个参数count是计数器的当前状态,setCounter 提供更新计数器状态的方法。
咱们可以在任何地方使用setCounter方法更新计数状态-在这种情况下,咱们在setCount函数内部使用它可以做更多的事情,使用 Hooks,能够使咱们的代码保持更多功能,还可以避免过多使用基于类的组件。
React 中的StrictMode(严格模式)是什么?
> React 的StrictMode是一种辅助组件,可以帮助咱们编写更好的 react 组件,可以使用 包装一组组件,并且可以帮咱们以下检查:
- 验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告。
- 验证是否使用的已经废弃的方法,如果有,会在控制台给出警告。
- 通过识别潜在的风险预防一些副作用。
为什么类方法需要绑定到类实例?
> 在 JS 中,this 值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this 引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:
class SubmitButton extends React.Component{
constructor(props){
super(props);
this.state={
isFormSubmitted:false
};
this.handleSubmit=this.handleSubmit.bind(this);
}
handleSubmit(){
this.setState({
isFormSubmitted:true
})
}
render(){
return (
Submit
)
}
}
什么是 prop drilling,如何避免?
> 在构建 React 应用程序时,在多层嵌套组件来使用另一个嵌套组件提供的数据。最简单的方法是将一个 prop 从每个组件一层层的传递下去,从源组件传递到深层嵌套组件,这叫做prop drilling。
prop drilling的主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护。
为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过Consumer组件或useContext Hook 使用上下文数据。
描述 Flux 与 MVC?
> 传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:
- 数据流不够清晰:跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
- 缺乏数据完整性:模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果。
使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性。
受控组件和非受控组件区别是啥?
- 受控组件是 React 控制中的组件,并且是表单数据真实的唯一来源。
- 非受控组件是由 DOM 处理表单数据的地方,而不是在 React 组件中。
尽管非受控组件通常更易于实现,因为只需使用refs即可从 DOM 中获取值,但通常建议优先选择受控制的组件,而不是非受控制的组件。
这样做的主要原因是受控组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式。
什么是 React Context?
> Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
什么是 React Fiber?
> Fiber 是 React 16 中新的协调引擎或重新实现核心算法。它的主要目标是支持虚拟DOM的增量渲染。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用等方面的适用性,并为不同类型的更新分配优先级,以及新的并发原语。
React Fiber 的目标是增强其在动画、布局和手势等领域的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中。
如何在 ReactJS 的 Props上应用验证?
> 当应用程序在开发模式下运行时,React 将自动检查咱们在组件上设置的所有 props,以确保它们具有正确的数据类型。对于不正确的类型,开发模式下会在控制台中生成警告消息,而在生产模式中由于性能影响而禁用它。强制的 props 用 isRequired定义的。
下面是一组预定义的 prop 类型
- React.PropTypes.string
- React.PropTypes.number
- React.PropTypes.func
- React.PropTypes.node
- React.PropTypes.bool
例如,咱们为用户组件定义了如下的propTypes
import PropTypes from 'prop-types';
class User extends React.Component{
render(){
return (
Welcome,{this.props.name}
Age,{this.props.age}
)
}
}
User.propTypes={
name:PropTypes.string.isRequired,
age:PropTypes.number.isRequired
}
在 React 中使用构造函数和 getInitialState 有什么区别?
> 构造函数和getInitialState之间的区别就是ES6和ES5本身的区别。在使用ES6类时,应该在构造函数中初始化state,并在使用React.createClass时定义getInitialState方法。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { /* initial state */ };
}
}
等价于
var MyComponent = React.createClass({
getInitialState() {
return { /* initial state */ };
},
});
如何有条件地向 React 组件添加属性?
> 对于某些属性,React 非常聪明,如果传递给它的值是虚值,可以省略该属性。例如:
var InputComponent=React.createClass({
render:function(){
var required=true;
var disabled=false;
return (
)
}
})
渲染结果:
另一种可能的方法是:
var condition = true;
var component = (
);
Hooks会取代 render props
和高阶组件吗?
> 通常,render props和高阶组件仅渲染一个子组件。React团队认为,Hooks 是服务此用例的更简单方法。
这两种模式仍然有一席之地(例如,一个虚拟的 scroller 组件可能有一个 renderItem prop,或者一个可视化的容器组件可能有它自己的 DOM 结构)。但在大多数情况下,Hooks 就足够了,可以帮助减少树中的嵌套。
如何避免组件的重新渲染?
> React 中最常见的问题之一是组件不必要地重新渲染。React 提供了两个方法,在这些情况下非常有用:
- React.memo():这可以防止不必要地重新渲染函数组件
- PureComponent:这可以防止不必要地重新渲染类组件
这两种方法都依赖于对传递给组件的props的浅比较,如果 props 没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。
通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。
什么是纯函数?
> 纯函数是不依赖并且不会在其作用域之外修改变量状态的函数。本质上,纯函数始终在给定相同参数的情况下返回相同结果。
当调用setState
时,React render
是如何工作的?
> 咱们可以将"render"分为两个步骤:
- 虚拟 DOM 渲染:当render方法被调用时,它返回一个新的组件的虚拟 DOM 结构。当调用setState()时,render会被再次调用,因为默认情况下shouldComponentUpdate总是返回true,所以默认情况下 React 是没有优化的。
- 原生 DOM 渲染:React 只会在虚拟DOM中修改真实DOM节点,而且修改的次数非常少——这是很棒的React特性,它优化了真实DOM的变化,使React变得更快。
如何避免在React重新绑定实例?
有几种常用方法可以避免在 React 中绑定方法:
- 将事件处理程序定义为内联箭头函数
class SubmitButton extends React.Component{
constructor(props){
super(props);
this.state={
isFormSubmitted:false
};
}
render(){
return (
{
this.setState({isFormSubmitted:true})
}}>Submit
)
}
}
- 使用箭头函数来定义方法
class SubmitButton extends React.Component{
state={
isFormSubmitted:false
}
handleSubmit=()=>{
this.setState({
isFormSubmitted:true
});
}
render(){
return (
Submit
)
}
}
- 使用带有 Hooks 的函数组件
const SubmitButton=()=>{
const [isFormSubmitted,setIsFormSubmitted]=useState(false);
return (
{
setIsFormSubmitted(true);
}}>Submit
)
}
如何理解“单一可信源”?
> 单一可信源(SSOT)是构造信息模型和相关数据模式的实践,其中每个数据元素都只能在一个地方掌握(或编辑)
Redux 使用“存储”将应用程序的整个状态存储在一个位置。因此,组件的所有状态都存储在存储中,并且存储本身会接收更新。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
列出 Redux 的组件。
- 动作——这是一个描述发生了什么的对象。
- Reducer——确定状态如何变化的地方。
- 存储——整个应用程序的状态 / 对象树保存在存储中。
- 视图——仅显示存储提供的数据。
在 Redux 中如何定义动作?
> React 中的动作必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,你也可以为其添加更多属性。在 Redux 中使用称为“动作创建者”的函数来创建动作。以下是动作和动作创建者的示例:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
说明 Reducer 的作用。
> Reducer 是用于指示 ACTION 反应中应用程序状态变化的简单功能。它接收先前的状态和动作,然后返回新的状态。它根据动作类型确定需要哪种更新,然后返回新值。如果没有要完成的工作,它将按原样返回先前状态。
在 Redux 中存储的用途是什么?
> 存储是一个 JavaScript 对象,可以保存应用程序的状态,并提供一些辅助方法来访问状态、调度动作并记录侦听器。应用程序的整个状态 / 对象树存储在单个存储中。因此 Redux 非常容易理解且可预测。我们可以将中间件转移到存储,以管理数据处理任务,并维护更改存储状态的各种活动的日志。通过 Reducer,所有活动都返回新的状态。
Redux 与 Flux 有何不同?
- Flux
- 存储包括状态和更改逻辑
- 有多个存储
- 所有存储不互通,是平行的
- 有单个调度器
- React组件订阅到存储
- 状态是可变的
- Redux
- 存储和更改逻辑是分离的
- 只有一个存储
- 带有分层Reducer的单个存储
- 没有调度器的概念
- 容器组件是有联系的
- 状态是不可变的
Redux 有哪些优势?
Redux 的优点如下:
- 结果的可预测性——由于总是有单一可信源,比如存储,因此当前状态与动作及应用程序的其他部分同步时不会出现混乱。
- 可维护性——代码易于维护,具有可预测的结果和严格的结构。
- 服务端渲染——你只需将在服务器上创建的存储传递给客户端即可。这对于初始渲染非常有用,并优化了应用程序性能,提供了更好的用户体验。
- 开发人员工具——从动作到状态更改,开发人员可以利用这些工具实时跟踪应用程序中发生的所有事情。
- 社区和生态系统——Redux 背后拥有巨大的社区,用起来更加便利。大批优秀的开发者为库的发展做出了贡献,并开发了很多应用程序。
- 易于测试——Redux 的代码主要是较小的、纯净的和孤立的函数。这使代码可测试且独立。
- 组织——Redux 精确地规定了代码的组织方式,这使得团队合作时代码更加一致,更容易理解。
什么是 React Router?
> React Router 是建立在 React 之上的功能强大的路由库。它使 URL 与网页上显示的数据保持同步。它保持标准化的结构和行为,可用于开发单页 Web 应用程序。React Router 有一个简单的 API。React Router 提供了一种方法,只会显示你的应用中路由匹配你的定义的那些组件。
为什么我们在 React 中需要一个路由器?
> 路由器用于定义多个路由,并且当用户键入特定的 URL 时,如果该 URL 与路由器内部定义的任何“路由”的路径匹配,则该用户将被重定向到该路由。因此我们需要在应用程序中添加一个路由器库,以允许创建多个路由,每个路由都为我们指向一个独特的视图。
从 React Router 包导入的组件有两个属性,一个是将用户引导到指定路径的 path,另一个是用于定义所述路径中内容的 component。
列出 React Router 的优点。
- 就像 React 基于组件的理念一样,在 React Router v4 中 API 是“完全组件化的”。路由器可以可视化为单个根组件(),其中包含特定的子路由()。
- 无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在组件中。
- 包是拆分的:三个包分别用于 Web、Native 和 Core。这使我们的应用更加紧凑。它们的编码样式类似,所以很容易来回切换。
React Router 与传统路由有何不同?
- 传统路由:
- 参与的页面:每个视图对应一个新页面
- URL更改:向服务器发送一个HTTP请求并接收对应的HTML页面
- 体验:用户其实是在每个视图的不同页面间切换
- React路由:
- 参与的页面:只涉及单个HTML页面
- URL更改:只有历史属性被更改
- 体验:用户以为自己正在不同的页面间切换
小程序/Hybrid
简述微信小程序原理
> 微信小程序采用 JavaScript、WXML、WXSS 三种技术进行开发,本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口
微信的架构,是数据驱动的架构模式,它的 UI 和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现
小程序分为两个部分 webview 和 appService 。其中 webview 主要用来展现 UI ,appService 有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层 JSBridge 实现通信,实现 UI 的渲染、事件的处理
小程序的数据绑定和vue哪里不一样
小程序直接 this.data 的属性是不可以同步到视图的,必须调用
this.setData({
// 这里设置
})
小程序的wxss和css有哪些不一样的地方
WXSS 和 CSS 类似,不过在 CSS 的基础上做了一些补充和修改
尺寸单位 rpx
rpx 是响应式像素,可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。如在 iPhone6 上,屏幕宽度为 375px,共有 750 个物理像素,则 750rpx = 375px = 750 物理像素
使用 @import 标识符来导入外联样式。@import 后跟需要导入的外联样式表的相对路径,用;表示语句结束
/** index.wxss **/
@import './base.wxss';
.container{
color: red;
}
请谈谈wxml与标准的html的异同?
- 都是用来描述页面的结构;
- 都由标签、属性等构成;
- 标签名字不一样,且小程序标签更少,单一标签更多;
- 多了一些 wx:if 这样的属性以及 {{ }} 这样的表达式
- WXML仅能在微信小程序开发者工具中预览,而HTML可以在浏览器内预览
- 组件封装不同, WXML对组件进行了重新封装,
- 小程序运行在JS Core内,没有DOM树和window对象,小程序中无法使用window对象和document对象。
小程序页面间有哪些传递数据的方法
- 使用全局变量实现数据传递 在 app.js 文件中定义全局变量 globalData, 将需要存储的信息存放在里面
// app.js
App({
// 全局变量
globalData: {
userInfo: null
}
})
使用的时候,直接使用 getApp() 拿到存储的信息
- 使用 wx.navigateTo 与 wx.redirectTo 的时候,可以将部分数据放在 url 里面,并在新页面 onLoad 的时候初始化
//pageA.js
wx.navigateTo({
url: '../pageD/pageD?name=raymond&gender=male',
})
wx.redirectTo({
url: '../pageD/pageD?name=raymond&gender=male',
})
// pageB.js
...
Page({
onLoad: function(option){
console.log(option.name + 'is' + option.gender)
this.setData({
option: option
})
}
})
需要注意的问题:
wx.navigateTo 和 wx.redirectTo 不允许跳转到 tab 所包含的页面;onLoad 只执行一次
- 使用本地缓存 Storage 相关
小程序的生命周期函数
- onLoad 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数
- onShow() 页面显示/切入前台时触发
- onReady() 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互
- onHide() 页面隐藏/切入后台时触发。 如 navigateTo 或底部 tab 切换到其他页面,小程序切入后台等
- onUnload() 页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时
哪些方法可以用来提高微信小程序的应用速度
- 提高页面加载速度
- 用户行为预测
- 减少默认 data 的大小
- 组件化方案
简述下 wx.navigateTo()
, wx.redirectTo()
, wx.switchTab()
, wx.navigateBack()
, wx.reLaunch()
的区别
- wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
- wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
- wx.switchTab():跳转到 abBar 页面,并关闭其他所有非 tabBar 页面
- wx.navigateBack()关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
- wx.reLaunch():关闭所有页面,打开到应用内的某个页面
bindtap和catchtap的区别是什么
- 相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分
- 不同点:他们的不同点主要是bindtap是不会阻止冒泡事件的,catchtap是阻值冒泡的
微信小程序与H5的区别
- 第一条是运行环境的不同
传统的HTML5的运行环境是浏览器,包括webview,而微信小程序的运行环境并非完整的浏览器,是微信开发团队基于浏览器内核完全重构的一个内置解析器,针对小程序专门做了优化,配合自己定义的开发语言标准,提升了小程序的性能。
- 第二条是开发成本的不同
只在微信中运行,所以不用再去顾虑浏览器兼容性,不用担心生产环境中出现不可预料的奇妙BUG
- 第三条是获取系统级权限的不同
系统级权限都可以和微信小程序无缝衔接
- 第四条便是应用在生产环境的运行流畅度
长久以来,当HTML5应用面对复杂的业务逻辑或者丰富的页面交互时,它的体验总是不尽人意,需要不断的对项目优化来提升用户体验。但是由于微信小程序运行环境独立
webview中的页面怎么跳回小程序中?
- 首先在外部网页的HTML要引入最新版的jweixin-1.3.2.js:
- 然后
wx.miniProgram.navigateTo({ //如果跳转到tabBar中的页面,必须换成switchTo
url: '/pages/login/login'+'$params'
})
使用webview直接加载要注意哪些事项?
- 必须要在小程序后台使用管理员添加业务域名;
- h5页面跳转至小程序的脚本必须是1.3.1以上;
- 微信分享只可以都是小程序的主名称了,如果要自定义分享的内容,需小程序版本在1.7.1以上;
- h5的支付不可以是微信公众号的appid,必须是小程序的appid,而且用户的openid也必须是用户和小程序的。
看你的技术栈对 Electron 比较熟悉,有使用过 React-native,请你谈谈使用的感受?
> React-native 的坑还是比较多,但是目前也算拥有成熟的生态了,开发简单的 APP 可以使用它。但是复杂的应用还是原生比较好,Electron 目前非常受欢迎,它基本上可以完成桌面应用的大部分需求,重型应用开发也是完全没问题的,可以配合大量 C# C++ 插件等。
hybrid是什么,为何用hybrid?
- hybrid是客户端与前端的混合开发
- hybrid存在的核心意义在意快速迭代,无需审核
- hybrid实现流程,以及webview和file协议
介绍一下hybrid更新和上线的流程?
- 服务端的版本和zip包维护
- 更新zip包之前,先对比版本号
- zip下载解压和覆盖
hybrid和h5的主要区别?
- 优点: 体验好,可快速迭代
- 缺点: 开发成本高,运维成本高
- 适合的场景: hybrid适合产品型,H5适合运营型
前端JS和客户端如何通讯?
- 通讯的基本形式: 前端调用能力,传递参数,监听回调
- 对schema协议的理解和使用:
- 定义了前端与客户端的约定;
- 可以通过ifream使用
- 调用schema代码的封装
放在客户端内置上线的好处: 更快,更安全
Flutter 是什么?
> Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
Flutter中,Widget 和 element 和 RenderObject 之间的关系?
- Widget是用户界面的一部分,并且是不可变的。
- Element是在树中特定位置Widget的实例。
- RenderObject是渲染树中的一个对象,它的层次结构是渲染库的核心。
Widget会被inflate(填充)到Element,并由Element管理底层渲染树。Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态。Flutter创建Element的可见树,相对于Widget来说,是可变的,通常界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑。就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中。Element会持有renderObject和widget的实例。记住,Widget 只是一个配置,RenderObject 负责管理布局、绘制等操作。
在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已。
Flutter中,mixin extends implement 之间的关系?
继承(关键字 extends)、混入 mixins (关键字 with)、接口实现(关键字 implements)。这三者可以同时存在,前后顺序是extends -> mixins -> implements。
Flutter中的继承是单继承,子类重写超类的方法要用@Override,子类调用超类的方法要用super。
在Flutter中,Mixins是一种在多个类层次结构中复用类代码的方法。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件。
Flutter中的Widget、State、Context 的核心概念?是为了解决什么问题?
- Widget: 在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget。
- Widget树: Widget以树结构进行组织。包含其他Widget的widget被称为父Widget(或widget容器)。包含在父widget中的widget被称为子Widget。
- Context: 仅仅是已创建的所有Widget树结构中的某个Widget的位置引用。简而言之,将context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context只从属于一个widget,它和widget一样是链接在一起的,并且会形成一个context树。
- State: 定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget。
这些状态的引入,主要是为了解决多个部件之间的交互和部件自身状态的维护。
工程化
介绍一下webpack基本的属性?
const path = require('path')
module.exports = {
entry: { // main是默认入口,也可以是多入口
main: './src/main.js'
},
// 出口
output: {
filename: './build.js', // 指定js路径
path: path.join(__dirname, '..', '', 'dist') // 最好是绝对路径
// 代表上一级的dist
},
module: {
// 一样的功能rules: webpack2.xx新加的
loaders: [ // require('./a.css||./a.js')
{
test: /\.css$/,
loader: 'style-loader!css=loader',
//多个loader用!分割
//顺序是反过来的 2!1 多个loader
},
{
test: /\.(jpg|svg)$/,
loaderL 'url-loader?limit=4096&name=[name].[ext]',
// limit=4096&name=[name].[ext]' 多个参数之间用&符号分割
//[name].[ext]内置提供的
options: {
limit: 4096,
name: '[name].[ext]'
}
}
]
},
plugins: [
// 插件的执行顺序是依次执行的,和loader是反过来的
new htmlWebpackPlugin({
template: './src/index.html',
})
// 将src下的template属性描述的文件根据当前配置的output.path,将文件移动到该目录。
// 在插件的执行过程中,它本身可以去拿当前所设置的webpack选项,便于对webpack选项的复用,
]
}
减少页面加载时间的方法
- 重复的HTTP请求数量应尽量减少
- 压缩Javascript、CSS代码
- 在文件头部放置css样式的定义
- 在文件末尾放Javascript脚本
- css、javascript改由外部调用,避免重复调用
- 尽可能减少DOM元素
- 避免使用CSS Expressions
- 添加文件过期或缓存头
- 使用CDN(Content Delivery Network)网络加速
- 服务器启用gzip压缩功能
- Ajax采用缓存调用
- 如果可以,Ajax调用尽量采用GET方法调用(其他方法会发送两次请求,一次option,一次为正常请求)
- 缩减iframe的使用,如无必要,尽量不要使用
- 合理使用Flush(后端)
- 避免采用301、302转向
- 优化图片文件
- 采用分页或翻页后展示
- 使用多域名负载网页内的多个文件、图片
web前端性能优化
- 减少http请求,合理设置http缓存
- 使用浏览器缓存
- 启用压缩
- css sprites
- lazyload images
- css放最上部,js放最下面
- 异步请求callback
- 减少cookie传输
- JavaScript代码优化:
- dom:html colleciton、重绘
- 慎用with
- 避免使用eval和Function
- 减少作用域链查找
- 数据访问
- 字符串拼接
- css选择符优化
- cdn加速
- 反向代理
babel转换es6语法工作原理?
> babel是一个转译器,感觉相对于编译器compiler,叫转译器transpiler更准确,因为它只是把同种语言的高版本规则翻译成低版本规则,而不像编译器那样,输出的是另一种更低级的语言代码。
但是和编译器类似,babel的转译过程也分为三个阶段:parsing、transforming、generating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:
ES6代码输入 ==》 babylon进行解析 ==》 得到AST ==》 plugin用babel-traverse对AST树进行遍历转译 ==》 得到新的AST树 ==》 用babel-generator通过AST树生成ES5代码
webpack本地开发怎么解决跨域的
- 下载 webpack-dev-server 插件
- 配置 webpack.config.js 文件
// webpack.config.js
var WebpackDevServer = require("webpack-dev-server");
module.exports = {
...
devServer: {
...
port: '8088', //设置端口号
// 代理设置
proxy: {
'/api': {
target: 'http://localhost:80/index.php', // 目标代理
pathRewrite: {'^/api' : ''}, // 重写路径
secure: false, // 是否接受运行在 HTTPS 上
}
}
}
}
webpack与grunt、gulp的不同
> 三者都是前端构建工具
grunt 和 gulp 是基于任务和流的。找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程
webpack 是基于入口的。webpack 会自动地递归解析入口所需要加载的所有资源文件,然后用不同的 Loader 来处理不同的文件,用 Plugin 来扩展 webpack 功能
webpack 与前者最大的不同就是支持代码分割,模块化(AMD,CommonJ,ES2015),全局分析
有哪些常见的Loader?他们是解决什么问题的
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
- slint-loader:通过 SLint 检查 JavaScript 代码
- babel-loader:把 ES6 转换成 ES5
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
- url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
有哪些常见的Plugin?他们是解决什么问题的
- define-plugin:定义环境变量
- commons-chunk-plugin:提取公共代码
Loader和Plugin的不同
- loader 加载器
Webpack 将一切文件视为模块,但是 webpack 原生是只能解析 js 文件. Loader 的作用是让 webpack 拥有了加载和解析非 JavaScript 文件的能力
在 module.rules 中配置,也就是说他作为模块的解析规则而存在,类型为数组
- Plugin 插件
扩展 webpack 的功能,让 webpack 具有更多的灵活性
在 plugins 中单独配置。类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入
webpack的构建流程是什么
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
- 确定入口:根据配置中的 entry 找出所有的入口文件
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果
是否写过Loader和Plugin?描述一下编写loader或plugin的思路
编写 Loader 时要遵循单一原则,每个 Loader 只做一种"转义"工作。 每个 Loader 的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用 this.callback() 方法,将内容返回给 webpack 。 还可以通过 this.async() 生成一个 callback 函数,再用这个 `callback`` 将处理后的内容输出出去
相对于 Loader 而言,Plugin 的编写就灵活了许多。 webpack 在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果
webpack的热更新是如何做到的?说明其原理
- 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
- 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
- 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
- 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
- webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
- HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。
- 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
- 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。
如何利用webpack来优化前端性能
- 压缩代码。删除多余的代码、注释、简化代码的写法等等方式
- 利用 CDN 加速。在构建过程中,将引用的静态资源路径修改为 CDN 上对应的路径
- 删除死代码 Tree Shaking)。将代码中永远不会走到的片段删除掉
- 优化图片,对于小图可以使用 base64 的方式写入文件中
- 按照路由拆分代码,实现按需加载,提取公共代码
- 给打包出来的文件名添加哈希,实现浏览器缓存文件
如何提高webpack的构建速度
- 多入口情况下,使用CommonsChunkPlugin来提取公共代码
- 通过externals配置来提取常用库
- 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。
- 使用Happypack 实现多线程加速编译
- 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
- 使用Tree-shaking和Scope Hoisting来剔除多余代码
怎么配置单页应用?怎么配置多页应用
- 单页应用可以理解为 webpack 的标准模式,直接在 entry 中指定单页应用的入口即可
- 多页应用的话,可以使用 webpack 的 AutoWebPlugin 来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范
什么是bundle,什么是chunk,什么是module
> bundle 是由 webpack 打包出来的文件,chunk 是指 webpack 在进行模块的依赖分析的时候,代码分割出来的代码块。module是开发中的单个模块
Git和SVN有什么区别?
- Git:
- Git是一个分布式的版本控制工具
- 它属于第3代版本控制工具
- 客户端可以在其本地系统上克隆整个存储库
- 即使离线也可以提交
- Push/pull 操作更快
- 工程可以用 commit 自动共享
- SVN:
- SVN 是集中版本控制工具
- 它属于第2代版本控制工具
- 版本历史记录存储在服务器端存储库中
- 只允许在线提交
- Push/pull 操作较慢
- 没有任何东西自动共享
什么是Git?
- Git 是分布式版本控制系统(DVCS)。它可以跟踪文件的更改,并允许你恢复到任何特定版本的更改。
- 与 SVN 等其他版本控制系统(VCS)相比,其分布式架构具有许多优势,一个主要优点是它不依赖于中央服务器来存储项目文件的所有版本。
- 每个开发人员都可以“克隆”我在图中用“Local repository”标注的存储库的副本,并且在他的硬盘驱动器上具有项目的完整历史记录,因此当服务器中断时,你需要的所有恢复数据都在你队友的本地 Git 存储库中。
- 还有一个中央云存储库,开发人员可以向其提交更改,并与其他团队成员进行共享,如图所示,所有协作者都在提交更改“远程存储库”。
什么是 Git 中的“裸存储库”?
> Git 中的 “裸” 存储库只包含版本控制信息而没有工作文件(没有工作树),并且它不包含特殊的 .git 子目录。相反,它直接在主目录本身包含 .git 子目录中的所有内容,其中工作目录包括:
一个 .git 子目录,其中包含你的仓库所有相关的 Git 修订历史记录。
工作树,或签出的项目文件的副本。
Git 是用什么语言编写的?
> Git使用 C 语言编写。 GIT 很快,C 语言通过减少运行时的开销来做到这一点。
列举工作中常用的几个git命令?
- 新增文件的命令:git add file或者git add .
- 提交文件的命令:git commit –m或者git commit –a
- 查看工作区状况:git status –s
- 拉取合并远程分支的操作:git fetch/git merge或者git pull
- 查看提交记录命令:git reflog
提交时发生冲突,你能解释冲突是如何产生的吗?你是如何解决的?
> 开发过程中,我们都有自己的特性分支,所以冲突发生的并不多,但也碰到过。诸如公共类的公共方法,我和别人同时修改同一个文件,他提交后我再提交就会报冲突的错误。
发生冲突,在IDE里面一般都是对比本地文件和远程分支的文件,然后把远程分支上文件的内容手工修改到本地文件,然后再提交冲突的文件使其保证与远程分支的文件一致,这样才会消除冲突,然后再提交自己修改的部分。特别要注意下,修改本地冲突文件使其与远程仓库的文件保持一致后,需要提交后才能消除冲突,否则无法继续提交。必要时可与同事交流,消除冲突。
发生冲突,也可以使用命令。
- 通过git stash命令,把工作区的修改提交到栈区,目的是保存工作区的修改;
- 通过git pull命令,拉取远程分支上的代码并合并到本地分支,目的是消除冲突;
- 通过git stash pop命令,把保存在栈区的修改部分合并到最新的工作空间中;
如果本次提交误操作,如何撤销?
> 如果想撤销提交到索引区的文件,可以通过git reset HEAD file;如果想撤销提交到本地仓库的文件,可以通过git reset –soft HEAD^n恢复当前分支的版本库至上一次提交的状态,索引区和工作空间不变更;可以通过git reset –mixed HEAD^n恢复当前分支的版本库和索引区至上一次提交的状态,工作区不变更;可以通过git reset –hard HEAD^n恢复当前分支的版本库、索引区和工作空间至上一次提交的状态。
如果我想修改提交的历史信息,应该用什么命令?
如果修改最近一次提交的历史记录,就可以用git commit –amend命令;vim编辑的方式;
如果修改之前提交的历史记录,就需要按照下面的步骤:
第一步:首先查看前三次的提交历史记录:
第二步:执行命令git rebase –i HEAD~3,会把前3次的提交记录按照倒叙列出来;这里把第一行的‘pick’修改为‘edit’,然后esc + :wq退出vim编辑器;
第三步:根据提示,执行git commit –amend命令,进入vim编辑器并修改提交信息。
第四步:然后执行git rebase –continue命令
你使用过git stash命令吗?你一般什么情况下会使用它?
命令git stash是把工作区修改的内容存储在栈区。
以下几种情况会使用到它:
- 解决冲突文件时,会先执行git stash,然后解决冲突;
- 遇到紧急开发任务但目前任务不能提交时,会先执行git stash,然后进行紧急任务的开发,然后通过git stash pop取出栈区的内容继续开发;
- 切换分支时,当前工作空间内容不能提交时,会先执行git stash再进行分支切换;
什么是git stash drop?
> git stash drop 命令用于删除隐藏的项目。默认情况下,它将删除最后添加的存储项,如果提供参数的话,它还可以删除特定项。
下面举个例子。
如果要从隐藏项目列表中删除特定的存储项目,可以使用以下命令:
git stash list:它将显示隐藏项目列表,如:
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert “added file_size”
stash@{2}: WIP on master: 21d80a5 added number to log
如果要删除名为 stash@{0} 的项目,请使用命令 git stash drop stash@{0}。
如何查看分支提交的历史记录?查看某个文件的历史记录呢?
查看分支的提交历史记录:
- 命令git log –number:表示查看当前分支前number个详细的提交历史记录;
- 命令git log –number –pretty=oneline:在上个命令的基础上进行简化,只显示sha-1码和提交信息;
- 命令git reflog –number: 表示查看所有分支前number个简化的提交历史记录;
- 命令git reflog –number –pretty=oneline:显示简化的信息历史信息;
如果要查看某文件的提交历史记录,直接在上面命令后面加上文件名即可。
注意:如果没有number则显示全部提交次数
如何找到特定提交中已更改的文件列表?
> 要获取特定提交中已更改的列表文件,请使用以下命令:
git diff-tree -r {hash}
给定提交哈希,这将列出在该提交中更改或添加的所有文件。 -r 标志使命令列出单个文件,而不是仅将它们折叠到根目录名称中。
你还可以包括下面提到的内容,虽然它是可选的,但有助于给面试官留下深刻印象。
输出还将包含一些额外信息,可以通过包含两个标志把它们轻松的屏蔽掉:
git diff-tree –no-commit-id –name-only -r {hash}
这里 -no-commit-id 将禁止提交哈希值出现在输出中,而 -name-only 只会打印文件名而不是它们的路径。
git config 的功能是什么?
git 使用你的用户名将提交与身份相关联。 git config 命令可用来更改你的 git 配置,包括你的用户名。
下面用一个例子来解释。
假设你要提供用户名和电子邮件 ID 用来将提交与身份相关联,以便你可以知道是谁进行了特定提交。为此,我将使用:
git config –global user.name "Your Name": 此命令将添加用户名。
git config –global user.email "Your E-mail Address": 此命令将添加电子邮件ID。
如果分支是否已合并为master,你可以通过什么手段知道?
- git branch –merged 它列出了已合并到当前分支的分支。
- git branch –no-merged 它列出了尚未合并的分支。
提交对象包含什么?
Commit 对象包含以下组件,你应该提到以下这三点:
- 一组文件,表示给定时间点的项目状态
- 引用父提交对象
- SHAI 名称,一个40个字符的字符串,提交对象的唯一标识。
怎样将 N 次提交压缩成一次提交?
将N个提交压缩到单个提交中有两种方式:
- 如果要从头开始编写新的提交消息,请使用以下命令:
git reset –soft HEAD~N &&
git commit
- 如果你想在新的提交消息中串联现有的提交消息,那么需要提取这些消息并将它们传给 git commit,可以这样:
git reset –soft HEAD~N &&
git commit –edit -m"$(git log –format=%B –reverse .HEAD@{N})"
什么是 Git bisect?如何使用它来确定(回归)错误的来源?
Git bisect 用于查找使用二进制搜索引入错误的提交。 Git bisect的命令是
git bisect
既然你已经提到过上面的命令,那就解释一下这个命令会做什么。
此命令用了二进制搜索算法来查找项目历史记录中的哪个提交引入了错误。你可以通过告诉它已知包含该错误的“错误”提交以及在引入错误之前已知的“良好”提交来使用它。然后 git bisect 在这两个端点之间选择一个提交,并询问你所选的提交是“好”还是“坏”。它继续缩小范围,直到找到引入更改的确切提交。
描述一下你所使用的分支策略?
- 功能分支(Feature branching)
要素分支模型将特定要素的所有更改保留在分支内。当通过自动化测试对功能进行全面测试和验证时,该分支将合并到主服务器中。
- 任务分支(Task branching)
在此模型中,每个任务都在其自己的分支上实现,任务键包含在分支名称中。很容易看出哪个代码实现了哪个任务,只需在分支名称中查找任务键。
- 发布分支(Release branching)
一旦开发分支获得了足够的发布功能,你就可以克隆该分支来形成发布分支。创建该分支将会启动下一个发布周期,所以在此之后不能再添加任何新功能,只有错误修复,文档生成和其他面向发布的任务应该包含在此分支中。一旦准备好发布,该版本将合并到主服务器并标记版本号。此外,它还应该再将自发布以来已经取得的进展合并回开发分支。
什么是SubGit?
> SubGit 是将 SVN 到 Git迁移的工具。它创建了一个可写的本地或远程 Subversion 存储库的 Git 镜像,并且只要你愿意,可以随意使用 Subversion 和 Git。
这样做有很多优点,比如你可以从 Subversion 快速一次性导入到 Git 或者在 Atlassian Bitbucket Server 中使用SubGit。我们可以用 SubGit 创建现有 Subversion 存储库的双向 Git-SVN 镜像。你可以在方便时 push 到 Git 或提交 Subversion。同步由 SubGit 完成。
能不能说一下git fetch和git pull命令之间的区别?
> 简单来说:git fetch branch是把名为branch的远程分支拉取到本地;而git pull branch是在fetch的基础上,把branch分支与当前分支进行merge;因此pull = fetch + merge。
使用过git merge和git rebase吗?它们之间有什么区别?
> 简单的说,git merge和git rebase都是合并分支的命令。
git merge branch会把branch分支的差异内容pull到本地,然后与本地分支的内容一并形成一个committer对象提交到主分支上,合并后的分支与主分支一致;
git rebase branch会把branch分支优先合并到主分支,然后把本地分支的commit放到主分支后面,合并后的分支就好像从合并后主分支又拉了一个分支一样,本地分支本身不会保留提交历史。
能说一下git系统中HEAD、工作树和索引之间的区别吗?
> HEAD文件包含当前分支的引用(指针);
工作树是把当前分支检出到工作空间后形成的目录树,一般的开发工作都会基于工作树进行;
索引index文件是对工作树进行代码修改后,通过add命令更新索引文件;GIT系统通过索引index文件生成tree对象;
之前项目中是使用的GitFlow工作流程吗?它有什么好处?
GitFlow可以用来管理分支。GitFlow工作流中常用的分支有下面几类:
- master分支:最为稳定功能比较完整的随时可发布的代码,即代码开发完成,经过测试,没有明显的bug,才能合并到 master 中。请注意永远不要在 master 分支上直接开发和提交代码,以确保 master 上的代码一直可用;
- develop分支;用作平时开发的主分支,并一直存在,永远是功能最新最全的分支,包含所有要发布 到下一个 release 的代码,主要用于合并其他分支,比如 feature 分支; 如果修改代码,新建 feature 分支修改完再合并到 develop 分支。所有的 feature、release 分支都是从 develop 分支上拉的。
- feature分支;这个分支主要是用来开发新的功能,一旦开发完成,通过测试没问题(这个测试,测试新功能没问题),我们合并回develop 分支进入下一个 release
- release分支;用于发布准备的专门分支。当开发进行到一定程度,或者说快到了既定的发布日,可以发布时,建立一个 release 分支并指定版本号(可以在 finish 的时候添加)。开发人员可以对 release 分支上的代码进行集中测试和修改bug。(这个测试,测试新功能与已有的功能是否有冲突,兼容性)全部完成经过测试没有问题后,将 release 分支上的代码合并到 master 分支和 develop 分支
- hotfix分支;用于修复线上代码的bug。**从 master 分支上拉。**完成 hotfix 后,打上 tag 我们合并回 master 和 develop 分支。
GitFlow主要工作流程
- 1.初始化项目为gitflow , 默认创建master分支 , 然后从master拉取第一个develop分支
- 2.从develop拉取feature分支进行编码开发(多个开发人员拉取多个feature同时进行并行开发 , 互不影响)
- 3.feature分支完成后 , 合并到develop(不推送 , feature功能完成还未提测 , 推送后会影响其他功能分支的开发);合并feature到develop , 可以选择删除当前feature , 也可以不删除。但当前feature就不可更改了,必须从release分支继续编码修改
4.从develop拉取release分支进行提测 , 提测过程中在release分支上修改BUG
5.release分支上线后 , 合并release分支到develop/master并推送;合并之后,可选删除当前release分支,若不删除,则当前release不可修改。线上有问题也必须从master拉取hotfix分支进行修改;
6.上线之后若发现线上BUG , 从master拉取hotfix进行BUG修改;
7.hotfix通过测试上线后,合并hotfix分支到develop/master并推送;合并之后,可选删除当前hotfix ,若不删除,则当前hotfix不可修改,若补丁未修复,需要从master拉取新的hotfix继续修改;
8.当进行一个feature时 , 若develop分支有变动 , 如其他开发人员完成功能并上线 , 则需要将完成的功能合并到自己分支上,即合并develop到当前feature分支;
9.当进行一个release分支时 , 若develop分支有变动 , 如其他开发人员完成功能并上线 , 则需要将完成的功能合并到自己分支上,即合并develop到当前release分支 (!!! 因为当前release分支通过测试后会发布到线上 , 如果不合并最新的develop分支 , 就会发生丢代码的情况);
GitFlow的好处
为不同的分支分配一个明确的角色,并定义分支之间如何交互以及什么时间交互;可以帮助大型项目理清分支之间的关系,简化分支的复杂度。
使用过git cherry-pick,有什么作用?
命令git cherry-pick可以把branch A的commit复制到branch B上。
在branch B上进行命令操作:
- 复制单个提交:git cherry-pick commitId
- 复制多个提交:git cherry-pick commitId1…commitId3
注意:复制多个提交的命令不包含commitId1.
git跟其他版本控制器有啥区别?
> GIT是分布式版本控制系统,其他类似于SVN是集中式版本控制系统。
分布式区别于集中式在于:每个节点的地位都是平等,拥有自己的版本库,在没有网络的情况下,对工作空间内代码的修改可以提交到本地仓库,此时的本地仓库相当于集中式的远程仓库,可以基于本地仓库进行提交、撤销等常规操作,从而方便日常开发。
我们在本地工程常会修改一些配置文件,这些文件不需要被提交,而我们又不想每次执行git status时都让这些文件显示出来,我们该如何操作?
- 首先利用命令touch .gitignore新建文件
$ touch .gitignore
- 然后往文件中添加需要忽略哪些文件夹下的什么类型的文件
$ vim .gitignore
$ cat .gitignore
/target/class
.settings
.imp
*.ini
如何把本地仓库的内容推向一个空的远程仓库?
- 首先确保本地仓库与远程之间是连同的。如果提交失败,则需要进行下面的命令进行连通:
git remote add origin XXXX
- 如果是第一次推送,则进行下面命令:
git push -u origin master // -u 是指定origin为默认主分支
- 之后的提交,只需要下面的命令:
git push origin master
你有提到白屏时间,有什么办法可以减少吗?都是什么原理?
> GZIP,SSR 同构、PWA 应用、预渲染、localStorage 缓存 js 文件等。
下面就是细分拆解答案,无限的连带问题,这里非常耗时,这些内容大都网上能搜到,我这里就不详细说
其中有问到 PWA 的原理,我的回答是:
Service Worker 有一套自己的声明周期,当安装并且处于激活状态时候,网站在 https 或者 localhost 的协议时候,可以拦截过滤发出的请求,会先把请求克隆一份(请求是流,消费就没有了),然后判断请求的资源是否在 Service Worker 缓存中,如果存在那么可以直接从 Service Worker 缓存中取出,如果不存在,那么就真正的发出这个请求。
介绍下你会用的自动化构建方式
1)Jenkins 自动化构建
2)自己搭建 Node.js 服务器,实现 Jenkins
3)Docker 配合 Travis CI 实现自动化构建
Jenkins 自动化构建:
配置,自动同步某个分支代码,打包构建。
自己搭建 Node.js 服务器,实现 Jenkins:
自己搭建 Node.js 的服务器,在 GitLab 上指定 webhook 地址,分支代码更新触发事件,服务器接受到 post 请求,里面附带分支的信息,执行自己的 shell 脚本命令,指定文件夹,构建打包。
服务器上使用 Docker-compose 指定镜像,每次代码推送到 gitHub,通过自己编写的 yml 和 dockerfile 文件构建打包,服务器自动拉取最新镜像并且发布到正式环境。
谈谈你对前端、客户端架构的认识?
> 前端的架构,首先明确项目的兼容性,面向浏览器编程,是否做成 PC、移动端的响应式布局。根据项目规模、后期可能迭代的需求制定技术方案,如果比较重型的应用应该选用原生开发,尽量少使用第三方库。
客户端架构:是否跨平台,明确兼容系统,例如是否兼容 XP ,如果兼容 XP 就选择 nw.js,再然后根据项目复杂度招聘相应技术梯度人员,安排系统学习相关内容,招聘人员或者购买定制开发相关原生插件内容。
虽然说只是谈谈,但是感觉面试的职位越高级、轮数越往后,越考验你的架构能力,前面考察基础,后面考察你的技术广度以及逻辑思维,能否在复杂的应用中保持清醒头脑,定位性能这类型的细节能力。很多人基础面试面得很好,但是拿不到 offer,原因就是没有这种架构能力,只能自己写代码,不能带领大家学习、写代码。这也是我在面试时偶然听到某个大公司 HR 之间的对话,原话是:他面试还可以,看起来是很老实(某个之前的面试者),但是他对之前项目整体流程并不是那么清楚,连自己做的项目,前后端流程都不清楚,感觉不合适。
谈谈你对微前端的看法,以及实践:
> 将 Vue 和 React 一起开发,其实一点都不难,只要自己能造出 Redux 这样的轮子,熟悉两个框架原理,就能一起开发,难的是将这些在一个合适的场景中使用。之前看到网上有微前端的实践,但是并不是那么完美,当然,类似 Electron 这样的应用,混合开发很正常,微前端并不是只单单多个框架混合开发,更多是多个框架引入后解决了什么问题、带来的问题怎么解决?毕竟 5G 还没完全普及,数据传输还是不那么快。过大的包容易带来客户端的过长白屏时间(自己给自己挖坑)
什么是jenkins?
Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。
Jenkins功能包括:
- 持续的软件版本发布/测试项目。
- 监控外部调用执行的工作
如何将 Jenkins 从一台服务器迁移或者复制到另一台服务器?
我会通过将 jobs 目录从旧服务器复制到新服务器的方式来完成这个事情。有很多种方法可以做到这一点:
- 只需复制相应的 job 目录,即可将 job 从一个 Jenkins 服务器移动到另一个。
- 通过使用其它名称克隆 job 目录来制作现有 job 的副本。
- 通过重命名目录来重命名现有 job。请注意,如果你更改了 job 名称,则需要更改尝试调用该重命名 job 的所有 job 。
如何在 Jenkins 中创建备份和复制文件?
> 定期备份 JENKINS_HOME 目录。这包含所有构建 job 配置,从属节点配置和构建历史记录。要创建 Jenkins 的备份,只需复制此目录即可,你还可以复制 job 目录或重命名目录。
如何配置 Jenkins 的 job?
关于这个答案的解决方法是首先提一下如何创建 job:转到 Jenkins 首页,选择“New Job”,然后选择“Build a free-style software project”。然后你可以设置这个自由式 job 的元素:
- 可选的 SCM,例如源代码所在的 CVS 或 Subversion。
- 用于控制 Jenkins 何时执行构建的触发器。
- 某种构建脚本,用于执行实际工作的构建(ant,maven,shell 脚本,批处理文件等)。
- 从构建中收集信息的可选步骤,例如归档制品、记录 javadoc 和测试结果。
- 配置构建结果通知其他人/系统的步骤,例如发送电子邮件、即时消息、更新问题跟踪器等。
列举 Jenkins 中一些有用的插件
- Maven 2 project
- Amazon EC2
- HTML publisher
- Copy artifact
- Join
- Green Balls
如何保证 Jenkins 的安全?
- 确保 global security 配置项已经打开。
- 确保用适当的插件将 Jenkins 与企业员工目录进行集成。
- 确保启用项目矩阵的权限访问设置。
- 通过自定义版本控制的脚本来自动化 Jenkins 中设置权限/特权的过程。
- 限制对 Jenkins 数据/文件夹的物理访问。
- 定期对其进行安全审核。
什么是docker?
- Docker是一个容器化平台,它将应用程序及其所有依赖项以容器的形式打包在一起,以确保应用程序在任何环境(无论是开发环境、测试环境还是生产环境)中无缝运行。
- Docker容器,将一个软件包在一个完整的文件系统中,其中包含运行所需的一切:代码、运行时、系统工具、系统库等任何可以安装在服务器上的东西。
- 它都将始终运行相同的程序,无论软件的环境如何。
什么是Docker镜像?
> Docker镜像是Docker容器的源代码。换句话说,Docker镜像用于创建容器。使用build命令创建镜像,并且在使用run启动时它们将生成容器。镜像存储在Docker注册表中,registry.hub.docker.com因为它们可能变得非常大,镜像被设计为由其他镜像层组成,允许在通过网络传输镜像时发送最少量的数据。
什么是Docker容器?
> Docker容器包括应用程序及其所有依赖项,但与其他容器共享内核,在主机操作系统的用户空间中作为独立进程运行。Docker容器不依赖于任何特定的基础架构:它们可以在任何计算机,任何基础架构和任何云中运行。
什么是Docker Hub?
> Docker hub是一个基于云的注册表服务,允许您链接到代码存储库,构建映像并测试它们,存储手动推送的镜像以及指向Docker云的链接,以便您可以将镜像部署到主机。它为整个开发流程中的容器发现,分发和变更管理,用户和团队协作以及工作流自动化提供了集中资源。
Dockerfile中最常见的指令是什么?
- FROM:我们使用FROM为后续指令设置基本镜像。在每个有效的Dockerfile中,FROM是第一条指令。
- LABEL:我们使用LABEL根据项目,模块,许可等组织我们的镜像。我们也可以使用LABEL来帮助实现自动化。在LABEL中,我们指定一个键值对,以后可用于以编程方式处理Dockerfile。
- RUN:我们使用RUN命令在当前图像之上的新图层中执行任何指令。使用每个RUN命令,我们在图像上添加一些内容,并在Dockerfile的后续步骤中使用它。
- CMD:我们使用CMD命令提供执行容器的默认值。在Dockerfile中,如果我们包含多个CMD命令,则只使用最后一条指令。
什么类型的应用程序 - 无状态或有状态更适合Docker容器?
> 最好为Docker Container创建无状态应用程序。我们可以从应用程序中创建一个容器,并从应用程序中取出可配置的状态参数。现在我们可以在生产环境和具有不同参数的QA环境中运行相同的容器。这有助于在不同场景中重用相同的镜像。另外,无状态应用程序比有状态应用程序更容易使用Docker容器进行扩展。
解释基本的Docker使用工作流程
- 一切都从Dockerfile开始。Dockerfile是镜像的源代码。
- 创建Dockerfile后,您可以构建它以创建容器的镜像。图像只是“源代码”的“编译版本”,即Dockerfile。
- 获得容器的镜像后,应使用注册表重新分发容器。注册表就像一个git存储库 - 你可以推送和拉取镜像。
- 接下来,您可以使用该图像来运行容器。在许多方面,正在运行的容器与虚拟机(但没有虚拟机管理程序)非常相似。
什么是虚拟化?
在其构想的形式中,虚拟化被认为是逻辑上划分大型机以允许多个应用程序同时运行的方法。但是,当公司和开源社区能够以某种方式提供处理特权指令的方法,并允许在单个基于x86的系统上同时运行多个操作系统时,情况发生了巨大变化。
实际效果是虚拟化允许您在同一硬件上运行两个完全不同的操作系统。每个客户操作系统都经历了引导,加载内核等所有过程。您可以拥有非常严格的安全性,例如,客户操作系统无法完全访问主机操作系统或其他客户,从而完全混乱。
可以基于虚拟化方法如何模仿客户操作系统的硬件并模拟客户操作环境来对虚拟化方法进行分类。主要有三种类型的虚拟化:
- 仿真
- 半虚拟化
- 基于容器的虚拟化
什么是管理程序?
> 管理程序处理创建用户虚拟机运行的虚拟环境。它监督用户系统,并确保在必要时为客户分配资源。虚拟机管理程序位于物理机和虚拟机之间,并为虚拟机提供虚拟化服务。为了实现它,它拦截虚拟机上的客户操作系统操作,并模拟主机操作系统上的操作。
虚拟化技术的快速发展(主要是在云端),通过允许在一个物理服务器上创建多个虚拟服务器,借助于管理程序(如Xen、VMware Player、KVM等),以及在商品处理器(如Intel VT AN)中集成硬件支持,进一步推动了虚拟化的使用。例如Intel VT和AMD-V。
什么是Docker Swarm?
> Docker Swarm是Docker的群集管理工具。它将Docker主机池转变为一个虚拟Docker主机。Docker Swarm提供标准的Docker API,任何已经与Docker守护进程通信的工具都可以使用Swarm扩展到多个主机。
您将如何监控生产中的Docker?
> Docker提供docker stats和docker events等工具来监控生产中的Docker。我们可以使用这些命令获取重要统计数据的报告。
Docker stats:当我们使用容器ID调用docker stats时,我们获得容器的CPU,内存使用情况等。它类似于Linux中的top命令。
Docker events:Docker events是一个命令,用于查看Docker守护程序中正在进行的任务。
一些常见的Docker事件是:attach,commit,die,detach,rename,destroy等。我们还可以使用各种选项来限制或过滤我们感兴趣的事件。
数据库
数据库事务的四个特征及含义
- 原子性:要么全部完成,要么不完成,若发生错误会进行回滚操作;
- 一致性:开始到结束后,数据库完整性约束没收到破坏;(实体完整性,参照完整性,用户定义的完整性)
- 隔离性:事务与事务之间相隔离,串行化执行;
- 持久性:事务完成对数据的影响是永久的;
数据库范式
- 1NF:每一列都是不可分割的基本数据项,同一列无二值;无重复的域;
- 2NF:实例依赖于主键部分;
- 3NF:属性不依赖于其他非主属性;
首先要明确的是:满足着第三范式,那么就一定满足第二范式、满足着第二范式就一定满足第一范式
第一范式:字段是最小的的单元不可再分
学生信息组成学生信息表,有年龄、性别、学号等信息组成。这些字段都不可再分,所以它是满足第一范式的
第二范式:满足第一范式,表中的字段必须完全依赖于全部主键而非部分主键。
其他字段组成的这行记录和主键表示的是同一个东西,而主键是唯一的,它们只需要依赖于主键,也就成了唯一的
学号为1的同学,姓名是damao,年龄是100岁。姓名和年龄字段都依赖着学号主键。
第三范式:满足第二范式,非主键外的所有字段必须互不依赖
就是数据只在一个地方存储,不重复出现在多张表中,可以认为就是消除传递依赖
比如,我们大学分了很多系(中文系、英语系、计算机系……),这个系别管理表信息有以下字段组成:系编号,系主任,系简介,系架构。那我们能不能在学生信息表添加系编号,系主任,系简介,系架构字段呢?不行的,因为这样就冗余了,非主键外的字段形成了依赖关系(依赖到学生信息表了)!正确的做法是:学生表就只能增加一个系编号字段。
MySQL存储过程与触发器的区别
- 分表 :真正的分表,每张表对应三个文件;提高MYSQL的并发能力;
- 分区 :表中的数据分成多个区块;突破磁盘的读写能力;
乐观锁和悲观锁
- 乐观锁:假定不会发生并发冲突,只在提交时检查,若有其他数据更新了数据,则回滚;使用数据版本标示数据(时间戳,版本号)
- 悲观锁:假定会发生并发冲突,屏蔽一切破坏数据库一致性的操作,主要用于数据争用激烈的环境,以及锁成本低于回滚成本时;排他锁;
实践中如何优化MySQL
- SQL语句及索引的优化
- 数据库表结构的优化
- 系统配置的优化
- 硬件的优化
优化MySQL数据库的方法
- 选取最适用的字段属性,尽可能减少定义字段宽度,尽量把字段设置NOTNULL,例如'省份'、'性别'最好适用ENUM
- 使用连接(JOIN)来代替子查询
- 适用联合(UNION)来代替手动创建的临时表
- 事务处理
- 锁定表、优化事务处理
- 适用外键,优化锁定表
- 建立索引
- 优化查询语句
简单描述mysql中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响(从读写两方面)
> 索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。
普通索引允许被索引的数据列包含重复的值。如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字UNIQUE把它定义为一个唯一索引。也就是说,唯一索引可以保证数据记录的唯一性。
主键,是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。
索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引,这就是联合索引。
索引可以极大的提高数据的查询速度,但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件。
什么是NoSQL数据库?NoSQL和RDBMS有什么区别?在哪些情况下使用和不使用NoSQL数据库?
> NoSQL是非关系型数据库,NoSQL = Not Only SQL。
关系型数据库采用的结构化的数据,NoSQL采用的是键值对的方式存储数据。
在处理非结构化/半结构化的大数据时;在水平方向上进行扩展时;随时应对动态增加的数据项时可以优先考虑使用NoSQL数据库。
在考虑数据库的成熟度;支持;分析和商业智能;管理及专业性等问题时,应优先考虑关系型数据库。
如何理解MongoDB中的GridFS机制,MongoDB为何使用GridFS来存储文件?
> GridFS是一种将大型文件存储在MongoDB中的文件规范。使用GridFS可以将大文件分隔成多个小文档存放,这样我们能够有效的保存大文档,而且解决了BSON对象有限制的问题。
MongoDB如何执行事务/加锁?
> MongoDB没有使用传统的锁或者复杂的带回滚的事务,因为它设计的宗旨是轻量,快速以及可预计的高性能。可以把它类比成MySQL MylSAM的自动提交模式。通过精简对事务的支持,性能得到了提升,特别是在一个可能会穿过多个服务器的系统里。
数据在什么时候才会扩展到多个分片(shard)里?
> MongoDB 分片是基于区域(range)的。所以一个集合(collection)中的所有的对象都被存放到一个块(chunk)中。只有当存在多余一个块的时候,才会有多个分片获取数据的选项。现在,每个默认块的大小是 64Mb,所以你需要至少 64 Mb 空间才可以实施一个迁移。
当我试图更新一个正在被迁移的块(chunk)上的文档时会发生什么?
> 更新操作会立即发生在旧的分片(shard)上,然后更改才会在所有权转移(ownership transfers)前复制到新的分片上。
介绍一下 Redis,为什么快,怎么做持久化存储?
> Redis 将数据存储在内存中,key-value 形式存储,所以获取也快。支持的 key 格式相对于 memorycache 更多,而且支持 RDB 快照形式、AOF。
> RDB 持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是 fork 一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB 是 Redis 默认的持久化方式,会在对应的目录下生产一个 dump.rdb 文件,重启会通过加载 dump.rdb 文件恢复数据。
优点:
1)只有一个文件 dump.rdb,方便持久化;
2)容灾性好,一个文件可以保存到安全的磁盘;
3)性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化(使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能) ;
4)如果数据集偏大,RDB 的启动效率会比 AOF 更高。
缺点:
1)数据安全性低。(RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不是特别严格的时候)
2)由于 RDB 是通过 fork 子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是 1 秒钟。
> AOF 持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补 RDB 的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
优点:
1)数据安全性更高,AOF 持久化可以配置 appendfsync 属性,其中 always,每进行一次命令操作就记录到 AOF 文件中一次。
2)通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
3)AOF 机制的 rewrite 模式。(AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点
1)AOF 文件比 RDB 文件大,且恢复速度慢;数据集大的时候,比 RDB 启动效率低。
2)根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB。
为什么要用 redis /为什么要用缓存
- 高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
- 高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
为什么要用 redis 而不用 map/guava 做缓存?
> 缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
redis 和 memcached 的区别
- redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
- 集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
redis 常见数据结构以及使用场景分析
- String:常用命令: set,get,decr,incr,mget 等。
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。
- Hash:常用命令: hget,hset,hgetall 等。
Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等
- List:常用命令: lpush,rpush,lpop,rpop,lrange等
list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
- Set:常用命令: sadd,spop,smembers,sunion 等
set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。
当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。
- Sorted Set:常用命令: zadd,zrange,zrem,zcard等
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。
redis 设置过期时间
Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。
我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。
如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?
定期删除+惰性删除。
通过名字大概就能猜出这两个删除方式的意思了。
定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!
但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢?
redis 内存淘汰机制。。
redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)
redis 提供 6种数据淘汰策略:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的).
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
redis 事务
> Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
介绍下缓存击穿和穿透
- 缓存穿透:是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果 key 不存在或者 key 已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
- 缓存击穿:是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
- 缓存雪崩
简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决办法:
- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存
- 缓存穿透
简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
如何解决 Redis 的并发竞争 Key 问题
> 所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
如何保证缓存与数据库双写时的数据一致性?
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存。
这种情况不存在并发问题么?
不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据。
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
如何解决上述并发问题?
首先,给缓存设有效时间是一种方案。其次,采用异步延时删除策略,保证读请求完成以后,再进行删除操作。