-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.json
1 lines (1 loc) · 260 KB
/
index.json
1
[{"content":"\r\n### 修改object中某项\r\n```js\r\nthis.setState({\r\n object: {...object, key: value}\r\n});\r\n```\r\n\r\n### 删除数组首位\r\n```js\r\narray.splice(0, 1);\r\nthis.setState({\r\n array\r\n});\r\n```\r\n\r\n### 删除数组尾部\r\n```js\r\narray.splice(array.length - 1);\r\nthis.setState({\r\n array\r\n});\r\n```\r\n\r\n### 删除数组任意一项\r\n```js\r\narray.splice(index, 1);\r\nthis.setState({\r\n array\r\n});\r\n```\r\n\r\n### 数组尾部添加一项\r\n```js\r\nthis.setState({\r\n array: [...array, item]\r\n});\r\n```\r\n\r\n### 数组头部添加一项\r\n```js\r\nthis.setState({\r\n array: [item, ...array]\r\n});\r\n```\r\n\r\n### 数组任意位置添加一项\r\n```js\r\narray.splice(index, 0, item);\r\nthis.setState({\r\n array\r\n});\r\n```\r\n\r\n### 修改数组中任意一项中值\r\n```js\r\nfunction updateArrayItem(index, key, value) {\r\n this.setState({\r\n array: array.map((item, _index) =\u003e _index == index ? {...item, [key]: value} : item)\r\n });\r\n}\r\n```\r\n\r\n### 复杂类型修改\r\n```js\r\nthis.setState(prevState =\u003e return newState);\r\n```","cover":"","link":"react-setstate.html","preview":"\u003cp\u003eState用来设置会变换的数据。State相当重要,所有的UI界面变化都离不开State。\u003c/p\u003e\n","title":"React setState 数组、对象多种方式"},{"content":"\r\n若使用微信开发者工具添加项目进行预览,添加的路径为项目根目录下的 dist 文件夹。由于 Taro 编译后的代码已经经过了转义和压缩,因此还需要在``设置-项目设置``关闭以下设置。\r\n\r\n- 关闭ES6转ES5功能\r\n- 关闭上传代码时样式自动补全\r\n- 关闭代码压缩上传\r\n\r\n### Taro 与 React 的差异\r\n由于微信小程序的限制,React 中某些写法和特性在 Taro 中还未能实现,后续将会逐渐完善。\r\n\r\n``暂不支持在 render() 之外的方法定义 JSX``\r\n\r\n由于微信小程序的 template 不能动态传值和传入函数,Taro 暂时也没办法支持在类方法中定义 JSX。\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\nclass App extends Component {\r\n _render() {\r\n return \u003cView /\u003e\r\n }\r\n}\r\n\r\nclass App extends Component {\r\n renderHeader(showHeader) {\r\n return showHeader \u0026\u0026 \u003cHeader /\u003e\r\n }\r\n}\r\n\r\nclass App extends Component {\r\n renderHeader = (showHeader) =\u003e {\r\n return showHeader\u0026 \u0026 \u003cHeader /\u003e\r\n }\r\n}\r\n```\r\n\r\n- 解决方案\r\n\r\n在 render 方法中定义。\r\n\r\n```js\r\nclass App extends Component {\r\n\r\n render () {\r\n const { showHeader, showMain } = this.state\r\n const header = showHeader \u0026\u0026 \u003cHeader /\u003e\r\n const main = showMain \u0026\u0026 \u003cMain /\u003e\r\n return (\r\n \u003cView\u003e\r\n {header}\r\n {main}\r\n \u003c/View\u003e\r\n )\r\n }\r\n}\r\n```\r\n\r\n### 不能在包含 JSX 元素的 map 循环中使用 if 表达式\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\nproducts.map((number) =\u003e {\r\n let element = null\r\n const isOdd = number % 2\r\n if (isOdd) {\r\n element = \u003cCustom /\u003e\r\n }\r\n return element\r\n})\r\n```\r\n\r\n以下代码不会被警告,在 Taro 任意端中能够运行:\r\n\r\n```js\r\nproducts.map((number) =\u003e {\r\n let isOdd = false\r\n if (number % 2) {\r\n isOdd = true\r\n }\r\n return isOdd \u0026\u0026 \u003cCustom /\u003e\r\n})\r\n```\r\n\r\n- 解决方案\r\n\r\n尽量在 map 循环中使用条件表达式或逻辑表达式。\r\n\r\n```js\r\nproducts.map((number) =\u003e {\r\n const isOdd = number % 2\r\n return isOdd ? \u003cCustom /\u003e : null\r\n})\r\n\r\nproducts.map((number) =\u003e {\r\n const isOdd = number % 2\r\n return isOdd \u0026\u0026 \u003cCustom /\u003e\r\n})\r\n```\r\n\r\n### 不能使用 Array#map 之外的方法操作 JSX 数组\r\nTaro 在小程序端实际上把 JSX 转换成了字符串模板,而一个原生 JSX 表达式实际上是一个 React/Nerv 元素(react-element)的构造器,因此在原生 JSX 中你可以随意地一组 React 元素进行操作。但在 Taro 中你只能使用 map 方法,Taro 转换成小程序中 wx:for。\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\ntest.push(\u003cView /\u003e)\r\n\r\nnumbers.forEach(numbers =\u003e {\r\n if (someCase) {\r\n a = \u003cView /\u003e\r\n }\r\n})\r\n\r\ntest.shift(\u003cView /\u003e)\r\n\r\ncomponents.find(component =\u003e {\r\n return component === \u003cView /\u003e\r\n})\r\n\r\ncomponents.some(component =\u003e component.constructor.__proto__ === \u003cView /\u003e.constructor)\r\n```\r\n\r\n以下代码不会被警告,在 Taro 任意端中能够运行:\r\n\r\n```js\r\nnumbers.filter(Boolean).map((number) =\u003e {\r\n const element = \u003cView /\u003e\r\n return \u003cView /\u003e\r\n})\r\n```\r\n\r\n- 解决方案\r\n\r\n先处理好需要遍历的数组,然后再用处理好的数组调用 map 方法。\r\n\r\n```js\r\nnumbers.filter(isOdd).map((number) =\u003e \u003cView /\u003e)\r\n\r\nfor (let index = 0; index \u003c array.length; index++) {\r\n // blah blah\t\r\n}\r\n\r\nconst element = array.map(item =\u003e {\r\n return \u003cView /\u003e\r\n})\r\n```\r\n\r\n### 不能在 JSX 参数中使用匿名函数\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\n\u003cView onClick={() =\u003e this.handleClick()} /\u003e\r\n\u003cView onClick={(e) =\u003e this.handleClick(e)} /\u003e\r\n\u003cView onClick={() =\u003e ({})} /\u003e\r\n\u003cView onClick={function () {}} /\u003e\r\n\u003cView onClick={function (e) {this.handleClick(e)}} /\u003e\r\n```\r\n\r\n以下代码不会被警告,在 Taro 任意端中能够运行:\r\n\r\n```js\r\n\u003cView onClick={this.hanldeClick} /\u003e\r\n\u003cView onClick={this.props.hanldeClick} /\u003e\r\n\u003cView onClick={this.hanldeClick.bind(this)} /\u003e\r\n\u003cView onClick={this.props.hanldeClick.bind(this)} /\u003e\r\n```\r\n\r\n- 解决方案\r\n\r\n使用 bind 或 类参数绑定函数。\r\n\r\n```js\r\n\u003cView onClick={this.props.hanldeClick.bind(this)} /\u003e\r\n```\r\n\r\n### 不能在 JSX 参数中使用对象展开符\r\n微信小程序组件要求每一个传入组件的参数都必须预先设定好,而对象展开符则是动态传入不固定数量的参数。所以 Taro 没有办法支持该功能。\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\n\u003cView {...this.props} /\u003e\r\n\u003cView {...props} /\u003e\r\n\u003cCustom {...props} /\u003e\r\n```\r\n\r\n以下代码不会被警告,在 Taro 任意端中能够运行:\r\n\r\n```js\r\nconst { id, ...rest } = obj\r\nconst [ head, ...tail] = array\r\nconst obj = { id, ...rest }\r\n```\r\n\r\n- 解决方案\r\n\r\n开发者自行赋值\r\n\r\n```js\r\nrender () {\r\n const { id, title } = obj\r\n return \u003cView id={id} title={title} /\u003e\r\n}\r\n```\r\n\r\n### 不允许在 JSX 参数(props)中传入 JSX 元素\r\n由于微信小程序内置的组件化的系统不能通过属性(props) 传函数,而 props 传递函数可以说 React 体系的根基之一,我们只能自己实现了一套组件化系统。而自制的组件化系统则不能使用内置组件化的 slot 功能。两权相害取其轻,我们暂时只能不支持该功能。\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\n\u003cCustom child={\u003cView /\u003e} /\u003e\r\n\u003cCustom child={() =\u003e \u003cView /\u003e} /\u003e\r\n\u003cCustom child={function () { \u003cView /\u003e }} /\u003e\r\n\u003cCustom child={ary.map(a =\u003e \u003cView /\u003e)} /\u003e\r\n```\r\n\r\n- 解决方案\r\n\r\n通过 props 传值在 JSX 模板中预先判定显示内容,或通过 props.children 来嵌套子组件\r\n\r\n### 不支持无状态组件(stateless component)\r\n由于微信的 template 能力有限,不支持动态传值和函数,Taro 暂时只支持一个文件只定义一个组件。为了避免开发者疑惑,暂时不支持定义 stateless component。\r\n\r\n- 规则详情\r\n\r\n以下代码会被 ESLint 提示警告,同时在 Taro(小程序端)也不会有效:\r\n\r\n```js\r\nfunction Test () {\r\n return \u003cView /\u003e\r\n}\r\n\r\nfunction Test (ary) {\r\n return ary.map(() =\u003e \u003cView /\u003e)\r\n}\r\n\r\nconst Test = () =\u003e {\r\n return \u003cView /\u003e\r\n}\r\n\r\nconst Test = function () {\r\n return \u003cView /\u003e\r\n}\r\n```\r\n以下代码不会被警告,在 Taro 任意端中能够运行:\r\n\r\n```js\r\nclass App extends Component {\r\n render () {\r\n return (\r\n \u003cView /\u003e\r\n )\r\n }\r\n}\r\n```\r\n- 解决方案\r\n\r\n使用 class 定义组件。","cover":"","link":"taro-tips.html","preview":"\u003cp\u003e成功安装 Taro 后,进行开发前,我们有必要了解一下 Taro 的一些注意事项避免踩坑。\u003c/p\u003e\n","title":"taro奥特曼变身前指北"},{"content":"\r\n## WXML\r\n```html\r\n \u003c!-- 屏幕背景变暗的背景 --\u003e\r\n \u003cview class=\"dialog_screen\" bindtap=\"hideModal\" wx:if=\"{{showModalStatus}}\"\u003e\u003c/view\u003e\r\n \u003c!--弹出框 --\u003e\r\n \u003cview animation=\"{{animationData}}\" class=\"dialog_box\" wx:if=\"{{showModalStatus}}\"\u003e\r\n xxxxxxxxxxxx //这个写弹出的内容\r\n \u003c/view\u003e\r\n```\r\n\r\n## JS\r\n```js\r\n//显示对话框\r\n showModal:() =\u003e {\r\n let animation = wx.createAnimation({\r\n duration: 150,\r\n timingFunction: \"linear\",\r\n delay: 0\r\n })\r\n this.animation = animation\r\n animation.translateY(300).step()\r\n this.setData({\r\n animationData: animation.export(),\r\n showModalStatus: true\r\n })\r\n setTimeout(() =\u003e {\r\n animation.translateY(0).step()\r\n this.setData({\r\n animationData: animation.export()\r\n })\r\n },150)\r\n },\r\n //隐藏对话框\r\n hideModal:() =\u003e {\r\n let animation = wx.createAnimation({\r\n duration: 150,\r\n timingFunction: \"linear\",\r\n delay: 0\r\n })\r\n this.animation = animation\r\n animation.translateY(300).step()\r\n this.setData({\r\n animationData: animation.export(),\r\n })\r\n setTimeout(() =\u003e {\r\n animation.translateY(0).step()\r\n this.setData({\r\n animationData: animation.export(),\r\n showModalStatus: false\r\n })\r\n },150)\r\n }\r\n```\r\n\r\n## CSS\r\n```css\r\n/* 使屏幕变暗 */\r\n.dialog_screen {\r\n width: 100%;\r\n height: 100%;\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n background: #000;\r\n opacity: 0.78;\r\n overflow: hidden;\r\n z-index: 98;\r\n color: #fff;\r\n}\r\n/* 对话框 */\r\n.dialog_box {\r\n width: 100%;\r\n overflow: hidden;\r\n position: fixed;\r\n bottom: 0;\r\n left: 0;\r\n z-index: 99;\r\n background: #fff;\r\n padding-top: 20rpx;\r\n}\r\n```","cover":"","link":"weapp-modal.html","preview":"\u003cp\u003e电商项目中商品详情页,加入购物车或者下单时可以选择商品属性的弹出框,通过设置view的平移动画,达到从底部弹出的样式。\u003c/p\u003e\n","title":"微信小程序商品详情页的底部弹出框"},{"content":"\r\n```js\r\nimport axios from 'axios';\r\n// req拦截\r\naxios.interceptors.request.use(\r\n let token = localStorage.getItem('token')\r\n config =\u003e {\r\n if (token === null) { // 判断是否存在token,如果存在的话,则每个http header都加上token\r\n config.headers.Authorization = `${token}`;\r\n }\r\n return config;\r\n },\r\n err =\u003e {\r\n return Promise.reject(err);\r\n });\r\n\r\n// res拦截\r\naxios.interceptors.response.use(\r\n response =\u003e {\r\n return response;\r\n },\r\n error =\u003e {\r\n if (error.response.code === 401) {\r\n // 返回 401 跳转到登录页面\r\n router.replace({\r\n path: 'login',\r\n query: {redirect: router.currentRoute.fullPath}\r\n })\r\n }\r\n return Promise.reject(error.response.msg) // 返回接口返回的错误信息\r\n });\r\n```\r\nPS: 建议把拦截器独立到一个js文件,然后在引入。\r\n","cover":"","link":"axios-intercterps.html","preview":"\u003cp\u003e因项目每次都要带token进行操作, 这时候可以用axios的http拦截, 每次路由跳转, 都先让后台验证一下token是否有效, 在http头添加token,\u003c/p\u003e\n","title":"axios token验证拦截器"},{"content":"\r\n#### 流程步骤\r\n* 用户同意,获取code。\r\n* 通过code获取网页授权access_token.\r\n* 获取用户信息。\r\n\r\n#### 开始搞事情 :p\r\n```js\r\nimport Koa from 'koa'\r\nimport path from 'path'\r\nimport route from 'koa-route'\r\nimport static from 'koa-static'\r\nimport keyBody from 'koa-body'\r\n\r\nconst app = new Koa()\r\n\r\n// 路由\r\nimport oauth from './routes/accredit/oauth'\r\nimport token from './routes/accredit/token'\r\nconst rootPath = path.join(__dirname + '/View')\r\nconst _static = static(rootPath)\r\n// 中间件\r\nconst logger = async(ctx, next) =\u003e {\r\n const rt_start = Date.now()\r\n await next()\r\n const rt_end = Date.now()\r\n ctx.set('X-Response-Time', `${rt_end - rt_start}ms`);\r\n console.log(ctx.request.method, ctx.url, `${rt_end - rt_start}ms`)\r\n}\r\n\r\napp.use(_static) // 静态资源\r\napp.use(keyBody()) // req body数据获取 (非参数序列化)\r\napp.use(logger) // 日志\r\n\r\n// page route\r\napp.use(route.get('/oauth', oauth)); //授权\r\napp.use(route.get('/token', token)); //获取openid\r\n\r\napp.listen(8088, (err) =\u003e {\r\n if (err) { console.error(err) }\r\n console.log('Listening At:', 8088)\r\n}\r\n```\r\n\r\n#### 1.在APP中访问oauth获取code\r\n```js\r\nimport config from './../config'\r\nimport request from 'request'\r\n/* 微信网页授权 */\r\nconst oauth = async(ctx, next) =\u003e {\r\n const { request: req, response: res } = ctx; \r\n var AppID = config.AppID;\r\n var AppSecret = config.AppSecret;\r\n // 第一步:用户同意授权,获取code\r\n var Router = 'jy';\r\n // 这是编码后的地址\r\n var return_uri = config.return_uri + Router;\r\n var scope = 'snsapi_base';\r\n // snsapi_userinfo可以获取用户信息与token与openid\r\n // snsapi_base只能获取到token与openid\r\n res.redirect('https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + AppID + '\u0026redirect_uri=' + return_uri + '\u0026response_type=code\u0026scope=' + scope + '\u0026state=123456#wechat_redirect');\r\n}\r\nmodule.exports = { oauth };\r\n```\r\nconfig.js里面写好以下配置参数\r\n- AppID\r\n- AppSecret\r\n\r\n#### 2、在客户端访问 tocken,tongguo code获取access_tocken\r\n```js\r\nimport config from './../config'\r\nimport request from 'request'\r\nimport axios from 'axios'\r\n\r\nconst token = async(ctx, next) =\u003e {\r\n const { request: req, response: res } = ctx\r\n var code = req.header.referer.match(new RegExp(\"[\\?\\\u0026]\" + 'code' + \"=([^\\\u0026]+)\", \"i\"))[1];\r\n var AppID = config.AppID;\r\n var AppSecret = config.AppSecret;\r\n var result = await request.get({\r\n url: 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' + AppID + '\u0026secret=' + AppSecret + '\u0026code=' + code + '\u0026grant_type=authorization_code',\r\n },\r\n function(error, response, body) {\r\n if (response.statusCode == 200) {\r\n // 第三步:拉取用户信息(需scope为 snsapi_userinfo)\r\n // console.log(JSON.parse(body));\r\n var data = JSON.parse(body);\r\n var access_token = data.access_token;\r\n var openid = data.openid;\r\n } else {\r\n console.log(response.statusCode);\r\n }\r\n }\r\n );\r\n ctx.type = 'json';\r\n ctx.body = result;\r\n}\r\n\r\nmodule.exports = { token }\r\n```\r\n我这里只需要获取到openid即可,所以在这里就已经返回result。\r\n","cover":"","link":"node-koa-openid.html","preview":"\u003cp\u003e因项目是node+koa, 然后需要获取openid, 没办法, 只能搞事情了.\u003c/p\u003e\n","title":"node+koa获取微信授权拿到openid"},{"content":"\r\n路由是用于描述 `URL` 与处理函数之间的对应关系的。比如用户访问 `http://localhost:3000/`,那么浏览器就会显示 `index` 页面的内容,如果用户访问的是 `http://localhost:3000/home`,那么浏览器应该显示 `home` 页面的内容。\r\n\r\n\r\n要实现上述功能,如果不借助 `koa-router` 或者其他路由中间件,我们自己去处理路由,那么写法可能如下所示:\r\n\r\n```js\r\nimport Koa from 'koa';\r\nconst app = new Koa();\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n if (ctx.request.path === '/') {\r\n ctx.response.body = '\u003ch1\u003eindex page\u003c/h1\u003e';\r\n } else {\r\n await next();\r\n }\r\n});\r\napp.use(async (ctx, next) =\u003e {\r\n if (ctx.request.path === '/home') {\r\n ctx.response.body = '\u003ch1\u003ehome page\u003c/h1\u003e';\r\n } else {\r\n await next();\r\n }\r\n});\r\napp.use(async (ctx, next) =\u003e {\r\n if (ctx.request.path === '/404') {\r\n ctx.response.body = '\u003ch1\u003e404 Not Found\u003c/h1\u003e';\r\n } else {\r\n await next();\r\n }\r\n});\r\n\r\napp.listen(3000, ()=\u003e{\r\n console.log('server is running at http://localhost:3000')\r\n})\r\n```\r\n\r\n\r\n把上述代码复制并覆盖到 `app.js` 中,然后执行以下命令启动 `node` 程序:\r\n\r\n```js\r\nnode app.js\r\n```\r\n\r\n\r\n启动之后在浏览器中分别访问 `http://localhost:3000/`、`http://localhost:3000/home`、`http://localhost:3000/404` 就能看到相应的页面了。\r\n\r\n\r\n上述 `app.js` 的代码中,由 `async` 标记的函数称为『异步函数』,在异步函数中,可以用 `await` 调用另一个异步函数,`async` 和 `await` 这两个关键字将在 ES7 中引入。参数 `ctx` 是由 `koa2` 传入的,我们可以通过它来访问 `request` 和 `response`,`next` 是 `koa2` 传入的将要处理的下一个异步函数。\r\n\r\n**注意:** 由于 `node` 在 `v7.6.0` 中才支持 `async` 和 `await`,所以在运行 `app.js` 之前请确保 node 版本正确,或者使用一些第三方的 `async` 库来支持。\r\n\r\n\r\n这样的写法能够处理简单的应用,但是,一旦要处理的 `URL` 多起来的话就会显得特别笨重。所以我们可以借助 `koa-router` 来更简单的实现这一功能。\r\n下面来介绍一下如何正确的使用 `koa-router`。\r\n\r\n\r\n## 安装 koa-router\r\n\r\n通过 `npm` 命令直接安装:\r\n\r\n```\r\nnpm i koa-router -S\r\n```\r\n\r\n`-S` 或者 `--save` 是为了安装完成之后能够在 `package.json` 的 `dependencies` 中保留 `koa-router`,以便于下次只需要执行 `npm i` 或者 `npm install` 就能够安装所有需要的依赖包。\r\n\r\n\r\n## 基本使用方法\r\n\r\n如果要在 `app.js` 中使用 `koa-router` 来处理 `URL`,可以通过以下代码来实现:\r\n\r\n```js\r\nimport Koa from 'koa'\r\nconst router = require('koa-router')() // 注意: 返回的是函数\r\nconst app = new Koa()\r\n\r\n // 添加路由\r\n router.get('/', async (ctx, next) =\u003e {\r\n ctx.response.body = `\u003ch1\u003eindex page\u003c/h1\u003e`\r\n})\r\n\r\nrouter.get('/home', async (ctx, next) =\u003e {\r\n ctx.response.body = '\u003ch1\u003eHOME page\u003c/h1\u003e'\r\n})\r\n\r\nrouter.get('/404', async (ctx, next) =\u003e {\r\n ctx.response.body = '\u003ch1\u003e404 Not Found\u003c/h1\u003e'\r\n})\r\n\r\n // 调用路由中间件\r\n app.use(router.routes())\r\n\r\napp.listen(3000, ()=\u003e{\r\n console.log('server is running at http://localhost:3000')\r\n})\r\n```\r\n\r\n\r\n运行 `app.js`:\r\n\r\n```js\r\nnode app.js\r\n```\r\n\r\n\r\n执行完上面的操作之后,我们在浏览器中访问 `http://localhost:3000/`:\r\n\r\n![](https://joe-10005639.cossh.myqcloud.com/blog/index.png)\r\n\r\n\r\n在浏览器中访问 `http://localhost:3000/home`:\r\n\r\n![](https://joe-10005639.cossh.myqcloud.com/blog/home.png)\r\n\r\n\r\n在浏览器中访问 `http://localhost:3000/404`:\r\n\r\n![](https://joe-10005639.cossh.myqcloud.com/blog/404.png)\r\n\r\n\r\n通过上面的例子,我们可以看到和之前不使用 `koa-router` 的显示效果是一样的。不过使用了 `koa-router` 之后,代码稍微简化了一些,而且少了 `if` 判断,还有省略了 `await next()`(因为没有其他中间件需要执行,所以这里就先省略了)。\r\n\r\n\r\n当然,除了 `GET` 方法,`koa-router` 也支持处理其他的请求方法,比如:\r\n\r\n```js\r\nrouter\r\n .get('/', async (ctx, next) =\u003e {\r\n ctx.body = 'Hello World!';\r\n })\r\n .post('/users', async (ctx, next) =\u003e {\r\n // ...\r\n })\r\n .put('/users/:id', async (ctx, next) =\u003e {\r\n // ...\r\n })\r\n .del('/users/:id', async (ctx, next) =\u003e {\r\n // ...\r\n })\r\n .all('/users/:id', async (ctx, next) =\u003e {\r\n // ...\r\n });\r\n```\r\n\r\n\r\n在任意http请求中,遵从 `RESTful` 规范,可以把 `GET`、`POST`、`PUT`、`DELETE` 类型的请求分别对应 `查`,`增`,`改`,`删`,这里 `router` 的方法也一一对应。通常我们使用 `GET` 来查询和获取数据,使用 `POST` 来更新资源。`PUT` 和 `DELETE` 使用比较少,但是如果你们团队采用 `RESTful架构`,就比较推荐使用了。我们注意到,上述代码中还有一个`all` 方法。`all` 方法通常用于匹配一组路由或者全部路由从而做一些统一设置和处理,也可以处理不确定客户端发送的请求方法类型的情况。\r\n\r\n\r\n举个例子,假设客户端使用 `jQuery` 来开发,有如下几个 `ajax` 请求:\r\n\r\n```js\r\n// 优先匹配和 router.get 方法中 url 规则一样的请求,如果匹配不到的话就匹配和 router.all 方法中 url 规则一样的请求。\r\n$.ajax({\r\n method: \"GET\",\r\n url: \"www.xxx.com\",\r\n data: { name: \"John\" }\r\n}).done(function( msg ) {\r\n // do something\r\n});\r\n\r\n// 优先匹配和 router.post 方法中 url 规则一样的请求,如果匹配不到的话就匹配和 router.all 方法中 url 规则一样的请求。\r\n$.ajax({\r\n method: \"POST\",\r\n url: \"www.xxx.com\",\r\n data: { name: \"John\" }\r\n}).done(function( msg ) {\r\n // do something\r\n});\r\n```\r\n\r\n\r\n上面例子中两个方法最主要的区别就是 `ajax` 中 `method` 的值,`method` 的值和 `router` 的方法一一对应。\r\n\u003cbr/\u003e\r\n再举一个使用`all`方法的例子,假设我们要为所有请求设置跨域头,可以通过如下代码实现:\r\n\r\n```js\r\nrouter.all('/*', async (ctx, next) =\u003e {\r\n // *代表允许来自所有域名请求\r\n ctx.set(\"Access-Control-Allow-Origin\", \"*\");\r\n // 其他一些设置...\r\n await next();\r\n});\r\n```\r\n这段代码表示对于所有请求,允许来自所有域名。这是一种很危险的做法,在真实项目中一定不能这么做。\r\n\r\n\r\n`*` 号是一种通配符,表示匹配任意 `URL`。这里的返回是一种简化的写法,真实开发中,我们肯定要去读取 `HTML` 文件或者其他模板文件的内容,再响应请求。关于这部分的内容后面的章节中会详细介绍。\r\n\r\n另外,如果一条路由在`all`方法和其他方法中同时命中,只有执行了`await next()`,那么这条路由会在`all`方法和其他方法中都会起作用,举个例子,看如下代码:\r\n\r\n```js\r\nimport Koa from 'koa'\r\nconst router = require('koa-router')()\r\nconst app = new Koa()\r\n\r\n// 添加路由\r\nrouter.get('/', async (ctx, next) =\u003e {\r\n ctx.response.body = `\u003ch1\u003eindex page\u003c/h1\u003e`\r\n await next();\r\n})\r\nrouter.all('/', async (ctx, next) =\u003e {\r\n console.log('match \"all\" method')\r\n await next();\r\n});\r\n// 调用路由中间件\r\napp.use(router.routes())\r\n\r\napp.listen(3000, ()=\u003e{\r\n console.log('server is running at http://localhost:3000')\r\n})\r\n```\r\n执行这段代码,我们不仅能够访问`http://localhost:3000`看到`“index page”`,也能够在控制台中看到`“'match \"all\" method'”`,说明路由\"/\"不仅执行了`get`方法的回调,也执行了`all`方法的回调函数。但是,如果我们把`get`方法中的`await next()`去掉,那么就不会命中`all`方法的路由规则,也不会执行`all`方法的回调函数了。因为说到底,对路由的处理也是一种中间件,如果不执行`await next()`把控制权交给下一个中间件,那么后面的路由就不会再执行了。\r\n## 其他特性\r\n\r\n\r\n### 命名路由\r\n\r\n在开发过程中我们能够很方便的生成路由 `URL`:\r\n\r\n```js\r\nrouter.get('user', '/users/:id', function (ctx, next) {\r\n // ...\r\n});\r\n\r\nrouter.url('user', 3);\r\n// =\u003e 生成路由 \"/users/3\"\r\n\r\nrouter.url('user', { id: 3 });\r\n// =\u003e 生成路由 \"/users/3\"\r\n\r\nrouter.use(function (ctx, next) {\r\n // 重定向到路由名称为 “sign-in” 的页面\r\n ctx.redirect(ctx.router.url('sign-in'));\r\n})\r\n```\r\n\r\n`router.url` 方法方便我们在代码中根据路由名称和参数(可选)去生成具体的 `URL`,而不用采用字符串拼接的方式去生成 `URL` 了。\r\n\r\n\r\n### 多中间件\r\n\r\n`koa-router` 也支持单个路由多中间件的处理。通过这个特性,我们能够为一个路由添加特殊的中间件处理。也可以把一个路由要做的事情拆分成多个步骤去实现,当路由处理函数中有异步操作时,这种写法的可读性和可维护性更高。比如下面的示例代码所示:\r\n\r\n```js\r\nrouter.get(\r\n '/users/:id',\r\n function (ctx, next) {\r\n return User.findOne(ctx.params.id).then(function(user) {\r\n // 首先读取用户的信息,异步操作\r\n ctx.user = user;\r\n next();\r\n });\r\n },\r\n function (ctx) {\r\n console.log(ctx.user);\r\n // 在这个中间件中再对用户信息做一些处理\r\n // =\u003e { id: 17, name: \"Alex\" }\r\n }\r\n);\r\n```\r\n\r\n\r\n### 嵌套路由\r\n\r\n我们可以在应用中定义多个路由,然后把这些路由组合起来用,这样便于我们管理多个路由,也简化了路由的写法。\r\n\r\n```js\r\nlet forums = new Router();\r\nlet posts = new Router();\r\n\r\nposts.get('/', function (ctx, next) {...});\r\nposts.get('/:pid', function (ctx, next) {...});\r\nforums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());\r\n\r\n// 可以匹配到的路由为 \"/forums/123/posts\" 或者 \"/forums/123/posts/123\"\r\napp.use(forums.routes());\r\n```\r\n\r\n\r\n### 路由前缀\r\n\r\n通过 `prefix` 这个参数,我们可以为一组路由添加统一的前缀,和嵌套路由类似,也方便我们管理路由和简化路由的写法。不同的是,前缀是一个固定的字符串,不能添加动态参数。\r\n\r\n```js\r\nlet router = new Router({\r\n prefix: '/users'\r\n});\r\n\r\nrouter.get('/', ...); // 匹配路由 \"/users\"\r\nrouter.get('/:id', ...); // 匹配路由 \"/users/:id\"\r\n```\r\n\r\n\r\n### URL 参数\r\n\r\n`koa-router` 也支持参数,参数会被添加到 `ctx.params` 中。参数可以是一个正则表达式,这个功能的实现是通过 `path-to-regexp` 来实现的。原理是把 `URL` 字符串转化成正则对象,然后再进行正则匹配,之前的例子中的 `*` 通配符就是一种正则表达式。\r\n\r\n```js\r\nrouter.get('/:category/:title', function (ctx, next) {\r\n console.log(ctx.params);\r\n // =\u003e { category: 'programming', title: 'how-to-node' }\r\n});\r\n```\r\n\r\n通过上面的例子可以看出,我们可以通过 `ctx.params` 去访问路由中的参数,使得我们能够对参数做一些处理后再执行后续的代码。\r\n","cover":"","link":"node-note3.html","preview":"\u003cp\u003eKoa2 \u0026ndash; 基于 Node.js 平台的下一代 web 开发框架。路由 koa-router 。\u003c/p\u003e\n","title":"node.js学习笔记(3) - Koa2路由"},{"content":"\r\n# middleware(中间件)\r\n\u003e 正是因为中间件的扩展性才使得 `Koa2`的代码简单灵活。\r\n\r\n\r\n在 `app.js` 中,有这样一段代码:\r\n\r\n```js\r\napp.use(async (ctx, next)=\u003e{\r\n await next()\r\n ctx.response.type = 'text/html'\r\n ctx.response.body = '\u003ch1\u003eHello World\u003c/h1\u003e' \r\n})\r\n```\r\n\r\n它的作用是:每收到一个 `http` 请求,`Koa2`都会调用通过 `app.use()` 注册的 `async` 函数,同时为该函数传入 `ctx` 和 `next` 两个参数。而这个 `async` 函数就是我们所说的中间件。\r\n\r\n下面我们简单介绍一下传入中间件的两个参数。\r\n\r\n\u003cbr\u003e\r\n\r\n## ctx\r\n\r\n`ctx` 作为上下文使用,包含了基本的 `ctx.request` 和 `ctx.response`。另外,还对 `Koa2`内部对一些常用的属性或者方法做了代理操作,使得我们可以直接通过 `ctx` 获取。比如,`ctx.request.url` 可以写成 `ctx.url`。\r\n\r\n\r\n除此之外,`Koa2`还约定了一个中间件的存储空间 `ctx.state`。通过 `state` 可以存储一些数据,比如用户数据,版本信息等。如果你使用 `webpack` 打包的话,可以使用中间件,将加载资源的方法作为 `ctx.state` 的属性传入到 `view` 层,方便获取资源路径。\r\n\r\n\r\n## next\r\n\r\n\r\n`next` 参数的作用是将处理的控制权转交给下一个中间件,而 `next()` 后面的代码,将会在下一个中间件及后面的中间件(如果有的话)执行结束后再执行。\r\n\r\n**注意:** 中间件的顺序很重要!\r\n\r\n\r\n我们重写 `app.js` 来解释下中间件的流转过程:\r\n\r\n```js\r\nimport Koa from 'koa'\r\nconst app = new Koa()\r\n\r\n// 记录执行的时间\r\napp.use(async (ctx, next) =\u003e {\r\n let stime = new Date().getTime()\r\n await next()\r\n let etime = new Date().getTime()\r\n ctx.response.type = 'text/html'\r\n ctx.response.body = '\u003ch1\u003eHello World\u003c/h1\u003e'\r\n console.log(`请求地址: ${ctx.path},响应时间:${etime - stime}ms`)\r\n});\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n console.log('中间件1 doSoming')\r\n await next();\r\n console.log('中间件1 end')\r\n})\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n console.log('中间件2 doSoming')\r\n await next();\r\n console.log('中间件2 end')\r\n})\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n console.log('中间件3 doSoming')\r\n await next();\r\n console.log('中间件3 end')\r\n})\r\n\r\napp.listen(3000, () =\u003e {\r\n console.log('server is running at http://localhost:3000')\r\n})\r\n```\r\n\r\n\u003cbr\u003e\r\n\r\n运行起来后,控制台显示:\r\n\r\n```txt\r\nserver is running at http://localhost:3000\r\n```\r\n\r\n\r\n然后打开浏览器,访问 `http://localhost:3000`,控制台显示内容更新为:\r\n\r\n```txt\r\nserver is running at http://localhost:3000\r\n中间件1 doSoming\r\n中间件2 doSoming\r\n中间件3 doSoming\r\n中间件3 end\r\n中间件2 end\r\n中间件1 end\r\n请求地址: /,响应时间:2ms\r\n```\r\n\r\n从结果上可以看到,流程是一层层的打开,然后一层层的闭合,像是剥洋葱一样 —— 洋葱模型。\r\n\r\n\r\n此外,如果一个中间件没有调用 `await next()`,会怎样呢?答案是『后面的中间件将不会执行』。\r\n\r\n\r\n修改 `app.js` 如下,我们去掉了第三个中间件里面的 `await`:\r\n\r\n```js\r\nimport Koa from 'koa'\r\nconst app = new Koa()\r\n\r\n// 记录执行的时间\r\napp.use(async (ctx, next)=\u003e{\r\n let stime = new Date().getTime()\r\n await next()\r\n let etime = new Date().getTime()\r\n ctx.response.type = 'text/html'\r\n ctx.response.body = '\u003ch1\u003eHello World\u003c/h1\u003e'\r\n console.log(`请求地址: ${ctx.path},响应时间:${etime - stime}ms`)\r\n});\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n console.log('中间件1 doSoming')\r\n await next();\r\n console.log('中间件1 end')\r\n})\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n console.log('中间件2 doSoming')\r\n // 注意,这里我们删掉了 next\r\n // await next()\r\n console.log('中间件2 end')\r\n})\r\n\r\napp.use(async (ctx, next) =\u003e {\r\n console.log('中间件3 doSoming')\r\n await next();\r\n console.log('中间件3 end')\r\n})\r\n\r\napp.listen(3000, () =\u003e {\r\n console.log('server is running at http://localhost:3000')\r\n})\r\n```\r\n\r\n\r\n重新运行代码后,控制台显示如下:\r\n\r\n```txt\r\nserver is running at http://localhost:3000\r\n中间件1 doSoming\r\n中间件2 doSoming\r\n中间件2 end\r\n中间件1 end\r\n请求地址: /,响应时间:1ms\r\n```\r\n","cover":"","link":"node-note2.html","preview":"\u003cp\u003eKoa2 \u0026ndash; 基于 Node.js 平台的下一代 web 开发框架。环境搭建什么的就不啰嗦了,直接从middleware开始记录笔记。\u003c/p\u003e\n","title":"node.js学习笔记(2) - Koa2中间件"},{"content":"\r\n看了后端小伙伴丢来的接口文档,这回终于不用写死数据了,立马从api接口拿数据。\r\n\r\n首先新建个 **utils** 文件夹,然后新建个 **api.js**,然后把api接口封装好暴露出去:\r\n\r\n```js\r\n let api = {\r\n //封装请求的接口\r\n getList() {\r\n let url = `https://www.xxx.com/v1/getlist`;\r\n return fetch(url).then((res) =\u003e res.json());\r\n }\r\n }\r\n\r\nmodule.exports = api; //暴露出去\r\n```\r\n\r\n然后到需要请求该接口的页面 **import** 进来:\r\n\r\n```js\r\nimport api from '../utils/api'\r\n\r\nclass GETLIST extends Component {\r\n //构造方法\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n //List初始化\r\n List: []\r\n }\r\n }\r\n\r\n //在生命周期中执行该function\r\n componentWillMount() {\r\n api.getlist().then((res) =\u003e {\r\n //从接口拿到数据并修改List\r\n this.setState({\r\n List: res.data\r\n })\r\n });\r\n }\r\n\r\n render() {\r\n return(\r\n \u003cView\u003e\r\n \u003cText\u003e\r\n {this.state.List}\r\n \u003c/Text\u003e\r\n \u003c/View\u003e\r\n );\r\n }\r\n}\r\n```\r\n","cover":"","link":"react-native-fetch-apis.html","preview":"\u003cp\u003e本来是准备用axios的,但是发现官方推荐用Fetch,吼啊,一颗赛艇。\u003c/p\u003e\n","title":"React Native爬坑之路 04 Fetch APIs"},{"content":"\r\n## Buffer\r\n\r\nBuffer 支持的编码格式:\"ascii\", \"utf8\", \"utf16le\", \"ucs2\", \"base64\" 和 \"hex\"。(默认是utf-8)\r\n\r\n写入缓冲区的方法:write(string,offeset,length,encoding);\r\n\r\n参数解释:\r\n\r\n* string:写入的字符串值\r\n* offeset:写入的位置,默认为 0,可以不填\r\n* length:写入长度,默认为 str.length, 可以不填\r\n* encoding:编码格式,默认为 utf-8 可以不填\r\n\r\n从缓冲区读取数据方法:buf.toString(encoding,start,end);\r\n参数均可不填,有默认值,与 write 保持一致。\r\n\r\n## Express\r\n框架核心特性:\r\n\r\n* 可以设置中间件来响应 HTTP 请求。\r\n* 定义了路由表用于执行不同的 HTTP 请求动作。\r\n* 可以通过向模板传递参数来动态渲染 HTML 页面。\r\n\r\n请求和响应:\r\n```js\r\n//get\r\napp.get('/', (req, res) =\u003e {\r\n // ...\r\n})\r\n//post\r\napp.post('/', (req, res) =\u003e {\r\n // ...\r\n})\r\n```\r\n\r\n* Request 常见属性:\r\n\r\n```js\r\nreq.app:当callback为外部文件时,用来访问express的实例\r\nreq.baseUrl:获取路由当前安装的URL路径\r\nreq.body / req.cookies:获得「请求主体」/ Cookies\r\nreq.fresh / req.stale:判断请求是否还「新鲜」\r\nreq.hostname / req.ip:获取主机名和IP地址\r\nreq.originalUrl:获取原始请求URL\r\nreq.params:获取路由的parameters\r\nreq.path:获取请求路径\r\nreq.protocol:获取协议类型\r\nreq.query:获取URL的查询参数串\r\nreq.route:获取当前匹配的路由\r\nreq.subdomains:获取子域名\r\nreq.accpets():检查请求的Accept头的请求类型\r\nreq.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages\r\nreq.get():获取指定的HTTP请求头\r\nreq.is():判断请求头Content-Type的MIME类型\r\n```\r\n\r\n* Response 常见属性:\r\n\r\n```js\r\nres.app:同req.app一样\r\nres.append():追加指定HTTP头\r\nres.set()在res.append():后将重置之前设置的头\r\nres.cookie(name,value,[option]):设置Cookie=\u003e opition: domain,expires,httpOnly,maxAge,path,secure,signed\r\nres.clearCookie():清除Cookie\r\nres.download():传送指定路径的文件\r\nres.get():返回指定的HTTP头\r\nres.json():传送JSON响应\r\nres.jsonp():传送JSONP响应\r\nres.location():只设置响应的Location HTTP头,不设置状态码或者close response\r\nres.redirect():设置响应的Location HTTP头,并且设置状态码302\r\nres.send():传送HTTP响应\r\nres.sendFile(path,[options],[fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type\r\nres.set():设置HTTP头,传入object可以一次设置多个头\r\nres.status():设置HTTP状态码\r\nres.type():设置Content-Type的MIME类型\r\n```\r\n","cover":"","link":"node-note1.html","preview":"\u003cp\u003e引入模块等不作记录。从 Buffer 章节开始记录笔记。\u003c/p\u003e\n","title":"node.js学习笔记(1) - Express"},{"content":"\r\nState的使用方式是在组件的构造函数中初始化State,在合适的地方调用setState方法,首先来看官方的例子,官方给出了一个文字闪烁的效果:\r\n```js\r\nclass Blink extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {showText: true};//这里设置了一个showText,默认值为true\r\n // Toggle the state every second\r\n setInterval(() =\u003e {\r\n this.setState({ showText: !this.state.showText });\r\n }, 1000);//这里是一个定时器,每1s会执行一次,调用定时器中的方法,重新给state赋值,注意this.state.showText是获取当前showText的值,同时需要注意的是调用this.setState()后回自动调用 render() 方法从而实现界面的刷新。\r\n }\r\n render() {\r\n let display = this.state.showText ? this.props.text : ' ';//这里通过showText 的值决定diaplay的值,如果为ture 则显示this.props.text属性的值,否则显示‘ ’,this.props.text为自定义属性,let等同于var;\r\n return (\r\n \u003cText\u003e{display}\u003c/Text\u003e\r\n );\r\n }\r\n }\r\n```\r\n在启动组件中使用\r\n```js\r\nclass BlinkApp extends Component {\r\n render() {\r\n return (\r\n \u003cView\u003e\r\n \u003cBlink text='I love to blink' /\u003e\r\n \u003cBlink text='Yes blinking is so great' /\u003e\r\n \u003cBlink text='Why did they ever take this out of HTML' /\u003e\r\n \u003cBlink text='Look at me look at me look at me' /\u003e\r\n \u003c/View\u003e\r\n );\r\n }\r\n}\r\n```\r\n","cover":"","link":"react-native-state.html","preview":"\u003cp\u003eReact native的组件可以通过两种方式进行状态控制,一种是Props用来设置不会改变的数据,另一种就是State,用来设置会变换的数据。State相当重要,所有的UI界面变化都离不开State。\u003c/p\u003e\n","title":"React Native爬坑之路 03 State"},{"content":"\r\nProps官网的介绍是:\r\n\r\n\u003e Most components can be customized when they are created, with different parameters. These creation parameters are called props.\r\n\r\n意思是:组件可以在创建时使用不同的参数进行自定义,这些参数就是Props。\r\n官网给我们举了一个例子:\r\n```js\r\nimport React, { Component } from 'react';\r\nimport { AppRegistry, Image } from 'react-native';\r\n\r\nclass Bananas extends Component {\r\n render() {\r\n return (\r\n \u003cImage source={{ uri: 'https://www.x.com/xxoo.jpg'}} style={{width: 10, height: 20}}/\u003e\r\n );\r\n }\r\n}\r\n\r\nAppRegistry.registerComponent('Bananas', () =\u003e Bananas);\r\n```\r\nsource就是Image 这个组件的一个属性,同理 style也是。\r\n再来看一个button的例子\r\n```js\r\nrender(){\r\n return(\r\n \u003cButton\r\n onPress={onButtonPress}\r\n title=\"Press Me\"\r\n accessibilityLabel=\"See an informative alert\"\r\n /\u003e\r\n );\r\n }\r\n```\r\n到这里我们应该基本了解了什么是Props吧,在以后实际开发过程中,可以通过API了解各个组件的属性。\r\n\r\n以上说的都是已有组件的属性那么我们自定义组件时该如何设置属性呢?官网给的解决办法是:在自定义组件的渲染函数(render)中通过this.props定义你的属性即可。\r\n还是来看官网的例子(略微修改了一下)\r\n```js\r\nclass Greeting extends Component {\r\n render() {\r\n return (\r\n \u003cText\u003e{this.props.name}\u003c/Text\u003e\r\n );\r\n }\r\n}\r\n```\r\n我们在Greeting 这个自定义组件的渲染函数中通过\u003cText\u003e组件定义了一个名为name的属性,注意看name前边的this.props,同时注意this.props.name是被{}包裹起来的。\r\n如何使用\r\n```js\r\nclass LotsOfGreetings extends Component {\r\n render() {\r\n return (\r\n \u003cView \u003e\r\n \u003cGreeting name='Android'\u003e\u003c/Greeting\u003e\r\n \u003cGreeting name='iOS'\u003e\u003c/Greeting\u003e\r\n \u003cGreeting name='WindowPhone'\u003e\u003c/Greeting\u003e\r\n \u003c/View\u003e\r\n );\r\n }\r\n}\r\n```\r\n\r\n\r\n修改一下Greeting的属性\r\n```js\r\nclass Greeting extends Component {\r\n render() {\r\n return (\r\n \u003cView\u003e\r\n \u003cText\u003e{this.props.name}\u003c/Text\u003e\r\n \u003cText\u003e{this.props.subname}\u003c/Text\u003e\r\n \u003c/View\u003e\r\n );\r\n }\r\n}\r\n\r\nclass LotsOfGreetings extends Component {\r\n render() {\r\n return (\r\n \u003cView \u003e\r\n \u003cGreeting name='Android' subname='iOS'\u003e\u003c/Greeting\u003e\r\n \u003c/View\u003e\r\n );\r\n }\r\n}\r\n```\r\n实现了同样的效果,所以说属性的使用方式还是很灵活的官网其实也说了,实际开发过程中可能需要自定义各种各样的组件,合理使用好组件的属性,从而达到想要的效果。\r\n","cover":"","link":"react-native-props.html","preview":"\u003cp\u003eProps是组件自身的属性,一般用于嵌套的内外层组件中,负责传递信息(通常由父层组件向子层组件传递)。\u003c/p\u003e\n","title":"React Native爬坑之路 02 Props"},{"content":"\r\n![](http://7rf9ir.com1.z0.glb.clouddn.com/3-3-component-lifecycle.jpg)\r\n\r\n如图,可以把组件生命周期大致分为三个阶段:\r\n\r\n* 第一阶段:是组件第一次绘制阶段,如图中的上面虚线框内,在这里完成了组件的加载和初始化;\r\n* 第二阶段:是组件在运行和交互阶段,如图中左下角虚线框,这个阶段组件可以处理用户交互,或者接收事件更新界面;\r\n* 第三阶段:是组件卸载消亡的阶段,如图中右下角的虚线框中,这里做一些组件的清理工作。\r\n\r\n## 生命周期回调函数\r\n下面来详细介绍生命周期中的各回调函数。\r\n\r\n* getDefaultProps\r\n\r\n在组件创建之前,会先调用 getDefaultProps(),这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。在组件被创建并加载候,首先调用 getInitialState(),来初始化组件的状态。\r\n\r\n* componentWillMount\r\n\r\n然后,准备加载组件,会调用 componentWillMount(),其原型如下:\r\n```js\r\nvoid componentWillMount()\r\n```\r\n这个函数调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。\r\n\r\n* componentDidMount\r\n\r\n在组件第一次绘制之后,会调用 componentDidMount(),通知组件已经加载完成。函数原型如下:\r\n```js\r\nvoid componentDidMount() \r\n```\r\n这个函数调用的时候,其虚拟 DOM 已经构建完成,你可以在这个函数开始获取其中的元素或者子组件了。需要注意的是,RN 框架是先调用子组件的 componentDidMount(),然后调用父组件的函数。从这个函数开始,就可以和 JS 其他框架交互了,例如设置计时 setTimeout 或者 setInterval,或者发起网络请求。这个函数也是只被调用一次。这个函数之后,就进入了稳定运行状态,等待事件触发。\r\n\r\n* componentWillReceiveProps\r\n\r\n如果组件收到新的属性(props),就会调用 componentWillReceiveProps(),其原型如下:\r\n```js\r\nvoid componentWillReceiveProps( \r\n object nextProps\r\n)\r\n```\r\n输入参数 nextProps 是即将被设置的属性,旧的属性还是可以通过 this.props 来获取。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState() 来更新你的组件状态,这里调用更新状态是安全的,并不会触发额外的 render() 调用。如下:\r\n```js\r\ncomponentWillReceiveProps: function(nextProps) { \r\n this.setState({\r\n likesIncreasing: nextProps.likeCount \u003e this.props.likeCount\r\n });\r\n}\r\n```\r\n\r\n* shouldComponentUpdate\r\n\r\n当组件接收到新的属性和状态改变的话,都会触发调用 shouldComponentUpdate(...),函数原型如下:\r\n```js\r\nboolean shouldComponentUpdate( \r\n object nextProps, object nextState\r\n)\r\n```\r\n输入参数 nextProps 和上面的 componentWillReceiveProps 函数一样,nextState 表示组件即将更新的状态值。这个函数的返回值决定是否需要更新组件,如果 true 表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。\r\n\r\n默认情况下,这个函数永远返回 true 用来保证数据变化的时候 UI 能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定 UI 是否需要更新,能有效提高应用性能。\r\n\r\n* componentWillUpdate\r\n\r\n如果组件状态或者属性改变,并且上面的 shouldComponentUpdate(...) 返回为 true,就会开始准更新组件,并调用 componentWillUpdate(),其函数原型如下:\r\n```js\r\nvoid componentWillUpdate( \r\n object nextProps, object nextState\r\n)\r\n```\r\n输入参数与 shouldComponentUpdate 一样,在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用 this.setState 来修改状态。这个函数调用之后,就会把 nextProps 和 nextState 分别设置到 this.props 和 this.state 中。紧接着这个函数,就会调用 render() 来更新界面了。\r\n\r\n* componentDidUpdate\r\n\r\n调用了 render() 更新完成界面之后,会调用 componentDidUpdate() 来得到通知,其函数原型如下:\r\n```js\r\nvoid componentDidUpdate( \r\n object prevProps, object prevState\r\n)\r\n```\r\n因为到这里已经完成了属性和状态的更新了,此函数的输入参数变成了 prevProps 和 prevState。\r\n\r\n* componentWillUnmount\r\n\r\n当组件要被从界面上移除的时候,就会调用 componentWillUnmount(),其函数原型如下:\r\n```js\r\nvoid componentWillUnmount() \r\n```\r\n在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。\r\n\r\n## 总结\r\n介绍完了完整的生命周期,在回头来看一下前面的图,就比较清晰了,把生命周期的回调函数总结成如下表格:\r\n\r\n生命周期|调用次数|能否setSate?\r\n:-:|:-:|:-:\r\ngetDefaultProps|1(全局调用一次)|no\r\ngetInitialState|1|no\r\ncomponentWillMount|1|yes\r\nrender|\u003e=1|no\r\ncomponentDidMount|1|yes\r\ncomponentWillReceiveProps|\u003e=0|yes\r\nshouldComponentUpdate|\u003e=0|no\r\ncomponentWillUpdate|\u003e=0|no\r\ncomponentDidUpdate|\u003e=0|no\r\ncomponentWillUnmount|1|no\r\n","cover":"","link":"react-native-lifecycle.html","preview":"\u003cp\u003e与Activity类似,React Native(RN) 中的组件也有生命周期(Lifecycle)。所谓生命周期,就是一个对象从开始生成到最后消亡所经历的状态,理解生命周期,是合理开发的关键。\u003c/p\u003e\n","title":"React Native爬坑之路 01 生命周期"},{"content":"\r\n处理 React 组件之间的交流方式,主要取决于组件之间的关系,然而这些关系的约定人就是你。\r\n我不会讲太多关于 data-stores、data-adapters 或者 data-helpers 之类的话题。\r\n\r\nReact 组件之间交流的方式,可以分为以下 3 种:\r\n\r\n* 【父组件】向【子组件】传值;\r\n* 【子组件】向【父组件】传值;\r\n* 没有任何嵌套关系的组件之间传值(如:兄弟组件之间传值)\r\n\r\n## 一、【父组件】向【子组件】传值\r\n这个是相当容易的,在使用 React 开发的过程中经常会使用到,主要是利用 props 来进行交流。例子如下:\r\n\r\n```js\r\n// 父组件\r\nvar MyContainer = React.createClass({\r\n getInitialState: function () {\r\n return {\r\n checked: true\r\n };\r\n },\r\n render: function() {\r\n return (\r\n \u003cToggleButton text=\"Toggle me\" checked={this.state.checked} /\u003e\r\n );\r\n }\r\n});\r\n\r\n// 子组件\r\nvar ToggleButton = React.createClass({\r\n render: function () {\r\n // 从【父组件】获取的值\r\n var checked = this.props.checked,\r\n text = this.props.text;\r\n\r\n return (\r\n \u003clabel\u003e{text}: \u003cinput type=\"checkbox\" checked={checked} /\u003e\u003c/label\u003e\r\n );\r\n }\r\n});\r\n```\r\n\r\n如果组件嵌套层次太深,那么从外到内组件的交流成本就变得很高,通过 props 传递值的优势就不那么明显了。(PS:所以我建议尽可能的减少组件的层次,就像写 HTML 一样,简单清晰的结构更惹人爱)\r\n\r\n```js\r\n// 父组件\r\nvar MyContainer = React.createClass({\r\n render: function() {\r\n return (\r\n \u003cIntermediate text=\"where is my son?\" /\u003e\r\n );\r\n }\r\n});\r\n\r\n// 子组件1:中间嵌套的组件\r\nvar Intermediate = React.createClass({\r\n render: function () {\r\n return (\r\n \u003cChild text={this.props.text} /\u003e\r\n );\r\n }\r\n});\r\n\r\n// 子组件2:子组件1的子组件\r\nvar Child = React.createClass({\r\n render: function () {\r\n return (\r\n \u003cspan\u003e{this.props.text}\u003c/span\u003e\r\n );\r\n }\r\n});\r\n```\r\n\r\n### 二、【子组件】向【父组件】传值\r\n接下来,【子组件】控制自己的 state 然后告诉【父组件】的点击状态,然后在【父组件】中展示出来。因此,我们添加一个 change 事件来做交互。\r\n\r\n```js\r\n// 父组件\r\nvar MyContainer = React.createClass({\r\n getInitialState: function () {\r\n return {\r\n checked: false\r\n };\r\n },\r\n onChildChanged: function (newState) {\r\n this.setState({\r\n checked: newState\r\n });\r\n },\r\n render: function() {\r\n var isChecked = this.state.checked ? \"yes\" : \"no\";\r\n return (\r\n \u003cdiv\u003e\r\n \u003cdiv\u003eAre you checked: {isChecked}\u003c/div\u003e\r\n \u003cToggleButton text=\"Toggle me\"\r\n initialChecked={this.state.checked}\r\n callbackParent={this.onChildChanged}\r\n /\u003e\r\n \u003c/div\u003e\r\n );\r\n }\r\n});\r\n\r\n// 子组件\r\nvar ToggleButton = React.createClass({\r\n getInitialState: function () {\r\n return {\r\n checked: this.props.initialChecked\r\n };\r\n },\r\n onTextChange: function () {\r\n var newState = !this.state.checked;\r\n this.setState({\r\n checked: newState\r\n });\r\n // 这里要注意:setState 是一个异步方法,所以需要操作缓存的当前值\r\n this.props.callbackParent(newState);\r\n },\r\n render: function () {\r\n // 从【父组件】获取的值\r\n var text = this.props.text;\r\n // 组件自身的状态数据\r\n var checked = this.state.checked;\r\n\r\n return (\r\n \u003clabel\u003e{text}: \u003cinput type=\"checkbox\" checked={checked} onChange={this.onTextChange} /\u003e\u003c/label\u003e\r\n );\r\n }\r\n});\r\n```\r\n``这样做其实是依赖 props 来传递事件的引用,并通过回调的方式来实现的,这样实现不是特别好,但是在没有任何工具的情况下也是一种简单的实现方式``\r\n\r\n这里会出现一个我们在之前讨论的问题,就是组件有多层嵌套的情况下,你必须要一次传入回调函数给 props 来实现子组件向父组件传值或者操作。\r\n\r\n#### Tiny-Tip: React Event System\r\n在 onChange 事件或者其他 React 事件中,你能够获取以下东西:\r\n\r\n* 【this】:指向你的组件\r\n* 【一个参数】:这个参数是一个 React 合成事件,SyntheticEvent。\r\n\r\nReact 对所有事件的管理都是自己实现的,与我们之前使用的 onclick、onchange 事件不一样。从根本上来说,他们都是绑定到 body 上。\r\n``document.on(\"change\", \"input[data-reactid=\".0.2\"]\", function () {...});``\r\n上面这份代码不是来自于 React,只是打一个比方而已。\r\n\r\n如果我没有猜错的话,React 真正处理一个事件的代码如下:\r\n\r\n```js\r\nvar listenTo = ReactBrowserEventEmitter.listenTo;\r\n...\r\nfunction putListener(id, registrationName, listener, transaction) {\r\n ...\r\n var container = ReactMount.findReactContainerForID(id);\r\n if (container) {\r\n var doc = container.nodeType === ELEMENT_NODE_TYPE ? container.ownerDocument : container;\r\n listenTo(registrationName, doc);\r\n }\r\n ...\r\n}\r\n// 在监听事件的内部,我们能发现如下:\r\ntarget.addEventListener(eventType, callback, false);\r\n```\r\n\r\n``多个子组件使用同一个回调的情况``\r\n\r\n```js\r\n// 父组件\r\nvar MyContainer = React.createClass({\r\n getInitialState: function () {\r\n return {\r\n totalChecked: 0\r\n };\r\n },\r\n onChildChanged: function (newState) {\r\n var newToral = this.state.totalChecked\r\n + (newState ? 1 : -1);\r\n this.setState({\r\n totalChecked: newToral\r\n });\r\n },\r\n render: function() {\r\n var totalChecked = this.state.totalChecked;\r\n return (\r\n \u003cdiv\u003e\r\n \u003cdiv\u003eHow many are checked: {totalChecked}\u003c/div\u003e\r\n \u003cToggleButton text=\"Toggle me\"\r\n initialChecked={this.state.checked}\r\n callbackParent={this.onChildChanged}\r\n /\u003e\r\n \u003cToggleButton text=\"Toggle me too\"\r\n initialChecked={this.state.checked}\r\n callbackParent={this.onChildChanged}\r\n /\u003e\r\n \u003cToggleButton text=\"And me\"\r\n initialChecked={this.state.checked}\r\n callbackParent={this.onChildChanged}\r\n /\u003e\r\n \u003c/div\u003e\r\n );\r\n }\r\n});\r\n\r\n// 子组件\r\nvar ToggleButton = React.createClass({\r\n getInitialState: function () {\r\n return {\r\n checked: this.props.initialChecked\r\n };\r\n },\r\n onTextChange: function () {\r\n var newState = !this.state.checked;\r\n this.setState({\r\n checked: newState\r\n });\r\n // 这里要注意:setState 是一个异步方法,所以需要操作缓存的当前值\r\n this.props.callbackParent(newState);\r\n },\r\n render: function () {\r\n // 从【父组件】获取的值\r\n var text = this.props.text;\r\n // 组件自身的状态数据\r\n var checked = this.state.checked;\r\n\r\n return (\r\n \u003clabel\u003e{text}: \u003cinput type=\"checkbox\" checked={checked} onChange={this.onTextChange} /\u003e\u003c/label\u003e\r\n );\r\n }\r\n});\r\n```\r\n\r\n这是非常容易理解的,在父组件中我们增加了一个【totalChecked】来替代之前例子中的【checked】,当子组件改变的时候,使用同一个子组件的回调函数给父组件返回值。\r\n\r\n### 三、没有任何嵌套关系的组件之间传值\r\n如果组件之间没有任何关系,组件嵌套层次比较深(个人认为 2 层以上已经算深了),或者你为了一些组件能够订阅、写入一些信号,不想让组件之间插入一个组件,让两个组件处于独立的关系。对于事件系统,这里有 2 个基本操作步骤:订阅(subscribe)/监听(listen)一个事件通知,并发送(send)/触发(trigger)/发布(publish)/发送(dispatch)一个事件通知那些想要的组件。\r\n\r\n下面讲介绍 3 种模式来处理事件,你能点击这里来比较一下它们。\r\n\r\n简单总结一下:\r\n\r\n- Event Emitter/Target/Dispatcher\r\n特点:需要一个指定的订阅源\r\n\r\n```js\r\n// to subscribe\r\notherObject.addEventListener(‘click’, function() { alert(‘click!’); });\r\n// to dispatch\r\nthis.dispatchEvent(‘click’);\r\n```\r\n\r\n- Publish / Subscribe\r\n特点:触发事件的时候,你不需要指定一个特定的源,因为它是使用一个全局对象来处理事件(其实就是一个全局 广播的方式来处理事件)\r\n\r\n```js\r\n// to subscribe\r\nglobalBroadcaster.subscribe(‘click’, function() { alert(‘click!’); });\r\n// to dispatch\r\nglobalBroadcaster.publish(‘click’);\r\n```\r\n\r\n- Signals\r\n特点:与Event Emitter/Target/Dispatcher相似,但是你不要使用随机的字符串作为事件触发的引用。触发事件的每一个对象都需要一个确切的名字(就是类似硬编码类的去写事件名字),并且在触发的时候,也必须要指定确切的事件。(看例子吧,很好理解)\r\n\r\n```js\r\n// to subscribe\r\notherObject.clicked.add(function() { alert(‘click’); });\r\n// to dispatch\r\nthis.clicked.dispatch();\r\n```\r\n\r\n如果你只想简单的使用一下,并不需要其他操作,可以用简单的方式来实现:\r\n\r\n```js\r\n// 简单实现了一下 subscribe 和 dispatch\r\nvar EventEmitter = {\r\n _events: {},\r\n dispatch: function (event, data) {\r\n if (!this._events[event]) { // 没有监听事件\r\n return;\r\n }\r\n for (var i = 0; i \u003c this._events[event].length; i++) {\r\n this._events[event][i](data);\r\n }\r\n },\r\n subscribe: function (event, callback) {\r\n // 创建一个新事件数组\r\n if (!this._events[event]) {\r\n this._events[event] = [];\r\n }\r\n this._events[event].push(callback);\r\n }\r\n};\r\notherObject.subscribe(\"namechanged\", function(data) { alert(data.name); });\r\nthis.dispatch(\"namechanged\", { name: \"John\" });\r\n```\r\n\r\n如果你想使用 Publish/Subscribe 模型,可以使用:PubSubJS\r\nReact 团队使用的是:js-signals 它基于 Signals 模式,用起来相当不错。\r\n\r\n### Events in React\r\n使用 React 事件的时候,必须关注下面两个方法:\r\n* componentDidMount\r\n* componentWillUnmount\r\n在处理事件的时候,需要注意:\r\n\r\n在 componentDidMount 事件中,如果组件挂载(mounted)完成,再订阅事件;当组件卸载(unmounted)的时候,在 componentWillUnmount 事件中取消事件的订阅。\r\n(如果不是很清楚可以查阅 React 对生命周期介绍的文档,里面也有描述。原文中介绍的是 componentWillMount 个人认为应该是挂载完成后订阅事件,比如Animation这个就必须挂载,并且不能动态的添加,谨慎点更好)\r\n因为组件的渲染和销毁是由 React 来控制的,我们不知道怎么引用他们,所以EventEmitter 模式在处理组件的时候用处不大。\r\n\r\npub/sub 模式可以使用,你不需要知道引用。\r\n\r\n下面来一个例子:实现有多个 product 组件,点击他们的时候,展示 product 的名字。\r\n(我在例子中引入了之前推荐的 PubSubJS 库,如果你觉得引入代价太大,也可以手写一个简版,还是比较容易的,很好用哈,大家也可以体验,但是我还是不推荐全局广播的方式)\r\n\r\n```js\r\n// 定义一个容器\r\nvar ProductList = React.createClass({\r\n render: function () {\r\n return (\r\n \u003cdiv\u003e\r\n \u003cProductSelection /\u003e\r\n \u003cProduct name=\"product 1\" /\u003e\r\n \u003cProduct name=\"product 2\" /\u003e\r\n \u003cProduct name=\"product 3\" /\u003e\r\n \u003c/div\u003e\r\n );\r\n }\r\n});\r\n// 用于展示点击的产品信息容器\r\nvar ProductSelection = React.createClass({\r\n getInitialState: function() {\r\n return {\r\n selection: \"none\"\r\n };\r\n },\r\n componentDidMount: function () {\r\n this.pubsub_token = PubSub.subscribe(\"products\", function (topic, product) {\r\n this.setState({\r\n selection: product\r\n });\r\n }.bind(this));\r\n },\r\n componentWillUnmount: function () {\r\n PubSub.unsubscribe(this.pubsub_token);\r\n },\r\n render: function () {\r\n return (\r\n \u003cp\u003eYou have selected the product : {this.state.selection}\u003c/p\u003e\r\n );\r\n }\r\n});\r\nvar Product = React.createClass({\r\n onclick: function () {\r\n PubSub.publish(\"products\", this.props.name);\r\n },\r\n render: function() {\r\n return \u003cdiv onClick={this.onclick}\u003e{this.props.name}\u003c/div\u003e;\r\n }\r\n});\r\n```\r\n\r\n### ES6: yield and js-csp\r\nES6 中有一种传递信息的方式,使用生成函数(generators)和 yield 关键字。\r\n\r\n```js\r\nfunction* list() {\r\n for(var i = 0; i \u003c arguments.length; i++) {\r\n yield arguments[i];\r\n }\r\n return \"done.\";\r\n}\r\nvar o = list(1, 2, 3);\r\nvar cur = o.next;\r\nwhile(!cur.done) {\r\n cur = o.next();\r\n console.log(cur);\r\n}\r\n```\r\n\r\n通常来说,你有一个队列,对象在里面都能找到一个引用,在定义的时候锁住,当发生的时候,立即打开锁执行。js-csp 是一种解决办法,也许以后还会有其他解决办法。\r\n\r\n### 结尾\r\n在实际应用中,按照实际要解决的需求选择解决办法。对于小应用程序,你可以使用 props 和回调的方法进行组件之间的数据交换。你可以通过 pub/sub 模式,以避免污染你的组件。在这里,我们不是在谈论数据,只是组件。对于数据的请求、数据的变化等场景,可以使用 Facebook 的 Flux、Relay、GraphQL 来处理,都非常的好用。","cover":"","link":"react-component-communicate.html","preview":"\u003cp\u003e今天群里面有很多都在讨论关于 React 组件之间是如何通信的问题,之前自己写的时候也遇到过这类问题。\u003c/p\u003e\n","title":"React 组件之间交流"},{"content":"\r\n熟悉 React 的思想后,我们先来尝试开发一个单纯的小组件,可以对比一下是不是比以前的开发模式更加舒适了,这里我主要以一个 Loading 组件来举栗子,实现了几个基本的功能:\r\n\r\n* 一种类型的 loading(菊花转)\r\n* 能够设置 loading 的三个属性:width height color\r\n* 能够控制 loading 的显示和隐藏\r\n\r\n其实对于一个简单需求来说,这三个属性已经很实用了。但是去网上看一些外国大神写的组件,有一些不明白的地方,所以自己就慢慢搞,do it!\r\n\r\n#### 设计\r\n我想这样用 loadding 组件:\r\n```js\r\nvar loadingClasses = cx({\r\n\tloadding: true,\r\n\tactive: this.state.isActiveLoading\r\n\t});\r\n\r\n\t\u003cLoading width={30} height={30} color={#000} className={loadingClasses} /\u003e\r\n```\r\n所以我定义这个组件的基本结构如下:\r\n```js\r\nvar Loading = React.createClass({\r\n // 控制组件属性的类型\r\n propTypes: {},\r\n // 控制组件属性的默认值\r\n getDefaultProps: function () {},\r\n // 组装基本的内联样式\r\n getComponentStyle: function () {},\r\n // 渲染基本的组件,拆分 render 方法的粒度\r\n renderBaseComp: function () {},\r\n // 最终的渲染方法\r\n render: function () {}\r\n});\r\n```\r\n这个组件中,我使用的 内联样式 来控制组件的内部基本样式的稳定。其实有时候我们会觉得内联样式不好,但是我个人觉得每一种设置 CSS 形式的方法,用在合适的场景中就是正确的。\r\n\r\n每部分的具体实现如下,代码中有一些讲解(这里我不会介绍具体 loading 效果是怎么出来的,看代码应该就会明白,主要介绍一个 react 制作简单组件的思路和写法)对于扩展性来说,\r\n```\r\n你还可以加入 className 和 type 这些修饰性的属性,但是我更倾向于迭代式的组件开发,\r\n小组件就要具有良好的封闭性,使用接口简单。大组件才考虑更好的鲁棒性和可扩展性,\r\n这样开发一个组件的性价比才高。需要注意对 getDefaultProps的理解,\r\n只有当使用接口的人代码中根本没有写那个属性的时候,才会使用定义的默认值。\r\n```\r\n\r\n#### 实现\r\n```js\r\nvar Loading = React.createClass({\r\n propTypes: {\r\n width: React.PropTypes.oneOfType([\r\n React.PropTypes.number,\r\n React.PropTypes.string\r\n ]),\r\n height: React.PropTypes.oneOfType([\r\n React.PropTypes.number,\r\n React.PropTypes.string\r\n ]),\r\n color: React.PropTypes.string,\r\n active: React.PropTypes.bool\r\n },\r\n getDefaultProps: function() {\r\n return {\r\n color: \"#00be9c\",\r\n height: 30,\r\n width: 30,\r\n active: false\r\n };\r\n },\r\n\r\n getComponentStyle: function() {\r\n var width = this.props.width,\r\n height = this.props.height,\r\n color = this.props.color;\r\n /* 中间圆心 */\r\n var cWidth = 0.4 * width,\r\n cHeight = 0.4 * height,\r\n cMarginLeft = -0.5 * cWidth,\r\n cMarginTop = -0.5 * cHeight;\r\n\r\n /* 基本样式 */\r\n return {\r\n loadingStyle: { // loadding 容器\r\n width: width,\r\n height: height\r\n },\r\n lineStyle: { // loadding 元件样式\r\n background: color\r\n },\r\n centerStyle: { // loadding 圆心样式\r\n width: cWidth,\r\n height: cHeight,\r\n marginLeft: cMarginLeft,\r\n marginTop: cMarginTop\r\n }\r\n };\r\n },\r\n\r\n renderBaseComp: function(compStyle) {\r\n /* 生成动画元件 */\r\n var n = 4; // 元件个数,todo: 定制个数\r\n var lines = []; // 元件元素集合\r\n for (var i = 0; i \u003c n; i++) {\r\n lines.push(\r\n \u003cdiv className=\"line\"\u003e\r\n \u003cspan className=\"top\" style={ compStyle.lineStyle }\u003e\u003c/span\u003e\r\n \u003cspan className=\"bottom\" style={ compStyle.lineStyle }\u003e\u003c/span\u003e\r\n \u003c/div\u003e\r\n );\r\n }\r\n return lines;\r\n },\r\n\r\n render: function() {\r\n /* 生成组件自己的样式 */\r\n var compStyle = this.getComponentStyle();\r\n /* 模拟渲染基本动画元件 */\r\n var lines = this.renderBaseComp(compStyle);\r\n\r\n // loadding 的class,控制交互\r\n var loadingClasses = cx({\r\n loading: true,\r\n active: this.props.active\r\n });\r\n\r\n return (\r\n \u003cdiv className={ loadingClasses } style={ compStyle.loadingStyle }\u003e\r\n {lines}\r\n \u003cdiv className=\"loading-center\" style={ compStyle.centerStyle }\u003e\u003c/div\u003e\r\n \u003c/div\u003e\r\n\r\n );\r\n }\r\n\r\n});\r\n```\r\n\r\n##### 最后,下面是基本的 SASS(不考虑不支持的情况,不支持都不用开发,直接用图,性价比更高)\r\n\r\n```css\r\n@include keyframes(load) {\r\n 0% {\r\n opacity: 0;\r\n }\r\n 25% {\r\n opacity: .25;\r\n }\r\n 50% {\r\n opacity: .5;\r\n }\r\n 75% {\r\n opacity: .75;\r\n }\r\n 100% {\r\n opacity: 1;\r\n }\r\n}\r\n\r\n.loading {\r\n display: none;\r\n position: absolute;\r\n \u0026.active {\r\n display: block;\r\n }\r\n .loading-center {\r\n position: absolute;\r\n left: 0;\r\n top: 50%;\r\n background: #fff;\r\n border-radius: 50%;\r\n }\r\n .line {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n height: 100%;\r\n .top {\r\n content: \"\";\r\n display: block;\r\n width: 1px;\r\n font-size: 0;\r\n height: 50%;\r\n }\r\n .bottom {\r\n @extend .top;\r\n }\r\n @for $i from 1 through 4 {\r\n \u0026:nth-child(#{$i}) {\r\n transform:rotate(45deg * ($i - 1));\r\n .top {\r\n @include animation(load, 0.8s, linear, 0s, infinite);\r\n }\r\n .bottom {\r\n @include animation(load, 0.8s, linear, 0.4s + $i/10, infinite);\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\n里面用到的一个 animation 混淆方法:\r\n```css\r\n@mixin keyframes($name) {\r\n @-webkit-keyframes #{$name} {\r\n @content;\r\n }\r\n @-moz-keyframes #{$name} {\r\n @content;\r\n }\r\n @-ms-keyframes #{$name} {\r\n @content;\r\n }\r\n @keyframes #{$name} {\r\n @content;\r\n }\r\n}\r\n\r\n@mixin animation ($name, $duration, $func, $delay, $count, $direction: normal) {\r\n -webkit-animation: $name $duration $func $delay $count $direction;\r\n -moz-animation: $name $duration $func $delay $count $direction;\r\n -o-animation: $name $duration $func $delay $count $direction;\r\n animation: $name $duration $func $delay $count $direction;\r\n}\r\n```","cover":"","link":"induction-react-component.html","preview":"\u003cp\u003e熟悉 React 的思想后,我们先来尝试开发一个单纯的小组件,可以对比一下是不是比以前的开发模式更加舒适了。\u003c/p\u003e\n","title":"React 组件开发入门"},{"content":"\r\nasync 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。\r\n想较于 Generator,Async 函数的改进在于下面四点:\r\n* 内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样\r\n* 更好的语义。async 和 await 相较于 * 和 yield 更加语义化\r\n* 更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)\r\n* 返回值是 Promise。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用\r\n\r\n## Async 与其他异步操作的对比\r\n\r\n先定义一个 Fetch 方法用于获取用户信息:\r\n```js\r\nfunction fetchUser() {\r\n return new Promise((resolve, reject) =\u003e {\r\n fetch('www.xxx.com')\r\n .then((data) =\u003e {\r\n resolve(data.json());\r\n }, (error) =\u003e {\r\n reject(error);\r\n })\r\n });\r\n}\r\n```\r\n\r\n* Promise 方式\r\n\r\n```js\r\nfunction getUserByPromise() {\r\n fetchUser()\r\n .then((data) =\u003e {\r\n console.log(data);\r\n }, (error) =\u003e {\r\n console.log(error);\r\n })\r\n}\r\ngetUserByPromise();\r\n```\r\nPromise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise的 then() 方法,如果处理流程复杂的话,整段代码将充满 then。语义化不明显,代码流程不能很好的表示执行流程。\r\n\r\n* Generator 方式\r\n```js\r\nfunction* fetchUserByGenerator() {\r\n const user = yield fetchUser();\r\n return user;\r\n}\r\n\r\nconst g = fetchUserByGenerator();\r\nconst result = g.next().value;\r\nresult.then((v) =\u003e {\r\n}, (error) =\u003e {\r\n console.log(error);\r\n})\r\n```\r\nGenerator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行。\r\n\r\n* async 方式\r\n\r\n```js\r\n async function getUserByAsync(){\r\n let user = await fetchUser();\r\n return user;\r\n }\r\ngetUserByAsync()\r\n.then(v =\u003e console.log(v));\r\n```\r\nasync 函数完美的解决了上面两种方式的问题。流程清晰,直观、语义明显。操作异步流程就如同操作同步流程。同时 async 函数自带执行器,执行的时候无需手动加载。\r\n\r\n## 语法\r\n\r\nasync 函数返回一个 Promise 对象\r\n\r\nasync 函数内部 return 返回的值。会成为 then 方法回调函数的参数。\r\n```js\r\nasync function f() {\r\n return 'hello world'\r\n};\r\nf().then( (v) =\u003e console.log(v)) // hello world\r\n```\r\n如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。\r\n```js\r\nasync function e(){\r\n throw new Error('error');\r\n}\r\ne().then(v =\u003e console.log(v))\r\n.catch( e =\u003e console.log(e));\r\n```\r\n\r\nasync 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变\r\n也就是说,只有当 async 函数内部的异步操作都执行完,才会执行 then 方法的回调。\r\n```js\r\nconst delay = timeout =\u003e new Promise(resolve=\u003e setTimeout(resolve, timeout));\r\nasync function f(){\r\n await delay(1000);\r\n await delay(2000);\r\n await delay(3000);\r\n return 'done';\r\n}\r\n\r\nf().then(v =\u003e console.log(v)); // 等待6s后才输出 'done'\r\n```\r\n正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise\r\n如下面这个例子:\r\n```js\r\nasync function f() {\r\n return await 1\r\n};\r\nf().then( (v) =\u003e console.log(v)) // 1\r\n```\r\n如果返回的是 reject 的状态,则会被 catch 方法捕获。\r\n\r\n## Async 函数的错误处理\r\n\r\nasync 函数的语法不难,难在错误处理上。\r\n先来看下面的例子:\r\n```js\r\nlet a;\r\nasync function f() {\r\n await Promise.reject('error');\r\n a = await 1; // 这段 await 并没有执行\r\n}\r\nf().then(v =\u003e console.log(a));\r\n```\r\n如上面所示,当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。\r\n解决办法:可以添加 try/catch。\r\n```js\r\n// 正确的写法\r\nlet a;\r\nasync function correct() {\r\n try {\r\n await Promise.reject('error')\r\n } catch (error) {\r\n console.log(error);\r\n }\r\n a = await 1;\r\n return a;\r\n}\r\n\r\ncorrect().then(v =\u003e console.log(a)); // 1\r\n```\r\n如果有多个 await 则可以将其都放在 try/catch 中。\r\n\r\n## 如何在项目中使用\r\n\r\n依然是通过 babel 来使用。\r\n只需要设置 presets 为 stage-3 即可。\r\n安装依赖:\r\n```js\r\nnpm i babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime\r\n```\r\n修改.babelrc:\r\n```json\r\n\"presets\": [\"es2015\", \"stage-3\"],\r\n\"plugins\": [\"transform-runtime\"]\r\n```\r\n这样就可以在项目中使用 async 函数了。\r\n","cover":"","link":"async-await.html","preview":"\u003cp\u003e刚出来不久的 ES8 包含了 async 函数,它的出现,终于让 JavaScript 对于异步操作有了终极解决方案。No more callback hell。\u003c/p\u003e\n","title":"理(瞎)解(说) async/await"},{"content":"\r\n由于 FetchAPI 是基于 Promise 设计,有必要先学习一下 Promise\r\n\r\n## 语法说明\r\n\r\n```js\r\nfetch(url, options).then((res) =\u003e {\r\n // handle HTTP response\r\n}).catch((err) =\u003e {\r\n\t// handle network error\r\n})\r\n```\r\n\r\n## 参数说明\r\n\r\n### url\r\n\r\n定义要获取的资源。这可能是:\r\n\r\n* 一个 USVString 字符串,包含要获取资源的 URL。\r\n* 一个 Request 对象。\r\n\r\n### options(可选)\r\n\r\n一个配置项对象,包括所有对请求的设置。可选的参数有:\r\n\r\n* method: 请求使用的方法,如 GET、POST。\r\n* headers: 请求的头信息,形式为 Headers 对象或 ByteString。\r\n* body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。\r\n* mode: 请求的模式,如 cors、 no-cors 或者 same-origin。\r\n* credentials: 请求的 credentials,如 omit、same-origin 或者 include。\r\n* cache: 请求的 cache 模式: default, no-store, reload, no-cache, force-cache, 或者 only-if-cached。\r\n\r\n### response\r\n\r\n一个 Promise,resolve 时回传 Response 对象:\r\n\r\n* 属性:\r\n\r\n1. status (number) - HTTP请求结果参数,在100–599 范围\r\n2. statusText (String) - 服务器返回的状态报告\r\n3. ok (boolean) - 如果返回200表示请求成功则为true\r\n4. headers (Headers) - 返回头部信息,下面详细介绍\r\n5. url (String) - 请求的地址\r\n\r\n* 方法:\r\n\r\n1. text() - 以string的形式生成请求text\r\n2. json() - 生成JSON.parse(responseText)的结果\r\n3. blob() - 生成一个Blob\r\n4. arrayBuffer() - 生成一个ArrayBuffer\r\n5. formData() - 生成格式化的数据,可用于其他的请求\r\n\r\n* 其他方法:\r\n\r\n1. clone()\r\n2. Response.error()\r\n3. Response.redirect()\r\n\r\n### response.headers\r\n\r\n- has(name) (boolean) - 判断是否存在该信息头\r\n- get(name) (String) - 获取信息头的数据\r\n- getAll(name) (Array) - 获取所有头部数据\r\n- set(name, value) - 设置信息头的参数\r\n- append(name, value) - 添加header的内容\r\n- delete(name) - 删除header的信息\r\n- forEach(function(value, name){ ... }, [thisContext]) - 循环读取header的信息\r\n\r\n## 使用案例\r\n* GET\r\n\r\n```js\r\nfetch('/user').then((res) =\u003e {\r\n return res.text()\r\n }).then((res) =\u003e {\r\n console.log(res)\r\n })\r\n```\r\n\r\n* POST\r\n\r\n```js\r\nfetch('/users', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({\r\n name: 'robot',\r\n code: '198964',\r\n })\r\n}).then((res) =\u003e {\r\n\tconsole.log(res)\r\n})\r\n```\r\n\r\n## 封装fetch\r\n\r\n```js\r\n/**\r\n * 将对象转成 a=1\u0026b=2的形式\r\n * @param obj 对象\r\n */\r\nfunction obj2String(obj, arr = [], idx = 0) {\r\n for (let item in obj) {\r\n arr[idx++] = [item, obj[item]]\r\n }\r\n return new URLSearchParams(arr).toString()\r\n}\r\n\r\n/**\r\n * 真正的请求\r\n * @param url 请求地址\r\n * @param options 请求参数\r\n * @param method 请求方式\r\n */\r\nfunction commonFetcdh(url, options, method = 'GET') {\r\n const searchStr = obj2String(options)\r\n let initObj = {}\r\n if (method === 'GET') { // 如果是GET请求,拼接url\r\n url += '?' + searchStr\r\n initObj = {\r\n method: method,\r\n credentials: 'include'\r\n }\r\n } else {\r\n initObj = {\r\n method: method,\r\n credentials: 'include',\r\n headers: new Headers({\r\n 'Accept': 'application/json',\r\n 'Content-Type': 'application/json'\r\n }),\r\n body: searchStr\r\n }\r\n }\r\n fetch(url, initObj).then((res) =\u003e {\r\n return res.json()\r\n }).then((res) =\u003e {\r\n return res\r\n })\r\n}\r\n\r\n/**\r\n * GET请求\r\n * @param url 请求地址\r\n * @param options 请求参数\r\n */\r\nfunction GET(url, options) {\r\n return commonFetcdh(url, options, 'GET')\r\n}\r\n\r\n/**\r\n * POST请求\r\n * @param url 请求地址\r\n * @param options 请求参数\r\n */\r\nfunction POST(url, options) {\r\n return commonFetcdh(url, options, 'POST')\r\n}\r\n```\r\n\r\n```http\r\nGET('https://www.xxxxxx.com/search/error.html', {a:1,b:2})\r\nPOST('https://www.xxxxxx.com/search/error.html', {a:1,b:2})\r\n```\r\n\r\n## CORS跨域\r\n如果服务器支持 CORS, 则在客户端设置相应的 ``Access-Control-Allow-Origin`` 即可得到数据。\r\n\r\n```js\r\nlet myHeaders = new Headers({\r\n 'Access-Control-Allow-Origin': '*',\r\n 'Content-Type': 'text/plain'\r\n});\r\nfetch(url, {\r\n method: 'GET',\r\n headers: myHeaders,\r\n mode: 'cors'\r\n}).then((res) =\u003e {\r\n // TODO \r\n})\r\n```\r\n","cover":"","link":"introduction-to-fetch.html","preview":"\u003cp\u003efetch能让我们完成类似 XMLHttpRequest (XHR) 提供的ajax功能。它的主要区别是,Fetch API 使用了 Promise,它让接口更简单、简洁,避免了回调的复杂性,省去了使用复杂的 XMLHttpRequest API。\u003c/p\u003e\n","title":"浅谈fetch及跨域"},{"content":"\r\n## 1. 路由变化页面数据不刷新问题\r\n出现这种情况是因为依赖路由的params参数获取写在created生命周期里面,因为相同路由二次甚至多次加载的关系 没有达到监听,退出页面再进入另一个文章页面并不会运行created组件生命周期,导致文章数据还是第一次进入的数据。\r\n\r\n- 解决方法:watch监听路由是否变化。\r\n\r\n```js\r\n watch: {\r\n // 方法1\r\n '$route' (to, from) { //监听路由是否变化\r\n if(this.$route.params.articleId){// 判断条件1 判断传递值的变化\r\n //获取文章数据\r\n }\r\n }\r\n //方法2\r\n '$route'(to, from) {\r\n if (to.path == \"/page\") { /// 判断条件2 监听路由名 监听你从什么路由跳转过来的\r\n this.message = this.$route.query.msg \r\n }\r\n }\r\n \r\n}\r\n```\r\n\r\n## 2. 异步回调函数中使用this无法指向vue实例对象\r\n- 解决方法:变量赋值和箭头函数。\r\n\r\n```js\r\n //使用变量访问this实例\r\nlet self=this; \r\n setTimeout(function () { \r\n console.log(self);//使用self变量访问this实例\r\n },1000);\r\n \r\n //箭头函数访问this实例 因为箭头函数本身没有绑定this\r\n setTimeout(() =\u003e { \r\n console.log(this);\r\n }, 500);\r\n ```\r\n\r\n## 3. setInterval路由跳转继续运行并没有及时进行销毁\r\n比如一些弹幕,走马灯文字,这类需要定时调用的,路由跳转之后,因为组件已经销毁了,但是setInterval还没有销毁,还在继续后台调用,控制台会不断报错,如果运算量大的话,无法及时清除,会导致严重的页面卡顿。\r\n\r\n- 解决办法:在组件生命周期beforeDestroy停止setInterval。\r\n\r\n```js\r\n//组件销毁前执行的钩子函数,跟其他生命周期钩子函数的用法相同。\r\nbeforeDestroy(){\r\n //我通常是把setInterval()定时器赋值给this实例,然后就可以像下面这么停止。\r\n clearInterval(this.intervalId);\r\n},\r\n```\r\n\r\n## 4. 实现vue路由拦截浏览器的需求,进行一系列操作 草稿保存等等\r\n为了防止用户失误点错关闭按钮等等,导致没有保存已输入的信息(关键信息)。\r\n\r\n- 解决方法:(见下方代码)\r\n\r\n```js\r\nbeforeRouteLeave (to, from, next) {\r\n if(用户已经输入信息){\r\n //出现弹窗提醒保存草稿,或者自动后台为其保存\r\n }else{\r\n next(true);//用户离开\r\n }\r\n}\r\n```\r\n\r\n## 5. vue本地代理配置 解决跨域问题,仅限于dev环境\r\n这个本地代理用来解决开发环境下的跨域问题,跨域可谓老生常谈的问题了,proxy 在vue中配置代理非常简单。\r\n\r\n- 解决方法:(见下方代码)\r\n\r\n```js\r\n//比方说你要访问 http://192.168.1.xxx:8888/backEnd/paper这个接口\r\n//配置 config.js下面proxyTable对象\r\nproxyTable: {\r\n '/backEnd':{\r\n target:'http://192.168.1.xxx:8888', //目标接口域名有端口可以把端口也写上\r\n changeOrigin:true\r\n }\r\n},\r\n// 发送request请求\r\n axios.get('/backEnd/page') //按代理配置 匹配到/backEnd就代理到目标target地址\r\n .then((res) =\u003e {\r\n console.log(res) // 数据完全拿得到 配置成功\r\n this.newsList = res.data\r\n }, (err) =\u003e {\r\n console.log(err)\r\n })\r\n```","cover":"","link":"vue-vulnerable.html","preview":"\u003cp\u003evue如今可谓是一匹黑马,github star数已居第一位!前端开发对于vue的使用已经越来越多,它的优点就不做介绍了,本篇是我在使用vue过程中遇到的一些惊(坑)喜(爹)。\u003c/p\u003e\n","title":"Vue 使用中踩过的坑"},{"content":"\r\n## 1. 化繁为简的Watch\r\n\r\n```js\r\ncreated(){\r\n this.fetchPostList()\r\n},\r\nwatch: {\r\n searchInputValue(){\r\n this.fetchPostList()\r\n }\r\n}\r\n```\r\n组件创建的时候我们获取一次列表,同时监听input框,每当发生变化的时候重新获取一次筛选后的列表这个场景很常见,有没有办法优化一下呢?\r\n\r\n招式:\r\n首先,在watch中,可以直接使用函数的字面量名称;其次,声明 **immediate:true** 表示创建组件时立马执行一次。\r\n\r\n```js\r\nwatch: {\r\n searchInputValue:{\r\n handler: 'fetchPostList',\r\n immediate: true\r\n }\r\n```\r\n\r\n## 2. 一劳永逸的组件注册\r\n\r\n```js\r\nimport BaseButton from './baseButton'\r\nimport BaseIcon from './baseIcon'\r\nimport BaseInput from './baseInput'\r\n\r\nexport default {\r\n components: {\r\n BaseButton,\r\n BaseIcon,\r\n BaseInput\r\n }\r\n}\r\n```\r\n\r\n```html\r\n\u003cBaseInput\r\n v-model=\"searchText\"\r\n @keydown.enter=\"search\"\r\n/\u003e\r\n\u003cBaseButton @click=\"search\"\u003e\r\n \u003cBaseIcon name=\"search\"/\u003e\r\n\u003c/BaseButton\u003e\r\n```\r\n\r\n我们写了一堆基础UI组件,然后每次我们需要使用这些组件的时候,都得先import,然后声明components,很繁琐!秉持能偷懒就偷懒的原则,我们要想办法优化!\r\n\r\n招式:\r\n我们需要借助一下神器webpack,使用 require.context() 方法来创建自己的(模块)上下文,从而实现自动动态require组件。这个方法需要3个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式。\r\n\r\n我们在components文件夹添加一个叫global.js的文件,在这个文件里借助webpack动态将需要的基础组件统统打包进来。\r\n\r\n```js\r\nimport Vue from 'vue'\r\n\r\nfunction capitalizeFirstLetter(string) {\r\n return string.charAt(0).toUpperCase() + string.slice(1)\r\n}\r\n\r\nconst requireComponent = require.context(\r\n '.', false, /\\.vue$/\r\n)\r\n\r\nrequireComponent.keys().forEach(fileName =\u003e {\r\n const componentConfig = requireComponent(fileName)\r\n\r\n const componentName = capitalizeFirstLetter(\r\n fileName.replace(/^\\.\\//, '').replace(/\\.\\w+$/, '')\r\n )\r\n\r\n Vue.component(componentName, componentConfig.default || componentConfig)\r\n})\r\n```\r\n最后我们在main.js中import 'components/global.js',然后我们就可以随时随地使用这些基础组件,无需手动引入了。\r\n\r\n## 3. 釜底抽薪的router key\r\n\r\n\r\n下面这个场景真的是伤透了很多程序员的心…先默认大家用的是Vue-router来实现路由的控制。\r\n假设我们在写一个博客网站,需求是从/post-page/a,跳转到/post-page/b。然后我们惊人的发现,页面跳转后数据竟然没更新?!原因是vue-router”智能地”发现这是同一个组件,然后它就决定要复用这个组件,所以你在created函数里写的方法压根就没执行。通常的解决方案是监听$route的变化来初始化数据,如下:\r\n\r\n```js\r\ndata() {\r\n return {\r\n loading: false,\r\n error: null,\r\n post: null\r\n }\r\n},\r\nwatch: {\r\n '$route': {\r\n handler: 'resetData',\r\n immediate: true\r\n }\r\n},\r\nmethods: {\r\n resetData() {\r\n this.loading = false\r\n this.error = null\r\n this.post = null\r\n this.getPost(this.$route.params.id)\r\n },\r\n getPost(id){\r\n ......\r\n }\r\n}\r\n```\r\nbug是解决了,可每次这么写也太不优雅了吧?秉持着能偷懒则偷懒的原则,我们希望代码这样写:\r\n\r\n```js\r\ndata() {\r\n return {\r\n loading: false,\r\n error: null,\r\n post: null\r\n }\r\n},\r\ncreated () {\r\n this.getPost(this.$route.params.id)\r\n},\r\nmethods () {\r\n getPost(postId) {\r\n ......\r\n }\r\n}\r\n```\r\n招式:\r\n那要怎么样才能实现这样的效果呢,答案是给router-view添加一个unique的key,这样即使是公用组件,只要url变化了,就一定会重新创建这个组件。(虽然损失了一丢丢性能,但避免了无限的bug)。同时,注意我将key直接设置为路由的完整路径,一举两得。\r\n\r\n```js\r\n\u003crouter-view :key=\"$route.fullpath\"\u003e\u003c/router-view\u003e\r\n```\r\n\r\n## 第四招: 无所不能的render函数\r\n\r\nvue要求每一个组件都只能有一个根元素,当你有多个根元素时,vue就会给你报错\r\n\r\n```js\r\n\u003ctemplate\u003e\r\n \u003cli\r\n v-for=\"route in routes\"\r\n :key=\"route.name\"\r\n \u003e\r\n \u003crouter-link :to=\"route\"\u003e\r\n {{ route.title }}\r\n \u003c/router-link\u003e\r\n \u003c/li\u003e\r\n\u003c/template\u003e\r\n\r\n\r\n ERROR - Component template should contain exactly one root element.\r\n If you are using v-if on multiple elements, use v-else-if\r\n to chain them instead.\r\n```\r\n\r\n招式:\r\n那有没有办法化解呢,答案是有的,只不过这时候我们需要使用render()函数来创建HTML,而不是template。其实用js来生成html的好处就是极度的灵活功能强大,而且你不需要去学习使用vue的那些功能有限的指令API,比如v-for, v-if。(reactjs就完全丢弃了template)\r\n\r\n```js\r\nfunctional: true,\r\nrender(h, { props }) {\r\n return props.routes.map(route =\u003e\r\n \u003cli key={route.name}\u003e\r\n \u003crouter-link to={route}\u003e\r\n {route.title}\r\n \u003c/router-link\u003e\r\n \u003c/li\u003e\r\n )\r\n}\r\n```\r\n\r\n## 5. 无招胜有招的高阶组件\r\n\r\n当我们写组件的时候,通常我们都需要从父组件传递一系列的props到子组件,同时父组件监听子组件emit过来的一系列事件。举例子:\r\n\r\n```js\r\n//父组件\r\n\u003cBaseInput\r\n :value=\"value\"\r\n label=\"密码\"\r\n placeholder=\"请填写密码\"\r\n @input=\"handleInput\"\r\n @focus=\"handleFocus\"\r\n\u003c/BaseInput\u003e\r\n\r\n\r\n//子组件\r\n\u003ctemplate\u003e\r\n \u003clabel\u003e\r\n {{ label }}\r\n \u003cinput\r\n :value=\"value\"\r\n :placeholder=\"placeholder\"\r\n @focus=$emit('focus', $event)\"\r\n @input=\"$emit('input', $event.target.value)\"\u003e\r\n \u003c/label\u003e\r\n\u003c/template\u003e\r\n```\r\n\r\n招数:\r\n\r\n* 1.每一个从父组件传到子组件的props,我们都得在子组件的Props中显式的声明才能使用。这样一来,我们的子组件每次都需要申明一大堆props, 而类似placeholer这种dom原生的property我们其实完全可以直接从父传到子,无需声明。方法如下:\r\n\r\n```js\r\n \u003cinput\r\n :value=\"value\"\r\n v-bind=\"$attrs\"\r\n @input=\"$emit('input', $event.target.value)\"\r\n \u003e\r\n```\r\n\r\n$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定,并且可以通过 v-bind=”$attrs” 传入内部组件——在创建更高层次的组件时非常有用。\r\n\r\n* 2.注意到子组件的@focus=$emit('focus', $event)\"其实什么都没做,只是把event传回给父组件而已,那其实和上面类似,我完全没必要显式地申明:\r\n\r\n```js\r\n\u003cinput\r\n :value=\"value\"\r\n v-bind=\"$attrs\"\r\n v-on=\"listeners\"\r\n\u003e\r\n\r\ncomputed: {\r\n listeners() {\r\n return {\r\n ...this.$listeners,\r\n input: event =\u003e\r\n this.$emit('input', event.target.value)\r\n }\r\n }\r\n}\r\n```\r\n$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件——在创建更高层次的组件时非常有用。\r\n\r\n* 3.需要注意的是,由于我们input并不是BaseInput这个组件的根节点,而默认情况下父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。所以我们需要设置inheritAttrs:false,这些默认行为将会被去掉, 以上两点的优化才能成功。\r\n","cover":"","link":"vue-best5.html","preview":"\u003cp\u003e对大部分人来说,掌握Vue.js基本的几个API后就已经能够正常地开发前端网站。但如果你想更加高效地使用Vue来开发,成为Vue.js大师,那下面我要传授的这五招你一定得认真学习一下了。\u003c/p\u003e\n","title":"Vue.js最佳五招实践"},{"content":"\r\n因项目登录界面用到Loading和Toast, 网上逛了一圈, 全是一整套组件, 有些东西又用不上, 太冗余了, 无奈自己撸个。\r\n\r\n### 定义js和css\r\n\r\n* JS\r\n\r\n```js\r\nvar Toast = {};\r\nvar showToast = false,\r\n showLoad = false,\r\n toastVM = null,\r\n loadNode = null;\r\n\r\nToast.install = function (Vue, options) {\r\n var opt = {\r\n defaultType: 'bottom',\r\n duration: '2500',\r\n wordWrap: false\r\n };\r\n for (var property in options) {\r\n opt[property] = options[property];\r\n }\r\n\r\n Vue.prototype.$toast = function (tips, type) {\r\n var curType = type ? type : opt.defaultType;\r\n var wordWrap = opt.wordWrap ? 'lx-word-wrap' : '';\r\n var style = opt.width ? 'style=\"width: ' + opt.width + '\"' : '';\r\n var tmp = '\u003cdiv v-show=\"show\" :class=\"type\" class=\"lx-toast ' + wordWrap + '\" ' + style + '\u003e{{tip}}\u003c/div\u003e';\r\n\r\n if (showToast) {\r\n return;\r\n }\r\n if (!toastVM) {\r\n var toastTpl = Vue.extend({\r\n data: function () {\r\n return {\r\n show: showToast,\r\n tip: tips,\r\n type: 'lx-toast-' + curType\r\n }\r\n },\r\n template: tmp\r\n });\r\n toastVM = new toastTpl()\r\n var tpl = toastVM.$mount().$el;\r\n document.body.appendChild(tpl);\r\n }\r\n toastVM.type = 'lx-toast-' + curType;\r\n toastVM.tip = tips;\r\n toastVM.show = showToast = true;\r\n\r\n setTimeout(function () {\r\n toastVM.show = showToast = false;\r\n }, opt.duration)\r\n };\r\n\r\n ['bottom', 'center', 'top'].forEach(function (type) {\r\n Vue.prototype.$toast[type] = function (tips) {\r\n return Vue.prototype.$toast(tips, type)\r\n }\r\n });\r\n\r\n Vue.prototype.$loading = function (tips, type) {\r\n if (type == 'close') {\r\n loadNode.show = showLoad = false;\r\n } else {\r\n if (showLoad) {\r\n return;\r\n }\r\n var loadTpl = Vue.extend({\r\n data: function () {\r\n return {\r\n show: showLoad\r\n }\r\n },\r\n template: '\u003cdiv v-show=\"show\" class=\"lx-load-mark\"\u003e\u003cdiv class=\"lx-load-box\"\u003e\u003cdiv class=\"lx-loading\"\u003e\u003cdiv class=\"loading loading_0\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_1\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_2\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_3\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_4\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_5\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_6\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_7\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_8\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_9\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_10\"\u003e\u003c/div\u003e\u003cdiv class=\"loading loading_11\"\u003e\u003c/div\u003e\u003c/div\u003e\u003cdiv class=\"lx-load-content\"\u003e' + tips + '\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e'\r\n });\r\n loadNode = new loadTpl();\r\n var tpl = loadNode.$mount().$el;\r\n document.body.appendChild(tpl);\r\n loadNode.show = showLoad = true;\r\n }\r\n };\r\n\r\n ['open', 'close'].forEach(function (type) {\r\n Vue.prototype.$loading[type] = function (tips) {\r\n return Vue.prototype.$loading(tips, type)\r\n }\r\n });\r\n}\r\n\r\nmodule.exports = Toast;\r\n```\r\n\r\n* CSS\r\n\r\n```css\r\n.lx-toast {position:fixed;bottom:100px;left:50%;box-sizing:border-box;max-width:80%;height:40px;line-height:20px;padding:10px 20px;transform:translateX(-50%);-webkit-transform:translateX(-50%);text-align:center;z-index:9999;font-size:14px;color:#fff;border-radius:5px;background:rgba(0,0,0,0.7);animation:show-toast .5s;-webkit-animation:show-toast .5s;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}\r\n.lx-toast.lx-word-wrap {width:80%;white-space:inherit;height:auto;}\r\n.lx-toast.lx-toast-top {top:50px;bottom:inherit;}\r\n.lx-toast.lx-toast-center {top:50%;margin-top:-20px;bottom:inherit;}\r\n@keyframes show-toast {from {opacity:0;transform:translate(-50%,-10px);-webkit-transform:translate(-50%,-10px);}\r\nto {opacity:1;transform:translate(-50%,0);-webkit-transform:translate(-50%,0);}\r\n}\r\n.lx-load-mark {position:fixed;left:0;top:0;width:100%;height:100%;z-index:9999;}\r\n.lx-load-box {position:fixed;z-index:3;width:7.6em;min-height:7.6em;top:180px;left:50%;margin-left:-3.8em;background:rgba(0,0,0,0.7);text-align:center;border-radius:5px;color:#FFFFFF;}\r\n.lx-load-content {margin-top:64%;font-size:14px;}\r\n.lx-loading {position:absolute;width:0px;left:50%;top:38%;}\r\n.loading {position:absolute;top:-1px;opacity:0.25;}\r\n.loading:before {content:\" \";position:absolute;width:9.14px;height:3.08px;background:#d1d1d5;box-shadow:rgba(0,0,0,0.0980392) 0px 0px 1px;border-radius:1px;-webkit-transform-origin:left 50% 0px;transform-origin:left 50% 0px;}\r\n.loading_0 {-webkit-animation:opacity-0 1.25s linear infinite;animation:opacity-0 1.25s linear infinite;}\r\n.loading_0:before {-webkit-transform:rotate(0deg) translate(7.92px,0px);transform:rotate(0deg) translate(7.92px,0px);}\r\n.loading_1 {-webkit-animation:opacity-1 1.25s linear infinite;animation:opacity-1 1.25s linear infinite;}\r\n.loading_1:before {-webkit-transform:rotate(30deg) translate(7.92px,0px);transform:rotate(30deg) translate(7.92px,0px);}\r\n.loading_2 {-webkit-animation:opacity-2 1.25s linear infinite;animation:opacity-2 1.25s linear infinite;}\r\n.loading_2:before {-webkit-transform:rotate(60deg) translate(7.92px,0px);transform:rotate(60deg) translate(7.92px,0px);}\r\n.loading_3 {-webkit-animation:opacity-3 1.25s linear infinite;animation:opacity-3 1.25s linear infinite;}\r\n.loading_3:before {-webkit-transform:rotate(90deg) translate(7.92px,0px);transform:rotate(90deg) translate(7.92px,0px);}\r\n.loading_4 {-webkit-animation:opacity-4 1.25s linear infinite;animation:opacity-4 1.25s linear infinite;}\r\n.loading_4:before {-webkit-transform:rotate(120deg) translate(7.92px,0px);transform:rotate(120deg) translate(7.92px,0px);}\r\n.loading_5 {-webkit-animation:opacity-5 1.25s linear infinite;animation:opacity-5 1.25s linear infinite;}\r\n.loading_5:before {-webkit-transform:rotate(150deg) translate(7.92px,0px);transform:rotate(150deg) translate(7.92px,0px);}\r\n.loading_6 {-webkit-animation:opacity-6 1.25s linear infinite;animation:opacity-6 1.25s linear infinite;}\r\n.loading_6:before {-webkit-transform:rotate(180deg) translate(7.92px,0px);transform:rotate(180deg) translate(7.92px,0px);}\r\n.loading_7 {-webkit-animation:opacity-7 1.25s linear infinite;animation:opacity-7 1.25s linear infinite;}\r\n.loading_7:before {-webkit-transform:rotate(210deg) translate(7.92px,0px);transform:rotate(210deg) translate(7.92px,0px);}\r\n.loading_8 {-webkit-animation:opacity-8 1.25s linear infinite;animation:opacity-8 1.25s linear infinite;}\r\n.loading_8:before {-webkit-transform:rotate(240deg) translate(7.92px,0px);transform:rotate(240deg) translate(7.92px,0px);}\r\n.loading_9 {-webkit-animation:opacity-9 1.25s linear infinite;animation:opacity-9 1.25s linear infinite;}\r\n.loading_9:before {-webkit-transform:rotate(270deg) translate(7.92px,0px);transform:rotate(270deg) translate(7.92px,0px);}\r\n.loading_10 {-webkit-animation:opacity-10 1.25s linear infinite;animation:opacity-10 1.25s linear infinite;}\r\n.loading_10:before {-webkit-transform:rotate(300deg) translate(7.92px,0px);transform:rotate(300deg) translate(7.92px,0px);}\r\n.loading_11 {-webkit-animation:opacity-11 1.25s linear infinite;animation:opacity-11 1.25s linear infinite;}\r\n.loading_11:before {-webkit-transform:rotate(330deg) translate(7.92px,0px);transform:rotate(330deg) translate(7.92px,0px);}\r\n@-webkit-keyframes opacity-0 {0% {opacity:0.25;}\r\n0.01% {opacity:0.25;}\r\n0.02% {opacity:1;}\r\n60.01% {opacity:0.25;}\r\n100% {opacity:0.25;}\r\n}\r\n@-webkit-keyframes opacity-1 {0% {opacity:0.25;}\r\n8.34333% {opacity:0.25;}\r\n8.35333% {opacity:1;}\r\n68.3433% {opacity:0.25;}\r\n100% {opacity:0.25;}\r\n}\r\n@-webkit-keyframes opacity-2 {0% {opacity:0.25;}\r\n16.6767% {opacity:0.25;}\r\n16.6867% {opacity:1;}\r\n76.6767% {opacity:0.25;}\r\n100% {opacity:0.25;}\r\n}\r\n@-webkit-keyframes opacity-3 {0% {opacity:0.25;}\r\n25.01% {opacity:0.25;}\r\n25.02% {opacity:1;}\r\n85.01% {opacity:0.25;}\r\n100% {opacity:0.25;}\r\n}\r\n@-webkit-keyframes opacity-4 {0% {opacity:0.25;}\r\n33.3433% {opacity:0.25;}\r\n33.3533% {opacity:1;}\r\n93.3433% {opacity:0.25;}\r\n100% {opacity:0.25;}\r\n}\r\n@-webkit-keyframes opacity-5 {0% {opacity:0.270958333333333;}\r\n41.6767% {opacity:0.25;}\r\n41.6867% {opacity:1;}\r\n1.67667% {opacity:0.25;}\r\n100% {opacity:0.270958333333333;}\r\n}\r\n@-webkit-keyframes opacity-6 {0% {opacity:0.375125;}\r\n50.01% {opacity:0.25;}\r\n50.02% {opacity:1;}\r\n10.01% {opacity:0.25;}\r\n100% {opacity:0.375125;}\r\n}\r\n@-webkit-keyframes opacity-7 {0% {opacity:0.479291666666667;}\r\n58.3433% {opacity:0.25;}\r\n58.3533% {opacity:1;}\r\n18.3433% {opacity:0.25;}\r\n100% {opacity:0.479291666666667;}\r\n}\r\n@-webkit-keyframes opacity-8 {0% {opacity:0.583458333333333;}\r\n66.6767% {opacity:0.25;}\r\n66.6867% {opacity:1;}\r\n26.6767% {opacity:0.25;}\r\n100% {opacity:0.583458333333333;}\r\n}\r\n@-webkit-keyframes opacity-9 {0% {opacity:0.687625;}\r\n75.01% {opacity:0.25;}\r\n75.02% {opacity:1;}\r\n35.01% {opacity:0.25;}\r\n100% {opacity:0.687625;}\r\n}\r\n@-webkit-keyframes opacity-10 {0% {opacity:0.791791666666667;}\r\n83.3433% {opacity:0.25;}\r\n83.3533% {opacity:1;}\r\n43.3433% {opacity:0.25;}\r\n100% {opacity:0.791791666666667;}\r\n}\r\n@-webkit-keyframes opacity-11 {0% {opacity:0.895958333333333;}\r\n91.6767% {opacity:0.25;}\r\n91.6867% {opacity:1;}\r\n51.6767% {opacity:0.25;}\r\n100% {opacity:0.895958333333333;}\r\n}\r\n```\r\n\r\n### Main.js引入Toast\r\n\r\n```js\r\nimport './components/Toast/toast.css';\r\nimport Toast from './components/Toast/toast.js';\r\n\r\nVue.use(Toast);\r\n```\r\n\r\n### 页面调用\r\n\r\n```js\r\nthis.$toast.top('要显示内容'); //在顶部显示\r\n```\r\n\r\n```js\r\nthis.$toast.center('要显示内容'); //在中部显示\r\n```\r\n\r\n```js\r\nthis.$toast.bottom('要显示内容'); //在低部显示(默认在底部显示)\r\n```\r\n\r\n```js\r\nthis.$loading('要显示内容'); //显示Loading\r\n```\r\n\r\n```js\r\nthis.$loading.close(); //关闭Loading\r\n```\r\n","cover":"","link":"vue-loading-toast.html","preview":"\u003cp\u003e因项目登录界面用到Loading和Toast, 网上逛了一圈, 全是一整套组件, 有些东西又用不上, 太冗余了, 无奈自己撸个。\u003c/p\u003e\n","title":"Vue自定义Loading、Toast组件"},{"content":"\r\n\u003e 首先说一下我遇到的需求。\r\n\r\n\u003e 有一个商品列表页,当列表滚动到底部时,继续往上拉,加载更多商品,里面的数据都是后端返回的,接口情况大致如下:\r\n\r\n\u003e www.xxx.com/?``limit``=xxx\u0026``offset``=xxx\r\n\r\n\u003e ``limit``是控制每次上拉刷新的数量,``offset``是控制从当前商品开始往下加载。\r\n\r\n# 实现原理\r\n当第一次访问接口时,传递2个必备参数(即limit和offset参数),后台返回数据过来,在请求成功的回调函数中,取出数据,渲染到视图层,并把Toast在列表显示出来;当判断返回的数据长度为0时,则没有数据可取,并把“没有更多了”显示出来。\r\n当用户已经滚动到列表底部(这里使用到小程序提供的onReachBottom事件),当每次触发onReachBottom事件,offset就会增加,再把2个必备参数(第2次加载,需要返回数据的个数)给后台,后台把其余的数据返回给前台,前台在原来数据的基础上往下增加新的商品数据。\r\n\r\n```js\r\n Page({\r\n \tdata: {\r\n \t\torigin_limit: 6, //控制每次加载的数量\r\n \t\torigin_offset: 0, //先初始化商品起始点\r\n \t\tproList: [] //放置返回数据的数组 \r\n \t}\r\n })\r\n```\r\n\r\n```js \r\n//下拉加载更多\r\n onReachBottom(){\r\n let that = this;\r\n let prolistAdd = that.data.proList;\r\n\r\n wx.showLoading({ //滚动到底部,弹出Loading。\r\n title: '拼命加载中..',\r\n duration: 5000\r\n })\r\n\r\n wx.request({\r\n url: 'www.xxx.com',\r\n data: {\r\n limit: that.data.orgin_limit,\r\n offset: that.data.orgin_offset\r\n },\r\n success: function (res) {\r\n wx.hideLoading(); //当请求成功时,隐藏Loading。\r\n that.setData({\r\n proList: prolistAdd.concat(res.data.objects), //在原来数据的基础上,增加新加载的商品数据并渲染到视图层。\r\n orgin_offset: that.data.orgin_offset + 6 //当每次触发上拉事件,offset就会在原数值上增加6。\r\n })\r\n if (res.data.objects.length == 0) { //当判断返回的数据长度为0,则没有数据可取,并把“没有更多了”显示出来。\r\n wx.showToast({\r\n title: '没有更多了',\r\n icon: 'none',\r\n duration: 3000\r\n })\r\n }\r\n }\r\n })\r\n },\r\n```\r\n\r\n```js\r\n//把下拉加载更多放到onLoad里面即可\r\n onLoad: function () {\r\n this.onReachBottom();\r\n },\r\n```","cover":"","link":"weapp-loadmore.html","preview":"\u003cp\u003e微信小程序开发中遇到的坑, 总有些坑你得一个一个的跳啊,/(ㄒoㄒ)/~~当用户打开一个页面时,假设后台数据量庞大时,一次性地返回所有数据给客户端,页面的打开速度就会有所下降,而且用户只看上面的内容而不需要看后面的内容时,也浪费用户流量,基于优化的角度来考虑,后台不要一次性返回所有数据,当用户有需要再往下翻的时候,再加载更加数据出来。\u003c/p\u003e\n","title":"微信小程序上拉加载更多的思(坑)路"},{"content":"\r\nTalk is cheap, show your code: \r\n\r\n```js\r\n\u003ctemplate\u003e\r\n \u003cdiv class=\"loadmore\"\u003e\r\n \u003cslot\u003e\u003c/slot\u003e\r\n \u003cslot name=\"bottom\"\u003e\r\n \u003c/slot\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cstyle\u003e\r\n .loadmore{\r\n width:100%;\r\n }\r\n\u003c/style\u003e\r\n\r\n\u003cscript\u003e\r\n export default {\r\n name: 'loadmore',\r\n props: {\r\n maxDistance: {\r\n type: Number,\r\n default: 0\r\n },\r\n autoFill: {\r\n type: Boolean,\r\n default: true\r\n },\r\n distanceIndex: {\r\n type: Number,\r\n default: 2\r\n },\r\n bottomPullText: {\r\n type: String,\r\n default: '上拉刷新'\r\n },\r\n bottomDropText: {\r\n type: String,\r\n default: '释放更新'\r\n },\r\n bottomLoadingText: {\r\n type: String,\r\n default: '加载中...'\r\n },\r\n bottomDistance: {\r\n type: Number,\r\n default: 70\r\n },\r\n bottomMethod: {\r\n type: Function\r\n },\r\n bottomAllLoaded: {\r\n type: Boolean,\r\n default: false\r\n },\r\n },\r\n data() {\r\n return {\r\n // 最下面出现的div的位移\r\n translate: 0,\r\n // 选择滚动事件的监听对象\r\n scrollEventTarget: null,\r\n containerFilled: false,\r\n bottomText: '',\r\n // class类名\r\n bottomDropped: false,\r\n // 获取监听滚动元素的scrollTop\r\n bottomReached: false,\r\n // 滑动的方向 down---向下互动;up---向上滑动\r\n direction: '',\r\n startY: 0,\r\n startScrollTop: 0,\r\n // 实时的clientY位置\r\n currentY: 0,\r\n topStatus: '',\r\n // 上拉加载的状态 '' pull: 上拉中\r\n bottomStatus: '',\r\n };\r\n },\r\n watch: {\r\n // 改变当前加载在状态\r\n bottomStatus(val) {\r\n this.$emit('bottom-status-change', val);\r\n switch (val) {\r\n case 'pull':\r\n this.bottomText = this.bottomPullText;\r\n break;\r\n case 'drop':\r\n this.bottomText = this.bottomDropText;\r\n break;\r\n case 'loading':\r\n this.bottomText = this.bottomLoadingText;\r\n break;\r\n }\r\n }\r\n },\r\n methods: {\r\n onBottomLoaded() {\r\n this.bottomStatus = 'pull';\r\n this.bottomDropped = false;\r\n this.$nextTick(() =\u003e {\r\n if (this.scrollEventTarget === window) {\r\n document.body.scrollTop += 50;\r\n } else {\r\n this.scrollEventTarget.scrollTop += 50;\r\n }\r\n this.translate = 0;\r\n });\r\n // 注释\r\n if (!this.bottomAllLoaded \u0026\u0026 !this.containerFilled) {\r\n this.fillContainer();\r\n }\r\n },\r\n\r\n getScrollEventTarget(element) {\r\n let currentNode = element;\r\n while (currentNode \u0026\u0026 currentNode.tagName !== 'HTML' \u0026\u0026\r\n currentNode.tagName !== 'BODY' \u0026\u0026 currentNode.nodeType === 1) {\r\n let overflowY = document.defaultView.getComputedStyle(currentNode).overflowY;\r\n if (overflowY === 'scroll' || overflowY === 'auto') {\r\n return currentNode;\r\n }\r\n currentNode = currentNode.parentNode;\r\n }\r\n return window;\r\n },\r\n // 获取scrollTop\r\n getScrollTop(element) {\r\n if (element === window) {\r\n return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);\r\n } else {\r\n return element.scrollTop;\r\n }\r\n },\r\n bindTouchEvents() {\r\n this.$el.addEventListener('touchstart', this.handleTouchStart);\r\n this.$el.addEventListener('touchmove', this.handleTouchMove);\r\n this.$el.addEventListener('touchend', this.handleTouchEnd);\r\n },\r\n init() {\r\n this.bottomStatus = 'pull';\r\n // 选择滚动事件的监听对象\r\n this.scrollEventTarget = this.getScrollEventTarget(this.$el);\r\n if (typeof this.bottomMethod === 'function') {\r\n // autoFill 属性的实现 注释\r\n this.fillContainer();\r\n // 绑定滑动事件\r\n this.bindTouchEvents();\r\n }\r\n },\r\n // autoFill 属性的实现 注释\r\n fillContainer() {\r\n if (this.autoFill) {\r\n this.$nextTick(() =\u003e {\r\n if (this.scrollEventTarget === window) {\r\n this.containerFilled = this.$el.getBoundingClientRect().bottom \u003e=\r\n document.documentElement.getBoundingClientRect().bottom;\r\n } else {\r\n this.containerFilled = this.$el.getBoundingClientRect().bottom \u003e=\r\n this.scrollEventTarget.getBoundingClientRect().bottom;\r\n }\r\n if (!this.containerFilled) {\r\n this.bottomStatus = 'loading';\r\n this.bottomMethod();\r\n }\r\n });\r\n }\r\n },\r\n // 获取监听滚动元素的scrollTop\r\n checkBottomReached() {\r\n if (this.scrollEventTarget === window) {\r\n return document.body.scrollTop + document.documentElement.clientHeight \u003e= document.body.scrollHeight;\r\n } else {\r\n // getBoundingClientRect用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。 right是指元素右边界距窗口最左边的距离,bottom是指元素下边界距窗口最上面的距离。\r\n return this.$el.getBoundingClientRect().bottom \u003c= this.scrollEventTarget.getBoundingClientRect().bottom + 1;\r\n }\r\n },\r\n // ontouchstart 事件\r\n handleTouchStart(event) {\r\n // 获取起点的y坐标\r\n this.startY = event.touches[0].clientY;\r\n this.startScrollTop = this.getScrollTop(this.scrollEventTarget);\r\n this.bottomReached = false;\r\n if (this.bottomStatus !== 'loading') {\r\n this.bottomStatus = 'pull';\r\n this.bottomDropped = false;\r\n }\r\n },\r\n // ontouchmove事件\r\n handleTouchMove(event) {\r\n if (this.startY \u003c this.$el.getBoundingClientRect().top \u0026\u0026 this.startY \u003e this.$el.getBoundingClientRect().bottom) {\r\n // 没有在需要滚动的范围内滚动,不再监听scroll\r\n return;\r\n }\r\n // 实时的clientY位置\r\n this.currentY = event.touches[0].clientY;\r\n // distance 移动位置和开始位置的差值 distanceIndex---\r\n let distance = (this.currentY - this.startY) / this.distanceIndex;\r\n // 根据 distance 判断滑动的方向 并赋予变量 direction down---向下互动;up---向上滑动\r\n this.direction = distance \u003e 0 ? 'down' : 'up';\r\n if (this.direction === 'up') {\r\n // 获取监听滚动元素的scrollTop\r\n this.bottomReached = this.bottomReached || this.checkBottomReached();\r\n }\r\n if (typeof this.bottomMethod === 'function' \u0026\u0026 this.direction === 'up' \u0026\u0026\r\n this.bottomReached \u0026\u0026 this.bottomStatus !== 'loading' \u0026\u0026 !this.bottomAllLoaded) {\r\n // 有加载函数,是向上拉,有滚动距离,不是正在加载ajax,没有加载到最后一页\r\n event.preventDefault();\r\n event.stopPropagation();\r\n if (this.maxDistance \u003e 0) {\r\n this.translate = Math.abs(distance) \u003c= this.maxDistance\r\n ? this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance : this.translate;\r\n } else {\r\n this.translate = this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance;\r\n }\r\n if (this.translate \u003e 0) {\r\n this.translate = 0;\r\n }\r\n this.bottomStatus = -this.translate \u003e= this.bottomDistance ? 'drop' : 'pull';\r\n }\r\n },\r\n // ontouchend事件\r\n handleTouchEnd() {\r\n if (this.direction === 'up' \u0026\u0026 this.bottomReached \u0026\u0026 this.translate \u003c 0) {\r\n this.bottomDropped = true;\r\n this.bottomReached = false;\r\n if (this.bottomStatus === 'drop') {\r\n this.translate = '-50';\r\n this.bottomStatus = 'loading';\r\n this.bottomMethod();\r\n } else {\r\n this.translate = '0';\r\n this.bottomStatus = 'pull';\r\n }\r\n }\r\n this.direction = '';\r\n }\r\n },\r\n mounted() {\r\n this.init();\r\n }\r\n };\r\n\u003c/script\u003e\r\n```\r\n建议抽出来放到common目录下,然后哪个页面需要,就在哪个页面import即可, 然后在引入他的页面写法如下:\r\n```js\r\n\u003ctemplate\u003e\r\n \u003csection class=\"finan\"\u003e\r\n \u003c!-- 上拉加载更多 --\u003e\r\n \u003cload-more\r\n :bottom-method=\"loadBottom\"\r\n :bottom-all-loaded=\"allLoaded\"\r\n :bottomPullText='bottomText'\r\n :auto-fill=\"false\"\r\n @bottom-status-change=\"handleBottomChange\"\r\n ref=\"loadmore\"\u003e\r\n \u003cdiv\u003e\r\n\t 这里写你需要的另外的模块\r\n \u003c/div\u003e\r\n \u003cdiv v-show=\"loading\" slot=\"bottom\" class=\"loading\"\u003e 这个div是为让上拉加载的时候显示一张加载的gif图\r\n \u003cimg src=\"./../../assets/main/uploading.gif\"\u003e\r\n \u003c/div\u003e\r\n \u003c/load-more\u003e\r\n \u003c/section\u003e\r\n\u003c/template\u003e\r\n```\r\n然后在data里和methods设置如下:\r\n```js\r\nexport default {\r\n name: 'FinancialGroup',\r\n props:{\r\n\t\t\r\n },\r\n data () {\r\n return {\r\n // 上拉加载数据\r\n scrollHeight: 0,\r\n scrollTop: 0,\r\n containerHeight: 0,\r\n loading: false,\r\n allLoaded: false,\r\n bottomText: '上拉加载更多...',\r\n bottomStatus: '',\r\n pageNo: 1,\r\n totalCount: '',\r\n }\r\n },\r\n methods: {\r\n /* 下拉加载 */\r\n _scroll: function(ev) {\r\n ev = ev || event;\r\n this.scrollHeight = this.$refs.innerScroll.scrollHeight;\r\n this.scrollTop = this.$refs.innerScroll.scrollTop;\r\n this.containerHeight = this.$refs.innerScroll.offsetHeight;\r\n },\r\n loadBottom: function() {\r\n this.loading = true;\r\n this.pageNo += 1; // 每次更迭加载的页数\r\n if (this.pageNo == this.totalGetCount) {\r\n // 当allLoaded = true时上拉加载停止\r\n this.loading = false;\r\n this.allLoaded = true;\r\n }\r\n api.commonApi.then(res =\u003e {\r\n setTimeout(() =\u003e {\r\n \t 要使用的后台返回的数据写在setTimeout里面\r\n this.$nextTick(() =\u003e {\r\n this.loading = false;\r\n \t })\r\n }, 1000)\r\n });\r\n },\r\n handleBottomChange(status) {\r\n this.bottomStatus = status;\r\n },\r\n }\r\n```","cover":"","link":"vue-loadmore.html","preview":"\u003cp\u003e菩提本无树,明镜亦非台,本来无一物,何处惹尘埃。世上本有坑, 踩得多了也就释然了。\u003c/p\u003e\n","title":"基于Vue写的上拉加载更多"},{"content":"\r\nVue应用在组件化之后,通常存在着 父子组件、兄弟组件、跨级组件 等组件关系,那么组件之间如何进行通信;Vue2.4提供了两种新的组件通讯方法。\r\n\r\n在 Vue 中,父子组件的关系可以总结为 props down、events up。\r\n\r\n* 父子组件通信 :父组件通过 props 向下传递数据给子组件\r\n* 子父组件通信 :子组件通过 events 给父组件发送消息\r\n - 使用 $on(eventName) 监听事件 \r\n - 使用 $emit(eventName) 触发事件\r\n\r\n* 非父子组件通信 :使用一个空的 Vue 实例作为中央事件总线\r\n\r\n在Vue 2.4 版本引入了组件通讯的新方式。\r\n\r\n#### 1. 重新引入 .sync 修饰符\r\n\r\n熟悉 Vue1.x 的朋友一定对 .sync 修饰器并不陌生。在Vue1.x 中我们可能会需要对一个 prop 进行『双向绑定』。当一个 子组件 改变了一个 prop的值时,这个变化也会 同步到父组件中 所绑定的值。\r\n\r\n因为它破坏了『单向数据流』的假设, .sync 在2.0版本被移除,引起了广泛的讨论。在2.3.0版本 .sync 又回来了,不过与1.x不同。\r\n\r\n这次只是原有语法的语法糖(syntax sugar)包装而成,其背后实现原理是,在组件上自动扩充一个额外的 v-on 监听器:\r\n\r\n代码如下:\r\n```html\r\n\u003ccomp:foo.sync=\"bar\"\u003e\u003c/comp\u003e\r\n```\r\n\r\n会被扩充为:\r\n```html\r\n\u003cchild:bar=\"foo\"@update:bar=\"e =\u003e foo = e\"\u003e\r\n```\r\n对于子组件,如果想要更新 foo 的值,则需要显式地触发一个事件,而不是直接修改 prop:\r\n```js\r\nthis.$emit('update:bar', newValue)\r\n```\r\n#### 2. $attrs 与 $listeners\r\n\r\n多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点杀鸡用牛刀。Vue 2.4 版本提供了另一种方法,使用 v-bind=”$attrs”, 将父组件中不被认为 props特性绑定的属性传入子组件中,通常配合 interitAttrs 选项一起使用。\r\n\r\n* 2.1 interitAttrs\r\n\r\n在版本 2.4 之前,默认情况下父作用域的不被作为props特性绑定的属性,将会作为普通的 HTML 属性,应用在跟元素上。\r\n```html\r\n // parent.vue\r\n\u003ctemplate\u003e\r\n \u003cchild-commpent:foo=\"f\":boo=\"b\"\u003e\u003c/child-comment\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nconst childComment = ()=\u003e import('./childCom.vue')\r\nexport default {\r\n data () {\r\n return {\r\n f: 'Hello world!'\r\n b: 'Hello Vue!'\r\n } \r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n```html\r\n// childComment.vue\r\n\u003ctemplate\u003e\r\n \u003cdiv\u003e{{ foo }}\u003cdiv\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nexport default {\r\n props: ['foo'] //父作用域的boo不被作为props绑定\r\n}\r\n\u003c/script\u003e\r\n```\r\n//boo会作为普通的 HTML 属性,应用在跟元素上。\r\n```html\r\n\u003cdiv boo=\"Hello Vue!\"\u003eHello world!\u003c/div\u003e\r\n```html\r\n设置 interitAttrs 为 false,之后,不会应用到跟元素上。\r\n```html\r\n// childCom.vue\r\n\u003ctemplate\u003e\r\n \u003cdiv\u003e{{ foo }}\u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\n export default {\r\n props: ['foo'],\r\n inheritAttrs: false\r\n }\r\n\u003c/script\u003e\r\n```\r\n\r\n//boo会作为普通的 HTML 属性,应用在跟元素上。\r\n```html\r\n\u003cdiv\u003eHello world!\u003c/div\u003e\r\n```\r\n\r\n* 2.2 $attrs, $listeners\r\n\r\n在Vue 2.4 版本,配合 interitAttrs选项, 父组件中未被props(v-on)绑定的属性(事件) 可以在子组件中,通过 $attrs , $listeners 获取。\r\n```html\r\n// demo.vue\r\n \u003ctemplate\u003e\r\n \u003cdiv\u003e\r\n \u003cchild-com:foo=\"foo\":boo=\"boo\":coo=\"coo\":doo=\"doo\"\u003e\u003c/child-com\u003e\r\n \u003c/div\u003e\r\n \u003c/tempalte\u003e\r\n \u003cscript\u003e\r\n const childCom = ()=\u003e import('./childCom1.vue')\r\n export default {\r\n data () {\r\n return {\r\n foo: 'Hello World!',\r\n boo: 'Hello Javascript!',\r\n coo: 'Hello Vue',\r\n doo: 'Last'\r\n }\r\n },\r\n components: { childCom }\r\n }\r\n \u003c/script\u003e\r\n```\r\n```html\r\n// childCom1.vue\r\n\u003ctemplate\u003e\r\n \u003cdiv\u003e\r\n \u003cp\u003efoo: {{ foo }}\u003c/p\u003e\r\n \u003cp\u003eattrs: {{ $attrs }}\u003c/p\u003e\r\n \u003cchild-com2v-bind=\"$attrs\"\u003e\u003c/child-com2\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\u003cscript\u003e\r\nconst childCom2 = ()=\u003e import('./childCom2.vue')\r\nexport default {\r\n props: ['foo'], // foo作为props属性绑定\r\n inheritAttrs: false,\r\n created () {\r\n console.log(this.$attrs) // { boo: 'Hello Javascript!', coo: 'Hello Vue', doo: 'Last' }\r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n```html\r\n// childCom2.vue\r\n\u003ctemplate\u003e\r\n \u003cdiv\u003e\r\n \u003cp\u003eboo: {{ boo }}\u003c/p\u003e\r\n \u003cp\u003eattrs: {{ $attrs }}\u003c/p\u003e\r\n \u003cchild-com3v-bind=\"$attrs\"\u003e\u003c/child-com3\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nconst childCom3 = ()=\u003e import('./childCom3.vue')\r\nexport default {\r\n props: ['boo'] // boo作为props属性绑定\r\n inheritAttrs: false,\r\n created () {\r\n console.log(this.$attrs) // { coo: 'Hello Vue', doo: 'Last' }\r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n#### 小结\r\n在Vue2.0被移除的 .sync 被重新加入到2.4版本,不同的是需要显式地触发一个事件,而不是直接修改 prop。\r\nVue2.4提供了 $attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。","cover":"","link":"vue-component-transmit.html","preview":"\u003cp\u003eVue应用在组件化之后,通常存在着 父子组件、兄弟组件、跨级组件等组件关系,那么组件之间如何进行通信;Vue2.4提供了两种新的组件通讯方法。\u003c/p\u003e\n","title":"吼啊! Vue2.4组件间通信新姿势"},{"content":"\r\n# flex属性\r\n一个flex布局的页面所具有的元素可分为2类。\r\n\r\n* 1.弹性容器\r\n* 2.弹性元素(包含在弹性容器内的元素)\r\n\r\n## 弹性容器\r\n弹性容器拥有横轴(main)和纵轴(cross)2种方向来决定着整体flex布局的流向,默认布局:横轴从左(main start)到右(main end),纵轴从上(cross start)到下(cross end)\r\n\r\n![](https://sfault-image.b0.upaiyun.com/134/028/1340284239-58c253c58c759_articlex)\r\n\r\n* flex-direction\r\n\r\n\u003e 指定了内部元素是如何在 flex 容器中布局的,定义了主轴的方向(正方向或反方向)。\r\n\r\nrow:默认,保持原布局不变,按照横轴方向布局,即从左(main start)到右(main end)\r\n\r\nrow-reverse:按照横方向布局,置换横默认方向,即从右(main end)到左(main start)\r\n\r\ncolumn: 按照纵轴默认方向布局,即从上(cross start)到下(cross end)\r\n\r\ncolumn-reverse:按照纵轴方向布局,置换纵轴默认方向,即从下(cross start)到上(cross end)\r\n\r\n![](https://sfault-image.b0.upaiyun.com/622/085/622085148-58c24de3a0d99_articlex)\r\n\r\n``注:flex-direction的值即为整个flex布局的主轴,flex-direction:row | row-reverse则于横轴为主轴,flex-direction:column | column-reverse则于纵轴为主轴,另外的为侧轴``\r\n\r\n* flex-wrap\r\n\r\n\u003e 指定 flex 元素单行显示还是多行显示 。如果允许换行,这个属性允许你控制行的堆叠方向\r\n\r\nnowrap: 默认,单行显示,假如宽度溢出,则自动压缩相应元素的宽度(压缩比例与flex-shrink相关),保持单行。\r\n\r\nwrap: 多行显示,假如宽度溢出,自动换行。\r\n\r\nwrap-reverse:多行显示,置换侧轴的方向。\r\n\r\n![](https://sfault-image.b0.upaiyun.com/363/850/3638502166-58c24eef71680_articlex)\r\n\r\n* flex-flow\r\n\r\n\u003e flex-direction 和 flex-wrap 的简写\r\n\r\n```css\r\n.class{\r\n flex-flow: column-reverse wrap;/*默认属性*/\r\n}\r\n```\r\n\r\n* justify-content\r\n\r\n\u003e 属性定义弹性元素在主轴方向的排列\r\n\r\nflex-start: 默认值,按照主轴方向进行排列\r\n\r\nflex-end: 置换主轴方向进行排列\r\n\r\ncenter: 向中点居中排列\r\n\r\nspace-between: 在主轴上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素与行首对齐,每行最后一个元素与行尾对齐。\r\n\r\nspace-around:在主轴上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素到行首的距离和每行最后一个元素到行尾的距离将会是相邻元素之间距离的一半。\r\n\r\n![](https://sfault-image.b0.upaiyun.com/348/615/3486154553-58c24f36cebc7_articlex)\r\n\r\n* align-items\r\n\r\n\u003e 属性定义了弹性元素在侧轴方向的排列\r\n\r\nflex-start: 默认值,按照侧轴方向排列\r\n\r\nflex-end: 置换侧轴方向进行排列,主轴不变\r\n\r\ncenter: 向中点居中排列\r\n\r\nbaseline: 所有元素向基线对齐。侧轴起点到元素基线距离最大的元素将会于侧轴起点对齐以确定基线(不是很明白,MDN上面的解释)\r\n\r\nstretch:拉伸弹性元素,填充侧轴空间\r\n\r\n![](https://sfault-image.b0.upaiyun.com/262/251/2622514220-58c24f56a61c0_articlex)\r\n\r\n* align-content\r\n\r\n\u003e 属性定义了当弹性元素超过一行时,元素在纵轴上的排列方式\r\n\r\nflex-start: 默认值,紧贴前一行,第一行紧贴边缘,与主轴排列方式有关\r\n\r\nflex-end: 置换侧轴方向进行排列,紧贴前一行\r\n\r\ncenter: 向中点居中排列,紧贴前一行\r\n\r\nspace-between: 所有行在容器中平均分布。相邻两行间距相等。容器的垂直轴起点边和终点边分别与第一行和最后一行的边对齐\r\n\r\nspace-around:所有行在容器中平均分布,相邻两行间距相等。容器的垂直轴起点边和终点边分别与第一行和最后一行的距离是相邻两行间距的一半\r\n\r\nstretch:平均分配侧轴空间,相邻行间距相等\r\n\r\n![](https://sfault-image.b0.upaiyun.com/174/364/1743644599-58c24f6beed1f_articlex)\r\n\r\n## 弹性元素\r\n包含在弹性容器里的每个节点都可以称为弹性元素,相应的,弹性元素也有自身的CSS属性\r\n\r\n* order\r\n\r\n\u003e 弹性元素根据自身order的值来进行排序\r\n\r\n![](https://sfault-image.b0.upaiyun.com/178/889/1788892906-58c24f83de31c_articlex)\r\n\r\n* align-self\r\n\r\n\u003e 属性和左右与align-items相同,但由于是直接作用在弹性元素上面,优先级比align-items高\r\n\r\n![](https://sfault-image.b0.upaiyun.com/336/214/3362144820-58c24fa3663e5_articlex)\r\n\r\n* flex-grow\r\n\r\n\u003e 定义弹性元素侧轴拉伸因子\r\n\r\n![](https://sfault-image.b0.upaiyun.com/256/359/256359781-58c24fb4858a3_articlex)\r\n\r\n* flex-shrink\r\n\r\n\u003e 与flex-grow相反,定义弹性元素的压缩比例,当弹性元素的总长度超过容器长度时,各自的压缩率(默认是1)\r\n\r\n![](https://sfault-image.b0.upaiyun.com/257/534/2575344999-58c24fc86313f_articlex)\r\n\r\n* flex-basis\r\n\r\n\u003e 用来代替width,优先级比width高(如果flex-basis和width有一个值为auto,那么另外一个非auto优先级更高)\r\n\r\n![](https://sfault-image.b0.upaiyun.com/206/563/2065634934-58c24fdb4953c_articlex)\r\n\r\n* flex\r\n\r\n\u003e flex-grow、flex-shrink、flex-basis的缩写\r\n\r\n```css\r\n.flex1{\r\n flex:none;\r\n /*\r\n 相当于\r\n flex-grow: 0;\r\n flex-shrink: 0;\r\n flex-basis: auto;\r\n */\r\n}\r\n\r\n.flex2{\r\n flex:1;\r\n /*\r\n 相当于\r\n flex-grow: 1;\r\n flex-shrink: 0;\r\n flex-basis: auto;\r\n */\r\n}\r\n\r\n.flex3{\r\n flex: 1 30px;\r\n /*\r\n 相当于\r\n flex-grow: 1;\r\n flex-shrink: 0;\r\n flex-basis: 30px;\r\n */\r\n}\r\n```\r\n","cover":"","link":"flex-basic.html","preview":"\u003cp\u003e使用弹性盒子的意义是在任何尺寸的屏幕上改变其和其子元素的尺寸填充屏幕可用空间。一个弹性框容器将延展它的子元素以填充可用空间,并且缩小它的子元素来避免溢出。\u003c/p\u003e\n","title":"Flex基础布局详解"},{"content":"\r\n## 传值\r\n\r\n```js\r\n// 定义参数\r\nlet params = {\r\n workItemId: 1,\r\n flowInstId: 21,\r\n itemStatus: false,\r\n type: type,\r\n srcId: srcId\r\n}\r\n// 保存参数\r\nthis.$store.dispatch('approvalParams', params);\r\n```\r\n\r\n* index.js\r\n\r\n```js\r\n\r\nimport Vue from 'vue'\r\nimport Vuex from 'vuex'\r\n\r\nimport actions from './actions'\r\nimport mutations from './mutations'\r\n\r\nVue.use(Vuex);\r\n\r\nexport default new Vuex.Store({\r\n modules:{\r\n mutations\r\n },\r\n actions\r\n});\r\n```\r\n\r\n* types.js\r\n\r\n```js\r\n// 审批历史页请求参数\r\nexport const APPROVAL_HISTORY_PARAMS = 'APPROVAL_HISTORY_PARAMS';\r\n```\r\n\r\n* actions.js\r\n\r\n```js\r\n// 引入 状态(类型),用于提交到mutations\r\nimport * as types from './types'\r\n\r\n// 导出\r\nexport default {\r\n // 保存 请求参数 approvalHistoryParams为上面的\"action名\"\r\n approvalHistoryParams: ({commit}, res) =\u003e {\r\n // 此处是触发mutation的 STORE_MOVIE_ID为\"mutation名\"\r\n commit(types.APPROVAL_HISTORY_PARAMS, res);\r\n }\r\n}\r\n```\r\n\r\n* mutations.js\r\n\r\n```js\r\nimport {\r\n APPROVAL_HISTORY_PARAMS // 审批历史参数\r\n} from './types'\r\n\r\n// 引入 getters\r\nimport getters from './getters'\r\n\r\n// 定义、初始化数据\r\nconst state = {\r\n approvalHistoryParams:{}\r\n}\r\n\r\n// 定义 mutations\r\nconst mutations = {\r\n // 匹配actions通过commit传过来的值,并改变state上的属性的值\r\n // 审批历史页 请求参数\r\n [APPROVAL_HISTORY_PARAMS](state, res){\r\n state.approvalHistoryParams = res; //state.数据名 = data\r\n }\r\n}\r\n\r\n// 导出\r\nexport default {\r\n state,\r\n mutations,\r\n getters\r\n}\r\n```\r\n\r\n * getters.js\r\n\r\n```js\r\n// 导出\r\nexport default {\r\n // 获取 审批历史参数\r\n approvalHistoryParams: (state) =\u003e {\r\n return state.approvalHistoryParams;\r\n }\r\n}\r\n```\r\n\r\n## 取值\r\n\r\n```js\r\nimport { mapGetters } from 'vuex';\r\n\r\nexport default {\r\n computed: {\r\n ...mapGetters([//...函数名 使用对象展开运算符将此对象混入到外部对象中\r\n 'approvalHistoryParams'\r\n ])\r\n },\r\n methods: {\r\n fetchData(){\r\n console.log(this.approvalHistoryParams.name);\r\n }\r\n }\r\n}\r\n```\r\n\r\n## PS\r\n\r\n* dispatch\r\n\r\n异步操作,例如向后台提交数据,写法:**this.$store.dispatch('mutations_name', val)**\r\n\r\n* commit\r\n\r\n同步操作,写法:**this.$store.commit('mutations_name', val)**\r\n","cover":"","link":"vuex-practice.html","preview":"\u003cp\u003e每天都要进步一点点。\u003c/p\u003e\n","title":"Vuex存值、取值的操作"},{"content":"\r\n对于很多新手来说,只是阅读文档是不好消化,我的建议是看看 vuex 的实例,通过研究实例来学习vuex。这样就会好理解多了。如果还是不能理解,最好办法就是先把store 的四个属性:state, getters, mutations, actions 记下来,然后再分析四个属性的特点,什么地方会用到,是怎样连接在一起的?通过这样问自己问题来进行学习。\r\n\r\n简单来说,vuex 就是使用一个 store 对象来包含所有的应用层级状态,也就是数据的来源。当然如果应用比较庞大,我们可以将 store 模块化,也就是每个模块都有自己的 store。一个 store 有四个属性:state, getters, mutations, actions。\r\n#### 1、State\r\nstate 上存放的,说的简单一些就是变量,也就是所谓的状态。没有使用 state 的时候,我们都是直接在 data 中进行初始化的,但是有了 state 之后,我们就把 data 上的数据转移到 state 上去了。当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:\r\n\r\n其实就是把 state 上保存的变量转移到计算属性上。当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。\r\n```js\r\ncomputed: mapState([\r\n // 映射 this.count 为 store.state.count\r\n 'count'\r\n])\r\n```\r\n为了更好地理解这个函数的作用,我们可以看看它的源代码。\r\n可以看到,mapstate 即可以接受对象,也可以接受数组。最终返回的是一个对象。并且 res[key] 的值都是来于 store 里的,红色那条代码就是。这样就把两个不相关的属性连接起来了,这也是映射。其他几个辅助函数也是类似的。\r\n\r\n#### 2、Getters\r\ngetters上简单来说就是存放一些公共函数供组件调用。getters 会暴露为 store.getters 对象,也就是说可以通过 store .getters[属性]来进行相应的调用。mapGetters 辅助函数仅仅是将 store 中的 getters 映射到局部计算属性,其实也就是从 getters 中获取对应的属性,跟解构类似。具体如下图这样我们就可以将 getters 中的 evenOrOdd 属性值传给对应组件中的 evenOrOdd 上。Getters 接受 state 作为其第一个参数,Getters 也可以接受其他 getters 作为第二个参数。\r\n\r\n#### 3、Mutations\r\nmutations 与事件类似,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。所以 mutations 上存放的一般就是我们要改变 state 的一些方法。\r\n\r\n```js\r\nconst store = new Vuex.Store({\r\n state: {\r\n count: 1\r\n },\r\n mutations: {\r\n increment (state) {\r\n // 变更状态\r\n state.count++\r\n }\r\n }\r\n})\r\n```\r\n我们不能直接调用一个 mutation handler。 这个选项更像是事件注册 :“当触发一个类型为 increment 的 mutation 时,调用此函数 。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:\r\n```js\r\nstore.commit('increment')\r\n```\r\n当 mutation 事件类型比较多的时候,我们可以使用常量替代 mutation 事件类型。同时把这些常量放在单独的文件中可以让我们的代码合作者对整个 app 包含的 mutation 一目了然:\r\n一条重要的原则就是要记住 mutation 必须是同步函数 。\r\n\r\n#### 4、Actions\r\n\r\n前面说了, mutation 像事件注册,需要相应的触发条件。而 Action 就那个管理触发条件的。\r\nAction 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。\r\n```js\r\nactions: {\r\n increment (context) {\r\n context.commit('increment')\r\n }\r\n }\r\n```\r\nAction 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。\r\n\r\n实践中,我们会经常会用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候)\r\n```js\r\nactions: {\r\n increment ({ commit }) {\r\n commit('increment')\r\n }\r\n}\r\n```\r\n还记得我们前面说过 mutation 像事件类型吗?因此需要我们给定某个动作来进行触发。而这就是分发 action。Action 通过 store.dispatch 方法触发:\r\n```js\r\nstore.dispatch('increment')\r\n```\r\n此外,我们还可以在我们可以在 action 内部执行 异步 操作:\r\n```js\r\nactions: {\r\n incrementAsync ({ commit }) {\r\n setTimeout(() =\u003e {\r\n commit('increment')\r\n }, 1000)\r\n }\r\n}\r\n```\r\n你在组件中使用``this.$store.dispatch('xxx')`` 分发 action,或者使用``mapActions``辅助函数将组件的 methods 映射为``store.dispatch``调用(需要先在根节点注入 store ):\r\n```js\r\nimport { mapActions } from 'vuex'\r\nexport default {\r\n // ...\r\n methods: {\r\n ...mapActions([\r\n 'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')\r\n ]),\r\n ...mapActions({\r\n add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')\r\n })\r\n }\r\n}\r\n```\r\n这句话意思其实是,当你使用了 ``mapActions``, 你就不需要再次使用 ``this.$store.dispatch('xxx')``,当你没使用的话,你可以需要手动去分法。\r\n什么时候用大家要根据情况而定的。","cover":"","link":"vuex-summary.html","preview":"\u003cp\u003e对于很多新手来说,只是阅读文档是不好消化,我的建议是看看 vuex的实例,通过研究实例来学习vuex。这样就会好理解多了。如果还是不能理解,最好办法就是先把store 的四个属性:state, getters, mutations, actions。\u003c/p\u003e\n","title":"Vuex 学习总结"},{"content":"\r\n### 父组件与子组件\r\n在vue中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。\r\n\r\n父组件\r\n``` vue\r\n\u003ctemplate\u003e \r\n \u003cdiv class=\"components-bus-container\"\u003e\r\n \u003cchildren-one :fatherDataOne=\"fatherDataOne\"\u003e\u003c/children-one\u003e\r\n \u003c/div\u003e \r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nexport default {\r\n data() {\r\n return {\r\n fatherDataOne: '',\r\n };\r\n },\r\n}\r\n\u003c/script\u003e\r\n```\r\n子组件\r\n``` vue\r\n\u003ctemplate\u003e\r\n \u003cdiv class=\"children-one-content\"\u003e\r\n \u003cdiv\u003e{{fatherDataOne}}\u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nexport default {\r\n data() {\r\n },\r\n props: ['fatherDataOne']\r\n}\r\n\u003c/script\u003e\r\n```\r\n`:fatherDataOne`为数据绑定写法,可以灵活的进行数据设置,如果直接绑定数据,写成`fatherDataOne=\"someData\"`即可\r\n\r\n### 子组件与父组件通信\r\n对于子组件将数据传给父组件,是通过`$emit`事件来实现的,在图中也可以体现,具体实现如下:\r\n子组件\r\n``` vue\r\n\u003ctemplate\u003e\r\n \u003cdiv class=\"children-one-content\"\u003e\r\n \u003cdiv\u003e{{fatherDataOne}}\u003c/div\u003e\r\n \u003cdiv @click=\"tellParent\"\u003e点击传递数据\u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nexport default {\r\n data() {\r\n },\r\n props: ['fatherDataOne'],\r\n methods: {\r\n tellParent() {\r\n this.$emit('listenOne', 'from childrenOne');\r\n }\r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n父组件\r\n``` vue\r\n\u003ctemplate\u003e \r\n \u003cdiv class=\"components-bus-container\"\u003e\r\n \u003cchildren-one :fatherDataOne=\"fatherDataOne\" @listenOne='getCompomentOne'\u003e\u003c/children-one\u003e\r\n \u003c/div\u003e \r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nexport default {\r\n data() {\r\n return {\r\n fatherDataOne: '',\r\n };\r\n },\r\n methods: {\r\n getCompomentOne(param) {\r\n console.log(param);\r\n }\r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n在父组件中绑定一个`listenOne`的事件,然后给这个事件添加`getCompomentOne`方法,当子组件children-one通过点击事件`tellParent`触发`listenOne`的时候,父组件里面的`getCompomentOne`方法就会执行,并且数据通过参数的形式传递给父组件,从而实现通信的功能。\r\n\r\n### 同级组件间通信\r\n对于同级间组件进行通信处理的方法是,新建一个Vue实例作为事件总线,具体实现如下:\r\n1.需要进行通信的同级组件引入`event.js`\r\n``` javascript\r\n\timport Vue from 'vue'; \r\n\tlet bus = new Vue(); \r\n\texport default bus; \r\n```\r\n2.组件`children-one`通过点击事件`sendMsgToTwo`触发`dataFromOne`\r\nchildren-one\r\n``` vue\r\n\u003ctemplate\u003e\r\n \u003cdiv class=\"children-one-content\"\u003e\r\n \u003cdiv\u003e{{fatherDataOne}}\u003c/div\u003e\r\n \u003cdiv @click=\"tellParent\"\u003e点击向父组件通信\u003c/div\u003e\r\n \u003cdiv @click=\"tellChildTwo\"\u003e点击向子组件2通信\u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nimport bus from 'path/event'\r\nexport default {\r\n data() {\r\n },\r\n props: ['fatherDataOne'],\r\n methods: {\r\n tellParent() {\r\n this.$emit('listenOne', 'from childrenOne');\r\n },\r\n tellChildTwo() {\r\n bus.$emit('dataFromOne', 'dataFromOne');\r\n }\r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n3.组件`children-two`中监听事件 `dataFromOne`\r\nchildren-two\r\n``` vue\r\n\u003ctemplate\u003e\r\n \u003cdiv class=\"children-two-content\"\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n\r\n\u003cscript\u003e\r\nimport bus from 'path/event'\r\nexport default {\r\n data() {\r\n },\r\n methods: {\r\n getMsgFromOne() {\r\n bus.$on('dataFromOne', data =\u003e {\r\n console.log(data);\r\n });\r\n }\r\n },\r\n mounted() {\r\n this.getMsgFromOne();\r\n }\r\n}\r\n\u003c/script\u003e\r\n```\r\n当`children-one`触发`dataFromOne`时,组件`children-two`中的监听事件`dataFromOne`就会被触发,数据通过参数的形式传入,从而实现同级组件间通信。\r\n\r\n### 扩展\r\n##### 1.父组件操作子组件的方法\r\n利用$refs实现这个功能\r\n父组件\r\n``` vue\r\n\u003cchildren-one :fatherDataOne=\"fatherDataOne\" @listenOne='getCompomentOne' ref=\"childrenOne\"\u003e\u003c/children-one\u003e\r\n```\r\n那么就可以通过`this.$refs.childrenOne.childenFunction()`来操作子组件中的方法。\r\n##### 2.父组件将数据传给子组件,出现`undefined`情况\r\n当父组件传递的数据是异步取值的时候,可能出现首先传过去是空值,然后异步刷新。对于这种情况处理的方法是`v-if`,当父组件拿到数据后,将其设置为true,从而避免undefined的情况。\r\n \r\n### 高级用法\r\n对于一些简单的组件间的通信个人觉得这些方式够用了,如果一个页面涉及到很多的组件,那么也许这种方法不是那么简洁了,这是就需要用到`vue`中很重要的一个插件了`vuex`,以下为官网对`vuex`的介绍:\r\n[vuex](https://vuex.vuejs.org/)是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 [devtools extension](https://github.com/vuejs/vue-devtools),提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。\r\n如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 global event bus 就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。","cover":"","link":"vue-component-communication.html","preview":"\u003cp\u003e最近在学习vue方面的的知识,毫无疑问,对于vue来说,组件化是其一个很大的特点,一方面可以提高代码的可读性,另一方面可以少写很多的代码,利于代码的维护。对于组件之间数据通信是一个必须要掌握的知识点,通信之间的方式一共可以分为三种,父组件与子组件,子组件与父组件,同级组件间通信。\u003c/p\u003e\n","title":"Vue组件之间通信"},{"content":"\r\nVue用$emit与$on来进行兄弟组件之间的通信\r\n```html\r\n\u003ctemplate\u003e\r\n \u003cdiv id=\"app\"\u003e\r\n \u003cdom-a\u003e\u003c/dom-a\u003e \r\n \u003cdom-b\u003e\u003c/dom-b\u003e \r\n \u003cdom-c\u003e\u003c/dom-c\u003e \r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n```\r\n\r\n```js\r\n \u003cscript\u003e\r\n var Event = new Vue();\r\n\r\n //组件A\r\n var A = {\r\n template: `\r\n \u003cdiv\u003e\r\n \u003cspan\u003e我是A组件的数据-\u003e{{a}}\u003c/span\u003e\r\n \u003cinput type=\"button\" value=\"把A数据传给C\" @click=\"send\"\u003e\r\n \u003c/div\u003e\r\n `,\r\n methods: {\r\n send () {\r\n Event.$emit(\"a-msg\", this.a);\r\n }\r\n },\r\n data () {\r\n return {\r\n a: \"我是a组件中数据\"\r\n }\r\n }\r\n };\r\n //组件B\r\n var B = {\r\n template: `\r\n \u003cdiv\u003e\r\n \u003cspan\u003e我是B组件的数据-\u003e{{a}}\u003c/span\u003e\r\n \u003cinput type=\"button\" value=\"把B数据传给C\" @click = \"send\"\u003e\r\n \u003c/div\u003e\r\n `,\r\n methods: {\r\n send () {\r\n Event.$emit(\"b-msg\", this.a);\r\n }\r\n },\r\n data () {\r\n return {\r\n a: \"我是b组件中数据\"\r\n }\r\n }\r\n };\r\n //组件C\r\n var C = {\r\n template: `\r\n \u003cdiv\u003e\r\n \u003ch3\u003e我是C组件\u003c/h3\u003e\r\n \u003cspan\u003e接收过来A的数据为: {{a}}\u003c/span\u003e\r\n \u003cbr\u003e\r\n \u003cspan\u003e接收过来B的数据为: {{b}}\u003c/span\u003e\r\n \u003c/div\u003e\r\n `,\r\n mounted () {\r\n //接收A组件的数据\r\n Event.$on(\"a-msg\", function (a) {\r\n this.a = a;\r\n }.bind(this));\r\n\r\n //接收B组件的数据\r\n Event.$on(\"b-msg\", function (a) {\r\n this.b = a;\r\n }.bind(this));\r\n },\r\n data () {\r\n return {\r\n a: \"\",\r\n b: \"\"\r\n }\r\n }\r\n };\r\n window.onload = function () {\r\n new Vue({\r\n el: \"#app\",\r\n components: {\r\n \"dom-a\": A,\r\n \"dom-b\": B,\r\n \"dom-c\": C\r\n }\r\n });\r\n };\r\n \u003c/script\u003e\r\n```\r\nVue用$emit与$on来进行跨页面之间的数据传输通信\r\non和emit的事件必须是在一个公共的实例上,才能触发。\r\n```js\r\nimport Vue from 'vue'\r\n\r\nexport var bus = new Vue()\r\nApp.vue里created方法里定义事件\r\nimport { bus } from 'bus.js'\r\n\r\ncreated () {\r\n bus.$on('tip', (text) =\u003e {\r\n alert(text)\r\n })\r\n}\r\nTest.vue组件内调用\r\nimport { bus } from 'bus.js'\r\nbus.$emit('tip', '123')\r\n```","cover":"","link":"vue-emit-on.html","preview":"","title":"Vue用$emit与$on来进行兄弟组件之间的通信"},{"content":"\r\n如果项目很大,组件很多,怎么样才能准确的、快速的寻找到我们想要的组件了??\r\n\r\n\r\n#### 1、$refs\r\n\r\n首先你的给子组件做标记。\r\n```html\r\n\u003cfirstchild ref=\"one\"\u003e\u003c/firstchild\u003e\r\n```\r\n\r\n然后在父组件中,通过this.$refs.one就可以访问了这个自组件了,包括访问自组件的data里面的数据,调用它的函数\r\n\r\n#### 2、$children\r\n\r\n他返回的是一个组件集合,如果你能清楚的知道子组件的顺序,你也可以使用下标来操作;\r\n```js\r\nfor(let i=0;i\u003cthis.$children.length;i++){\r\n console.log(this.$children[i].msg); //输出子组件的msg数据;\r\n }\r\n```\r\n接下来就来一个点的demo\r\n\r\n首先定义一个父组件:parentcomponent,\r\n\r\n在父组件中我又是使用了两个自组件(假如有一百个自组件) [明确一点,组件只能有一个根节点 ],根节点是啥,我不知道。。。。。。\r\n```html\r\n\u003ctemplate id=\"parentcomponent\"\u003e\r\n \u003cdiv \u003e\r\n \u003cp\u003ethis is a parent-component\u003c/p\u003e\r\n \u003cfirstchild ref=\"f1\"\u003e\u003c/firstchild\u003e\r\n \u003csecondchild ref=\"f2\"\u003e\u003c/secondchild\u003e\r\n \u003cbutton @click='show_child_of_parents'\u003eshow child msg\u003c/button\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n```\r\n分别给出两个字组件的定义:(第2个使用的是template,第1个是script)\r\n```html\r\n\u003cscript type=\"text/x-template\" id=\"childOne\"\u003e\r\n \u003cdiv\u003e\r\n \u003cp\u003ethis is first child\u003c/p\u003e\r\n \r\n //使用stop阻止默认事件(vue的事件处理机制)\r\n \u003cbutton @click.stop='getParent'\u003eget parent msg\u003c/button\u003e\r\n \u003c/div\u003e\r\n\u003c/script\u003e\r\n\r\n\u003ctemplate id=\"childSec\"\u003e\r\n \u003cdiv\u003e\r\n \u003cp\u003ethis is second child\u003c/p\u003e\r\n \u003c/div\u003e\r\n\u003c/template\u003e\r\n```\r\n组件模板定义好了,就是用:\r\n\r\n- 挂在元素:\r\n\r\n```js\r\n\u003cscript\u003e\r\n new Vue({\r\n el:\"#app\",\r\n data:{},\r\n components:{\r\n \"parent-component\":{\r\n template:'#parentcomponent', \r\n data(){\r\n return{msg:'这是父组件中的内容'} \r\n },\r\n methods:{\r\n show_child_of_parents(){\r\n //children方式访问自组件\r\n for(let i=0;i\u003cthis.$children.length;i++){\r\n console.log(this.$children[i].msg);\r\n }\r\n //通过$ref打标记,访问子组件 \r\n console.log(this.$refs.f1.msg);\r\n this.$refs.f1.getParent();\r\n }, \r\n }, \r\n \r\n components:{\r\n 'firstchild':{\r\n template:'#childOne',\r\n data(){\r\n return {msg:'这是第一个子组件'};\r\n },\r\n methods:{\r\n getParent(){\r\n let a=1;\r\n console.log(a);\r\n alert(this.$parent.msg);\r\n \r\n }\r\n },\r\n },\r\n \r\n 'secondchild':{\r\n template:'#childSec',\r\n data(){\r\n return {msg:\"这是第二个组件\"};\r\n }\r\n }\r\n \r\n }\r\n \r\n }\r\n }\r\n \r\n });\r\n\r\n\u003c/script\u003e\r\n```\r\n- 使用父组件了\r\n\r\n```html\r\n \u003cbody\u003e\r\n \u003cp\u003e\u003cstrong\u003e可以通过$refs访问父组件的子组件\u003c/strong\u003e\u003c/p\u003e\r\n \u003cdiv id=\"app\"\u003e\r\n \u003cparent-component\u003e\u003c/parent-component\u003e\r\n \u003c/div\u003e\r\n \u003c/body\u003e\r\n```\r\n\r\n#### 小结\r\n\r\n- 组件只能一个根节点\r\n\r\n- 可以在自组件中使用this.$parent.属性值,或者函数\r\n\r\n- 在父组件中可以使用this.$refs.组件的标记访问子组件,或者this.$children[i].属性访问子组件\r\n\r\n- 你需要注意this的指向","cover":"","link":"vue-children-refs-parent.html","preview":"\u003cp\u003e如果项目很大,组件很多,怎么样才能准确的、快速的寻找到我们想要的组件了??\u003c/p\u003e\n","title":"Vue组件($children,$refs,$parent)的使用"},{"content":"\r\n\r\n* 第一步,分别设置不同的接口地址。找到下面的文件:\r\n\r\n```js\r\n/config/dev.env.js\r\n/config/prod.env.js\r\n```\r\n其实,这两个文件就是针对生产环境和发布环境设置不同参数的文件。我们打开 **dev.env.js** 文件。代码如下:\r\n\r\n```js\r\nvar merge = require('webpack-merge')\r\nvar prodEnv = require('./prod.env')\r\n\r\nmodule.exports = merge(prodEnv, {\r\n NODE_ENV: '\"development\"'\r\n})\r\n```\r\n好,我们在 **NODE_ENV** 下面增加一项,代码如下:\r\n\r\n```js\r\nvar merge = require('webpack-merge')\r\nvar prodEnv = require('./prod.env')\r\n\r\nmodule.exports = merge(prodEnv, {\r\n NODE_ENV: '\"development\"',\r\n API_ROOT: '\"http://192.168.x.x/api\"'\r\n})\r\n```\r\n然后,我们编辑prod.env.js文件,\r\n\r\n```js\r\nmodule.exports = {\r\n NODE_ENV: '\"production\"',\r\n API_ROOT: '\"http://www.xxx.com/api\"'\r\n}\r\n```\r\n好。我们分别设定的路径已经有了,下面就是如何调用的问题了。\r\n\r\n* 第二部,在代码中调用设置好的参数\r\n以我们之前的演示代码为例。你自己的项目请根据你自己的情况调整。以下文件和代码仅供参考。\r\n\r\n我们打开src/config/api.js文件,将原来开头的代码\r\n\r\n```js\r\n// 配置API接口地址\r\nconst root = 'http://www.xxx.com/api/v1'\r\n```\r\n\r\n修改为\r\n\r\n```js\r\n// 配置API接口地址\r\nconst root = process.env.API_ROOT\r\n```\r\n\r\n然后就完成了我们的配置工作。最后,重启项目,就能使新配置的接口地址生效了。\r\n\r\n* 在经过这样的配置之后,我们在运行\r\n\r\n**npm run dev** 的时候,跑的就是测试接口。\r\n\r\n**npm run build** 打包项目的时候,打包的是服务器正式接口,我们就不用调来调去得了。\r\n","cover":"","link":"webpack-dev-build-env.html","preview":"\u003cp\u003e由于前后端分离,我们在开发项目的时候,往往会在同一个局域网进行开发。我们前端调用后端给的接口也是在局域网内部的。但是,当项目推到线上的时候,我们会从真实服务器上获取接口,因此,我们可能在测试接口和真实接口之间频繁切换,让人十分恶心。因此,我们有必要解决这个问题。\u003c/p\u003e\n","title":"webpack 给生产环境和发布环境配置不同的接口地址"},{"content":"\r\n### Babel\r\n\r\nES6标准虽然已经发布了,但是很多浏览器环境都还不支持,webpack是通过Babel这个转码器将ES6代码转为ES5,从而在现有环境执行。babel是在webpack的配置文件webpack.config.js的module参数中的loaders中配置,如下:\r\n\r\n```js\r\nmodule.exports = {\r\n\t...\r\n\tmodule: {\r\n\t\tloaders: [\r\n\t\t{\r\n\t\t\ttest: /\\.js$/,\r\n\t\t\tloader: 'babel',\r\n\t\t\texclude: /node_modules/\r\n\t\t},\r\n\t\t...\r\n\t\t]\r\n\t}\r\n}\r\n```\r\n\r\n配置完成后还需要安装”babel-loader”模块\r\n\r\n```js\r\nnpm i babel-loader -D\r\n```\r\n\r\n然后webpack就可以对用了ES6语法的js文件进行转码了。下面总结一些常用到的ES6语法。\r\n\r\n### let和const命令\r\n\r\n* let命令\r\n\r\nES6中let命令用来声明变量,用法类似于var,但是let所声明的变量是局部变量,只在let命令所在的代码块内有效。所以在for循环中很适合用let变量做计数器。\r\nlet变量不会像var变量那样会进行变量提升,变量一定要在声明后使用,否则会报错。\r\n只要块级作用域内存在let命令,它所声明的所有变量都绑定这个作业域,不收外部变量的影响,即形成了一个封闭的作用域。\r\nlet不允许在相同作用域内重复声明同一个变量。可以看出,let变量实际上为JavaScript新增了块级作用域\r\n\r\n* const命令\r\n\r\nconst声明一个只读的常量,一旦声明,值就不能改变。所以,const一旦声明就必须立即初始化,不能只声明不初始化。\r\nconst作用域和let命令相同,只在声明所在的块级作用域中有效。const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。\r\n\r\n### 函数的扩展\r\n\r\n* 函数参数的默认值\r\n\r\n在ES6之前不能直接为函数的参数指定默认值,只能采用变通的方法。ES6允许为函数参数设置默认值,直接写在参数定义的后面。\r\n\r\n```js\r\nfunction log(x, y='World') {\r\n\tconsole.log(x, y);\r\n}\r\nlog('Hello')\r\n```\r\n\r\n* 参数默认值的位置\r\n\r\n通常定义了默认值的参数应该是函数的尾参数,如果是非尾部的参数设置默认值,实际上这个参数是没法省略的。除非显式输入undefined。\r\n\r\n* 箭头函数\r\n\r\nES6允许使用“箭头”(=\u003e)定义函数。\r\n\r\n```js\r\nvar f = v =\u003e v;\r\n```\r\n\r\n上述函数等同于:\r\n\r\n```js\r\nvar f = function(v) {\r\n\treturn v;\r\n};\r\n```\r\n\r\n若箭头函数不需要参数或者需要多于一个参数,就使用一个圆括号代表参数部分。\r\n\r\n```js\r\nvar f = () =\u003e 5;\r\n// 等同于\r\nvar f = function() {\r\n\treturn 5;\r\n}\r\n```\r\n\r\n```js\r\nvar sum = (num1, num2) =\u003e num1 + num2;\r\n// 等同于\r\nvar sum = function(num1, num2) {\r\n\treturn num1 + num2;\r\n}\r\n```\r\n\r\n若箭头函数的代码部分多于一条语句,就要使用大括号将他们括起来,并且使用return语句返回。\r\n\r\n```js\r\nvar sum = (num1, num2) =\u003e { return num1 + num2; }\r\n```\r\n\r\n#### 使用注意点\r\n\r\n函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。\r\n不可当做构造函数,不可以使用new命令,否则会抛出错误。\r\n不可使用arguments对象,该对象在函数体内不存在,可以用Rest参数代替。\r\n\r\n### Module\r\n\r\nES6之前,JavaScript一直没有模块(module)体系,ES6在语言规格的层面上,实现了模块功能,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。\r\nES6模块不是对象,而是通过export命令显式指定输出的代码,输入时采用静态命令的形式。\r\n\r\n```js\r\nimport {stat, exists, readFile} from 'fs';\r\n```\r\n\r\n上面代码就是从fs模块加载3个方法,其他方法不加载。这种加载成为“编译时加载”。ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。\r\n\r\n### export命令\r\n\r\n模块功能主要由两个命令构成,export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。\r\n一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。\r\n\r\n```js\r\nexport var firstName = 'Michael';\r\nexport var lastName = 'Jackson';\r\nexport var year = 1958;\r\n```\r\n\r\n还可以如下写:\r\n\r\n```js\r\nvar firstName = 'Michael';\r\nvar lastName = 'Jackson';\r\nvar year = 1958;\r\nexport {firstName, lastName, year};\r\n```\r\n\r\nexport除了输出变量通用可以输出函数或者类,export输出的变量还可以用as关键字重命名。\r\n\r\n```js\r\nfunction v1() { ... }\r\nfunction v2() { ... }\r\nexport {\r\n v1 as streamV1,\r\n v2 as streamV2,\r\n v2 as streamLatestVersion\r\n};\r\n```\r\n\r\n### import命令\r\n\r\n使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。用法上面已经有介绍,如果想为输入的变量重命名,使用as关键字。\r\n\r\n```js\r\nimport { lastName as surname } from './profile';\r\n```","cover":"","link":"webpack-vue-es6.html","preview":"\u003cp\u003eES6标准虽然已经发布了,但是很多浏览器环境都还不支持,webpack是通过Babel这个转码器将ES6代码转为ES5,从而在现有环境执行。babel是在webpack的配置文件webpack.config.js的module参数中的loaders中配置。\u003c/p\u003e\n","title":"webpack+vue项目中常用ES6语法总结"},{"content":"\r\n### vue-router 传参\r\n在使用 vue-router 进行页面跳转的时候,有以下两种方式可以实现:\r\n* 声明式: `\u003crouter-link\u003eHome\u003c/router-link\u003e`\r\n\r\n``` html\r\n\u003c!-- 字符串 --\u003e\r\n\u003crouter-link to=\"home\"\u003eHome\u003c/router-link\u003e\r\n\u003c!-- 渲染结果 --\u003e\r\n\u003ca href=\"home\"\u003eHome\u003c/a\u003e\r\n\r\n\u003c!-- 使用 v-bind 的 JS 表达式 --\u003e\r\n\u003crouter-link v-bind:to=\"'home'\"\u003eHome\u003c/router-link\u003e\r\n\r\n\u003c!-- 不写 v-bind 也可以,就像绑定别的属性一样 --\u003e\r\n\u003crouter-link :to=\"'home'\"\u003eHome\u003c/router-link\u003e\r\n\r\n\u003c!-- 同上 --\u003e\r\n\u003crouter-link :to=\"{ path: 'home' }\"\u003eHome\u003c/router-link\u003e\r\n\r\n\u003c!-- 命名的路由 --\u003e\r\n\u003crouter-link :to=\"{ name: 'user', params: { userId: 123 }}\"\u003eUser\u003c/router-link\u003e\r\n\r\n\u003c!-- 带查询参数,下面的结果为 /register?plan=private --\u003e\r\n\u003crouter-link :to=\"{ path: 'register', query: { plan: 'private' }}\"\u003eRegister\u003c/router-link\u003e\r\n```\r\n* 编程式: `router.push(...)`\r\n\r\n该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:\r\n\r\n``` javascript\r\n// 字符串\r\nrouter.push('home')\r\n\r\n// 对象\r\nrouter.push({ path: 'home' })\r\n\r\n// 命名的路由\r\nrouter.push({ name: 'user', params: { userId: 123 }})\r\n\r\n// 带查询参数,变成 /register?plan=private\r\nrouter.push({ path: 'register', query: { plan: 'private' }})\r\n```\r\n注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:\r\n\r\n``` javascript\r\nconst userId = 123\r\nrouter.push({ name: 'user', params: { userId }}) // -\u003e /user/123\r\nrouter.push({ path: `/user/${userId}` }) // -\u003e /user/123\r\n// 这里的 params 不生效\r\nrouter.push({ path: '/user', params: { userId }}) // -\u003e /user\r\n```\r\n\r\n这两种方式传递参数的方式其实是一样的,从写法也可以大致看出,下面就对参数的传递方式进行介绍\r\n首先创建一个Router实例\r\n``` javascript\r\nconst router = new VueRouter({\r\n routes: [\r\n {\r\n path: '/user',\r\n name: 'user',\r\n component: User\r\n }\r\n ]\r\n})\r\n```\r\n#### 1.使用params方式\r\n``` javascript\r\nrouter.push({ name: 'user', params: { userId: 123 }}) // -\u003e /user\r\n```\r\n获取参数:\r\n``` javascript\r\nrouter.params.userId //123\r\n```\r\n这种方式看上去不错,对于传递的参数没有在浏览器的地址栏显示,而是隐藏了。但是当我们再次刷新的时候,`router.params.userId`就变成了`undefined`,对于这个解决方法如下:\r\n修改Router实例,在路由路径上增加该参数\r\n``` javascript\r\nconst router = new VueRouter({\r\n routes: [\r\n {\r\n path: '/user/:userId',\r\n name: 'user',\r\n component: User\r\n }\r\n ]\r\n})\r\n```\r\n此时,浏览器的地址栏就会变成:/user/123,然后不管怎么刷新也会取到参数。其实使用`query`方式传递参数也可以避免这个问题。\r\n\r\n#### 2.使用query方式\r\n\r\n``` javascript\r\nrouter.push({ path: '/user', query: { userId: 123 }}) // -\u003e/user?userId=123\r\n```\r\n获取参数:\r\n``` javascript\r\nrouter.query.userId //123\r\n```","cover":"","link":"vue-router.html","preview":"\u003cp\u003e一直对 vue-router 有点敬畏之心,因为总感觉对它的理解模模糊糊的,今天看了一下官网文档,有了一点点的理解,时来兴起,就有了这篇文章。\u003c/p\u003e\n","title":"Vue-router的一些记录"},{"content":"\r\nJavaScript在过去几年里发生了很大的变化。这里有12个新功能,您可以学习使用它们!\r\n\r\n![](http://css88.b0.upaiyun.com/css88/2016/10/es6-core-features-overview-large.png)\r\n## JavaScript历史\r\n\r\n新增加的语言称为ECMAScript 6。它也称为ES6或ES2015 +。\r\n\r\n自从 1995年提出的JavaScript构想以来,发展进展非常缓慢。每隔几年新增一次。1997年以来 ECMAScript 一直作为JavaScript实现的基础,引导JavaScript 发展。它已经发布了好几个版本,如ES3,ES5,ES6等。\r\n\r\n\u003e ECMAScript与JavaScript的关系:\r\n\r\n\u003e ECMA-262标准的描述如下:“ ECMAScript 可以为不同种类的宿主环境提供核心的脚本编程能力,因此核心的脚本语言时与任何特定的宿主环境分开进行规定的。”简单地说,ECMAScript描述了以下内容:语法、类型、语句、关键字、保留字、运算符、对象等。\r\n\r\n\u003e ECMAScript是JavaScript的一个重要标准,但它并不是JavaScript唯一的部分,当然,也不是唯一被标准化的部分。比如在WEB前端开发中,Web浏览器对于ECMAScript来说是一个宿主环境,但它并不是唯一的宿主环境。事实上,还有不计其数的其他各种环境(例如目前很火的Node.js\r\n)。一个完整的JavaScript实现是由以下3个不同部分组成的:核心(ECMAScript)、文档对象模型(DOM)、浏览器对象模型(BOM)。\r\n\r\n\u003e 正如你所看到的,ES3,ES5和ES6之间分别存在10年和6年的时间间隔。目前ECMAScript发展的新模式是每年进行少量变更。而不是像ES6一样做大量的更改。\r\n\r\n## 浏览器支持\r\n\r\n![](http://css88.b0.upaiyun.com/css88/2016/10/es6-javascript-support.png)\r\n\r\n所有现代浏览器和环境都已经支持ES6了!Chrome,MS Edge,Firefox,Safari,Node等等已经内置支持JavaScript ES6的大部分功能。所以,你在本教程中学到的一切,你可以立即开始使用它。\r\n\r\n让我们开始使用 ECMAScript 6 吧!\r\n\r\n## 核心ES6特性\r\n\r\n变量的块作用域\r\n\r\n在过去声明变量使用 var,而在ES6中,声明变量还可以使用 let / const。\r\n\r\nvar 有什么问题?\r\n\r\nvar 的问题是变量会被泄漏到其他代码块,例如 for 循环或 if 代码块。\r\n```js\r\nES5 代码:\r\nvar x = 'outer';\r\nfunction test(inner) {\r\n if (inner) {\r\n var x = 'inner'; // 作用域是整个function\r\n return x;\r\n }\r\n return x; //被重新定义,因为第4行声明被提升\r\n}\r\ntest(false); // undefined \r\ntest(true); // inner\r\n```\r\n上面代码中,对于test(false) 你可能期望的是返回 outer, 但是不是, 你得到的是 undefined。\r\n\r\n为什么?\r\n\r\n因为即使 if 代码块没有被执行,第4行中的表达式var x也是被提升的。\r\n\r\n\u003e var 变量提升\r\n\u003e var是函数作用域。它在整个函数中是可用的,甚至在被声明之前。\r\n\u003e 初始化 不 提升。如果你使用var ,请总是在顶部声明你的变量。\r\n\u003e 应用提升规则后我们可以更好地了解发生了什么:\r\n\r\n```js\r\nES5 代码:\r\nvar x = 'outer';\r\nfunction test(inner) {\r\n var x; // 提升声明\r\n if (inner) {\r\n x = 'inner'; // 初始化不提升\r\n return x;\r\n }\r\n return x;\r\n}\r\n```\r\n用ECMAScript 6 来拯救:\r\n```js\r\nES6 代码:\r\nlet x = 'outer';\r\nfunction test(inner) {\r\n if (inner) {\r\n let x = 'inner';\r\n return x;\r\n }\r\n return x; // 从第1行获得预期结果\r\n}\r\ntest(false); // outer\r\ntest(true); // inner\r\n```\r\n从var改用 let,使代码按你设想的那样执行。 if 代码块没有被执行,变量x不会从if 代码块中被提升。\r\n\r\nlet 提升 和 “暂时性死区”\r\n\r\n在ES6中,let将把变量提升到代码块的顶部(不是像ES5那样的函数顶部)。\r\n但是,代码块中,在变量声明之前引用这个变量会导致一个 ReferenceError 错误。\r\nlet是块作用域。在声明之前不能使用它。\r\n“暂时性死区”是指从代码块开始直到变量被声明的区域。\r\n## IIFE\r\n\r\n在解释LIFE之前,让我们举个例子。 看看这里:\r\n```js\r\nES5 代码:\r\n{\r\n var private = 1;\r\n}\r\nconsole.log(private); // 1\r\n```\r\n正如你所看到的,变量private 被泄漏到了代码块外面。你需要使用IIFE(immediately-invoked function expression,即:立即调用函数表达式)来包含它:\r\n\r\n```js\r\nES5 代码:\r\n(function(){\r\n var private2 = 1;\r\n})();\r\nconsole.log(private2); // 未捕获 ReferenceError\r\n```\r\n\r\n如果你看看jQuery / lodash或其他开源项目,您将注意到他们使用IIFE以避免污染全局环境并只是在全局定义,如 _,$ 或 jQuery 。\r\n\r\n在ES6中更干净,如果我们只是现在某个代码块中使用使用某个变量,我们可以使用let,再也不需要使用IIFE了:\r\n```js\r\nES6 代码:\r\n{\r\n let private3 = 1;\r\n}\r\nconsole.log(private3); // 未捕获 ReferenceError\r\n```\r\n## Const\r\n\r\n如果你想要一个变量一直不改变,你也可以使用 const。\r\n\r\n![](http://css88.b0.upaiyun.com/css88/2016/10/javascript-es6-const-variables-example.png)\r\n\r\n底线:var 区分 let 和 const。\r\n\r\n对于所有引用使用const; 避免使用var。\r\n如果你必须重新分配引用(愚人码头注:变量需要重新赋值的),使用let而不是const。\r\n\r\n## 模板字面量(Template Literals)\r\n\r\n当我们有了模板字面量,我们就不需要做更多的嵌套连接。看一看:\r\n```js\r\nES5 代码:\r\nvar first = 'Adrian';\r\nvar last = 'Mejia';\r\nconsole.log('Your name is ' + first + ' ' + last + '.');\r\n```\r\n现在你可以使用反引号 ( ` ` ) 和插值字符串 ${};\r\n```js\r\nES6 代码:\r\nconst first = 'Adrian';\r\nconst last = 'Mejia';\r\nconsole.log(`Your name is ${first} ${last}.`);\r\n```\r\nES6的模板字面量没有转义、循环、条件判断等内置语法,感觉功能还很弱。\r\n\r\n## 多行字符串\r\n\r\n我们不必在连接字符串时需要加 \\n,就像这样:\r\n```js\r\nES5 代码:\r\nvar template = '\u003cli *ngFor=\"let todo of todos\" [ngClass]=\"{completed: todo.isDone}\" \u003e\\n' +\r\n' \u003cdiv class=\"view\"\u003e\\n' +\r\n' \u003cinput class=\"toggle\" type=\"checkbox\" [checked]=\"todo.isDone\"\u003e\\n' +\r\n' \u003clabel\u003e\u003c/label\u003e\\n' +\r\n' \u003cbutton class=\"destroy\"\u003e\u003c/button\u003e\\n' +\r\n' \u003c/div\u003e\\n' +\r\n' \u003cinput class=\"edit\" value=\"\"\u003e\\n' +\r\n'\u003c/li\u003e';\r\nconsole.log(template);\r\n```\r\n在ES6中,我们可以再次使用反引号来解决这个问题:\r\n```js\r\nES6 代码:\r\nconst template = `\u003cli *ngFor=\"let todo of todos\" [ngClass]=\"{completed: todo.isDone}\" \u003e\r\n \u003cdiv class=\"view\"\u003e\r\n \u003cinput class=\"toggle\" type=\"checkbox\" [checked]=\"todo.isDone\"\u003e\r\n \u003clabel\u003e\u003c/label\u003e\r\n \u003cbutton class=\"destroy\"\u003e\u003c/button\u003e\r\n \u003c/div\u003e\r\n \u003cinput class=\"edit\" value=\"\"\u003e\r\n\u003c/li\u003e`;\r\nconsole.log(template);\r\n```\r\n这两段代码将等到完全相同的结果。\r\n\r\n## 解构分配\r\n\r\nES6解构非常有用和简洁。 按照这个例子:\r\n\r\n从数组中获取元素\r\n```js\r\nES5 代码:\r\nvar array = [1, 2, 3, 4];\r\nvar first = array[0];\r\nvar third = array[2];\r\nconsole.log(first, third); // 1 3\r\n```\r\n等价于:\r\n```js\r\nES6 代码:\r\nconst array = [1, 2, 3, 4];\r\nconst [first, ,third] = array;\r\nconsole.log(first, third); // 1 3\r\n```\r\n交换值\r\n```js\r\nES5 代码:\r\nvar a = 1;\r\nvar b = 2;\r\nvar tmp = a;\r\na = b;\r\nb = tmp;\r\nconsole.log(a, b); // 2 1\r\n```\r\n等价于:\r\n```js\r\nES6 代码:\r\nlet a = 1;\r\nlet b = 2;\r\n[a, b] = [b, a];\r\nconsole.log(a, b); // 2 1\r\n```\r\n多个返回值的解构\r\n```js\r\nES5 代码:\r\nfunction margin() {\r\n var left=1, right=2, top=3, bottom=4;\r\n return { left: left, right: right, top: top, bottom: bottom };\r\n}\r\nvar data = margin();\r\nvar left = data.left;\r\nvar bottom = data.bottom;\r\nconsole.log(left, bottom); // 1 4\r\n```\r\n在第3行,你也可以在一个数组中返回它像这样(并保存一些类型):\r\n```js\r\njs 代码:\r\nreturn [left, right, top, bottom];\r\n```\r\n但是调用者需要顾及到返回数据的顺序。\r\n```js\r\njs 代码:\r\nvar left = data[0];\r\nvar bottom = data[3];\r\n```\r\n使用ES6,调用者只需要选择他们需要的数据(第6行):\r\n```js\r\nES6 代码:\r\nfunction margin() {\r\n const left=1, right=2, top=3, bottom=4;\r\n return { left, right, top, bottom };\r\n}\r\nconst { left, bottom } = margin();\r\nconsole.log(left, bottom); // 1 4\r\n```\r\n注意:第3行,我们可以看到ES6的一些其他特性。我们可以压缩{left:left}为{left}。看看它比 ES5 简洁了很多。是不是很酷?\r\n\r\n## 参数匹配的解构\r\n```js\r\nES5 代码:\r\nvar user = {firstName: 'Adrian', lastName: 'Mejia'};\r\nfunction getFullName(user) {\r\n var firstName = user.firstName;\r\n var lastName = user.lastName;\r\n return firstName + ' ' + lastName;\r\n}\r\nconsole.log(getFullName(user)); // Adrian Mejia\r\n```\r\n等价于:\r\n```js\r\nES6 代码:\r\nconst user = {firstName: 'Adrian', lastName: 'Mejia'};\r\nfunction getFullName({ firstName, lastName }) {\r\n return `${firstName} ${lastName}`;\r\n}\r\nconsole.log(getFullName(user)); // Adrian Mejia\r\n```\r\n## 深度匹配\r\n```js\r\nES5 代码:\r\nfunction settings() {\r\n return { display: { color: 'red' }, keyboard: { layout: 'querty'} };\r\n}\r\nvar tmp = settings();\r\nvar displayColor = tmp.display.color;\r\nvar keyboardLayout = tmp.keyboard.layout;\r\nconsole.log(displayColor, keyboardLayout); // red querty\r\n```\r\n等价于:\r\n```js\r\nES6 代码:\r\nfunction settings() {\r\n return { display: { color: 'red' }, keyboard: { layout: 'querty'} };\r\n}\r\nconst { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();\r\nconsole.log(displayColor, keyboardLayout); // red querty\r\n```\r\n这也称为对象解构。\r\n\r\n正如你所看到的,解构是非常有用的,并鼓励良好的编码风格。\r\n\r\n最佳实践:\r\n\r\n使用数组解构来获取元素或交换变量。它可以避免创建临时引用。\r\n对于函数多个返回值不要使用数组解构,而使用对象解构。\r\n\r\n## 类与对象\r\n\r\n使用ECMAScript 6,我们可以从“构造函数”过渡到“类”。\r\n\r\n在JavaScript中每一个对象都有一个原型,这是另一个对象。所有JavaScript对象继承了它们原型中的方法和属性。\r\n\r\n在ES5中,我们面向对象编程(OOP)需要使用构造函数来创建对象,如下:\r\n```js\r\nES5 代码:\r\nvar Animal = (function () {\r\n function MyConstructor(name) {\r\n this.name = name;\r\n }\r\n MyConstructor.prototype.speak = function speak() {\r\n console.log(this.name + ' makes a noise.');\r\n };\r\n return MyConstructor;\r\n})();\r\nvar animal = new Animal('animal');\r\nanimal.speak(); // animal makes a noise.\r\n```\r\n在ES6中,我们有一些语法糖。我们可以用较少的板式代码,以及class和constructor等新的关键字做同样的事情。\r\n另外,请注意我们如何清楚地定义方法constructor.prototype.speak = function () vs speak():\r\n```js\r\nES6 代码:\r\nclass Animal {\r\n constructor(name) {\r\n this.name = name;\r\n }\r\n speak() {\r\n console.log(this.name + ' makes a noise.');\r\n }\r\n}\r\nconst animal = new Animal('animal');\r\nanimal.speak(); // animal makes a noise.\r\n```\r\n正如你所看到的,两种样式(ES5/6)在幕后产生相同的结果,并且可以以相同的方式使用。\r\n\r\n最佳实践:\r\n\r\n始终使用class语法,并避免直接操作 prototype。为什么?因为它使代码更简洁和更容易理解。\r\n避免使用空的构造函数。如果没有指定,类有一个默认构造函数。\r\n\r\n## 继承\r\n\r\n基于前面的Animal类。假设我们想要扩展它并定义Lion类。\r\n\r\n在ES5中,它更多地涉及原型继承。\r\n```js\r\nES5 代码:\r\nvar Lion = (function () {\r\n function MyConstructor(name){\r\n Animal.call(this, name);\r\n }\r\n \r\n // prototypal inheritance\r\n MyConstructor.prototype = Object.create(Animal.prototype);\r\n MyConstructor.prototype.constructor = Animal;\r\n \r\n MyConstructor.prototype.speak = function speak() {\r\n Animal.prototype.speak.call(this);\r\n console.log(this.name + ' roars ');\r\n };\r\n return MyConstructor;\r\n})();\r\nvar lion = new Lion('Simba');\r\nlion.speak(); // Simba makes a noise.\r\n// Simba roars.\r\n```\r\n我不详细描述所有细节,但请注意:\r\n\r\n* 第3行,我们用参数显式调用Animal构造函数。\r\n* 第7-8行,我们将Lion原型分配给Animal原型。\r\n* 第11行,我们从父类Animal中调用speak方法。\r\n* 在ES6中,我们有2个新的关键字extends和super。\r\n\r\n```js\r\nES6 代码:\r\nclass Lion extends Animal {\r\n speak() {\r\n super.speak();\r\n console.log(this.name + ' roars ');\r\n }\r\n}\r\nconst lion = new Lion('Simba');\r\nlion.speak(); // Simba makes a noise.\r\n// Simba roars.\r\n```\r\n看看ES6的代码比ES5看起来清晰了很多,并且他们做的事情完全一样。\r\n\r\n最佳实践:\r\n\r\n使用extends内置继承方式来实现继承。\r\n\r\n## 原生的 Promises\r\n\r\n从回调地狱 到 promises\r\n```js\r\nES5 代码:\r\nfunction printAfterTimeout(string, timeout, done){\r\n setTimeout(function(){\r\n done(string);\r\n }, timeout);\r\n}\r\nprintAfterTimeout('Hello ', 2e3, function(result){\r\n console.log(result);\r\n // nested callback\r\n printAfterTimeout(result + 'Reader', 2e3, function(result){\r\n console.log(result);\r\n });\r\n});\r\n```\r\n我们有一个函数接收一个回调,当done时执行。我们必须一个接一个地两度执行它。这就是为什么我们在回调中第二次调用printAfterTimeout的原因。\r\n\r\n如果你需要第3或第4次回调,那么你很快就凌乱了。让我们看看我们如何使用promises:\r\n```js\r\nES6 代码:\r\nfunction printAfterTimeout(string, timeout){\r\n return new Promise((resolve, reject) =\u003e {\r\n setTimeout(function(){\r\n resolve(string);\r\n }, timeout);\r\n });\r\n}\r\nprintAfterTimeout('Hello ', 2e3).then((result) =\u003e {\r\n console.log(result);\r\n return printAfterTimeout(result + 'Reader', 2e3);\r\n}).then((result) =\u003e {\r\n console.log(result);\r\n});\r\n```\r\n正如你说看到的,使用 promises,我们可以使用then在一个函数完成后做另一些事情。不再需要嵌套函数。\r\n\r\n## 箭头函数\r\n\r\nES6没有删除函数表达式,但它添加了一个新的函数表达式,称为箭头函数。\r\n\r\n在ES5中,对于this我们有一些疑问:\r\n```js\r\nES5 代码:\r\nvar _this = this; // 需要保持一个引用\r\n$('.btn').click(function(event){\r\n _this.sendData(); // 引用函数外层的 this\r\n});\r\n$('.input').on('change',function(event){\r\n this.sendData(); // 引用函数外层的 this\r\n}.bind(this)); // 绑定函数外层的 this\r\n```\r\n你需要使用一个临时的 this ,以便在函数内部引用,或使用bind。在ES6中,可以使用箭头函数!\r\n```js\r\nES6 代码:\r\n// this 将引用外部的那个 this\r\n$('.btn').click((event) =\u003e this.sendData());\r\n// 隐式返回\r\nconst ids = [291, 288, 984];\r\nconst messages = ids.map(value =\u003e `ID is ${value}`);\r\n```\r\n## For…of\r\n\r\n从 for 到 forEach 再到 for...of:\r\n```js\r\nES5 代码:\r\n// for\r\nvar array = ['a', 'b', 'c', 'd'];\r\nfor (var i = 0; i \u003c array.length; i++) {\r\n var element = array[i];\r\n console.log(element);\r\n}\r\n// forEach\r\narray.forEach(function (element) {\r\n console.log(element);\r\n});\r\n```\r\nES6 的 for...of 同样允许我们迭代。\r\n```js\r\nES6 代码:\r\n// for ...of\r\nconst array = ['a', 'b', 'c', 'd'];\r\nfor (const element of array) {\r\n console.log(element);\r\n}\r\n```\r\n## 默认参数\r\n\r\n从检查变量是否被定义 到 分配一个值给默认参数。你以前做过类似的事情吗?\r\n```js\r\nES5 代码:\r\nfunction point(x, y, isFlag){\r\n x = x || 0;\r\n y = y || -1;\r\n isFlag = isFlag || true;\r\n console.log(x,y, isFlag);\r\n}\r\npoint(0, 0) // 0 -1 true \r\npoint(0, 0, false) // 0 -1 true \r\npoint(1) // 1 -1 true\r\npoint() // 0 -1 true\r\n```\r\n肯定这些做过吧?这是一个常见的模式来检查是变量是否赋值,否则分配一个默认值。但是,注意有一些问题:\r\n* 第7行,我们传递0, 0,得到0, -1。\r\n* 第8行,我们传递false,但得到true。\r\n如果你将一个布尔值作为默认参数或将值设置为0,它就不正常工作了。你知道为什么吗???我将在ES6示例后面告诉你;)\r\n\r\n现在,如果你用ES6,可以用更少的代码做的更好!\r\n```js\r\nES6 代码:\r\nfunction point(x = 0, y = -1, isFlag = true){\r\n console.log(x,y, isFlag);\r\n}\r\npoint(0, 0) // 0 0 true\r\npoint(0, 0, false) // 0 0 false\r\npoint(1) // 1 -1 true\r\npoint() // 0 -1 true\r\n```\r\n注意第4行和第5行, 我们得到了预期的结果。ES5的示例则没有正常工作。我们必须属性检查 undefined,因为false,null,undefined和0是假(falsy)值。我们需要更加多的代码来修复这个问题:\r\n```js\r\nES5 代码:\r\nfunction point(x, y, isFlag){\r\n x = x || 0;\r\n y = typeof(y) === 'undefined' ? -1 : y;\r\n isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;\r\n console.log(x,y, isFlag);\r\n}\r\npoint(0, 0) // 0 0 true\r\npoint(0, 0, false) // 0 0 false\r\npoint(1) // 1 -1 true\r\npoint() // 0 -1 true\r\n```\r\n我们检查 undefined,现在它就能按预期工作了。\r\n\r\n## Rest参数(多余参数)\r\n\r\n从 arguments 到 rest参数 和 扩展运算符。\r\n\r\n在ES5中,获取任意数量的参数是非常麻烦的:\r\n```js\r\nES5 代码:\r\nfunction printf(format) {\r\n var params = [].slice.call(arguments, 1);\r\n console.log('params: ', params);\r\n console.log('format: ', format);\r\n}\r\nprintf('%s %d %.2f', 'adrian', 321, Math.PI);\r\n```\r\n我们可以使用rest运算符...做同样的事情。\r\n```js\r\nES6 代码:\r\nfunction printf(format, ...params) {\r\n console.log('params: ', params);\r\n console.log('format: ', format);\r\n}\r\nprintf('%s %d %.2f', 'adrian', 321, Math.PI);\r\n```\r\n## 扩展运算符\r\n\r\n从apply()到扩展运算符,我们有 ... 拯救:\r\n\r\n提醒:我们使用apply() 将数组转换为一个参数列表。例如,Math.max()获取参数列表,但是如果我们有一个数组,我们可以使用apply来使它工作。\r\n\r\n![](http://css88.b0.upaiyun.com/css88/2016/10/javascript-math-apply-arrays.png)\r\n\r\n正如我们在前面看到的,我们可以使用apply将数组作为参数列表传递:\r\n```js\r\nES5 代码:\r\nMath.max.apply(Math, [2,100,1,6,43]) // 100\r\n```\r\n在ES6中,你可以使用 扩展运算符。\r\n```js\r\nES6 代码:\r\nMath.max(...[2,100,1,6,43]) // 100\r\n```\r\n另外,我们可以使用扩展运算符来 concat(合并)数组:\r\n```js\r\nES5 代码:\r\nvar array1 = [2,100,1,6,43];\r\nvar array2 = ['a', 'b', 'c', 'd'];\r\nvar array3 = [false, true, null, undefined];\r\nconsole.log(array1.concat(array2, array3));\r\n```\r\n在ES6中,可以使用扩展运算符合并数组:\r\n```js\r\nES6 代码:\r\nconst array1 = [2,100,1,6,43];\r\nconst array2 = ['a', 'b', 'c', 'd'];\r\nconst array3 = [false, true, null, undefined];\r\nconsole.log([...array1, ...array2, ...array3]);\r\n```","cover":"","link":"javascript-es6.html","preview":"\u003cp\u003eJavaScript在过去几年里发生了很大的变化。ECMAScript是JavaScript的一个重要标准,但它并不是JavaScript唯一的部分,当然,也不是唯一被标准化的部分。这里有12个新功能,您可以学习使用它们!\u003c/p\u003e\n","title":"JavaScript ES6核心特性概述"},{"content":"\r\n这个操作很简单的,只需要一个插件就好了,就是extract-text-webpack-plugin\r\n\r\n## 1、安装extract-text-webpack-plugin\r\n```js\r\ncnpm install extract-text-webpack-plugin --save-dev\r\n```\r\n\u003c!-- more --\u003e\r\n\r\n## 2、配置文件添加对应配置\r\n\r\n首先require一下\r\n```js\r\nvar ExtractTextPlugin = require(\"extract-text-webpack-plugin\");\r\n```\r\nplugins里面添加\r\n```js\r\nnew ExtractTextPlugin(\"styles.css\"),\r\n```\r\n我这里如下:\r\n```json\r\nplugins: [\r\n new webpack.optimize.CommonsChunkPlugin('common.js'),\r\n new ExtractTextPlugin(\"styles.css\"),\r\n],\r\n```\r\nmodules里面对css的处理修改为\r\n```js\r\n{test:/\\.css$/, loader: ExtractTextPlugin.extract(\"style-loader\", \"css-loader\")},\r\n```\r\n千万不要重复了,不然会不起作用的\r\n\r\n我这里如下:\r\n```json\r\nmodule: {\r\n loaders: [\r\n {test:/\\.css$/, loader: ExtractTextPlugin.extract(\"style-loader\", \"css-loader\")},\r\n {test: /\\.scss$/, loader: \"style!css!sass\"},\r\n {test: /\\.less$/, loader: \"style!css!less\"},\r\n ]\r\n},\r\n```\r\n## 3、在引入文件里面添加需要的css\r\n```js\r\nrequire('../less/app.less');\r\nrequire('./bower_components/bootstrap-select/dist/css/bootstrap-select.min.css');\r\nrequire('./bower_components/fancybox/source/jquery.fancybox.css');\r\n```","cover":"","link":"webpack-css.html","preview":"\u003cp\u003e这个操作很简单的,只需要一个插件就好了,就是extract-text-webpack-plugin\u003c/p\u003e\n","title":"Webpack分离CSS单独打包"},{"content":"\r\n经过几天的瞎折腾实现了webpack可以与gulp完美结合的进行打包静态文件,并将静态文件上传到七牛云存储,当然也可以传到你想传的云存储了,这里只分享一个七牛的云存储方案。\r\n\r\n关于如何使用webpack打包静态代码,这个可以参考我之前的一些文章和方案。\r\n\r\n这里只分享一下gulp这边的操作,然后给一个例子实现如何一条命令打包静态文件并更新CDN文件的方法。\r\n\r\n```js\r\nconst gulp = require('gulp');\r\nconst uglify = require('gulp-uglify');\r\nconst concat = require('gulp-concat');\r\nconst shrink = require('gulp-cssshrink');\r\nconst webpack = require('gulp-webpack');\r\nconst qn = require('gulp-qn');\r\n\r\nconst rev = require('gulp-rev-qn');\r\nconst revCollector = require('gulp-rev-collector');\r\nconst runSequence = require('run-sequence');\r\nconst config = require('./webpack.config');\r\nconst qiniu_options = {\r\n accessKey: 'xxxxxxxxxx',\r\n secretKey: 'xxxxxxxxxx',\r\n bucket: 'xxxxxxxxxxxxx',\r\n domain: 'http://xxxxx.com'\r\n};\r\ngulp.task('publish-js', function () {\r\n return gulp.src(['./build/js/*.js'])\r\n .pipe(uglify())\r\n .pipe(rev())\r\n .pipe(gulp.dest('./build/js'))\r\n .pipe(qn({\r\n qiniu: qiniu_options,\r\n prefix: 'js'\r\n }))\r\n .pipe(rev.manifest())\r\n .pipe(gulp.dest('./build/rev/js'));\r\n});\r\ngulp.task('publish-font', function () {\r\n return gulp.src(['./build/js/*.woff2','./build/js/*.ttf','./build/js/*.eot','./build/js/*.woff'])\r\n .pipe(qn({\r\n qiniu: qiniu_options,\r\n prefix: 'js'\r\n }));\r\n});\r\ngulp.task('publish-css', function () {\r\n return gulp.src(['./build/js/*.css'])\r\n .pipe(rev())\r\n .pipe(gulp.dest('./build/js'))\r\n .pipe(qn({\r\n qiniu: qiniu_options,\r\n prefix: 'css'\r\n }))\r\n .pipe(rev.manifest())\r\n .pipe(gulp.dest('./build/rev/css'));\r\n});\r\ngulp.task('publish-html', function () {\r\n return gulp.src(['./build/rev/**/*.json', './build/views/*.html'])\r\n .pipe(revCollector({\r\n dirReplacements: {\r\n '/js/': ''\r\n }\r\n }))\r\n .pipe(gulp.dest('./build/views'));\r\n});\r\ngulp.task('default',function(callback){\r\n runSequence(\r\n ['publish-css','publish-js','publish-font'],\r\n 'publish-html',\r\n callback);\r\n});\r\n```\r\n## PS:\r\npublish-js:将js文件进行版本更新并上传到七牛。\r\n\r\npublish-css:将css文件进行版本更新并上传到七牛。\r\n\r\npublish-font:将字体文件上传到七牛。\r\n\r\npublish-html:将html文件中对应的js路径进行替换。","cover":"","link":"webpack-gulp-qiniu-cdn.html","preview":"\u003cp\u003e经过几天的瞎折腾实现了webpack可以与gulp完美结合的进行打包静态文件,并将静态文件上传到七牛云存储,当然也可以传到你想传的云存储了,这里只分享一个七牛的云存储方案。\u003c/p\u003e\n","title":"webpack+gulp 静态文件打包并自动上传到七牛云"},{"content":"\r\n# webpack是什么?\r\n\r\nweb开发中常用到的静态资源主要有JavaScript、CSS、图片、Jade等文件,webpack中将静态资源文件称之为模块。webpack是一个模块打包工具,其可以兼容多种js书写规范,且可以处理模块间的依赖关系,具有更强大的js模块化的功能。 官方网站中用下图清晰的描述了webpack采用不同的loader加载不同的资源文件,打包生成多个js文件,也可以根据设置生成独立的图片、css文件等。\r\n\r\n# why webpack?\r\n\r\n在以往的开发过程中,经常会遇到以下三种情况:\r\n\r\n* 项目中资源多样性和依赖性 - js、css、png、less、jade等 为了方便开发,我们经常会使用不同的语法来编写文档,用less、sass、jade等会提高开发效率,但同时我们需要借助gulp或grunt来编写任务编译文件或对图片进行压缩等。\r\n* js模块规范复杂化 - AMD、CommonJS、UMD、ES6等 requireJS主要用来处理AMD规范的js文件,若使用CommonJS规范的js库文件,需进行AMD规范的封装,才能正常使用。而browserify主要处理CommonJS规范的文件,其他规范也需要进行转化。近期ES6的兴起,前面两种打包工具已经不能满足我们的需求了。\r\n* 开发与线上文件不一致性(打包压缩造成影响)\r\n\r\nwebpack可以很好地解决上面的问题,它具有Grunt、Gulp对于静态资源自动化构建的能力,是一个出色的前端自动化构建工具、模块化工具、资源管理工具。\r\n\r\n# webpack 特性\r\n\r\nwebpack具有requireJs和browserify的功能,但仍有很多自己的新特性:\r\n\r\n1. 对 CommonJS 、 AMD 、ES6的语法做了兼容\r\n2. 对js、css、图片等资源文件都支持打包\r\n3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持\r\n4. 有独立的配置文件webpack.config.js\r\n5. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间\r\n6. 支持 SourceUrls 和 SourceMaps,易于调试\r\n7. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活\r\n8. webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快\r\n\r\n# webpack 安装及使用\r\n\r\nwebpack 可以作为全局的npm模块安装,也可以在当前项目中安装。\r\n\r\n```js\r\nnpm install -g webpack\r\n```\r\n\r\n```js\r\nnpm install --save-dev webpack\r\n```\r\n\r\nwebpack的使用通常有三种方式:\r\n\r\n* 命令行使用:webpack \u003centry.js\u003e \u003cresult.js\u003e 其中entry.js是入口文件,result.js是打包后的输出文件\r\n* node.js API使用:\r\n\r\n```js\r\nvar webpack = require('webpack');\r\nwebpack({\r\n//configuration\r\n}, function(err, stats){});\r\n```\r\n默认使用当前目录的webpack.config.js作为配置文件。如果要指定另外的配置文件,可以执行:webpack --config webpack.custom.config.js\r\n\r\n# webpack 常用命令\r\n\r\nwebpack的使用和browserify有些类似,下面列举几个常用命令:\r\n\r\n* ``webpack`` 最基本的启动webpack命令\r\n* ``webpack -w`` 提供watch方法,实时进行打包更新\r\n* ``webpack -p`` 对打包后的文件进行压缩\r\n* ``webpack -d`` 提供SourceMaps,方便调试\r\n* ``webpack --colors`` 输出结果带彩色,比如:会用红色显示耗时较长的步骤\r\n* ``webpack --profile`` 输出性能数据,可以看到每一步的耗时\r\n* ``webpack --display-modules`` 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块\r\n前面的四个命令比较基础,使用频率会比较大,后面的命令主要是用来定位打包时间较长的原因,方便改进配置文件,提高打包效率。\r\n\r\n# webpack 配置文件\r\n\r\n项目中静态资源文件较多,使用配置文件进行打包会方便很多。最简单的Webpack配置文件webpack.config.js如下所示:\r\n```js\r\nmodule.exports = {\r\n entry:[\r\n './entry.js',\r\n ...\r\n ],\r\n output: {\r\n path: __dirname + '/output/',\r\n publicPath: \"/output/\",\r\n filename: 'result.js'\r\n }\r\n};\r\n```\r\n\r\n* 其中entry参数定义了打包后的入口文件,数组中的所有文件会打包生成一个filename文件\r\n* output参数定义了输出文件的位置及名字,其中参数path是指文件的绝对路径,publicPath是指访问路径,filename是指输出的文件名。\r\n\r\n开发中需要将多个页面的公用模块独立打包,从而可以利用浏览器缓存机制来提高页面加载效率,减少页面初次加载时间,只有当某功能被用到时,才去动态的加载。这就需要使用webpack中的CommonsChunkPlugin插件。具体配置如下:\r\n\r\n```js\r\nvar CommonsChunkPlugin = require(\"webpack/lib/optimize/CommonsChunkPlugin\");\r\nmodule.exports = {\r\n entry: { a: \"./a\", b: \"./b\" },\r\n output: { filename: \"[name].js\" },\r\n plugins: [ new CommonsChunkPlugin(\"common.js\") ]\r\n}\r\n```\r\n在文件中根据下面的方式引用即可。\r\n```html\r\n\u003cscript src=\"common.js\"\u003e\u003c/script\u003e\r\n\u003cscript src=\"a.js\"\u003e\u003c/script\u003e\r\n\u003cscript src=\"b.js\"\u003e\u003c/script\u003e\r\n```\r\n# webpack 模块加载器\r\n\r\n在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都是模块,不同模块的加载是通过模块加载器(webpack-loader)来统一管理的。loaders之间是可以串联的,一个加载器的输出可以作为下一个加载器的输入,最终返回到JavaScript上。loader的配置可以写在配置文件中,通过正则表达式的方式对文件进行匹配,具体可参见下面的示例:\r\n```js\r\nmodule: {\r\n loaders: [{\r\n test: /\\.less/,\r\n loader: 'style-loader!css-loader!less-loader'\r\n }, {\r\n test: /\\.(png|jpg)$/,\r\n loader: 'url-loader?limit=10000\u0026name=build/[name].[ext]'\r\n }]\r\n}\r\n```\r\nloader也支持在js文件中通过require的方式进行加载,只需要在require资源path的前面指定loader,用!来串联不同的loader和资源即可。\r\n\r\n```js\r\nrequire(\"style!css!less!./mystyles.less\");\r\n```\r\n\r\n# css文件独立打包\r\n\r\n在webpack中编写js文件时,可以通过require的方式引入其他的静态资源,可通过loader对文件自动解析并打包文件。通常会将js文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文件,以外链的形式加载。这时extract-text-webpack-plugin插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。\r\n\r\n```js\r\nnpm install extract-text-webpack-plugin –save-dev\r\n```\r\n```js\r\nplugins: [\r\n new ExtractTextPlugin('styles.css')\r\n]\r\n```\r\n# 图片打包\r\n\r\nwebpack中对于图片的处理,可以通过url-loader来实现图片的压缩。\r\n```js\r\ndiv.img{\r\n background: url(../image/xxx.jpg)\r\n}\r\n\r\n//或者\r\nvar img = document.createElement(\"img\");\r\nimg.src = require(\"../image/xxx.jpg\");\r\ndocument.body.appendChild(img);\r\n```\r\n针对上面的两种使用方式,loader可以自动识别并处理。根据loader中的设置,webpack会将小于指点大小的文件转化成 base64 格式的 dataUrl,其他图片会做适当的压缩并存放在指定目录中。\r\n```js\r\nmodule: {\r\n {\r\n test: /\\.(png|jpg)$/,\r\n loader: 'url-loader?limit=10000\u0026name=build/[name].[ext]'\r\n }]\r\n}\r\n```\r\n对于上面的配置,如果图片资源小于10kb就会转化成 base64 格式的 dataUrl,其他的图片会存放在build/文件夹下。\r\n\r\n# webpack-dev-server\r\n\r\nwebpack除了可以对模块进行打包,还提供了一个开发服务器。它的特点是:\r\n\r\n* 基于Node.js Express框架的轻量开发服务器\r\n* 静态资源Web服务器\r\n* 开发中会监听文件的变化在内存中实时打包\r\n\r\nwebpack-dev-server需要单独安装,命令如下:\r\n\r\n```js\r\nnpm install -g webpack-dev-server\r\n```\r\n\r\n可以使用webpack-dev-server直接启动,也可以增加参数来获取更多的功能,具体配置可以参见[官方文档](http://webpack.github.io/docs/webpack-dev-server.html)。默认启动端口8080,通过localhost:8080/webpack-dev-server/可以访问页面,文件修改后保存时会在页面头部看到sever的状态变化,并且会进行热替换,实现页面的自动刷新。\r\n\r\n# 双服务器模式\r\n\r\n项目开发中,仅有一台静态服务器是不能满足需求的,我们需要另启一台web服务器,且将静态服务器集成到web服务器中,就可以使用webpack的打包和加载功能。我们只需要修改一下配置文件就可以实现服务器的集成。\r\n```js\r\n entry: [\r\n './src/page/main.js',\r\n 'webpack/hot/dev-server',\r\n 'webpack-dev-server/client?http://127.0.0.1:8080'\r\n ]\r\n output: {\r\n path: __dirname,\r\n filename: '[name].js',\r\n publicPath: \"http://127.0.0.1:8080/assets/\"\r\n }\r\n plugins: [\r\n new webpack.HotModuleReplacementPlugin()\r\n ]\r\n```\r\n如果在开发中启动两个服务器并不是一个很好地选择,webpack提供了一个中间件webpack-dev-middleware,但其只能在生产环境中使用,可以实现在内存中实时打包生成虚拟文件,供浏览器访问以及调试。使用方式如下:\r\n```js\r\nvar webpackDevMiddleware = require(\"webpack-dev-middleware\");\r\nvar webpack = require(\"webpack\");\r\n\r\nvar compiler = webpack({\r\n // configuration\r\n output: { path: '/' }\r\n});\r\n\r\napp.use(webpackDevMiddleware(compiler, {\r\n // options\r\n}));\r\n```\r\n# PS\r\n\r\n通过上面的介绍,基本涵盖了webpack的各个特性及简单的使用方法。最近出了个``hjs-webpack``,可以简化webpack中复杂的配置项,只需要安装开发中所需的loader,无需再module中配置,即可正确使用。有兴趣的同学可以尝试一下。","cover":"","link":"webpack-basic.html","preview":"\u003cp\u003eweb开发中常用到的静态资源主要有JavaScript、CSS、图片、Jade等文件,webpack中将静态资源文件称之为模块。webpack是一个模块打包工具,其可以兼容多种js书写规范,且可以处理模块间的依赖关系,具有更强大的js模块化的功能。\u003c/p\u003e\n","title":"Webpack 入门及实践"},{"content":"\r\n## gulp-sass\r\n\r\n本章使用的是 ruby-sass 如果你不方便安装 ruby 或编译速度慢,建议使用`gulp-sass`\r\n\r\n----------\r\n\r\n\u003e Sass 是一种 CSS 的开发工具,提供了许多便利的写法,大大节省了开发者的时间,使得 CSS 的开发,变得简单和可维护。\r\n\r\n本章使用 `ruby-sass` 编译 css,若你没有安装 ruby 和 sass 请移步`使用ruby.taobao安装 Sass`\r\n\r\n\r\n安装\r\n---\r\n\r\n```\r\nnpm install gulp-ruby-sass\r\n```\r\n\r\n基本用法\r\n-------\r\n\r\n```js\r\n// 获取 gulp\r\nvar gulp = require('gulp')\r\n// 获取 gulp-ruby-sass 模块\r\nvar sass = require('gulp-ruby-sass')\r\n\r\n// 编译sass\r\n// 在命令行输入 gulp sass 启动此任务\r\ngulp.task('sass', function() {\r\n return sass('sass/') \r\n .on('error', function (err) {\r\n console.error('Error!', err.message);\r\n })\r\n .pipe(gulp.dest('dist/css'))\r\n});\r\n\r\n\r\n// 在命令行使用 gulp auto 启动此任务\r\ngulp.task('auto', function () {\r\n // 监听文件修改,当文件被修改则执行 images 任务\r\n gulp.watch('sass/**/*.scss', ['sass'])\r\n});\r\n\r\n// 使用 gulp.task('default') 定义默认任务\r\n// 在命令行使用 gulp 启动 sass 任务和 auto 任务\r\ngulp.task('default', ['sass', 'auto'])\r\n```\r\n\r\n\r\nSass 代码和编译后的 CSS 代码\r\n----------\r\n\r\nsass/a.scss\r\n\r\n```css\r\n.sass{\r\n a{\r\n color:pink;\r\n }\r\n}\r\n```\r\n\r\nsass/import.scss\r\n\r\n\r\n```css\r\n@import \"a.scss\";\r\n.import{\r\n a{\r\n color:red;\r\n }\r\n}\r\n```\r\n\r\nsass/a.css\r\n\r\n```css\r\n.sass a {\r\n color: pink;\r\n}\r\n```\r\n\r\nsass/import.css\r\n\r\n```css\r\n.sass a {\r\n color: pink;\r\n}\r\n.import a{\r\n color: red;\r\n}\r\n```","cover":"","link":"gulp-sass.html","preview":"\u003cp\u003eSass 是一种 CSS 的开发工具,提供了许多便利的写法,大大节省了开发者的时间,使得 CSS 的开发,变得简单和可维护。\u003c/p\u003e\n","title":"使用 gulp 编译 Sass"},{"content":"\r\n## 1.安装gulp\r\n* npm install gulp\r\n\r\n## 2.全局安装Babel。\r\n* npm install -g babel-cli\r\n\r\n## 3.Babel的配置文件是.babelrc\r\nwindows下新建该文件会提示必须键入文件名,解决办法是文件名如下.babelrc.\r\n\r\n## 4.ES2015转码规则\r\n* npm install --save-dev babel-preset-es2015\r\n\r\n## 5.将规则加入到.babelrc文件中\r\n```json\r\n { \r\n \"presets\": [ \r\n \"es2015\"\r\n ], \r\n \"plugins\": [] \r\n } \r\n```\r\n\r\n## 6.配置工具\r\n\r\n* 6.1 安装gulp-babel\r\n npm install --save-dev gulp-babel\r\n\r\n* 6.2 配置gulpfile.js文件\r\n\r\n```js\r\nvar gulp = require(\"gulp\"); \r\nvar babel = require(\"gulp-babel\"); \r\n\r\ngulp.task(\"default\", function () { \r\n return gulp.src(\"src/a.js\") \r\n .pipe(babel()) \r\n .pipe(gulp.dest(\"lib\")); \r\n});\r\n```\r\n\r\n* 6.3实时转码\r\n 安装gulp-watch\r\n - npm install --save-dev gulp-watch\r\n\r\n##### 配置gulpfile文件\r\n\r\n```js\r\n var gulp = require(\"gulp\"), \r\n babel = require(\"gulp-babel\"),\r\n watch = require(\"gulp-watch\"); \r\n\r\n gulp.task(\"babeljs\", function () { \r\n return gulp.src(\"www/js/*.js\") \r\n .pipe(babel()) \r\n .pipe(gulp.dest(\"dist/js\")); \r\n }); \r\n gulp.task(\"watch\",function(){\r\n gulp.watch('www/js/*.js',['babeljs']);\r\n })\r\n\r\n gulp.task('default',['watch','babeljs']);\r\n```\r\n","cover":"","link":"gulp-babel-es6.html","preview":"","title":"使用gulp+babel实时转es6"},{"content":"\r\n\u003e Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量、混合(mixin)、函数等功能,让 CSS 更易维护。\r\n\r\n安装\r\n---\r\n\r\n```\r\nnpm install gulp-less\r\n```\r\n\r\n基本用法\r\n-------\r\n\r\n```js\r\n// 获取 gulp\r\nvar gulp = require('gulp')\r\n// 获取 gulp-less 模块\r\nvar less = require('gulp-less')\r\n\r\n// 编译less\r\n// 在命令行输入 gulp less 启动此任务\r\ngulp.task('less', function () {\r\n // 1. 找到 less 文件\r\n gulp.src('less/**.less')\r\n // 2. 编译为css\r\n .pipe(less())\r\n // 3. 另存文件\r\n .pipe(gulp.dest('dist/css'))\r\n});\r\n\r\n// 在命令行使用 gulp auto 启动此任务\r\ngulp.task('auto', function () {\r\n // 监听文件修改,当文件被修改则执行 less 任务\r\n gulp.watch('less/**.less', ['less'])\r\n})\r\n\r\n// 使用 gulp.task('default') 定义默认任务\r\n// 在命令行使用 gulp 启动 less 任务和 auto 任务\r\ngulp.task('default', ['less', 'auto'])\r\n```\r\n\r\n你可以访问 [gulp-less](https://github.com/plus3network/gulp-less) 以查看更多用法。\r\n\r\nLESS 代码和编译后的CSS代码\r\n----------\r\n\r\nless/a.less\r\n\r\n```css\r\n.less{\r\n a{\r\n color:pink;\r\n }\r\n}\r\n```\r\nless/import.less\r\n\r\n\r\n```css\r\n@import \"a.less\";\r\n.import{\r\n a{\r\n color:red;\r\n }\r\n}\r\n```\r\nless/a.css\r\n\r\n```css\r\n.less a {\r\n color: pink;\r\n}\r\n```\r\nless/import.css\r\n\r\n```css\r\n.less a {\r\n color: pink;\r\n}\r\n.import a{\r\n color: red;\r\n}\r\n```\r\n","cover":"","link":"gulp-less.html","preview":"\u003cp\u003eLess 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量、混合(mixin)、函数等功能,让 CSS 更易维护。\u003c/p\u003e\n","title":"使用 gulp 编译 LESS"},{"content":"\r\n\r\n相信做前端的同学都做过这样的事情,为优化图片,减少请求会把拿到切好的图标图片,通过ps(或者其他工具)把图片合并到一张图里面,再通过css定位把对于的样式写出来引用的html里面。对于一些图片较多的项目,这个过程可能要花费我们一天的时间,来实现这步。今天我给大家带来一个工具,将这一步缩短到几秒钟就能完成,究竟是什么工具这么神奇呢,他就是gulp的一个插件gulp.spritesmith。下面一张图来说明他能做什么。\r\n\r\n![](http://p1.bpimg.com/567571/e55bcb8c0182dab2.png)\r\n看到这个图片介绍,相信大家已经对gulp.spritesmith能做到什么一目了然了,其他的不多说,下面说直接开撸:\r\n\r\n## 安装gulp.spritesmith\r\n```js\r\nnpm install gulp.spritesmith --save-dev\r\n```\r\n\r\n## 编写Gulpfile.js\r\n```js\r\n/引入gulp\r\nvar gulp=require(\"gulp\"),\r\n spritesmith=require('gulp.spritesmith');\r\n\r\ngulp.task('default', function () {\r\n return gulp.src('images/*.png')//需要合并的图片地址\r\n .pipe(spritesmith({\r\n imgName: 'sprite.png',//保存合并后图片的地址\r\n cssName: 'css/sprite.css',//保存合并后对于css样式的地址\r\n padding:5,//合并时两个图片的间距\r\n algorithm: 'binary-tree',//注释1\r\n cssTemplate:\"css/handlebarsStr.css\"//注释2\r\n }))\r\n .pipe(gulp.dest('dist/'));\r\n});\r\n```\r\n## 注释一:\r\n\r\nAlgorithm 有四个可选值分别为top-down、left-right、diagonal、alt-diagonal、binary-tree\r\n\r\n对应如下:\r\n![](http://p1.bpimg.com/567571/3e5e6867f51ea2a3.png)\r\n\r\n\r\n## 注释二:\r\n\r\ncssTemplate 是生成css的模板文件可以是字符串也可以是函数。是字符串是对于相对于的模板地址 对于模板文件样式格式是:\r\n```css\r\n{{#sprites}}\r\n.icon-{{name}}{\r\n background-image: url(\"{{escaped_image}}\");\r\n background-position: {{px.offset_x}} {{px.offset_y}};\r\n width: {{px.width}};\r\n height: {{px.height}};\r\n}\r\n{{/sprites}}\r\n```\r\n\r\n如果是函数的话,这可以这样写:\r\n```js\r\n//引入gulp\r\nvar gulp=require(\"gulp\"),\r\n spritesmith=require('gulp.spritesmith');\r\n\r\ngulp.task('default', function () {\r\n\r\n return gulp.src('images/*.png')//需要合并的图片地址\r\n .pipe(spritesmith({\r\n imgName: 'sprite.png',//保存合并后图片的地址\r\n cssName: 'css/sprite.css',//保存合并后对于css样式的地址\r\n padding:5,//合并时两个图片的间距\r\n algorithm: 'binary-tree',//注释1\r\n cssTemplate: function (data) {\r\n var arr=[];\r\n data.sprites.forEach(function (sprite) {\r\n arr.push(\".icon-\"+sprite.name+\r\n \"{\" +\r\n \"background-image: url('\"+sprite.escaped_image+\"');\"+\r\n \"background-position: \"+sprite.px.offset_x+\"px \"+sprite.px.offset_y+\"px;\"+\r\n \"width:\"+sprite.px.width+\";\"+\r\n \"height:\"+sprite.px.height+\";\"+\r\n \"}\\n\");\r\n });\r\n return arr.join(\"\");\r\n }\r\n\r\n }))\r\n .pipe(gulp.dest('dist/'));\r\n});\r\n```\r\n以上就是实现将css代码中的切片图片合并成雪碧图的实现,有问题的大家可以留言","cover":"","link":"gulp-sprite.html","preview":"\u003cp\u003e相信做前端的同学都做过这样的事情,为优化图片,减少请求会把拿到切好的图标图片,通过ps(或者其他工具)把图片合并到一张图里面,再通过css定位把对于的样式写出来引用的html里面。\u003c/p\u003e\n","title":"使用gulp自动合成雪碧图"},{"content":"\r\n压缩 图片文件可降低文件大小,提高图片加载速度。\r\n\r\n找到规律转换为 gulp 代码\r\n\r\n\r\n---\r\n找到 `images/` 目录下的所有文件,压缩它们,将压缩后的文件存放在 `dist/images/` 目录下。\r\n\r\ngulp 代码\r\n---------\r\n\r\n**一、安装 gulp-imagemin** 模块\r\n\r\n提示:你需要使用命令行的 `cd` 切换至对应目录再进行安装操作和 gulp 启动操作。\r\n\r\n在命令行输入\r\n\r\n```bash\r\nnpm install gulp-imagemin\r\n```\r\n\r\n安装成功后你会看到如下信息:(安装时间可能会比较长)\r\n\r\n```bash\r\ngulp-imagemin@2.2.1 node_modules/gulp-imagemin\r\n├── object-assign@2.0.0\r\n├── pretty-bytes@1.0.3 (get-stdin@4.0.1)\r\n├── chalk@1.0.0 (escape-string-regexp@1.0.3, ansi-styles@2.0.1, supports-color@1.3.1, has-ansi@1.0.3, strip-ansi@2.0.1)\r\n├── through2-concurrent@0.3.1 (through2@0.6.3)\r\n├── gulp-util@3.0.4 (array-differ@1.0.0, beeper@1.0.0, array-uniq@1.0.2, lodash._reevaluate@3.0.0, lodash._reescape@3.0.0, lodash._reinterpolate@3.0.0, replace-ext@0.0.1, minimist@1.1.1, vinyl@0.4.6, through2@0.6.3, multipipe@0.1.2, lodash.template@3.3.2, dateformat@1.0.11)\r\n└── imagemin@3.1.0 (get-stdin@3.0.2, optional@0.1.3, vinyl@0.4.6, through2@0.6.3, stream-combiner@0.2.1, concat-stream@1.4.7, meow@2.1.0, vinyl-fs@0.3.13, imagemin-svgo@4.1.2, imagemin-optipng@4.2.0, imagemin-jpegtran@4.1.0, imagemin-pngquant@4.0.0, imagemin-gifsicle@4.1.0)\r\n```\r\n\r\n**二、创建 `gulpfile.js` 文件编写代码**\r\n\r\n在对应目录创建 `gulpfile.js` 文件并写入如下内容:\r\n\r\n```js\r\n// 获取 gulp\r\nvar gulp = require('gulp');\r\n\r\n// 获取 gulp-imagemin 模块\r\nvar imagemin = require('gulp-imagemin')\r\n\r\n// 压缩图片任务\r\n// 在命令行输入 gulp images 启动此任务\r\ngulp.task('images', function () {\r\n // 1. 找到图片\r\n gulp.src('images/*.*')\r\n // 2. 压缩图片\r\n .pipe(imagemin({\r\n progressive: true\r\n }))\r\n // 3. 另存图片\r\n .pipe(gulp.dest('dist/images'))\r\n});\r\n\r\n// 在命令行使用 gulp auto 启动此任务\r\ngulp.task('auto', function () {\r\n // 监听文件修改,当文件被修改则执行 images 任务\r\n gulp.watch('images/*.*)', ['images'])\r\n});\r\n\r\n// 使用 gulp.task('default') 定义默认任务\r\n// 在命令行使用 gulp 启动 images 任务和 auto 任务\r\ngulp.task('default', ['images', 'auto'])\r\n```\r\n\r\n你可以访问 [gulp-imagemin](https://github.com/sindresorhus/gulp-imagemin) 以查看更多用法。\r\n\r\n------\r\n\r\n**三、在 `images/` 目录下存放图片**\r\n\r\n在 `gulpfile.js` 对应目录创建 `images` 文件夹,并在 `images/` 目录下存放图片。\r\n\r\n--------\r\n\r\n**四、运行 gulp 查看效果**\r\n\r\n在命令行输入 `gulp` +回车\r\n\r\n你将看到命令行出现如下提示\r\n\r\n```\r\ngulp\r\n[18:10:42] Using gulpfile ~/Documents/code/gulp-book/demo/chapter4/gulpfile.js\r\n[18:10:42] Starting 'images'...\r\n[18:10:42] Finished 'images' after 5.72 ms\r\n[18:10:42] Starting 'auto'...\r\n[18:10:42] Finished 'auto' after 6.39 ms\r\n[18:10:42] Starting 'default'...\r\n[18:10:42] Finished 'default' after 5.91 μs\r\n[18:10:42] gulp-imagemin: Minified 3 images (saved 25.83 kB - 5.2%)\r\n```","cover":"","link":"gulp-compress-img.html","preview":"\u003cp\u003e压缩 图片文件可降低文件大小,提高图片加载速度。找到规律转换为 gulp 代码\u003c/p\u003e\n","title":"使用 gulp 压缩图片"},{"content":"\r\n\r\n压缩 css 代码可降低 css 文件大小,提高页面打开速度。\r\n\r\n我们接着将规律转换为 gulp 代码\r\n\r\n---\r\n找到 `css/` 目录下的所有 css 文件,压缩它们,将压缩后的文件存放在 `dist/css/` 目录下。\r\n\r\ngulp 代码\r\n---------\r\n\r\n当熟悉`使用 gulp 压缩 JS`的方法后,配置压缩 CSS 的 gulp 代码就变得很轻松。\r\n\r\n\r\n**一、安装 gulp-minify-css** 模块\r\n\r\n提示:你需要使用命令行的 `cd` 切换到对应目录后进行安装操作。\r\n\r\n在命令行输入\r\n\r\n```\r\nnpm install gulp-minify-css\r\n```\r\n\r\n安装成功后你会看到如下信息:(安装时间可能会比较长)\r\n\r\n```\r\ngulp-minify-css@1.0.0 node_modules/gulp-minify-css\r\n├── object-assign@2.0.0\r\n├── vinyl-sourcemaps-apply@0.1.4 (source-map@0.1.43)\r\n├── clean-css@3.1.8 (commander@2.6.0, source-map@0.1.43)\r\n├── through2@0.6.3 (xtend@4.0.0, readable-stream@1.0.33)\r\n├── vinyl-bufferstream@1.0.1 (bufferstreams@1.0.1)\r\n└── gulp-util@3.0.4 (array-differ@1.0.0, beeper@1.0.0, array-uniq@1.0.2, lodash._reescape@3.0.0, lodash._reinterpolate@3.0.0, lodash._reevaluate@3.0.0, replace-ext@0.0.1, minimist@1.1.1, multipipe@0.1.2, vinyl@0.4.6, chalk@1.0.0, lodash.template@3.3.2, dateformat@1.0.11)\r\n```\r\n\r\n**二、参照`使用 gulp 压缩 JS`创建 `gulpfile.js` 文件编写代码**\r\n\r\n在对应目录创建 `gulpfile.js` 文件并写入如下内容:\r\n\r\n```js\r\n// 获取 gulp\r\nvar gulp = require('gulp')\r\n\r\n// 获取 minify-css 模块(用于压缩 CSS)\r\nvar minifyCSS = require('gulp-minify-css')\r\n\r\n// 压缩 css 文件\r\n// 在命令行使用 gulp css 启动此任务\r\ngulp.task('css', function () {\r\n // 1. 找到文件\r\n gulp.src('css/*.css')\r\n // 2. 压缩文件\r\n .pipe(minifyCSS())\r\n // 3. 另存为压缩文件\r\n .pipe(gulp.dest('dist/css'))\r\n})\r\n\r\n// 在命令行使用 gulp auto 启动此任务\r\ngulp.task('auto', function () {\r\n // 监听文件修改,当文件被修改则执行 css 任务\r\n gulp.watch('css/*.css', ['css'])\r\n});\r\n\r\n// 使用 gulp.task('default') 定义默认任务\r\n// 在命令行使用 gulp 启动 css 任务和 auto 任务\r\ngulp.task('default', ['css', 'auto'])\r\n```\r\n\r\n你可以访问 [gulp-minify-css](https://github.com/jonathanepollack/gulp-minify-css) 以查看更多用法。\r\n\r\n------\r\n\r\n**三、创建 css 文件**\r\n\r\n在 `gulpfile.js` 对应目录创建 `css` 文件夹,并在 `css/` 目录下创建 `a.css` 文件。\r\n\r\n```css\r\n/* a.css */\r\nbody a{\r\n color:pink;\r\n}\r\n```\r\n\r\n--------\r\n\r\n**四、运行 gulp 查看效果**\r\n\r\n在命令行输入 `gulp` +回车\r\n\r\n你将看到命令行出现如下提示\r\n\r\n```\r\ngulp\r\n[17:01:19] Using gulpfile ~/Documents/code/gulp-book/demo/chapter3/gulpfile.js\r\n[17:01:19] Starting 'css'...\r\n[17:01:19] Finished 'css' after 6.21 ms\r\n[17:01:19] Starting 'auto'...\r\n[17:01:19] Finished 'auto' after 5.42 ms\r\n[17:01:19] Starting 'default'...\r\n[17:01:19] Finished 'default' after 5.71 μs\r\n```\r\n\r\ngulp 会创建 `dist/css` 目录,并创建 `a.css` 文件,此文件存放压缩后的 css 代码。","cover":"","link":"gulp-compress-css.html","preview":"\u003cp\u003e压缩 css 代码可降低 css 文件大小,提高页面打开速度。我们接着将规律转换为 gulp 代码\u003c/p\u003e\n","title":"使用 gulp 压缩 CSS"},{"content":"\r\n压缩 js 代码可降低 js 文件大小,提高页面打开速度。在不利用 gulp 时我们需要通过各种工具手动完成压缩工作。\r\n\r\n所有的 gulp 代码编写都可以看做是将规律转化为代码的过程。\r\n\r\n\r\n---\r\n\r\n找到 `js/` 目录下的所有 js 文件,压缩它们,将压缩后的文件存放在 `dist/js/` 目录下。\r\n\r\ngulp 代码\r\n----\r\n\r\n**建议**:你可以只阅读下面的代码与注释或同时阅读代码解释\r\n\r\ngulp 的所有配置代码都写在 `gulpfile.js` 文件。\r\n\r\n**一、新建一个 `gulpfile.js` 文件**\r\n```\r\nchapter2\r\n└── gulpfile.js\r\n```\r\n\r\n---------\r\n\r\n**二、在 `gulpfile.js` 中编写代码**\r\n\r\n```js\r\n// 获取 gulp\r\nvar gulp = require('gulp')\r\n```\r\n\r\n\u003e `require()` 是 node (CommonJS)中获取模块的语法。\r\n\u003e \r\n\u003e 在 gulp 中你只需要理解 `require()` 可以获取模块。\r\n\r\n---------\r\n\r\n**三、获取 `gulp-uglify` 组件**\r\n\r\n```js\r\n// 获取 uglify 模块(用于压缩 JS)\r\nvar uglify = require('gulp-uglify')\r\n```\r\n\r\n---------\r\n\r\n**四、创建压缩任务**\r\n\r\n```js\r\n// 压缩 js 文件\r\n// 在命令行使用 gulp script 启动此任务\r\ngulp.task('script', function() {\r\n // 1. 找到文件\r\n gulp.src('js/*.js')\r\n // 2. 压缩文件\r\n .pipe(uglify())\r\n // 3. 另存压缩后的文件\r\n .pipe(gulp.dest('dist/js'))\r\n})\r\n```\r\n\r\n- `gulp.task(name, fn)` - 定义任务,第一个参数是任务名,第二个参数是任务内容。\r\n- `gulp.src(path)` - 选择文件,传入参数是文件路径。\r\n- `gulp.dest(path)` - 输出文件\r\n- `gulp.pipe()` - 管道,你可以暂时将 pipe 理解为将操作加入执行队列\r\n\r\n参考:[gulp API文档](http://www.gulpjs.com.cn/docs/api/)\r\n\r\n---------\r\n\r\n**五、跳转至 `gulpfile.js` 所在目录**\r\n\r\n打开命令行使用 `cd` 命令跳转至 `gulpfile.js` 文件所在目录。\r\n\r\n例如我的 `gulpfile.js` 文件保存在 `C:\\gulp-book\\demo\\chapter2\\gulpfile.js`。\r\n\r\n那么就需要在命令行输入\r\n```\r\ncd C:\\gulp-book\\demo\\chapter2\r\n```\r\n\r\n\u003e Mac 用户可使用 `cd Documents/gulp-book/demo/chapter2/` 跳转\r\n\r\n---------\r\n\r\n**六、使用命令行运行 script 任务**\r\n\r\n在控制台输入 `gulp 任务名` 可运行任务,此处我们输入 `gulp script` 回车。\r\n\r\n注意:输入 `gulp script` 后命令行将会提示错误信息\r\n```\r\n// 在命令行输入\r\ngulp script\r\n\r\nError: Cannot find module 'gulp-uglify'\r\n at Function.Module._resolveFilename (module.js:338:15)\r\n at Function.Module._load (module.js:280:25)\r\n```\r\n\r\n`Cannot find module 'gulp-uglify'` 没有找到 `gulp-uglify` 模块。\r\n\r\n----------\r\n\r\n**七、安装 `gulp-uglify` 模块**\r\n\r\n因为我们并没有安装 `gulp-uglify` 模块到本地,所以找不到此模块。\r\n\r\n使用 npm 安装 `gulp-uglify` 到本地\r\n\r\n```\r\nnpm install gulp-uglify\r\n```\r\n\r\n安装成功后你会看到如下信息:\r\n```\r\ngulp-uglify@1.1.0 node_modules/gulp-uglify\r\n├── deepmerge@0.2.7\r\n├── uglify-js@2.4.16 (uglify-to-browserify@1.0.2, async@0.2.10, source-map@0.1.34, optimist@0.3.7)\r\n├── vinyl-sourcemaps-apply@0.1.4 (source-map@0.1.43)\r\n├── through2@0.6.3 (xtend@4.0.0, readable-stream@1.0.33)\r\n└── gulp-util@3.0.4 (array-differ@1.0.0, beeper@1.0.0, array-uniq@1.0.2, object-assign@2.0.0, lodash._reinterpolate@3.0.0, lodash._reescape@3.0.0, lodash._reevaluate@3.0.0, replace-ext@0.0.1, minimist@1.1.1, chalk@1.0.0, lodash.template@3.3.2, vinyl@0.4.6, multipipe@0.1.2, dateformat@1.0.11)\r\nchapter2 $\r\n```\r\n\r\n在你的文件夹中会新增一个 `node_modules` 文件夹,这里面存放着 npm 安装的模块。\r\n\r\n目录结构:\r\n```\r\n├── gulpfile.js\r\n└── node_modules\r\n └── gulp-uglify\r\n```\r\n\r\n接着输入 `gulp script` 执行任务\r\n\r\n```\r\ngulp script\r\n[13:34:57] Using gulpfile ~/Documents/code/gulp-book/demo/chapter2/gulpfile.js\r\n[13:34:57] Starting 'script'...\r\n[13:34:57] Finished 'script' after 6.13 ms\r\n```\r\n\r\n------------\r\n\r\n**八、编写 js 文件**\r\n\r\n我们发现 gulp 没有进行任何压缩操作。因为没有js这个目录,也没有 js 目录下的 `.js` 后缀文件。\r\n\r\n创建 `a.js` 文件,并编写如下内容\r\n\r\n```\r\n// a.js\r\nfunction demo (msg) {\r\n alert('--------\\r\\n' + msg + '\\r\\n--------')\r\n}\r\n\r\ndemo('Hi')\r\n```\r\n\r\n目录结构:\r\n```\r\n├── gulpfile.js\r\n├── js\r\n│ └── a.js\r\n└── node_modules\r\n └── gulp-uglify\r\n```\r\n\r\n接着在命令行输入 `gulp script` 执行任务\r\n\r\ngulp 会在命令行当前目录下创建 `dist/js/` 文件夹,并创建压缩后的 `a.js` 文件。\r\n\r\n目录结构:\r\n```\r\n├── gulpfile.js\r\n├── js\r\n│ └── a.js\r\n├── dist\r\n│ └── js\r\n│ └── a.js\r\n└── node_modules\r\n └── gulp-uglify\r\n```\r\n\r\n[dist/js/a.js](https://github.com/nimojs/gulp-book/blob/master/demo/chapter2/dist/js/a.js)\r\n```js\r\nfunction demo(n){alert(\"--------\\r\\n\"+n+\"\\r\\n--------\")}demo(\"Hi\");\r\n```\r\n\r\n---------\r\n\r\n**九、检测代码修改自动执行任务**\r\n\r\n`js/a.js`一旦有修改 就必须重新在命令行输入 `gulp script` ,这很麻烦。\r\n\r\n可以使用 `gulp.watch(src, fn)` 检测指定目录下文件的修改后执行任务。\r\n\r\n在 `gulpfile.js` 中编写如下代码:\r\n```\r\n// 监听文件修改,当文件被修改则执行 script 任务\r\ngulp.watch('js/*.js', ['script']);\r\n```\r\n\r\n但是没有命令可以运行 `gulp.watch()`,需要将 `gulp.watch()` 包含在一个任务中。\r\n\r\n```\r\n// 在命令行使用 gulp auto 启动此任务\r\ngulp.task('auto', function () {\r\n // 监听文件修改,当文件被修改则执行 script 任务\r\n gulp.watch('js/*.js', ['script'])\r\n})\r\n```\r\n\r\n接着在命令行输入 `gulp auto`,自动监听 `js/*.js` 文件的修改后压缩js。\r\n\r\n```\r\n$gulp auto\r\n[21:09:45] Using gulpfile ~/Documents/code/gulp-book/demo/chapter2/gulpfile.js\r\n[21:09:45] Starting 'auto'...\r\n[21:09:45] Finished 'auto' after 9.19 ms\r\n```\r\n\r\n此时修改 `js/a.js` 中的代码并保存。命令行将会出现提示,表示检测到文件修改并压缩文件。\r\n\r\n```\r\n[21:11:01] Starting 'script'...\r\n[21:11:01] Finished 'script' after 2.85 ms\r\n```\r\n至此,我们完成了 gulp 压缩 js 文件的自动化代码编写。\r\n\r\n**注意:**使用 `gulp.watch` 后你的命令行会进入“运行”状态,此时你不可以在命令行进行其他操作。可通过 `Ctrl + C` 停止 gulp。\r\n\r\n\u003e Mac 下使用 `control + C` 停止 gulp\r\n\r\n**十、使用 gulp.task('default', fn) 定义默认任务**\r\n\r\n增加如下代码\r\n\r\n```js\r\ngulp.task('default', ['script', 'auto']);\r\n```\r\n\r\n此时你可以在命令行直接输入 `gulp` +回车,运行 `script` 和 `auto` 任务。\r\n\r\n最终代码如下:\r\n\r\n```js\r\n// 获取 gulp\r\nvar gulp = require('gulp')\r\n\r\n// 获取 uglify 模块(用于压缩 JS)\r\nvar uglify = require('gulp-uglify')\r\n\r\n// 压缩 js 文件\r\n// 在命令行使用 gulp script 启动此任务\r\ngulp.task('script', function() {\r\n // 1. 找到文件\r\n gulp.src('js/*.js')\r\n // 2. 压缩文件\r\n .pipe(uglify())\r\n // 3. 另存压缩后的文件\r\n .pipe(gulp.dest('dist/js'))\r\n})\r\n\r\n// 在命令行使用 gulp auto 启动此任务\r\ngulp.task('auto', function () {\r\n // 监听文件修改,当文件被修改则执行 script 任务\r\n gulp.watch('js/*.js', ['script'])\r\n})\r\n\r\n\r\n// 使用 gulp.task('default') 定义默认任务\r\n// 在命令行使用 gulp 启动 script 任务和 auto 任务\r\ngulp.task('default', ['script', 'auto'])\r\n```\r\n\r\n去除注释后,你会发现只需要 11 行代码就可以让 gulp 自动监听 js 文件的修改后压缩代码。但是还有还有一些性能问题和缺少容错性,将在后面的章节详细说明。\r\n\r\n\r\n你可以访问 [gulp-uglify](https://github.com/terinjokes/gulp-uglify) 以查看更多用法。","cover":"","link":"gulp-compress-js.html","preview":"\u003cp\u003e压缩 js 代码可降低 js 文件大小,提高页面打开速度。在不利用 gulp 时我们需要通过各种工具手动完成压缩工作。所有的 gulp 代码编写都可以看做是将规律转化为代码的过程。\u003c/p\u003e\n","title":"使用 gulp 压缩 JS"},{"content":"\r\n# 1. Browserify简介\r\n\u003e \"Browserify lets you require('modules') in the browser by bundling up all of your dependencies.\" - Browserify.org\r\n\r\n上面的描述是摘自 browserify 官网;用通俗的话讲就是:browserify 是一个浏览器端代码模块化工具,可以处理模块之间的依赖关系,让服务器端的 CommonJS 格式的模块可以运行在浏览器端。\r\n\r\n\r\n![](https://cloud.githubusercontent.com/assets/3995814/11768221/b22531fe-a200-11e5-8e98-8e36d8471bf8.png)\r\n\r\nbrowserify的原理:\r\n\r\n* 处理代码依赖,将模块打包到一起\r\n\r\n打包为单个文件存在的问题:\r\n\r\n* 暂时用不到的代码也会被打包,体积大,首次加载速度慢\r\n\r\n* 只要一个模块更新,整个文件缓存失效\r\n\r\n注:暂时用不到的代码指不同的页面有不同的 JS 文件,不需要在当前页面引用其他页面的代码即为暂时用不到的代码\r\n\r\nBrowserify的解决方案:\r\n\r\n* entry point:入口点技术,每个入口点打包一个文件,两个入口点的相同依赖模块单独打包为common.js\r\n\r\n### 安装与配置\r\n安装 browserify\r\n\r\n``npm install -g browserify``\r\n\r\n引入 browserify\r\n\r\n``import browserify from 'browserify'``\r\n\r\n基本配置\r\n```js\r\nglup.taks('browserify', function() {\r\n browserify({\r\n //先处理依赖,入口文件\r\n entries: ['./foo.js','./main.js'],\r\n //进行转化\r\n transform: []\r\n })\r\n .bundle() // 多个文件打包成一个文件\r\n .pipe(source()) // browserify的输出不能直接用做gulp输入,所以需要source进行处理 \r\n .pipe(gulp.dest('./')); \r\n})\r\n```\r\n### 安装一些依赖插件\r\n```bash\r\nnpm install --save-dev vinyl-source-stream vinyl-buffer gulp-sourcemaps\r\n```\r\n\r\n``vinyl-source-stream``: browserify的输出不能直接用着gulp的输入,vinly-source-stream 主要是做一个转化\r\n\r\n``vinyl-buffer``: 用于将vinyl流转化为buffered vinyl文件(gulp-sourcemaps及大部分Gulp插件都需要这种格式)\r\n\r\n``gulp-sourcemaps``: Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置,便于调试\r\n\r\n``Watchify``: 加速 browserify 编译\r\n\r\n# 2. 编写 CommonJS 模块\r\n\r\n### 目录结构\r\n```\r\n|-- dist/\r\n |-----bundle.js\r\n|-- src/\r\n |-----foo.js\r\n |-----main.js\r\n|--gulpfile.babel.js\r\n|--package.json\r\n```\r\n\r\n### 新建两个模块文件 foo.js, main.js\r\n``$ touch foo.js main.js``\r\n\r\n### 让我使用 CommonJs 规范来写一些代码\r\n\u003e CommonJS 规范是为了解决 JavaScript的作用域问题而定义的模块形式,可以使每个模块在它自身的命名空间中执行。\r\n\u003e 该规范的主要内容是,模块必须通过module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。\r\n\r\n```js\r\n// foo.js\r\n// 定义foo.js模块,通过 module.exports 导出对外的变量或接口\r\nlet variable = 8;\r\nlet sum = (a, b = 6) =\u003e (a + b);\r\nlet square = (b) =\u003e {\r\n return b * b;\r\n};\r\nmodule.exports.variable = variable;\r\nmodule.exports.sum = sum;\r\nmodule.exports.square = square;\r\n\r\n// mian.js\r\n// 通过 require() 来导入 foo.js 模块\r\nvar bar = require('./foo')\r\nconsole.log(bar); // Object\r\nconsole.log(bar.variable); // 8\r\nconsole.log(bar.sum(1)); // 7\r\nconsole.log(bar.square(5)); // 25\r\n```\r\n\u003e 上面我们使用 ES6 的语法写了两个模块,分别是 foo.js 和 main.js; 在 foo.js 中通过 module.exports 导出对外的变量或接口;在 main.js 中通过 require() 来导入 foo.js 模块,那我们就可以在 mian.js 模块中使用 foo.js 中的变量和接口了。这就是一个最基本的 CommonJs 示例了\r\n\r\n### 配置 browserify\r\n通过第一小节的学习,我们知道要在浏览器中运行 CommonJs 风格的模块代码,就需要借助 browserify 来作为转换工具,下面我们在 gulp.babel.js 中来配置 browserify:\r\n```js\r\n// set browserify task\r\ngulp.task('browserify',()=\u003e {\r\n browserify({\r\n entries: ['src/js/main.js','src/js/foo.js'],\r\n debug: true, // 告知Browserify在运行同时生成内联sourcemap用于调试\r\n })\r\n .transform(\"babelify\", {presets: [\"es2015\"]})\r\n .bundle()\r\n .pipe(source('bundle.js'))\r\n .pipe(buffer()) // 缓存文件内容\r\n .pipe(sourcemaps.init({loadMaps: true})) // 从 browserify 文件载入 map\r\n .pipe(sourcemaps.write('.')) // 写入 .map 文件\r\n .pipe(gulp.dest('dist/js'))\r\n .pipe(notify({ message: 'browserify task complete' }));\r\n})\r\n```\r\n\r\n运行\r\n``gulp-browserify``\r\n\r\n![](https://cloud.githubusercontent.com/assets/3995814/11768266/592159dc-a202-11e5-8be3-2d4ddaefe5c3.png)\r\n\r\n### 打开浏览器,查看运行结果(见上面main.js文件的注释)\r\n\r\n# 编写 ES6 Module 模块\r\n\r\n上面的代码主要是 CommonJs 模块化的写法,我们再来看看最近火热的 ES6 提供的 Module;让我们使用 ES6 Module 来改写上面的代码:\r\n```js\r\n// foo.js\r\n// 定义foo.js模块,通过 exports 导出对外的变量或接口\r\nlet variable = 8;\r\nlet sum = (a, b = 6) =\u003e (a + b);\r\nlet square = (b) =\u003e {\r\n return b * b;\r\n};\r\nexport { variable, sum, square };\r\n\r\n// main.js\r\n// 测试一:\r\n// 通过 import 来导入 foo.js 模块\r\nimport {variable, sum, square} from './foo';\r\nconsole.log(variable); // 8\r\nconsole.log(sum(1)); // 7\r\nconsole.log(square(5)); // 25\r\n\r\n// 测试二:\r\n// 直接引用整个 foo 模块\r\nimport bar from './foo';\r\nconsole.log(bar); // 输出值是undefined,后面做解释\r\n\r\n// 测试三:\r\n// 通过 ES6 的语法加载整个 foo模块\r\nimport * as bar from './foo'\r\nconsole.log(bar); // Object\r\n```\r\n\r\n## 总结 CommonJs 和 ES6 Module\r\n\r\n### CommonJs\r\n* 根据 CommonJS 规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(包括函数和类),都是私有的,对其他文件是不可见的\r\n* 通过 module.exports 对象,定义对外接口,其他文件加载该模块,实际上就是读取 module.exports 变量\r\n* 通过 require 命令加载模块文件,然后返回该模块的exports对象\r\n### ES6 Module\r\n* 通过 export 命令用于规定模块的对外接口\r\n* 通过 import 命令用于加载其他模块提供的功能\r\n### ES6 Module 与 CommonJs 的区别\r\n* 在ES6中使用 import 取代 require\r\n* 在ES6中使用 export 取代 module.exports\r\n* ES6 在编译时就能确定模块的依赖关系,以及输入和输出的变量,而 CommonJs 只能在运行时确定模块的依赖关系以及输入和输出的变量。\r\n- 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”\r\n- 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,输入时采用静态命令的形式。即在输入时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”\r\n注:上面提到 ES6 加载模块时是采用指定加载某个输出值的形式,如果你要想加载整个模块,你可以这么做:\r\n``import * as customName from './moduleFileName';``","cover":"","link":"gulp-commonjs-es6.html","preview":"\u003cp\u003eCommonJS 规范是为了解决 JavaScript的作用域问题而定义的模块形式,可以使每个模块在它自身的命名空间中执行。该规范的主要内容是,模块必须通过module.exports 导出对外的变量或接口,通过 require()来导入其他模块的输出到当前模块作用域中。\u003c/p\u003e\n","title":"使用 gulp 搭建CommonJs \u0026 ES6 模块化环境"},{"content":"\r\n是时候抛弃繁重的Grunt了。Gulp是一个直观的、配置的、基于流的任务发布系统,而且它更高效。\r\n\r\n\r\n![](http://p1.bpimg.com/567571/0440708ae3690092.jpg)\r\n\r\n为什么我会感兴趣呢?好问题。Gulp通过配置写代码不仅使得它编写任务简单,而且更加方便阅读和维护。\r\n\r\nGulp运用node.js的流,这使得它构建任务很快,因为没有磁盘文件的读写操作,如果你想了解更多关于流的知识,你可以看看[这个](https://github.com/substack/stream-handbook)。Gulp允许你输入源文件,然后在一系列的管道插件中处理,最后输出,不像Grunt你需要为每个插件配置输入和输出。下面就让我们通过一个sass编译的例子来看看Gulp和Grunt的差异吧。\r\n\r\n**Grunt:**\r\n\r\n```javascript\r\nsass: {\r\n dist: {\r\n options: {\r\n style: 'expanded'\r\n },\r\n files: {\r\n 'dist/assets/css/main.css': 'src/styles/main.scss',\r\n }\r\n }\r\n},\r\n\r\nautoprefixer: {\r\n dist: {\r\n options: {\r\n browsers: [\r\n 'last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'\r\n ]\r\n },\r\n src: 'dist/assets/css/main.css',\r\n dest: 'dist/assets/css/main.css'\r\n }\r\n},\r\n\r\ngrunt.registerTask('styles', ['sass', 'autoprefixer']);\r\n```\r\n\r\nGrunt要求每个插件配置要相互独立、要分别为每个插件配置输入源和输出路径。如,我们在sass插件里面配置了一个输入文件,然后保存输出。接着我们需要配置Autoprefixer的输入为Sass的输出,然后再输出了一个文件。让我们来看看Gulp是怎么做的:\r\n\r\n**Gulp:**\r\n\r\n```javascript\r\ngulp.task('sass', function() {\r\n return gulp.src('src/styles/main.scss')\r\n .pipe(sass({ style: 'compressed' }))\r\n .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))\r\n .pipe(gulp.dest('dist/assets/css'))\r\n});\r\n```\r\n\r\n在Gulp中我们只配置一次输入文件,然后依次通过Sass插件处理,再传给`Autoprefixer`插件处理,然后我们得到输出文件。整个过程没有读取和写入不必要的文件,效率大大提高。\r\n\r\n因此,你感兴趣了么?让我们从安装Gulp,创建基本的任务配置文件`gulpfile`开始吧。\r\n\r\n**安装gulp**\r\n\r\n在我们开始配置任务之前,我们先要安装gulp:\r\n\r\n```bash\r\nnpm install gulp -g\r\n```\r\n\r\n这样gulp就以全局的方式安装了,你可以在任何node命令行里面调用`gulp CLI`。然后我们需要在本地的某个项目里面使用`gulp`。使用`cd`命令进入到项目目录,运行下面的命令(先确保项目目录存在`package.json`文件):\r\n\r\n```bash\r\nnpm install gulp --save-dev\r\n```\r\n\r\n这会把gulp安装到本地项目,并且把依赖的包写入到`package.json`文件的`devDependencies`里面\r\n\r\n**安装gulp插件**\r\n\r\n我们将会安装下列插件来开始我们的任务:\r\n\r\n- Sass 编译 ([gulp-ruby-sass](https://github.com/sindresorhus/gulp-ruby-sass))\r\n- 添加浏览器前缀Autoprefixer([gulp-autoprefixer](https://github.com/Metrime/gulp-autoprefixer))\r\n- CSS压缩([gulp-minify-css](https://github.com/jonathanepollack/gulp-minify-css))\r\n- JS语法检查 ([gulp-jshint](https://github.com/wearefractal/gulp-jshint))\r\n- 文件合并 ([gulp-concat](https://github.com/wearefractal/gulp-concat))\r\n- JS压Uglify ([gulp-uglify](https://github.com/terinjokes/gulp-uglify))\r\n- 图片压缩([gulp-imagemin](https://github.com/sindresorhus/gulp-imagemin))\r\n- LiveReload ([gulp-livereload](https://github.com/vohof/gulp-livereload))\r\n- 图片缓存,只压缩修改过的图片([gulp-cache](https://github.com/jgable/gulp-cache/))\r\n- 修改提醒([gulp-notify](https://github.com/mikaelbr/gulp-notify))\r\n- 文件清理 ([del](https://www.npmjs.org/package/del))\r\n\r\n运行下面的命令安装这些插件:\r\n\r\n```bash\r\nnpm install gulp-ruby-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev\r\n```\r\n\r\n这将会安装所有的依赖插件,并写入到package.json的devDependencies里面。所有的gulp插件列表可以[在这里](http://gratimax.net/search-gulp-plugins/)看到。\r\n\r\n**加载插件**\r\n\r\n我们需要创建一个`gulpfile.js`,然后使用这些插件:\r\n\r\n```javascript\r\nvar gulp = require('gulp'),\r\n sass = require('gulp-ruby-sass'),\r\n autoprefixer = require('gulp-autoprefixer'),\r\n minifycss = require('gulp-minify-css'),\r\n jshint = require('gulp-jshint'),\r\n uglify = require('gulp-uglify'),\r\n imagemin = require('gulp-imagemin'),\r\n rename = require('gulp-rename'),\r\n concat = require('gulp-concat'),\r\n notify = require('gulp-notify'),\r\n cache = require('gulp-cache'),\r\n livereload = require('gulp-livereload'),\r\n del = require('del');\r\n```\r\n\r\n我们也可以像grunt那样自动加载插件:[auto load](https://github.com/jackfranklin/gulp-load-plugins)\r\n\r\n**创建任务**\r\n\r\n*编译sass、加前缀、压缩*\r\n\r\n```javascript\r\ngulp.task('styles', function() {\r\n return gulp.src('src/styles/main.scss')\r\n .pipe(sass({ style: 'expanded' }))\r\n .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))\r\n .pipe(gulp.dest('dist/assets/css'))\r\n .pipe(rename({suffix: '.min'}))\r\n .pipe(minifycss())\r\n .pipe(gulp.dest('dist/assets/css'))\r\n .pipe(notify({ message: '样式任务完成' }));\r\n});\r\n```\r\n\r\n\u003e sass({ style: 'expanded' }:编译后保留原格式\r\n\r\n\r\n```javascript\r\ngulp.task('styles', function() { ... )};\r\n```\r\n\r\n`gulp.task`API是用来创建任务的。然后通过命令`gulp styles`运行这个任务。\r\n\r\n\t\r\n```javascript\r\nreturn gulp.src('src/styles/main.scss')\r\n```\r\n\r\n`gulp.src`API用来配置输入的源文件。也可以用模式匹配,如`/**/*.scss`匹配所有文件夹下面后缀为`.scss`的文件作为输入。通过返回流使得它是异步的,确保在提醒任务完成的时候任务是完成了的。\r\n\r\n```javascript\r\n.pipe(sass({ style: 'expanded' }))\r\n```\r\n\r\n通过`.pipe()`把源文件流入一个插件的管道中。然后我们可以去插件的官网看看这个插件的详细用法。\r\n\r\n```javascript\r\n.pipe(gulp.dest('dist/assets/css'));\r\n```\r\n\r\n`gulp.dest`API是用来告知输出文件的路径的。一个任务可以有多个输出,如一个用来输出原来的版本(即源文件),一个输出处理后的版本(即输出文件)。你可以在上面的`styles`任务中看到。\r\n\r\n建议去看[gulp api文档](https://github.com/gulpjs/gulp/blob/master/docs/API.md),这样会更加清楚。\r\n\r\n**js语法检查、合并和压缩任务**\r\n\r\n```javascript\r\ngulp.task('scripts', function() {\r\n return gulp.src('src/scripts/**/*.js')\r\n .pipe(jshint('.jshintrc'))\r\n .pipe(jshint.reporter('default'))\r\n .pipe(concat('main.js'))\r\n .pipe(gulp.dest('dist/assets/js'))\r\n .pipe(rename({suffix: '.min'}))\r\n .pipe(uglify())\r\n .pipe(gulp.dest('dist/assets/js'))\r\n .pipe(notify({ message: 'Scripts task complete' }));\r\n});\r\n```\r\n\r\n这里用的`JSHin`t插件,我们使用了默认的`JSHint Reporter`,可能适用于大多数人,想了解更多可以去[jshint官网](http://www.jshint.com/docs/reporters/)看\r\n\r\n**图片压缩任务**\r\n\r\n```javascript\r\ngulp.task('images', function() {\r\n return gulp.src('src/images/**/*')\r\n .pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))\r\n .pipe(gulp.dest('dist/assets/img'))\r\n .pipe(notify({ message: 'Images task complete' }));\r\n});\r\n```\r\n\r\n这里我们只用了`imagemin`插件,但是可以做的更好,我们可以缓存修改过的图片,或者只对修改过的图片进行再次的压缩操作,因此我们可以使用[gulp-cahce](https://github.com/jgable/gulp-cache)插件,因此我们需要将这行代码:\r\n\r\n```javascript\r\n.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))\r\n```\r\n\r\n改成:\r\n\r\n```javascript\r\n.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))\r\n```\r\n\r\n此时,只有新的图片或者改变过的图片才会被压缩。\r\n\r\n**文件清理**\r\n\r\n在再次发布之前,我们最好把目标文件的文件先清理掉,然后重新构建:\r\n\r\n```javascript\r\ngulp.task('clean', function(cb) {\r\n\t del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], cb)\r\n\t});\r\n```\r\n\r\n**默认任务**\r\n\r\n我们可以通过`$ gulp`启动默认任务,然后在默认任务中调用其他任务:\r\n\r\n```javascript\r\ngulp.task('default', ['clean'], function() {\r\n\tgulp.start('styles', 'scripts', 'images');\r\n});\r\n```\r\n\r\n\r\n看到`gulp.task`里面的数组了吧?这里定义了任务的依赖,也就是说`default`任务依赖`clean`任务。在这个例子中,执行`gulp.start`之前会先运行`clean`任务。Gulp里面的任务同时进行,没有明确的顺序哪个先完成,所以我们要确保`clean`任务执行完之后再执行`gulp.start`里面的任务。\r\n\r\n\u003e 虽然不建议在执行依赖任务数组的时候使用`gulp.start`,但是在这里我们没有办法确保`clean`任务执行完毕后再执行其它任务,因此这里使用`gulp.start`貌似是最好的选择。\r\n\r\n\r\n**Watch任务**\r\n\r\n当文件发生变化的时候,我们可能需要重新执行任务,因此我们需要配置一个监听文件变化的任务:\r\n\r\n```javascript\r\ngulp.task('watch', function() {\r\n\r\n // Watch .scss files\r\n gulp.watch('src/styles/**/*.scss', ['styles']);\r\n\r\n // Watch .js files\r\n gulp.watch('src/scripts/**/*.js', ['scripts']);\r\n\r\n // Watch image files\r\n gulp.watch('src/images/**/*', ['images']);\r\n\r\n});\r\n```\r\n\r\n我们通过`gulp.watch`API来监听文件的变化,然后执行相关的依赖任务。现在我们可以执行`$ gulp watch`命令来执行我们的`watch`任务,监听`.scss`、`.js`或者图片文件的变化执行相应的任务。\r\n\r\n**LiveReload任务**\r\n\r\n当我们代码修改的时候,Gulp也可以主动帮我们刷新页面,此时我们需要配置`LiveReload`服务,并修改我们的`watch`任务:\r\n\r\n```javascript\r\ngulp.task('watch', function() {\r\n\r\n // Create LiveReload server\r\n livereload.listen();\r\n\r\n // Watch any files in dist/, reload on change\r\n gulp.watch(['dist/**']).on('change', livereload.changed);\r\n\r\n});\r\n```\r\n\r\n要让这个任务生效,我们还需要安装并开启浏览器LiveReload插件,我们也可以[手动添加代码片段](http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-)。\r\n\r\n**整合这些任务**\r\n\r\n把上面的这些任务综合起来,就构成了一个完整的`gulpfile`:\r\n\r\n```javascript\r\n// gulpfile.js\r\n// Load plugins\r\nvar gulp = require('gulp'),\r\n sass = require('gulp-ruby-sass'),\r\n autoprefixer = require('gulp-autoprefixer'),\r\n minifycss = require('gulp-minify-css'),\r\n jshint = require('gulp-jshint'),\r\n uglify = require('gulp-uglify'),\r\n imagemin = require('gulp-imagemin'),\r\n rename = require('gulp-rename'),\r\n concat = require('gulp-concat'),\r\n notify = require('gulp-notify'),\r\n cache = require('gulp-cache'),\r\n livereload = require('gulp-livereload'),\r\n del = require('del');\r\n \r\n// Styles\r\ngulp.task('styles', function() {\r\n return gulp.src('src/styles/main.scss')\r\n .pipe(sass({ style: 'expanded', }))\r\n .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))\r\n .pipe(gulp.dest('dist/styles'))\r\n .pipe(rename({ suffix: '.min' }))\r\n .pipe(minifycss())\r\n .pipe(gulp.dest('dist/styles'))\r\n .pipe(notify({ message: 'Styles task complete' }));\r\n});\r\n \r\n// Scripts\r\ngulp.task('scripts', function() {\r\n return gulp.src('src/scripts/**/*.js')\r\n .pipe(jshint('.jshintrc'))\r\n .pipe(jshint.reporter('default'))\r\n .pipe(concat('main.js'))\r\n .pipe(gulp.dest('dist/scripts'))\r\n .pipe(rename({ suffix: '.min' }))\r\n .pipe(uglify())\r\n .pipe(gulp.dest('dist/scripts'))\r\n .pipe(notify({ message: 'Scripts task complete' }));\r\n});\r\n \r\n// Images\r\ngulp.task('images', function() {\r\n return gulp.src('src/images/**/*')\r\n .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))\r\n .pipe(gulp.dest('dist/images'))\r\n .pipe(notify({ message: 'Images task complete' }));\r\n});\r\n \r\n// Clean\r\ngulp.task('clean', function(cb) {\r\n del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], cb)\r\n});\r\n \r\n// Default task\r\ngulp.task('default', ['clean'], function() {\r\n gulp.start('styles', 'scripts', 'images');\r\n});\r\n \r\n// Watch\r\ngulp.task('watch', function() {\r\n \r\n // Watch .scss files\r\n gulp.watch('src/styles/**/*.scss', ['styles']);\r\n \r\n // Watch .js files\r\n gulp.watch('src/scripts/**/*.js', ['scripts']);\r\n \r\n // Watch image files\r\n gulp.watch('src/images/**/*', ['images']);\r\n \r\n // Create LiveReload server\r\n livereload.listen();\r\n \r\n // Watch any files in dist/, reload on change\r\n gulp.watch(['dist/**']).on('change', livereload.changed);\r\n \r\n});\r\n```","cover":"","link":"gulp-basic.html","preview":"\u003cp\u003e是时候抛弃繁重的Grunt了。Gulp运用node.js的流,这使得它构建任务很快,因为没有磁盘文件的读写操作\u003c/p\u003e\n","title":"Gulp入门指南"},{"content":"\r\nBootstrap3使用了四种栅格选项来形成栅格系统,这四种选项在官网上的介绍很多人不理解,这里跟大家详解一下四种栅格选项之间的区别,其实区别只有一条就是适合不同尺寸的屏幕设备。我们看class前缀这一项,我们姑且以前缀命名这四种栅格选项,他们分别是col-xs ,col-sm,col-md,col-lg,我们懂英文的就知道,lg是large的缩写,md是mid的缩写,sm是small的缩写,xs是extra small的缩写。这样命名就体现了这几种class适应的屏幕宽度不同。下面我们分别介绍这几种class的特点。 \r\n\r\n## 1、col-xs类\r\n用法是\u003ccode\u003e\u0026lt;div class=\"col-xs-*\"\u0026gt;\u003c/code\u003e。它星号代表1~12的数字。我们知道栅格系统总共有12列,我们在这里使用数字几就代表着该div占用几列的宽度。假如我们在给超级小屏幕开发界面,那么我们使用该类,该类没有任何行为,不管屏幕小到多少,都不会改变div的布局。\r\n\r\n## 2、col-sm类\r\n用法是\u003ccode\u003e\u0026lt;div class=\"col-sm-*\"\u0026gt;\u003c/code\u003e。星号的意义同上,但是该类适合屏幕宽度为`750px`的设备,如果在屏幕宽度小于750px的设别上,该div就会水平堆叠。\r\n这是在`大于750px`屏幕上的样式:\r\n![](http://7xoatu.com1.z0.glb.clouddn.com/o_1ahgo0fcs11eg114i150n8mtgdga.jpg)\r\n这是在`小于750px`屏幕上的样式:\r\n![](http://7xoatu.com1.z0.glb.clouddn.com/o_1ahgnv4oa1o2ni62183v8l61npea.jpg)\r\n\r\n\r\n## 3、col-md类\r\n用法是\u003ccode\u003e\u0026lt;div class=\"col-md-*\"\u0026gt;\u003c/code\u003e。该类适合`970px`以上屏幕。通上面讲的道理一样,假如使用屏幕尺寸小于`970px`的设备查看网页,div就会垂直堆叠。\r\n这是在`大于970px`屏幕上的样式:\r\n![](http://7xoatu.com1.z0.glb.clouddn.com/o_1ahgo0fcs11eg114i150n8mtgdga.jpg)\r\n这是在`小于970px`屏幕上的样式:\r\n![](http://7xoatu.com1.z0.glb.clouddn.com/o_1ahgnv4oa1o2ni62183v8l61npea.jpg)\r\n\r\n\r\n## 4、col-lg类\r\n用法是\u003ccode\u003e\u0026lt;div class=\"col-lg-*\"\u0026gt;\u003c/code\u003e。该类适合`1170px`以上屏幕。通上面讲的道理一样,假如使用屏幕尺寸小于`1170px`的设备查看网页,div就会垂直堆叠。\r\n这是在`大于1170px`屏幕上的样式:\r\n![](http://7xoatu.com1.z0.glb.clouddn.com/o_1ahgo0fcs11eg114i150n8mtgdga.jpg)\r\n这是在`小于1170px`屏幕上的样式:\r\n![](http://7xoatu.com1.z0.glb.clouddn.com/o_1ahgnv4oa1o2ni62183v8l61npea.jpg)\r\n\r\n如何组合使用这几个类?\r\n我们使用\u003ccode\u003e\u0026lt;div class=\"col-sm-10 col-md-8\"\u0026gt;\u003c/code\u003e这样的方式来表示:在中等屏幕设备上该div占用8列的宽度;在小屏幕上该div占用10列的宽度。","cover":"","link":"bootstrap-grid.html","preview":"\u003cp\u003eBootstrap3使用了四种栅格选项来形成栅格系统,这四种选项在官网上的介绍很多人不理解,这里跟大家详解一下四种栅格选项之间的区别,其实区别只有一条就是适合不同尺寸的屏幕设备。\u003c/p\u003e\n","title":"Bootstrap详解:栅格系统"},{"content":"\r\n\r\nLESS, Sass 和其他 CSS 预处理器是一种超棒的方法用来扩展 CSS功能,使之更适合程序员。\r\n你可以使用变量、函数、混合、继承等多种编程常用方法来编写 CSS,以更少的代码完成更多的样式。\r\n学习这些工具最好的方法是通过各种实例快速入门,今天我们向你介绍 10 个非常有用的使用 Less CSS 的实例。\r\n\r\n# 1、变量\r\n变量允许我们单独定义一系列通用的样式,然后在需要的时候去调用。所以在做全局样式调整的时候我们可能只需要修改几行代码就可以了。\r\n```css\r\n // LESS\r\n\r\n@color: #4D926F;\r\n\r\n#header {\r\n color: @color;\r\n}\r\nh2 {\r\n color: @color;\r\n}\r\n```\r\n\r\n/* 生成的 CSS */\r\n```css\r\n#header {\r\n color: #4D926F;\r\n}\r\nh2 {\r\n color: #4D926F;\r\n}\r\n```\r\n\r\n# 2、混合\r\n混合可以将一个定义好的class A轻松的引入到另一个class B中,从而简单实现class B继承class A中的所有属性。我们还可以带参数地调用,就像使用函数一样。\r\n```css\r\n// LESS\r\n\r\n.rounded-corners (@radius: 5px) {\r\n border-radius: @radius;\r\n -webkit-border-radius: @radius;\r\n -moz-border-radius: @radius;\r\n}\r\n\r\n#header {\r\n .rounded-corners;\r\n}\r\n#footer {\r\n .rounded-corners(10px);\r\n}\r\n```\r\n/* 生成的 CSS */\r\n```css\r\n#header {\r\n border-radius: 5px;\r\n -webkit-border-radius: 5px;\r\n -moz-border-radius: 5px;\r\n}\r\n#footer {\r\n border-radius: 10px;\r\n -webkit-border-radius: 10px;\r\n -moz-border-radius: 10px;\r\n}\r\n```\r\n\r\n# 3、嵌套规则\r\n我们可以在一个选择器中嵌套另一个选择器来实现继承,这样很大程度减少了代码量,并且代码看起来更加的清晰。\r\n```css\r\n// LESS\r\n\r\n#header {\r\n h1 {\r\n font-size: 26px;\r\n font-weight: bold;\r\n }\r\n p { font-size: 12px;\r\n a { text-decoration: none;\r\n \u0026:hover { border-width: 1px }\r\n }\r\n }\r\n}\r\n```\r\n/* 生成的 CSS */\r\n```css\r\n#header h1 {\r\n font-size: 26px;\r\n font-weight: bold;\r\n}\r\n#header p {\r\n font-size: 12px;\r\n}\r\n#header p a {\r\n text-decoration: none;\r\n}\r\n#header p a:hover {\r\n border-width: 1px;\r\n}\r\n```\r\n\r\n# 4、函数 \u0026 运算\r\n运算提供了加,减,乘,除操作;我们可以做属性值和颜色的运算,这样就可以实现属性值之间的复杂关系。LESS中的函数一一映射了JavaScript代码,如果你愿意的话可以操作属性值。\r\n```css\r\n// LESS\r\n\r\n@the-border: 1px;\r\n@base-color: #111;\r\n@red: #842210;\r\n\r\n#header {\r\n color: @base-color * 3;\r\n border-left: @the-border;\r\n border-right: @the-border * 2;\r\n}\r\n#footer { \r\n color: @base-color + #003300;\r\n border-color: desaturate(@red, 10%);\r\n}\r\n```\r\n/* 生成的 CSS */\r\n```css\r\n#header {\r\n color: #333;\r\n border-left: 1px;\r\n border-right: 2px;\r\n}\r\n#footer { \r\n color: #114411;\r\n border-color: #7d2717;\r\n}\r\n```\r\n\r\n# 5、圆角\r\nCSS3 一个非常基本的新属性可以快速的生产圆角效果,如上图所示。要使用 CSS3 的圆角效果我们必须针对不同的浏览器定义各自的前缀,而如果使用了 LESS 就可以不用那么麻烦。\r\n* 简单的圆角半径\r\n我的第一个 LESS 代码是我最简单的 LESS 代码之一,我需要设置圆角的半径,而且我希望使用一个变量来调整这个半径大小。\r\n下面代码使用 mixin 技术,通过定义 .border-radius 并接收一个 radius 参数,该参数默认值是 5px,你可以在多个地方重复使用该 mixin 方法:\r\n```css\r\n/* Mixin */\r\n.border-radius (@radius: 5px) {\r\n -webkit-border-radius: @radius;\r\n -moz-border-radius: @radius;\r\n border-radius: @radius;\r\n}\r\n \r\n/* Implementation */\r\n#somediv {\r\n .border-radius(20px);\r\n}\r\n```\r\n将这个 less 编译成 css 后的结果是:\r\n```css\r\n/* Compiled CSS */\r\n#somediv {\r\n -webkit-border-radius: 20px;\r\n -moz-border-radius: 20px;\r\n border-radius: 20px;\r\n}\r\n```\r\n* 四角的半径定制\r\n如果你希望用户可自由定制四个角的半径,那么我们需要对上面代码做下改进。\r\n使用4个变量分别代表四个边角的半径大小:\r\n\r\n```css\r\n/* Mixin */\r\n.border-radius-custom (@topleft: 5px, @topright: 5px, @bottomleft: 5px, @bottomright: 5px) {\r\n -webkit-border-radius: @topleft @topright @bottomright @bottomleft;\r\n -moz-border-radius: @topleft @topright @bottomright @bottomleft;\r\n border-radius: @topleft @topright @bottomright @bottomleft;\r\n}\r\n \r\n/* Implementation */\r\n#somediv {\r\n .border-radius-custom(20px, 20px, 0px, 0px);\r\n}\r\n```\r\n编译后的 CSS\r\n```css\r\n/* Compiled CSS */\r\n#somediv {\r\n -webkit-border-radius: 20px 20px 0px 0px;\r\n -moz-border-radius: 20px 20px 0px 0px;\r\n border-radius: 20px 20px 0px 0px;\r\n}\r\n```","cover":"","link":"newbie-5-less-css.html","preview":"\u003cp\u003eLESS, Sass 和其他 CSS 预处理器是一种超棒的方法用来扩展 CSS功能,使之更适合程序员。你可以使用变量、函数、混合、继承等多种编程常用方法来编写 CSS,以更少的代码完成更多的样式。学习这些工具最好的方法是通过各种实例快速入门,今天我们向你介绍 10 个非常有用的使用 Less CSS 的实例。\u003c/p\u003e\n","title":"新手必须掌握5个LESS CSS"}]