offer收割机-Web前端面试宝典【精编版-4】

344 阅读45分钟

在线访问手册: hanxueqing.github.io/Web-Front-e… github地址: github.com/Hanxueqing/…

React—用于构建用户界面的 JavaScript 库

组件

创建组件的方式

1、无状态函数式组件

创建纯展示组件,只负责根据传入的props来展示,不涉及到要state状态的操作,是一个只带有一个render方法的组件类。

2、组件是通过React.createClass创建的(ES5)

3、React.Component

在es6中直接通过class关键字来创建

组件其实就是一个构造器,每次使用组件都相当于在实例化组件

react的组件必须使用render函数来创建组件的虚拟dom结构

组件需要使用ReactDOM.render方法将其挂载在某一个节点上

组件的首字母必须大写

React.Component是以ES6的形式来创建react的组件的,是React目前极为推荐的创建有状态组件的方式,相对于 React.createClass可以更好实现代码复用。将上面React.createClass的形式改为React.Component形式如下:

class Greeting extends React.Component{
   constructor (props) {
       super(props);
       this.state={
            work_list: []
        }

        this.Enter=this.Enter.bind(this); //绑定this
    }
    render() {
        return (
            <div>
                <input type="text" ref="myWork" placeholder="What need to be done?" onKeyUp={this.Enter}/>

                <ul>
                    {
                        this.state.work_list.map(function (textValue) {
                            return <li key={textValue}>{textValue}</li>;
                        })
                    }
                </ul>

            </div>
        );
    }
    Enter(event) {
        var works = this.state.work_list;
        var work = this.refs.myWork.value;
        if (event.keyCode == 13) {
            works.push(work);
            this.setState({work_list: works});
            this.refs.myWork.value = "";
        }


    }
}

关于this

React.createClass创建的组件,其每一个成员函数的this都有React自动绑定,任何时候使用,直接使用this.method即可,函数中的this会被正确设置

React.Component创建的组件,其成员函数不会自动绑定this,需要手动绑定,否则this不能获取当前组件实例对象

React.Component三种手动绑定this的方法

1.在构造函数中绑定

 constructor(props) {
       super(props);
       this.Enter = this.Enter.bind(this);
  }

2.使用bind绑定

    <div onKeyUp={this.Enter.bind(this)}></div> 

3.使用arrow function绑定

    <div onKeyUp={(event)=>this.Enter(event)}></div> 

我们在实际应用中应该选择哪种方法来创建组件呢?

  • 只要有可能,尽量使用无状态组件创建形式
  • 否则(如需要state、生命周期方法等),使用React.Component这种es6形式创建组件

无状态组件与有状态的组件的区别为?

没有状态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构

无状态组件非常简单,开销很低,如果可能的话尽量使用无状态组件。

无状态的函数创建的组件是无状态组件,它是一种只负责展示的纯组件

无状态组件可以使用纯函数来实现。

 const Slide = (props)=>{return (<div>.....</div>)}这就是无状态组件(函数方式定义组件)  可以简写为  const Slide = props =>(<div>......</div>)

React父子组件通信

(1)this.props

(2)ref链

(3)Redux

高阶组件

高阶组件是什么?如何理解?

什么是高级组件?首先你得先了解请求ES6中的class只是语法糖,本质还是原型继承。能够更好的进行说明,我们将不会修改组件的代码。而是通过提供一些能够包裹组件的组件, 并通过一些额外的功能来增强组件。这样的组件我们称之为高阶组件(Higher-Order Component)。

高阶组件(HOC)是React中对组件逻辑进行重用的高级技术。但高阶组件本身并不是React API。它只是一种模式,这种模式是由React自身的组合性质必然产生的。

说到高阶组件,就先得说到高阶函数了,高阶函数是至少满足下列条件的函数:

1、接受一个或多个函数作为输入 2、输出一个函数

高阶组件定义

类比高阶函数的定义,高阶组件就是接受一个组件作为参数,在函数中对组件做一系列的处理,随后返回一个新的组件作为返回值。

高阶组件的缺点

高阶组件也有一系列的缺点,首先是被包裹组件的静态方法会消失,这其实也是很好理解的,我们将组件当做参数传入函数中,返回的已经不是原来的组件,而是一个新的组件,原来的静态方法自然就不存在了。如果需要保留,我们可以手动将原组件的方法拷贝给新的组件,或者使用hoist-non-react-statics之类的库来进行拷贝。

参考:浅谈React高阶组件

www.jb51.net/article/137…

使用过哪些高阶组件

  1. withRouter高阶组件,可以根据传入的组件生成一个新的组件,并且为新组件添加上router相关的api。

  2. connect 用于连接容器组件与UI组件,connect(mapStateToProps,mapDispatchToProps)(ui组件),当状态改变的时候,容器组件内部因为通过store.subscribe可以监听状态的改变,给ui组件传入新的属性,返回容器组件(智能组件),这个函数返回什么,ui组件props上就会挂载什么,ui组件的属性上就就会有改变状态的方法了,用的话通过this.props.方法名。

虚拟dom

####React高性能的体现:虚拟DOM

在Web开发中我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。

React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A-B,B-A,React会认为A变成B,然后又从B变成A UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。

尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,部而对实际DOM进行操作的仅仅是Diff分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。

为什么虚拟dom会提升代码性能?

虚拟DOM就是JavaScript对象,就是在没有真实渲染DOM之前做的操作。 真实dom的比对变成了虚拟dom的比对(js对象的比对) 虚拟dom里面比对,涉及到diff算法。 key值 (key值相同dom可以直接进行复用)

react的diff算法实现流程

1.DOM结构发生改变-----直接卸载并重新create 2.DOM结构一样-----不会卸载,但是会update变化的内容 3.所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点 (其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)

React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点

diff算法和fiber算法的区别

diff算法是同步进行更新和比较,必须同步执行完一个操作再进行下一个操作,所耗时间比较长,JavaScript是单线程的,一旦组件开始更新,主线程就一直被React控制,这个时候如果再次执行交互操作,就会卡顿。

React Fiber重构这种方式,渲染过程采用切片的方式,每执行一会儿,就歇一会儿。如果有优先级更高的任务到来以后呢,就会先去执行,降低页面发生卡顿的可能性,使得React对动画等实时性要求较高的场景体验更好。

如何理解React中key?

keys是什么帮助 React 跟踪哪些项目已更改、添加或从列表中删除。

每个 keys 在兄弟元素之间是独一无二的。

keys 使处理列表时更加高效,因为 React 可以使用子元素上的 keys 快速知道元素是新的还是在比较树时才被移动。

keys 不仅使这个过程更有效率,而且没有 keys ,React 不知道哪个本地状态对应于移动中的哪个项目。

例如:数组循环出来三项,每一项前面有一个多选框,假设第一个多选框勾选了,然后我再动态添加新的元素,会发现新添加的元素就会被勾选了,这就是个问题!设置key值,这样的话就可以解决了。

####JSX 语法

在vue中,我们使用render函数来构建组件的dom结构性能较高,因为省去了查找和编译模板的过程,但是在render中利用createElement创建结构的时候代码可读性较低,较为复杂,此时可以利用jsx语法来在render中创建dom,解决这个问题,但是前提是需要使用工具来编译jsx

JSX是一种语法,全称:javascript xml

JSX语法不是必须使用的,但是因为使用了JSX语法之后会降低我们的开发难度,故而这样的语法又被成为语法糖。

react.js中有React对象,帮助我们创建组件等功能

HTML 中所有的信息我们都可以用 JavaScript 对象来表示,但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 XML的方式写起来就方便很多了。

于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 XML 标签结构的语法,这样写起来就方便很多了编译的过程会把类似 XML 的 JSX 结构转换成 JavaScript 的对象结构

在不使用JSX的时候,需要使用React.createElement来创建组件的dom结构,但是这样的写法虽然不需要编译,但是维护和开发的难度很高,且可读性很差。

所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:

JSX代码 — > 使用react构造组件,bable进行编译—> JavaScript对象 — ReactDOM.render()函数进行渲染—>真实DOM元素 —>插入页面

另:

  • JSX就是在js中使用的xml,但是,这里的xml不是真正的xml,只能借鉴了一些xml的语法,例如:

最外层必须有根节点、标签必须闭合

  • jsx借鉴xml的语法而不是html的语法原因:xml要比html严谨,编译更方便

webpack中,是借助loader完成的jsx代码的转化,还是babel?

在vue中,借助webpack提供的vue-loader来帮助我们做一些转化,让vue代码可以在浏览器中执行。 react中没有react-loader来进行代码的转化,而是采用babel里面babel-preset-react来实现的。

调用setState之后,发生了什么?

constructor(props){
	super(props);
	this.state = {
		age:1
	}
}

通过调用this.setState去更新this.state,不能直接操作this.state,请把它当成不可变的。 调用setState更新this.state,他不是马上就会生效的,他是异步的。所以不要认为调用完setState后可以立马获取到最新的值。

多个顺序执行的setState不是同步的一个接着一个的执行,会加入一个异步队列,然后最后一起执行,即批处理。

setState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。

this.setState((prevState)=>({
	age:++prevState.age
}),()=>{
	console.log(this.state.age) //获取更新后的最新的值
});

React实现异步请求

redux中间件

通常情况下,action只是一个对象,不能包含异步操作

redux-thunk中间件

redux-thunk原理:

-可以接受一个返回函数的actionCreators,如果这个actionCreators返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行

-如果不安装redux-thunk中间件,actionCreators只能返回一个对象

-安装了redux-thunk中间件之后,actionCreators可以返回一个函数了,在这个函数里面可以写异步操作的代码

-redux中间件,创建出来的action在到达reducer之间,增强了dispatch的派发功能

####refs的作用业务场景?

通过ref对dom、组件进行标记,在组件内部通过this.refs获取到之后,进行操作

<ul ref='content'><li>国内新闻</li></ul>
...
this.refs.content.style.display = this.state.isMenuShow?'block':'none'

ref用于给组件做标记,例如获取图片的宽度与高度。

非受控组件,input输入框,获取输入框中的数据,可以通过ref做标记。

 <input ref={el=>this.input = el}/>

ref是一个函数,为什么?

避免组件之间数据相互被引用,造成内存泄漏

class Test extends React.Component{
	componentDidMount(){
		console.log(this.el);
	}
	render(){
		//react在销毁组件的时候会帮助我们清空掉ref的相关引用,这样可以防止内存泄漏等一系列问题。
		return <div ref={el=>this.el=el}></div>
	}
}

受控组件与非受控组件的区别

受控组件与非受控组件是相对于表单而言。

受控组件: 受到数据的控制。组件的变化依靠数据的变化,数据变化了,页面也会跟着变化了。输入框受到数据的控制,数据只要不变化,input框输什么都不行,一旦使用数据,从状态中直接获取。

 <input value={this.state.value}/>

非受控组件: 直接操作dom,不做数据的绑定。通过refs来获取dom上的内容进行相关的操作。

<input ref={el=>this.el=el}/> //不需要react组件做管理

数据驱动是react核心。

实现监听滚动高度

class Test extends React.Component{
	constructor(props){
		super(props);
		this.handleWidowScroll = this.handleWidowScroll.bind(this);
	}
	handleWidowScroll(){
		this.setState({
			top:document.body.scrollTop
		})
	}
	componentDidMount(){//绑定监听事件
		window.addEventListener("scroll",this.handleWindowScroll);
	}
	componentWillUnmount(){//移除监听事件
		window.removeEventListener("scroll",this.handleWindowScroll);
	}
}

####React中data为什么返回一个函数

为了防止组件与组件之间的数据共享,让作用域独立,data是函数内部返回一个对象,让每个组件或者实例可以维护一份被返回对象的独立的拷贝。

路由

react-router4的核心思想是什么?

路由也变成组件了,所以它是非常灵活的(NavLink Route)。 vue中的路由需要单独的配置 vue-router。

react-router的两种模式是什么?

hashHistory # 不需要后端服务器的配置 browserHistory / 需要后端服务器的配置 (后端人员不清楚路由重定向等相关的概念)

hash路由的实现原理

通过onhashchange事件监听路由的改变,一旦路由改变,这个事件就会被执行,就可以拿到更改后的哈希值,通过更改后的哈希值就可以让我们的页面进行一个关联,一旦路由发生改变了,整个页面状态就会发生改变,但是整个页面是没有发生任何http请求的,整个页面处于一种无刷新状态。

  • hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件:
window.onhashchange = function(event) {
    console.log(event.oldURL, event.newURL);
    let hash = loaction.hash  //通过location对象来获取hash地址
    console.log(hash)    // "#/notebooks/260827/list"#号开始
}

因为hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用 ,这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来,后来人们给它起了一个霸气的名字叫前端路由,成为了单页应用标配。

spa单页应用:根据页面地址的不同来实现组件之间的切换,整个页面处于一种无刷新状态。

history路由

随着history api的到来,前端路由开始进化了,前面的hashchange,你只能改变#后面的url片段,而history api则给了前端完全的自由

history api可以分为两大部分:切换和修改 【切换路由/修改路由】

(1)切换历史状态

包括括back、forwardgo三个方法,对应浏览器的前进,后退,跳转操作

history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进

(2)修改历史状态

包括 了pushState、replaceState两个方法,这两个方法接收三个参数:stateObj,title,url。

两种模式的区别是什么?

在hash模式下,前端路由修改的是#中的信息,而浏览器请求时是不带它玩的,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来,需要后端人员去做一个配置。

生命周期

react的生命周期函数

【初始化阶段】:

(1)getDefaultProps:实例化组件之后,组件的getDefaultProps钩子函数会执行

这个钩子函数的目的是为组件的实例挂载默认的属性

这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,创建出所有实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性

理论上来说,写成函数返回对象的方式,是为了防止实例共享,但是react专门为了让实例共享,只能让这个函数只执行一次

组件间共享默认属性会减少内存空间的浪费,而且也不需要担心某一个实例更改属性后其他的实例也会更改的问题,因为组件不能自己更改属性,而且默认属性的优先级低。

(2)getInitialState:为实例挂载初始状态,且每次实例化都会执行,也就是说,每一个组件实例都拥有自己独立的状态。

(3)componentWillMount:执行componentWillMount,相当于Vue里的created+beforeMount,这里是在渲染之前最后一次更改数据的机会,在这里更改的话是不会触发render的重新执行。

(4)render:渲染dom

render()方法必须是一个纯函数,他不应该改变state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果shouldComponentUpdate()返回falserender()不会被调用。

(5)componentDidMount:相当于Vue里的mounted,多用于操作真实dom

【运行中阶段】

当组件mount到页面中之后,就进入了运行中阶段,在这里有5个钩子函数,但是这5个函数只有在数据(属性、状态)发送改变的时候才会执行

(1)componentWillReceiveProps(nextProps,nextState)

当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行。初始化props时候不会主动执行

当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数还没有同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态。

(2)接下来就会执行shouldComponentUpdate(nextProps,nextState),这个函数的作用:当属性或状态发生改变后控制组件是否要更新,提高性能,返回true就更新,否则不更新,默认返回true。

接收nextProp、nextState,根据根据新属性状态和原属性状态作出对比、判断后控制是否更新

如果shouldComponentUpdate()返回falsecomponentWillUpdate,rendercomponentDidUpdate不会被调用。

(3)componentWillUpdate,在这里,组件马上就要重新render了,多做一些准备工作,千万千万,不要在这里修改状态,否则会死循环 相当于Vue中的beforeUpdate

(4)render,重新渲染dom

(5)componentDidUpdate,在这里,新的dom结构已经诞生了,相当于Vue里的updated

【销毁阶段】

当组件被销毁之前的一刹那,会触发componentWillUnmount,临死前的挣扎

相当于Vue里的beforeDestroy,所以说一般会做一些善后的事情,例如使定时器无效,取消网络请求或清理在componentDidMount中创建的任何监听。

image

为什么Vue中有destroyed,而react却没有componentDidUnmount

Vue在调用$destroy方法的时候就会执行beforeDestroy,然后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说如果想要对残留的dom结构进行处理必须在destroyed处理,但是react执行完componentWillUnmount之后把事件、数据、dom都全部处理掉了,所以根本不需要其他的钩子函数了

React中怎么样就算组件被销毁:

  1. 当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件相当于被销毁
  2. 调用ReactDOM.unmountComponentAtNode(node) 方法来将某节点中的组件销毁

哪个生命周期里面发送ajax?

AJAX请求应该在componentDidMount生命周期事件中。

如何避免ajax数据重新获取?

将所有的数据存储在redux中进行管理,既可以解决该问题。

为什么不把请求数据的操作写在componentWillMount中而写在componentDidMount中?

(1)此钩子函数在16版本中会被频繁调用:15.X版本用的是diff算法,不会被频繁调用,而React下一代调和算法Fiber会通过开始或停止渲染的方式优化应用性能,其会影响到comonentWillMount的触发次数,对于componentWillMount这个生命周期的调用次数就会变得不确定。React可能会多次频繁调用componentWillMount,如果我们将ajax请求放到componentWillMount函数中,那么显而易见就会被触发多次,自然也就不是好的选择。

(2)componentWillMount()将在React未来版本(官方说法 17.0)中被弃用。为了避免副作用和其他的订阅,官方都建议使用componentDidMount()代替。这个方法是用于在服务器渲染上的唯一方法。

componentWillReceiveProps调用时机?

初始化父组件第一次将数据传递给子组件的时候不会去执行,只有属性props改变的时候,子组件的钩子函数才会触发执行。

受控组件与非受控组件的区别

受控组件:受到数据控制,例如表单元素,当输入框中的内容发生改变的时候,使其更改组件。数据驱动的理念,提倡内部的一些数据最好与组件的状态进行关联。

父组件可以将自己的属性传递给子组件,子组件通过this.props调用。

非受控组件;不会受到数据(state)的控制,由DOM本身进行管理,输入框的内容发生改变了,直接通过ref进行标记,然后直接获取使用即可。

Redux

你会把数据统一放入到redux中管理,还是共享数据放在redux中管理?

把所有的数据放入到redux中管理。(props,state) 项目一旦有问题,可以直接定位问题点。 组件扩展的时候,后续涉及到传递的问题。本来的话,自己使用数据,但是后来公用,还需要考虑如何传递。

redux中存储数据可以存储至少5G以上的数据。 目的就是方便数据统一,好管理。

React连接Redux用到什么?

react-redux辅助工具

核心组件:Provider提供者,属性上通过store将数据派给容器组件,connect用于连接容器组件与UI组件。

引入provider,哪个地方需要用到状态和属性,就包裹一下,并且一旦有状态改变,就会监听到,并且将最新的状态返回给UI组件。

ReactDOM.render(
    <Provider store = {store}>
        <Router>
            <App />
        </Router>
    </Provider>, document.getElementById('root'));

connect()(UI组件) ==》返回一个容器组件

这个方法参数是state=>store.getState()

这个方法返回什么,UI组件的属性上就是有什么

当状态改变的时候,容器组件就会监听状态的变化,并且把更新后的状态通过属性的方法传递给UI组件

因为容器组件已经帮助我们实现了store.subscribe方法的订阅,这时候就不需要constructor函数和监听函数,容器组件就会自动订阅状态的变化,UI组件通过this.props来获取函数中返回的state,这时候当我们对state进行操作的时候,状态就会改变,视图重新渲染,componentWillReceiveProps这个钩子函数就会执行,实现了对状态改变的事实监听。

connect中有两个参数,一个是映射状态到组件属性(mapStateToProps),一个是映射方法到组件属性(mapDispatchToProps),最终内部返回一个容器组件帮助我们做监听操作,一旦状态更改,UI组件就会重新渲染。

connect(mapStateToProps,mapDispatchToProps)(ui组件)

容器组件内部帮你做了 store.subscribe() 状态变化 ==> 容器组件监听状态改变了 ==> 通过属性的方式给ui组件传递

store.getState()的状态转化为展示组件的props

当我们需要挂载很多方法的时候我们可以将之简写

首先我们引入bindActionCreators

import {bindActionCreators} from "redux"

然后我们使用bindActionCreators将所有操作状态的方法全部取出来绑定到UI组件的属性上,使用的时候直接通过this.props取即可。

//actionCreators很纯粹了,需要创建action然后返回action即可!
//ui组件的属性上就就会有改变状态的方法了,用的话通过this.props.方法名
const mapDispatchToProps = dispatch=>{
    return bindActionCreators(actionsCreators,dispatch)
}
connect(mapStateToProps,mapDispatchToProps)(UI组件)

Redux的组成

redux有四个组成部分:

store:用来存储数据

reducer:真正的来管理数据

actionCreator:创建action,交由reducer处理

view: 用来使用数据,在这里,一般用react组件来充当

image

什么时候用redux?

如果你不知道是否需要 Redux,那就是不需要它

只有遇到 React 实在解决不了的问题,你才需要 Redux

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

需要使用redux的项目:

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

从组件层面考虑,什么样子的需要redux:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

redux的设计思想:

  1. Web 应用是一个状态机,视图与状态是一一对应的。

  2. 所有的状态,保存在一个对象里面(唯一数据源)。

Redux的流程

Redux的流程:

  1. 创建store:

    从redux工具中取出createStore去生成一个store。

  2. 创建一个reducer,然后将其传入到createStore中辅助store的创建。

    reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态。

    想要给store创建默认状态其实就是给reducer一个参数创建默认值。

  3. 组件通过调用store.getState方法来使用store中的state,挂载在了自己的状态上。

  4. 组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer

  5. reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变, reducer返回什么状态,store.getState就可以获取什么状态。

  6. 我们可以在组件中,利用store.subscribe方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态。

reducer是一个纯函数?你对纯函数是怎么理解的?

reducer是state最终格式的确定。它是一个纯函数,也就是说,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。 reducer对传入的action进行判断,然后返回一个通过判断后的state,这就是reducer的全部职责

Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。

纯函数是函数式编程的概念,必须遵守以下一些约束。

不得改写参数

不能调用系统 I/O 的API

不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

(1)只要是同样的输入,必定得到一个同样的输出。

(2)千万不能更改之前的状态,必须要返回一个新状态

(3)里面不能有不纯的操作,例如Math.random(),new Date(),io操作

redux中间件的原理是什么?如何理解?

通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量。

redux中间件就是指action到达store之间。store.dispatch(action)方法将action派发给了store 并且我们的action只能是一个对象,需求的时候,就需要考虑到一些异步逻辑放在哪里去实现? 采用中间件之后,action就可以是一个函数的形式了,并且会把函数式的action转成对象,在传递给store.

dispatch一个action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware。你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

换言之,redux的中间件都是对store.dispatch()的增强

redux有哪些中间件?

做异步的操作在action里面去实现!需要安装redux中间件 redux-thunk redux-saga (基于配置文件 es7 async await) redux-promise

redux-thunk原理

Redux-thunk是一个Redux中间件,位于 Action与 Strore中间,简单的说,他就是对store.dispatch进行了一次升级,他通过改造store.dispatch,可以使得store.dispatch可以接受函数作为参数。

可以看出来redux-thunk最重要的思想,就是可以接受一个返回函数的action creator。如果这个action creator 返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行。 正因为这个action creator可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。

React项目相关

项目中遇到哪些问题?如何解决?

用于轮播图组件的远程数据已经请求回来了,并且也已经实例化完毕了。发现navbar能滑,但是滑不过去的现象

原因:因为我们在ComponentDidMount中请求数据,这个操作是异步操作,不会阻止后续代码,所以我们一边执行请求数据的代码一边实例化,数据还在请求中的时候,实例化已经开始执行,等数据回来的时候实例化已经早就结束了。

方法一:放入在componentDidUpdate钩子函数里面

问题:当页面中的无关数据改变的时候同样会走这个钩子函数,那就会导致它重新执行。

所以我们给Swiper的实例化起一个别名

在componentDidUpdate这个函数中if语句判断它是否存在,如果不存在再去实例化,存在的话就不需要再去执行实例化操作。

//在这个钩子函数里面  就可以获取到因数据改变导致的虚拟dom重新渲染完成的真实dom结构了
    componentDidUpdate(){
        if(!this.swiper)this.initSwiper()  //数据可能还在请求当中,但是这个实例化操作已经完毕了。等后续数据来了,实例化提前早就结束了。
    }

方法二:会发现上面的方案会多写一个钩子函数,可不可以在componentDidmount里面实现此功能呢?

将实例化操作写在获取数据的回调函数里

componentDidMount(){
        //请求数据 更改navs
        this.props.getNavs(()=>{
            this.initSwiper()  
        })
    }

在store/home/actionCreators文件中让getNavs接收这个回调函数,在数据请求结束后执行callback回调函数。

import {Get} from "../../modules/axios-utils"
import {GET_NAV_INFO} from "./const"
export default {
    getNavs(callback){
        return dispatch=>{
            Get({
                url:"/sk/navs"
            }).then(res=>{
                let navs = res.data.data.object_list
                dispatch({ type: GET_NAV_INFO,navs})
                callback && callback() 
            })
        }
    }
}

我们跳转页面的时候它会多次请求数据,所以我们需要在componentDidMount这个钩子函数中判断redux里面navs是否存在,存在就不需要再发送请求了,这时候从别的页面再跳转回首页就不会重复请求数据,但是数据划不动,所以我们需要在函数中再次执行Swiper初始化操作。

let {navs} = this.props;
        if(navs){
            this.initSwiper()
            return false;
        }

reducer中的深拷贝与浅拷贝

我们进行改变状态操作时,componentWillReceiveProps()这个函数没有被触发,说明视图没有检测到状态的改变。这时候我们来到reducer.js这个文件中,查看执行添加操作的函数,我们通过

let new_state = {...prevState}

这句代码将prevState解构出来赋值给new_stat,,我们往new_state中的todos数组push一个新内容,并没有返回新的状态,那是因为当我们对new_state这个数组进行操作的时候,会影响到之前的prevState中的todos,因为todos是个引用类型,它和new_state中的todos指向同一块内存空间,所以当我们执行push操作的时候相当于更改了之前的状态。在redux中规定,千万不能对之前的状态进行任何操作,必须要返回一个新状态,内部以此为依据来判断到底有没有新的状态产生,根据之前状态与新状态的地址比较,更改之后的地址跟之前的地址是同一个的话,就说明没有产生新状态。所以即便我们操作的是new_state中的todos,实际上我们更改的也是prevState中的todos,所以不会有新的状态产生。

所以我们要使用深拷贝,拷贝出来一份一样的数组,并且这个新数组的引用地址和之前的引用地址完全不同。

ew_state.todos = new_state.todos.slice();

深拷贝浅拷贝有什么区别?

1. 浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。

2. 深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

react,jquey,vue是否可以共存在一个项目中?

可以存在,互不干扰。

 <div></div>
  <div id="react"></div>
  <div id="vue"></div>

ReactDOM.render(,document.getElementById("react")); new Vue({el:"#vue",router,store});

服务端渲染SSR

什么是服务端渲染? 核心在于方便seo优化

后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完整的html页面,再直接返回给浏览器,以便用户浏览。 例如:www.cnblogs.com/cate/design

什么是客户端渲染? 分担到客户端

数据由浏览器通过ajax动态获得,再通过js将数据填充到dom元素上最终展示到网页中,这样的过程就叫做客户端渲染。 例如:m.maizuo.com/v5/#/films/…

服务端渲染与客户端渲染区别?

客户端渲染不利于SEO搜索引擎优化 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的 服务端渲染对SEO友好,经过服务端渲染的页面,在网络传输的时候,传输的是一个真实的页面,所以爬虫就会对这个页面中的关键数据进行分析、收录。 服务端渲染缺点就是 对服务器压力比较大 客户端渲染减轻了服务器端的渲染压力,能够实现前后端分离开发 客户端渲染缺点就是 对SEO相当的不友好

主流UI开发框架

你都使用过哪些UI开发框架(类库)?

VUE:Vant、Element、Mint UI、iView

React:Ant Design

移动端:Ant Design Mobile

PC端:Bootstrap、Ant Design

混合开发:MUI

有没有用一些脚手架的搭建?,脚手架有什么作用?

Vue-cli create-react-app wepy-cli

包含基础的依赖库,只需要 npm install就可以安装,快速搭建项目。

Mint-UI用到哪些模块,怎么用,在vue里怎么注册,引入

引入:

(1)完整引入

(2)按需引入

image

移动端

移动端开发

编写移动端时的四个步骤

(1)添加mate声明

在编辑器中输入mate:vp按tab键

	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

(2)写移动端必须用怪异盒模型,box-sizing:border-box;

使用弹性盒布局:display:flex;

(3)根据设计图设置单位,rem相对于根元素。

Iphone5:宽度640px;

html:font-size:31.25vw;

31.25vw=100px=1rem;

Iphone6:宽度750px;

html:font-size:26.67vw;

26.675vw=100px=1rem;

(4)编写一屏页面:

html,body{height:100%;}

移动设备单位

ppi/dpi (每英寸所拥有像素点的数量)

​ dpr > 设备像素比 (物理像素、逻辑像素)

​ 逻辑像素就是css设置的像素

​ 物理像素就是设备显示的像素

​ dpr == 物理像素 / 逻辑像素

​ dpr 一般考虑的值 > 2或者3

​ 如果移动端设计图的宽度为750/640 > 选择的dpr为2

​ 如果移动端设计图的宽度为1080 > 选择的dpr为3

​ 例:

​ 如果设计图为640px

​ dpr为2

​ 如果从ps中量出元素宽度为300px;

​ 在css里面设置的为 300px / dpr(2) == 150px;

页面布局有哪几种方式

**固定布局:**以像素作为页面的基本单位,不管设备屏幕及浏览器宽度,只设计一套尺寸;

**可切换的固定布局:**同样以像素作为页面单位,参考主流设备尺寸,设计几套不同宽度的布局。通过识别的屏幕尺寸或浏览器宽度,选择最合适的那套宽度布局;

**弹性布局(百分比布局):**以百分比作为页面的基本单位,可以适应一定范围内所有尺寸的设备屏幕及浏览器宽度,并能完美利用有效空间展现最佳效果;

**混合布局:**同弹性布局类似,可以适应一定范围内所有尺寸的设备屏幕及浏览器宽度,并能完美利用有效空间展现最佳效果;只是混合像素、和百分比两种单位作为页面单位。

**布局响应:**对页面进行响应式的设计实现,需要对相同内容进行不同宽度的布局设计,有两种方式:pc优先(从pc端开始向下设计);

移动优先(从移动端向上设计);无论基于那种模式的设计,要兼容所有设备,布局响应时不可避免地需要对模块布局做一些变化(发生布局改变的临界点称之为断点)

####什么是响应式设计?响应式设计的基本原理是什么

响应式是指根据不同设备浏览器分辨率或尺寸来展示不同页面结构、行为、表现的设计方式。

响应式设计的基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理。

####说说对于移动端页面部局你都知道哪几种布局方案,并说明其各自特点及实现的方法

移动端布局常用的有100%布局,等比缩放布局,或是混合布局。

百分比布局也称作流式布局,一般适用一些流式页面的布局;

等比缩放布局可以利用rem或vw等方式来实现;

rem

rem和em的区别

rem(font size of the root element)是指相对于根元素 (html)的字体大小的单位

em是相对于父元素

rem方法的封装

document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + "px"

window.onresize = function(){
    document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + "px"
}

px和em的区别

px和em都是长度单位,区别是:px的值是固定的,指定是多少就是多少,计算比较容易。em得值不是固定的,并且em会继承父级元素的字体大小。

浏览器的默认字体高都是16px。所以未经调整的浏览器都符合: 1em=16px。那么12px=0.75em, 10px=0.625em。

弹性盒

####Flex弹性盒布局

Flex容器:采用 Flex 布局的元素的父元素;

Flex项目:采用 Flex 布局的元素的父元素的子元素;

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。

项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。

不兼容所有浏览器,不适合写pc端,主要用于移动端。

弹性元素只能影响子元素,无法影响所有的后代元素。

弹性布局属于css3只兼容高版本浏览器

Flex垂直居中

flex:弹性盒

justify-content 项目在主轴上的对齐方式

参数:center 居中/space-between 两端对齐/space-around均分对齐

align-items 项目在交叉轴上的对齐方式

参数:center 居中

flex-direction 决定主轴的方向

row X轴/column Y轴

参考:Flex 布局教程:语法篇

www.ruanyifeng.com/blog/2015/0…

布局方式

媒体查询如何实现?

媒体查询 @media screen and (max-width:300px){ …} 最大宽度

Bootstrap12栅格系统

Row col col-md-4 col-md-4 col-md-4

在Bootstrap框架当中,每列基本被分为12格,要使用栅格系统需要在该<div>标签当中设置class="container",而对于每一行则用<div class="row">包着,内部由于有12格,因此可以结合具体情况分配比例,举例:

<div class="container">
    <!-- 定义栅格系统 -->
    <div class="row">
        <!-- 定义一行 -->
        <div class="col-md-4">
            <!-- 定义了三列,每列占3格 -->
            <img src="timg.jpg" width="300px">
        </div>
        <div class="col-md-4">
            <img src="timg.jpg" width="300px">
        </div>
        <div class="col-md-4">
            <img src="timg.jpg" width="300px">
        </div>
    </div>
    <div class="row">
        <!-- 定义了4列,分别占6、3、2、1格 -->
        <div class="col-md-6">
            <img src="timg.jpg" width="300px">
        </div>
        <div class="col-md-3">
            <img src="timg.jpg" width="300px">
        </div>
        <div class="col-md-2">
            <img src="timg.jpg" width="300px">
        </div>
        <div class="col-md-1">
            <img src="timg.jpg" width="300px">
        </div>
    </div>
</div>

Grid网格布局

网格布局(Grid)是最强大的 CSS 布局方案。

它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了。

image

参考:CSS Grid 网格布局教程

www.ruanyifeng.com/blog/2019/0…

趋势:flex和grid使布局更简单

www.cnblogs.com/hsprout/p/8…

混合开发&小程序

混合开发模式

我们知道混合开发的模式现在主要分为两种,H5工程师利用某些工具如DCLOUD产品、codorva+phonegap等等来开发一个外嵌native壳子的混合app。 还有就是应用比较广泛的,有native开发工程师和H5工程师一起写作开发的应用,在native的webview里嵌入H5页面,当然只是部分界面这么做,这样做的好处就是效率高,开发成本和维护成本都比较低,较为轻量,但是有一个问题不可避免的会出现,就是js和native的交互。

进入域后根据不同的情况显示不同的页面(PC/MOBILE)

很多情况下,一个应用会有PC和移动端两个版本,而这两个版本因为差别大,内容多,所以不能用响应式开发但是单独开发,而域名只有一个,用户进入域后直接返回对应设备的应用,做法主要有两种:

1. 前端判断并跳转
    进入一个应用或者一个空白页面后,通过navigator.userAgent来判断用户访问的设备类型,进行跳转

2. 后端判断并响应对应的应用
    用户地址栏进入域的时候,服务器能接收到请求头上包含的userAgent信息,判断之后返回对应

小程序开发的形式

目前小程序开发主要有三种形式:原生、wepy、mpvue,其中wepy是腾讯的开源项目;mpvue是美团开源的一个开发小程序的框架,全称mini program vue(基于vue.js的小程序),vue开发者使用了这个框架后,开发小程序的效率将得到很大的提升。

wepy与mpvue如何选择?

image

参考:使用mpvue开发微信小程序——原生微信小程序、mpvue、wepy对比

blog.csdn.net/fabulous111…

微信小程序里面各个部分是干什么的

这些文件可以分为四类,分别是以js、wxml、wxss和json结尾的文件。

以js结尾的文件,一般情况下是负责功能的,比如,点击一个按钮,按钮就会变颜色。

以wxml为后缀的文件,一般情况下负责布局,比如,把按钮放在屏幕的上方,还是放在屏幕的正中间。

以wxss为后缀的文件,是负责渲染的功能,比如,按钮是什么颜色,是正方形还是圆形。

以json为后缀的文件,这里可以暂时理解为更改屏幕上方的标题的,也就是说明页面的顶部标题。

参考:微信小程序里面各个部分是干什么的

www.jianshu.com/p/3863f8b75…

你经常浏览哪些技术网站

  • 酷壳
  • 掘金
  • stackoverflow(国外)
  • 思否SegmentFault

前端开发的优化问题

性能优化

简述一下你对web性能优化的方案?

1、尽量减少 HTTP 请求

2、使用浏览器缓存

3、使用压缩组件

4、图片、JS的预载入

5、将脚本放在底部

6、将样式文件放在页面顶部

7、使用外部的JS和CS

如何优化项目

(1) 减少http请求次数:CSS Sprites(雪碧图), JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。

(2) 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数

(3) 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。

(4) 当需要设置的样式很多时设置className而不是直接操作style。

(5) 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。

(6) 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。

(7) 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。

(8) 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。

雅虎14条性能优化原则

1. 尽可能的减少 HTTP 的请求数 content
2. 使用 CDN(Content Delivery Network) server
3. 添加 Expires 头(或者 Cache-control ) server
4. Gzip 组件 server
5. 将 CSS 样式放在页面的上方 css
6. 将脚本移动到底部(包括内联的) javascript
7. 避免使用 CSS 中的 Expressions css
8. 将 JavaScript 和 CSS 独立成外部文件 javascript css
9. 减少 DNS 查询 content
10. 压缩 JavaScript 和 CSS (包括内联的) javascript css
11. 避免重定向 server
12. 移除重复的脚本 javascript
13. 配置实体标签(ETags) css
14. 使 AJAX 缓存

你如何对网站的文件和资源进行优化?

  1. 文件合并:目的是减少http请求
  2. 文件压缩:目的是直接减少文件下载的体积
  3. 使用CDN(内容分发网络来托管资源)
  4. 缓存的使用:并且多个域名来提供缓存
  5. GZIP压缩JS和CSS文件

参考:你如何对网站的文件和资源进行优化?

blog.csdn.net/xujie_0311/…

SEO优化

一个单页应用程序SEO友好吗?

单页应用实际是把视图(View)渲染从Server交给浏览器,Server只提供JSON格式数据,视图和内容都是通过本地JavaScript来组织和渲染。而搜索搜索引擎抓取的内容,需要有完整的HTML和内容,单页应用架构的站点,并不能很好的支持搜索。

参考:单页应用SEO浅谈

www.chinaz.com/web/2014/12…

一个单页应用程序SEO友好吗?

baijiahao.baidu.com/s?id=160547…

网页自身SEO优化(如何让网站被搜索引擎搜索到)

  1. 页面主题优化

实事求是的写下自己网站的名字,网站的名字要合理,最好包含网站的主要内容。

  1. 页面头部优化

页面头部指的是代码中部分,具体一点就是中的“Description(描述)”和“Keywords(关键字)”两部分

  1. 超链接优化

(1)采用纯文本链接,少用,最好是别用Flash动画设置链接,因为搜索引擎无法识别Flash上的文字.

(2)按规范书写超链接,这个title属性,它既可以起到提示访客的作用,也可以让搜索引擎知道它要去哪里.

(3)最好别使用图片热点链接,理由和第一点差不多。

  1. 图片优化(alt属性,title属性)

网络爬虫对title和alt友好,网络优化时必须写。

  1. 为网站制作一个“网站地图”

  2. PageRank(pr值,友情链接)

  3. 静态页面与动态页面

  4. 避免大“体积”的页面

  5. 最重要的一点!合理的代码结构

框架性能优化

react性能优化

  • 使用生产环境production版本的react.js

  • 重写shouldComponentUpdate来避免不必要的dom操作,一旦返回true ,组件更新操作;返回false,就不会更新,节省性能。

  • 使用key来帮助React识别列表中所有子组件的最小变化

  • PureComponent 纯组件 ,自带shouldComponentUpdate,可以对props进行浅比较,不会比较对象这些东西。 发现后面的props与前面的props一样,就不会进行render了。

    class Test extends React.PureComponent{     <div><Test a={10}/></div>
    	constructor(props){
    		super(props);
    	}
    	render(){
    		return <div>hello...{this.props.a}</div>
    	}
    }
    

VUE性能优化

像VUE这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时,简单说就是:进入首页不用一次加载过多资源造成的用时时长。