soulcm / blog Goto Github PK
View Code? Open in Web Editor NEW2017开通
2017开通
1月9号,微信小程序如期发布,各个团队也陆续推出了小程序。前面得肺炎住院今天也出院了,想着自己最近的学习状态,使得自己心里的那一丝丝担忧和迷茫更加的加深了。
作为一个差不多两年经验的前端,从开始的啥都不懂,慢慢成长到现在能够去有意识的阅读源码,理解各个框架的**,并能独立承担起一些开发和框架的搭建。
但最近每天在一如既往的学习的同时,心里突然感觉到虽然在学习,但跟社区和业界大牛差距还是越来越远,感觉自己一直是在追着前端的脚步,并且有点越来越难追上。
前端这一两年发展真的很快,node(express)、react、vue、rxjs、无线端、weex、koa、gulp、webpack、函数式编程、服务端渲染等等一个一个接踵而来,虽然自己也一直拥抱着前端的变化,会去了解和使用它们,但如今真的感觉有点踹不过气(加上生活的压力),越往里钻,研究的越深越感觉到差距。
公司团队本身确实也有很大一部分影响,一个好的团队,一个拥抱着技术的团队,一个好的项目,一个好的环境,肯定对一个人的成长是非常有益的,但现如今的公司团队感觉只是为了解决现有业务,不断的叠加,技术上也不愿尝新和过多的研究,团队学习气氛也不是很浓厚,只能自己平常花时间去学习,但这样的效率肯定与一个好的团队在一起讨论学习差了很多。
后面自己是该调整一下心态了,春节后也许出去放松下,换个环境,然后重新投入到一个满意的学习环境中去,才能在前端上面更上一层楼吧。
如今数据驱动视图的框架已经成为前端开发的主流,如react、vue、angular等,当然还包括我们用的regular。这些框架大大提高了我们的开发效率,但同时现在的页面结构越来越复杂,虽然有这些mv*的框架,但如果我们使用不当,也会造成状态数据管理混乱,代码难以维护的困扰。
因此组件化的**就是为了提高开发效率和后期维护的效率。
一个组件只做一件事,基于功能做好职责划分。
对组件的封装都是为了对数据逻辑业务的梳理,使得不同组件各司其职,即把大块的业务界面,拆分成若干小块,然后进行组装。当然这里不局限与js,其实css同样适用(如nec规范)。
数据驱动视图,顾名思义视图的状态肯定是根据数据进行变化的,因此数据的状态管理就变得尤为重要。
无状态组件为只接受props,根据props的不同展示出不同的样式,并且会抛出事件来通知外部组件需要的更改(ps. 按照react以及vue的单向绑定原则,props传入是不能被更改的,这和regular的双向绑定不同)
单向绑定能更好的帮助我们控制住数据的状态,特别对于越来越复杂的前端
这部分就是我们视图上看到的各个UI单元,如输入框、tab框、表格、下拉框等等,其中有一些其实可以是我们上述所说的无状态组件,我们常见的UI库如ant-design(react)、eleme-element(vue)以及nek、kmui等。
特点:复用性强,根据接受参数显示不同的视图,以及开放一些接口与外部通信。就如同一个对html标签的扩展
就是按照一个页面的业务逻辑进行划分的单元,如优惠券、商品1x2、商品1x3、倒计时等等,它们中有一些有一定的复用性,但大部分可能只会在特定的业务中使用。它们里面是由一个个UI组件组成。
一个包裹业务模块的盒子,一般来说一个业务模块的入口。它接收着业务组件所需要的所有数据,然后根据每个业务组件的需要来进行分发数据,使对应的数据进入到对应的业务组件中。如下是一个使用了redux和react的container组件
import {connect} from 'react-redux';
import Topic from '../components/topic';
const mapStateToProps = (state) => {
return {
info: state.topicInfo
}
}
const mapDispatchToProps = (dispatch) => {
}
export default connect(mapStateToProps, mapDispatchToProps)(Topic)
上面的container就是将topic的业务组件进行了一个包裹并将数据通过props传入给了topic。
这篇将是redux源码解析的终篇,主要讲applyMiddleware的原理以及中间件的实现
最近太忙了,先占个坑
@todo
上篇介绍了createStore,了解了redux的工作流程,此篇将介绍一个辅助函数,其实就算不使用bindActionCreators也照样可以使用redux,每次直接通过store.dispatch(action)的调用来改变状态就行,那这个函数还有什么用呢。
惟一使用 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它。
看它的源码
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
其接收一个function或一个通过import * as actions from
获取到的action对象,第二个参数是dispatch
当是一个对象时,会遍历每个属性,然后将其对应的actionCreator通过一个高阶函数包裹一层dispatch,这样直接调用时就可以察觉不到dispatch的存在。函数最终返回一个新的对象,各属性对应的值是已经包裹过dispatch的函数。
冒泡排序
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i< len; i ++) {
for (var j = 0; j < len - 1 - i; j ++) {
if (arr[j] > arr[j+1]) {
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
选择排序
function selectSort(arr) {
var min;
for (var i = 0; i< arr.length - 1; i++) {
min = i;
for (j = i + 1; j < arr.length; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if (min !== i) {
var temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
return arr
}
插入排序
function insertSort(arr) {
var key;
for (var i = 0; i< arr.length; i++) {
var j = i;
key = arr[j];
while(--j > -1) {
if (arr[j] > key) {
arr[j + 1] = arr[j];
} else {
break;
}
}
arr[j + 1] = key;
}
return arr
}
快速排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] > pivot) {
right.push(arr[i])
} else {
left.push(arr[i])
}
}
return quickSort(left).concat([pivot], quickSort(right))
}
弹性布局经过了几个世纪的演变,相信对于大部分人来说应该都不算陌生了,最起码或多或少有了解过。
对于没有完全了解清楚却使用过此属性的人,应该是对它又爱又恨吧。
爱
上上下下左左右右各种居中
均分一块容器,避免了通过float+百分比去计算
各种对齐
恨
对某个容器不想均分时,经常会遇见某块的宽度设置无效,然后css调来调去,突然又有效了,只是自己也不知道调了哪个熟悉产生的,只能先将就着用了
文字溢出显示省略号,和上面类似,感觉就像是有时有效果,有时没效果
兼容性,要写好多前缀,不过聪明的你肯定会有各种办法避开手写
本篇文章主要就针对大家对它的恨来分解flex的三个基本属性,在看完后,希望大家对它的恨意能有所减少。关于flex的基础内容,推荐大家查看阮老师的博客Flex 布局教程:语法篇
还是先来回顾下三个基本的属性
flex-grow: 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
flex-shirnk: 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
flex-basis: 定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小
上面3个属性看似好像挺通俗易懂,但实际操作中,若不理解清楚的话,就可能会遇到一些不可控的情况,下面就对其进行详细介绍。
直接通过一个例子进行讲解了,看下面的代码
<style>
.wrap {
width: 600px;
display: flex;
}
.item {
height: 50px;
line-height: 50px;
text-align: center;
color: #fff;
}
.item1 {
width: 200px;
flex: 2 1 0%;
background: #f00;
}
.item2 {
width: 150px;
flex: 2 1 auto;
background: #0f0;
}
.item3 {
flex: 1 1 200px;
background: #00f;
}
</style>
<div class="wrap">
<div class="item item1">item1</div>
<div class="item item2">item2</div>
<div class="item item3">item3</div>
</div>
具体宽度数值请戳我查看
通过例子发现,实际宽度都不是我们上述设置的宽度或者基准宽度,我们来看看它的计算方式。
主轴总长度为600,子项基准宽度加起来为 0 + 150 + 200 = 350(PS.当flex-basis设置为0%的时候,其设置的宽度将不起任何作用,如item1中的wdith)
因此还剩余宽度为 600 - 350 = 250
flex-grow系数为 2 + 2 + 1 = 5
每一项将分配的剩余宽度计算方式为
w = (flex-grow /所有flex-grow的总和) * 剩余值
因此实际长度为 item1 = 0 + 250*2/5 = 100 或 content的尺寸
item2 = 150 + 250*2/5 = 250
item3 = 200 + 250*1/5 = 250
极端情况,若将wrap宽度改为400,虽然还是会有剩余宽度 400 - 350 = 50,但通过计算后
item1 = 0 + 50*2/5 = 20,已经小余其content的尺寸了,因此item1就等于其content尺寸38.77,此时其他项的剩余宽度将减少 38.77 - 20 = 18.77,各子项尺寸将按flex-grow的比例去分掉这18.77
item2 = 150 + 50*2/5 - 18.77/3*2 = 157.48
item3 = 200 + 50*2/5 - 18.77/3*1 = 203.74
至于为什么是felx-grow大一些,最后要减掉的多一些,我就不得而知了
当子项基准宽度加起来大于主轴总长度时,子项将会被压缩,请自行修改上述例子wrap的宽度为300查看效果
因为item1基准值为0,其尺寸将直接等于其内容content的尺寸,通过例子可以看到item宽度为38.77px
因此子元素基准值加起来溢出值为 350-(300-38.77) = 88.77
加权值计算公式为 所有子项flex-basis*flex-shrink的总和
s = 0*1 + 150*1 + 200*1 = 350
每项压缩的长度 w = (flex-basis*flex-shrink / s) * 溢出的值
w-item1 = 0*1/350*88.77 = 0;
w-item2 = 150*1/350*88.77 = 38.044
w-item3 = 200*1/350*88.77 = 28.57
最终
item1 = content - 0 = content
item2 = 150 - 38.044 = 111.95
item3 = 200 - 28.57 = 149.27
上述例子展示了在计算时的两种情况,可以发现flex-basis基准值起了很重要的作用。
总结:
PS.以上的计算弹性尺寸的方法都是自己查阅资料和摸索出来的,若有不对的地方欢迎指出
众所周知,优化一个页面的方法之一就是利用浏览器的缓存机制,在文件名不改变的情况下,使客户端不用频繁向服务端请求和重复下载相同的资源,节约了流量的开销。
现在前端的主要构建工具就是使用webpack,现在就说说使用webpack时遇到的hash的坑。
在生产环境发布时,为了利用缓存,都会加一个标识,webpack打包时也可加入这个功能,对于一个基本的应用,最开始的用法估计这样,见如下代码
module.exports = {
entry: {
index: path.resolve(__dirname, '../src/js/index.js'),
index2: path.resolve(__dirname, '../src/js/index2.js'),
vendor: ['react', 'react-dom'] //第三方模块
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].[hash:8].js',
publicPath: '/'
},
module: {
rules: [{
test: /\.jsx?$/,
use: ['babel-loader'],
exclude: /node_modules/
}
//省略其他loader
]
},
plugins: [
new CommonsChunkPlugin({
name: 'vendor'
})
//省略其他plugin
]
}
这里的hash是每次编译时所计算出来的一个值,因此当任何一个文件修改,hash都将会改变,而且所打包出来的文件名也将不一样了,这样对于需频繁发布的项目不是很友好,会造成每次有新版本,用户浏览器都将重新下载所有的文件,为了避免这种情况,webpack还提供了chunkhash(注:这里提取出的css文件hash值不一样,是因为使用的是extract-text-webpack-plugin插件,它提供了自己的一个contenhash,也是对于css文件建议的一种用法,保证了css有自己独立的hash,不会受到js文件的干扰)
将上述代码中的hash换成chunkhash后打包结果如下:
看到每个chunk有自己单独的hash值,此时只修改某一个模块里的文件,将不会影响到其他的模块打包出的hash值了,这样也就能充分利用hash缓存了。
你以为到此处就结束了吗,too young too simple!
你可以试试改动一个文件,打包后vendor的hash值每次都是在变化的,第三方模块是最不长改动的模块,更应该被缓存住,可为什么vendor的chunkhash总是变化,是因为webpack runtime由于entry对应的Id变化而发生了变化,chunkhash的计算又依赖于runtime,因此vendor的chunkhash也发生了变化。
为了解决这个问题,我们首先得保证chunkId的稳定,参考webpack2的文档caching,可以使用HashedModuleIdsPlugin的插件(webpack1也可以使用,但需要将HashedModuleIdsPlugin.js自行引入),然后就是创建一个额外的chunk来提取runtime,就是文档中说的manifest,部分代码如下
plugins: [
new CommonsChunkPlugin({
names: ['vendor', 'manifest'],
minChunks: Infinity
}),
new webapck.HashedModuleIdsPlugin()
]
打包后将多出一个很小的manifest.js的文件,但保证了vendor的hash没有改变,对于manifest的内容我们需要优先引入,在此可以借助inline-manifest-webpack-plugin将manifest内容内联进html文件中,以免多发一次js的请求,使用方式可直接参考文档。
至此对于hash的变化才算真正结束,才能达到利用hash的变化真正的控制住缓存。
PS. 建议不要用webpack-md5-hash,会有坑的,见webpack-md5-hash-issue
此处所说的组件,是指modal、tooltip、toast、message等等这类公共组件
因为最近在对项目进行一些优化,tooltip组件需要进行重构,因此在写的过错中发现前面的思路感觉根本不对了,在此记录一下
以前的tooltip长这样
class Tooltip extends React.component {
//省略部分代码,直接看render
render() {
const {placement} = this.props;
return(
<div className="tooltip tooltip-right">
{this.props.children}
</div>
)
}
}
//调用的地方
import Tooltip from 'somepath/Tooltip';
class BusinessComp extends React.component {
constructor(props) {
super(props);
this.state = {
showTip: false
}
this.handleShowTip = this.handleShowTip.bind(this);
}
handleShowTip() {
//计算tip位置的代码,并放入tipData中
this.setState({
showTip: !showTip
tipData: tipData
})
}
tipRender() {
if (this.state.showTip) {
return (
<Tooltip style={style}>我是tip</Tooltip>
)
}
}
render() {
return (
<div>
<span onClick={this.handleShowTip}></span>
{this.tipRender()}
</div>
)
}
}
这样造成每次页面都要去计算tip位置,并通过props传入,而且render出来的tip也是属于当前页面元素内部,有可能嵌套过多时样式会出现问题,后来通过观察ant-design的组件样式以及源码发现,一般都是把这类组件渲染到body里面,因此开始了重构之路。
首先不应该每次都要手动计算位置然后传入,应该是组件自己操作,而且应该想办法渲染到body上面,因此改造成下面这样
//引入方式改变
import Tooltip from 'somepath/Tooltip';
class BusinessComp extends React.component {
render() {
return (
<div>
<Tooltip content="我是tip" placement="left"><span>点击我弹出tip</span></Tooltip>
</div>
)
}
}
//Tooltip
class Tooltip extends React.component {
constructor(props) {
super(props);
this.state = {
visible: !!props.visible
}
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
this.getContainer();//获取到需要插入进body的元素
}
getContainer() {
this.getInstancePosition(); //计算tip的位置
this.container = document.createElement('div');
const parent = this.props.parentSelector;
parent.appendChild(this.container); //插入container
this.renderTip(this.props); //渲染tip
}
renderTip(props) {
const {placement, title, content, style} = props;
ReactDOM.unstable_renderSubtreeIntoContainer(this,
<div className={`tooltip tooltip-${placement}`}
style={assign({width: 300, opacity: 0, display: 'none'}, style)}
ref={(c) => this.tipDom = c}>
<div className="title">{title}</div>
<div dangerouslySetInnerHTML={{__html: content}}></div>
</div>, this.container
)
}
handleClick() {
this.setState({
visible: !this.state.visible
})
}
render() {
const {children, placement, style} = this.props;
const child = React.isValidElement(children) ? children : <span>{children}</span>; //判断是否为react element
const newChildProps = {}; //绑定方法或添加其他属性, 如onClick/onMouseEnter等等
newChildProps.onClick = this.handleClick;
return React.cloneElement(child, newChildProps) //render中只需要渲染child
}
}
上述只是部分代码和实现思路
写这个时候,开始是直接创建的div,但发现那样传进来的props属性没法加到弹出的div上面去,后来一直翻阅蚂蚁的源码,会发现都是在react-componet各组件上面进行的封装,而react-component里面用到了ReactDOM.unstable_renderSubtreeIntoContainer这个方法,但react官网API里面并没有把这个给列出来,估计是一个不太正式的API,但还是希望react把这个API放出来,因为很多情况下确实需要用到。
PS. 听说facebook正在研究react专门针对dom的处理方式,还是期待下的
总结:
toast.info('我是toast')
通过前面的学习,我们知道,redux的核心是只有一个store,对于不同业务,不能够拆分store,而是拆分reducer,拆分后的每一块独立负责管理 state 的一部分。
combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore
看它的源码
export default function combineReducers(reducers) {
}
接收的reducers是一个对象,其key为控制state的key的命名,值为各个reducer函数。如调用combineReducers({ counter, todos })
,state结果也就为{todos, counter}
刚开始会遍历这个对象,并清理掉值不是function的属性
var reducerKeys = Object.keys(reducers)
var finalReducers = {}
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
接着进行一系列校验之后,最终返回一个根级的reducer函数,这个函数其实就和自己写子级的reducer函数的结构是一样的。
return function combination(state = {}, action) {
var hasChanged = false
var nextState = {}
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
遍历最终的reducer对象,因为state的结构和reducers对象的结构是一样的,因此可以得到每个state的值var previousStateForKey = state[key]
然后调用它的reducer函数,将会返回一个新的state,本身约定缺省值是返回的初始的state,不能返回undefined。
nextState和previousState比较,判断state是否改变,若改变了,即返回全新的state,没改变就返回老的state。return hasChanged ? nextState : state
这样就通过拆分reducer,将各个业务模块给分离开来,方便管理了。
先看一段代码,请使用chrome验证你所得出的答案
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
要答对上面的问题,就需要理解js本身的event loop。我们都知道js是单线程的,同一时间只能执行一个任务,那怎么才能做到异步的感觉呢。这就需要引入几个概念了,
task、microtask、macrotask。
所有同步任务都将在主线程上执行,形成一个task队列和microtask的队列,然后先将task按顺序压入执行栈执行,task队列清空后就将microtask压入执行栈执行,在一次event loop中都清空后,就会进行一次视图的渲染,然后执行macrotask。
microtask紧跟着task,一旦没有task压入执行栈,microtask就会被压入而执行。常见的microtask有process.nextTick、Promise、MutationObserver等。
那macrotask又是什么呢,它其实就是setTimeout以及node中的setImmediat的回调,它们都是创建一个异步任务,会在event loop的末尾才执行,它其实也属于一个task。至于它与microtask的区别就是microtask会影响IO回调,microtask不执行完的话,界面会一直卡住,macrotask就不会有这个问题。
回到最开始,我们看下那段代码的执行过程
代码执行,同步代码全部放入在task或microtask中
tasks: [concole.log, setTimeout cb, concole.log]
stack: [console.log]
microtask: [promise then]
task的代码依次执行,先打印出script start
,然后在打印出script end
,此时剩余一个macrotask和microtask
tasks: [setTimeout cb]
stack: [promise then]
microtask: [promise then]
然后promise执行,打印promise 1
,然后接着另一个promise打印promise 2
tasks: [setTimeout cb]
stack: [setTimeout cb]
microtask: []
打印setTimeout
,至此队列全部清空,一个event loop完成。
tasks: []
stack: []
microtask: []
接着上面的知识,我们可以来看vue的nextTick方法了。在vue中,数据监测都是通过Object.defineProperty来重写里面的set和get方法实现的,vue更新DOM是异步的,每当观察到数据变化时,vue就开始一个队列,将同一事件循环内所有的数据变化缓存起来,等到下一次event loop,将会把队列清空,进行dom更新,内部使用的microtask MutationObserver来实现的。
虽然数据驱动建议避免直接操作dom,但有时也不得不需要这样的操作,这时就该Vue.nextTick(callback)
出场了,它接受一个回调函数,在dom更新完成后,这个回调函数就会被调用。不管是vue.nextTick还是vue.prototype.$nextTick都是直接用的nextTick这个闭包函数
export const nextTick = (function () {
const callbacks = []
let pending = false
let timerFunc
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
//other code
})()
callbacks就是缓存的所有回调函数,nextTickHandler就是实际调用回调函数的地方。让这个函数延迟执行,vue优先用promise来实现,其次是html5的MutationObserver,然后是setTimeout。前两者属于microtask,后一个属于macrotask。都是达到一个异步的过程。
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
timeFunc = () => {
setTimeout(nextTickHandle, 0)
}
}
来看最后一部分
return function queueNextTick(cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) cb.call(ctx)
if (_resolve) _resolve(ctx)
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
这就是我们正在调用的nextTick函数,在一个event loop内它会将调用nextTick的cb回调函数都放入callbacks中,pending用于判断是否有队列正在执行回调,例如有可能在nextTick中还有一个nextTick,此时就应该属于下一个循环了。最后几行代码是promise化,可以将nextTick按照promise方式去书写(暂且用的较少)。
在与服务端进行异步请求时,你是否还在写着地狱般的回调,是否还在写着繁琐的配置,是否还觉得调用非常的混乱,这就是我们一直使用了很长时间的XMLHttpRequest来实现的ajax技术,尽管各种框架的封装就是为了解决上述的问题,但有时需要阅读到深层的源码时,又将会是另外一番痛苦,同时ajax技术也不符合关注分离的原则,因此,fetch应运而生了,它的出现正式为了解决传统xhr存在的问题。
fetch是基于标准的Promise设计的,使得语法上更加直观和简介,与前端ES6/ES7语法能更好的结合。既然fetch是新产生的技术,相信你心里肯定在想它的兼容性怎么样,是否能完美的运用到所需场景中去。那我们就先来看看它的兼容性以及不兼容的解决方法。
通过上图可以看到,很多低版本的浏览器,fetch还是如你所想的那样不被支持的,因此为了支持更多的浏览器,就需要实现fetch的polyfill了。思路其实很简单,就是判断浏览器是否支持原生的fetch,不支持的话,就仍然使用XMLHttpRequest的方式实现,同时结合Promise来进行封装。常见的polyfill就有:
fetch函数的参数本质就是一个Request,函数本身返回一个promise,可以通过then方法接收一个Response实例,因此基本用法如下
let request = new Request('/someurl', {
method: 'GET',
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json'
})
});
fetch(request).then((response) => {
//处理返回的数据
}).catch((err) => {
//错误处理
})
//同样可以直接写成
fetch('someurl', {
method: 'POST',
mode: 'cors',
body: JSON.stringify(jsonData);
headers: new Headers({
'Content-Type': 'application/json'
})
}).then().catch()
关于Request的参数,这里挑几个重点说下
mode
这个是设置跨域的参数,值为same-origin时,不允许跨域,它需要遵守同源策略。值为cors时,默认值,该模式支持跨域请求,顾名思义它是以CORS的形式跨域。值为no-cors时,该模式用于跨域请求但是服务器不带CORS响应头,也就是服务端不支持CORS,这也是fetch的特殊跨域请求方式,该模式允许浏览器发送本次跨域请求,但是不能访问响应返回的内容。
credentials
fetch本身请求默认是不发送cookie的,需要带cookie就需要设置此参数。omit默认值,忽略传送cookie。same-origin表示cookie只允许同域传送,不允许跨域进行传送。include表示cookie都会被传送。
fetch默认对服务端通过Set-Cookie头设置的cookie也会忽略,若想选择接受来自服务端的cookie信息,同样必须要配置credentials选项
Response介绍
response本身继承自Body,因此可以使用Body的方法,例如返回的是json对象,可以这样获取到返回结果
fetch(/someurl.json).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response.json()
}
}).then((data) => {
})
注意: 当使用了response.json()、response.blob()等方法后,body.bodyUsed就变为true了,将不能再继续使用这些方法了,否则会抛出错误。Body还有一个clone方法,但需要在response读取之前调用。
const fetchApi = (url, cfg = {}) => {
return new Promise((resolve, reject) => {
fetch(url, cfg).then((response) => {
if (response.ok || response.status >= 200 && response.status < 300) {
resolve(response.json())
} else {
throw response
}
}).catch((err) => {
reject(err)
})
})
}
function timeout(ms = 5 * 60 * 1000) {
return new Promise((resolve, reject) => {
setTimeout(function () {
reject({ok: false, text: 'timeout', title: '服务器超时!请重试!'});
}, ms);
});
}
const fetchApi = (url, cfg = {}) => {
return new Promise((resolve, reject) => {
Promise.race([fetch(url, cfg), timeout()]).then((response) => {
if (response.ok || response.status >= 200 && response.status < 300) {
resolve(response.json())
} else {
throw response
}
}).catch((err) => {
reject(err)
})
})
}
fetch的timeout即使超时发生了,本次请求也不会被abort丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已
const fetchApi = (url, cfg) => {
retrun new Promise((resolve, reject) => {
let abort = () => {
reject({ok: false, text: 'abort', title: 'abort'});
}
let p = fetch(url, cfg).then((response) => {
if (response.ok || response.status >= 200 && response.status < 300) {
resolve(response.json())
} else {
throw response
}
}).catch((err) => {
reject(err)
})
p.abort = abort
return p
})
}
现如今前端应用已经是数据驱动试图的时代,但当应用越来越大,越来越复杂时,其数据状态的管理就变得至关重要,基于facebook的flux状态管理框架,redux也应运而生了,其核心**就是如下几点
1、应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。
2、惟一改变 state 的办法是触发 action,一个描述发生什么的对象。
3、为了描述 action 如何改变 state 树,你需要编写 reducers。
针对这几条核心**,来统一管理一个store,redux的源码实现也非常的简介清晰,下面将结合自己的阅读对其源码进行一定的分析。先来看看整个src目录
|--src
|--utils
|--warning.js
|--applyMiddleware.js
|--bindActionCreators.js
|--combineReducers.js
|--compose.js //一个组合函数
|--createStore.js //store的创建,核心
|--index.js //对外提供的接口
所有代码加起来粗略估算还不超过700行,可以看出redux的实现非常简介,其**有很多值得我们借鉴的地方。
首先来看最核心的createStore.js文件,它直接对外抛出了一个创建store的createStore函数供外部调用,其接收一个纯函数reducer,一个初始的state,以及一个可选的增强函数enhancer
export default function createStore(reducer, preloadedState, enhancer) {
//code
}
此篇先来看看没有enhancer的情况(enhancer的情况将放在后面介绍),按照redux的核心,要改变state,必须dispatch一个action,那就先来看看dispatch函数的源码
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
return action
}
刚开始对传入的action进行了校验,action必须是一个对象,且按约定,应该包含一个type属性。然后currentState = currentReducer(currentState, action)
就是纯函数reducer的执行,其接收一个当前的state和这个action,根据action的type来进行分发,返回一个新的state,如果未匹配到,就返回初始的state。一个基本的reducer应该是这样
export default function(state={}, action) {
switch(action.type) {
case 'A':
return Object.assign({}, state, {a: 2})
default:
return state
}
}
接着可以看到会遍历当前的listener并执行,这些listener是在subscribe订阅函数中注册的,每当dispatch一个action都会执行这些listener,源码如下,其将返回一个取消订阅的函数,可以随时取消订阅。
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
var isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
整个store暴露出了几个接口,以供调用
return {
dispatch,
subscribe, //订阅listener
getState, //获取currenState
replaceReducer, //用新的reducer替换currentReducer
[$$observable]: observable
}
下篇将介绍bindActionCreators
常规的npm版本号 X.Y.Z
X: 大版本(major),当更新了不兼容的变更时,大版本 X 必须增加
Y: 小版本(minor),当更新了向后兼容的新功能时,小版本 Y 必须增加
Z: 补丁版本(patch),修复了bug或做了很小的兼容改的时,Z增加
需要更新npm包时,除了手动改版本号之外,可运行npm version <major/minor/patch>
中的一个进行自动更改,若为git仓库,此命令还将自动进行一次修改版本号之后的commit。
当你只想发一个测试版本到npm仓库中,让部分人员提前使用时,此时就需要在npm publish时手动指定tag了。
默认在npm publish时,会打上一个latest的tag,当其他人npm i yournpm
时,会去寻找tag为latest的版本装上。因此你可以手动指定tag为非latest的值,如下
//将当前version发布一个tag为beat的版本,version的修改为X.Y.Z-beat.1
npm publish --tag beat
//安装此版本
npm i yournpm@beat
//测试完之后,修改版本号为X.Y.Z
npm publish
查看npm包的tag,可使用npm dist-tag命令
也可使用npm info命令查看该包的所有信息,包含了tag以及version
以前的博客和记录只是看到哪就写在哪,没有一个归类,若因什么事情耽搁,就会好久不进行管理,现决定在2017年改变这个现状。
这个blog是于2017年1月1日,在医院开的,此时我还在医院住着院,挂着盐水。16年底,因为抵抗力下降,得了肺炎,连续3天高烧不退,因此来到了医院,这两天温度算是稳住了,精神恢复了,所以才把电脑拿出来了,坐在医院想着觉得2017年得换个活法了,因此先开了这篇blog。文笔有限,想到什么写什么
2016年,对于我的前端之路来说发生了很多的事,从16年春节过后开始接触react,想利用新项目提高大伙的积极性,想着一起团队建设好好搞一搞,可事与愿违。由于总公司层面的影响,直至16年6月份,我们前端团队已经四分五裂,老大的一走,团队也就散掉了,我在留下来跟公司耗着也没什么意思,因此在6月份我也被迫选择了换工作。找工作途中也遇到了很多坎,因为自己经验的不足,平常学习的也不多,所以一直很难找到一个双方都满意的工作,也让我意识到了平常自我学习积累的重要性。
对于现如今的前端发展,只要自己在努力,工作肯定会有的,在7月份的时候,我也加入了现在的公司。这边工作了半年以来,对现如今公司的感情很复杂,心里可能并没有达到很舒畅的感觉吧。但这不会影响到我对前端知识的执着,平常的业务工作做完后,会自己去学习,react、vue以及它们周边技术的运行和**,同时也会学习node端的一些东西,如express、koa等,会去社区逛逛,了解最近的各个动态,或碰到自己能解答的问题,也会伸出自己的援助之手,无线端的坑也只有自己踩过才知道坑之大,一锅顿不下啊。
2017年的前端,我相信仍然会有很大的变化和发展,如h5跟原生怎样相辅相成(如阿里的weex,这里不做评价,可去知乎上搜索),angular2的推广肯定也会对react、vue造成一定的冲击,node生态圈的更加完善,koa2正式版的发布,也都会推动着前端的进步与发展,我也会紧随着前端发展的脚步,努力下去。
2017对于自己生活来说,也有一件大事要发生,那就是我要结婚了,以后也将不再是一个人,而是有了家庭,自然也就有了更多的责任感,在做事时也将考虑的更加全面。
这次住院真切感受到,身体真的很重要。2017我要健康的撸代码,撸健康的代码。
最后借用2016年D2的主题
不忘初心,不惧未来。
webpack相信大家现在都已经或多或少有一些接触了,基本的entry、output等我就不用多说了,这次主要说说CommonsChunkPlugin的那些事
顾名思义,这个是提取出代码公共部分,先看一段代码,了解基本的用法
var CommonsChunkPlugin = new webpack.optimize.CommonsChunkPlugin({
name: 'commons'
});
module.exports = {
entry: {
//省略
},
output: {
//省略
},
module: {
rules: []
},
plugins: [CommonsChunkPlugin]
}
在静态html文件中除了将正常打包的文件bundler.js引入之外,还需要将commons.js引入(且须优先引入),也可以用html-webpack-plugin插件自动引入
commonsChunk根据页面模块引入的不同,所产生的效果也不一样,下面将分情况进行说明
index1和index2中都引用了chunk1和chunk2以及vue、vue-router,chunk1中也引用了chunk2
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
main: path.resolve(__dirname, 'src/index1.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules|vue\/dist/,
loader: 'babel-loader'
}]
},
resolve: {
extensions: ['.js', '.vue']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'commons'
})
]
}
可以看出commons.js里面并没有打入什么东西,但在引入的时候commons.js必须先于main.js引入
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
main: path.resolve(__dirname, 'src/index1.js'),
vendor: ['vue', 'vue-router']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules|vue\/dist/,
loader: 'babel-loader'
}]
},
resolve: {
extensions: ['.js', '.vue']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
})
]
}
可以看到第三方类库都被打包进了vendor.js中,引入时需先于main.js引入
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
main: path.resolve(__dirname, 'src/index1.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules|vue\/dist/,
loader: 'babel-loader'
}]
},
resolve: {
extensions: ['.js', '.vue']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
minChunks: 2
})
]
}
可以看到chunk2还是被打包进了main.js中,这说明minChunks在这种情况下并没有起作用
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
main1: path.resolve(__dirname, 'src/index1.js'),
main2: path.resolve(__dirname, 'src/index2.js'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules|vue\/dist/,
loader: 'babel-loader'
}]
},
resolve: {
extensions: ['.js', '.vue']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
minChunks: 2
})
]
}
看到两个入口文件中都引入了的chunk1和chunk2都已打包到了commons.js中
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
main1: path.resolve(__dirname, 'src/index1.js'),
main2: path.resolve(__dirname, 'src/index2.js'),
vendor: ['vue', 'vue-router']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules|vue\/dist/,
loader: 'babel-loader'
}]
},
resolve: {
extensions: ['.js', '.vue']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['commons', 'vendor'],
minChunks: 2
})
]
}
可以看到vue等第三方类库打包到了vendor中,两个入口都引入了的chunk1,chunk2打包到了commons.js中,页面引入时需按vendor>>commons>>入口文件的顺序引入
项目demo请在此处下载查看webpack-common-chunk
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.