webpack学习再回顾

在我的心中,webpack是一个变革者,他提供了前端一个全新的开发模式,并将这一职业推向一个新的高点。

追溯webpack的起源

这些年互联网发展迅速,越来越多的网站以及web应用的需求不断增加,一个井喷的web程序井喷的时代,为数不多的程序猿们,如何应对呢?自然,得提高效率嘛,于是大家就想办法提高效率,怎么提高呢?啦啦啦,此处省略一千字,答案是web设计师行业开始变更为前端工程师,前后端概念出现,简单的说,前端负责人机交互、内容的良好呈现、把需要的信息提交给后端,而后端即服务器端,负责处理服务器问题、与前端交互、以及数据的处理并与数据库交互。这样初步解决了团队效率问题。日复一日,程序越来越复杂,前端的代码量开始增多,代码维护性出现了性功能问题,哈哈。于是渐渐的,为了解决前端开发工程化复杂的问题,使其代码更加结构化、维护性更好,出现了grunt、gulp、webpack等工具。

webpack概念

webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle – 通常只有一个,由浏览器加载。

它是高度可配置的,但是,在开始前你需要先理解四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)。不详细解释,如有需要,见其官网。

webpack能力大致描述

我们都知道,前端从大致的方向上看,主要关注点在四个方面,一个是html模板,一个是css样式,一个是javaScript负责的行为逻辑,最后是其他资源,如图片。

  • js需要模块打包,好的,简单的说,webpack内部使用了browserify来处理commonJS和es6的模块的依赖关系来完成打包。你写的jsx语法、es6语法等,浏览器不支持不兼容怎么办?没关系,提前准备了babel的编译器,实现优雅编译。js想要丑化压缩下,没问题,咱有插件系统。

  • 对于css文件,由于webpack本身只理解javaScript,css文件如何打包呢?简单的说,借助loader来实现,css-loader和style-loader就是很好的任务完成者。css-loader出色的完成了css模块化的问题,style-loader出色的完成了在html中对模块化的css插入问题。你写了less,好的,聪明的开发者搞了个less-loader,负责less的编译问题。

  • 图片想要处理下,base64能做得到吗?没问题,咱有url-loader。资源想要集中化一下,没事咱有file-loader。

感受webpack的架构

nodeJS诞生后,各种前端自动化工具成为了可能。webpack出色的架构,很好的解决了自动化过程中需要处理的问题。在我看来,webpack将资源处理或者说应用程序源代码处理,看做一个文件流,甚至可以形象的比喻为一根有很多处理工厂的管道,从这头到那头的过程,自动识别要处理的资源,进行预先设定的加工处理,然后输出。加工厂可以自定义无限扩展,主要分成两类,一类是loader,一类是plugins。loader主要是做文件转换工作的。而plugins是用来做loader不能做的事儿的,因为loader局限在了文件转换,而在打包的生命周期需要执行的任务,它无法完成,插件就是来做这个工作的,比如打包完成需要对js进行执行uglify,这个时候就需要插件来完成这个任务。

废话说完了,嘻嘻。

纯前端canvas俄罗斯方块小游戏实现

小时候打的第一款游戏,说起来大概就是俄罗斯方块了,这种游戏老少咸宜,设计的非常巧妙,不高冷,不装逼,之前查询过这个游戏的过往,发现那些年火爆经典的游戏中,俄罗斯方块的火爆经典程度简直超乎我的想象。其实仔细想下,其实我们的确可以自己来实现一个类似的游戏。于是乎,经过多天的构思和参考别人写过的demo,我也自己实现了一遍,下面我就来谈谈自己的俄罗斯方块。

我的俄罗斯方块

功能点罗列

  • 地图数据结构
  • 方块数据结构
  • 地图生成
  • 地图绘制
  • 地图更新
  • 方块创建
  • 方块擦除
  • 方块下落
  • 方块的方向键操作
  • 方块变形
  • 方块满行消除
  • 方块触底检测
  • 方块左右碰撞检测

当然还有canvas元素的处理,这里就不说了。

地图数据结构

使用0, 1这样的数字来代表有无方块,地图上无方块时,就都是0. 示例如下:

[
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
]

方块数据结构

跟地图类似,使用0,1来代表形状,总结归纳出方块的几种情况:

[
    [1, 1, 1, 1]], // 一字形
    [[1, 1], [1, 1]], // 田字形
    [[1, 1, 0], [0, 1, 1]], // z字形
    [[0, 1, 1], [1, 1, 0]], // 反z字形
    [[1, 0, 0], [1, 1, 1]], // L字形
    [[0, 0, 1], [1, 1, 1]], // 反L字形
    [[0, 1, 0], [1, 1, 1]] // T字形
]

地图生成

我们将地图控制的变量明确出来,这样就可通过设置控制地图的生成。 这些变量是,多少行,多少列,一个格子多大,格子间距多大,方块生成初始化时离x轴起点的位置,方块下落速度, 加速下落速度等。

var tetrisGame = new Tetris({
    row: 12,
    col: 12,
    grid: 30,
    margin: 10,
    offsetX: 4,
    interval: 400,
    fasterInterval: 100
})
tetrisGame.start()

地图绘制

根据地图的数据,借助canvas提供的api(就两个api就可以搞定,分别是fillStyle和fillRect),给不同的数据点绘制不同的颜色。

// 地图绘制
render: function () {
    var map = this.map
    var mRowLen = map.length
    var mColLen = map[0].length
    var margin
    var grid = this.etting.grid
    margin = this.setting.margin
    for (var i = 0; i < mRowLen; i++) {
        for (var j = 0; j < mColLen; j++) {
            if (!map[i][j]) {
                this.ctx.fillStyle = 'grey'
            } else if (map[i][j] === 1) {
                this.ctx.fillStyle = 'orange'
            }
            this.ctx.fillRect(j * (grid + margin), i * (grid + margin), grid, grid)
        }
    }
}

地图更新

方块的移动需要地图做更新操作,方块就是地图中的一部分,它的移动其实就是每个格子状态根据一定的算法来改变,达到动画的效果的。因此地图或者说游戏的更新,就是根据方块当前的位置和方块本身的数据结构来更新的。

方块创建

方块的生成是随机的,利用js的随机数以及预定义好的方块数据结构,很容易做到。然后根据我们预设的方块的初始化的位置,就可以在地图中生成。

方块擦除

方块是随着时间运动的,直到触底。因此在运动的时候,我们根据时间的特性,下一个状态的方块来临之前,我们将上一个状态的方块清掉,这里注意的是,只清除那些格子有形的部分,否则会出现当与其他已定方块耦合时,将已定方块的某些部分也清除了。

方块下落

方块下落使用定时器来处理,每一个时间点,做一次清除然后增加方块y轴的值,然后再更新地图。同时在下落的时候,需要判断是否落地。

方块的方向键操作

根据keydown事件和keyCode的值,我们给不同的方向键提供不同的处理方法。向上时,我们做方块变形,向下时,我们加速下落,左右键做左右移动操作。

// 方块移动及变形操作
enableKeyControl: function () {
    var _this = this
    document.onkeydown = function (e) {
        switch (e.keyCode) {
            case 37: // 向左
                if (!_this.borderTest(_this.curBlock, -1)) {
                    _this.clearBlock()
                    _this.x--
                    _this.updateMap()
                }
                break
            case 39: // 向右
                if (!_this.borderTest(_this.curBlock, 1)) {
                    _this.clearBlock()
                    _this.x++
                    _this.updateMap()
                }
                break
            case 38: // 向上即变形
                _this.clearBlock()
                _this.transform()
                _this.updateMap()
                break
            case 40: // 向下即加速
                if (!_this.onkeydownFlag) {
                    _this.onkeydownFlag = true
                    clearInterval(_this.timer)
                    _this.fall(_this.setting.fasterInterval)
                }
                break
        }
    }
    document.onkeyup = function (e) {
        if (e.keyCode === 40) {
            _this.onkeydownFlag = false
            clearInterval(_this.timer)
            _this.fall(_this.setting.interval)
        }
    }
}

方块变形

方块变形就是方块的旋转,我们通常喜欢顺时针来旋转,比较舒服,当然你喜欢逆时针,也可以做的。我是用的顺时针。所以根据旋转的规律,就可以根据旋转前方块的数据结构得出旋转后的数据结构。这里也需要注意一点,是否可以旋转,需要判断,因为有时候一旋转出了边界,一旋转和底部实体融合了,等等。判断的方法是,方块的数据暂时不变更,我们将旋转后的数据,去做测试,判断是否触底或超出边界,即去做方块触底检测和左右方向碰撞检测,如果没有碰撞,就更新方块的数据,如果有,就不允许更新方块数据。

// 方块变形
transform: function () {
    var result = []
    var curBlock = this.curBlock
    var blockRowLen = curBlock.length
    var blockColLen = curBlock[0].length
    for (var i = 0; i < blockColLen; i++) {
        result.push([])
        for (var j = 0; j < blockRowLen; j++) {
            result[i][blockRowLen - j - 1] = curBlock[j][i]
        }
    }
    if (
        !this.groundTest(result) &&
        !this.borderTest(result, -1, true) &&
        !this.borderTest(result, 1, true)
    ) this.curBlock = result
}

方块满行消除

满行的条件触发是以方块落地开始的,所以落地判断成功,就应该需要去判断是否满行,然后做相应的处理。满行的判断,是通过遍历整个地图,看是否有那么些行的格子里都填满了,如果是就需要消除,消除就是在地图的数据结构中,干掉这一样,当然,这个时候的同时,我们可以在地图的最前面行unshift一个充满空格子的空行。

方块触底检测

触底分为两种情况,第一种,是最底部,这个很好判断。第二种,是与其他已经落下的方块之间的判断,看是否还需要往下走。第二种判断方法是,先判断方块的最底部那一行,如果有实体的地方对应的正下方的地图的格子也是实体,那么就不应该再下落,也就是说触底了,如果没有,就去对底部没有实体的那些列去做while循环,向上找到方块的实体格子,这个是总能找到的,因为方块的设计缘故,找到后,就也做类似的判断,看这个方块处的格子对应的正下方地图的格子是否是实体,进而判断是否触底。

方块左右碰撞检测

左右碰撞也分为两种,一种是边界,一种是与其他方块。这里碰撞后,下落还是继续的,不是停止。这个地方的判断,归根结底,其实和下落时与其他方块的判断很类似,只是判断的时候换了一个方向而已。

啦啦啦,终于写完啦。这个简易的俄罗斯方块的代码我上传到了github,看源码请参考https://github.com/Andrewuetyang/tetris

react框架入门小结

概要

前端框架纷繁复杂且多样的今天,似乎要学习的东西特别多。当然,不得不说,大家的时间都是有限的,而且大多数人是不能够做到大神般的领悟能力,即用极少的时间学到大量的知识。由于本人最先接触的两个框架并实际在工作中应用的是angular 1.x和vue,自然在学习react的时候,我会频繁拿他们来做为对比,毕竟对比学习,是人类常见的一种学习方式。所以在我看来,根据前面的应用框架经验,前端框架的学习主要集中在这几个方面。
* 核心理念
* 语法
* 数据绑定
* 列表渲染
* 条件渲染
* 事件处理
* 组件系统
* 路由系统

react核心理念

react

react在优化web技术性能方面,提出了非常具有革命性意义的方案。简单的说,有三个方面:

  • 虚拟dom技术
  • JSX语法
  • 组件系统

这三个方面很好地解决了web性能、开发效能、代码维护性的问题。当然优化没有绝对完美,这个过程还将持续…

为什么采用JSX语法?

JSX语法,在很多人初次接触时,觉得很不友好,大量的HTML都往js里写,非常让人不适应,有一种原罪感。但是,你如果了解react的组件系统,你就知道,这样做的意义有多大。JSX语法其实是服务于组件编写的。react的组件化野心很大,react提供的组件玩法,如果玩家在拆分组件得当的情况下,后面的玩法就像撘积木一样,组合好就行。而组件的写法,可以用写类的方式来写,妥妥的契合了面向对象的思想,而这种思想,大量的存在于各种软件项目中,是程序员最为熟悉的一种编程思想。

数据绑定

jsx语法其实已经包括了数据如何绑定到你想要绑定的位置上,简单的说jsx语法使用{}花括号的方式,可以将state, props等数据绑定到你想要绑定的位置。{}里面可以是js表达式。如:

import React from 'react';
import ReactDom from 'react-dom';

let data = {
  id: 123,
  text: 'hello world'
};

ReactDom.render(
  <div>{data.text}</div>,
  document.getElementById('root')
);

列表渲染

相比于其他框架,如angular、vue,react没有提供指令系统,自然对于列表渲染的处理方式会有所不同。其实列表渲染的方式,主要还是由先决的jsx语法和组件系统决定的。一个简单的例子可以说明一下其列表渲染的方式(例子来自react官网)。这个方式首先给我留下一个影响,相比于angular和vue来说,不够简洁。虽然并不妨碍什么。

import React from 'react';
import ReactDom from 'react-dom';

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

条件渲染

对于需要条件渲染的部分,我们可以提取出来作为一个组件,这样,在组件里面,我们就可以对其做程序流的处理,如简单的渲染还是不渲染,就可以通过判断,返回还是不返回对应的html片段。当然如果只是渲染文本节点,也可以在jsx语法里面直接使用js表达式来做条件渲染。

事件处理

react事件处理其实还是组件系统中的一部分,其基本语法如下:

import React, {Component} from 'react';

class Test extends Component {
  constructor (props) {
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick(e) {
    // handle code go here
  }
  render() {
    return (
      <button onClick={handleClick}>点击</button>
    );
  }
}

组件系统

借助于jsx语法,react的组件可以很灵活,这也是它的特色之一。组件系统,解决了数据绑定、列表渲染、条件渲染和事件处理等问题。写高可维护性、高可扩展性的组件,是我们追求的一个事,也可以从某种程度上说明一个程序员的经验和能力。一般来说,编写组件需要考虑以下几个问题:

  • 如何做到业务与组件逻辑分离?
  • 组件的细粒度要做到什么程度?
  • 组件的接口是否预留完备?

路由系统

当前越来越多的web应用采用SPA即单页面应用的技术,而单页面应用不得不提到的就是其路由系统。react中,官方也一直在维护其路由解决方案,即react-router。当前,该路由系统已经升级到4.x版本,可谓变化之快。路由的使用,可以参考react-router官方文档,这里就不做介绍了。

(完)

构建一个全栈web应用的骨架

为什么要去做这种尝试?

前端的开发模式已经在这几年发生了翻天覆地的变化了。现在借助于node和行业著名的几个前端框架可以轻松通吃前后端。当然也有不适用的场景,这里就略过。

全栈

于是乎,随着自己技术的成长,必然会去玩儿,一个人搞定一个web应用,也是很酷的事儿,以前后端开发工程师经常干这事儿,而前端人员大多数人技术面较窄,不过这也不是一成不变的,前端人员也开始往其他方向延伸,技术能力也在不断提升,以后的前端工程师,必会出现越来越多懂后端技术的人。

主要技术栈

  • vue
  • webpack
  • element-UI
  • nodeJS
  • mongoDB

项目结构

项目结构

开发思路梳理

虽然是一个人开发,但是也有一个基本的逻辑,就是还是需要大致分为两个部分来做。一个是前端逻辑,一个是后端逻辑。有点废话了,咳咳…

前端部分,这个对于我来说就相当熟悉了,vue-cli工具,基本可以把项目架构起来。而且可以通过node下的express框架可以轻松的做mock数据,开发效率大大增加。不过也感觉是废话,因为在完全的前后端分离的项目,前端肯定会用mock

后端部分,使用express + moongoDB。在后端开发的时候,单独于项目开发,也可以直接在项目中开发。由于是一个人开发,我觉得没必要独立于该项目,新建一个项目。

统筹部署,假设前端部分和后端逻辑,api接口及数据库操作都已实现,如何部署呢?我们知道,前端部分是会在完成开发的时候,打包成一个静态资源的,我们只需要在后端处理时,设置好静态资源所在的目录,同时被请求是返回index.html即可。在项目中,本地查看,可通过node命令,运行后端入口文件,即可启动整个web应用。

(完)

前端知识体系梳理

非常完善的前端知识体系是什么样的?

废话不多说,咱上图! 这是一张相对来说,非常完善的一张关于前端开发知识结构的图,这种图,一般给人的感觉就是,怎么有这么多东西要学啊!!!前端,从入门到放弃 🙁

的确,一个大神级别的前端开发,的确这是必须掌握的知识结构,不仅要求广度,还要求精。作为一个向大神看齐的我,又有超级有能量的好奇心驱使,我已入坑,注定会在coding的世界,越走越远,希望玩得快乐,享受痛苦,不断进步。

我的梳理

首先,我的梳理很个性化,完全是面向自己的,不是面向对象哟,所以,不求严禁,不求完善,只求过得去自己就行。

前端,从代码组成方面来看,主要涉及三种语言HTML,CSS和JavaScript。他们各自的功能为,HTML负责结构化,CSS是表现,JS是行为。随着互联网的发展,为了适应这种发展,前端作为web应用的一个重要组成部分,开始变得越来越重要,因为前端是最贴近用户的,它的性能和表现,直接影响用户的感受。所以,前端的终极目的就是用户的最佳的体验,服务好用户。

  • JavaScript是解释类语言,解析就有效率问题,于是经过竞争,googleV8引擎胜出;
  • JS很灵活,但是对于大型应用,灵活不是一种好事,走在前面的高级JSer们,遇水搭桥,逢山开路,出现了借鉴静态强类型的语言,如C#Java而获得了利器TypeScriptCoffeeScript
  • 随着日益复杂的网页出现,功能要求开始增多,JS操作dom的繁琐,导致开发效率低下,JQuery出现了;
  • web2.0到来,互联网越来越庞大,信息、服务越要越多,技术知道自己需要提前做好准备,于是在这之前,HTML5, css3, ES6到来了;
  • JQuery很锋利,可是随着交互需求升级,JQuery操作dom越来越频繁,导致网页或应用的性能下降,开发效率也开始下降,到了这个口子,优化的需求迫切,于是为了解决这个问题,前端框架实践开始,诞生了很多框架,最后到今天,经过竞争,脱颖而出了Angular, React, Vue三大框架;
  • 开发效率问题,网页或应用性能问题,代码扩展性和维护性问题,问题层出不穷,前辈们理清问题,打造工具,于是grunt, gulp, webpack等工具就问世了;
  • 某位工程师想要开发一款高性能web服务器,经过考虑,比对优缺点,由于JS的特性,V8引擎的性能,node.js诞生;node.js它不仅在web服务器端表现良好,更对前端的开发,起到了很好地辅助作用;
  • 未来的故事,还将继续…

(完)

微信小程序编写经验分享

  1. 微信开发者工具:小程序的开发,需要使用微信开发者工具,小程序需要编译才能执行。注意不要文件保存时自动编译。编译是一个比较耗时的过程。这样非常影响开发效率和体验,还是手动编译好;

  2. 数据绑定:小程序是没有数据双向绑定的概念,是单向绑定。要实现双向绑定,可以利用事件;

  3. 阻止事件冒泡。小程序中有冒泡事件和非冒泡事件的概念。bind开头的事件是冒泡事件,catch开头的事件是非冒泡事件;

  4. 事件绑定:a.事件绑定如何往事件处理函数中传值?小程序在事件处理中会传入一个event对象,可通过event对象找到target(或currentTarget)下面的dataset。dataset对应标签上属性data-*里指向的值;b.event对象下的target和currentTarget的区别。target简单的说是触发事件的源组件;currentTarge是指事件绑定的当前组件。

  5. 跳转:navigateTo跳转是跳转到一个新页面,页面栈的情况是push一个页面入栈,但对上一个页面不做处理。redirectTo跳转到一个新页面,页面栈的情况是push一个页面入栈,但会干掉上一个页面。navigateBack跳转是从当前这个页面在页面栈的位置中回退到上一个页面。reLaunch跳转是把所有页面栈清掉,在push一个新页面入栈。

  6. 组件化(小程序中叫模板):关键字是template标签和引入标签import。

  7. 可以使用parseInt等api吗? 可以使用setTimeout等api吗? 都可以

  8. 如何获取url中的参数?onLoad函数中自带有个options参数,这个options就是处理好的url参数对象。

  9. es6支持度,暂时不支持for of的object.entries方法。

  10. 对于富文本的解析,可以使用wxParse这个开源工具。其原理是将富文本转化为一个json数据格式,再通过模板template进行渲染。

  11. 改变checkbox的大小,需要使用transform来做缩放。