Code Monkey home page Code Monkey logo

blog's People

Contributors

soulcm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

一点困惑

一点困惑

1月9号,微信小程序如期发布,各个团队也陆续推出了小程序。前面得肺炎住院今天也出院了,想着自己最近的学习状态,使得自己心里的那一丝丝担忧和迷茫更加的加深了。

作为一个差不多两年经验的前端,从开始的啥都不懂,慢慢成长到现在能够去有意识的阅读源码,理解各个框架的**,并能独立承担起一些开发和框架的搭建。

但最近每天在一如既往的学习的同时,心里突然感觉到虽然在学习,但跟社区和业界大牛差距还是越来越远,感觉自己一直是在追着前端的脚步,并且有点越来越难追上。

前端这一两年发展真的很快,node(express)、react、vue、rxjs、无线端、weex、koa、gulp、webpack、函数式编程、服务端渲染等等一个一个接踵而来,虽然自己也一直拥抱着前端的变化,会去了解和使用它们,但如今真的感觉有点踹不过气(加上生活的压力),越往里钻,研究的越深越感觉到差距

公司团队本身确实也有很大一部分影响,一个好的团队,一个拥抱着技术的团队,一个好的项目,一个好的环境,肯定对一个人的成长是非常有益的,但现如今的公司团队感觉只是为了解决现有业务,不断的叠加,技术上也不愿尝新和过多的研究,团队学习气氛也不是很浓厚,只能自己平常花时间去学习,但这样的效率肯定与一个好的团队在一起讨论学习差了很多。

后面自己是该调整一下心态了,春节后也许出去放松下,换个环境,然后重新投入到一个满意的学习环境中去,才能在前端上面更上一层楼吧。

组件的一些思考

组件的一些思考

如今数据驱动视图的框架已经成为前端开发的主流,如react、vue、angular等,当然还包括我们用的regular。这些框架大大提高了我们的开发效率,但同时现在的页面结构越来越复杂,虽然有这些mv*的框架,但如果我们使用不当,也会造成状态数据管理混乱,代码难以维护的困扰。

因此组件化的**就是为了提高开发效率和后期维护的效率。

什么是组件化

一个组件只做一件事,基于功能做好职责划分。

对组件的封装都是为了对数据逻辑业务的梳理,使得不同组件各司其职,即把大块的业务界面,拆分成若干小块,然后进行组装。当然这里不局限与js,其实css同样适用(如nec规范)。

组件的划分

module-and-components

无状态组件

数据驱动视图,顾名思义视图的状态肯定是根据数据进行变化的,因此数据的状态管理就变得尤为重要。

无状态组件为只接受props,根据props的不同展示出不同的样式,并且会抛出事件来通知外部组件需要的更改(ps. 按照react以及vue的单向绑定原则,props传入是不能被更改的,这和regular的双向绑定不同)

单向绑定能更好的帮助我们控制住数据的状态,特别对于越来越复杂的前端

UI组件

这部分就是我们视图上看到的各个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。

组件的原则

  1. state状态应尽量简单
  2. 单向原则,子组件不应该影响父组件,当某个子组件删除后,只会影响此子组件的UI展示,其他组件都不应该产生影响
  3. 参数的扁平化,接收的props应该尽量做到是基本数据类型。

redux源码解析(四)

这篇将是redux源码解析的终篇,主要讲applyMiddleware的原理以及中间件的实现
最近太忙了,先占个坑
@todo

redux源码解析(二)

bindActinoCreators

上篇介绍了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的函数。

js部分排序算法的实现

冒泡排序

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))
}

flex属性之flex-grow、flex-shrink、flex-basis详解

flex属性之flex-grow、flex-shrink、flex-basis详解

引子

弹性布局经过了几个世纪的演变,相信对于大部分人来说应该都不算陌生了,最起码或多或少有了解过。

对于没有完全了解清楚却使用过此属性的人,应该是对它又爱又恨吧。

上上下下左左右右各种居中

均分一块容器,避免了通过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>

1

具体宽度数值请戳我查看

通过例子发现,实际宽度都不是我们上述设置的宽度或者基准宽度,我们来看看它的计算方式。

主轴总长度为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基准值起了很重要的作用。

总结:

  1. 当值默认为auto时,以此元素设置的尺寸为准,若也是auto,则以内容content尺寸为准
  2. 当flex-basis设置了值时,以此设置尺寸的为准
  3. 若为百分比,则根据父容器进行计算。若为0%,则其设置的width将无效。
  4. 建议在子项中通过设置flex-basis的方式去设置想要的尺寸,而不是设置width
  5. 缩写flex:auto(flex: 1 1 auto),flex:none(0 0 auto),flex:1(1 1 0%)
  6. 若想某项不参与弹性布局的计算,应将其设置成flex:none

PS.以上的计算弹性尺寸的方法都是自己查阅资料和摸索出来的,若有不对的地方欢迎指出

参考资料

CSS 伸缩盒布局模组

Flex 布局教程:语法篇

flex布局踩过的那些坑

webpack构建之hash缓存的利用

webpack构建之hash缓存的利用

众所周知,优化一个页面的方法之一就是利用浏览器的缓存机制,在文件名不改变的情况下,使客户端不用频繁向服务端请求和重复下载相同的资源,节约了流量的开销。

现在前端的主要构建工具就是使用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
    ] 
}

打包结果如下:
hash1

这里的hash是每次编译时所计算出来的一个值,因此当任何一个文件修改,hash都将会改变,而且所打包出来的文件名也将不一样了,这样对于需频繁发布的项目不是很友好,会造成每次有新版本,用户浏览器都将重新下载所有的文件,为了避免这种情况,webpack还提供了chunkhash(:这里提取出的css文件hash值不一样,是因为使用的是extract-text-webpack-plugin插件,它提供了自己的一个contenhash,也是对于css文件建议的一种用法,保证了css有自己独立的hash,不会受到js文件的干扰)

将上述代码中的hash换成chunkhash后打包结果如下:
hash2
看到每个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

参考资料:

webpack的cache

extract-text-webpack-plugin

webpack-md5-hash

http缓存

inline-manifest-webpack-plugin

webpack-md5-hash-issue

react部分组件写法的思考

react部分组件写法的思考

此处所说的组件,是指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的处理方式,还是期待下的

总结:

  • modal、tip这类组件,都不应该mount到本身页面元素里,而是应该渲染到最外面
  • 对于message和toast之类组件应该是提供对外的静态方法,外部直接调用就好,如toast.info('我是toast')

redux源码解析(三)

combineReducers

通过前面的学习,我们知道,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,将各个业务模块给分离开来,方便管理了。

参考资料

combineReducers 用法

combineReducers 进阶

从event loop看vue的nextTick

从event loop看vue的nextTick

先看一段代码,请使用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就不会有这个问题。

回到最开始,我们看下那段代码的执行过程

  1. 代码执行,同步代码全部放入在task或microtask中

    tasks: [concole.log, setTimeout cb, concole.log]
    stack: [console.log]
    microtask: [promise then]
    
  2. task的代码依次执行,先打印出script start,然后在打印出script end,此时剩余一个macrotask和microtask

    tasks: [setTimeout cb]
    stack: [promise then]
    microtask: [promise then]
    
  3. 然后promise执行,打印promise 1,然后接着另一个promise打印promise 2

    tasks: [setTimeout cb]
    stack: [setTimeout cb]
    microtask: []
    
  4. 打印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方式去书写(暂且用的较少)。

参考资料

event loop

Tasks microtasks queues and schedules

MutationObserver

fetch简介

fetch简述

在与服务端进行异步请求时,你是否还在写着地狱般的回调,是否还在写着繁琐的配置,是否还觉得调用非常的混乱,这就是我们一直使用了很长时间的XMLHttpRequest来实现的ajax技术,尽管各种框架的封装就是为了解决上述的问题,但有时需要阅读到深层的源码时,又将会是另外一番痛苦,同时ajax技术也不符合关注分离的原则,因此,fetch应运而生了,它的出现正式为了解决传统xhr存在的问题。

概念

fetch是基于标准的Promise设计的,使得语法上更加直观和简介,与前端ES6/ES7语法能更好的结合。既然fetch是新产生的技术,相信你心里肯定在想它的兼容性怎么样,是否能完美的运用到所需场景中去。那我们就先来看看它的兼容性以及不兼容的解决方法。
fetch-polyfill

通过上图可以看到,很多低版本的浏览器,fetch还是如你所想的那样不被支持的,因此为了支持更多的浏览器,就需要实现fetch的polyfill了。思路其实很简单,就是判断浏览器是否支持原生的fetch,不支持的话,就仍然使用XMLHttpRequest的方式实现,同时结合Promise来进行封装。常见的polyfill就有:

es6-promisefetch-ie8isomorphic-fetch等等。

使用

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的参数,这里挑几个重点说下

  1. mode

    这个是设置跨域的参数,值为same-origin时,不允许跨域,它需要遵守同源策略。值为cors时,默认值,该模式支持跨域请求,顾名思义它是以CORS的形式跨域。值为no-cors时,该模式用于跨域请求但是服务器不带CORS响应头,也就是服务端不支持CORS,这也是fetch的特殊跨域请求方式,该模式允许浏览器发送本次跨域请求,但是不能访问响应返回的内容。

  2. 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读取之前调用。

特殊处理

  • fetch对于网络响应400或500等状态并不会reject,相反它会被resolve,只有在网络本身发生错误时才会被reject,因此在我们使用的时候可以对其进行一层封装
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)
        })
    })
}
  • fetch本身不支持超时处理,因此我们可以通过自己进行一些polyfill。
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丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已

  • fetch本身也是不支持取消的,因为promise本身是不能取消的,有提议希望未来出现一个可取消的Promise标准,但暂且我们也只能进行一个封装
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
    })
}

参考资料

传统 Ajax 已死,Fetch 永生

fetch API

es6 Promise

isomorphic-fetch

Request

Response

Body

Headers

redux源码分析(一)

redux源码分析(一)

现如今前端应用已经是数据驱动试图的时代,但当应用越来越大,越来越复杂时,其数据状态的管理就变得至关重要,基于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 version & tag

npm version && tag

常规的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.01.01

以前的博客和记录只是看到哪就写在哪,没有一个归类,若因什么事情耽搁,就会好久不进行管理,现决定在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插件CommonsChunkPlugin细说

webpack之CommonsChunkPlugin的正确打开方式

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根据页面模块引入的不同,所产生的效果也不一样,下面将分情况进行说明
dir

index1和index2中都引用了chunk1和chunk2以及vue、vue-router,chunk1中也引用了chunk2
index
chunk1

1、单页应用(只有index1一个入口文件)

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'
        })
    ]
}

webpack1

可以看出commons.js里面并没有打入什么东西,但在引入的时候commons.js必须先于main.js引入

2、单页应用(只有index1一个入口文件),将第三方库单独打出

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'
        })
    ]
}

webpack2

可以看到第三方类库都被打包进了vendor.js中,引入时需先于main.js引入

3、单页应用,加入minChunks参数

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
        })
    ]
}

webpack3
可以看到chunk2还是被打包进了main.js中,这说明minChunks在这种情况下并没有起作用

4、多页应用,使用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
        })
    ]
}

webpack4
看到两个入口文件中都引入了的chunk1和chunk2都已打包到了commons.js中

5、多页应用,将第三方模块分开打包,同时使用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'),
        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
        })
    ]
}

webpack5
可以看到vue等第三方类库打包到了vendor中,两个入口都引入了的chunk1,chunk2打包到了commons.js中,页面引入时需按vendor>>commons>>入口文件的顺序引入

项目demo请在此处下载查看webpack-common-chunk

参考资料

commons-chunk-plugin

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.