【框架学习】前端技术概览 - hippowc/hippowc.github.io GitHub Wiki
前端基础
- html
- js -- 语法糖 jQuery
- css
- angular(使用TypeScript)
- React
- Vue.js
- Ember js
- Preact
- Inferno
React相关技术
- Flux
- Redux
React
- Yarn
- npm
- webpack
- roll up
- RequireJs/AMD
- Browserify
- npm scripts
- gulp
- Grut
其他类型的语言目前都是通过编译器编译为js代码,js可以被成为新一代汇编语言了
- es6(ECMAScript 6,是一套标准)
- TypeScript(微软)
- flow(facebook)
- Dart(google,已经不管了,开始用微软的TypeScript)
- Jest
- Mocha
- Bootstrap
- Foundation
- Sematic UI
- Sass
- Less
- Stylus
- PostCss
-- React
- 中文文档:https://react.docschina.org/
- 脚手架:https://github.com/facebook/create-react-app
- 组件库:https://react.docschina.org/community/ui-components.html
-- vue
参考:es6语法
- let和const
let声明的变量只在它所在的代码块有效,let实际上为 JavaScript 新增了块级作用域。
const声明一个只读的常量。一旦声明,常量的值就不能改变。const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了
- 变量的解构赋值:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
1 数组
let [a, b, c] = [1, 2, 3];
2 对象
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
- 函数的扩展
1 ES6 引入 rest 参数(形式为...变量名),rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
2 name属性:函数的name属性,返回该函数的函数名。
function foo() {}
foo.name // "foo"
3 箭头函数
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
注意点:
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 等等
4 双冒号运算符
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
- 数组的扩展
1 扩展运算符 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。
- 对象的扩展
ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁, 直接写变量。这时,属性名为变量名, 属性值为变量的值
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
除了属性简写,方法也可以简写。
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
解构赋值: 对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
- Class 基本语法
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
类的方法内部如果含有this,它默认指向类的实例。解决方法是使用箭头函数。
- 模块的语法
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
// 或者
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
export命令除了输出变量,还可以输出函数或类(class)。通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 报错
export 1;
// 报错
var m = 1;
export m;
应该为
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
同样的,function和class的输出,也必须遵守这样的写法。
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错
import 语法
import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。但是,如果a是一个对象,改写a的属性是允许的。
import { lastName as surname } from './profile.js';
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
export default 命令
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。export default命令,为模块指定默认输出。import命令可以为该匿名函数指定任意名字。需要注意的是,这时import命令后面,不使用大括号。
export default function () {
console.log('foo');
}
import customName from './export-default';
export default命令用在非匿名函数前,也是可以的。一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。
- 元素是构成 React 应用的最小单位,用来描述你在屏幕上看到的内容。
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。
将元素渲染到 DOM 中,在一个 HTML 页面中添加一个 id="root" 的
<div id="root"></div>
此 div 中的所有内容都将由 React DOM 来管理,所以我们将其称之为 “根” DOM 节点。
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
更新元素渲染:更新界面的唯一办法是创建一个新的元素,然后将它传入 ReactDOM.render() 方法
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
- jsx,一种 JavaScript 的语法扩展,用来声明 React 当中的元素。
可以任意地在 JSX 当中使用 JavaScript 表达式,在 JSX 当中的表达式要包含在大括号里。
在编译之后,JSX描述的元素其实会被转化为普通的 JavaScript 对象。其实可以在 if 或者 for 语句里使用 JSX,将它赋值给变量,当作参数传入,作为返回值都可以。
JSX 属性:可以使用引号来定义以字符串为值的属性,也可以使用大括号来定义以 JavaScript 表达式为值的属性
JSX 标签是闭合式的,那么你需要在结尾处用 />,JSX 标签同样可以相互嵌套。JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称。
JSX实际是对象:Babel 转译器会把 JSX 转换成一个名为 React.createElement() 的方法调用。下面两种代码的作用是完全相同的:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement() 这个方法首先会进行一些避免bug的检查,之后会返回一个类似下面例子的对象:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};
- 组件和Props
组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素。
定义一个组件最简单的方式是使用JavaScript函数,这种类型的组件为函数定义组件,是因为从字面上来看,它就是一个JavaScript函数。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
你也可以使用 ES6 class 来定义一个组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
当React遇到的元素是用户自定义的组件,它会将JSX属性作为单个对象传递给该组件,这个对象称之为“props”。
组件可以在它的输出中引用其它组件,这就可以让我们用同一组件来抽象出任意层次的细节。
组件的返回值只能有一个根元素。这也是我们要用一个
无论是使用函数或是类来声明一个组件,它决不能修改它自己的props
- state和生命周期
状态与属性十分相似,但是状态是私有的,完全受控于当前组件。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
构造函数是唯一能够初始化 this.state 的地方。
状态更新可能是异步的:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
- 事件处理
React事件绑定属性的命名采用驼峰式写法,而不是小写。如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)
在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。
你必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的。如果你忘记绑定 this.handleClick 并把它传入 onClick, 当你调用这个函数的时候 this 的值会是 undefined。可以在声明时使用箭头函数默认绑定类对象。
向事件处理程序传参:这两种方法都可以
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
- 受控组件
在HTML当中,像,<textarea>, 和 这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。
对于这种我们可以抛弃元素自身状态,统一使用react来维护更新,这种由React控制的输入表单元素称为“受控组件”。
- 状态提升
使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。
在React应用中,对应任何可变数据理应只有一个单一“数据源”。通常,状态都是首先添加在需要渲染数据的组件中。此时,如果另一个组件也需要这些数据,你可以将数据提升至离它们最近的父组件中。你应该在应用中保持 自上而下的数据流,而不是尝试在不同组件中同步状态。如果某些数据可以由props或者state提供,那么它很有可能不应该在state中出现。
npm -v : 查看版本号
npm install -g : 安装模块,-g参数代表全局安装。
本地安装:将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。可以通过 require() 来引入本地安装的包。如果要运行该模块,需执行 node_modules/.bin/
全局安装:将安装包放在 /usr/local 下或者你 node 的安装目录,可以直接在命令行里使用。
npm install moduleNames --save/--save-dev 会在package.json的dependencies/devDependencies属性下添加moduleNames 即发布依赖时候任依赖的插件
npm config set registry https://registry.npm.taobao.org 修改包下载源,此例修改为了淘宝镜像
npm config get prefix 查看全局安装路径
npm config set prefix G:/node_modules_global 修改全局安装路径
npm init 初始化目录,创建模块,会生成package.json文件
npm uninstall
npm ls --global 会查看到安装包所包含的所有依赖文件 npm ls --global -depth 0 只查看顶级安装包
npm ls 查看本地安装包
npm cache clean 删除安装包缓存
npm update xxx 更新安装包
npm search xxx 查找验证某个包是否已经存在
npm root 查看当前包安装路径 npm root -g 查看全局包安装路径
- 关于package.json:
npm 允许在package.json文件里面,使用scripts字段定义脚本命令。然后命令行下使用npm run <script>命令,就可以执行这段脚本。相当于在命令行执行
- npm版本号 版本号格式:major.minor.patch
^表示同一主版本号中不小于该版本号 ~表示同一主次版本号中不小于该版本号
npm可以安装指定版本号的包,npm i @1.0.0
单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序,单页面的好处就是体验好。
但是多个url去控制展示肯定是需要的,url的改变一般都是会引起页面跳转的,想要根据路由变化来展示不同的内容,同时页面又不跳转,如何做呢。
在url中,#号之后的内容是不会引起页面跳转的,所以我们利用这一点来实现,当url是#/a的时候,显示a的html片段,同理#/b则显示b的html片段,这样一来,前端就有了自己的路由控制,可以来实现前端自己的 多页面 。
react是基于组件(component)来构建页面的,从这个定义上来讲,其实 页面只不过是一个更大的组件 ,它可以用来容纳和组织其他各个组件,从而编织出一个完整可用的页面。
但是这个所谓的 大组件 怎么看都与其他组件有那么些不一样的地方,它的目的不是被复用,而是负责为其他组件处理并注入数据,这样的组件我们称为 容器组件 (container component)。容器组件并不一定就是页面,它可以是各种粒度的组件组合。一个纯粹接收外部数据(不作处理)并只负责展示的组件,我们称为 展示组件 (presentational component)。
react搭配react-router可以很轻易地实现前端路由。使用react集成router很简单,直接installreact-router即可:
import React, { Component, PropTypes } from 'react'
import ReactDom from 'react-dom'
import { Router, Route, IndexRoute, hashHistory } from 'react-router'
import Index from './containers/Index'
import About from './containers/About'
ReactDom.render(
<Router history={hashHistory}>
<Route path="/" component={Index}/>
<Route path="/about" component={About}/>
</Router>,
document.getElementById('root')
)
每个Route就是一个路由规则,path对应路径,component即对应要显示的容器组件,也就是我们前面说的页面。所以现在我们访问/,可以看到链接自动变成了访问/#/,同时显示Index的内容,然后我们访问/#/about,会发现页面变成了About的内容。
但是这样会有一个问题:使用webpack打包后的代码,如果没有使用插件分离的话,我们的css是会打包到js当中的,这样会使得js体积非常庞大,所以出于性能考虑,我们应该把css分离出来,通过使用extract-text-webpack-plugin可以实现。
这个webpack插件会把所有css都打包到一个独立的文件当中。。。我们需要一些小技巧,来规避css互相影响的问题。在每个组件的最外层元素,都会定义一个根className
还会遇到另外的问题:SEO,由于所有的内容都集中在了一个页面里,百度爬虫能抓取到的页面就变少了;由于页面是由js动态构建的,数据是通过api异步获取的,而百度搜索引擎目前还不支持解析js,所以页面在爬虫眼里是没有内容的。
另另外。。。还有一个问题:就是都打包在一个文件中,太大了,要考虑分别加载。webpack的 code splitting 可以解决这个问题
code splitting 即代码分割,webpack支持将文件分割成若干份,然后异步地加载到页面上来。通过使用require.ensure的方式,webpack会把a和b这两个模块以及callback里面的代码,单独打包成独立的资源文件,然后在运行的时候通过jsonp的方式加载回来。
import React, { Component, PropTypes } from 'react'
import ReactDom from 'react-dom'
import { Router, Route, IndexRoute, hashHistory } from 'react-router'
import Index from './containers/Index'
ReactDom.render(
<Router history={hashHistory}>
<Route path="/" component={Index}/>
<Route path="/index" getComponent={(nextState, cb) => {
require.ensure(['./containers/About'], function(require) {
var About = require('./containers/About').default
cb(null, About)
})
}}/>
</Router>,
document.getElementById('root')
)
Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端或 Unix/Linux shell,我们可以在终端中输入命令,并接收系统的响应。node是个好东西,使得js语言在服务端也有了生命力,要不是因为node,我真心深入学习js语法。
- node的模块系统
node编程中最重要的思想之一就是模块,这个功能使得js工程化成为可能。node的模块化标准遵循CommonJs,它使用require全局方法来引入模块,但是es6后来指定的规范是使用import,不过node解释器目前还不认识import,只认识require()
模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。
模块的导出:通过module.exports = <js对象> 或者 exports.<模块名> = js对象。其中:
1 module.exports 初始值为一个空对象 {}; 2 exports 是指向的 module.exports 的引用; 3 require() 返回的是 module.exports 而不是 exports;
所以,千万别用exports = js对象,这样exports指向的引用就变了。
模块的导入:var foo = require(<文件名>); require会依次在文件模块缓存、原生模块、文件模块中加载
- node的回调函数
Node.js 异步编程的直接体现就是回调。Node 所有 API 都支持回调函数,都支持同步和异步两种方法。譬如:
// 同步
var fs = require("fs");
var data = fs.readFileSync('input.txt');
// 异步
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数接收错误对象作为第一个参数。
- node事件循环
Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高。Node.js 几乎每一个 API 都是支持回调函数的。Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.
Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件
- node的全局对象
也是js的全局对象。
__filename:表示当前正在执行的脚本的文件名。
__dirname:__dirname 表示当前执行脚本所在的目录。
setTimeout(cb, ms):setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。:setTimeout() 只执行一次指定函数。返回一个代表定时器的句柄值。
clearTimeout(t):clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。
setInterval(cb, ms):setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。返回一个代表定时器的句柄值。可以使用 clearInterval(t) 函数来清除定时器。setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
console:console 用于提供控制台标准输出,它是由 Internet Explorer 的 JScript 引擎提供的调试工具,后来逐渐成为浏览器的实施标准。Node.js 沿用了这个标准,提供与习惯行为一致的 console 对象,用于向标准输出流(stdout)或标准错误流(stderr)输出字符。
process:它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。
- node的常用原生模块
1 var util = require('util');提供常用函数的集合,用于弥补核心JavaScript 的功能 过于精简的不足。
2 var fs = require("fs");Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。
3 var http = require('http'); var url = require('url'); 用以接受http请求,以及url的处理
4 其他工具:os, path, net,dns,domain
- web框架express,使用示例:
npm install express
npm install body-parser // node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。
npm install cookie-parser // 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
npm install multer // node.js 中间件,用于处理 enctype="multipart/form-data"(设置表单的MIME编码)的表单数据。
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World');
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
为啥会需要使用到flux和redux?首先看下React遇到了什么问题:React 能够根据 state 的变化来更新 view,React 中每个组件都有 setState 方法用于改变组件当前的 state,所以可以把更改 state 的逻辑写在各自的组件里,但这样做的问题在于,当项目逻辑变得越来越复杂的时候,将很难理清 state 跟 view 之间的对应关系(一个 state 的变化可能引起多个 view 的变化,一个 view 上面触发的事件可能引起多个 state 的改变)。我们需要对所有引起 state 变化的情况进行统一管理,于是就有了 Flux。
Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构是同一类东西,但是更加简单和清晰。Flux存在多种实现(至少15种)
Flux将一个应用分成四个部分。
- View: 视图层
- Action(动作):视图层发出的消息(比如mouseClick)
- Dispatcher(派发器):用来接收Actions、执行回调函数
- Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
Flux最大的特点就是数据单向流动:
- 用户访问 View
- View 发出用户的 Action
- Dispatcher 收到 Action,要求 Store 进行相应的更新
- Store 更新后,发出一个"change"事件
- View 收到"change"事件后,更新页面
Redux:
Redux由Flux演变而来,但受 Elm 的启发,避开了 Flux 的复杂性
不同于Flux架构,Redux中没有dispatcher这个概念,并且Redux设想你永远不会变动你的数据,你应该在reducer中返回新的对象来作为应用的新状态。但是它们都可以用(state, action) => newState来表述其核心思想,所以Redux可以被看成是Flux思想的一种实现,但是在细节上会有一些差异。
看下redux的数据流:
- 创建action
action其实就是一个普通的对象,只是对行为的抽象描述,需要指定action的类型(相当于方法名),和携带的数据(相当于参数)
{
type: INCREMENT, //该动作的抽象描述
number, // 该动作携带的数据
}
- 创建reducer函数
reducer作为整个Redux中action的处理中枢,接收state与action并对此修改数据,返回应用的下一个状态。 主要的动作都是在这里。state是不可修改的,所以通过assign归并我们对数据的操作,返回的是state副本修改后的对象,并非直接修改了输入的state。
function countReducer(state, action) {
switch (action.type) {
case INCREMENT:
return Object.assign({}, {
counter: state.counter + action.number,
});
case DECREMENT:
return Object.assign({}, {
counter: state.counter - action.number,
});
default: return state;
}
}
- 创建唯一store
通过Redux中的createStore方法传入reducer函数来创建整个应用的store。
const store = createStore(countReducer);
- 发起action
通过store的dispatch方法来发起一个action。
store.dispatch(incrementCreator(5));
store.dispatch(decrementCreator(4));
简单来讲:webpack会处理你的文件(不一定是js)以及依赖的文件,并将这些打包成一个js。
这样打包的好处是,组件化。可以将你一个组件(譬如一个button)所需要的css,html,js,图片等,合并到一个js中,这样就可以单独使用这个完整的组件。
webpack可以类比为java中的maven,它在处理每个文件时,可以为每个文件定义处理使用的loader。loader的作用是可以将其他非js文件或者js还不支持的语法,转换成目前浏览器支持的语法。这个功能,弱化了js这个有缺陷的语言在开发中的必要性。为前端技术提供了多样化的选择性,你可以随意选择es6语法,jsx,TypeScript等。这样看来webpack的重要性,要大于js语言本身的重要性。
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
function f(x){
return g(x);
}
尾调用不一定出现在函数尾部,只要是最后一步操作即可。
尾递归, 函数调用自身,称为递归。如果尾调用自身,就称为尾递归
- 柯里化:把接受多个参数的函数变换成接受一个单一参数的函数,并且返回(接受余下的参数而且返回结果的)新函数的技术,柯里化后,将第一个参数变量存在函数里面了(闭包),然后本来需要n个参数的函数可以变成只需要剩下的(n - 1个)参数就可以调用