lihuiyang99 / lihuiyang.github.io Goto Github PK
View Code? Open in Web Editor NEW李会洋学习博客
李会洋学习博客
用来检测数据类型的运算符,使用typeof检测数据类型,首先返回一个字符串,其次字符串中包含了对应的数据类型,例如:
局限性:
检测某一个实例是否属于某个类
局限性:
1、不能用来检测和处理自变量方式创建出来的基本数据类型的值
2、instanceof特性:只要在当前实例的原型链上我们用其检测的结果都是true
3、在类的原型继承中,最后的结果未必准确
对于基本类型来说,自变量创建出来的结果和实例创建出来的结果是有一定区别的,从严格意义上来讲,只有实例创建出来的才是标准的对象数据类型,对于字面量的方式创建出来的结果是基本的数据类型,不是严谨的实例,但由于js的松散型,导致 了可以使用Number.prototype上提供的方法
构造函数 作用与instanceof 可以处理基本数据类型
局限性:
我们可以把类的原型重写,在写的过程中很有可能把之前的constructor给覆盖了,这样检测出来结果就是不准确的。
对于特殊的数据类型null和undefined他们的所属类Null和Undefined,浏览器吧两个类保护起来了,U允许在外面访问使用
最准确最常用的方式
Vue.js 是用于构建交互式的 Web 界面的库。它提供了 MVVM 数据绑定和一个可组合的组件系统,具有简单、灵活的 API。从技术上讲, Vue.js 集中在 MVVM 模式上的视图模型层,并通过双向数据绑定连接视图和模型。实际的 DOM 操作和输出格式被抽象出来成指令和过滤器。相比其它的 MVVM 框架,Vue.js 更容易上手。
创建一个Vue的实例 vm=$scope vm就是当前的viewModule var vm=new Vue({ el:'#app',选择作用的范围 data:{ 绑定数据 是数据层 name:1 } })
var vm=new Vue({ el:'#app', data:message }) created:function(){console.log('实例已经创建')}; beforeCompiled:function(){console.log('编译之前')}; compiled:function(){console.log('编译后')}; ready:function(){console.log('插入到文档中')}; beforeDestroy:function(){console.log('销毁之前')}; destroyed:function(){console.log('销毁')}; vm.$mount('#app');
computed:{bb:function(){return this.name+'hello'}}
computed:{ get:fn set:fn }
12、v-cloak 放置闪烁 里面的内容都不会闪烁
13、v-show v-if
14、template 防止嵌套无意义的标签 当执行从的时候不解析template 不用使用到v-show
15、v-else 紧跟show 和if 不要跨逻辑
16 v-for 数组遍历
17、$index 索引 放置位置
举例:
< div v-for='(index,book) in books'> < div>{{index}}{{$index}}< /div> < /div>
< div @click='dosome(1,$event)'> methods:{ dosome:function(a,ev){} }< /div>
v-bind:keyup.enter='tap' 自定义b事件修饰 Vue.directive('on').keyCodes.b=66; 默认的操作键enter left right。。。。
26、v-bind:class={{style}} 直接动态引入数据
27、:class="['red','yellow']" 绑定数组
28、:class="['red',flag?'orange':'yellow']" 三元表达式
29、:class="['red',{yellow:flag},flag?'orange':yellow]" 最简单的用法 数组 对象并用
30、注:class="{{className}}"和v:bind:class="obj"不要混用
class可以和v-bind:class公用
31、过滤器
capitalize 首字母大写 uppercase lowercase currency {{1| pluralize 'item'}} {{json|json}} debounce 5000 给事件进行延迟 事件名后
举例
< button @click="count+=2">下一页 < button> < div v-for="a in arr|limited 2 count">{{a.name}}< /div>
我们想传递数据 先从属性 传递到props 在从props取出来绑定到组件里
组件可以嵌套
先创建子组件 子组件创建后查到父组件里
看组件上的属性 如果设置的属性前面有:说明是动态的 去vm上找到对应的数据
传到父级的组件上 找到组件中的props 对应的变量 先默认走coerce 如果没有coerce 并且数据没有传递走default 在一次验证
html: < div id='app'> < input type='radio' value='home' v-model='current' name='cc'>home < input type='radio' value='page' v-model='current' name='cc'>page < component :is="current">< /component> < /div> js: component:{ home:{ template:'< div>Hello' }, home:{ template:'< div>world< /div>' }, }
在canvas画布上使用鼠标单击选中一个三维模型对象,三维模型然后做出反应,比如弹出一个窗口,颜色发生变化,要实现这一点首先把鼠标单击位置的屏幕坐标转化成标准设备坐标,然后社祝Vector3对象的方法unproject把标准设备坐标转化成世界坐标,然后利用鼠标单击位置对应的屏幕坐标转化得到世界坐标和相机对象的世界坐标两个创建一个射线对象 Ravcaster
下面的代码完成的作用是当鼠标单击canvas画布的时候,如果选中一个网格模型的时候,该网格模型的材质对象有不透明状态转化成半透明状态
//射线拾取函数 //选中的网格模型变为半透明的效果 function ray(e){ var Sx = e.clientX; //鼠标单击位置横坐标 var Sy = e.clientY; //鼠标单击位置纵坐标 //屏幕坐标转世界坐标 var x = (Sx/window.innerWidth)*2 -1; //标准设备的横坐标 var y = -(Sy/window.innerHeight)*2+1; //标准设备的纵坐标 var standarVector = new THREE.Vector3(x,y,0.5) //标准设备坐标 // 标准坐标转换成世界坐标 var worldVector = standardVector.unproject(camera) // 射线投射方向单位单位向量(worldVector坐标减相机的位置坐标) var ray = worldVector.sub(camera.position).normalize() // 创建射线投影器对象 var raycaster = new THREE.Raycaster(camera.position,ray) // 返回射线选中的对象 var intersects = raycaster.intersectObject([boxMesh,sphereMash,cylinderMesh]) if(intersects.length>0){ intersects[0].obect.material.transparent = true; intersects[0].obect.material.opacity = 0.6; } } addEventListener('click',ray)
通过方法addEventLister可以实时监控鼠标事件,一旦发生鼠标单击事件就执行函数ray(),执行ray函数,首先通过鼠标事件的返回的事件event的坐标属性 clientX、clientY获取鼠标单位位置相对浏览器窗口客户区的坐标
//屏幕坐标转化标准设备坐标 var x = (Sx/window.innerWidth)*2 -1; var y = -(Sy/window.innerHeight)*2+1; var standardVector = new THREE.Vector3(x,y,0.5)
通过返回坐标值clientX、clientY读入坐标变换公式转化为标准设备坐标系下标准设备坐标 X、y 、z可以随意自定义一个值
//标准设备坐标转世界坐标 var worldVector = standardVector.unproject(camera)
unproject是Vector3对象的方法,作用是把标准设备坐标转化为世界,方法的参数是相机对象camera,该方法的使用可以参考Vector3对象的project方法,这两个方法的作用都是进行坐标变换,只不过方向是反过来的
格式Raycaster(origin,direction,near,far),origin表示射线起点位置,direction表示射线方向,两个参数的数据类型都是Vector3对象
通过构造函数new THREE.Raycaster(camera.position,ray),创建一个射线发射器对象,camera.position是相机的位置,ray表示射线方向向量, 通过语句 ray = worldVector.sub(camera.position).normalize();创建,sub是一个Vector3方法,两个坐标向量进行相减运算,normalize方法表示对象向量进行归一化计算,计算相机对上面转化得到的世界坐标进行减法计算,然后归一化得到一个单位向量
通过射线发射器对象的方法intersectObjects 可以返回射线旋转的所有对象,该方法的参数是一个object3D对象构成的数组,表示射线对象选择的范围,凡是选中的都会以数组的形式返回,返回的数据结构是[{distance,point ,face,faceIndex,indices,object},...],比如案例中参数,如果两个网格模型屏幕坐标位置是重合的,那么都会被选中,因此可以通过数组下标的形式访问第几个对象,被选中的网格模型对象以object属性的形式存在,代码中intersectObjects[0].object就表示被选中的所有网格模型中的第一个网格模型对象,通过语句intersectObjects[0].object.material.opacity = 0.6;可以更改材质对象的透明度
// 原来的 module.exports 代码赋值给变量 webpackConfig,
//即将原来的module.exports 改为 const webpackConfig
module.exports = vuxLoader.merge(webpackConfig, { plugins: ['vux-ui'] })
在Three中相机的表示是THREE.camera,他是相机的抽象类,其子类有两种相机,分别是正投影相机THREE.OrthographicCamera和透视相机THBREE.PerspectiveCamera,
正投影和透视投影的区别是:透视投影有个基本点,就是远处的事物比近处的物体小,
我们来介绍正投影相机,正投影的构造数如下所示,OrthographicCamera(left,right,top,bottom.near,far)
我们假设的相机中心点,下面介绍一下构造函数的参数:
- 1、left 参数:左平面距离相机中心点的垂直距离,从图中可以看出,左平面是屏幕里面的那个平面
- 2、right参数:右平面距离相机中心点的垂直距离,从图中可以看到,右平面是屏幕稍微外面的一点的那个平面
- 3、top参数:顶平面距离相机中心点的垂直距离,上图中的顶平面,是长方体头顶朝天的平面
- 4、bottom参数:地平面距离相机中心点的垂直距离,地平面是头朝着地平面
- 5、near参数:近平面距离相机中心点的垂直距离,近平面是左边竖着的那个平面
- 6、far参数:远平面距离相机中心点的垂直距离,远平面是右边竖着的那个平面
有了这些参数和相机的中心点,我们这里将相机中心点又定义为相机的位置,通过这些参数,我们就能够在三维空间里唯一的确定上图的一个长方形,这个长方体也叫作视景体
投影变换的目的就是定义一个视景体,使得视景体外的多余的部分剪掉,最终图像只是视景体内部有关部分
简单例子如下
var camera = THREE.OrthographicCamera(width/-2,width/2.hright/2,hright/-2,1,1000) scene.add(camera)
透视投影相机更加符合我们视觉的投影,当透视一个女人时,就是因为远近高低各不同,女人才显得美丽,正因为,透视相机有这么大的魅力,所以在各种应用中运用广泛,
PerspectiveCamera(fov,aspect,near,far)
先来明确这个图里涉及的概念
- 视角fov:这个最难理解,我的理解是眼睛睁开的大小,如果设置为0,相当于闭上了眼睛,
- 近平面near:表示你近处截面的距离,也可以认为是眼睛距离近处的距离,
- 远平面far:表示远处的截面
- 纵横比aspect:实际窗口的纵横比,即宽度除以高度,这个值越大,说明你的宽度越大,
小例子
var camera = new THREE.PerspeactiveCamera(45,width/height,1,1000)
var renderer function initThree(){ //这个函数的作用是 定义渲染器 并将渲染器加入模块中 width = document.getElementById('##').clientWidth; height = document.getElementById('##).clientHeight; renderer = new THREE.WebGLRenderer({ antialias:true }) renderer.setSize(width,height) document.getElementById('##').appendChild(renderer.domElement) renderer.setClearColor(oooxxx,1.0) } var camera; function initCamera(){ // 定义相机 相机的位置 camera = new THREE.PerspectiveCamera(45,width/height,1.1000); camera = new THREE.orthograpicCamera(window.innerWidth/-2,width.innerWidth/2,Height.innerHeight/2,Height.innerHeight/-2,1,1000) camera.position.x=0; camera.position.y=0; camera.position.z=500; camera.up.x=0; camera.up.y=1; camera.up.z=0; camera.lookAt({ x:0, y:0, z:0 }) } var scene function initScene(){ // 定义舞台 scene = new THREE.Scene(); } var light //光源设置 // AmbientLight: 环境光,基础光源,它的颜色会被加载到整个场景和所有对象的当前颜色上。 // PointLight:点光源,朝着所有方向都发射光线 //SpotLight :聚光灯光源:类型台灯,天花板上的吊灯,手电筒等 // DirectionalLight:方向光,又称无限光,从这个发出的光源可以看做是平行光. function initLight(){ light = new THREE.AmbientLight('XXX'); //环境光 light.position.set(100,100,200); scene.add(light); light =new THREE.PointLight('XXX'); //点光源 light.position.set(0,0,300); scene.add(light) } var cube //材质和几何体构成网格,决定几何体是否像金属,透明与否,已经是否显示成线框。 //MeshBasicMaterial:对光照无感,给几何体一种简单的颜色或显示线框 //MeshLambertMaterial:这种材质对光照有反应,用于创建暗淡的不发光的物体 //MeshPhongMaterial:这种材质对光照也有反应,用于创建金属类明亮的物体 function initObject(){ var geometry = new THREE.CylinderGeometry(70,100,200); //定义一个圆柱体 var material =new THREE.meshLamberMaterial({color:XXXX}) // var material = new THREE.Mesh(geometry,material); mesh.position = new THREE.Vector3(0,0,0) scene.add(mesh) } function threeStart(){ initTHREE() initCamera() initScene() initLight() initObject() animation() } function animation(){ changeFov(); //更换角度吧 renderer.render(scene,canera) requestAnimationFrame(animation); } function setCameraFov(fov){ camera.fov = fov; camera.updateProjectionMatrix(); //矩阵 } fucntion changeFov(){ var txtFov = document.getElementById('##').value; var val = parseFloat(txtFov); setCameraFov(val) }
函数中的this指向和当前函数在那里定义或执行的没有任何的关系
分析this指向的规律如下:
var obj={ fn:(function(i){ //this 是window return function(){ //this 是obj } })(0) }; obj.fn();- 2、给元素的某个行为绑定一个方法,当行为触发的时候,执行绑定的方法,此时方法中的this是当前元素案例一 oDiv.onclick=function(){ //this 是oDiv }; 案例二 function fn(){ //this 是window } oDiv.onclick=function(){ //this 是oDiv fn(); };
案例一 var obj ={fn:fn}; function fn(){} fn.prototype.aa=function(){}; var f=new fn; fn();//this 就是window obj.fn();//this就是obj fn.prototype.aa();//this 就是prototype f.aa;this就是f f.__proto__.aa();//this就是f.__proto__
案例一 function Fn(){ this.x=100;//this是window this.getX=function(){ console.log(this);//this是f 因为getX在执行的时候,“.”前面是F,所以this是f } }
"use strict";//告诉浏览器我们接下来编写的JS代码采用严格模式
var obj={ fn:(function(){ //this 是undefined return function(){ //this 是obj} })(0) }; obj.fn();
var obj={fn:fn}; function fn(){} fn();//this 是undefined obj.fn();//this 是obj
我们发现严格模式下的this相对于非严格模式下的this主要区别在于:对于JS代码这种没有写执行主体的情况下,非严格模式下默认的都是window执行的,所以this指向的是window;但是在严格的模式下,没有写就是没有执行主体,this指向的是undefined
(ps:https://ssr.vuejs.org/zh/)
注意 本指南需要最低为如下版本的Vue,以及以下library支持 1、vue & vue-server-render2.3.0+ 2、vue-router 2.5.0+ 3、vue-loader 12.0.0+ & vue-style-loader 3.0.0+
vue.js是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出Vue组件,进行生成DOM和操作DOM,然而,也可以在同一个组件渲染为服务器端的HTML字符串,将他们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
服务器渲染的vue.js应用程序也可以被认为是同构或者通用,因为引应用程序的大部分代码都可以在 服务器端和浏览器端上进行。
与传统的SPA相比,服务器端渲染的优势主要在于:
更好的SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
请注意,截止目前,Google和Bing可以很好对同步JS应用程序进行索引,在这里,同步是关键,如果你的应用程序初始展示loading菊花图,然后通过Ajax获取内容,抓取工具并不会等待异步完成后再行抓取页面内容,也就是说SEO对你的站点至关重要,而你的页面优势异步获取内容,则你可能需要服务器端渲染解决此类问题
更快的内容到达时间(time-to-content),特别是对于缓慢的网络情况或者运行缓慢的设备,无需等待所有的JS都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快的看到完整渲染的页面,通常可以产生更好的用户体验,并且对于那些 内容到达时间(time-to-content)与转化率直接相关的应用程序而言,服务器渲染(SSR)至关重要
使用服务器端渲染时还需要一些权衡之处
在对你的应用程序使用服务器端渲染之前,你应该问的第一个问题是,是否真的需要他,这主要取决于内容到达时间对应用程序的重要程度。例如,如果你正在构建一个内部仪表盘,初始加载时间的额外几百毫秒并不重要,这种情况下去使用服务器端渲染将是一个小题大作,然而,内容到达时间要求是一个决对关键的指标,在这种情况下,服务器端渲染可以帮助你实现最佳的初始加载性能
如果你调研服务器端渲染只是用来改善少数营销页面(例如:/,/about, /contant等)的SEO,那么你可能需要预渲染,无需使用web服务器实时动态编译HTML,而是使用预渲染方式,在构建时间简单的生成对特定路由的静态HTML文件,优点是设置预渲染更简单,并可以将你的前端作为一个完全的静态的站点。
如果你用的是webpack,你可以使用prerender-spa-plugin轻松的添加预渲染,他已经被Vue应用程序广发测试
npm install vue vue-server-renderer --save
//第一步:创建一个vue实例 const Vue = require('vue') const app = new Vue({ template:`< div>Hello world< /div>` }) //第二步:创建一个renderer const renderer = require('vue-server-renderer').creatRenderer() //第三步:将vue实例渲染为Html renderer.renderToString(app,(err,html)=>{ if(err) throw err console.log(html) //< div data-server-render='true'>Hello world < /div> }) //在2.5.0+,如果没有传入回调函数,则会返回 Promise; renderer.renderToString(app).then(html=>{ console.log(html) }).catch(err=>{ console.log(err) })
在Node服务器中使用时相当简单直接,例如 Express
npm install express --save
const Vue = require('vue') cosnt server = require('express')() const renderer = require('vue-server-renderer').createRenderer() server.get('*',(res,req)=>{ const app = new Vue({ data:{ url:req.url }, template:`< div>访问的URL是:{{url}}}< /div>` }) renderer.renderToString(app,(err,html)=>{ if(err) { res.status(500).end('Internal Server Error') return } res.end(` < !DOCTYPE html> < html lang="en"> < head>< title>Hello< /title>< /head> < body>${html}< /body> < /html> `) }) }) server.listen(8080)
当你在渲染Vue应用程序时,renderer只从应用程序中生成HTML标记,在这个示例中,我们必须用一个额外的HTML页面包裹容器,来包裹生成的HTML标记
为了简化这些,你可以直接创建renderer时候提供一个页面模板,多数时候,我们会将页面模板放在特有的文件中,例如:index.temoplate.html:
< !DOCTYPE html> < html lang = 'en'> < head> < title>Hello< /title>< /head> < body> < !-- vue-ssr-outlet --> < /body> < /html >
注意 vue-ssr-outlet 注释 这里将是应用程序HTML标记注入的地方
然后 ,我们可以读取和传入文件到Vue renderer中
const renderer = createRenderer({ template:require('fs').readFileSync('./index.template.html','utf-8') }) renderer.renderToSttrinf(app,(err,html)=>{ cosole.log(html) //HTML将是注入应用程序内容的完整页面 })
ES2017标准引入了async函数,使得异步操作变的更加方便
async函数是什么,一句话,它就是Generator函数的语法糖
前文有一个Generator函数,以此读取两个文件
const fs = require('fs'); const readFile = function(fileName){ return new Promise(function(resolve,reject){ fs.readFile(fileName,function(err,data){ if(err) retutn reject(err) resolve(data) }) }) } const gen = function* { const f1 = yield readFile('a') const f2 = yield readFile('b') const f3 = yield readFile('c') }
写成async函数,就是下面这样
const asyncReadFile = async function(){ const f1 = await readFile('a'); const f2 = await readFile('b'); }
一比较就会发现,async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已
async函数对Generator函数改进,体现在一下四点。
Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器,也就是说 async函数的执行,与普通函数一样,
asyncReadFile
上面的代码调用了asyncReadFile函数,然后就会自动执行,输出最后结果,这完全不像Generator函数,需要调用next方法,或者co模块,才真正执行,才得到最后结果
async 和await,比起星号和yield,语意更清楚了,async表示函数里有异步操作,await表示紧跟后面的表达式需要等待结果,
co模块约定,yield,命令后面只能是Thunk函数或者Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值,字符串,布尔值,但是这等同于同步操作)
async函数的返回值是Promise对象,这比Generator函数的返回值Iterator对象方便多了,可以用then方法指定下一步操作
进一步说,async函数完全可以看做多个异步操作,包装成一个Promise对象,而await命令就是内部then命令的语法糖
async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回,等待异步操作完成,再接着执行函数体内后面的语句
下面一个例子
async function getStockPriceByName(name){ const symbol = await getStockSymbol(name) const stockPrice= await getStockPrice(symbol) return stockPrice; } getStockPriceByName('goog').then(function(result){ console.log(result) })
上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作,调用该函数时,会立即返回一个Promise对象
下面是另一个例子,指定多少毫秒输出一个值
function timeput(ms){ return new Promise(resolve=>{ setTimeout(resolve,ms) }) } async function asyncPrint(value,ms){ await timeout(ms); console.log(value); } asyncPrint('hello world',50)
上面代码指定59毫秒以后,输出hello world
由于async函数返回的是Promise对象,可以作为await命令的参数,所以,上面的例子也可以写成下面形式
async function timeout(ms){ await new Promise(function(resolve,reject){ setTimeout(resolve,ms) }) }
async函数有多种使用形式
//函数声明 async function foo(){} //函数表达式 foo = async function(){} //对象的方法 let obj={async foo(){}}; obj.foo().then()
async 函数的语法规则总体上比较简单,难点是错误处理机制
async函数返回一个Promise对象
async函数内部return语句返回的值,会成为then方法回调函数的参数
async function f(){ return 'hello world' } f().then(function(value){ console.log(value) })
上面代码中,函数内部return命令返回的值,会被then方法回调函数接收到。
async函数内部抛出错误,会导致返回的Promise对象变成为reject状态,抛出的错误会被catch方法回调函数接收到
async function f(){ throw new Error('出错了') } f().then( v=>console(v) err=>console(err) ) //出错了
async函数的Promise对象,必须等到内部所有await命令后面的Promise对像执行完,才会发生状态改变,除非遇到return语句或者抛出错误,也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
下面是一个例子
async function getTitle(url){ let response = await fetch(url) let html = await response.text() return html.match(/< title >([\s\S]+)< title>/i)[1] } getTitle('https://tc39.github.io/ecma262/').then(console.log)
上面代码中,函数getTitle内部有三个操作,抓取网页,取出文本,匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log
正常情况下,await命令后面是一个Promise对象,如果不是,会被转成一个立即resolve的Promise对象
async function f(){ return await 123; } f().then(v=>console.log(v))
上面代码中,await命令的参数是数值123,他被转化成Promise对象,并立即resolve。
await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收
只要一个await语句后面的Promise变成reject,那么整个async函数都会中断执行
async function(){ await Promise.reject('出错了') await Promise.resolve('hello world') }
上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。
有时,我们希望即使前面一个异步操作失败,也不要中断后面的一部操作,这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function f(){ try{ await Promise.reject('出错了') }catch(e){ } return await Promise.resolve('hello world'); } f() .then(v=>console.log(v)) //hello world
如果await后面异步操作出错,那么就等于async函数返回的Promise对象被reject。
async function f(){ await new Promise(function(resolve,reject){ throw new Error('出错了'); }) }
如果有多个await命令,可以统一放在try...catch解构中
async function main(){
try{
const val1 = await firstStep()
const val2 = await secondStep()
const val3 = await thirdStep()
console.log('first:',val3)
}
catch(err){
console.log(err)
}
}
下面的例子使用try ... catch解构,现实多次重复尝试
const superagent = require('superagent') const NUM_RETRIES = 3; async function test(){ let i; for(i=0;i < NUM_RETRIES;++1){ try{ await superagent.get('http://google.com/this-thows-an-error'); break; }catch(err){} } console.log(i) } test()
上面代码中,如果await操作成功,就会使用break语句退出循环,如果失败,会被catch语句捕捉,然后进入下一轮循环
第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令方法try...catch代码块中
async function myFunction(){ try { await somethingThatReturnsAPromise(); }catch(err){ console.log(err) } } //另一种写法 async function myFunction(){ await somehtingThatReturnAPoromise() .catch(function(err){ console.log(err) }) }
第二点,多个await命令后面的异步操作,如果不存在激发关系,最好让他们同时触发。
let foo=await getFoo() let bar=await getBar()
上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写生继发关系,这样比较耗时,因为只有getFoo完成以后,才会执行个getBar,完全可以让他们同时触发
//写法一 let [foo,bar]= await Promise.all([getFoo(),getBar()]) //写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
上面两种写法,getFoo和getBar同时触发,这样会缩短程序的执行时间
第三点,await命令只能用在async函数中,如果用在普通函数中,就会报错
async function fn(args){ //... } //等同于 function fn(){ return spawn(function* (){ //... }) }
所有的async函数都可以上面第二种形式,其中spawn函数就是自执行器。
下面给出spawn函数的实现,基本上前文自执行器的翻版
function spawn(genF){ return new Promise(function(resolve,reject){ const gen = genF(); function step(nextF){ let next; try{ } } }) }
用法:entry:string|Array
entry属性的单个入口语法,是下面的简写
const config = { entry:{ main:'./path/file.js' } }
当你向entry传入一个数组时会发生什么?向entry属性传入[文件路径(file path)数组]将创建多个主入口,在你想要多个依赖文件一起注入,并且将他们的依赖导向一个chunk时,传入数组的方式就很有用
用法:entry:{[entryChunkName:string]:string|Array}
webpack.config.js
entry:{ app:'./src/app.js', vendors:'./src/vendors.js' }
对象语法会比较繁琐,然而,这是应用程序中定义入口的最可扩展的方式
可拓展的webpack配置,可重用并且可以与其他配置组合使用,这是一种流行的技术,用于将关注点从环境,构建目标,运行时中分离,然后使用专门的工具将他们合并。
webpack.config.js
entry:{ app:'./src/app.js', vendors:'./src/vendors.js' }
这是什么? 从表面上看,这告诉我们webpack从app.js和vendor.js开始创建依赖图,这些依赖图是彼此完全分离、相互独立的。
为什么?此设置允许你使用CommonChunkPlugin从[应用程序bundle]中提取vendor引用到vendor bundle,并把引用vendor的部分替换为__webpack_require__()调用,如果应用程序bundle中没有vendor代码,那么你可以在webpack中实现被称为’长效缓存‘的通用模式。
const config = { entry:{ pageOne:'./src/pageOne/index.js', pageTwo:'./src/pageTwo/index.js', pageThree:'./src/pageThree/index.js' } }
这是什么? 我们告诉webpack需要3个独立的依赖图
< template> < div class="hello"> {{msg}} < alert @changeVal = 'changeVal' :checkId="wel">< /Alert> //组件alert < /div> < /template> < script> import alert from './alert.vue'; export default { data () { return { msg: 'Welcome to Your Vue.js App', wel:'welcome' } }, components:{alert}, methods:{ changeVal(val){ //方法接收子组件返回的信息 this.msg = val } } } < /script>
< template> < div> < p @click="changeValue">{{checkId}}< /p> < /div> < /template> < script> export default{ props:['checkId'], //接收父组件的信息 methods:{ changeValue(){ this.$emit('changeVal','hello') //把信息$emit给父组件 } } } < /script>
< router-link :to="{path: '/fans/massmessage', query: {shareIndex: this.shareIndex}}" class="send_btn">更多查询< /router-link> this.$router.push({path: '/fans/massmessage', query: {shareIndex: this.shareIndex}});
this.$route.query.shareIndex
{ path: 'ordersdetails/:ordersId',component: ordersDetails,meta: {title: '订单详情'}},//在路由中定义 < router-link v-show="bottomShow" :to="{ path:'/index/ordersdetails/1', query: {invoice:invoice}}" class="send_btn">查看物流< /router-link>
this.$route.params.ordersId
- 使用前可以在全局定义一个eventBus,window.eventBus = new Vue();
- 在需要传递参数的组件中,定义一个emit发送需要传递的值,键名可以自己定义(可以为对象),eventBus.$emit('eventBusName',id)
- 在需要接收参数的组件中,用on接收改值(或对象) eventBus.$on('eventBusName',function(value){value})
- 最后记住要在beforeDetroy()中关闭这个eventBus eventBus.$off('eventBusName')
express框架是nodeJS出来不久就诞生的webserver构建框架,
大家一致认同的前后端分离的例子就是SPA,所有用到的展示数据都是后端通过异步接口方法提供的,前端只管展现。
SPA式前后端分离,是从屋里层做区分(认为只要是客户端就是前端,服务端就是后端),这种分发已经无法满足我们前后端分离的需求,我们认为从职责上划分才能满足我们的场景:
不同的开发模式各有各的适用场景,没有哪一种完全取代另一种
在业务逻辑复杂的系统里,我们最怕维护前后端混杂在一起的代码,因为没有约束,M_V_C每一层都一层都可能出现别的层的代码,日积月累,完全没有维护性可言,
虽然前后端分离没有办法解决这种情况,但是可以大大缓解,因为从物理层上保证你不可能这么做
电商网站web基本上都是基于MVC框架,框架决定了前端只能依赖后端,所以我们的开发模式依然是,前端写好静态Demo,后端翻一成VM模板,这种模式问题就不说了,被吐槽了很久
直接基于后端黄精开发也很痛苦,配置安装使用都很麻烦
性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作才能碰撞出火花,但是由于后端框架的限制,我们很难使用Bigpipe等技术方案来优化性能
如果想实现上图的分层,就必然需要一种web服务帮我们实现前后端的事情,
现在主要的MCV/MVVM模式进行开发,这种模式严重阻碍了前端的开发效率,也让后端不能专注于业务开发
解决方案是让前端能控制Controller层,但如果在现有的技术体系下很难做到,因为不可能让所有的前端学JAVA,安装后端的开发环境,写VM
NodeJS就能很好的解决这个问题,我们无需学习一门新的语言,就能做到以前开发帮我们做的事情,一切都显得那么自然
分层就涉及每层之间的通讯,肯定会有一定的性能损耗,但是合理的分层让职责清晰,也方便协作,会大大提高开发效率,分层带来的损失,一定能在其他方面的收益弥补回来,另外,一旦决定分层,我们可以通过优化通讯方式,通讯协议、尽可能的把消耗降到最低。
举个例子
淘宝宝贝详情页静态化之后,还是有不少需要实时获取的信息,比如说物流、促销等,因为这些信息在不同的业务系统中,所以需要前端发送5、6个异步请求来回填这些内容,有了NodeJS之后,前端可以在NodeJS中去代理这5个异步请求,还很容易做bigpipe,这块的优化让整个渲染效率提升很多,可能在PC端5、6个异步请求也没什么,但是在移动端,在客户手机上建立一个HTTP请求开销很大,有了这个优化,性能一下子提升好几倍
相对于页面制作,工作量肯定会增加了,但是当前模式下有联调、沟通环节,这个过程非常花时间,也容易出bug,还很难维护
所以,虽然工作量会增加一点,但是总体的开发效率会提升很多
随着node大规模使用,系统运维安全部门的同学也会加入到基础建设中,他们会帮助我们去完善各个环节可能出现的问题,保障系统的稳定性
基于node的全栈式开发模式淘宝是一个很好的例子,基于开源,我们大可充分借鉴成功的经验
ps:内容参考来源:https://blog.csdn.net/u011413061/article/details/50294263
物体运动还有一个关键点,就是要渲染物体运动的每一个过程,让他显示给观众,渲染的时候,我们调用的是渲染器的render()函数,代码如下:
renderer.render(scene,camera)
如果我们改变了物体的位置或者颜色之类的属性,就必须重新调用render()函数,才能够将新的场景绘制到浏览器中,不然浏览器是不会自动刷新场景的
如果不断地改变物体的颜色,那么就要不断地绘制新的场景,所以我们最好的方式是让画面执行一个循环,不断地调用render来重绘,这个循环就是渲染循环,在游戏中也叫作游戏循环
为了实现循环,我们需要JavaScript的一个特殊函数,这个函数是requestAnimationFrame
调用requestAnimationFrame函数,传递一个callback参数,则在下一个动画帧时,回调用callback这个函数
于是游戏循环会这样写:
function animate(){ render() requestAnimationFrame(animate) }
这样就会不断地执行animate这个函数,也就是不断地执行render函数,在执行中不断改变物体的位置和颜色
function animate(){ camera.position.x = camera.position.c+1; renderer.render(scene,camera) requestAnimationFrame(animate) }
将相机不断地沿着X轴移动一个单位,物体的移动方向是相反的
球的例子
//简单的绘制一个网状球 var geometry = new THREE.SphereGeometry(300,80,20) var material = new THREE.MeshBasicMaterial({color:0xffff00,wireframe:true}) mesh = new THREE.Mesh(geometry,material) scene.add(mesh) //球体运动 function animation(){ mesh.position.x= mesh.position.x+1; renderer.render(scene,camera) requestAnimationFrame(animation) }
关于性能,测试一个程序,性能上是否有瓶颈,在3D世界里,经常使用帧数的概念,首先我们来定义一个帧数的意义
帧数:图形处理器每秒钟能够刷新几次,通常用fps(Frames per Second)来表示,如下是每秒钟59次刷新的应用
毫无疑问,帧数越高,画面的感觉就会越好,所以大部分的游戏都会有超过30fps,看看你的程序哪里占用了很多的CPU时间,就需要学习一下性能监视器
1、性能监视器Stats
在THREE中,性能由性能监控器来管理,其中FPS表示上一秒帧数,这个数越大越好,一般为60左右,点击FPS进入MS视图界面,MS表示渲染需要的毫秒数,这个数字是越小越好
2、性能监视器Stats的使用
在THREE中,性能监视器被封装在一个类中,这个类叫做Stats,下面是一段伪代码,表示Stats的使用
var stats = new Stats(); stats.setMode(1); // 0 :fps 1:ms stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px' stats.domElement.style.top = '0px' document.body.appendChild(stats.domElement) setInterval(function(){ stats.begin(); start.end() },1000/60)
Stats到底做了什么事情呢?分析一下
1、setMode函数 参数为0的时候,表示显示的是FPS界面,参数为1的时候,表示显示的是MS界面
2、stats的domElement 表示绘制的目的地,波形就会绘制在这上面
3、begin 在你要测试的代码前面调用begin函数,在你代码执行完调用end函数,这样就能够统计出这段代码执行的平衡帧数了
stats的begin和end函数本质上是在统计代码执行的时间和帧数,然后用公式fps = 帧数/时间,能够得到FPS,stats的这个功能,被封装成更好的update,只要在循环中调用就可以了,
var renderer; var stats; function initTHree(){ width=... height=... renderer=new THREE.WebGLRenderer({ antialis:true }) renderer.setSize(width,height) ..appendChild(renderer.domElement) stats = new Stats(); stats.domElement.style.position='absolute' ...appendChild(stats.domElement) } function animation(){ mesh.position.x -= 1; renderer.render(scene,camera) stats.update() }
以上代码一共几个步骤:
stat = new Stats()
stats.domElement.style.position = 'absolute' ...
stats.update()
上面介绍了通过移动相机和移动物体来产生动画的效果,使用的方法是在渲染循环里去移动相机或者物体的位置,如果动画稍微复杂一些,这种方式实现起来就比较麻烦一些了
为了使编程更加容易一点,我们可以使用使用动画引擎来实现动画效果,和THREE紧密相连的动画引擎是Tween
快速构建动画
function(){ new TWEEN.Tween(mesh.position).to({x:-400,300}).repeat(Infinity).start() }
TWEEN.Tween的构造函数接受的是要改变属性的对象,这里传入的是mesh的位置,Tween的任意一个函数返回的都是自身,所以可以用串联的方式调用各个函数。
to函数,接受两个参数,第一个参数是一个集合,里面存放的键值对,键x表示mesh.position的x属性,值-400表示,动画结束的时候需要移动到的位置,第二个参数,是完成动画需要的时间,这里是3000ms,
repeat(infinity)表示重复我穷次,也可以接受一个整数,例如5次
Start表示开始动画,默认情况下是匀速的将mesh.position.x移动到-400的位置
- 3、需要在渲染函数中不断地更新Tween,这样才能够让mes.position.x移动位置
<pre>
function animation(){
renderer.render(scene,camera);
requestAnimationFrame(animation);
stats.update();
TWEEN.update();
}</pre>
其中的TWEEN.update完成了让动画动起来的目标
## 7、使用动画引擎TWEEN。js来创建不规则动画
< script src="https://.../three.js">< /script>
这就表示引入threeJs文件,这个文件会自己初始化threejs的一些变量和环境,
为了方便实验,我们提供了两个简单的框架供你使用,你只要改变其中的一些代码或者参数,就能够得到实验的结果,第一个框架的效果是显示一个绿色的正方体
1、三大组件
在Three中 ,要渲染到网页中,我们需要3个组件:场景(scene)、相机(camera)和渲染器(renderer),有了这三样东西,才能将物体渲染到网页中去。
创建这三要素的代码如下:
var scene = new THREE.Scene() //场景 var camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000) //透视相机 var renderer = new THREE.WebGLRenderer() //渲染器 renderer.setSize(window.innerWidth,window.innerHeight); //渲染器的大小为窗口的内宽度,也就是内容去的宽度 document.body.appendChild(renderer.domElement)
在Three中场景就只有一种,用THREE.Scene来表示,要构建一个场景也很简单,只要new一个对象就可以了。代码
var scene = new THREE.Scene()
场景是所有物体的容器,如果要显示一个苹果,就需要将苹果对象加入场景中
2、相机
另一个组件是相机,相机决定了场景中哪个角度颜色会显示出来,相机就像人的眼睛一样,人站在不同的位置,抬头或者低头都能看到不同的景色,
场景只有一种,但是相机却有很多种,和现实一样,不同的相机确定了成像的各个方面,比如有的相机适合人像,有的相机适合风景,专业的摄影师根据实际用途不一样,选择不同的相机,对程序员来说,只要设置不同的相机参数,就能够让相机产生不一样的效果。
在threeJs中,这里介绍两种,他们是:
透视相机(THREE.PerspectiveCamera)、这里我们使用一个透视相机、透视相机参数很多
var camera = new THREE.PerspectiveCamera(-,-,-,-)
3、渲染器
最后一步就是设置渲染器了,渲染器决定渲染结果应该画在页面的什么元素上,并且以什么样的方式来绘制,这里我们有定义了一个WebRenderer渲染器,代码如下所示:
var renderer = new THREE.WebGLRenderer() renderer.setSize(window.innerWidth,window.innerHeight) document.body.appendChild(renderer.domElement)
注意,渲染器renderer的domElement元素,表示渲染器的画布,所有的渲染都是在domElement上的,所以在这里的appendChild表示将这个domElement挂接在body下面,这样渲染的结果就可以在页面中显示了
4、添加物体到场景中去
那现在,我们将一个物体添加到场景中去
var geometry = new THREE.CubeGeometry(1,1,1); var material = new THREE.MeshBasicMaterial({color:red}) var cube = new THREE.Mesh(geometry,material) scene.add(cube)
代码中出现了THREE.CubeGeometry,这是个什么东西呢,他是一个几何体(正方体或这长方体),他是由三个参数所决定,cubeGeometry的原型如下代码所示
CubeGeometry(width,height,depth,segmentWidth,segmentHeight,segmentDepth,materials,sides)
这三个参数就可以能确定一个立方体了。剩下的几个参数后期了解
终于到了最后一步了,这步做完就完成了,
渲染应该使用渲染器,结合相机和场景来得到结果画像,实现这个功能的函数是renderer.render(scene,camera)
渲染函数的原型如下:render(scene,camera,renderTarget,forceClear)
各个参数的含义是:
6、渲染循环
渲染有两种方式:实时渲染和离线渲染
离线渲染就是实现渲染好一帧一帧的图片,然后然后再把图片拼接成电影的,这就是离线渲染
实时渲染就是需要不停的对画面进行渲染,即使画面中什么也没有改变,也需要重新渲染,下面就是一个渲染循环
function render(){ cube.rotation.x += 0.1; cube.ratation.y += 0.1; renderer.render(scene,camera); requestAnimationFrame(render) }
其中重要的一个函数是requestAnimationFrame,这个函数就是让浏览器去执行一次参数中的函数,这样通过上面render中调用requestAnimationFrame()函数,让render再执行一次,形成了我们通常说的游戏循环了
7、场景,相机,渲染器之间的关系
Three.js的场景是一个物体的容器,开发者可以将需要的角色放入场景中,例如苹果、葡萄。同时,角色自身也管理着其中的位置。
相机的作用就是面对场景,在场景中取一个合适的景。把他拍下来
渲染器的作用就是将相机中拍下来的图片,放到浏览器中去显示
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统行为完全不一样
Generator函数有多种理解角度,首先可以理解成,Generator函数是一个状态机,封装了多个内部状态
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数,返回遍历器对象,可以依次遍历Generator函数的内部每一个状态
形式上Generator是一个普通的函数,但是有两个特征,一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同状态
function* helloWorldIdGenerator(){ yield 'hello'; yield 'world'; return 'ending' } let hw = helloWorldIdGenerator()
上面代码定义一个Generator函数helloWorldGenerator,它内部有两个yield表达式(hello 和 world)即函数有三个状态:hello world return语句
然后,Generator函数的调用方法与普通函数一样,不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,
下一步必须调用遍历器对象的next方法,使得指针移向下一个状态,也就是说,每次调用next方法,内部指针就从头部或者上一次停下来的地方开始执行,直到遇到下一个yield表达式或者return为止,换言之,Generator是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
hw.next() //{value:'hello',done:false} hw.next() //{value:'world',done:false} hw.next() //{value:'end',done:true} hw.next() //{value:undefined,done:true}
上面代码一共调用了四次next方法
function * fun(x,y){} function *fun(x,y){} function* function(){} funtuion*fun(){}
ES6没有规定。function关键字与函数名之间的星号写在那个位置,这导致上面写法都能通过
由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个状态,所以其实提供了一种可以暂停执行的函数,yield表达式就是暂停标志
遍历器对象next方法的运行逻辑如下
需要注意的是:yield表达式后面的表达式,只有当调用next方法,内部指针指向改语句时才会执行
function* gen(){ yield 123+456 }
上面代码中,yield后面的表达式123+456,不会立即求值,只有在next方法将指针移到这一句时,才会求值
yield表达式与return语句既有相似之处,也有区别,相似之处在于,能返回紧跟在语句后面的那个表达式的值,区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能,一个函数里,只能执行一次return语句,但可以执行多次yield表达式,正常函数只能返回一个值,因为只能执行一次return,Generator可以返回一系列的值,因为可以有任意个yield,从另一个角度看,也可以说Generator生成一系列的值
Generator函数可以不用yield表达式,这时变成了一个单纯的暂缓执行的函数
function* f(){ console.log('执行了') } var generator=f(); settimeout(function(){ generator.next() },2000)
上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行,但是,函数f是一个Generator函数,就变成只有调用next方法时,函数f才会执行
另外需要注意,yield表达式只能在generator函数里面,用在其他地方都会报错
var arr=[1,[[2,3],4],[5,6]]; var flat=function* (a){ var length = a.length; for(var i=0;i< length;i++){ var item=a[i]; if(typeof item !='number'){ yield* flat(item) }else{ yield item } } } for(var f of flat(arr)){ console.log(f) } // 123456
另外,yield表达式如果在另一个表达式之中,必须放在圆括号内
yield表达式本身没有返回值,或者说返回值总是返回undefined,next方法可以带一个参数,该参数就会被当做上一个yield返回值
function* f(){ for(var i=0;true;i++){ var reset = yield i; if(reset){ i = -1 } } } var g=f() g.next() //{value:0,done:false} g.next() //{value:1,done:false} g.next(true) //{value:0,done:false}
上面定义一个无限运行的生成器函数f,如果next方法没有参数。每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数,因此i会等于-1,下一轮循环就会从-1开始递增
这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,他的上下文状态是不变的,通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值,也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数的行为
在看一个例子
function* foo(x){ var y=2*(yield (x+1)) var z=yield(y/3) return (x+y+z) } var a=foo(5); a.next() //{value:6,done:false} a.next() //{value:NaN,done:false} a.next() //{value:NaN,done:true} var b=foo(5) b.next() //{value:6,done:false} b.next(12) //{value:8,done:false} b.next(13) //{value:42,done:true}
上面代码中,第二次运行next方法的时候不带参数,导致y的值等于2*undefined (NaN),除3以后还是NaN,因此返回对象的value属性也等于NaN,第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象属性等于5+NaN+undefined,即NaN
如果向next方法提供参数,返回结果就不一样了,上面代码第一次调用b的next方法时,返回x+1=6.第二次调用next方法,将上次yield表达式的值设为12,因此y等于24,返回值8,第三次调用next方法,将上次yield表达式的返回值设为13,z=13,x=5,y=24,所以return的值等于42
注意,由于next方法的参数的表示上一个yield表达式的返回值,所以在第一次使用的next方法时,传递参数是无效的,v8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数是有效的
for ... of 循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法
funvtion* foo(){ yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } var f=foo() for(let v of f){ console.log(v) } // 1 2 3 4 5
上面代码使用for ... of循环,以此显示5个yield表达式的值。这里需要注意,一旦next方法的返回对done为true,for...of循环就会终止,且不返回改返回对象,所以上面代码return语句返回的6,不包括在for ...of之中,
下面是一个利用Generator函数和 for ... of循环,实现斐波那契数列的列子
function* fibonacci(){ let [prev,curr]=[0,1] for(;;){ [prev,curr]=[curr,prev+curr]; yield curr } } for(let n of fibonacci()){ if(n>1000)break; console.log(n) }
从上面代码可见,使用for ... of语句不需要使用next方法
利用for ...of 循环,可以写出遍历任何对象的方法,原生的JavaScript对象没有遍历接口,无法使用for...of循环,通过Generator函数为他加上这个接口就可以用了,
function* objectEntries(obj){ let propKeys=Reflect.ownKeys(obj); for(let propKey of propKeys){ yield [proKey,obj[propKey]]; } } let jane = {first:'Jane',last:'Doe'}; for(let [key,value] of objectEntries(jane)){ console.log(`${key}:${value}`) }
上面代码中,对象Jane原生不具备Iterator接口,无法用for...of遍历,这是,我们通过Generator函数objectEntries为他加上遍历器接口,就可以用for...of 遍历了,加上不遍历器接口的另一种写法是,将Generator函数加到对象的Symbol.iterator属性上面
除了for ...of 循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的都是遍历器接口,
function* number(){ yield 1 yield 2 return 4 yield 3 } //扩展运算符 [...number()] // [1, 2] //Array.from Array.from(number()) //[1,2] //解构赋值 let [x,y]=number() //x:1 y:2 //for of for(let v of number()){ console.log(v) // 1 2 }
Generator函数返回可遍历的对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获
var g=function* (){ try{ yield; }catch(e){ console.log('内部捕获',e) } }
return方法可以返回给定的值,并且终结遍历Generator函数
function* gen(){ yield 1; yield 2; yield 3; } var g=gen(); g.next() {value:1,done:false} g.return('foo') {value:foo,done:true} g.next() {value:undefined,done:true}
网友提出next throw return本质上是同一件事,可以放在一起理解,他们的作用都是让Generator函数恢复执行,并且是不同的语句替换yield表达式
next是将yield表达式替换成一个值
const g=function* (x,y){ let result = yield x+y; return result; } const gen=g(1,2) gen.next() //{value:3,done:false} gen.next(1) //{value:1,done:false} 相当于将 let result=yield x+y换成 let result = 1
上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1,如果next方法没有参数,就相当于替换成undefined
throw是将yield表达式替换成一个throw语句
gen.throw(new Error('出错了')) //Uncaught:出错了 //相当于 let result = yield x+y 表达式替换成 let result = throw(new Error('出错了'))
return是将yield表达式替换成一个return语句
gen.return(2); {value:2.done:true} 替换成 let result = return 2
如果在Generator函数内部,调用一个Generator函数,默认情况下是没有效果的,
function* foo(){ yield 'a'; yiled 'b'; } function* bar(){ yield 'x'; foo(); yield 'y'; } for(let v of bar){ console.log(v) } // x // y
上面代码中,foo和bar函数都是Generator函数,在bar里面调用foo是没有效果的
这个就需要用到yield表达式,用来在一个Generator函数里面执行一个Generator函数
function* bar(){ yield 'x'; yield* foo(); yield 'y' }
yield命令可以方便的取出嵌套数组的所有成员
function iterTree(tree){ if(Array.isArray(tree)){ for(let i=0;i< tree.length;i++){ yield* iterTree(tree[i]) } }else{ yield tree } } for(let x of iterTree()){ console.log(x) }
使用yield遍历二叉树
function Tree(left,label,right){ this.left=left; this.label=label; this.right=right; } function* inorder(t){ if(t){ yield* inorder(t.left) yield t.label yield* inorder(t.right) } } function make(array){ if(array.length==1) return new Tree(null,array[0],null) return new Tree(make(array[0]),array[1],make(array[2])) } let tree = make([[[a],b,[c]],d,[[e],f,[g]]]) var result = []; for(let node of inorder(tree)){ result.push(node) }
如果一个对象的属性是Generator函数,可以简写成下面形式
let obj={ * myGeneratorMethod(){ ... } } 等价于 let obj={ myGeneratorMethod:function* (){ } }
Generator可以暂停函数执行,返回任意表达式的值,这种特点使得Generator有多重场景,
function* loadUI(){ showLoaddingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen() } var loader=loadUI(); //加载UI loader.next() //卸载UI loader.next()
定时器例子
function* foo(){ foo1() yield foo2() foo3() } function foo1(){ console.log('foo1') } function foo2(){ settimeout(function(){ console.log('定时器') f.next() },5000) } function foo3(){ console.log('foo3') }
自己运行看结果
step1(function(value1){ step2(value1,function(value2){ step3(value2,function(value3){ step4(value3,function(value4){ Do something with value4 }) }) }) })
采用Promise改写上面代码
Promise.resolve(step1) .then(step2) .then(step3) .then(function(value4){ console.log(value) },function(err){ console.log(err) }).done()
上面代码已经把回调函数改成了直线执行的形式,但是加入大量的Promise的语法,Generator可以进一步的改善运行流程
function* longRuningTask(value1){ try{ var value2 = yield step1(value1) var value3 = yield step2(value2) var value4 = yield step3(value3) var value5 = yield step4(value4) }catch(e){ //... } }
然后使用一个函数,按照次序自动执行所有步骤。
scheduler(longRuningTask(initialVal)) function scheduler(task){ var taskObj=task.next(task.value) if(!taskObj.done){ task.value = taskObj.value scheduler(task) } }
Babel是一个广泛使用的转码器,可以将ES6 转化成ES5代码,从而在现在环境执行
这意味着,你可以现在就用ES6编写程序,而不用担心现有环境是否支持,下面是一个例子
//转码前 input.map((item)=>item+1) //转码后 input.map(function(item){ return item+1 })
上面的原始代码用了箭头函数,这个特性还没有得到广泛支持,Babel将其转化成普通函数,就能在现有的JavaScript环境执行了
Babel 的配置文件是.babelrc,存放在项目的根目录下,使用Babel的第一步,就是配置这个文件。
该文件用来设置转码规则和插件,基本格式如下。
{ "presets":[], "plugins":[] }
presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装
//ES5 转码规则 npm install --save-dev babel-preset-es2015 //react转码规则 npm install --save-dev babel-preset-react //ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个 npm install --save-dev babel-preset-stage-0 npm install --save-dev babel-preset-stage-1 npm install --save-dev babel-preset-stage-2 npm install --save-dev babel-preset-stage-3
然后,将这些规则加入 .babelrc
{ "presets":[ "ES2015", "ES2016", "stage-2" ], "plugins":[] }
注意,以下所有babel工具和模块的使用,都必须先写好 .babelrc
Babel提供babel-cli工具,用于命令行转码
他的安装命令如下
npm install --global babel-cli
基本用法如下
//转码结果输出到标准输出 babel example.js //转码结果写入一个文件 --out-file 或 -o 参数指定输出文件 babel example.js --out-file compiled.js 或者 babel example.js -o complied.js 整个目录转码 --out-dir 或 -d 参数指定输出目录 babel src --out-dir lib 或者 babel src -d lib -s 参数生成source map文件 babel src -d lib -s
上面代码是在全局环境下,进行Babel转码,这意味着,如果项目要运行,全局环境必须装有babel,也就是说项目生产了对环境的依赖,另一方面,这样做也无法支持不同项目使用不同版本Babel
一个解决办法是将babel-cli安装在项目之中
//安装 npm install --save-dev babel-cli
然后改写package.json文件
{ "devDependencies":{ "babel-cli":"^6.0.0" }, "scripts":{ "build":"babel src -d lib" } }
转码的时候,执行下面的命令
npm run build
babel-cli工具自带一个babel-node命令,提供一个支持ES6的REPL环境,它支持Node的REPL环境的所有功能,而且可以直接运行ES6代码,
他不用单独安装,而是随babel-cli一起安装。然后,执行babel-node就进入REPL环境
babel-node (x=>x*2)(1) 2
babel-node 命令可以直接运行ES6脚本,将上面的代码放入脚本文件es6.js,然后直接运行
babel-node es6.js 2
babel-node也可以安装在项目中,
npm install --save-dev babel-cli
然后,改写package.json
{ "scripts":{ "script-name":"babel-name-script.js" } }
babel-register模块改写require命令,为他加上一个钩子,此后,每当使用require加载.js .jsx .es和 .es6后缀名的文件,就会先用Babel进行转码
{ npm install --save-dev babel-register }
使用时,必须首先加载babel-register
{ require('babel-register') require('./index') }
然后,就不需要手动对index.js转码了,
需要注意的是,babel-register只会对require命令加载的文件转码,而不会对当前文件转码,另外 ,由于它是实时转码,所以只适合在快发环境使用
如果某些代码需要调用babel的API进行转码,就需要使用babel-core模块
安装命令如下:
npm install babel-core --save
然后,项目中就可以调用babel-core
var babel=require('babel-core') //字符串转码 babel.transform('code();',options) //文件转码(异步) babel.transformFile('filename.js',options,function(err,result){ rersult }) babel.transformFileSync('filename.js',options) //Babel Ast转码 babel.transformFromAst(ast,code,options)
配置对象options,可以参看官方文档
下面是一个例子
var es6Code='let x = n => n+1' var es5Code=require('babel-core').transform(es6Code,{ presets:['es2015'] }).code;
上面代码中,transform方法的第一个参数是一个字符串,表示要转换的ES6代码,第二个参数是转化的配置对象
Babel默认只转换新的JavaScript句法,而不是转化新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及定义在全局变量的方法(Obbject.assign)都不会转码
举个例子来说。ES6 Array对象上新增的Array.from方法,babel就不会转码这个方法,如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片
按装命令如下:
npm install --save babel-polyfill
然后在脚本头部,加入如下一行代码。
import 'babel-polyfill' 或者 require('babel-polyfill')
Babel默认不转码的API非常多。详细清单可以查看babel-plugin-transfrom-runtime模块的definition.js文件
Babel也可以用于浏览器环境,但是,从Babel 6.0开始,不在直接提供;浏览器版本,而是用构建工具构建出来,如果你没有或者不想使用构建工具,可以通过安装5.x版本的babel-core模块来获取
npm install babel-core@old
运行上面命令以后,就可以在当前目录的node_modules/babel-core/子目录里面,找到babel的浏览器版本browsers.js 和 browser.min.js 已精简
然后,将下面代码插入网页
< script src="node_modules/babel-core/browser.js">< script> < script type = 'text/babel'> //your ES6 code < script>
上面代码中,browser.js是Babel提供的转化器脚本,可以在浏览器运行,用户的ES6脚本放在script标签之中,但是要注明 type 'text/babel'
另一种方法是使用babel-standalone模块提供浏览器版本,将其插入页面
注意,网页中实时将ES6转码为ES5,对性能会有影响,生产环境需要加载转码完成的脚本
下面是如何将代码打包成浏览器可以使用的脚本,以Babel配合Browserify为例子,首先安装babelify模块
npm install --save-dev babelify babel-preset-es205
然后,再用命令行转换ES6脚本
browserify script.js -o bundle.js \ -t [babelify --presets [es2015 react]]
上面代码中将ES6脚本script.js转换为 bundle.js 浏览器直接加载后者就可以了
在package.json设置下面的代码,就不用每次命令行都输入参数了
{ "browserify":{ "transform":[["babelify",{"presets":[es2015]}]] } }
宇宙中的物体有的是发光的,有的是不发光的,我们把发光的物体叫做光源,太阳、点灯都是光源,在THREE的世界里,有了光,就不会黑暗
作为3D技术的发展趋势,浏览器端3D技术越来越被一些技术公司重视,由此,THREEjs非常重视3D渲染效果的真实性,对渲染真实性来说,使用光源的技巧
在ThreeJs中,光源用Light表示,他是所有光源的基类,他的构造函数是
HREE.Light(hex)
他有一个参数hex,接受一个16进制的颜色值,例如要定义一个红色的光源,我们可以这样来定义,
var redLight = new THREE.Light(XXX)
THREE.Light只是其他所有光源的基类,要让光源除了具有颜色的特性之外,我们需要其他的光源,
THREE.Light=》环境光(AmbientLight)、区域光(AreaLight)、DirectionLight(方向光)、聚光灯(Spotlight)、点光源(PointLight)
### 3、环境光
环境光是经过多次反射而来的光源称之为环境光,无法确定其最初的方向,环境光是一种无处不在的光。环境光源放出的光线被认为来自于任何地方,因此,当你的场景指定环境光时,所有的物体无论法向量如何,都将表示为同样的明暗程度(这是因为反射光可以从各个方向进入你的眼睛)
环境光用THREE.AmbientLight来表示,他的构造函数如下所示,
<pre>
THREE.AmbientLight(hex)</pre>
他仍然接受一个16进制的颜色值,作为光源的颜色,环境光将照射场景中的所有问题,让物体显示出某种颜色,环境光的使用例子如下所示
<pre>
var light = new THREE.AmbientLight(xxx)
scene.add(light)</pre>
只需要将光源加入场景,场景就能通过光源渲染出来的效果
点光源:有这种光源放出的光线来自同一点,且方向辐射自四面八方,如蜡烛放出来的光,萤火虫的光,
点光源用Point来表示,他的构造函数如下所示:
<pre>
PointLight(color,intensily,distance)</pre>
这个类的参数稍微复杂一点,我们花时间解释一下
- color:光的颜色
- Intensity:光的强度,默认是1.0,就是说100%强度的灯光
- distance :光的距离,从光源所在的位置,经过distance这段距离之后,光的强度将从Intensity衰减到0,默认情况下,这个值为0,表示光源强度不衰减
聚光灯:这种光源的光线从椎体中射出,在被照的物体上产生聚光效果,使用这种光源需要指定的光的射出方向以及锥体的顶角,
聚光灯的构造函数是:
<pre>
THREE.SpotLight(hex,intensity,distance,angle,exponent)</pre>
函数的参数如下所示:
- Hex:聚光灯发出的颜色,如XXX;
- Intensity:光源的强度,默认1.0,如果强度是0.5,则强度是一半,颜色会淡一些,和上面的光源一样,
- Distance:光线的强度,从最大值到0,需要的距离。默认为0,表示不衰减
- Angle:聚光灯着色的角度,用弧度作为单位,这个角度和光源的方向形成的角度
- exponent:光源模型中,衰减的一个参数,越大衰减越快
材质与光源有什么关系,这是个容易混淆的问题,在没有深入讲解前,我们只能说他们是互相联系,相互依托的关系
1、材质的真相
材质是什么,材质是物体的质地,我们可以用撤分文字的方法来解释,材质就是材料和质感的完美结合
如果不理解可以引用这段话:
在渲染程序中,他是表面各可视属性的结合,这些可视属性是表面的色彩、纹理、光滑度、透明度、反射率、折射率、发光率等,正是有了这些属性,才能让我们识别三维中的模型是什么做的,正是有了这些属性,我们计算机三维的虚拟世界才会和真实世界一样缤纷多彩
我们首先在屏幕上画一个物体,不带任何光源,定义物体的颜色为黑色,其值为XXX,定义材质如下
<pre>
var material = new THREE.MeshLambertMaterial({color:XXX}) //这是兰伯特,材质中的一种,先看看最终的运行截图,如下所示</pre>
当没有任何光源的时候,最终的颜色将是黑色,无论材质是什么颜色。
最常见的材质之一是Lambert材质,这是在灰色的或不光滑的表面产生均匀散射而形成的材质类型,比如,一张纸就是Lambert表面,首先他粗糙不均匀,不会产生镜面效果,这就是Lambert材质
Lambert材质表面会在所有方向上均匀地散射灯光,这就会使眼色看上去比较均匀,想想一张纸,无论什么颜色,是不是纸的各个部分的颜色都比较均匀呢
Lambert材质会受环境光的影响,呈现环境光的颜色,与材质颜色关系不大
分析一下代码
我们设置一个红色的环境光,并把它放在一个位置上 只用淡红色的Lambert材质
最后整个效果中,长方形呈现的是红色,我们要说的是,长方体显示红色,是因为长方形反射了红色的光,长方形本身的颜色是红色,光源是淡红色,红色的光照在物体上,物体反射了红色的光,所以呈现红色
我们一直在使用环境光,从环境光的构造函数来看,只有颜色,其位置对场景中的物体并没有影响,因为他是均匀的反射到物体表面的,
### 9、环境光对物体的影响
环境光在场景中无处不在的光,他对物体的影响是均匀的,也就是无论你在物体的哪个角度观察,物体的颜色都是一样的,这就是伟大的环境光,可以把环境光放在任何一个位置,他的光线是不会衰减的,是一个永恒的光源
异步编程对JavaScript语言太重要,JavaScript语言执行环境是‘单线程’的,如果没有异步编程,根本没法用,非卡死不可,本章主要介绍Generator函数如何完成异步操作
ES6诞生之前,异步编程的方法大致有以下四种,
所谓异步,简单说就是一个任务不是连续完成的,可以理解成任务被人分成两个阶段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
比如:有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件,然后,程序执行其他任务,等操作系统返回文件,在接着执行任务的第二段,这种不连续的执行,叫做异步。
相应的连续的执行叫做同步,由于是连续执行不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序执行干等着。
JavaScript语言对异步编程的实现,就是回调函数,所谓回调函数,就是把任务的第二段单独写在一个函数里面,等着重新执行这个任务的时候,就直接调用这个函数,回调函数的英文名字callback,(重新调用)
读取文件进行处理,是这样的
fs.readFile('etc/passwd','utf-8',function(){ if(err) throw err console.log(data) })
上面代码中第三个参数,就是回调函数,也是任务的第二段,等操作系统返回了/etc/passwd/这个文件之后,回调函数才会执行。
一个有趣的问题是,为什么Node约定,回调函数的第一个参数,必须是错误对象err
原因是执行分成两端,第一段完成之后,任务所在的上下文环境就已经结束了,在这以后抛出的错误,原来的上下文环境已经捕捉,只能当做参数,传入第二段
回调函数本身并没有问题,他的问题出现在多个回调函数嵌套,假定读取A文件之后,在读取B文件,代码如下
fs.readFile('fileA','utf-8',function(err,data){ fs.readFile('fileB','utf-8',function(){ //... }) })
不难想象,如果以此读取两个以上的文件,就会出现多重嵌套,代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理,因为多个异步形成了强耦合,只要有一个操作需要修改,他的上层回调函数和下层回调函数,可能都要跟着修改。这种情况成为‘回调函数地狱’。
Promise对象就是为了解决这个问题而提出来的,他不是新的语法功能,而是一种新的写法,允许将回调函数嵌套,改成链式调用,采用Promise,连续读取多个文件,写法如下
var readFile = require('fs-readFile-promise'); readFile(fileA).then(function(data1){ console.log(data1) }).then(function(){ return readFile(fileB) }).then(function(data2){ console.log(data2) }).catch(function(err){ console.log(err) })
上面代码中,使用了fs-readfile-promise模块,他的作用是返回一个Promise版本的readfile函数,Promise提供then方法加载回调函数,catch捕捉执行过程中抛出的错误
可以看出,Promise的写法只是回调函数的改进,使用then方法以后,异步任务的两段看的更清楚了,除此之外,并无新意
Promise的最大问题是代码的冗余,原来的任务被Promise包装了一下,不管什么操作,一眼看去都是一堆then,原来的语句变的不清楚。
传统的编程语言,早有异步编程的解决方案,其中有一个叫做‘协程’,意思是多个线程相互协作,完成异步任务。
协程有点像函数,又有点像线程,他的运行流程大致如下
上面流程的协程A,就是异步任务,因为它分为两段(或多段)执行。
举个例子来说,读取文件的协程写法如下
function* asyncJob(){ //...其他代码 var f= yield readFile(fileA); //... 其他代码 }
上面代码的函数asyncJob是一个协程,他的奥妙就是其中的yield命令,他表示执行到此处,执行权就交给其他协程,也就是说,yield是异步两个阶段的分割线
协程遇到yield命令就暂停,等到执行权返回 ,再从暂停的地方往后执行,他的最大优点,就是代码的写法非常向同步操作,如果去除yield命令,就一模一样
Generator函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行)
整个Generator函数就是一个封装的异步任务,或者说异步任务的容器,异步操作需要暂停的地方,都用yield语句注明,Generator函数执行方法如下
function* gen(x){ var y=yield x+2; return y } var g=gen(1) g.next() //{value:3,done:false} g.next() //{value:undefined,done:true}
上面代码中,调用Generator函数,会返回一个内部指针(即遍历器)g,这是Generator函数不同于普通函数的另一个地方,即执行他不会返回结果,返回的是只针对象,调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,上例是执行到x+2为止,
换言之,next方法的作用是分阶段执行Generator函数,每次调用next方法,会返回一个对象,表示当前阶段的信息(value和done),value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段
Generator函数可以暂停执行和恢复执行,这是他能封装异步任务的根本原因,除此之外,他还有两个特性,使他可以作为异步编程的完整解决方案:函数体内的数据交换和错误机制处理
next返回值的value属性,是Generator函数向外输出数据,next方法还可以接受参数,向Generator函数体内输入数据。
function* gen(x){ var y=yield x+2; return y; } var g=gen(1) g.next() // {value:3,done:false} g.next(2) //{value:2,done:true}
上面代码中,第一next方法的属性value。返回表达式x+2的值3,第二个next方法带有参数2,这个参数可以传入Generator函数,作为上一个阶段异步任务的返回结果,被函数体内的变量y接受,因此,这一步的value属性,返回值就是2
Generator函数内部还可以部署错误处理代码,获取函数体外抛出的错误
function* gen(x){ try{ var y=yield x+2; }catch(e){ console.log(e) } return y } var g=gen(1); g.next() //{value:3,done:false} g.throw('出错了') //出错了
上面代码的最后一行,Generator函数体外,使用指针对象的throw方法抛出的错误,可以被函数体内的try...catch代码块捕获,这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无意是很重要的
下面来看看如何使用Generator函数,执行一个真实的异步任务
var fetch = requre('node-fetch') function* gen(){ var url = 'https://api.github.com/users/github' var result = yield fetch(url); console.log(result.bio) }
上面代码中,Generator函数封装一个异步操作,该操作先读取了一个远程接口,然后从JSON格式的数据解析信息,就像前面说过的,这段代码非常相同步操作,除了加上yield命令
执行这段代码的方法如下:
var g = gen(); var result = g.next() result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data) })
上面代码中,首先执行Generator函数,获取遍历器对象,然后使用next方法(第二行),执行任务的第一阶段,由于fetch模块返回一个Promise对象,因此要用then方法调用下一个next()方法
可以看到,虽然Generator函数将异步操作表示的很简洁,但是流程管理却不方便(核实执行第一阶段、第二阶段)
Thunk函数是自执行函数Generator函数的一种
Thunk函数早在上个世纪60年代就诞生了,
那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好,一个争论的焦点是‘求值策略’,即函数的参数到底该何时求值
编译器的传名调用实现,往往是将参数放到一个临时的函数当中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数
function f(m){ return m*2 } f(x+5) //等同于 var Thunk=function(){ return x+5; } function(Thunk){ return Thunk*2 }
上面代码中,函数f的参数x+5被一个函数替换了,凡是用到原参数的地方,对Thunk函数求值即可
这句是Thunk函数,他是‘传名调用’的一种实现策略,用来替换某一个表达式
JavaScript语言就是传值调用,他的Thunk函数含义有所不同,在JavaScript语言中,Thunk函数替换的不是表达式,而是多参函数,将其替换成一个只接受回调函数作为参数的单参数函数
fs.readFile(fileName,callback); var Thunk = function(fileName){ return function(callback){ return fs.readFile(fileName,callback) } } var readFileThunk = Thunk(fileName) readFileThunk(callback)
上面代码中,fs模块的readFile方法一个多参数函数,两个参数分别为文件名和回调函数,经过转化器处理,他变成了一个单参数函数,只接受回调函数作为参数
任何函数,只要参数有回调函数,就写成Thunk函数形式,下面是一个简单的Thunk函数转化器
//ES5 var Thunk = function(fn){ return function(){ var args = Array.prototype.slice.call(arguments); return function(callback){ args.push(callback); return fn.apply(this,args) } } } //ES6 const Thunk = function(fn){ return function(...args){ return function(callback){ return fn.call(this,...args,callback) } } }
使用上面的转化器,生成fs.readFile的Thunk函数
var readFileThunk=Thunk(fs.readFile) readFileThunk(fileA)(callback);
下面是一个完整的例子
function f(a,callback){ cb(a) } const ft = Thunk(f) ft(1)(console.log)
生产环境转化器,建议使用Thunkify模块
首先是安装
npm install thunkify
使用方式如下
var thunkify = require('thunkify'); var fs = require('fs') var read = thunkify(fs.readFile) read('package.json')(function(err,data){ console.log(data.) })
本质上,webpack是一个现在jS应用的静态模块打包器,当webpack处理应用程序时,他会递归构建一个依赖关系图(dependency),其中包含应用程序需要的每个模块,然后将所有的这些模块打包成一个或者多个bundle。
他是高度可配置的,但是,在开始前你需要先理解四核心概念:
入口七点(entry point)指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始,进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接或间接)依赖的
每个依赖项随即被处理,最后输出到称之为bundles的文件中,
可以通过webpack配置中配置entry属性,来指定一个入口起点(或者多个入口起点)
entry例子
webpack.config.js
module.exports = { entry:'./path/file.js' }
output属性告诉我们webpack在哪里输出它所创建的bundles,以及如何命名这些文件,你可以通过在配置中指定一个output字段,来配置这些处理过程
webpack.config.js
const path = require('path); module.exports = { entry:'./path/file.js', output:{ path:path.resolve(__dirname,dist), filename:'bundles.js' } }
loader让webpack能去处理那些非JS文件(webpack自身只能理解)。loader可以将所有的类型的文件转化成webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对他们进行处理。
注意:loader能够import导入任何类型的模块(例如 .css文件),这是webpack特有的功能,其他打包程序或任务执行器的可能并不支持,我们认为这种语言扩展是很有必要的,因为这可以是开发人员创建出更准确的依赖关系图
在更高的层面,webpack的配置中loader有两个目标:
webpack.config.js
const path = require('path); module.exports = { entry:'./path/file.js', output:{ path:path.resolve(__dirname,dist), filename:'bundles.js' }, module:{ rules:[ {test:/\/txt$/,use:'raw-loader'} ] } } module.exports = config
laoder被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到冲新定义环境中的变量,插件接口功能及其强大,可以用来处理各种各样的任务
想要使用一个插件,只需要require就可以,然后把它添加到plugins数组中。多数插件可以通过选项自定义,你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用new 操作符来创建他的一个实例
const path = require('path); module.exports = { entry:'./path/file.js', output:{ path:path.resolve(__dirname,dist), filename:'bundles.js' }, module:{ rules:[ {test:/\/txt$/,use:'raw-loader'} ] }, plugins:[ new webpack.optimize.UglifyJsPlugin(), new HtmlWebpackPlugin({template:'./src/index.html'}) ] } module.exports = config
更改Vuex的store中状态的唯一方法是提交mutation.Vuex中的mutation非常类似于事件,每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。每个回调函数就是我们实际进行更改的地方,并且他会接受state最为一个参数:
const store = new Vuex({ state:{ count:1 } mutation:{ increment(state){ state.count++ } } })
你不能直接调用一个mutation handler,这个选项更像是事件注册:当你触发一个类型为increment的mutation时,调用此函数。要唤醒一个mutation handler,你需要以相应的type 调用 store.commit方法
store.commit('increment')
你可以向store.commit传入额外的参数,即mutation的载荷(payload)
mutations:{ increment(state,n){ state.count+=n } } store.commit('increment',10)
在大多数情况下。载荷应该是一个对象,这样可以包含多个字段并且记录的,mutation会更容易读:
increment(state,payload){ state.count+=payload.amoumt } store.commit('increment',{ amount:10 })
提交mutation的另一张方式是直接使用包含type属性的对象:
store.commit({ type:'increment', amount:10 })
当使用对象风格的提交方式,整个对象都作为载荷传给mutation函数,因此handler保持不变:
mutation:{ increment(state,payload){ store.state+=payload.count } }
既然Vuex的store中的状态是响应式的,那么当我们更改状态时,监视状态的vue组件也会自动更新,这也就意味着Vuex中的mutation也需要与使用Vue一样遵守一些注意事项
最好提前在你的store中初始化好所有的属性
当需要在对象上添加新的属性时,你应该
state.obj = {...state.obj ,newProp:123}## 使用常量替代Mutation事件类型 使用常量代替事件类型在各种Flux实现中很常见的模型,这样可以使linter之类的工具发挥作用,//mutation-type.js export const SOME_MUTATION = 'SOME_MUTATION' //store.js import Vuex from 'Vuex' import {SOME_MUTATION} from './mutation-type' const store = new Vuex({ state:{ count:1 }, mutations:{ // 我们可以使用ES2015风格的计算属性命名功能来使用一个常量为函数名 [SOME_MUTATION](state){ } } })用不用常量取决于你--在需要多人协作的大型项目中,这会很有帮助,如果你不喜欢,你可以不这样做
Mutation必须是同步函数
一条重要的原则就是mutation 必须是同步函数
在组件中提交mutation
你可以在组件中使用this.$store.commit('xx')提交mutation,或者使用mapMutation辅助函数将组件中的method映射为store.commit调用
important {mapMutation} from 'Vuex' export default({ data(){ return { } }, methods:{ ...mapMutation([ 'increment', //this.$store.commit('increment') 'incrementBy' //this.$store.commit('incrementBy') ]) } })
(参考来源:https://www.cnblogs.com/daijinxue/p/6640153.html)
这是一道经典的面试题,这道面试题不光前端面试会问到,后端面试也会问道,这道题没有一个标准的答案,他涉及的很多知识点,面试官会通过这道题了解你对哪一方面的只是比较擅长,然后继续追问看看你的掌握程度,从前端的角度出发,必须回答的几个基本点,然后根据你的理解深入回答
浏览器的地址栏输入url并按下回车
浏览器查找当前的url是否存在缓存,并比较缓是否过期
DNS解析URL对应的IP
根据IP建立起TCP链接(三次握手 )
HTTP发起请求
服务器处理请求,浏览器接收HTTP响应
渲染页面,构建DOM树
关闭TCP链接(四次挥手)
我们常见的url是这样的 http://ww.baidu.com,这个域名有三部分组成:协议名、域名、端口号,这里的端口号默认80,除此之外,还会有
讲完URL,我们说一说浏览器的缓存,HTTP缓存有多重规则,根据是否需要向服务器发起请求来分类,分为强制缓存和协商缓存
我们知道在地址栏输入的域名并不是最后资源所在的真实位置,域名只是与他的IP地址的一个映射,网络服务器的IP那么多,我们不可能一记串串的数字,因此域名就产生了,域名解析的过程就是将域名还原为IP地址的过程
首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。
如果没有找到则会查找本地DNS解析器缓存,如果查到则返回
如果还是没有找到则会查找本地DNS服务器,如果查到则返回
最后迭代查询,按根域服务器->顶级域.cn->第二层域hb.cn->子域 www.hb.cn的顺序找到IP地址
在通过第一步DNS域名解析后,获取到了服务器的IP地址,在获取到IP地址后,边会开始建立一次链接,这是由TCP协议完成的,主要通过三次握手进行链接。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn = k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
客户端收到服务器端的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务端进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完整的HTTP请求包含请求起始行,请求头、请求主题三部分
服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完结果以HTTP的的Response对象返回,主要包括状态码,响应吗、响应头、响应报文三个部分
如果说响应的内容是HTML文档的话,就需要浏览器进行解析渲染呈现给用户,整个过程涉及两个方面,解析和渲染,在渲染页面之前,需要构建DOM和CSSOM树
在浏览器还没收到完整的HTML文件时,他开始渲染页面了,在在遇到外部链接的脚本标签或样式标签或图片时,会发送发送HTTP请求重复上述的步骤,在收到CSS文件后会对已经渲染的页面重新渲染,加入他们应有样式,图片加载完成后立刻显示在想用的位置,在这一过程中可能会触发页面的重绘和重排。这里就涉及了,两个重要的概念:reflow和repaint
laoder用于对模块的源代码进行管理,loader可以使你在import或者加载模块时预处理文件。因此,loader类似与其他构建工具中’任务(task)‘,并提供了处理前段构建步骤的强大方法,loader可以是文件从不同语言(typeScript)转化为JS,或者将内联图像转化为data URL,loader甚至允许你直接在JS模块中 import css
例如,你可以使用loader告诉加载css文件,或者将TypeScipt转化为JS,为此,首先安装相应的loader:
npm install --save-dev css-loader npm install --save-dev ts-loader
然后指示webpack对每个.css使用 css-loader,以及对所有的.ts文件使用ts-loader
webpack.config.js
module.exports = { modules:{ rules:[ {test:/\.css$/,use:'css-loader'}, {test:/\'.ts$/,use:'ts-loader'} ] } }
在你的应用程序中,有三个使用loader的方式:
module.rules允许你在webpack配置中指定多个loader,这是展示laoder的一种简明方式,并且有助于使代码变的简洁,同时让你对各个laoder有个全局的概念
module:{ rules:[ { test:/\.css$/, use:[ {loader:'style-loader'}, { loader:'css-laoder', options:{ modules:true } } ] } ] }
整理中。。。
本教程关注的是Bash,也就是Boune Again Shell,由于易用和免费,Bash在生活中被广泛使用,同时,Bash也是大多数Linux系统默认的Shell
#!/bin/bash echo "Hello World!"
#!是一个约定的标记,他告诉系统这个脚本需要什么样的解释器来执行,即使用哪一种
chmod +x ./test.sh #使脚本具有执行权限 ./test.sh #执行脚本
注意 ./test.sh是全局的还是当前路径下的
/bin/sh test.sh /bin/php test.php
这种方式运行的脚本,不需要在第一行的指定解释器信息,写了也没用
格式
your_name="runcob.com"
除了显式的直接复制,还可以用语句给变量赋值:如
for file in `ls/etc`
以上语句将/etc下的目录文件名循环出来
使用一个定义过的变量,只要在变量名前面加美元符号即可:如
your_name="qinjx" echo $your_name echo ${your_name}
变量名外面的花括号是可选的,加不加都可以,加花括号是为了帮助解释器识别变量的边界,如
for skill in Ada Coffe Action Java;do echo "I am good at ${skill}Script" done
已定义的变量,可以被重新定义
使用readonly命令可以将变量定义为只读,只读变量的值不能被改变
下面的例子尝试更改只读变量,结果报错:
#!/bin/bash myurl="http://www.baidu.com" readonly myUrl myUrl="http://www.runoob.com"
运行脚本报错
使用unset命令可以删除变量。语法:
unset variable_name
变量被删除后不能再次使用,unset命令不能删除只读变量,如
#!/bin/sh myUrl="http://runobb.com" unset myUrl echo $myUrl
##变量类型
运行shell时,会同时存在三种变量:
字符串是Shell编程中最常用的数据类型(除了数字和字符串,也没啥其他的类型了),字符串可以用单引号,也可以用双引号,也可以不用引号
str='this is a string'
单引号字符串的限制:
your_name='lihuiyang' str="Hello,I know your are \"$your_name\"!\n "
双引号的优点
your_name="lihuiyang" greeting="hello,"$your_name"!" greet_1="hello,${your_name}" echo $greeting $greeting_1
string="abcd" echo ${#string}
以下实例从字符串第2个 字符开始截取4个字符
string="runoob is a great site" echo ${string:1:4} #输出unoo
查找字符“i 或 s”的位置
string="runoob is a great company" echo `expr index "$string" is` #输出8
注意:以上脚本“``”是反引号,而不是单引号,
bash支持一维数组(不支持多维数组),并且没有限定数组大小
类似与C语言,数组的下标由0开始编号,获取数组中的元素要利用下标,下标可以是整数或者算数表达式,其值大于或者等于0
在Shell中,用括号来表达数组,数组元素用“空格”符号分开,定义数组的一般形式为:
数组名=(值1 值2 值3 ... 值n)
array_name=(value0 value1 value2 value3) array_name=( value0 value1 value2 value3 ) 还可以 array_name[0]=value0 array_name[1]=value1 array_name[n]=valuen
读取数组元素值得一般格式
${数组名[下标]} ${数组名[@]}全部元素
获取数组长度的方法和获取字符串长度的方法相同,如
#取得数组元素的个数 length=${#array_name[@]} #或者 length=${#array_name[*]} #或者数组单个元素的长度 length=${array_name[n]}
我们可以在执行Shell脚本时,像脚本传递参数,脚本内获取参数的格式为:$n. n代表一个数字,1为执行脚本的第一个参数 2代表执行脚本的第二个参数 以此类推……
以下实例我们向脚本传递三个参数,并分别输出,其中$0为执行的文件名:
#!/bin/bash # author:李会洋 # url:www.runoob.com echo "Shell 传递参数实例!" echo "执行的文件名:$0" echo "执行的文件名:$1"
互联网早期,用户使用浏览器浏览的都是一些没有复杂逻辑、简单页面,这些页面都是在后端将html拼接好的然后将之返回给前端完整的html文件,浏览器拿到这个HTML文件之后就可以直接解析展示了,而这也就是所谓的服务器端渲染,而随着前端页面复杂性的提高,前端就不仅仅是普通的页面展示了,而可能添加了更多的功能性的组件,复杂性大,另外,彼时ajax的兴起,使得业界就开始推崇前后端分离的开发模式,即后端不提供完整的HTML页面,而提供一些api使得亲爱单可以获取到json数据,然后前端拿到json数据之后再在前端进行HTML页面的拼接,然后展示在浏览器上,这就是所谓的客户端渲染,这样前端就可以专注的UI的开发,后端专注于逻辑的开发
客户端渲染和服务器渲染的最重要的区别是究竟是谁来完成HTML文件的完整拼接,如果实在服务器端完成,然后就返回到客户端,就是服务器端渲染,而如果是前端做了更多的工作完成HTML拼接,则是客户端渲染
实际上,时至今日,前后端分离一定是必然或者趋势,因为早期在web1.0时代的网页就是简单的网页,而如今的网页越来越朝向app前进,而如今的网页越来越朝app前进,而前后端分离就是实现必然的结果,所以我们可以认为HTML、css、JS组成了这个app。然后浏览器作为虚拟机来运行这些程序,即浏览器组成了app的运行环境,成了客户端,我们目前接触的前端工程化、编译、各种MVC/MVVM框架、依赖工具、npm、bable、webpack等看似很新鲜、创新的东西实际上都是桌面开发所形成的概念,只是近几年来前端发展较快而借鉴过来的,本质上就是开元社区东拼西凑做出来的visual studio。
Javascript原有的表示‘集合’的数据结构,主要是数组和对象,ES6又添加了Map和Set,这样就有了四种数据集合,用户还可以组合使用他们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象,这样就需要一种统一的接口机制,来处理不同的数据结构
遍历器就是这样的一种机制,他是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据只要部署Iterator接口,就可以完成遍历操作(即依次处理改数据结构的所有成员)
Iterator的作用有三个:一是为各种数据结构,提供一个统一的,简便的访问接口;二是使得数据结构的成员能够按某种次序排列,三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for ...of 消费
Iterator遍历的过程是这样的。
每一次调用next方法,都会返回数据结构当前成员的信息,具体来说,就是返回一个包含value和done两个属性的对象,其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束,
下面是一个模拟next方法的返回值的例子
var it = makeIterator(['a','b']); it.next() //{value:'a',done:false} it.next() //{value:'b',done:false} it.next() //{value:undefined,done:true} function makeIterator(array){ var nextIndex=0; return{ next:function(){ return nextIndex < array.length?{value:array[nextIndex++],done,false}:{value:undefined,done:true} } } }
上面代码定义了一个makeIterator函数,他是一个遍历器生成函数,作用就是返回一个遍历器,对数组['a','b']执行这个函数,就会返回该数组的遍历器对象(即针对对象)it
指针对象的next方法,用来针对指针,开始时,指针指向数组的开始位置,然后,每次调用next方法。指针就会指向数组的下一个成员,第一次调用,指向a;第二次调用,指向b
next方法返回一个对象,表示当前数据成员的信息,这个对象具有value和done两个属性,value返回当前位置的成员,done属性是一个布尔值,表示遍历是否结束,即是否还有必要再调一下next方法
总之,调用指针对象的的next方法,就可以遍历事先给定的数据结构
对于遍历器对象来说,done:false value:undefined属性是可以省略的,因此上面的makeIterator函数可以简写成
function maleIterator(array){ var nextIndex=0; return { next:functiuon(){ return nextIndex < array.length?{value:array[nextIndex++]}:{done:true} } } }
由于Iterator只是把接口规格加到数据结构上,所以,遍历器与他所有遍历那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构,下面是一个无线运行的遍历器对象的样子
var it = idMake() it.next().value //'0' it.next().value //'1' it.next().value //'2' //... function idMake(){ var index = 0; return { next:function(){ return {value:index++,done:false} } } }
上面的例子中,便离去生成函数idMake,返回一个遍历器对象,但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来
Iterator接口的目的,就是为所以数据结构,提供一种访问机制,即for ...of,当使用for ... of 循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
一种数据结构只要部署了Iterator接口,我们就称这种数据结构是可遍历性的
原生具备Iterator接口的数据结构如下
下面的例子是数组的Symbol.iterator属性
let arr = ['a','b','c'] let iter = arr[Symbol.iterator]() iter.next() //{value:'a',done:false} iter.next() //{value:'b',done:false} iter.next() //{value:'c',done:false} iter.next() //{value:undefined,done:true}
上面代码中,变量arr是一个数组,原生就具有遍历器接口,部署在arr的Symbol.iterator属性上面,所以。调用这个属性,就得到遍历器对象
Action类似与mutation,不同在于:
让我们来注册一个简单的action
const store = new Vuex({ state:{ count:0 }, mutations:{ increment(state){ state.count++ } }, actions:{ increment(context){ context.commit('increment') } } })
Action 函数接受一个与store实例相同方法和属性的context对象,因此你可以调用context.commit提交一个mutation,或者context.state和context.getter来获取state和getter。
实践中,我们会经常用到ES2015的参数来简化代码(特别是我们需要调用commit很多次的时候)
actions:{ increment({commit}){ commit('increment') } }
Action通过store.dispatch方法触发
store.dispatch('increment')
咋一看上去感觉多次一举,我们直接分发mutation岂不是更方便,还记得mutation必须同步执行这个限制么,Action就不受限制,我们可以在Action内部执行异步函数
action:{ incrementAsync({commit}){ settimeout(()=>{ commit('increment') },1000) } }
Actions支持同样的载荷方式和对象方式进行分发
//以载荷形式分发 store.dispatch('incrementAsync',{ amount:10 }) //以对象形式分发 store.dispatch({ type:'incrementAsync', amount:10 })
来看一个更加实际的购物车示例 ,涉及到调用API和分发多重mutation:
actions:{ checkout({commit,state},products){ //吧当前购物车里的物品备份起来 const savedCartItem = [...state.cart.added]; //发出账单请求,然后乐观的清空购物车 commit(type.CHECKOUT_REQUEST) //购物API接受一个成功函数和一个失败函数 shop.buyProducts({ products, ()=>{ commit(type.CHECKOUT_SUCCESS) }, ()=>{ commit(type.CHECKOUT_FAILURE,savadCartItems) } }) } }
你在组件中使用this.$store.dispatch('xxx')分发action,或者使用mapAction辅助函数将组件的methods映射为store.dispatch调用
import {mapActions} from 'Vuex'; export default{ metheds:{ ...mapActions([ 'increment', //this.$store.dispatch('increment') 'incrementBy' //this.$store.dispatch('increment') ]) ...mapActions([ 'add':'increment' ]) } }
Action通常是异步的,那么如何知道Action什么时候结束呢,更主要的是,我们如何组合多个Action,以处理更加复杂的异步流程
首先,你需要明白store.dispatch可以处理被触发的Action的处理函数返回store.dispatch仍旧返回Preomise
actions:{ actionA({commit}){ return new Promise((resolve,reject)=>{ settimeout(()=>{ commit('someMutation') resolve() },1000) }) } }
现在你可以:
store.dispatch('actionA').then(()=>{ //... })在另一个action中也可以actions:{ actionB({dispatch,commit}){ return dispatch('actionA').then(()=>{ commit('someOtherMutation') }) } }最后,我们利用async/await,我们可以如下组合
//加入getData()和getOtherData()返回的是Promise actions:{ async actionA({commit}){ commit('gotData',await getData()) }, async actionB({dispatch,commit}){ await dispatch('actionA') //等待actionA完成 commit('gotOtherData',await getOtherData()) } }
ES6新增let命令。用来声明变量,他的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
{ let a=10; var b=1; }
上面代码在代码块之中,分别用let和var声明了两个变量,然而在代码块之外调用这两个变量,结果let声明报错,var声明的变量反悔了正确的值,这表明。let声明的变量只在他所在的代码块中有效。
for循环的计数器,就很适合使用let命令
var a=[]; for(var i=0;i<10;i++){ a[i]=function(){ console.log(i) } } a[6]() //10
上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局变量只有一个变量i。每一次循环,变量i的值都会发生变化,而循环内被赋给数组a的函数内部的console.log(i)=,里面的i指向的就是全局的i,导致运行时输出的是最后一轮的i的值,也就是10.
如果使用let,声明的变量仅在块级作用域内有效,最后输出是6
var a=[]; for(let i=0;i<10;i++>){ a[i]=function(){ console.log(i) } } a[6]() //6
上面代码中,变量i是let声明的,当前的i只在本轮循环中有效,所以每一次循环的i其实都是一个新的变量,所有最后输出的是6,JavaScript引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮的基础上进行计算
另外,for循环还有一个特别之处,就是设置环境变量的那部分是一个父级作用域,而循环体内部是一个单独的作用域
for(let i=0;i<3;i++>){ let i='abc'; console.log(i) } //abc //abc //abc
上面代码正确运行,输出了3次abc,这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域
//var 的情况 console.log(foo) //输出undefined var foo=2; //let情况 console.log(bar) var bar=2
上面代码中,变量foo用var命令声明,会发生变量提升,变量bar用let命令声明,不会发生变量提升,这表示在声明他之前bar是不存在的
在代码块中,使用let命令声明的变量之前,该变量是不可用的,这在语法上成为‘暂时性死区’
let不允许在相同作用域内,重复声明同一个变量,
//报错 function(){ let a=10; var a=1; } //报错 function(arg){ let arg }
const声明一个只读的变量,一旦声明,常量的值就不能改变
ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称之为解构
以前,为变量赋值,只能直接指定值
let a=0; let b=1; let c=3; 可以写成 let [a,b,c]=[0,1,2] 数组对应位置赋值
本质上,这种写法属于“模式匹配”,只需要等号两边的模式相同,左边的变量会被赋予对应的值,下面是一些使用嵌套数组进行结构的例子
let [foo,[[bar],baz]]=[1,[[2],3]] //foo =1; bar=2; baz =3 let [ , ,third]=["foo","bar","baz"] // third =baz let [head,...tail]=[1,2,3,4] head //1,tail //[2,3,4] let [x,y,...z]=["a"] x //"a" y //undefined z //[]
如果不解构成功,foo的值都会等于undefined
let [foo]=[]; let [bar,foo]=[1]
另一种情况是不完全解构,即等号左边的模式,只能匹配一部分等号右边的数组,这种情况下,解构已然成功
let [x,y]=[1,2,3] let [a,[b],c]=[1,[2,3],4]
解构赋值允许指定默认值
let [foo=true]=[] let [x,y='b']=['a'] let [x,y='undefined']=['a',undefined]
注:ES6内部使用严格的相等运算符(===),判读眼部 一个位置是否有值,所以,如果一个数组成员不严格等于undefined,默认值是不会生效的,
let [x=1]=[undefined] let [x=1]=[null]
上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候才会求值
funtion f(){ console.log('aaa') } let [x=f()]=[1]
上面代码中,因为x能取到值,就不会执行f函数,上面的代码等价于下面的代码
let x; if([1][0]==undefined){ x=f(); }else{ x=[1][0] }
解构不仅仅用于数组,还可以用于对象
let {foo,bar}={foo:'aaa',bar:'bbb'}; foo //'aaa' bar //'bbb'
对象的解构与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值有它的位置决定,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let {foo:baz}={foo:'aaa',bar:'bbb'} baz //aaa
对象的解构内部机制,实现找到同名属性,然后再赋给对应的变量,真正的赋值是后者,而不是前者。
foo是匹配的模式,baz才是变量,真正被赋值的是变量baz,而不是foo
字符串也是可以解构赋值的,这是因为此时,字符串被转换成了一个类似的数组对象
const [a,b,c,d,e]='hello' let {length:len}='hello' len //5
类数组的对象都有一个length属性,因此还可以对这个属性解构赋值
解构赋值时,如果等号右边是数值和布尔值,则先转为对象
let {toString:s}=123; s===Number.prototype.toString let {toString:s}=true s===Boolean.prototype.toString
上面代码中,数值和布尔值包装对象都有toString属性,因此s都能取到值
函数参数也可以解构赋值
function add([x,y]){ return x+y; } add([1,2])
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数被解构城变量x和y,对于函数内部的代码来说,他们能感受到的参数就是x和y
[[1,2],[3,4]].map((a,b)=>a+b) //3 7
变量的解构赋值用途很多
let x=1; let y=2; [x,y]=[y,x]
上面代码交换变量x和y的值
function example(){ return [1,2,3] } let [a,b,c]=example(); function example(){ return { foo:'1', bar:'2' } } let {foo,bar}=example()
传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在可另一个字符串中,ES6有提供了三种方法
var s='Hello world!' s.startWith('Hello') //true s.endWith('!') //true s.includes('o') //true
ES6在Number对象上,提供了以上两个方法
Number.isFinite()用来检查一个数值是否为有限的(finite)
Number.isFinite(15)// true Number.isFinite(NaN) //false Number.isFinite(true) //false
Number.isNaN()用来检查一个值是否为NaN
(function(global){ var global_isFinite=global.isFinite; Object.definePrototy(Number,'isFinite',{ value:function isFinite(value){ return typeof value==='number'&&global_isFinite(value) }, configurable:true, enumberable:false, writable:true }) })(this)Number.isNaN(NaN) //true
Number.isNaN(9/NaN) //true
Number.isNaN('true'/0) //true
Number.isNaN('true'/'true')(function(global){ var global_isNaN=global.isNaN; Object.definePrototy(Number,'isNaN',{ value:function isNaN(value){ return typeof value==='number'&&global_isNaN(value) }, configurable:true, enumberable:false, writable:true }) })(this)Number.perseInt()和Number.parseFloat()
ES6将全局方法perseInt perseFloat() 移植到Number上面,行为完全保持不变
perseInt('12.34') //12 perseFloat('12.345#') //12.345 Number.perseInt('12.34') //12 Number.perseFloat('12.345#') //12.345这样走的目的是减少全局方法
Number.isInteger()
number.isInteger()用来判断一个值是否为整数,要注意的是,在JavaScript内部,整数和浮点数都是同样的储存方法,所以3和3.0被视为同一值
Number.isInteger(25) //true Number.isInteger(25.0) //true Number.isInt(25.1) //false Number.isInt('25') //false(function(global){ var floor=Math.floor, isFinite=global.isFinite Object.definePrototy(Number,'isInteger',{ value:function(value){ return typeof value ==='number'&&isFinte(value)&&floor(value)===value }, configurable:true, enumberable:false, writable:true }) })(this)Math对象的扩展
ES6在math对象新增了17个与数字相关的方法,所有这些方法都是静态方法,只能在Math对象上调用
function log(x,y='world'){ console.log(x,y) } log('hello') //hello world log('hello','world') hello world log('hello','') hello
function foo({x,y=5}){ console.log(x,y) } foo({}) //undefined 5 foo({x:1}) //1 5 foo({1,2}) //1 2 foo() //x报错 应该传一个对象
function foo({x,y=5}={}){ console.log(x,y) } foo() //undefined 5
如果没有参数,函数foo的参数默认为一个空对象
通常情况下,定义了默认值的参数,应该是函数的为参数,因为这样比较容易看出来,到底省略了那些参数,如果是非尾部的参数设置默认值,实际上这个参数是无非省略的,
function f(x=1,y){ return [x,y] } f() //1 undefined f(2) //2 undefined f(,1) //报错 f(undefined,1) //1 1 function f(x,y=5,z){ return [x,y,z] } f() //[ undefined 5 undefined] f(1) //1,5,undefined f(1, ,2) //报错 f(1,undefined,2) //[1,5,2]
上面代码中,有默认值的参数都不是尾参数,这时,无法只省略改参数,而不省略他后面的参数,除非显示输入undefined
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数的个数,也就是说,指定了默认之后,length属性将失真
(function(a){}).length //1 (function(a=5){}).length //0 (function(a,b,c=3){}).length //2
上面问题中,length属性的返回值,等于函数的参数个数减去指定默认值参数的个数
这是因为length属性的含义是,改函数预期传入的参数个数,某个参数指定默认值后,预期传入的参数就不包括这个参数了,同理,后文的rest参数也不会计入length属性。
(function(a=0,b){}).length //0 (function(a,b=9,c){}).length //1
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,等到初始化结束,这个作用域就会消失,这种语法行为,在不设置参数默认值=时,是不会出现的
var x=1 function f(x,y=x){ console.log(y) } f(2) //2
如果参数的默认值是一个函数,改函数的作用域也遵守这个规则,请看下面例子
let foo='outer' function bar(func=()=>foo){ foo='inner' console.log(func) } foo() //outer
上面代码中,函数bar的参数func的默认值是一个匿名函数,返回值变量为foo,函数参数形成单独作用域里面,并没有定义变量foo,所以foo指向外层的全局变量foo。因此输出outer
下面一个更复杂的例子
var x=1; function foo(x,y=function(){x=2}){ var x=3; y(); console.log(x) } foo(); //3 x //1
上面代码中,函数foo的参数形成一个单独的作用域,这个作用域里面,首先声明了变量x,然后声明了变量Y,y的默认值是一个匿名函数,这个匿名函数的内部变量x,指向同作用于的第一个参数x。函数foo内部有声明了一个内部变量x,该变量与第一个参数不在同一个作用域,不是一个变量,执行y之后,内部变量x和外部局部变量x值都不变
如果将var x=3的var去掉,函数foo的内部变量x就指向第一个参数,与匿名函数内部是一致的,所有输出的值为2
下面代码是一个rest参数代替argument变量的例子
//argument写法 function sortNumbers(){ return Array.prototype.slice.call(arguments).sort() } //rest参数写法 const sortNumber=(...number)=>number.sort()
上面两种写法,比较后发现,rest参数的写法更加自然也更简洁。
argument对象不是数组,还是一个类数组,为了使用数组的方法,需要把类数组转化成数组,Array.prototype.slice.call(),rest就不存在这个问题了,他是一个真正的数组,可以使用数组的任何方法,
//写一个push的例子 function push(array,...items){ items.forEach(function(item){ array.push(item) }) } var a=[]; push(a,1,2,3,4,5)
函数的name属性,返回该函数的函数名
function foo(){} foo.name //foo
ES6允许使用箭头函数来定义函数(=>)
var f=v=>v 等同于 var f=function(v){ return v }
如果箭头函数不需要参数或者需要多个参数,就使用一个圆括号代表参数部分
var f=()=>5 等同于 var f=function(){ return 5 } var sum=(sum1,sum2)=>sum1+sum2 等同于 var sun=function(sum1,sum2){ return sum1+sum2 }
如果箭头函数的代码块部分多于一条语句,就需要将他们括起来,并且使用return语句返回
var sum=(num1,num2)=>{return num1+num2}
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
//报错 let getTempItem=id=>{id:id,name:'Temp'} //不报错 let getTempItem =id=>({id:id,name:'Temp'})
箭头函数可以与变量解构结合使用
const full=({first,last})=>first+' '+last 等同于 function full(person){ person.first+''+person.last }
箭头函数的一个用处是简化回调函数
//正常写法 [1,2,3].map(function(x){ return x*x; }) //箭头函数 [1,2,3].map(x=>x*x)
箭头函数有一个使用注意点
上面四点中,第一点尤其值得注意,this对象的指向是可变的,但是在箭头函数中他是固定的
function foo(){ setTimeout(()=>{ console.log('id:',this.id) }) } var id=21 foo.call({id:42}) // 42
上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而他的真正执行是在100毫秒后,如果是普通函数,执行时this应该指向全局的window,但是箭头函数导致this总是指向函数定义生效时所在的对象。
function Timer(){ this.s1=0; this.s2=0; setInterval(function(){ this.s2++ },1000) setInterval(()=>this.s1++,1000) } var timer=new Timer() setTimeout(()=>console.log(timer.s1),3100) //3 setTimeout(()=>console.log(timer.s2),3100) //0
含义:扩展运算符(spread)是三个点(...).他好比rest的逆运算,将一个数组转化为逗号分隔的参数序列
console.log(...[1,2,3]) 1,2,3
##提点数组的apply方法
由于扩展运算符可以展开数组,所以不再需要apply方法,数组转为函数的参数
//es5 function f(a,b,c){} var args=[0,1,2] f.apply(null,args) //es6 function f(a,b,c){} let args=[0,1,2] f(...args)
下面一个求最大数的例子
Math.max.apply(null,[1,2,3]) Math.max(...[1,2,3]) 等同于 Math.max(1,2,3)
//es5 [1,2].concat(more) //es6 [1,2,...more]
Array.from方法用于将两类对象转化为真正的数组:类似数组的对象和可遍历的对象
下面是一个类似数组的对象,Array.from将它转化为真正的数组
let arrayLike={'0':'a','1':'b'} //es5的写法 var arr1=[].slice.call(arrayLike) //['a','b'] //es6的写法 var arr2=Array.from(arrayLike) //['a','b']
Array.of方法用于将一组值转化为数组
Array.of(1,2,3,4) //[1,2,3,4] Array.of(1,2).length //2
这个方法的目的是弥补构造函数Array的不足,因为参数个数的不同,会导致Array的行为有差异
Array() //[] Array(3) //[ , , ] Array(1,2,3) //[1,2,3]
Array.of() //[] Array.of(undefined) //[undefined] Array.of(3) //[3] Array.of(1,2,3,4) //[1,2,3,4]
数组实例的find方法,用于找出第一个符合条件的数组成员,他的第一个参数是回调函数,所有数组成员依次执行该回调函数,直到找到第一个返回值为true的成员,然后返回该成员,如果没有找到符合条件的成员,则返回undefined;
[1,2,3,4,-5].find(n=>n<0) //-5
[1,2,3,4,5].find((value,index,arr)=>value>1) //2
数组实例的findIndex方法与find非常相似,返回第一个符合条件的位置,如果条件不符合,返回-1
fill方法使用给定值,填充一个数组
es6提供三个新的方法用于遍历数组,分别是对键名的遍历、键值的遍历、键值对的遍历
for(let index of ['a','b'].keys()){ console.log(index) } //0 //1 for(let item of ['a','b'].value()){ console.log(item) } //a //b for(let [index,item] of ['a','b'].entries()){ console.log(index,item) } //0,'a' //1,'b'
Array.prototype.includes方法返回一个布尔值,表示数组是否包含给定的值,与字符串的includes方法类似
[1,2,3].includes(2) //true [1,2,3].includes(4) //false [1,2,NaN].includes(NaN) //true
es6允许直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁
const foo='bar' const baz={foo} // baz {foo:bar} 等同于 const baz={foo:foo}
javascript定义对象的属性,有两中方法
obj.foo=true obj['a'+'bc']=123
主要学习一下es6的方法
用方法二作为对象的属性名,即把表达式方在中括号里面
let propKey='foo' let obj={ [propKey]=true, ['a'+'bc']=123 }
基本用法,方法用于对象的合并,将原对象(source)的所有可枚举属性,复制到目标对象
const target={a:1} const source1={b:2} const source2={c:3} Object.assign(target,source1,source2) target {a:1,b:2,c:3}
object.assign第一个参数是目标对象,后面的参数是源对象
如果目标对象和源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
const set=new Set([1,2,3,4,5,5,4,3,2,1]) [...set]
Set实例分为两大类:操作方法(用于操作数据)和遍历数据,
上面这些属性和方法的实例如下
let s=new Set() s.add(1).add(2).add(3) s.size() //3 s.has(1) //true s.has(4) //false s.delete(2) s.has(2) //false
let s=new Set([1,2,3,4,5]) console.log([...s]) console.log(Array.from(s)) s.add(10) console.log(s) console.log(s.has(2)) s.delete(2) console.log(s.has(2)) s.clear() console.log([...s]) //[ 1, 2, 3, 4, 5 ] //[ 1, 2, 3, 4, 5 ] //Set { 1, 2, 3, 4, 5, 10 } //true //false //[]
Set结构的实例有四个方法,可以用于遍历成员
需要特别指出的是,Set的遍历顺序就是插入顺序,这个特性有时非常有用,比如使用Set保存一个回掉函数列表,调用时就能保证按照添加顺序调用。
keys方法、value、entries返回都是遍历器对象,由于Set结构没有键名,只有键值,所以keys方法和value方法行为完全一致
扩展运算符(...)内部使用for of循环,所以也可以用于Set结构
let set = new Set(['1','2','3']) let arr = [...set] //['1','2','3'] let arr = [1,2,3,4,5,4,3,2,1] let unique = [...new Set(arr)] // unique [1,2,3,4,5] let set = Set([1,2,3]) set = new Set([...set].filter(x=>x>2)) set = new Set([...set].map(x=x*2))
WeakSet结构与Set类似,也是不重复的值得集合,但是,它与Set有两个区别,
const ws = new WeakSet()
作为构造函数,WeakSet可以接受一个数组或类数组的对象作为参数,
const a = [[1,2],[3,4]] cosnt ws = new WeakSet(a) // WeakSet {[1,2],[3,4]}
含义与基本用法
javaScript对象。本质上是键值对的集合(Hash结构),但是传统上只能字符串当做键,这给它的使用带来很大的限制
const m= new Map(); m.set('o','content') m.get('o') //'centent' m.has('o') //true m.delete('o') m.has('o') //false
Map结构的实例有一下属性和操作方法
整理中。。。
1、什么是OAuth2.0授权?(http://justcoding.iteye.com/blog/1950270)
所谓OAuth(即Open Authorization,开放授权),它是为用户资源授权提供了一种安全简单的标准,也就是说用户在访问第三方web或应用的时候,第三方不会知道用户的信息(登录密码等),现在基本都支持OAuth2.0版本了。
首先来看看我们在第三方使用oauth流程如下:
第一步:用户登录第三方网站,使用qq登录。
第二步:点击登录后,会跳到qq平台提示输入用户名和密码。
第三步:如果用户名和密码正确,会提示是否接受授权,如果授权成功,第三方网站就能访问你的资源了,qq头像、用户名等
认证和授权过程(包括三方)
1、服务提供方,用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表。
2、用户,存放在服务提供方的受保护的资源的拥有者。
3、客户端,要访问服务提供方资源的第三方应用,通常是网站。在认证过程之前,客户端要向服务提供者申请客户端标识。
用户访问客户端的网站,想操作用户存放在服务提供方的资源。
客户端向服务提供方请求一个临时令牌。
服务提供方验证客户端的身份后,授予一个临时令牌。
客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方。
用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。
授权成功后,服务提供方引导用户返回客户端的网页,并返回已授权的临时凭证。
客户端根据已授权的临时令牌从服务提供方那里获取访问令牌。
服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌。
客户端使用获取的访问令牌访问该用户存放在服务提供方上的受保护的资源。(客户端只能访问给予它授权的用户的资源信息)
2、授权流程及操作
http://www.fangbei.org/wxpay/js_api_call.php
时跳转到
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx8888888888888888&redirect_uri=http://www.fangbei.org/wxpay/js_api_call.php&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
以此来获得code参数,并根据code来获得授权access_token及openid其实现的详细流程可参考 微信公众平台开发(71)OAuth2.0网页授权(http://www.cnblogs.com/txw1958/p/weixin71-oauth20.html)
在微信支付的Demo中,其代码为
//使用jsapi接口 $jsApi = new JsApi_pub(); //=========步骤1:网页授权获取用户openid============ //通过code获得openid if (!isset($_GET['code'])) { //触发微信返回code码 $url = $jsApi->createOauthUrlForCode(WxPayConf_pub::JS_API_CALL_URL); Header("Location: $url"); }else { //获取code码,以获取openid $code = $_GET['code']; $jsApi->setCode($code); $openid = $jsApi->getOpenId(); }
这一步的最终结果就是获得了当前用户的openid
ou9dHt0L8qFLI1foP-kj5x1mDWsM
$this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 $this->parameters["spbill_create_ip"] = $_SERVER['REMOTE_ADDR'];//终端ip $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 $this->parameters["sign"] = $this->getSign($this->parameters);//签名
//统一支付接口中,trade_type为JSAPI时,openid为必填参数! $unifiedOrder->setParameter("openid","$openid");//商品描述 $unifiedOrder->setParameter("body","方倍工作室");//商品描述 //自定义订单号,此处仅作举例 $timeStamp = time(); $out_trade_no = WxPayConf_pub::APPID."$timeStamp"; $unifiedOrder->setParameter("out_trade_no","$out_trade_no");//商户订单号 $unifiedOrder->setParameter("total_fee","1");//总金额 $unifiedOrder->setParameter("notify_url",WxPayConf_pub::NOTIFY_URL);//通知地址 $unifiedOrder->setParameter("trade_type","JSAPI");//交易类型
//非必填参数,商户可根据实际情况选填 //$unifiedOrder->setParameter("sub_mch_id","XXXX");//子商户号 //$unifiedOrder->setParameter("device_info","XXXX");//设备号 //$unifiedOrder->setParameter("attach","XXXX");//附加数据 //$unifiedOrder->setParameter("time_start","XXXX");//交易起始时间 //$unifiedOrder->setParameter("time_expire","XXXX");//交易结束时间 //$unifiedOrder->setParameter("goods_tag","XXXX");//商品标记 //$unifiedOrder->setParameter("openid","XXXX");//用户标识 //$unifiedOrder->setParameter("product_id","XXXX");//商品ID
1 10012345
https://api.mch.weixin.qq.com/pay/unifiedorder
wx201410272009395522657a690389285100
前面的准备工作做好了以后,JS API根据prepay_id生成jsapi支付参数
生成代码如下
//=========步骤3:使用jsapi调起支付============ $jsApi->setPrepayId($prepay_id); $jsApiParameters = $jsApi->getParameters();
生成的json数据如下
{ "appId": "wx8888888888888888", "timeStamp": "1414411784", "nonceStr": "gbwr71b5no6q6ne18c8up1u7l7he2y75", "package": "prepay_id=wx201410272009395522657a690389285100", "signType": "MD5", "paySign": "9C6747193720F851EB876299D59F6C7D" }
在微信浏览器中调试起js接口,代码如下
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<title>微信安全支付</title>
<script type="text/javascript">
//调用微信JS api 支付
function jsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
<?php echo $jsApiParameters; ?>,
function(res){
WeixinJSBridge.log(res.err_msg);
//alert(res.err_code+res.err_desc+res.err_msg);
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
}else{
jsApiCall();
}
}
</script>
</head>
<body>
</br></br></br></br>
<div align="center">
<button style="width:210px; height:30px; background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >贡献一下</button>
</div>
</body>
</html>
当用户点击“贡献一下”按钮时,将弹出微信支付插件,用户可以开始支付。
1
构造超类-->
function Super() {
this.name="AA"
}
Super.prototype.getName=function () {
console.log(this.name)
};
让子类的原型指向 超类的一个实例,就会拥有 实例私有的属性。通过 实例的__proto__找到超的原型,里面的属性和方法一样可以找到。
即 var sub=new Sub(); sub拥有Super的私有和原型上的属性和方法。
核心理念: 通过 增加sub子类和super超类之间的原型链 链接,让 Sub的实例能找到Super的原型和私有属性,
以后Super的原型上添加方法。sub依然可以找到
function Sub() {
}
Sub.prototype=new Super();
var sub=new Sub();
console.log(sub.name);
sub.__proto__.name='BB';
sub.getName();
console.log(Sub.prototype);
var sub1=new Sub();
console.log(sub1.name);
console.dir(sub.__proto__);
只会继承私有的 实例化的时候调用父类的构造函数
function Sub() {
Super.call(this);
}
var sub=new Sub();
console.log(sub.name)
实例化的时候,把父级私有的公有的都遍历出来,都加到子级的实例私有属性上。没有原型链的联系
function Sub() {
var super1=new Super;
for(var key in super1){
this[key]=super1[key];
}
super1=null;
}
var sub=new Sub();
console.log(sub.name);
sub.getName()*/
我们的call继承 继承了私有的。缺少公有的方法。那我们可以用
call+原型继承来实现公有属性的继承。 好处是 私有的还在 实例的私有属性上,每一个实例都有自己的。通过 原型链也可以找到公有的属性。缺点是 原型上 多了一份 私有的属性。 超级的构造函数也执行了两次
一种在 超类和 子类加了一层原型链的方式 .当然只是继承原型上的方法,也就是公有的。 最坑的地方就是不兼容 不然用这个 + call继承 可以完美实现 继承
function Sub() {
}
Sub.prototype.__proto__=Super.prototype;
var sub=new Sub();
console.log(sub.getName);
综合以上的继承方法,我们想实现那种 私有的属性是私有的,公有的就在原型上。只能用 组合式的继承来实现。把共享的属性、方法用原型链继承实现,独享的属性、方法用借用构造函数实现,所以组合继承几乎完美实现了js的继承.但是一个bug就是 原型上多了 私有的属性。而且让超类构造函数执行两次。
// 现在的ECMAscript提供了一个 方法, Object.create(proto,[propertiesObject])
// 返回一个对象。该对象的原型 指向 Super.prototype
function Sub() {
}
Sub.prototype=Object.create(Super.prototype);
console.log(Sub.prototype);
这样子类就继承了超类 公有的方法
但是ES5 兼容性 我们需要自己写一个 Objectcreate方法
function objectCreate(o) {
function Temp() {}
Temp.prototype=o;
return new Temp();
}
function Sub() {
}
Sub.prototype=objectCreate(Super.prototype);
var sub=new Sub;
console.log(sub.getName)
我创建一个仅用于 封装继承的函数 用这个当做Super类的副本 实现共享原型, 但是由于这个 副本 不会被操作,也就不用担心 超类的原型被修改。然后让子类 去继承这个副本的原型。 创建一个实例,让子类的原型指向这个实例。即实现了原型链的继承,还不会影响超类的原型。 而且以后调用 父类的构造函数了,只需要执行这个副本函数。
Vuex是一个转为Vue.js应用开发的状态管理模式,它采用集中式储存管理应用的所有组件的状态,并以相应的规则保证以一种可预测的方式变化。
让我们从一个简单的Vuex计数应用开始
new Vue({ data(){ return{ count:0 } }, template:`< div>{{count}}< /div>`, methed:{ increment(){ this.count++ } } })
在这个状态自管理应用包含以下几个部分
但是,当我们遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
对于问题一,传参的方式对于多层嵌套的组件会非常繁琐,并且对于兄弟组件间的状态传递无能为力,对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝,以上的模式非常脆弱,通常会导致无法维护的代码
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的视图,不管在树的哪一个位置,任何组件都能获取状态或触发行为
另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且容易维护
这就是Vuex背后的基本**,与其他的模式不同,Vuex是专门为Vue.js设计的状态管理库,以利用Vue.js的细粒度数据响应机制来进行高效的状态更新
type 的值可以有很多,下面有我们常用的
//在 https://github.com/lihuiyang99/vue-prerender-snipcart.git 项目基础上开发
我们应用中所有的内容都是使用JS动态渲染的,这很不利于搜索引擎优化(SEO),网页中的异步内容不能被'蜘蛛'有效的识别并抓取,这样的话,我们的电子商务网站错过了所有有用的'网络爬虫',这不是一个明智的选择
相对于Vue的SSR(服务器端渲染),preRendering则更容易使用,另外,这两种技术在实现SEO层面所达到的效果是相似的。
预渲染将使我们的前端作为一种快速,轻量级的静态网站,以便于爬虫进行爬取
让我们来看看如何使用他:转到WebPack配置文件,在plugin配置项中添加以下配置:
//在 https://github.com/lihuiyang99/vue-prerender-snipcart.git 项目基础上开发 plugin:[ new CopyWebpackPlugin()[{ from:'src/static' }], new PreenderSpaPlugin( //将渲染的文件放到dist目录下 path.join(__dirname,'dist'), //需要预渲染的路由信息 ['/','/products/1','/products/2','/products/3'], //在一定时间后再捕获页面信息,使得页面数据信息加载完成 captureAfterTime: 50000, //忽略打包错误 ignoreJSErrors: true, phantomOptions: '--web-security=false', maxAttempts: 10, ) ]
CopywebpackPlugin 将会复制static文件夹中的文件到dist文件夹中,然后,PrerenderSpaPlugin使用phantomJS加载网页的内容,并将结果作为我们的静态资源
纹理就是皮肤
在THREE中,或者在3D引擎中,纹理应该怎样来实现呢,首先应该有一个文理类,其次是有一个加载图片的方法,将这张图片和这个纹理捆绑起来。
在THREE中,纹理类有Three.Texture表示,其构造函数如下所示,
THREE.Texture(img,mapping,wrapS,wrapT,magFilter,minFilter,format,type,anisotropy)
各个参数的意义是:
在正常的情况下,你在0.0到1.0之间的范围指定纹理坐标,当我们用一副图来做纹理的时候,那么这幅图就隐士的被赋予如图一样的纹理坐标,这个纹理坐标将被对应到一个形状上
var camera ,scene ,renderer; var mesh; init(); animation(); function init(){ renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth,window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,1,1000) camera.position.z=400; scene = new THREE.Scene(); var geometry = new THREE.PlaneGeometry(500,300,1,1); geometry.vertices[0].uv = new THREE.Vector2(0,0) geometry.vertices[0].uv = new THREE.Vector2(2,0) geometry.vertices[0].uv = new THREE.Vector2(2,2) geometry.vertices[0].uv = new THREE.Vector2(0,2) var texture = THREE.TextureUtils.loadTexture('/a.img'); var material = new THREE.MeshBasicMaterial({map:texture}); var mesh = new THREE.Mesh(geometry,material) scene.add(mesh) } fucntion animation(){ requeatAnimationFrame(animation); renderer.render(scene,camera) }
阅读上面代码,一共完成了四件事情
- 1、画一个平面
- 2、为平面赋予一个纹理坐标
- 3、加载纹理
- 4、将纹理应用到材质
通过PlaneGeometry可以画一个平面,代码如下:
<pre>var geometry = new THREE.PlaneGeometry(500,300,1,1)</pre>
这个平面的宽度是500,高度是300
平面有4个顶点,所以我们只需要指定4个纹理坐标就可以了,纹理坐标由顶点的UV成员表示,uv被定义为一个二维向量THREE.Vector2(),可以通过如下代码来为平面定义纹理:
<pre>
geometry.vertices[0].uv = new THREE.Vector(0,0)
geometry.vertices[1].uv = new THREE.Vector(1,0)
geometry.vertices[2].uv = new THREE.Vector(1,1)
geometry.vertices[3].uv = new THREE.Vector(0,1)</pre>
注意,4个顶点分别对应了纹理的4个顶点,还要注意他们之间的顺序是逆时针方向,
纹理作为一张图片,可以来源于互联网,或者本地服务器,这里加纹理使用了上面介绍的loadTexture函数,代码如下:
<pre>
var texture = THREE.ImageUtils.loadTextrue('a.img',null,function(){})</pre>
这个函数的第一个参数是一个相对路径,表示与你的网页之间的相对路径,
第二个参数为null 表示时候要传入一个纹理坐标参数,来覆盖前面在geometry中的参数
第三个参数是回调函数,表示成功加载完纹理后要执行的参数,参数t是textrue
加载好纹理,只需要将纹理映射到材质就可以了,我们这里使用一个普通的材质THREE.MeshBasicMaterial(),材质中有一个map属性,可以直接接受纹理,我们可以这样定义一个带纹理的材质:
<pre>
var material = new THREE.MeshBasicMaterial({map:texture})</pre>
直接将纹理甩给Mesh,同时也别忘了Mesh也需要geometry
<pre>
var mesh = new THREE.Mesh(geometry,material)
scene.add(mesh)</pre>
一种方法是使用材料数组,我们创建6个新材料,每一个使用不同的纹理贴画,1.img,2.img,...6.img,
相应的,我们把材料构造代码改为:
<pre>
var material1 = new THREE.MeshPhonMaterial({
map:THREE.ImageUtils.loadTexture('1.img')
})
var material2 = new THREE.MeshPhonMaterial({
map:THREE.ImageUtils.loadTexture('2.img')
})
.
.
var material6 = new THREE.MeshPhonMaterial({
map:THREE.ImageUtils.loadTextture('6.img')
})
var materials = [material1,material2,...material6]
var meshFaceMaterial = new THREE.MeshFacMaterial(materials)
</pre>
上述代码中,我们先分别创建了6个材料,并使用这个数组创建一个MeshFaceMaterial对象。
最后我们需要告诉我们的3D模型,来使用这个新的组合'面材料',修改下面的代码
<pre>
mesh = new THREE.mesh(geometry,material);
改为
mesh = new THREE.mesh(geometry,meshFaceMaterial)</pre>
你就可以看到立方体各个表面使用了不同的贴图
那么问题来了,随着3D模型的面增长,为每个面创建贴图是不现实的,这就是为什么我们需要另一个更为普遍的解决方法,uv映射的原因
UV映射作典型的额例子就是把一张地图映射到3D球体的地球仪上去,其本质就是把平面图像的不同区块映射到3D模型的不同面去,我们把之前的6张图拼装成一张图
修改代码为
var material = new THREE.MeshPhonMaterial({ map:THREE.ImageUtils.loadTexture('image/texture-atlas.jpg') })
我们又把代码改回来使用一张贴图,接下来我们需要把贴图的不同位置映射到立方体的不同面上去
首先,我们创建贴图的6个字图,在创建完材料的代码后面添加几行
var bricks = [new THREE.Vextor2(0,.666),new THREE.vector2(0.5,.666),new THREE.vector2(0.5,1),new THREE.vector2(0,1)] var clouds = [new THREE.vector2(0.5,0.666),new THREE.vector2(1.0.666),new THREE.vector2(1,1),new THREE.vector2(0.5,1)] . . var wood = [new THREE.vector2(.5,.333),new THREE.vector2(1,0.333),new THREE.vector2(1,0.333),new THREE.vector2(.5,.333)]
上面组建了6个数组,每一个对应于纹理贴图中的每一个字图,每个数组包含4个点,定义图像的边界,坐标的范围值0-1,(0,0)表示左下角,(1,1)表示右上角
定义好图像后,我们发现现在需要把他们映射到立方体的各个面上去,首先填写下面代码
geometry.faceVertecUvs[0] = [] //上面代码清除现有的UV映射,接着我们添加如下代码 geometry.faceVertexUvs[0][0] = [brick[0],brick[1],brick[3]] geometry.faceVertexUvs[0][1] = [brick[1],brick[2],brick[3]] . . geomety.faceVertexUvs[0][10] = [wood[0],wood[1],wood[3]] geomety.faceVertexUvs[0][11] = [wood[1],wood[2],wood[3]]
geometry对象的faceVertexUvs属性包含改geometry的各个面的坐标映射,既然我们映射到一个多维数据集,你可能会疑惑为什么数组中有12个面,原因是在THREE中,立方体的每个面实际上是由2个三角形组成的,上述场景中,THREE将为我们加载单一材质的贴图,自动拆分成三角形并映射到每个面
这里要注意每个面的顶点坐标的定义顺序必须遵循逆时针方向,为了映射底部三角形,我们需要的顶点指数0、1、3,而要映射顶部三角形,我们需要索引1、2、3
最后,我们替换如下代码
mesh = new THREE.Mesh(geometry,material)
运行代码,看到各个面使用不同的贴图旋转立方体
在几何空间中,点可以用一个向量来表示,在THREE中也是用一个向量来表示的,代码如下表示:
THREE.Vector3 = function(x,y,z){ this.x = x || 0; this.y = y || 0; this.z = z || 0; }
我们来分析一下这段代码,前面我们已经知道THREE是Three.js引擎的一个全局变量,只要你想用它,就可以在任何地方用它,THREE.Vector3就是表示Vector3是定义在THREE下面的一个类,以后要用Vector3就必须加THREE前缀,THREE.Vector3被赋值为一个函数,这个函数有3个参数,分别是代表X坐标、Y坐标和z坐标的分量,函数体内的代码将他们分别赋值给成员变量x、y、z,看着上面的||运算符,就是相当于x=null或者undefined时。this.x的值应该取0
在3D的世界里,点可以用THREE.Vector3来表示,对应源码为/src/math/Vector3.js ,现在来看看怎么定义这个点,假设有一个点x=4,y=8,z=9.你可以这样定义它:
var point1 = new THREE.Vector3(4,8,9)
另外你也可以用set方法
var point1 = new THREE.Vector3() point1.set(4,8,9)
我们这里使用set方法,为了以后深入学习的方便,将Vector3的方法列一下
//js部分 var renderer; function initThree(){ width = document.getElementById('canvas-frame').clientWidth; height = document.getElementById('canvas-frame').clientHeight; renderer = new THREE.WebGLRenderer({ antialias:true }); renderer.setSize(width,height); document.getElementById('canvas-frame').appendChild(renderer.domElemnt) renderer.setClearColor(0xffffff,1.0) } var camera; function initCamera(){ camera = new THREE.perspectiveCamera(45,width/height,1,10000) camera.position.x = 0; camera.position.y =1000; camera.position.z = 0; camera.up.x = 0; camera.up.y = 0; camera.up.z = 1; camera.lookAt({ x:0, y:0, z:0 }) } var scene; function initScene(){ scene = new THREE.Scene(); } var light; function initLight(){ light = new THREE.DirectionalLight(0xff0000,1.0,0); light.position.set(100,100,200); scene.add(light) } var cube; function initObject(){ var geometry = new THREE.Geometry(); var material = new THREE.lineBasicMaterial({vertexColor:true}); var color1 = new THREE.Color(0x444444),color2 = new THREE .Color(0xFF0000) var p1 = new THREE.Vector3(-100,0,100); var p2 = new THREE.Vector3(100.0,-100); geometry.vertices.push(p1); geometry.vertices.push(p2); geometry.colors.push(color1,color2); var line = new THREE.line(geometry,material,THREE.linePieces) scene.add(line) } function threeStart(){ initThree() initCamera() initScene() initLight() initObject() renderer.clear() renderer.render(scene,camera) }
var geometry = new THREE.Geometry();几何体里面有个Vertices变量,可以用来存点
LineBasicMaterial(parameters)
Parameters是一个定义材质外观的对象,它包含多个属性来定义材质,这些属性是:
这里使用了定点颜色,vertexColor:THREE.VertexColor,就是线条的颜色会根据顶点来计算
var material = new THREE.LinebasicMaterial({VertexColor:THREEColors})
var color1 = new THREE.Color(0x444444) var color2 = new THREE.COlor(0xFF0000)
var p1 = new THREE.Vector3(-100,0,100) var p2 = new THREE.Vector3(100,0,-100) geometry.vertices.push(p1) geometry.vertices.push(p2)
geometry.color.push(color1,color2);
geometry中color表示顶点的颜色,必须材质中vertexColor等于THREE.VertexColor时,颜色才有效,如果vertexColor等于THREE.NoColor时,颜色就没效果了,那么就会去取材质中color的值,这个很重要,一定要记住
定义线条,使用THREE.line类,代码如下所示:
var line = new THREE.line(geometry,material,THREE.linePieces)
第一个参数是几何体geometry,里面包含了2个顶点和顶点的颜色,第二个参数是线条的材质,或者是线条的属性,表示线条以哪种方式取色,第三个参数是一组点的链接方式,
然后将这条线放到scene中 scene.add(line)
Threejs使用的是右手坐标系,
在ThreeJS中,一条线由点、材料和颜色组成。
点是由THREE.Vector3表示,Threejs中没有提供单独画点的函数,他必须被放到一个THREE.Geometry形状中,这个解构中包含一个数组vertices,这个vertices就是存放无数点的数组,为了绘制一条直线,首先我们需要定义两个点,代码如下
var point1 = THREE.Vector3(-100,0,100) var point2 = THREE.Vector3(100,0,-100)
请大家思考一下,这两个点的坐标什么位置,然后我们声明一个THREE.Geometry,并把点加进去,代码如下
var geometry = new THREE.Geometry() geometry.vertices.push(p1) geometry.vertices.push(p2) geometry.vertices能够使用push方法,是因为geometry.vertices是一个数组,这样geometry中有两个点了
然后我们需要给线加一种材质,可以专为线准备的材质,THREE.LineBasicMaterial
最后我们通过THREE.Line绘制一条线,代码如下
var line = new THREE.Line(geometry,material,THREE.LinePieces)这样,这就是我们需要的线条
var geometry = new THREE.Geometry(); geometry.vertices.push(new THREE.Vector3(-500,0,0)); geometry.vertices.push(new THREE.Vector3(500,0,0)); for(var i=0;i< =20;i++>){ var line = new THREE.Line(geometry,new THREE.LineBasicMaterial({color:xxxxxxxx,opacity:0.2})) line.position.z=(i*50)-500; scene.add(line) var line = new THREE.Line(geometry,new THREE.LineBasicMaterial({color:xxxxxxx,opacity:0.2})) line.position.x = (i*50) -500; line.rotation.y = 90 *Math.PI/180; scene.add(line) }
思路:我们要画一个网格的坐标,那么我们就应该找到线的点,把网格虚拟城正方形,在正方形边界上找到几个等分点,用这些点两两链接,就能画出整个网格来
1、定义两个点
在x轴上定义两个点p1 (500,0,0)和p2(-500,0,0)
geometry.vertices.push(new THREE.Vector3(p1)) geometry.vertices.push(new THREE.Vertor3(p2))
2、算法
这两个点决定了X轴上的一断线,将这段线复制20次,平移到z轴的不同位置,就形成一组平行的线段,同理,将P1P2这条线先围绕y轴旋转90度,然后再复制20分,平行于z轴移动到不同的位置,也形成一组平行线,经过上面的步骤,就得到坐标网格了,代码如下
for(var i=0; i< 20;i++>){ var line = new THREE.Line(geometry,new THREE.LineBasicMaterial({color:xxx,opacity:0.2})); line.position.z = (i*50) - 500; scene.add(line) var line = new THREE.Line(geometry,new THREE.LineBasicMaterial({color:xxx,opacity:0.2})); line.position.x = (i*50)-500; line.rotation.y = 90*Math.PI/180 //旋转90 scene.add(line) }
1、global全局作用域
我们用var创建的变量不是挂载global上的,如果没有var就会挂载在global
global上的属性 和文件模块上的属性__dirname __filename
2、exports module.exports 区别 导出引用数据类型采用module.exports;
function(require,exports,module,__dirname,__filename){ exports=modile.exports={}; exports=function(){} return module.exports }
3、require 在js中引入模块
4、 计算运行某段所需时间
console.time('start'); console.timeEnd('start');
5、__dirname 当前文件所在的文件夹 不可改变
6、__filename 当前文件的名字
7、globall.process
8、argv 参数 pid 当前进程的id nextTick 添加到当前事件列表最底部 stdout 控制台输出流监听用户写入 stdin 控制台输入流 skill 杀掉进程
9、setImmediate setTimeout
10、process.cwd current working direction 当前文件夹 可改变
11、process.chdir('../')改变当前文件夹
12、memoryUsage 内存的使用
一个文件require多次只执行一次,存在缓存机制 如果把缓存给删除了 就可以进行多次执行
13、require.catch 缓存
14、require.resolve('') 代表当前文件制定的文件额绝对路径
15、delete require.catch(require.resolve('');
16、setTimeout和setImmediate都是同一队列 setImmediate给机会
util 核心模块内置的 不需要去安装
判断是否为内置模块 require('./')
17、inherit 继承原型链继承 util.inherit(B,A) B继承A
18、defineproperty 定义属性
object.defineProperty(obj,age,{ enumerable:true, 可枚举的 value:100, 当前的age属性值 configurable:true, 看属性是否可以删除 writable:true 是否可写入 })
util.inspect(obj,{showHidden:true,depth:0,colors:true}) showHidden - 如果为 true,那么对象的不可枚举属性将也能被显示。默认为 false。 depth - 告诉 inspect 当格式化对象时递归多少次。这在查看非常复杂的对象时是很有用的。默认为2。想让它无限递归传递 null。 colors - 如果为 true,那么那么输出将使用 ANSI 颜色代码风格。默认为 false。颜色可以定做,见下文。 customInspect - 如果为 false,那么定义在正在被查看的对象上的定制 inspect() 函数将不会被调用。默认为 true。
20、util.isArray()
21 util.isError();
22 util.isDate()
23 util.isRegExp()
引用自己的包 会默认先找index.js 再找index.json 再通过package.json去查找main字段找到对应指定的index
24 buffer =new Buffer([]);通过数组形式定义的Buffer是16进制形式
25、var buffer=new Buffer('lihuiyang','utf8') 通过字符串的形式 node 不支持gbk
26、通过buffer的长度创建
var buffer =new Buffer(6) 随机分配内存
buff1.copy(buffer,'目标开始',源的开始,源的结束)
32、Buffer.concat([buffer1,buffer2],6)
33、 StringDecoder = require('string_decoder').StringDecoder
34、parseInt('1111',2)将其他进制转化成10进制;
10.toString(2) 将10进制转化成其他进制
35、Buffer数据的转化
(231).toString(2) (143).toString(2) (160).toString(2)
parse('00111001',2) parse('00111001',2) parse('00111001',2) parse('00111001',2)
readFile 异步 readFileSync 同步
//将我们的文件一直读到内存里,大文件我们不采用这种方法 使用流 var obj={}; var name=fs.readfileSync('./name.text',{encoding:'utf8',flag:'r'}) obj.name=name;
var obj={}; fs.readFile('./name.text','utf8',function(err,data){ obj.name=data; out(); }) fs.readFile('./age.txt','utf8',function(err,data){ obj.age=data; out(); }) function out(){ if(Object.key(obj).length==2){ console.log(obj); } }
fs.writeFile('./write.txt',new Buffer('珠峰')); fs.writeFileSync('./name1.txt',read,{flag:'a'}) fs.appendFileSync();等同于{flag:'a'}
function copy(source,target,callback)
配置output选择可以控制webpack如何向硬盘写入编译文件,注意,即使可以存在多个入口起点,但是只指定一个输出配置
在webpack中配置output属性,的最低要求是,将他的值设置为一个对象,包括以下两点:
const config = { output:{ filename:'bundle.js', path:'/home/assets' } }
如果配置了创建多个单独的’chunk‘,则应该使用占位符来确保每个文件具有唯一的名称。
entry:{ app:'./src/app.js', vendor:'./src/search.js' }. output:{ filename:'[name].js', path:__dirname+'/dist' }
Vuex使用单一状态树,是的,用一个对象就包含全部应用层级的状态,至此他便作为‘唯一数据源(SSOT)’の3而存在,这就意味着,每个应用将仅仅包含一个store实例,单一状态树让我们直接定义任何的状态片段,在调试的过程中也能轻松的取得当前的应用状态的快照
单状态树与模块化并不冲突。
那么我们如何展示状态呢?由于Vuex的状态存储是响应式的,从store实例中读取状态最简单的就是在计算属性中返回的每个状态
//创建一个Counter组件 const Counter = { template:`< div>{{count}}< /div>`, computed:{ count(){ return store.state.count } } }
每当store.state.count变化的时候,都会重新求取计算属性,并且触发更新相关联的DOM
然而,这种模式导致组件依赖全局状态单例。在模块化构建系统中,在每个需要使用state的组件中需要频繁的导入,并且在测试组件时需要模拟状态。
Vuex通过store选项,提供一种机制将状态从跟组件‘注入到’每个子组件中(需要调用Vue.use(Vuex));
el:'#app', store, component:{Counter}, template:` < div> < Counter>< /Counter> < /div>`
通过在跟实例中注册store选项,改store实例会注入到跟组件下的所有子组件中,且子组件能通过this.$store访问到,让我们更新一下Counter的实现:
const Counter = { template:`< div>{{count}}< /div>`, computed:{ count(){ return this.$store.count } } }
当一个组件需要获取多个状态时,将这些状态都声明为就算属性会有些重复和冗余,为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性
import {mapState} from 'Vuex' export default{ computed:mapState(['count']) }
mapState函数返回一个对象,我们如何将他与局部计算属性混合使用呢?通常需要使用工具函数将对象合并为一个,以使我们可以将最终的对象传给computed属性,但是自从有了对象展开运算符,我们可以极大的简化写法
computed:{ localComputed(){}, ...mapState(['state']) }
插件是webpack的支柱功能,webpack自身也是构建与,你在webpack配置中用到的相同的插件系统之上
插件的目的在于解决loader无法实现的其他事
由于插件可以携带参数/选项,你必须在webpack配置中,向plugins属性传入new 实例。
根据你的webpack用法,这里有许多方式使用插件
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const path = require('path') const config = { entry:'./file.js', output:{ filename:'bundle.js', path:path.resolve(__dirname,'dist') }, mudole:{ laoder:[ { test:/\.(js|jsx)$/, use:'babel-laoder' } ] }, plugins:[ new webpack.optimize.UglifyJsPlugin(); new HtmlWebpackPlugin({template:'./src/idnex.html'}) ] } module.exports = config
有时候我们需要从store中的state中排出一些状态,例如对列表进行过滤并计算:
computed:{ doneTodoCount(){ return this.$store.todos.filter(todo => todo.done).length } }
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入他--无论哪种方式都不是还很理想。
Vuex允许我们在store中定义‘getter’,就像计算属性一样,getter的返回值会根据他的依赖被缓存起来,且只有当它的依赖发生变化了改变才被重新计算。
Getter接受state作为其第一个参数
const store = new Vuex.Store({ state:{ todos:[ {id:1,text:'...',done:true }, {id:2,text:'...',done:false} ] }, getter:{ doneTodos:state =>{ return state.todos.filter(todo => todo.done) } } })
Getter会暴露为store.getter对象:
store.getter.doneTodos //{id:1,text:'...',done:true}
Getter也可以接受其他的getter作为第二参数:
doneTodosCount:(state,doneTodos)=>{ return doneTodos.length } store.getter.doneTodosCount //1我们可以很容易地在任何组件中使用它:computed{ doneTodosCount(){ return this.$store.getter.doneTodosCount } }你也可以通过让getter返回一个函数,来实现给getter传参,在你对store里的数组进行查询时非常有用
getter:{ getTodoById:(state)=>(id)=>{ return state.todos.find(todo=>todo.id==id) } } store.getter.getTodoById(2) //{id:2,text:'...',done:false}mapGetter辅助函数
mapGetter辅助函数仅仅是将store的getter映射到局部计算属性:
import {mapGetter} from 'Vuex' expoer default({ data(){ retuen{ count:0 } }, computed:{ ...mapGetter([ 'doneTOdosCount', anotherGetter ]) } })如果你想将一个getter属性另取一个名字,使用对象形式:
mapGetter([ doneCount:''doneTodosCount' ])
Promise是一种异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大。它有社区最早提出和实现,es6将其写进了语言标准,统一了方法,原生提供了Promise对象
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上说,Promise是一个对象,他可以获取异步操作的消息,Promise提供API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点
对象的状态不受外界影响,Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果可以决定当前是哪一种状态
一旦状态改变,就不会改变,任何时候都可以得到这个结果
有了Promise对象,就可以将异步操作同步操作的流程表达出来,避免层层嵌套回调函数,此外,Promise对象提供统一的接口,使得控制异步操作更加容易
ES6规定,Promise对象是个构造函数,用来生成Promise实例
下面代码创造了一个Promise实例
var promise=new Promise(function(resolve,reject){ if(){ resolve(value) }else{ reject(error) } })
Promise构造函数接受一个函数作为参数,该参数的两个参数分别是reject和resolve,他们是两个函数,由JavaScript引擎提供,不用自己部署。
resolve的作用是,将Promise对象的状态从‘未完成’变为‘成功’,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去,reject函数的作用是,将Promise对象的状态编程‘失败’,
Promise实例生成以后,可以用then方法分别指定resolved和rejected状态的回调函数
promise.then(function(value){ //success },function(value){ //failure })
then方法可以接受两个回调函数作为参数,第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象状态变为rejected时调用,其中第二个参数是可选的,不一定提供。
下面是一个Promise简单的例子
function timeout(ms){ return new Promise((resolve,reject)=>{ setTimeout(resolve,ms,'done') }) } timeout(100).then((value)={ console.log(value) })
上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生结果,过了指定的时间后。Promise实例的状变为resolved,就会触发then方法绑定的回调函数
Promise新建后就会立即执行
let promise = new Promise(function(resolve,reject){ console.log("Promise") resolve() }) promise.then((value)=>{ console.log('resolved') }) console.log('hello world')
Promise新建后立即执行,所以首先输出Promise,然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出
下面是异步加载图片的例子
function loadImageAsync(url){ return new Promise(function(resolve,reject){ var image=new Image(); image.onLoad=function(){ resolve(image) } image.onerror=function(){ reject(new Error('Could not load image at' + url)) } image.src=url }) }
上面代码中,使用Promise包装了一个图片加载的异步操作,如果加载成功,就调用resolve方法,否则就调用reject方法
下面是一个用Promise对象实现的ajax操作
var getJson=function(url){ return new Promise(function(resolve,reject){ var xml=new XMLHttpRequest(); xml.open('Get',url) xml.onreadystatechange=function{ if(xml.readystate==4&&xml.status==200){ resolve(xml.response) }else{ reject(new Error(xml.statusText)) } } xml.send(); }) } getJson('/posts.json').then(function(value){ console.log('Content:'+value) },function(){ console.log('出错了',error) })
上面代码中,getJson是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的Http请求,并且返回一个Promise对象,在getJson内部,resolve和reject函数调用时,都带有参数
如果调用resolve和reject函数时带有参数,那么他们的参数会被传递给回调函数,reject函数参数通常是Error对象的实例,表示抛出错误,resolve函数的参数除了正常的值以外,还有可能是另一个Promise实例。比如像下面这样
var p1=new Promise(function(resolve,reject){}) var p2 = new Promise(function(resolve,reject){ resolve(p1) })
上面代码中,p1和p2都是Promise的实例,但是P2的resolve方法将P1作为参数,即一个异步操作返回的还是异步操作。
注意:这时P1的状态就会转递给p2
var p1 = new Promise(function(resolve,reject){ setTimeout(()=>reject(new Error('fail'),3000)) }) var p2 = new Promise(function(resolve,reject){ setTimeout(()=>resolve(p1),1000) }) p2.then(result=>console.log(result)).catch(error=>console.log(error))
上面代码中,p1是一个Promise,3秒之后变成rejected,p2的状态在1秒之后改变,resolve方法返回的是p1.由于p2返回的是另一个Promise,导致P2自己的状态无效了,由p1的状态决定p2的状态,所以后面的then语句都变成了针对后者(p1),又过了2秒,p1变成rejected,导致触发catch方法指定的回调函数
注意:调用resolve或reject并不会终结Promise的参数函数执行
new Promise(function(resolve,reject){ resolve(1) console.log(2) }).then(function(r){ console.log(r) }) //2 //1
Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的,他的作用是为Promise实例添加状态改变时的回调函数,前面说过,then的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
then方法返回的是一个新的Promise实例(注意,不是原来的那个Promise实例)。因此可以采用链式写法,即then后面可以再调用另一个then方法
getJson('/posts.json').then(function(json){ return json.post }).then(function(post){ //... })
上面代码使用then方法,以此指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个参数
Promise.prototype.catch方法是.then(null,rejection)的别名,用于指定发生错误时的回调函数。
getJson('/posts.json').then(function(posts){ //... }).catch(function(err){ console.log(err) })
上面代码中,getJson方法返回一个Promise对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数,如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误,另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
p.then((val)=>console.log('true',val)) .catch((err)=>console.log('rejected',err)) 等同于 p.then((val)=>console.log('true',val)).then(null,(err)=>console.log('rejected',err))
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也就是说错误总会被下一个catch捕获
getJson('/post/1.json').then(function(post){ return getJson(post.commentUrl) }).then(function(comment){ // }).catch(function(){ // })
上面代码中,一共有三个Promise对象,他们之中任何一个抛出的错误,都会被最后一个catch捕获
Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1,p2,p3])
上面代码中,Promise.all方法接受一个数组作为参数,p1 p2 p3都是Promise实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理
P的状态由P1 P2 P3决定,分为两种情况
//生成一个Promise对象的数组 var promises=[2,3,5,7,11,13].map(function(id){ return getJson('/post/'+id+'.json') }) Promise.all(promises).then(function(posts){ //... }).catch(function(reason){ //... })
上面代码中,promises 是包含6个Promise实例的数组,只有这6个实例状态变为fulfilled,或者其中一个变为rejected,才会调用Promise.all方法后面的回调函数
下面是另一个例子
const databasePromise=connectDatabase() const booksPromise=databasePromise.then(findAllbooks); const userPromise = databasePromise.then(getCurrentUser) Promise.all([booksPromise,userPromise]).then(([books,user])=>pickTopRecommentations(books,user))
上面代码中,两个异步操作,只有等到他们的结果都返回了,才会触发pickTopRecommentations回调函数
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例
var p=Promise.race([p1,p2,p3])
上面代码中,只要p1 p2 p3中有一个实例率先改变状态,p的状态就跟着改变,那个率先改变的Promise实例的返回值,就传递给p的回调函数
Promise.race方法的参数和Promise.all方法参数一样,如果不是Promise实例,就会先调用下面的讲到的Promise.resolve方法,将参数转化为Promise实例,进一步处理
下面例子,如果指定时间内没有获取结果,就将Promise的状态改变成reject否则变为resolve
const p = Promise.race([fetch('/resource-that-take-while'),new Promise(function(resolve,reject){ setTimeout(()=>{ reject(new Error('request timeout'),500) }) })]) p.then(response=>console.log(response)) p.catch(error=>console.log(error))
上面代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数
有时候需要将现有的对象转化为Promise对象,Promise.resolve方法起到这个作用
var JSPromise = Promise.resolve($.ajax('/whatever.json'))
上面代码转化成Promise对象
Promise.resolve('foo') //等价于 new Promise(resolve=>resolve('foo'))
Promise.resolve方法的参数分为四种
Promise.reject(reason)方法也返回一个新的Promise实例,该状态为rejected
我们可以将图片加载写成一个Promise,一旦加载完成,Promise的状态就会发生变化。
const PreloadImage=function(url){ return new Promise(function(resolve,reject){ var image = new Image(); image.onload=function(){ resolve(image) } image.onError=function(){ console.log('图片路径没找到') } image.src=url }) }
实际开发中会遇到一种情况,不知道或者不想区分,函数是同步还是异步操作,但是想用Promise来处理它,因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法来处理f抛出的错误,一般就会采用下面的写法
Promise.resolve().then(f)
上面写法有一个缺点,就是如果f是同步函数,那么他会在本轮事件循环的末尾执行
如果函数f是同步的,但是用Promise包装以后变成异步的了
那么有没有一种方法,让同步函数还是同步的,异步函数执行异步,并且让他们具有统一的API呢,回答是可以的,并且还有两种方法,第一种是用async函数来表示
const f=()=>console.log('hello world') (async ()=>f())() console.log('next')
上面代码中,第二行是一个立即执行的自执行函数,会立即执行里面的async函数,因此如果f是同步的,就会得到是同步的结果,如果是异步的就可以用then指定下一步,例如
(async ()=>f())().then(...).catch(...)
第二种使用Promise()
const f=()=>console.log('hello world') ( ()=>new Promise(resolve=>resolve(f())) )()
上面代码也是使用立即执行匿名函数,执行new Promise(),这就情况下,同步函数也是同步执行的,
鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try()方法代替上面的写法
const f=()=>console.log('hello world') Promise.try(f) console.log('nihao')
Promise.try(database.users.get({id:userId})) .then(...) .catch(...)
这样try就可以执行同步操作,then进行异步操作
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.