react-router-dom入门到深入

React-router-dom

一、安装

1
npm install react-router-dom -save-d

二、使用

创建A和B两个页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//A.js
import React from 'react';
function A(){
return (
<div>A</div>
);
}

//B.js
import React from 'react';
function B(){
return (
<div>B</div>
);
}

创建一个路由文件router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react';
import {HashRouter, Route, Switch} from 'react-router-dom';
import A from './a';
import B from './b';

const TestRouter=()=>(
<HashRouter>
<Switch>
<Route exact path="/" component={A}/>
<Route exact path="/b" component={B}/>
</Switch>
</HashRouter>
);
export {TestRouter};

最后在组件的渲染页面,渲染该路由即可

1
2
3
4
5
6
7
8
import React from 'react';
import ReactDOM from 'react-dom';
import {TestRouter} from './router/router';

ReactDOM.render(
<Router/>,
document.getElementById('root')
);

此时在页面输入http://localhost:3000/#/就会看到A页面,输入http://localhost:3000/#/b就会看到B页面。url中的#号目前暂不考虑

三、路由的跳转

3.1 a标签

可以通过a标签进行页面的跳转,如下修改A页面就可以通过点击A页面中的A

来实现跳转到B页面了

1
2
3
4
5
6
7
8
9
//A.js
import React from 'react';
function A(){
return (
<div>
<a href='#/b' >A</a>
</div>
);
}
3.2 通过Link标签跳转

react-router-dom中提供了Link标签也可以提供页面的跳转,Link的底层还是使用的a标签。

如下面修改B页面,就可以实现点击B跳到A页面

1
2
3
4
5
6
7
8
9
//B.js
import React from 'react';
function B(){
return (
<div>
<Link to='#/'>B</Link>
</div>
);
}
3.3 通过函数跳转

react-router-dom中提供了hashHistory,设置给组件的history属性,然后同过改变this.props.history参数,用来实现页面的跳转.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在router.js文件中给 HashRouter引入history属性
...
import {HashRouter, Route, Switch, hashHistory} from 'react-router-dom';
...
<HashRouter history={hashHistory}>
...

//A.js
import React from 'react';
function A(props){
return (
<div>
<a href='#/b' >A</a>
<button onClick={() => props.history.push('b')}>test</button>
</div>
);
}

这样点击test按钮就可以使用向B页面的跳转了

其中改变this.props.history参数的方法有:

  • push() :添加新的路由
  • replace():替换路由(猜测是替换当前路由,被替换的路由则不能通过goBack返回,防止死循环)
  • goBack():返回上一个路由(返回上级页面)

四、路由传参

4.1 通过路由配置传参

在上面router.js文件中,我们只是配置了路由的绝对路径,有些时候我们在跳转某个路由时还要接收一些参数,该怎么处理呢?简单的可以通过在路由后追加’/:key’,然后再路由对应的组件通过this.props.match.params.key来接收到相应的参数值

如下,我们给B页面传递name参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//router.js
const TestRouter=()=>(
<HashRouter>
<Switch>
<Route exact path="/" component={A}/>
<Route exact path="/b/:name" component={B}/>
</Switch>
</HashRouter>
);
...

//B.js
import React from 'react';
function B(props){
return (
<div>
B
<p>我接收的参数是{props.match.params.name}</p>
</div>
);
}

当然如果传递多个参数就再追加即可

1
<Route exact path="/b/:name/:age" component={B}/>

这样皆可以通过http://localhost:3000/#/b/afei/21来访问到afei和age了,

这种方式虽然可以实现传参,但是稍微少传一个参数就会报错。

如果是使用Link标签如何传参呢,很简单使用query关键 字即可

1
2
3
4
5
6
7
8
9
//B.js
import React from 'react';
function B(){
return (
<div>
<Link to={path:'/a',query:{name:'afei',age:19}}>B</Link>
</div>
);
}

并且总觉得的不如http://localhost:3000/#/b?name=afei&age=19来的方便。(日后补上)

4.2 通过函数隐式传参

那如果是通过函数跳转是,如何传参呢?很简单,在触发时给history传递对象,然后再对应的页面通过this.props.history.location.state获取即可,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//A.js
<button onClick={() => props.history.push(
{
pathname:'/b',
state:{
name:'afei',
age:18
}
}
)}>test</button>

//B.js
import React from 'react';
function B(props){
return (
<div>
B
<p>我的名字是{props.history.location.state.name}</p>
<p>我的年龄是{props.history.location.state.age}</p>

</div>
);
}

注意上边两种方式传参和接参方式不能混了

五、BrowserRouter和hashRouter

有一个现象当我们的router.js使用HashRouter时,我们可以通过浏览器地址栏直接键入url来访问任意路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//HashRouter
import React from 'react';
import {HashRouter, Route, Switch} from 'react-router-dom';
import A from './a';
import B from './b';

const TestRouter=()=>(
<HashRouter>
<Switch>
<Route exact path="/" component={A}/>
<Route exact path="/b" component={B}/>
</Switch>
</HashRouter>
);
export {TestRouter};

此时

  • 当我们可以输入http://localhost:3000/时,地址栏会自动变为http://localhost:3000/#/进行正常访问;
  • 当地址栏输入http://localhost:3000/#/可以正常访问A页面

  • 当地址栏输入http://localhost:3000/#/b可以正常访问B页面

但是当我们的router.js中将HashRouter换成BrowserRouter时,就会出现意外。

1
2
3
4
5
6
7
...
<BrowserRouter>
<Switch>
<Route exact path="/" component={A}/>
<Route exact path="/b" component={B}/>
</Switch>
</BrowserRouter>

表现为:

  • 当我们可以输入http://localhost:3000/时,可以正常访问A页面
  • 当地址栏输入http://localhost:3000/b不能正常访问B页面,报错Cannot GET/b
  • 当我们在A页面以<Link to="b">B</Link>的形式,可以正常访问B页面

神不神奇,为什么HashRouter可以直接访问路由,而BrowserRouter访问不到?

这是应为BrowserRouter每次改变路由时,会向服务器请求该路由,服务端没有该路径的资源,自然会报错了。当然可以开启一个服务,路由对应的路径下放置资源文件,此错误就会消失。

而使用HashRouter时,会在路径后边添加/#/,并且/#/及后边的内容是不会发送给服务器的,当访问http://localhost:3000/#/b时,其实服务器端收到的依然是http://localhost:3000/#/及后边的内容

会直接在前端进行处理区分。

但是官方却还是推荐我们使用BrowserRouter,这就和它们内部的机制有关了

  • BrowserRouter使用的是HTML5中的history的api,通过pushStatereplaceStatepopState来完成UI和URL的同步
  • HashRouter使用的是URL的hash方式,来完成UI和URL的同步

下章细讲

六、history和URL

下面的内容只做了解

6.1 history

history接口允许操作浏览器的曾经在标签页或者框架里访问会话历史记录。它有一些自己的属性,这里是说一下比较重要的History.stateHistory.length

  • state是一个只读属性,访问该属性会返回一个表示历史堆栈顶部的状态的值。它可以不必等待popstate事件而查看状态的方式
  • length是一个只读属性,表示会话历史中元素的数目,包括当前的加载页。

下面来看history中三个比较重要的方法

  • pushState():按指定的名称和URL(如果提供该参数)将数据push进会话历史栈,这个数据被DOM 进行了不透明处理。可以理解为往历史记录里追加新的页面
  • replaveState():按指定的数据,名称和URL(如果提供该参数),更新历史栈上最新的入口。这个数据被DOM 进行了不透明处理。可以理解更新历史记录里当前页面信息
  • popState():很奇怪,没有查到该方法
  • back():往上一页,模拟浏览器左上角的撤销功能
  • forward():往下一页,模拟浏览器左上角的下一页功能
  • go():通过当前页面的相对位置从浏览器历史记录( 会话记录 )加载页面。比如:参数为-1的时候为上一页,参数为1的时候为下一页。参数超过区间范围(length区间)或不合法时不做任何操作

总的来说可以将history看作是维护浏览器历史记录的管理器。

6.2 URL

URL 接口是一个包含若干静态方法的对象,用来创建 URLs。它的属性主要是实现URLUtils 中定义的属性,包括:

  • href:包含完整URL的字符串
  • host:包含URL域名+“ : ”+端口号的字符串
  • Hostname:包含URL域名的字符串
  • port:包含URL端口号的字符串
  • pathname:以/起头紧跟URL文件路径的字符串
  • search:以起头紧跟URL文件路径的字符串
  • hash:以#起头紧跟URL文件路径的字符串
  • username:包含在域名前面指定的用户名字符串
  • password:包含在域名前面指定的密码字符串
  • origin:只读属性,返回一个包含协议名、域名和端口号的字符串

  • searchParams:返回一个用来访问当前URL GET请求参数的 URLSearchParams 对象。

URL还有一些静态方法,不再赘述。这里只需要明白所谓的#是URL的hash属性的规范,就跟seacrh的?一样。

参考:

https://www.jianshu.com/p/8954e9fb0c7e

https://reacttraining.com/react-router/web/api/BrowserRouter

https://www.cnblogs.com/soyxiaobi/p/11096940.html

https://developer.mozilla.org/zh-CN/docs/Web/API/History

https://developer.mozilla.org/zh-CN/docs/Web/API/URL

React高阶组件笔记

一、高阶组件的概念

  • 高阶组件就是接收一个组件作为参数并返回一个数组
  • 高阶组件是一个函数,并不是组件

二、为什么需要高阶组件

  • 多个组件都需要某个相同的功能,使用高阶组件减少重复实现

三、高阶组件示例

  • react-redux中的connnect
1
export default connect(mapStateToProps,mapDispatchToProps)(Header);

四、编写高阶组件

  • 实现一个普通组件
  • 将普通组件使用函数包裹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React ,{Component} from 'react';
//接收一个入参,参数就是一个组件
function d(WrappedComponent){
return class D extends Component{
render(){
return (
<div>
<WrappedComponent />
</div>
);
}
}
}
export default d;

五、使用高阶组件

  • higherOrderComponent(WrappedComponent);

  • @higherOrderComponent. 装饰器模式

    1、运行命令:react-scripts reject 中途选yes

    2、安装依赖:npm install -D babel-preset-stage-2

    ​ npm install -D babel-preset-react-native-stage-0

    3、添加.babelrc的文件

    ​ {

    ​ “presets”:[“react-native-stage-0/decorator-support”]

    ​ }

    此时我们的被装饰者可以这么写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    > import React ,{Component} from 'react';
    > import d from './D';
    >
    > @d
    > class B extends Component{
    > render(){
    > return (
    > <div>
    > //...
    > </div>
    > );
    > }
    > }
    >

六、高阶组件的应用

  • 代理方式的高阶组件

    返回的新组件类直接继承自React.Component类,新组件扮演的角色传入参数组件的一个代理,在新组件的render函数中,将被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全都转手给了被包裹的组件

  • 继承方式的高阶组件

    采用继承关联作为参数的组件和返回组件,加入传入的组件参数是WrappedComponent,那么返回的组件就直接继承自WrappedComponent

  • 高阶组件显示名

    在使用高阶组件时,有时候不好调试,并不知道最终渲染的是哪一个子组件,这时候就需要使用displayName了,可组件内部可以定义该静态变量

    1
    2
    > static displayName=`newComponent${getDisplayName(WrappedComponent)}`;//getDisplayName()是在外部自定义的
    >

6.1 代理方式的高阶组件

  • 操纵prop. 可以增加、删除属性
  • 抽取状态
  • 访问ref
  • 包装组件

七、实战

7.1 初始化工程

1
2
3
create-react-app tabbar
//若未安装create-react-app,可以使用
npx create-react-app tabbar

src文件夹下新建components的文件夹,用来放置组件类;

components的文件夹下,新建tabbar的文件夹;

然后在tabbar文件夹下新建index.js的文件,作为开发的主文件,同时在tabbar文件夹下新建一个样式文件index.css

并且在App.js中引入Tarbar组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//index.js
import React ,{Component} from 'react';
class Tarbar extends Component{
render(){
return (
<div className='tarbar'>
123
</div>
);
}
}

//index.css
.tarbar{
height:50px;
}

//App.js
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';
class Tarbar extends Component{
render(){
return (
<div className='App'>
<Tarbar />
</div>
);
}
}

7.2使用iconfont

进入阿里巴巴矢量图标库,下载四个我们需要的图标(home、car、category、user)。添加项目-下载到本地-解压,然后将里面iconfont开头的文件拷出

1
2
3
4
5
6
iconfont.css
iconfont.eot
iconfont.js
iconfont.svg
iconfont.ttf
iconfont.woff

src文件夹下新建static的文件夹,用来放置所有的静态文件,将上面的iconfont的文件放进来。

然后在App.js文件中引入iconfont

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//App.js
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';
import './static/iconfont.css';//引入iconfont

class Tarbar extends Component{
render(){
return (
<div className='App'>
<Tarbar />
<div className='iconfont iconfont-home'></div>
</div>
);
}
}

经完善后,APP.js的文件为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//App.js
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';
import './static/iconfont.css';//引入iconfont

const tabbar={
{img:'icon-home',text:'首页'},
{img:'icon-fenlei',text:'分类'},
{img:'icon-gouwuche',text:'购物车'},
{img:'icon-user',text:'我的'},
}

class Tarbar extends Component{
render(){
return (
<div className='tabbar'>
<div className='tabbar-content'>
{
tabbar.map((v,i)=>{
<div key={i} className='tabbar-item'>
<div className={'iconfont '+v.img}></div>
<div>{v.text}</div>
</div>
});
}
</div>
</div>
);
}
}

接下来index.css文件中添加样式文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
.iconfont{
font-size:28px !important;//!important强制生效
}
.tarbar{
height:50px;
position:absolute;
bottom:0;
width:100%;
border:1px solid #ccc;
padding:5px 0;
}
.tabbar-content{
display:flex;
}
.tabbar-item{
flex:1;//每个元素的区间是均分的
}

7.4 添加事件

需要给tabbar-item添加点击切换事件,图标响应不同颜色。思路:通过点击事件-改变state-实时更新样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//Tabbar
...
class Tarbar extends Component{
constructor(props){
super(props);
this.state={
index:''
}
}
itemChange=(i)=>{
this.setState({index:i});
}

render(){
return (
<div className='tabbar'>
<div className='tabbar-content'>
{
tabbar.map((v,i)=>{
<div key={i} className={'tabbar-item'+ this.state.index===i?'active:'' } onClick={()=>this.itemChange(i)}>
<div className={'iconfont '+v.img}></div>
<div>{v.text}</div>
</div>
});
}
</div>
</div>
);
}
}

//在index.css中给active添加样式
.active{
color:red;
}

7.5 添加路由

安装包

1
cnpm install -S react-router-dom

在src 目录下新建文件router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import {BrowserRouter,Route,Switch} from 'react-router-dom ';
//下面四个页面在src目录的pages的页面
import Home from "./pages/home";
import Category from "./pages/category";
import Car from "./pages/car";
import User from "./pages/user";

export default ()=>(
<BrowserRouter>
<Switch>
<Route path='/home' component={Home}></Route>
<Route path='/category' component={Category}></Route>
<Route path='/car' component={Car}></Route>
<Route path='/user' component={User}></Route>
</Switch>
</BrowserRouter>
);

然后在App.js中将router.js导入即可

1
2
3
4
5
6
7
8
9
10
11
12
13
//App.js
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';
import RouterMap from './router';
class Tarbar extends Component{
render(){
return (
<div className='App'>
<RouterMap />
</div>
);
}
}

这样当我们在浏览器输入localhost:3030/home时就显示的Home页面

7.6 Tabbar路由的切换

接下来我们需要在Home、Car、Category、User中引入Tabbar

1
2
3
4
5
6
7
8
9
10
11
12
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';

class Home extends Component{
render(){
return (
<div>
<Tabbar />
</div>
);
}
}

为了方便,将Home等组件内容用一张图片代替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';

class Home extends Component{
render(){
return (
<div>
<img className='bg' src={require('../static/imgs/home.png')} />
<Tabbar />
</div>
);
}
}
export default Home;

//在APP.css中添加样式
.bg{
width:100%;
height:100%;
}

此时我们点击下面四个按钮,只是按钮 变红,实际上我们想让我们的路由改变,展示不同的页面;

我们需要修改Tabbar各item的点击事件,可以先将数据写进tabbar数据中

1
2
3
4
5
6
const tabbar={
{img:'icon-home',text:'首页',link:'/home'},
{img:'icon-fenlei',text:'分类',link:'/category'},
{img:'icon-gouwuche',text:'购物车',link:'/car'},
{img:'icon-user',text:'我的',link:'/user'},
}

这样在跳转的时候我们就知道是哪一个路由了,为了简单也不可以不用点击事件,用link标签来做;

1
2
3
4
5
6
7
8
9
10
11
12
import {Link} from 'react-router-dom';

<div className='tabbar-content'>
{
tabbar.map((v,i)=>{
<Link to={v.link} key={i} className={'tabbar-item'+ this.state.index===i?'active:'' }>
<div className={'iconfont '+v.img}></div>
<div>{v.text}</div>
</Link>
});
}
</div>

这是link标签又默认样式,需要去掉默认样式,

在App.css中给a标签编写样式

1
2
3
4
a:hover,a:visited,a:link,a:active{
text-decoration:none;//去掉下划线
color:#333
}

最后还需要给tabbar添加背景颜色,并且发现在点击不同选项时颜色不改变了。这是因为之前我们是根据数字来判断选项是否更改颜色,现在由于在不同页面中没有数字的概念,所以active是不生效的。可以通过获取当前的url来概念颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//Tabbar
import {Link} from 'react-router-dom';
...
class Tarbar extends Component{
constructor(props){
super(props);
this.state={
index:''
}
}
itemChange=(i)=>{
this.setState({index:i});
}

render(){
const url=window.location.href;
return (
<div className='tabbar'>
<div className='tabbar-content'>
{
tabbar.map((v,i)=>{
<Link to={v.link} key={i} className={'tabbar-item'+ (url.indexOf(v.link)>-1)?'active:'' }>
<div className={'iconfont '+v.img}></div>
<div>{v.text}</div>
</Link>
});
}
</div>
</div>
);
}
}

这时可以给选项添加正确的class,但是class并未生效,active的作用被覆盖了,需要使用!important 来强制生效

1
2
3
4
//在index.css中给active添加样式
.active{
color:red !important;
}

此时还会发现另一个问题,我们的tabbar会遮挡Home、Car等页面,该如何处理呢,

可以给Home等组件的的背景css添加margin-bottom,值设为tabbar的高度

1
2
3
4
5
6
//在APP.css中添加样式
.bg{
width:100%;
//height:100;//需要把这个属性删除,以实现内容的自动滚动
margin-bottom:50px;
}

但是这时又发现我们的tabbar随着Home等组件上下滚动了,这不是我们想要的结果,查看tarbar的布局,position:absolute,我门想要tabbar相对于视口来布局的,将position改为fixed即可

7.7 高阶组件改写Tabbar

上面我们已经把所有功能实现了,但是对于Home、Car、Category、User存在大量重复性的代码,这就可以使用高阶组件进行提取。

很简单,遵循两个步骤就可以了

  • 实现一个普通组件
  • 将普通组件使用函数包裹

在index.js中找到Tabbar,使用函数将其包裹起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
const Tabbar=(WrappedComponent)=> class Tabbar extends Component{
...
render(){
const url=window.location.href;
reutrn (
<div className='tabbar-container'>
//注意必须将WrappedComponent在该组件渲染
<div className='tabbar-children'>
<WrappedComponent />
</div>
<div className='tabbar
<div className='tabbar-content'>
//...
</div>
div>
</div>
);
}
}

//将下面的内容放在上边
//class Tabbar extends Component{}

//在tabbar的css文件index.css中给tabbar-children设置样式,就可以把App.css文件中的.bg去掉了。
.tabbar-children{
margin:bottom:50px;
}

在使用时,需要改变一下了,在Home.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React ,{Component} from 'react';
import Tabbar from './component/tarbar';

class Home extends Component{
render(){
return (
<div>
<img className='bg' src={require('../static/imgs/home.png')} />
~~<Tabbar />~~
</div>
);
}
}

export default Tabbar(Home);//输出的时候,用函数更改即可

link可以出发路由的改变,路由改变之后需要APP去监听做何改变

注意:此文为慕课网视频笔记
https://www.imooc.com/learn/1075

js中的EventLoop

一、由promise和setTimeout引出的问题

面试中最尴尬的情况大概就是当对方抛出问题时,你却不知从何说起。当问起promise和setTimeout时接下来很大程度上都会问到执行顺序,引出js的EventLoop的话题。

二、什么是EventLoop

​ JavaScript语言的一大特点就是单线程,这已经成了这门语言的核心特征,将来也不会改变。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

​ js中任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

1
2
3
4
* 所有的同步任务会在主线程中依次执行,而异步任务会被挂起;
* 当被挂起的异步任务有了运行结果,回调函数就会被添加在任务队列中,该任务队列等待被主线程下次执行;
* 当主线程内当然的同步任务被执行完之后,就会获取任务队列中任务,依次同步执行;
* 依次往复,每当主线程中的任务被执行完后,都会去重新获取任务队列中的任务,这就是js的EventLoop

当然上面说的是最简单的模型,当任务队列中还存在异步任务时,会发生什么呢?

1
2
3
* 当主线程执行一个新的任务队列时,回依次执行里面的同步任务,
* 若碰到异步任务,会将它挂起,待异步任务有了运行结果,回调函数会被添加到新的任务队列
* 新的任务对象,等待被主线程下次获取,从而执行。

到这里就可以回答一个初级的面试问题了,当使用setTimeout时延时指定的时间后,函数会不会立即执行?

答案是不会立即执行,因为setTimeout延迟指定时间后,只是将函数放入任务队列中,主线程首先得先执行完当前的所有同步任务后,才能去获取新的任务队列中的任务,然后依次执行任务队列中的任务。

当然,事情远不如此简单。异步跟异步也是有区分的。

三、宏任务(macro-task)和微任务(micro-task)

宏任务一般包括:setTimeout,setInterval

微任务一般包括:Promise(then方法的执行)、process.nextTick

区别在哪呢?

1
2
3
4
* 假设所有的异步任务都有了运行结果,它们的回调被放在了两个任务队列中:宏任务队列和微任务队列
* 当主线程执行完毕后,首先执行微任务队列中的任务,然后执行红任务队列中的任务
* 如果在微任务队列中,有新的微任务,则该微任务队列执行完毕后,会立即执行新的微任务队列,然后再执行宏任务队列
* 当执行宏任务队列时,同样若存在新的微任务,也会在本宏任务队列执行完后立即执行微任务队列;若存在新的宏任务时,新的宏任务会被挂起然后放在新的宏任务队列中,等待主线程的下次轮询。

上面说的比较拗口,可以换一种说法:

1
2
3
* 所有宏任务有了运行结果,回调会被存放了任务队列中;
* 所有的微任务有了运行结果,回调会被理解添加到当前主线程的执行序列中,
* 这样当主线程执行到最后,都会继续执行所有微任务的回调,然后获取新的任务队列进行轮询

更进阶的面试题就是,说出下面的代码的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function(){
console.log('1');
},0);
new Promise(function(resolve){
console.log(2);
process.nextTick(function() {
console.log('3');
})
resolve();
}).then(function(){
console.log(4);
});
console.log(5);

正确答案是:2-5-3-4-1

网上有更多的练习题,可以自己去看看。本文讲到这里

四、其它

宏任务还有setImmediate,node的事件轮询也有一些差别,这里不再详述,可以参考下面的文档。

参考:

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

https://blog.csdn.net/qq_41805715/article/details/84849280
https://blog.csdn.net/u014298440/article/details/88430838

WebAssembly初体验

一、编译wasm

1.1 准备环境

  • 安装git

  • 安装python

  • 准备Emscripten SDK

    1
    2
    3
    4
    5
    6
    $ git clone https://github.com/emscripten-core/emsdk.git
    $ cd emsdk
    $ ./emsdk install latest
    $ ./emsdk activate latest

    $ source ./emsdk_env.sh --build=Release //本命令行窗口可以直接使用emcc命令

1.2 编辑wasm文件

  • 编写math.c文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int add(int a,int b){
    return a+b;
    }

    int minus(int a,int b){
    return a-b;
    }

    int multiply(int a,int b){
    return a*b;
    }
  • 通过emsdk生成math.wasm

    1
    2
    //注意emcc命令,需要事先source ./emsdk_env.sh --build=Release
    $ emcc math.c -Os -s WASM=1 -s SIDE_MODULE=1 -o math.wasm
  • 这样我们的math.wasm文件就生成好了

二、js引入wasm

2.1 初始化一个工程目录

略,注意webpack的版本必须是4及以上,

2.2 引入loader和fetch

1
2
npm install wasm-loader --save-dev
npm install node-fetch --save-dev

2.3 编写引入函数和测试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const getExportFunction = async (url) => {
const env = {
memoryBase: 0,
tableBase: 0,
memory: new WebAssembly.Memory({
initial: 256
}),
table: new WebAssembly.Table({
initial: 2,
element: 'anyfunc'
})
};
const instance = await fetch(url).then((response) => {
return response.arrayBuffer();
}).then((bytes) => {
return WebAssembly.instantiate(bytes, {env: env})
}).then((instance) => {
return instance.instance.exports;
});
return instance;
};

const test = async () => {
const wasmUrl = 'http://localhost:3000/math.wasm';
const { add,minus,multiply } = await getExportFunction(wasmUrl);
console.log('200+100=',add(200,100));
console.log('200-100=',minus(200,100));
console.log('200*100=',multiply(200,100));
alert('打开控制台查看');
};

//测试执行
test();

打开控制台就能看到下面的内容了

1
2
3
200+100= 300
200-100= 100
200*100= 20000

2.4 其它

上述的方法是将wasm作为一个外部的静态资源文件加载;

若要降wasm作为内部文件加载,仅仅需要通过配置webpack即可;

思考,能不能载工程源码中直接编写c或c++代码呢,然后统一编译。

参考:

https://webassembly.org/getting-started/developers-guide/

https://segmentfault.com/a/1190000008402872

https://www.cnblogs.com/detectiveHLH/p/9881626.html

webgl2基础

翻译自https://webgl2fundamentals.org/webgl/lessons/webgl-fundamentals.html

这些文章是讲解WebGL2的,如果你对WebGL1.0感兴趣,点击这。WebGL2完全兼容WebGL1,也就是说一旦你开启WebGL2,它会像以往的方式来工作。

WebGL通常被认为是一种3D的apis。人们常说“我要使用WebGL来完成炫酷的3D效果”。实际上WebGL只是一个光栅化引擎。它基于你提供的代码来绘制点、线和三角面。想让WebGL来完成炫酷的任务就要看你掌控点线三角面运作的能力了。

WebGL在计算机的GPU中运行。所以你需要提供可以在GPU中运行的代码。这样的代码有两种函数形式。这两种函数被称为顶点着色器(vertex shader)片元着色器(fragment shader),它们的书写格式严格遵循一种类C/C++的名的GLSL的语言。这两个着色器成对出现,被称为一个程序(program)

顶点着色器的任务是计算顶点的位置。函数可以做基于这些位置的输出(包含点、线、三角面的基元),然后WebGL就可以对这些基元进行光栅化处理。当进行光栅化处理时,就会调用片元着色器了,片元着色器的任务是计算当前绘制的每一个基元的像素颜色值。

几乎整个WebGL的API都是用来设置这两个着色器状态的。任何需要绘制的对象都需要设置一系列的状态,然后通过函数在GPU中执行这对着色器,通过gl.drawArraysgl.drawElements函数执行。

任何这些函数可以访问的到的数据都必须提供给GPU。着色器有四种方式来接收数据:

  1. Attributes(属性),Buffers(缓冲区)和Vertex Arrays(顶点数组)

    Buffers(缓冲区)是需要上传给GPU的二进制数据的数组。通常Buffers(缓冲区)包含信息:位置、法线,纹理坐标,顶点颜色等,尽管你有权传递任意数据进去。

    Attributes(属性)用来指定如何从Buffers(缓冲区)中提取数据,并且提供给顶点着色器。例如你可以向缓冲区中添加32位浮点数的位置数据,你需要告诉一个指定的Attributes(属性)包括:哪一个缓冲区来提取数据、它要提取的数据是什么类型、在缓存区中从偏移量多少的位置开始提取数据、从一个位置到下一个位置有多少个字节。

    Buffers(缓冲区)不是随机访问的。相反,顶点着色器需要指定没执行的次数。每一次执行时,都需要从指定的Buffers(缓冲区)提取下一个值分配给一个Attributes(属性)。

    Attributes(属性)的状态,会被buffers用到的,以及如何从这些buffers中提取数据,被搜集在一个顶点数组对象中(VAO),

  2. Uniforms

    在你执行你的着色器程序之前,Uniforms是有效的全局变量。

  3. Textures(纹理)

    纹理是数据数组,你可以在你的着色器程序中随机访问。最常见的传递给纹理的对象是image数据,但是纹理只是数据,可以包含除颜色之外的其它数据。

  4. Varyings

    Varyings是顶点着色器向片元着色器传递数据的一种方式。根据要渲染的内容(点、线或三角面),当执行片元着色器时,通过顶点着色器给Varying设置的值会执行内插值计算。

WebGL Hello World

WebGL只关心两个事情:裁剪空间坐标和颜色。作为一个WebGL程序员,你的工作就是给WebGL提供这两个东西。这些工作通过提供两个着色器来完成,顶点着色器负责提供裁剪空间坐标,片元着色器负责提供颜色。

裁剪空间坐标通常介于(-1,1)的区间,不管canvas的尺寸多大。下面是一个简单的WebGL的例子,以简单的形式展示WebGL。

以顶点着色器开始

1
2
3
4
5
6
7
8
9
10
#version 300 es

//一个attribute是一个顶点着色器的输入
//它会从buffer中接收数据
in vec4 a_position;

//所有的着色器,都有一个main函数
void main(){
gl_position=a_position;
}

当执行的时候,如果整个都是使用JavaScript编写的,而不是GLSL,你需要考虑这样书写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var positionBuffer=[
0,0,0,0,
0,0.5,0,0,
0.7,0,0,0,
];

var attributes={};
var gl_Psition;

drawArrays(..., offset, count){
var stride=4;
var size=4;

for(var i=0;i<count;++i){
//从positionBuffer中复制4个值给a_position attribute
attributes.a_position=positionBuffer.slice((offset+1)*stide,size);
runVertexShader();
...
doSomethingWith_gl_Position();
}
}

实际上并没有那么简单,因为positionBuffer需要转换成二进制数据(如下所示),所以实际从buffer中获取数据的计算会有一些不同,但是希望通过这样能让你了解到顶点着色器是如何执行的。

下面需要一个片元着色器

1
2
3
4
5
6
7
8
9
10
#version 300es
//片元着色器没有默认的精度,所以我们需要指定一个。medium是很好的默认值
precision mediump float;

//需要给片元着色器声明一个输出
out.vec4 outColor;
void main(){
//将输出设置成紫色
outColor=vec4(1,0,0.5,1);
}

上边声明outColor作为片元着色器的输出,将outColor设置为1,0,0.5,1,1表示红色,0表示绿色,0.5表示蓝色,1表示透明度。在WebGL中颜色介于(0,1)之间。

现在已经写好了两个着色器函数,接下来可以使用WebGL执行了

首先需要HTML的canvas元素

1
<canvas id="c"></canvas>

在JavaScript中如下面

1
var canvas=document.getElementById("c");

现在可以创建一个WebGL2的渲染背景了

1
2
3
4
5
var gl=canvas.getContext("webgl2");
if(!gl){
//
...
}

现在需要编译这些着色器了,将它们提供给GPU。所以首先我们需要将它们转为字符串。可以以任何正常的方式创建GLSL字符串。可以通过拼接字符串,可以使用、AJAX下载它们,通过将它们放在非JavaScript标签中,或者以如下多行模板的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var vertexShaderSource='#version 300 es
in vec4 a_position;

void main(){
gl_Position=a_position;
}
';

var fragmentShaderSource='#version 300 es
precision mediump float;
out vec4 outColor;
void main(){
outColor=vec4(1,0,0.5,1);
}
';

实际上,大多数3D引擎使用各种各样的模板快速生成GLSL着色器。本网站上的例子都没有复杂到在运行时生生GLSL。

注意:#verison 300 es必须是着色器的首行。它之前不允许有任何命令或空格。#verison 300 es告诉WebGL2你要使用WebGL2的名为GLSL ES3.00的着色器语言。如果你不在首行放置它,着色器语言会默认使用WebGL 1.0的GLSL ES 1.00语言,它们之间有很大的不同,缺少很多特性。

下面需要一个函数来创建一个着色器,上传GLSL源码,并且编译着色器。注意饿哦并没有编写任何评论因为函数的命名清晰的展示了发生了什么

1
2
3
4
5
6
7
8
9
10
11
function createShader(gl, type, source){
var shader=gl.createShader(type);
gl.shaderSource(shader,source);
gl.compileShader(shader);
var success=gl.getShaderParameter(shader,gl.COMPILE_STATUS);
if(success){
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}

现在可以调用函数创建第二个着色器了

1
2
var vertexShader = createShader(gl,gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader=createShader(gl,gl.FRAGMENT_SHADER,fragmentShaderSource);

然后将这两个着色器连接到程序(program)

1
2
3
4
5
6
7
8
9
10
11
12
function createProgram(gl,vertexShader,fragmentShader){
var program=gl.createProgram();
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
gl.linkProgram(program);
var success=gl.getProgramParameter(program,gl.LINK_STATUS);
if(success){
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}

并且调用它

1
var program=createProgram(gl,vertexShader,fragmentShader);

现在我们在GPU中创建一个GLSL程序,并且需要给它提供数据。大部分的WebGL API都是涉及到给供给给GLSL程序提供的数据设置状态。在这个例子中给GLSL 程序提供的输入只有a_position,它是一个attribute。首先要做的是给我们创建的程序找到attribute的位置

1
vav positionAttributeLocation=gl.getAttributeLocation(program,"a_position");

找到attribute的位置(唯一的位置)是在初始化阶段要完成的事情,而不是在渲染阶段。

Attribute需要从buffers中获取数据,所以需要创建一个buffer

1
var positionBuffer=gl.createBuffer();

WebGL允许我们操纵许多全局绑定点中的WebGL资源。可以将绑定点看做是WebGL内部的去哪局变量。首先给绑定点绑定资源,然后所有的其它函数通过绑定点来绑定这些资源。所以我们绑定位置buffer

1
gl.bindBuffer(gl.ARRAY_BUFFER,positionBuffer);

现在可以通过绑定点将数据提供给buffer了

1
2
3
4
5
6
var positions=[
0,0,
0,0.5,
0.7,0,
];
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(positions),gl.STATIC_DRAW);

在这里做了很多事情,首先我们有一个positions的JavaScript数组。WebGL需要强类型的数据,所以通过new Float32Array(positions);创建一个新的32位浮点型点数据数组,并且从positions拷贝数据。通过gl.bufferData将数据拷贝到GPU的positionBuffer中。它使用了位置buffer,因为我们通过绑定点将它绑定到了ARRAY_BUFFER

最后一个参数gl.STATIC_DRAW是对WebGL的一个提示,提示如何使用数据。WebGL可以通过有这些提示来优化一些事情。gl.STATIC_DRAW告诉WebGL我们不太可能会改变这些数据。

现在,我们已经将需要告诉attribute如何和提取数据的数据放进了buffer中。我们需要通过调用一个顶点数组来创建一个attribute状态的集合。

1
var vao=gl.createVertexArray();

并且需要让它成为当前的顶点数组,所以所有的attribute设置会应用在那个attribute状态集上。

1
gl.bindVertexArray(vao);

接下来在顶点数组中设置attribute。首先我们需要开启attribute,这告诉WebGL我们想要从buffer中提取数据。如果我们没有开启attribute那么attribute就会是一个常量值。

1
gl.enableVertexAttribArray(positionAttributeLocation);

然后我们需要指定如何将数据提取出来

1
2
3
4
5
6
var size=4;
var type=gl.FLOAT;
var normalize=false;
var stride=0;
var offset=0;
gl.vertexAttribPointer(positionAttributeLocation,size,type,normalize,stride,offset);

gl.vertexAttribPointer的作用是将当前的ARRAY_BUFFER绑定给attribute。也就是说现在attribute绑定给了positionBuffer。这意味着我们可以绑定其它东西给ARRAY_BUFFER绑定点。attribute会集训使用positionBuffer

注意从我们的GLSL 顶点着色器视图的点,a_positionattrubute是一个vec4

1
in vec4 a_position;

vec4是一个4个浮点值。在JavaScript中你可以把它看做是a_position={x:0,y:0,z:0,w:0}。在设置size=2之前,attribute默认是0,0,0,1所以这个attribute就会从我们的buffer中获取前两个值(x和y)。z和w分别是是默认的0和1。

在绘制之前,需要重置canvas的尺寸来匹配展示尺寸。canvas们就像图像一样拥有两个尺寸。像素的数量通常位于它的内部,和它的展示尺寸是相分离的。CSS决定了canvas的展示尺寸。你应该优先选择使用css改变canvas的尺寸,因为这是最便利的方法。

为了让canvas的内部像素数量和展示尺寸相匹配。我使用了一个辅助函数

几乎所有的例子的canvas的尺寸都是400*300像素,它会在自己的窗口上运行。如果位于iframe内部,那么它就会拉伸填充整个可用区域,就像位于这个页面之上一样。通过让CSS控制尺寸,然后调整匹配,可以轻松的处理这两种情况。

1
webglUtils.resizeCanvasToDisplaySize(gl.canvas);

我们需要告诉WebGL如何进行剪裁空间值的转换,需要设置gl_Position返还给像素,通常被称为屏幕空间。为了做到它,可以调用gl.viewport并且给它传递canvas的当前尺寸。

1
gl.viewport(0,0,gl.canvas.width,gl.canvas.height);

它告诉WebGL将(-1,1)的剪裁空间映射到(0,gl.canvas.width)给x轴,(0,gl.canvas.height)给y轴

我们清空画布。0,0,0,0分别代表红、绿、蓝、透明度。所以在这个例子中,我们让画布透明

1
2
gl.clearColor(0,0,0,0,);
gl.clear(gl.COLOR_BUFFER_BIT);

接下来我们需要告诉WebGL执行哪一个着色器程序

1
gl.useProgram(program);

然后我们需要告诉它需要用到哪一个buffers集,并且如何从这些buffers中提取数据给attributes

1
gl.bindVertexArray(vao);

最后需要请求WebGL来执行GLSL程序

1
2
3
4
var primitiveType=gl.TRIANGLES;
var offset=0;
var count=3;
gl.drawArrays(primitiveType,offset,count);

因为count是3,所以会执行顶点着色器3次。第一次中顶点着色器attribute的a_position.xa_position.y会被设置成positionBuffer的前两个值;第二次中a_position.xy会别设置成随后的两个值;最后一次中会别设置成最后两个值。

因为我们将primitiveType设置为了gl.TRIANGLES,每次我们的顶点着色器都执行三次,WebGL会基于我们设置给gl.Positionde 三个值来绘制三角形。无论canvas的尺寸是多少,在剪裁空间中的这些值在每个方向上都是(-1,1)的区间。

由于顶点着色器只是简单的将positionBuffer中的值拷贝到gl_Position,三角形会在剪裁空间坐标中绘制。

1
2
3
0,0,
0,0.5,
0.7,0

如果canvas的尺寸正好是400*300,从剪裁空间向屏幕坐标的转化会如下:

clip space screen space
0,0 —> 200,150
0,0.5 —> 200,225
0.7,0 —> 340,150

WebGL现在就回渲染这个三角形面了。对于每一个要绘制的像素,WebGL都会调用片元着色器。我们的片元着色器仅仅设置了outColor1,0,0.5,1。由于Canvas在每个通道都是8位的,意味着WebGL可以将[255,0,127,255]写进canvas、

low-graphics mode解决方法

虚拟机运行ubuntu系统时,有时候会出现The system is running in low-graphics mode而进不了系统。怎么办呢?
1、命令行
如果你要做的工作不用图形化界面,可以Ctrl + Alt + F1 || F2 || F3 || F4 || F5 || F6 使用命令行登录。
2、其它
但是命令行内不可以运行可视化的软件,想要恢复图形化界面怎么办呢?
上网上收了一下,有很多解决方法,好多都需要敲命令。其实不需要,出现问题的原因是由于显卡的不匹配,只需要把虚拟机的显卡虚拟化一下即可,在虚拟机设置-硬件-处理器-虚拟化引擎中勾选虚拟化Intel VT-x/EPT 或 AMD-V/RVI(V)即可。

Ubuntu固定ip和软件愿

一、固定ip

1.1 设置静态ip

compute-allsetting-network中选择wired在右下角options中选择IPv4 Setting

  • Method中选择Manual;
  • Address中点击右侧add按钮,在里面添加Address、Netmask、Geteway;
  • DNS servers中添加相应的地址。
    其中dns servers的地址可以在任意一台电脑上cmd中输入ipconfig -all的本地连接中查看,可以有多个。
    可以参考:ubuntu静态IP教程

1.2 关闭防火墙

如果是虚拟机的话,首先将网络连接方式设为桥接模式(B):直接连接物理网络,并且勾选复制物理网络连接状态(p)
接下来就需要命令行了,首先进入管理员模式,

  1. 关闭防火墙:
    1
    2
    > ufw disable
    >
  1. 开启防火墙
    1
    2
    > ufw enable
    >

一般这样就可以了,可以ping一下其它地址试一试,不行的话就重启一下系统。

二、更换软件源

软件源文件位于/etc/apt/sources.list,
2.1 最好先做一下备份

1
2
3
cd /etc/apt

cp sources.list sources.list.bak

2.1 然后编辑sources.list

1
vi sources.list sources.list

2.3 然后替换里面的地址,可以使用批量替换的命令
:%s#旧字符#新字符#g
其中g表示全部替换.
2.4 最后更新一下软件源:

1
2
3
sudo apt-get update

sudo apt-get upgrade

浏览器跨域

常见的跨域问题


今天在测试提出以一个问题,具体情形不说了,就是报错出现了这么个问题:

1
Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.

一眼看出就是跨域问题,说也奇怪,每次发送请求都是直接报错,但是请求还是可以正常发送成功,就是不能执行正确的回调函数了,如下:

1
2
3
4
5
6
7
8

var successHandler=function(res){
callback(res);
};
var errorHandler = function(errorInfo) {
alert(errorInfo);
};
HGIS.Utils.send(routeNetAnlyseURL.shortestPathPoints,param,"POST","json",successHandler,errorHandler);

具体情形就是:每次发送请求HGIS.Utils.send的请求,它就立即直接回调errorHandler返回了,但是通过监听Network中该请求,当处理errorHandler时,该网络请求为阻塞状态,当点击alert中的确定按钮后console中提示上面的跨域错误信息,但是网络请求居然可以请求成功,并且返回正确的结果。不过这个正确的结果没有走我的successHandler函数。

出现跨域问题可以理解,毕竟我的html是离线文件。疑惑的是之前测试的时候就没有问题,可以正常访问,为啥现在出现这个问题了呢,说是跨域,可明明可以访问的到,为啥就不按我设定的流程走呢?

寻找陈老师帮忙,首先确定这个跨域是浏览器拦截的,相当与在网络请求没有发出时就没浏览器给提前处理了,直接进入错误处理流程。

如何让浏览器可以进行跨域的请求的,通过百度https://www.cnblogs.com/micua/p/chrome-file-protocol-support-ajax.html找到一个方法。

  • 在桌面建立一个chrome的快捷方式
  • 右键该快捷方式,点击属性
  • 属性-快捷方式-目标框中后天添加--allow-file-access-from-files,注意该字符串之前要有一个空格键,最终的结果可能是"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files
  • 然后关闭所有的chrome浏览器,再双击该快捷方式打开浏览器
  • 最后将离线html文件拖入浏览器即可

问题解决了,可以为啥会出现这么问题呢,为啥上个月的时候不用有这些设置就可以正常访问呢?

原因是浏览器版本的缘故,原来由于某些原因,我本地的chrome被更新到最新的版本了

1
2
3
Google Chrome 已是最新版本

版本 65.0.3325.181(正式版本) (64 位)

,而chrome最新版本增强了对网络请求的安全限制

最后查看chrome的安装位置,发现确实是在一周前的时候我的浏览器被更新了

git rebase

git rebase命令

一、rebase的作用

先看例子,假设我们的操作如下:

1
2
3
4
5
6
7
8
1、master分支已有文件master.txt;
2、从master拉取分支B,并创建文件b1.txt,add-commit;
3、从master拉取分支C,并创建文件c1.txt,add-commit;
4、切换分支B,并创建文件b2.txt,add-commit;
5、切换分支C,并创建文件c2.txt,add-commit;

6、在分支C,合并master分支,然后将结果合并到master;
7、在分支B,合并master分支,然后将结果合并到master;

此时结果怎么样呢?会发现分支B和分支C的提交,相互交叉,按照DATE排序的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
commit 17efaf6eeb99ecf9e6af5e78781279ae2e88af86 (HEAD -> master, B)
Merge: e791fb5 4d25aef
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 12:06:34 2019 +0800

Merge branch 'master' into B

commit 4d25aef5fdadd54bf68ab6a6cd0f83ade7a46fe7 (C)
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:56:24 2019 +0800

[add]C添加文档2

commit e791fb50580a2eaecac426df07bec10fb3d59ddc
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:55:43 2019 +0800

[add]B添加文件2

commit 1567f12c0739fa898a89348422038507ab2cfd01
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:54:44 2019 +0800

[add]C添加文件1

commit cecc88eca0f9b8d37d2f92fd99250cbb5b0da7e7
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:53:38 2019 +0800

[add]B添加文件1

commit 8207c1f12cc1430208e2273d9429c04675afddbc
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:52:44 2019 +0800

[add]master添加文件1

如果我们不想让B和C的commit交叉,该怎么做呢,就可以使用rebase命令了,只需要修改第6、7步即可

7、在分支B,合并master分支,然后将结果合并到master;

1
7、在分支B, rebase master分支,然后将结果合并到master;

此时我们再看一下log,就会返现日志是将C的commit完成之后,然后再有commitB中的提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
commit 552d2ba40803fc9ab013b6571efc2cda58f71236 (HEAD -> master, B)
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:55:43 2019 +0800

[add]B添加文件2

commit 07686a4be8a2ba5fb94810f1f23fdf96478a4193
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:53:38 2019 +0800

[add]B添加文件1

commit 4d25aef5fdadd54bf68ab6a6cd0f83ade7a46fe7 (C)
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:56:24 2019 +0800

[add]C添加文档2

commit 1567f12c0739fa898a89348422038507ab2cfd01
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:54:44 2019 +0800

[add]C添加文件1

commit 8207c1f12cc1430208e2273d9429c04675afddbc
Author: yahui.yang <yahui.yang@ecarx.com.cn>
Date: Thu Oct 10 11:52:44 2019 +0800

[add]master添加文件1

什么原因呢?rebase命令会把分支B中的commit取消掉,将它们保存为补丁(patch),然后将分支B更新为master中最新的内容,最后将补丁应用到分支B中。

二、冲突的解决

在merge过程中如果出现的冲突,一般需要add并且commit进行处理。

但是在rebase过程中若出现冲突,解决完冲突后并不需要add和commit,只需要执行

git rebase --continue即可,

三、rebase的取消

在任何时候都可以使用命令git rebase --abort来终止rebase的动作,并且分支会回到rebase之前的状态

参考:http://gitbook.liuhui998.com/4_2.html

sublime的使用

sublime插件的安装使用

参考

1.直接安装

安装Sublime text 3插件很方便,可以直接下载安装包解压缩到Packages目录(菜单->preferences->Browse Packages)。

2.使用Package Control组件安装

也可以安装package control组件,然后直接在线安装:

按Ctrl+ `(此符号为tab按键上面的按键) 调出console(注:避免热键冲突)
粘贴以下代码到命令行并回车:

1
import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read())

按回车键,会看到下面出现东西在左右摆动,说明正在下载。

3. 下载完成之后重启Sublime Text 3。
4. 如果在Perferences->中看到package control这一项,则安装成功。
5.用Package Control安装插件的方法:
  • 按下Ctrl+Shift+P调出命令面板
  • 输入install 调出 Install Package 选项并回车,
  • 然后在列表中选中要安装的插件。

其它:

JSFormat JS代码格式化插件。

使用方法:使用快捷键ctrl+alt+f