Coder Social home page Coder Social logo

lihuiyang.github.io's People

Contributors

lihuiyang99 avatar

Stargazers

 avatar

Watchers

 avatar

lihuiyang.github.io's Issues

JS-四种检测方式

1、typeof

用来检测数据类型的运算符,使用typeof检测数据类型,首先返回一个字符串,其次字符串中包含了对应的数据类型,例如:

局限性:

  • 不细分是数组还是正则,还是对象类型的其他值,使用typeof检测数据类型,对于对象类型的,返回object

2、instanceof

检测某一个实例是否属于某个类

局限性:

  • 1、不能用来检测和处理自变量方式创建出来的基本数据类型的值

  • 2、instanceof特性:只要在当前实例的原型链上我们用其检测的结果都是true

  • 3、在类的原型继承中,最后的结果未必准确

  • 对于基本类型来说,自变量创建出来的结果和实例创建出来的结果是有一定区别的,从严格意义上来讲,只有实例创建出来的才是标准的对象数据类型,对于字面量的方式创建出来的结果是基本的数据类型,不是严谨的实例,但由于js的松散型,导致 了可以使用Number.prototype上提供的方法

3、constructor

构造函数 作用与instanceof 可以处理基本数据类型

  • constructor检测object和instanceof不一样,一般情况下是检测不了的

局限性:

  • 我们可以把类的原型重写,在写的过程中很有可能把之前的constructor给覆盖了,这样检测出来结果就是不准确的。

  • 对于特殊的数据类型null和undefined他们的所属类Null和Undefined,浏览器吧两个类保护起来了,U允许在外面访问使用

4、Object.prototype.ToString.call

最准确最常用的方式

  • 首先获取object原型上的tostring方法,让方法执行,并且改变this关键字的指向
  • tostring理解:某些tostring不仅仅是转化为字符串
  • Number上的tostring方法是转化为二进制八进制十进制
  • 对于Number 正则 布尔 data function原型上的tostring是转化为字符串

Vue1.0基础

Vue

Vue.js 是用于构建交互式的 Web 界面的库。它提供了 MVVM 数据绑定和一个可组合的组件系统,具有简单、灵活的 API。从技术上讲, Vue.js 集中在 MVVM 模式上的视图模型层,并通过双向数据绑定连接视图和模型。实际的 DOM 操作和输出格式被抽象出来成指令和过滤器。相比其它的 MVVM 框架,Vue.js 更容易上手。

  • 1、 viewModule view model
     创建一个Vue的实例  vm=$scope  vm就是当前的viewModule
        var vm=new Vue({
           el:'#app',选择作用的范围
           data:{  绑定数据  是数据层
            name:1
           }
        })
  • 2、解析html标签使用三个{{{}}}
  • 3、{{}}支持三元表达式
  • 4、{{*name}}事件只绑定一次
  • 5、在input中通过v-model实现双向数据绑定
  • 6、vm.$el=document.getelementById('app');
  • 7、已经绑定的元素是不能改变的
  • 8、vm.$data===message;
  • 9、vue的生命周期
    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');
  • 10、computed 计算属性 有些数据就是根据我们提供的数据计算出来的
    computed:{bb:function(){return this.name+'hello'}}
  • 11、set方法 get方法 当给B设置属性时会立即调用set get方法
     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>
  • 18、track-by='$index';数组中有重复的用track-by
  • 19、$key 是当前对象的键
  • 20、v-src === :(冒号)
  • 21、v-on:click===@click
  • 22、v-bind 绑定动态数据 简写:
  • 23、methods中的函数的参数
    < div @click='dosome(1,$event)'>
        methods:{
        dosome:function(a,ev){} }< /div>
  • 24、修饰符
    • 1)self 只在自己的身上有作用 parent的儿子点击会触发父亲,阻止一下
    • 2)stop 阻止事件的冒泡传播
    • 3)prevent 阻止默认样式
  • 25、keyCode
    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>
  • 32、Vue.filter 中read and write方法 当我们input标签写入内容的时候 会默认调用write方法
    表单
  • 33、v-model中的数据绑定的是value值 取value值时 对应的checkbox 必须是数组
  • 34、 v-bind:value 动态邦定值
    组件 把html取出来当模板 用数据渲染好,将我们的组件扔到页面
  • 35、extend 用extend方法创建出组件,在实例创建前
  • 36、template 就是组件的模板 后可以加'#id'
  • 37、Vue.component('组件的名字',创建的组件)

语法糖

我们想传递数据 先从属性 传递到props 在从props取出来绑定到组件里

组件可以嵌套
先创建子组件 子组件创建后查到父组件里

  • 38、props 对属性进行验证
  • 39、type:[String] 只能传递字符串
  • 40、default 默认值 可以不符合type类型
  • 41、twoWay:true 要求必须是双向的 sync配合使用
  • 42、validator true 不写的话没有校验器
  • 43、coerce 传进来的值 强制进行改变 在验证之前

看组件上的属性 如果设置的属性前面有:说明是动态的 去vm上找到对应的数据
传到父级的组件上 找到组件中的props 对应的变量 先默认走coerce 如果没有coerce 并且数据没有传递走default 在一次验证

  • 44、slot取出组件里的内容 差入到模板 可以改变组件里的循序
  • 45、components 创建动态的组件
    举例
    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>'
             },
    }

Three.js入门学习7-射线拾取

射线拾取

在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

格式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;可以更改材质对象的透明度

vue移动端插件vux安装流程

Vue中使用vux的配置,分为两种情况:

一、根据vux文档直接安装,无需手动配置
  • npm install vue-cli -g // 如果还没安装
  • vue init airyland/vux2 my-project // 创建名为 my-project 的模板
  • cd my-project // 进入项目
  • npm install --registry=https://registry.npm.taobao.org // 开始安装
  • npm run dev // 运行项目
二、想在已创建的Vue工程里引入vue组件
  • <1>. 在项目里安装vux
    • npm install vux --save
  • <2>. 安装vux-loader (这个vux文档似乎没介绍,当初没安装结果报了一堆错误)
    • npm install vux-loader --save-dev
  • <3>. 安装less-loader (这个是用以正确编译less源码,否则会出现 ' Cannot GET / ')
    • npm install less less-loader --save-dev
  • <4>. 安装yaml-loader (以正确进行语言文件读取, 我没安装似乎也没报错)
    • npm install yaml-loader --save-dev
  • <5>. 在build/webpack.base.conf.js 文件进行配置
    • const vuxLoader = require('vux-loader')
    • const webpackConfig = originalConfig

// 原来的 module.exports 代码赋值给变量 webpackConfig,

//即将原来的module.exports 改为 const webpackConfig
module.exports = vuxLoader.merge(webpackConfig, { plugins: ['vux-ui'] })

Three.js入门学习4-三维空间的观察

三维空间的观察

一、单反、双反都是相机

1、认识相机

在Three中相机的表示是THREE.camera,他是相机的抽象类,其子类有两种相机,分别是正投影相机THREE.OrthographicCamera和透视相机THBREE.PerspectiveCamera,

2、两者的区别

正投影和透视投影的区别是:透视投影有个基本点,就是远处的事物比近处的物体小,

二、正投影相机

我们来介绍正投影相机,正投影的构造数如下所示,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)

3、透视投影相机

透视投影相机更加符合我们视觉的投影,当透视一个女人时,就是因为远近高低各不同,女人才显得美丽,正因为,透视相机有这么大的魅力,所以在各种应用中运用广泛,


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

JS-this用法总结

this用法

函数中的this指向和当前函数在那里定义或执行的没有任何的关系
分析this指向的规律如下:

[非严格模式下]

  • 1、自执行函数中的this永远是window
    案例一
     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();
   };
  • 3、方法执行,看方法名前面是否有“.” 有的话 “.”前面是谁this就是谁,没有的话this就是window
    案例一
   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__
  • 4、在构造函数中,函数体的this.xxx=xxx中的this是当前类的一个实例
    案例一
   function Fn(){
     this.x=100;//this是window
     this.getX=function(){
     console.log(this);//this是f 因为getX在执行的时候,“.”前面是F,所以this是f
     }
   }
  • 5、使用call/apply来改变this的指向(一旦遇到call/apply上述的四条就没用了)

严格模式

"use strict";//告诉浏览器我们接下来编写的JS代码采用严格模式

  • 1、自执行函数中的this永远是undefined
    案例一
    var obj={
     fn:(function(){
             //this 是undefined
     return function(){
             //this 是obj}
     })(0)
   };
   obj.fn();
  • 3、执行方法,看方法名前面是否有“.”,有的话“.”前面是谁this就是谁,没有this的话就是undefined
    案例一
     var obj={fn:fn};
   function fn(){}
  fn();//this 是undefined
   obj.fn();//this 是obj
    我们发现严格模式下的this相对于非严格模式下的this主要区别在于:对于JS代码这种没有写执行主体的情况下,非严格模式下默认的都是window执行的,所以this指向的是window;但是在严格的模式下,没有写就是没有执行主体,this指向的是undefined

vue.js服务器端渲染(了解)

Vue.js服务器端渲染简述

(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+
什么是服务器端渲染(ssr)

vue.js是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出Vue组件,进行生成DOM和操作DOM,然而,也可以在同一个组件渲染为服务器端的HTML字符串,将他们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

服务器渲染的vue.js应用程序也可以被认为是同构或者通用,因为引应用程序的大部分代码都可以在 服务器端和浏览器端上进行。

为什么使用服务器端渲染(ssr)

与传统的SPA相比,服务器端渲染的优势主要在于:

  • 更好的SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

    请注意,截止目前,Google和Bing可以很好对同步JS应用程序进行索引,在这里,同步是关键,如果你的应用程序初始展示loading菊花图,然后通过Ajax获取内容,抓取工具并不会等待异步完成后再行抓取页面内容,也就是说SEO对你的站点至关重要,而你的页面优势异步获取内容,则你可能需要服务器端渲染解决此类问题

  • 更快的内容到达时间(time-to-content),特别是对于缓慢的网络情况或者运行缓慢的设备,无需等待所有的JS都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快的看到完整渲染的页面,通常可以产生更好的用户体验,并且对于那些 内容到达时间(time-to-content)与转化率直接相关的应用程序而言,服务器渲染(SSR)至关重要

使用服务器端渲染时还需要一些权衡之处

  • 开发条件有限。浏览器特定的代码,只能在某些生命周期钩子函数中使用;一些外部扩展库可能需要特殊处理,才能在服务器端渲染应用程序中运行
  • 涉及构建和部署的更多要求,与可能部署在任何静态文件服务器上的完全静态单页面应用程序不同,服务器渲染应用程序,需要处于node server运行环境
  • 更多的服务器端负载,在node中渲染完整的应用程序,显然会比仅仅提供静态文件的server更加大量占用CPU资源,因此如果你预料在高流量环境下,请准备响应的服务器负载,并明智的采用缓存策略

在对你的应用程序使用服务器端渲染之前,你应该问的第一个问题是,是否真的需要他,这主要取决于内容到达时间对应用程序的重要程度。例如,如果你正在构建一个内部仪表盘,初始加载时间的额外几百毫秒并不重要,这种情况下去使用服务器端渲染将是一个小题大作,然而,内容到达时间要求是一个决对关键的指标,在这种情况下,服务器端渲染可以帮助你实现最佳的初始加载性能

服务器端VS域渲染

如果你调研服务器端渲染只是用来改善少数营销页面(例如:/,/about, /contant等)的SEO,那么你可能需要预渲染,无需使用web服务器实时动态编译HTML,而是使用预渲染方式,在构建时间简单的生成对特定路由的静态HTML文件,优点是设置预渲染更简单,并可以将你的前端作为一个完全的静态的站点。

如果你用的是webpack,你可以使用prerender-spa-plugin轻松的添加预渲染,他已经被Vue应用程序广发测试

安装

    npm install vue vue-server-renderer --save
注意
  • 推荐使用nodeJS 6+
  • vue-serer-renderer 和vue的版本一致
  • vue-server-renderer依赖NodeJS原生模块,因此只能在NodeJS中使用

渲染一个Vue实例

    //第一步:创建一个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将是注入应用程序内容的完整页面
    })

ES6--async学习

async函数

  • 含义
  • 基本用法
  • 语法
  • async函数的实现原理
  • 与其他异步函数处理方法的比较
  • 实例:按顺序完成异步操作
  • 异步遍历器

含义

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函数改进,体现在一下四点。

  • (1)内置执行器

Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器,也就是说 async函数的执行,与普通函数一样,

asyncReadFile

上面的代码调用了asyncReadFile函数,然后就会自动执行,输出最后结果,这完全不像Generator函数,需要调用next方法,或者co模块,才真正执行,才得到最后结果

  • (2)更好的语意

async 和await,比起星号和yield,语意更清楚了,async表示函数里有异步操作,await表示紧跟后面的表达式需要等待结果,

  • (3)更广的实用性

co模块约定,yield,命令后面只能是Thunk函数或者Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值,字符串,布尔值,但是这等同于同步操作)

  • (4)返回值是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 函数的语法规则总体上比较简单,难点是错误处理机制

返回Promise对象

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)
)  //出错了

Promise对象的状态变化

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命令

正常情况下,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函数的实现原理,就是将Generator函数和自执行函数执行器,包装在一个函数里

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

webpack基础2-entry

入口起点

单个入口(简写)语法

用法: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配置,可重用并且可以与其他配置组合使用,这是一种流行的技术,用于将关注点从环境,构建目标,运行时中分离,然后使用专门的工具将他们合并。

常见场景

分离 应用程序[app]和第三方库[vendor]入口

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个独立的依赖图

  • 使用CommonsChunkPlugin为每个页面间的应用程序共享代码创建bundle,由于入口起点增多,多页面应用能够复用入口起点之间的大量代码/模块,从而可以极大地从这些技术中收益。

vue数据传递 - props、$emit、vue-router中 query、params、eventBus

1、父组件传递给子组件

  • 引入组件
        < 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>
 

2、路由专递信息(query)

  • 定义
    < 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

3、路由专递信息(params)

  • 定义
    { 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

4、全局专递信息

通过eventBus传递数据

- 使用前可以在全局定义一个eventBus,window.eventBus = new Vue();
- 在需要传递参数的组件中,定义一个emit发送需要传递的值,键名可以自己定义(可以为对象),eventBus.$emit('eventBusName',id)
- 在需要接收参数的组件中,用on接收改值(或对象) eventBus.$on('eventBusName',function(value){value})
- 最后记住要在beforeDetroy()中关闭这个eventBus   eventBus.$off('eventBusName')

状态管理器vuex

搭建node中间层

express搭建node中间层

express介绍(摘自官网)

express框架是nodeJS出来不久就诞生的webserver构建框架,

  • web应用,是一个基于nodeJS平台的及简、灵活的web应用开发框架,他提供一系列强大的特性,帮助你创建各种web和移动的设备应用
  • API ,丰富的HTTP快捷方法和任意排列组合的Connect中间件,让你创建健壮、友好的API变的既快速又简单
  • 性能 Express不对NodeJS已具有的特性进行了二次抽象,我们只是在他之上扩展了web应用所需的基本功能

前后端分离(参考淘宝)

什么是前后台分离

大家一致认同的前后端分离的例子就是SPA,所有用到的展示数据都是后端通过异步接口方法提供的,前端只管展现。

  • 从某种意义上来说,SPA确实做到了前后端分离,但是这种方式存在两个问题:
    • WEB服务中,SPA类站的比例很少,很多场景下还有同步/异步+异步混合的模式,SPA不能作为一种通用的解决方案
    • 现阶段的SPA开发模式,接口通常是按照展现逻辑来提供的,有时候为了提高效率,后端会帮我们处理一些展现逻辑,这就意味着后端还是涉足View层的工作,不是真正的前后端分离。

SPA式前后端分离,是从屋里层做区分(认为只要是客户端就是前端,服务端就是后端),这种分发已经无法满足我们前后端分离的需求,我们认为从职责上划分才能满足我们的场景:

  • 前端:负责View和View-Model层
  • 后端:只负责Model层,业务处理/数据等

为什么要前后端分离

现在的开发模式的适合场景

不同的开发模式各有各的适用场景,没有哪一种完全取代另一种

  • 比如以后端为主的MVC,做一些同步展现的业务效率很高,但是遇到同步异步结合的页面,与后端开发沟通起来会比较麻烦
  • Ajax为主的SPA型开发模式,比较适合开发APP类的场景,但是只适合做APP,因为SEO等问题不好解决,对于很多类型的系统,这种开发方式也过重
前后端职责不清楚

在业务逻辑复杂的系统里,我们最怕维护前后端混杂在一起的代码,因为没有约束,M_V_C每一层都一层都可能出现别的层的代码,日积月累,完全没有维护性可言,

虽然前后端分离没有办法解决这种情况,但是可以大大缓解,因为从物理层上保证你不可能这么做

开发效率问题

电商网站web基本上都是基于MVC框架,框架决定了前端只能依赖后端,所以我们的开发模式依然是,前端写好静态Demo,后端翻一成VM模板,这种模式问题就不说了,被吐槽了很久

直接基于后端黄精开发也很痛苦,配置安装使用都很麻烦

前端发挥的局限性

性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作才能碰撞出火花,但是由于后端框架的限制,我们很难使用Bigpipe等技术方案来优化性能

怎么做前后端分离
  • 前端:负责View和MV层
  • 后端:负责model层,业务处理/数据等

基于NodeJS全栈式开发

如果想实现上图的分层,就必然需要一种web服务帮我们实现前后端的事情,

title
这张图看起来简单而且很好理解,单没尝试过,会有很多疑问

  • SPA模型中,后端已供所需的数据接口,View前端已经可以控制,为什么要多加Node.js这一层?
  • 多加这一层性能怎么样
  • 多加这一层,前端的工作量是不是增加了
  • 多加这一层就多一层风险,怎么破
  • NodeJs什么都做,为什么还要JAVA
为什么要增加一层NodeJs

现在主要的MCV/MVVM模式进行开发,这种模式严重阻碍了前端的开发效率,也让后端不能专注于业务开发

解决方案是让前端能控制Controller层,但如果在现有的技术体系下很难做到,因为不可能让所有的前端学JAVA,安装后端的开发环境,写VM

NodeJS就能很好的解决这个问题,我们无需学习一门新的语言,就能做到以前开发帮我们做的事情,一切都显得那么自然

性能问题
分层就涉及每层之间的通讯,肯定会有一定的性能损耗,但是合理的分层让职责清晰,也方便协作,会大大提高开发效率,分层带来的损失,一定能在其他方面的收益弥补回来,另外,一旦决定分层,我们可以通过优化通讯方式,通讯协议、尽可能的把消耗降到最低。

举个例子
    淘宝宝贝详情页静态化之后,还是有不少需要实时获取的信息,比如说物流、促销等,因为这些信息在不同的业务系统中,所以需要前端发送5、6个异步请求来回填这些内容,有了NodeJS之后,前端可以在NodeJS中去代理这5个异步请求,还很容易做bigpipe,这块的优化让整个渲染效率提升很多,可能在PC端5、6个异步请求也没什么,但是在移动端,在客户手机上建立一个HTTP请求开销很大,有了这个优化,性能一下子提升好几倍
前端的工作量是不是增加了

相对于页面制作,工作量肯定会增加了,但是当前模式下有联调、沟通环节,这个过程非常花时间,也容易出bug,还很难维护

所以,虽然工作量会增加一点,但是总体的开发效率会提升很多

Node层带来的风险怎么控制?

随着node大规模使用,系统运维安全部门的同学也会加入到基础建设中,他们会帮助我们去完善各个环节可能出现的问题,保障系统的稳定性

淘宝基于Node的前后端分离

title

  • 最上端是服务器端,就是我们常说的后端,后端对于我们来说,就是一个接口的集合,服务端提供各式各样的接口供我们使用,因为有Node层,也不用局限是什么形式的服务,对于后端来说,他们只关心业务代码的接口实现
  • 服务端下面是Node应用
  • Node应用中有一层Model Proxy与服务端进行通信,这一层主要目前是抹平我们对不同接口的调用,封装一些view层需要的model。
  • Node层需要使用什么框架有开发着自己决定,不过推荐使用 express+xTemple的组合
  • 怎么用Node大家自己决定,但是令人兴奋的是,我们终于可以使用Node轻松实现我们想要输出的方式:Json/jsonp/restful/html/Bigpipe/同步/异步 ,想怎么整就怎么整,完全根据你的场景决定
  • 浏览器在我们这个架构中没有变化,也不希望你引入Node改变你以前在浏览器中的开发认知
  • 引入node,只要把本该就前端控制的部分交友前端控制

我们还需要 做什么

  • 把node的开发流程集成到淘宝现有的SCM流程中
  • 基础设施建设,比如session、logger等通用模块
  • 线上成功案例
  • 大家对Node前后端分离概念的认知
  • 安全
  • 性能

总结

基于node的全栈式开发模式淘宝是一个很好的例子,基于开源,我们大可充分借鉴成功的经验

ps:内容参考来源:https://blog.csdn.net/u011413061/article/details/50294263

Three.js入门学习3-让场景动起来

threeJS让场景动起来

一、场景动起来

1、场景动起来的方法

  • 物体在坐标系里面移动,摄像头不懂
  • 让摄像头在坐标系里面移动,物体不动

渲染循环

物体运动还有一个关键点,就是要渲染物体运动的每一个过程,让他显示给观众,渲染的时候,我们调用的是渲染器的render()函数,代码如下:

 renderer.render(scene,camera)

如果我们改变了物体的位置或者颜色之类的属性,就必须重新调用render()函数,才能够将新的场景绘制到浏览器中,不然浏览器是不会自动刷新场景的

如果不断地改变物体的颜色,那么就要不断地绘制新的场景,所以我们最好的方式是让画面执行一个循环,不断地调用render来重绘,这个循环就是渲染循环,在游戏中也叫作游戏循环

为了实现循环,我们需要JavaScript的一个特殊函数,这个函数是requestAnimationFrame

调用requestAnimationFrame函数,传递一个callback参数,则在下一个动画帧时,回调用callback这个函数

于是游戏循环会这样写:

 function animate(){
   render()
   requestAnimationFrame(animate)
 }

这样就会不断地执行animate这个函数,也就是不断地执行render函数,在执行中不断改变物体的位置和颜色

2、改变相机的位置,让物体移动

 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函数,这样就能够统计出这段代码执行的平衡帧数了

5、性能测试实例

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

以上代码一共几个步骤:

  • 1、new一个stata对象,
  stat = new Stats()
  • 2、将这个对象加入到html网页中,代码如下
  stats.domElement.style.position = 'absolute'
  ...
  
  • 3、调用stats.update()函数来统计时间和帧数
stats.update()

6、使用动画引擎Tween.js来创建动画

上面介绍了通过移动相机和移动物体来产生动画的效果,使用的方法是在渲染循环里去移动相机或者物体的位置,如果动画稍微复杂一些,这种方式实现起来就比较麻烦一些了

为了使编程更加容易一点,我们可以使用使用动画引擎来实现动画效果,和THREE紧密相连的动画引擎是Tween

快速构建动画

  • 1、需要引擎文件JS
  • 2、构建Tween对象,对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来创建不规则动画

Three.js入门学习1

threeJs入门

ThreeJs嵌入

  • ThreeJs怎么嵌入网页中,让他运行起来?很简单,只要html文件引入threejs文件就可以
 < script src="https://.../three.js">< /script>

这就表示引入threeJs文件,这个文件会自己初始化threejs的一些变量和环境,

  • 为了验证threejs确实启动,我们用chore浏览器打开上面的网页,浏览器里面什么都没有,这时按F12,打开调试工具,在console下输入threeJS输入 THREE.REVISION命令,得到73,这表示现在我们使用的three版本是73,这样three就运行起来了

第一个框架

为了方便实验,我们提供了两个简单的框架供你使用,你只要改变其中的一些代码或者参数,就能够得到实验的结果,第一个框架的效果是显示一个绿色的正方体

  • 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)
  • 参数含义
    • width:立方体X轴的长度
    • height:立方体y轴的长度
    • depth:立方体z轴的长度,即深度

这三个参数就可以能确定一个立方体了。剩下的几个参数后期了解

  • 5、渲染

终于到了最后一步了,这步做完就完成了,

渲染应该使用渲染器,结合相机和场景来得到结果画像,实现这个功能的函数是renderer.render(scene,camera)

渲染函数的原型如下:render(scene,camera,renderTarget,forceClear)

  • 各个参数的含义是:

    • scene:前面定义的场景
    • 前面定义的相机
    • renderTarget:渲染目标,默认渲染到前面定义的render变量中
    • forceClear:每次绘制之前都将画布的内容给清除,即使自动清除标志autoClear为false,也会清除。
  • 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的场景是一个物体的容器,开发者可以将需要的角色放入场景中,例如苹果、葡萄。同时,角色自身也管理着其中的位置。
    相机的作用就是面对场景,在场景中取一个合适的景。把他拍下来

    渲染器的作用就是将相机中拍下来的图片,放到浏览器中去显示

ES6--Generator生成器学习(1)

Generator(生成器) 函数的语法

  • 1、简介
  • 2、next方法的参数
  • 3、for...of循环
  • 4、Generator.prototype.throw()
  • 5、Generator.prototype.return()
  • 6、next() throw() return()
  • 7、yield* 表达式
  • 8、作为对象属性的Generator函数
  • 9、Generator函数中的this
  • 10、含义
  • 11、应用

1、简介

基本概念

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关键字与函数名之间的星号写在那个位置,这导致上面写法都能通过

yield表达式

由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个状态,所以其实提供了一种可以暂停执行的函数,yield表达式就是暂停标志

遍历器对象next方法的运行逻辑如下

  • 1、遇到yield表达式。就会暂定执行后面的操作,并将紧跟在yield后面的那个表达式,作为返回值的对象value属性值
  • 2、下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
  • 3、如果没有yield表达式就会一直运行到函数结束,直到return语句为止,并将return语句后面表达式的值,作为返回值对象value属性值
  • 4、如果没有return语句,返回undefined

需要注意的是: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表达式如果在另一个表达式之中,必须放在圆括号内

next方法参数

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 循环

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.prototype.throw()

Generator函数返回可遍历的对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获

var g=function* (){
    try{
        yield;
    }catch(e){
        console.log('内部捕获',e)
    }
}

Generator.prototype.return()

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() retuen() 的共同点

网友提出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

yield表达式

如果在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函数

如果一个对象的属性是Generator函数,可以简写成下面形式

let obj={
    * myGeneratorMethod(){
        ...
    }
}
等价于
let obj={
    myGeneratorMethod:function* (){

    }
}

应用

Generator可以暂停函数执行,返回任意表达式的值,这种特点使得Generator有多重场景,

  • (1)异步操作的同步化表达
    Generator函数的暂停执行效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行,这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行,所以,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')
}

自己运行看结果

  • (2)控制流管理
    如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
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)
    }
}

ES6--babel学习(1)

Babel基础

Babel是一个广泛使用的转码器,可以将ES6 转化成ES5代码,从而在现在环境执行

这意味着,你可以现在就用ES6编写程序,而不用担心现有环境是否支持,下面是一个例子

    //转码前
    input.map((item)=>item+1)

    //转码后
    input.map(function(item){
        return item+1
    })

上面的原始代码用了箭头函数,这个特性还没有得到广泛支持,Babel将其转化成普通函数,就能在现有的JavaScript环境执行了

一、配置文件.babelrc

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-cli

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-node

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

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-core

如果某些代码需要调用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-polyfill

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.js入门学习5-光源

光源

一、世界有了光就不在黑暗

宇宙中的物体有的是发光的,有的是不发光的,我们把发光的物体叫做光源,太阳、点灯都是光源,在THREE的世界里,有了光,就不会黑暗

二、ThreeJs里面的光源

作为3D技术的发展趋势,浏览器端3D技术越来越被一些技术公司重视,由此,THREEjs非常重视3D渲染效果的真实性,对渲染真实性来说,使用光源的技巧

1、光源基类

    在ThreeJs中,光源用Light表示,他是所有光源的基类,他的构造函数是
    HREE.Light(hex)
他有一个参数hex,接受一个16进制的颜色值,例如要定义一个红色的光源,我们可以这样来定义,
var redLight = new THREE.Light(XXX)

2、由基类派生出来的其他种类光源

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>
只需要将光源加入场景,场景就能通过光源渲染出来的效果

4、点光源

    点光源:有这种光源放出的光线来自同一点,且方向辐射自四面八方,如蜡烛放出来的光,萤火虫的光,

    点光源用Point来表示,他的构造函数如下所示:
    <pre>
    PointLight(color,intensily,distance)</pre>
    这个类的参数稍微复杂一点,我们花时间解释一下
    - color:光的颜色
    - Intensity:光的强度,默认是1.0,就是说100%强度的灯光
    - distance :光的距离,从光源所在的位置,经过distance这段距离之后,光的强度将从Intensity衰减到0,默认情况下,这个值为0,表示光源强度不衰减

5、聚光灯

    聚光灯:这种光源的光线从椎体中射出,在被照的物体上产生聚光效果,使用这种光源需要指定的光的射出方向以及锥体的顶角,

    聚光灯的构造函数是:
    <pre>
    THREE.SpotLight(hex,intensity,distance,angle,exponent)</pre>
    函数的参数如下所示:
    - Hex:聚光灯发出的颜色,如XXX;
    - Intensity:光源的强度,默认1.0,如果强度是0.5,则强度是一半,颜色会淡一些,和上面的光源一样,
    - Distance:光线的强度,从最大值到0,需要的距离。默认为0,表示不衰减
    - Angle:聚光灯着色的角度,用弧度作为单位,这个角度和光源的方向形成的角度
    - exponent:光源模型中,衰减的一个参数,越大衰减越快

6、材质与光源的关系

   材质与光源有什么关系,这是个容易混淆的问题,在没有深入讲解前,我们只能说他们是互相联系,相互依托的关系
1、材质的真相

材质是什么,材质是物体的质地,我们可以用撤分文字的方法来解释,材质就是材料和质感的完美结合

如果不理解可以引用这段话:

在渲染程序中,他是表面各可视属性的结合,这些可视属性是表面的色彩、纹理、光滑度、透明度、反射率、折射率、发光率等,正是有了这些属性,才能让我们识别三维中的模型是什么做的,正是有了这些属性,我们计算机三维的虚拟世界才会和真实世界一样缤纷多彩

7、不带光的任何光源问题

我们首先在屏幕上画一个物体,不带任何光源,定义物体的颜色为黑色,其值为XXX,定义材质如下
<pre>
var material = new THREE.MeshLambertMaterial({color:XXX})  //这是兰伯特,材质中的一种,先看看最终的运行截图,如下所示</pre>

当没有任何光源的时候,最终的颜色将是黑色,无论材质是什么颜色。

8、兰伯特材质与光源

最常见的材质之一是Lambert材质,这是在灰色的或不光滑的表面产生均匀散射而形成的材质类型,比如,一张纸就是Lambert表面,首先他粗糙不均匀,不会产生镜面效果,这就是Lambert材质

Lambert材质表面会在所有方向上均匀地散射灯光,这就会使眼色看上去比较均匀,想想一张纸,无论什么颜色,是不是纸的各个部分的颜色都比较均匀呢

Lambert材质会受环境光的影响,呈现环境光的颜色,与材质颜色关系不大

分析一下代码

我们设置一个红色的环境光,并把它放在一个位置上  只用淡红色的Lambert材质

最后整个效果中,长方形呈现的是红色,我们要说的是,长方体显示红色,是因为长方形反射了红色的光,长方形本身的颜色是红色,光源是淡红色,红色的光照在物体上,物体反射了红色的光,所以呈现红色

我们一直在使用环境光,从环境光的构造函数来看,只有颜色,其位置对场景中的物体并没有影响,因为他是均匀的反射到物体表面的,

### 9、环境光对物体的影响
环境光在场景中无处不在的光,他对物体的影响是均匀的,也就是无论你在物体的哪个角度观察,物体的颜色都是一样的,这就是伟大的环境光,可以把环境光放在任何一个位置,他的光线是不会衰减的,是一个永恒的光源 

ES6--Generator函数的异步应用

Generator函数的异步应用

  • 1、传统方法
  • 2、基本概念
  • 3、Generator函数
  • 4、Thunk函数
  • 5、co函数

异步编程对JavaScript语言太重要,JavaScript语言执行环境是‘单线程’的,如果没有异步编程,根本没法用,非卡死不可,本章主要介绍Generator函数如何完成异步操作

传统函数

ES6诞生之前,异步编程的方法大致有以下四种,

  • 回调函数
  • 时间监听
  • 发布订阅
  • Promise对象
    Generator函数将JavaScript异步编程带入一个全新的阶段

基本概念

异步

所谓异步,简单说就是一个任务不是连续完成的,可以理解成任务被人分成两个阶段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

比如:有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件,然后,程序执行其他任务,等操作系统返回文件,在接着执行任务的第二段,这种不连续的执行,叫做异步。

相应的连续的执行叫做同步,由于是连续执行不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序执行干等着。

回调函数

JavaScript语言对异步编程的实现,就是回调函数,所谓回调函数,就是把任务的第二段单独写在一个函数里面,等着重新执行这个任务的时候,就直接调用这个函数,回调函数的英文名字callback,(重新调用)

读取文件进行处理,是这样的

fs.readFile('etc/passwd','utf-8',function(){
    if(err) throw err
    console.log(data)
})

上面代码中第三个参数,就是回调函数,也是任务的第二段,等操作系统返回了/etc/passwd/这个文件之后,回调函数才会执行。

一个有趣的问题是,为什么Node约定,回调函数的第一个参数,必须是错误对象err

原因是执行分成两端,第一段完成之后,任务所在的上下文环境就已经结束了,在这以后抛出的错误,原来的上下文环境已经捕捉,只能当做参数,传入第二段

Promise

回调函数本身并没有问题,他的问题出现在多个回调函数嵌套,假定读取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,原来的语句变的不清楚。

Generator函数

协程

传统的编程语言,早有异步编程的解决方案,其中有一个叫做‘协程’,意思是多个线程相互协作,完成异步任务。

协程有点像函数,又有点像线程,他的运行流程大致如下

  • 第一步,协程A开始执行
  • 第二部,协程A执行到一半,进入暂定,执行权转移到协程B
  • 第三部,(一段时间后)协程B交还执行权
  • 第四部,协程A恢复执行

上面流程的协程A,就是异步任务,因为它分为两段(或多段)执行。

举个例子来说,读取文件的协程写法如下

function* asyncJob(){
    //...其他代码
    var f= yield readFile(fileA);
    //... 其他代码
}

上面代码的函数asyncJob是一个协程,他的奥妙就是其中的yield命令,他表示执行到此处,执行权就交给其他协程,也就是说,yield是异步两个阶段的分割线

协程遇到yield命令就暂停,等到执行权返回 ,再从暂停的地方往后执行,他的最大优点,就是代码的写法非常向同步操作,如果去除yield命令,就一模一样

协程的Generator函数实现

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函数数据交换和错误处理

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函数

Thunk函数是自执行函数Generator函数的一种

参数求值策略

Thunk函数早在上个世纪60年代就诞生了,

那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好,一个争论的焦点是‘求值策略’,即函数的参数到底该何时求值

Thunk函数的含义

编译器的传名调用实现,往往是将参数放到一个临时的函数当中,再将这个临时函数传入函数体。这个临时函数就叫做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函数含义有所不同,在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模块

生产环境转化器,建议使用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基础-1

概念

本质上,webpack是一个现在jS应用的静态模块打包器,当webpack处理应用程序时,他会递归构建一个依赖关系图(dependency),其中包含应用程序需要的每个模块,然后将所有的这些模块打包成一个或者多个bundle。

他是高度可配置的,但是,在开始前你需要先理解四核心概念:

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugins)

入口[entry]

入口七点(entry point)指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始,进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接或间接)依赖的

每个依赖项随即被处理,最后输出到称之为bundles的文件中,

可以通过webpack配置中配置entry属性,来指定一个入口起点(或者多个入口起点)

entry例子
webpack.config.js

    module.exports = {
        entry:'./path/file.js'
    }

出口[output]

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

loader让webpack能去处理那些非JS文件(webpack自身只能理解)。loader可以将所有的类型的文件转化成webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对他们进行处理。

注意:loader能够import导入任何类型的模块(例如 .css文件),这是webpack特有的功能,其他打包程序或任务执行器的可能并不支持,我们认为这种语言扩展是很有必要的,因为这可以是开发人员创建出更准确的依赖关系图

在更高的层面,webpack的配置中loader有两个目标:

  • test属性,用于标识出应该被对应的loader进行转化的某个或者某些文件
  • use属性,表示进行转换时,应该使用哪个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

插件[plugins]

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-Mutation

Mutation

更改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')

提交载荷(payload)

你可以向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
        }
    }

Mutation需要遵守Vue的相应规则

既然Vuex的store中的状态是响应式的,那么当我们更改状态时,监视状态的vue组件也会自动更新,这也就意味着Vuex中的mutation也需要与使用Vue一样遵守一些注意事项

  • 最好提前在你的store中初始化好所有的属性

  • 当需要在对象上添加新的属性时,你应该

    • 使用Vue.set(obj,'newProp',123)
    • 以新对象替换老对象,例如,利用对象展开运算符我们可以这样写
      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')
              ])
          }
      })
    

从输入url到页面加载完成发生了什么?——前端角度

从输入url到页面加载完成发生了什么?——前端角度

(参考来源:https://www.cnblogs.com/daijinxue/p/6640153.html)

这是一道经典的面试题,这道面试题不光前端面试会问到,后端面试也会问道,这道题没有一个标准的答案,他涉及的很多知识点,面试官会通过这道题了解你对哪一方面的只是比较擅长,然后继续追问看看你的掌握程度,从前端的角度出发,必须回答的几个基本点,然后根据你的理解深入回答

  • 浏览器的地址栏输入url并按下回车

  • 浏览器查找当前的url是否存在缓存,并比较缓是否过期

  • DNS解析URL对应的IP

  • 根据IP建立起TCP链接(三次握手 )

  • HTTP发起请求

  • 服务器处理请求,浏览器接收HTTP响应

  • 渲染页面,构建DOM树

  • 关闭TCP链接(四次挥手)

    URL

    我们常见的url是这样的 http://ww.baidu.com,这个域名有三部分组成:协议名、域名、端口号,这里的端口号默认80,除此之外,还会有

    • protocol 协议,常用的协议是http
    • hostname 主机地址,可以是域名,也可以是IP地址
    • port 端口 http协议默认端口是:80端口,如果不写默认就是:80端口
    • path 路径 网络资源在服务器中的指定路径
    • parameter 参数 如果要向服务器传入参数,在这部分输入
    • query 查询字符串 如果需要从服务器那里查询内容,在这里编辑
    • fragment 片段 网页中可能会分为不同的片段,如果想访问网页后直接到达指定位置,可以在这部分设置
      我们最常见的协议是http协议,除此之外还会有HTTPS协议、FTP协议等等,HTTP协议的端口号默认80、HTTPS的端口号默认443,
缓存

讲完URL,我们说一说浏览器的缓存,HTTP缓存有多重规则,根据是否需要向服务器发起请求来分类,分为强制缓存和协商缓存

  • 强制缓存是一个绝对时间、即服务器时间,浏览器检查当前时间,如果还没有失效时间就直接使用缓存文件,但是服务器时间和客户端时间可能不一致,因此该字段已经很少使用,catch-control中的max-age保存一个相对时间,例如 catch-Control:max-age=484200,表示浏览器收到文件后,缓存在484200时间内有效,如果同时存在catch-control和expire,浏览器总是优先使用catch-control
  • 协商缓存通过HTTP的last-modified、etag字段来判断
    • last-modified是第一次请求自=资源时,服务器返回的字段,表示最后一次更新的时间,下一次浏览器请求资源时就发送if-last-modified-since字段,服务器用本地Last-modified与if-modified-since时间比较,如果不一致则认为缓存已经过期;如果时间一直则发送304状态码,让浏览器继续使用缓存
    • eTag:资源的实体标识,当资源内容更新时,Etag会改变。服务器会判断etag是否发生变化,如果变化则返回新资源,否则返回304

title

DNS域名解析

我们知道在地址栏输入的域名并不是最后资源所在的真实位置,域名只是与他的IP地址的一个映射,网络服务器的IP那么多,我们不可能一记串串的数字,因此域名就产生了,域名解析的过程就是将域名还原为IP地址的过程

  • 首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。

  • 如果没有找到则会查找本地DNS解析器缓存,如果查到则返回

  • 如果还是没有找到则会查找本地DNS服务器,如果查到则返回

  • 最后迭代查询,按根域服务器->顶级域.cn->第二层域hb.cn->子域 www.hb.cn的顺序找到IP地址

    title

  • 递归查询,按照上一级DNS服务器->上上级->……逐级向上查询找到IP地址
    title

TCP链接

在通过第一步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连接成功)状态,完成三次握手。

    完成三次握手,客户端与服务器开始传送数据
    title

浏览器向服务器发送HTTP请求
完整的HTTP请求包含请求起始行,请求头、请求主题三部分
浏览器接收响应

服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完结果以HTTP的的Response对象返回,主要包括状态码,响应吗、响应头、响应报文三个部分

  • 响应头主要有Catch-Control、Connection、Date、Pragma等组成
  • 响应体为服务器返回给浏览器的信息,主要由HTML、CSS、JS,图片组成
页面渲染

如果说响应的内容是HTML文档的话,就需要浏览器进行解析渲染呈现给用户,整个过程涉及两个方面,解析和渲染,在渲染页面之前,需要构建DOM和CSSOM树
title

在浏览器还没收到完整的HTML文件时,他开始渲染页面了,在在遇到外部链接的脚本标签或样式标签或图片时,会发送发送HTTP请求重复上述的步骤,在收到CSS文件后会对已经渲染的页面重新渲染,加入他们应有样式,图片加载完成后立刻显示在想用的位置,在这一过程中可能会触发页面的重绘和重排。这里就涉及了,两个重要的概念:reflow和repaint

关闭TCP连接或者继续保持连接

title

  • 第一次挥手是浏览器发完数据后,发送FIN请求断开连接。
  • 第二次挥手是服务器发送ACK表示同意,如果在这一次服务器也发送FIN请求断开连接似乎也没有不妥,但考虑到服务器可能还有数据要发送,所以服务器发送FIN应该放在第三次挥手中。
  • 这样浏览器需要返回ACK表示同意,也就是第四次挥手。

Git学习

git 代码管理工具的学习

1

  • 初始化一个git仓库,使用 git init 命令
  • 添加文件到git仓库,分两步
    • 第一步,使用git add ,注意,可反复使用,添加多个文件,
    • 第二部,使用命令git commit 完成

2

  • 随时掌握工作区的状态,使用git status命令
  • 如果git status 告诉你有没有文件被修改过,用git diff可以查看
    • 工作区 - 暂存区 git diff
    • 暂存区 - 历史版本区 git diff --catch

3、回退版本

  • git reset --hard HEAD^ 或者HEAD~100 或者 commit_id
  • 转换前,用git log查看提交历史,以便确定要回退到那个版本
  • 要回到以后,用 git reflog 查看命令历史,以便确定要回到未来那个版本

4、管理修改

  • 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令 git checkout --file
  • 场景2:当不但改乱了工作区某个文件内容,还添加到暂存区,像丢弃修改,分两步,1、用命令git reset HEAD file 就回到场景1,第二步,按场景1操作
  • 场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,git reset --hard HEAD^

5、删除文件

  • 命令git rm 用于删除一个文件,如果一个文件已经被提交到版本库,那么永远不要担心误删,

6、远程关联

  • 关联远程库,使用命令 git remote add origin url
  • 关联后,使用命令 git push -u origin master

7、克隆代码

  • 要克隆一个仓库,使用代码 git clone url

8、创建与合并分支

  • 查看分支:git branch
  • 创建分支:git branch name
  • 切换分支:git checkout name
  • 创建切换:git checkout -b name
  • 合并某分支到当前分支:git merge name
  • 删除分支:git branch -d name

9、解决冲突

  • 当git无法自动合并分支时,就必须首先解决冲突,解决冲突后,再提交,合并完成
  • 用 git log --graph

10、分支管理策略

  • git merge name 合并时,是快速合并,会丢失合并信息,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支嫩恶搞看出曾经合并过

11、Bug分支

  • 修复bug时,我们会创建新的bug分支进行修复,然后合并,最后删除,
  • 当手头工作没有完成,先把工作现场隐藏起来 git stash 一下,然后去修复bug,修复完后,再git stash pop,回到工作现场

12、Feature分支

  • 开发一个新的功能,做好新建一个分支 feature;
  • 如果要丢弃一个没有合并过的分支,可以用 git branch -D name 强制删除

13、多人协作

  • 使用 git remote -v 查看 origin地址
  • 多人协作时,大家都会往master 和dev 分支上推送各自的修改
    当git clone 的时候,默认情况下,只能看到本地的master分支,而现在要在dev上开发,就必须创建远程origin的dev分支到本地,使用 git checkout -b dev origin/dev 就可以在dev分支上操作,并不断把dev分支push到远程,使用代码 git push origin dev
  • 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull 抓取远程的新提交
  • 在本地创建和远程分支对应的分支,使用 git checkout -b branch-name origin/branch-name
  • 建立本地分支与远程分支的关联,使用 git checkout --set-upstream branch-name origin/branch-name

webpack-loader

Loader

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

在你的应用程序中,有三个使用loader的方式:

  • 配置:在webpack.config.js文件中指定laoder
  • 内联:在每个inport语句中显示指定laoder
  • CLI:在shell命令中指定
配置[Configguration]

module.rules允许你在webpack配置中指定多个loader,这是展示laoder的一种简明方式,并且有助于使代码变的简洁,同时让你对各个laoder有个全局的概念

    module:{
        rules:[
            {
                test:/\.css$/,
                use:[
                    {loader:'style-loader'},
                    {
                        loader:'css-laoder',
                        options:{
                            modules:true
                        }
                    }
                ]
            }
        ]
    }

Shell学习

shell教程

shell脚本

  • Shell脚本(shell script),是一种为shell编程的脚本程序

shell环境

  • shell 编程跟Java、php编程一样。只要有一个能编写代码的文本编辑器和一个能解释的脚本解释器就可以了
  • linux的shell种类众多,常见的有:
    • Boune Shell(/usr/bin/sh或/bin/sh)
    • Boune Again Shell(/bin/bash)
    • C Shell(/usr/bin/csh)
    • K Shell(/usr/bin/ksh)
    • Shell for Root(/sbin/sh)

本教程关注的是Bash,也就是Boune Again Shell,由于易用和免费,Bash在生活中被广泛使用,同时,Bash也是大多数Linux系统默认的Shell

第一个shell脚本

  • 打开文本编辑器(可以使用vi/vim命令来创建文件),新建一个文件test.sh,扩展名为sh
         #!/bin/bash
         echo "Hello World!" 

#!是一个约定的标记,他告诉系统这个脚本需要什么样的解释器来执行,即使用哪一种

运行shell脚本有两种方式

  • 作为可执行程序
    将上面的代码保存为test.sh,并cd 到相应的目录:
         chmod +x ./test.sh  #使脚本具有执行权限
         ./test.sh   #执行脚本 

注意 ./test.sh是全局的还是当前路径下的

  • 作为解释器参数
    这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名
     /bin/sh test.sh
     /bin/php test.php

这种方式运行的脚本,不需要在第一行的指定解释器信息,写了也没用

Shell变量

格式

your_name="runcob.com"
  • 注意
    • 首个字符必须为字母(a-z,A-Z)
    • 中间不能有空格,可以使用下划线(_)
    • 不能使用标点符号
    • 不能使用bash里的关键字

除了显式的直接复制,还可以用语句给变量赋值:如

 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实例有效,其他Shell启动的程序不能访问局部变变量
  • 环境变量 所有的程序,包括Shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行,必要的时候Shell脚本也可以定义环境变量
  • Shell变量 Shell变量是由Shell程序设置的特殊变量,Shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了Shell的正常运行

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

注意:以上脚本“``”是反引号,而不是单引号,

Shell 数组

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传递参数

我们可以在执行Shell脚本时,像脚本传递参数,脚本内获取参数的格式为:$n. n代表一个数字,1为执行脚本的第一个参数 2代表执行脚本的第二个参数 以此类推……

实例

以下实例我们向脚本传递三个参数,并分别输出,其中$0为执行的文件名:

   #!/bin/bash
   # author:李会洋
   # url:www.runoob.com
   
   echo "Shell 传递参数实例!"
   echo "执行的文件名:$0"
   echo "执行的文件名:$1"

什么是服务器端渲染和客户端渲染

什么是服务器端渲染和客户端渲染(ps:https://www.cnblogs.com/zhuzhenwei918/p/8795945.html)

互联网早期,用户使用浏览器浏览的都是一些没有复杂逻辑、简单页面,这些页面都是在后端将html拼接好的然后将之返回给前端完整的html文件,浏览器拿到这个HTML文件之后就可以直接解析展示了,而这也就是所谓的服务器端渲染,而随着前端页面复杂性的提高,前端就不仅仅是普通的页面展示了,而可能添加了更多的功能性的组件,复杂性大,另外,彼时ajax的兴起,使得业界就开始推崇前后端分离的开发模式,即后端不提供完整的HTML页面,而提供一些api使得亲爱单可以获取到json数据,然后前端拿到json数据之后再在前端进行HTML页面的拼接,然后展示在浏览器上,这就是所谓的客户端渲染,这样前端就可以专注的UI的开发,后端专注于逻辑的开发

两者本质区别是什么

客户端渲染和服务器渲染的最重要的区别是究竟是谁来完成HTML文件的完整拼接,如果实在服务器端完成,然后就返回到客户端,就是服务器端渲染,而如果是前端做了更多的工作完成HTML拼接,则是客户端渲染

服务器端的优缺点是怎样样的?

优点:
  • 前端耗时少,因为后端拼接完成了HTML,浏览器需要直接渲染出来
  • 有利于SEO,因为在后端有完整的HTML页面,所以爬虫更容易爬去获得信息,更有利于SEO
  • 无需占用客户端资源,即解析模板的工作完全交由后端来做,客户端只需要解析标准的HTML页面即可,这样客户端的资源占用更少,尤其是移动端,可以更省电
  • 后端生成静态文件,即生成缓存片段,这样就可以减少数据库查询浪费的时间,且对数据库变化不大的页面非常高效。
缺点:
  • 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,则对前后端复杂页面高的项目,不利于项目高效开发,另外,是服务器端渲染,则前端一般就写一个静态HTML页面,然后后端再修改为模板,还是非常低效的,并且还常常需要前后端共同的=完成页面的修改的动作;或者是前端直接完成HTML模板,然后交由后端。另外如果后端改了模板,前端还需要根据改动的模板再调节css,这样使得前后端联调的时间增加。
  • 占用服务器资源。即服务器端完成html模板的解析,如果请求较多,会对服务器端造成一定的访问压力。而如果使用前端渲染,那就是把这些压力分摊了前端,而这里确实完全交给了一个服务器

客户端渲染的优缺点是怎样的?

优点:
  • 前端后端分离。前端专注于前端UI,后端专注于api开发,且前端有更多的选择性,不需要遵循后端特定的模板。
  • 体验更好。比如,我们将网站做成spa或者部分做成spa,这样,就其实移动端,可以是体验更加接近原生app。
缺点:
  • 前端响应比较慢,如果客户端渲染,前端还要进行拼接字符串的过程,需要耗时额外的时间,不如服务器端渲染速度快。
  • 不利于SEO,目前比如百度,谷歌的爬虫对于spa都是不认识的,只是记录一个页面,所以SEO很差,因为服务器端可能没有保存完整的html,而是前端通过JS进行dom的拼接,那么爬虫无法爬取信息,除非搜索引擎的SEO可以增加对于JS的爬取能力,这才能保证SEO

使用服务器端渲染还是客户端渲染?

  • 不谈业务场景而盲目选择使用何种渲染方式都是耍流氓,比如企业级网站,主要功能是展示而没有复杂的交互,而且需要良好的SEO,则这时候我们就需要使用服务器端渲染;而类似于后台管理页面,交互性较强,不需要考虑SEO,那么就可以使用客户端的渲染。
  • 另外,具体使用何种渲染方法并不是绝对,比如现在一些网站采用了首屏服务器端渲染,即对于用户最开始打开的那个页面采用的服务器渲染,这样就保证了渲染速度,而其他的页面采用客户端渲染,这样就完成了前端后端分离

对于前后端分离,如何进行SEO优化?

  • 如果进行了前后端分离,那么前端就是通过JS来修改dom使得HTML拼接完全,然后再显示,或者是使用SPA,这样,SEO几乎没有,那么这种情况下如何做SEO优化呢?
  • 目前的react和vue都提供了ssr,即服务器端渲染,这也是提供SEO不好的解决方式

究竟如果理解前后端分离

实际上,时至今日,前后端分离一定是必然或者趋势,因为早期在web1.0时代的网页就是简单的网页,而如今的网页越来越朝向app前进,而如今的网页越来越朝app前进,而前后端分离就是实现必然的结果,所以我们可以认为HTML、css、JS组成了这个app。然后浏览器作为虚拟机来运行这些程序,即浏览器组成了app的运行环境,成了客户端,我们目前接触的前端工程化、编译、各种MVC/MVVM框架、依赖工具、npm、bable、webpack等看似很新鲜、创新的东西实际上都是桌面开发所形成的概念,只是近几年来前端发展较快而借鉴过来的,本质上就是开元社区东拼西凑做出来的visual studio。

ES6--Iterator(遍历器)学习

Iterator 和 for ...of循环

  • 1、Iterator(遍历器)的概念
  • 2、默认Iterator接口
  • 3、调用Iterator接口的场合
  • 4、字符串的Iterator接口
  • 5、Iterator接口与Generator函数
  • 6、遍历器对象的return()、throw()
  • 7、for ... of循环

Iterator(遍历器)的概念

Javascript原有的表示‘集合’的数据结构,主要是数组和对象,ES6又添加了Map和Set,这样就有了四种数据集合,用户还可以组合使用他们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象,这样就需要一种统一的接口机制,来处理不同的数据结构

遍历器就是这样的一种机制,他是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据只要部署Iterator接口,就可以完成遍历操作(即依次处理改数据结构的所有成员)

Iterator的作用有三个:一是为各种数据结构,提供一个统一的,简便的访问接口;二是使得数据结构的成员能够按某种次序排列,三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for ...of 消费

Iterator遍历的过程是这样的。

  • 1、创建一个针对对象,指向当前数据结构的起始位置,也就是说,遍历器对象本质上就是一个指针对象
  • 2、第一次调用只针对象的next()方法,可以将指针指向数据结构的第一个成员
  • 3、第二次调用指针对象的next()方法,指针就指向数据结构的第二个成员
  • 4、不断地调用指针对象的next方法,直到他的指向数据结构的结束位置

每一次调用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接口

Iterator接口的目的,就是为所以数据结构,提供一种访问机制,即for ...of,当使用for ... of 循环遍历某种数据结构时,该循环会自动去寻找Iterator接口

一种数据结构只要部署了Iterator接口,我们就称这种数据结构是可遍历性的

原生具备Iterator接口的数据结构如下

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的arguments对象
  • NodeList对象

下面的例子是数组的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属性上面,所以。调用这个属性,就得到遍历器对象

Vuex-Action

Action

Action类似与mutation,不同在于:

  • Action提交的是mutation,而不是直接变更状态。
  • Action可以包含任意异步操作

让我们来注册一个简单的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

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

在组件中分发Action

你在组件中使用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什么时候结束呢,更主要的是,我们如何组合多个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学习

1、let和const 命令

1、let命令

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命令

const声明一个只读的变量,一旦声明,常量的值就不能改变

2、变量的解构赋值

  • 1、数组的解构赋值
  • 2、对象的解构赋值
  • 3、字符串的解构赋值
  • 4、数值和布尔值的解构赋值
  • 5、函数参数的解构赋值
  • 6、圆括号问题
  • 7、用途

1、数组的解构赋值

基本用法

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

2、对象的解构赋值

解构不仅仅用于数组,还可以用于对象

let {foo,bar}={foo:'aaa',bar:'bbb'};
foo //'aaa'
bar //'bbb'

对象的解构与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值有它的位置决定,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值

let {foo:baz}={foo:'aaa',bar:'bbb'}
baz  //aaa

对象的解构内部机制,实现找到同名属性,然后再赋给对应的变量,真正的赋值是后者,而不是前者。
foo是匹配的模式,baz才是变量,真正被赋值的是变量baz,而不是foo

3、字符串的解构赋值

字符串也是可以解构赋值的,这是因为此时,字符串被转换成了一个类似的数组对象

const [a,b,c,d,e]='hello'
let {length:len}='hello'
len  //5

类数组的对象都有一个length属性,因此还可以对这个属性解构赋值

4、数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则先转为对象

let {toString:s}=123;
s===Number.prototype.toString
let {toString:s}=true
s===Boolean.prototype.toString

上面代码中,数值和布尔值包装对象都有toString属性,因此s都能取到值

5、函数参数的解构赋值

函数参数也可以解构赋值

 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

7、用途

变量的解构赋值用途很多

  • (1)交换变量值
  let x=1;
  let y=2;
  [x,y]=[y,x]

上面代码交换变量x和y的值

  • (2)从函数返回多个值
    function example(){
        return [1,2,3]
    }
    let [a,b,c]=example();
    function example(){
        return {
            foo:'1',
            bar:'2'
        }
    }
    let {foo,bar}=example()
  
  • (3)函数参数的定义
    解构赋值可以方便地将一组参数与变量名对应起来
  • (4)提取JSON数据
  • (5)函数参数的默认值
  • (6)遍历Map解构

3、字符串的扩展

  • 1、字符串的Unicode表示法
  • 2、codePointAt()
  • 3、String.fromCodePonit()
  • 4、字符串的遍历器接口
  • 5、at()
  • 6、normalize()
  • 7、includes(),startWith(),endsWith()
  • 8、repeat()
  • 9、padStart()、padEnd()
  • 10、模板字符串
  • 11、模板编译
  • 12、标签模板
  • 13、String.raw()
  • 14、模板字符串的限制

7、includes(),startWith(),endsWith()

传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在可另一个字符串中,ES6有提供了三种方法

  • includes() :返回布尔值,表示是否找到参数字符串
  • startWith():返回布尔值,表示参数字符串是否在元字符串的头部
  • endWith() :返回布尔值,表示参数字符串是否包含在元字符串的尾部
   var s='Hello world!'
   s.startWith('Hello')  //true
   s.endWith('!')  //true
   s.includes('o') //true

正则的扩展

  • 1、RegExp构造函数
  • 2、字符串的正则方法
  • 3、u修饰符
  • 4、y修饰符
  • 5、sticky属性
  • 6、flags属性
  • 7、s修饰符:dotAll模式

数值的扩展

Number.isFinite(),NUmber.isNaN()

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对象上调用

  • Math.trunc() 方法用于去除一个数的小数部分,返回整数部分
  • Math.sign() 方法用来判断一个数到底是整数 负数还是零,会现将其转化为数值 返回五种值
    • 参数为整数,返回+1;
    • 参数为负数,返回-1;
    • 参数为0,返回0;
    • 参数为-0,返回-0;
    • 其他值,返回NaN
  • Math.cbrt() 方法用于计算一个数的立方根,非数返回NaN
  • Math.clz32() 方法用于整数使用32位二进制形式表示,math.clz32返回一个数的32位无符号整数形式有多少个前导0
  • Math.imul() 方法返回两个数以32位符号整数形式相乘的结果
  • Math.fround()
  • Math.hypot() 方法返回所有参数的平方和的平方根

函数的扩展

  • 1、函数参数的默认值
  • 2、rest函数
  • 3、严格模式
  • 4、name属性
  • 5、箭头函数
  • 6、绑定this
  • 7、尾调用优化
  • 8、函数参数的尾逗号
  • 9、catch语句参数

基本用法

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属性,将返回没有指定默认值的参数的个数,也就是说,指定了默认之后,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参数

下面代码是一个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属性

函数的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)

!使用注意点

箭头函数有一个使用注意点

  • (1)函数体内的this对象,就是定义时所在的对象
  • (2)不可用当做构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  • (3)不可以使用argument对象,该对象在函数体内不存在。如果要用,可以用rest来代替
  • (4)不可以使用yield命令,因此箭头函数不能用作 Generator函数

上面四点中,第一点尤其值得注意,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

数组的扩展

  • 1、扩展运算符
  • 2、Array.from()
  • 3、Array.of()
  • 4、数组实例的copyWithin()
  • 5、数组实例的find() findIndex()
  • 6、数组实例的fill
  • 7、数组实例的entries()、keys() value()
  • 8、数组实例的includes()
  • 9、数组的空位

扩展运算符

含义:扩展运算符(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)

扩展运算符的应用

  • (1)合并数组
    扩展运算符提供了数组合并的新写法,
    //es5
    [1,2].concat(more)
    //es6
    [1,2,...more]

Array.from()

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方法用于将一组值转化为数组

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]

数组实例的copyWithin()

数组实例的find() findIndex()

数组实例的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()

fill方法使用给定值,填充一个数组

数组实例entries(),keys(),values()

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'

数组实例的includes()

Array.prototype.includes方法返回一个布尔值,表示数组是否包含给定的值,与字符串的includes方法类似

[1,2,3].includes(2)  //true
[1,2,3].includes(4)  //false
[1,2,NaN].includes(NaN)   //true

对象的扩展

  • 1、属性的简洁表示法
  • 2、属性名表达式
  • 3、方法的name属性
  • 4、object.is()
  • 5、object.assign()
  • 6、属性的可枚举型和遍历
  • 7、object.getOwnPropertyDescriptors()
  • 8、proto、object.setPrototypeof() object.getPrototypeOf()
  • 9、Object.keys() Object.value() object.entries()
  • 10、对象的扩展运算符
  • 11、Null传到运算符

属性的简洁表达式

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
}

Object.assign()

基本用法,方法用于对象的合并,将原对象(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第一个参数是目标对象,后面的参数是源对象
如果目标对象和源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性

set和map数据结构

set 可以起到数组去重的效果

const set=new Set([1,2,3,4,5,5,4,3,2,1])
[...set]

Set实例分为两大类:操作方法(用于操作数据)和遍历数据,

  • add(value) 添加某个值,返回Set结构本身
  • delete(value)删除某个值,返回一个布尔值,表示删除是否成功;
  • has(value):返回一个布尔值,表示该值是否为Set成员,
  • clear():清除所有成员,没有返回值

上面这些属性和方法的实例如下

 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结构的实例有四个方法,可以用于遍历成员

  • keys() :返回键名的遍历器
  • value() :返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数数遍历每个成员

需要特别指出的是,Set的遍历顺序就是插入顺序,这个特性有时非常有用,比如使用Set保存一个回掉函数列表,调用时就能保证按照添加顺序调用。

(1)keys() value() entries()

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

WeakSet结构与Set类似,也是不重复的值得集合,但是,它与Set有两个区别,

  • 1、WeakSet的成员只能是对象,而不是其他类型的值
  • 2、WeakSet中的对象都是弱引用,即是垃圾回收机制不考虑WeakSet对该对象的引用
    语法:WeakSet是一个构造函数,可以使用new命令,创建WeakSet数据结构
const ws = new WeakSet()

作为构造函数,WeakSet可以接受一个数组或类数组的对象作为参数,

const a = [[1,2],[3,4]]
cosnt ws = new WeakSet(a)
// WeakSet {[1,2],[3,4]}

Map

含义与基本用法
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、Size属性
  • 2、set(key,value)
  • 3、get(key)
  • 4、has(key)
  • 5、delete(key)
  • 6、clear()

遍历方法

  • key()
  • value()
  • entries()
  • forEach()

Proxy

  • 概述
  • Proxy实例的方法
  • Proxy.revocal()
  • this 问题
  • 实例:web 服务的客户端

微信开发学习

微信支付开发(1) JS API支付

本文介绍微信支付下的jsapi实现流程

流程实现

1. OAuth2.0授权

  • 1、什么是OAuth2.0授权?(http://justcoding.iteye.com/blog/1950270)

    • 一、什么是OAuth协议
      OAuth(开放授权)是一个开放标准。
      允许第三方网站在用户授权的前提下访问在用户在服务商那里存储的各种信息。
      而这种授权无需将用户提供用户名和密码提供给该第三方网站。
      OAuth允许用户提供一个令牌给第三方网站,一个令牌对应一个特定的第三方网站,同时该令牌只能在特定的时间内访问特定的资源。
    • 二、OAuth的原理和授权流程
      OAuth的认证和授权的过程中涉及的三方包括:
      服务商:用户使用服务的提供方,一般用来存消息、储照片、视频、联系人、文件等(比如Twitter、Sina微波等)。
      用 户:服务商的用户
      第三方:通常是网站,该网站想要访问用户存储在服务商那里的信息。
      比如某个提供照片打印服务的网站,用户想在那里打印自己存在服务商那里的网络相册。
      在认证过程之前,第三方需要先向服务商申请第三方服务的唯一标识。
      OAuth认证和授权的过程如下:
      1、用户访问第三方网站网站,想对用户存放在服务商的某些资源进行操作。
      2、第三方网站向服务商请求一个临时令牌。
      3、服务商验证第三方网站的身份后,授予一个临时令牌。
      4、第三方网站获得临时令牌后,将用户导向至服务商的授权页面请求用户授权,然后这个过程中将临时令牌和第三方网站的返回地址发送给服务商。
      5、用户在服务商的授权页面上输入自己的用户名和密码,授权第三方网站访问所相应的资源。
      6、授权成功后,服务商将用户导向第三方网站的返回地址。
      7、第三方网站根据临时令牌从服务商那里获取访问令牌。
      8、服务商根据令牌和用户的授权情况授予第三方网站访问令牌。
      9、第三方网站使用获取到的访问令牌访问存放在服务商的对应的用户资源。
    • 三、目前支持OAuth的网站有哪些?
      t.sina.com.cn
      t.qq.com
      t.sohu.com
      t.163.com
      www.douban.com
      www.twitter.com
      www.facebook.com
      Google Buzz
  • 所谓OAuth(即Open Authorization,开放授权),它是为用户资源授权提供了一种安全简单的标准,也就是说用户在访问第三方web或应用的时候,第三方不会知道用户的信息(登录密码等),现在基本都支持OAuth2.0版本了。
    首先来看看我们在第三方使用oauth流程如下:
    第一步:用户登录第三方网站,使用qq登录。
    alt text
    第二步:点击登录后,会跳到qq平台提示输入用户名和密码。
    alt text
    第三步:如果用户名和密码正确,会提示是否接受授权,如果授权成功,第三方网站就能访问你的资源了,qq头像、用户名等
    alt text
    认证和授权过程(包括三方)
      1、服务提供方,用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表。
      2、用户,存放在服务提供方的受保护的资源的拥有者。
      3、客户端,要访问服务提供方资源的第三方应用,通常是网站。在认证过程之前,客户端要向服务提供者申请客户端标识。
    alt text
    用户访问客户端的网站,想操作用户存放在服务提供方的资源。

      客户端向服务提供方请求一个临时令牌。
      服务提供方验证客户端的身份后,授予一个临时令牌。
      客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方。
      用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。
      授权成功后,服务提供方引导用户返回客户端的网页,并返回已授权的临时凭证。
      客户端根据已授权的临时令牌从服务提供方那里获取访问令牌。
      服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌。
      客户端使用获取的访问令牌访问该用户存放在服务提供方上的受保护的资源。(客户端只能访问给予它授权的用户的资源信息)

  • 2、授权流程及操作

    • JSAPI 支付前需要调用 登录授权接口获取到用户的 Openid 。所以需要做一次授权,这次授权是不弹出确认框的。
      其实质就是在用户访问
         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
 

2. 统一支付

  • 统一支付是JSAPI/NATIVE/APP各种支付场景下生成支付订单,返回预支付订单号的接口,目前微信支付所有场景均使用这一接口
    统一支付中以下参数从配置中获取,或由类自动生成,不需要用户填写
          $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);//签名
    
  • 在JSAPI支付中,另外填写以下参数\
          //统一支付接口中,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
      
  • 这些参数最终组成了这样的xml数据,
            
              
              
              
              1
              
              
              
              10012345
              
              
              
            
      
  • 将这些数据提交给统一支付接口

    https://api.mch.weixin.qq.com/pay/unifiedorder
  • 将获得返回 如下数据
    
    
  • 其中包含了最重要的预支付ID参数,prepay_id,值为
        wx201410272009395522657a690389285100
  

3、JS API支付

  • 前面的准备工作做好了以后,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>
    
  • 当用户点击“贡献一下”按钮时,将弹出微信支付插件,用户可以开始支付。

4、支付通知

  • 支付成功后,通知接口中也将收到支付成功的xml通知
             
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
               1  
                 
                
             
     

nodejs支付宝支付流程

  • http://neutra.github.io/2013/%E6%94%AF%E4%BB%98%E5%AE%9DWAP%E6%94%AF%E4%BB%98%E6%8E%A5%E5%8F%A3%E5%BC%80%E5%8F%91/)
    alt text
  • 1、用户点击购买按钮,想网站发起购买请求
  • 2、网站创建订单,指派一个唯一的订单号
  • 3、网站把订单号、企业支付宝账号、交易金额、数量等信息,用私钥签名发送给支付宝
  • 4、支付宝创建一个交易订单,返回一个交易令牌(token)
  • 5、网站按照指定要求,用token和自己的私钥,构造一个重定向得到支付地址
  • 6、网站把重定向的地址返回给浏览器
  • 7、浏览器自动重定向到该地址,既包含了token、网站签名的支付宝交易页面
  • 8、支付宝显示当前的交易金额、数量、卖家信息
  • 9、用户用自己的支付宝账号支付这比金额
  • 10、支付宝把用户支付成功(或失败)这个消息和订单上加上支付宝签名,使用http post的方式通知网站(失败的话,会隔段时间重新发送)
  • 11、网站处理交易后续逻辑(发货、订单状态储蓄之类的)
  • 12、网站返回‘success’字符串给支付宝,表示该通知已经处理,不用再重新发送
  • 13、支付宝显示支付成功的页面给用户(这一步和第10步是不分先后发生的)
  • 14、支付成功页面延迟自动跳转,或用户点击‘返回商品页面’,跳转到页面的支付结束页面(此时不一定成功处理支付宝发来的通知),但会在url中带上当前的订单号和状态

可以发现,整个流程有点像oAuth,主要有三部:

  • 一是申请支付宝交易号(获取token),这一步可以理解,让支付宝验证网站的有效性、让网站指定交易要支付多少钱
  • 二是用户到支付宝页面付款,这一步可以理解为支付宝验证用户的有效性,让用户在一个不受网站监视的环境下进行支付,
  • 三是用户付款后,处理结果告诉用户支付成功(同步通知),另外异步通知网站服务器该订单已经支付

JS中继承

js中的继承小结

构造超类-->

    function Super() {
        this.name="AA"
    }
    Super.prototype.getName=function () {
        console.log(this.name)
    };

1.原型继承

让子类的原型指向 超类的一个实例,就会拥有 实例私有的属性。通过 实例的__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__);
    

2.call继承

只会继承私有的 实例化的时候调用父类的构造函数

    function Sub() {
        Super.call(this);
    }
    var sub=new Sub();
    console.log(sub.name)

3. 对象冒充继承

实例化的时候,把父级私有的公有的都遍历出来,都加到子级的实例私有属性上。没有原型链的联系

  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()*/

4.组合继承

我们的call继承 继承了私有的。缺少公有的方法。那我们可以用
call+原型继承来实现公有属性的继承。 好处是 私有的还在 实例的私有属性上,每一个实例都有自己的。通过 原型链也可以找到公有的属性。缺点是 原型上 多了一份 私有的属性。 超级的构造函数也执行了两次

5. 中间类继承

一种在 超类和 子类加了一层原型链的方式 .当然只是继承原型上的方法,也就是公有的。 最坑的地方就是不兼容 不然用这个 + call继承 可以完美实现 继承

function Sub() {

    }
    Sub.prototype.__proto__=Super.prototype;
    var sub=new Sub();
    console.log(sub.getName);

6. 寄生组合

综合以上的继承方法,我们想实现那种 私有的属性是私有的,公有的就在原型上。只能用 组合式的继承来实现。把共享的属性、方法用原型链继承实现,独享的属性、方法用借用构造函数实现,所以组合继承几乎完美实现了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-开篇

Vuex是什么?

Vuex是一个转为Vue.js应用开发的状态管理模式,它采用集中式储存管理应用的所有组件的状态,并以相应的规则保证以一种可预测的方式变化。

什么是状态管理模式

让我们从一个简单的Vuex计数应用开始

 new Vue({
     data(){
         return{
             count:0
         }
     },
     template:`< div>{{count}}< /div>`,
     methed:{
         increment(){
             this.count++
         }
     }
 }) 
 

在这个状态自管理应用包含以下几个部分

  • state ,驱动应用的数据源
  • view,以声明的方式将state映射到视图
  • action,响应在view上的用户输入导致状态的变化

avatar
但是,当我们遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖统一个状态。
  • 来自不同视图的行为需要变更统一状态

对于问题一,传参的方式对于多层嵌套的组件会非常繁琐,并且对于兄弟组件间的状态传递无能为力,对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝,以上的模式非常脆弱,通常会导致无法维护的代码

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的视图,不管在树的哪一个位置,任何组件都能获取状态或触发行为

另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且容易维护

这就是Vuex背后的基本**,与其他的模式不同,Vuex是专门为Vue.js设计的状态管理库,以利用Vue.js的细粒度数据响应机制来进行高效的状态更新
title

服务器Node环境搭建

服务器Node环境配置

wget命令下载Node.js安装包

  • wget https://nodejs.org/dist/v6.9.5/node-v6.9.5-linux-x64.tar.xz
  • 解压文件 tar xvf node-v6.9.5-linux-x64.tar.xz
  • 创建软链接,使node、npm、pm2命令全局有效
  • ln -s /root/node-v6.9.5-linux-x64/bin/node /usr/local/bin/node
  • ln -s /root/node-v6.9.5-linux-x64/bin/npm /usr/local/bin/npm
  • npm install pm2 -g
  • ln -s /root/node-v6.9.5-linux-x64/bin/pm2 /usr/local/bin/pm2

查看node、npm、pm2版本

  • node -v
  • npm -v
  • pm2 -v

服务器部署方法

  • 将sever文件夹打包上传服务器
  • cd server 进入项目目录
  • npm install 安装依赖
  • rm -rf static 删除服务器脚本的static文件夹

项目上线方法

  • npm run build 在dist目录下生成静态页面资源(static文件夹和index.html)
  • 将index.html放入static文件夹,并将文件夹打包上传服务器脚本所在文件夹,解压
  • pm2 start start.js 在PM2监控下运行脚本,项目上线
  • pm2 stop all 停止所有脚本
  • pm2 kill all 删除所有脚本

git commit提交代码规则

git commit 提交规则

type 的值可以有很多,下面有我们常用的

  • feat:新功能
  • fix:修复bug
  • doc: 文档改变
  • style:代码格式改变
  • refactor:某个已有功能重构
  • perf:性能优化
  • test:增加测试

vue预渲染(Prerendering)—使用prerender-spa-plugin插件处理

vue预渲染——使用Prerender插件处理Vue.js SEO(ps:https://segmentfault.com/a/1190000010613010)

//在 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.js入门学习6-纹理

纹理

一、纹理的描述

纹理就是皮肤

二、纹理是由图片组成

在THREE中,或者在3D引擎中,纹理应该怎样来实现呢,首先应该有一个文理类,其次是有一个加载图片的方法,将这张图片和这个纹理捆绑起来。

在THREE中,纹理类有Three.Texture表示,其构造函数如下所示,

  THREE.Texture(img,mapping,wrapS,wrapT,magFilter,minFilter,format,type,anisotropy)

各个参数的意义是:

  • Image:这是个图片类型,基本上他有ImageUtils来加载,代码如下:
    var image = THREE.ImageUtils.loadTextrue(url), //url是一个图片地址,
  • mapping:是一个THREE.UVMapping()类型,他表示的是纹理的坐标
  • wrapS:表示X轴的纹理回环方式,就是当纹理的宽度小于需要贴图的平面的宽度的时候,平面剩下的部分应该以何种方式贴图的问题
  • wrapT:表示Y轴的纹理回环方式
  • magFilter和minFilter:表示过滤的方式
  • format:表示加载的图片的格式,这个参数可以取值THREE.RGBAFormat、RGBFormet等
  • type:表示存储纹理的内存的每一个字符格式,是符号还是没有符号,是整形还是浮点型,这里默认的是,无符号行
  • anisotropy:各向异性过滤,使用各向异性过滤能够使纹理的效果更好,但是会消耗内存更多内存、CPU、GPU

3、纹理坐标

在正常的情况下,你在0.0到1.0之间的范围指定纹理坐标,当我们用一副图来做纹理的时候,那么这幅图就隐士的被赋予如图一样的纹理坐标,这个纹理坐标将被对应到一个形状上

4、实例

  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、将纹理应用到材质

4.1、画一个平面

    通过PlaneGeometry可以画一个平面,代码如下:
    <pre>var geometry = new THREE.PlaneGeometry(500,300,1,1)</pre>
    这个平面的宽度是500,高度是300

4.2、为平面赋予纹理坐标

平面有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个顶点,还要注意他们之间的顺序是逆时针方向,

4.3、加载纹理

    纹理作为一张图片,可以来源于互联网,或者本地服务器,这里加纹理使用了上面介绍的loadTexture函数,代码如下:
    <pre>
    var texture = THREE.ImageUtils.loadTextrue('a.img',null,function(){})</pre>
    这个函数的第一个参数是一个相对路径,表示与你的网页之间的相对路径,
    第二个参数为null  表示时候要传入一个纹理坐标参数,来覆盖前面在geometry中的参数
    第三个参数是回调函数,表示成功加载完纹理后要执行的参数,参数t是textrue

4.4将纹理应用于材质

加载好纹理,只需要将纹理映射到材质就可以了,我们这里使用一个普通的材质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映射

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.js入门学习2-点线面

点、面、线(1)

  • Three中定义一个点
    在三维空间中的某一个点可以用一个坐标来表示,一个坐标由xyz三个分量构成,在three.js中,点可以在右手坐标系中表示

在几何空间中,点可以用一个向量来表示,在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)
  }
  
  • 1、首先,我们声明一个几何体geometry,
    var geometry = new THREE.Geometry();
    几何体里面有个Vertices变量,可以用来存点
  • 2、定义一种线条的材质,使用THREE.LineBasicMaterial类型来定义,他接受一个集合作为参数,其原型如下:
  LineBasicMaterial(parameters)

Parameters是一个定义材质外观的对象,它包含多个属性来定义材质,这些属性是:

  • Color:线条的颜色,用16进制来表示,默认颜色是白色
  • Linewidth:线条的宽度,默认是一个单位宽度
  • Linecap:线条两端的的外观,默认是圆角端点,当线条比较粗的时候才能看出效果,线条细,看不出效果
  • Linejoin:两个线条连接点处的外观,默认是‘round’,表示圆角
  • VerexColor:定义线条材质是否使用定点颜色,这个是一个布尔值,意思是,线条各部分的颜色会根据定顶点的颜色来进行差值,
  • Fog:定义材质的颜色是否受全局雾效的影响

这里使用了定点颜色,vertexColor:THREE.VertexColor,就是线条的颜色会根据顶点来计算

  var material = new THREE.LinebasicMaterial({VertexColor:THREEColors})
  • 3、接下来,定义两种颜色,分别表示线条两个端点的颜色,如下所示:
  var color1 = new THREE.Color(0x444444)
  var color2 = new THREE.COlor(0xFF0000)
  • 4、定义2个顶点的位置,并放到geometry中,代码如下:
  var p1 = new THREE.Vector3(-100,0,100)
  var p2 = new THREE.Vector3(100,0,-100)
  geometry.vertices.push(p1)
  geometry.vertices.push(p2)
  • 5、为4中定义的2个顶点,设置不同的颜色,代码如下所示:
  geometry.color.push(color1,color2);

geometry中color表示顶点的颜色,必须材质中vertexColor等于THREE.VertexColor时,颜色才有效,如果vertexColor等于THREE.NoColor时,颜色就没效果了,那么就会去取材质中color的值,这个很重要,一定要记住

  • 6、定义一条线

定义线条,使用THREE.line类,代码如下所示:

  var line = new THREE.line(geometry,material,THREE.linePieces)

第一个参数是几何体geometry,里面包含了2个顶点和顶点的颜色,第二个参数是线条的材质,或者是线条的属性,表示线条以哪种方式取色,第三个参数是一组点的链接方式,

然后将这条线放到scene中 scene.add(line)

点面线(二)

坐标系

  • 1、右手坐标系

Threejs使用的是右手坐标系,

  • 2、线条的深入理解

在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)
这样,这就是我们需要的线条
  • 3、平面坐标
    画点与画面的区别
  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)
  }

Node基础

  • 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   是否可写入
    })
  • 19、showHidden 隐藏属性 depth输出对象的深度
    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)   随机分配内存
  • 27、buffer.fill(0) buffer 初始化
  • 28、buffer.write('string',offset,length,encoding) 偏移量包前不包后 要写入的内容 偏移量 长度 编码
  • 29、buffer.toString() 将字符串变成buffer类型
  • 30 buffer.slice 改变截取后的buffer会对原buffer有影响
  • 31、buffer.copy方法
    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数据的转化

    • 1)先转化成buffer var buffer=new Buffer('珠');
      1. 通过索引取出buffer默认是10进制的
      1. 上一步将16进制转化成10进制 在转成2进制
    (231).toString(2)
    (143).toString(2)
    (160).toString(2)
  • 4)将3个8位的 变成4个6位的 不够8位的补0
  • 5)再将2进制转化成10进制的
    parse('00111001',2)
    parse('00111001',2)
    parse('00111001',2)
    parse('00111001',2)
  • 6)将10进制转化成对应的课件字符 通过算出来的10进制找到对应的字母

文件操作

  • 1、所有的方法都是异步和同步同时出现
    readFile  异步
    readFileSync 同步
  • 2、第一个 参数是读取的文件 第二个参数是utf8
    //将我们的文件一直读到内存里,大文件我们不采用这种方法  使用流
    var obj={};
    var name=fs.readfileSync('./name.text',{encoding:'utf8',flag:'r'})
    obj.name=name;
  • 3、异步方法
    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);
        }
    }
 
  • 4、var fs=require('fs');
    • 1)读是读到内容 写是写的数据
    • 2)如果写的文件不存在 会创建这个文件
    • 3)如果默认不写options 那写入的文件格式是utf8格式的
    • 4)没有返回值
    fs.writeFile('./write.txt',new Buffer('珠峰'));
    fs.writeFileSync('./name1.txt',read,{flag:'a'})
    fs.appendFileSync();等同于{flag:'a'}
  • 5、copy 异步方式 回调函数
    function copy(source,target,callback)

webpack基础3-output

输出[output]

配置output选择可以控制webpack如何向硬盘写入编译文件,注意,即使可以存在多个入口起点,但是只指定一个输出配置

用法

在webpack中配置output属性,的最低要求是,将他的值设置为一个对象,包括以下两点:

  • filename用于输出文件的文件名
  • 目标输出目录 path 的绝对路径
    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-State

State

单一状态树

Vuex使用单一状态树,是的,用一个对象就包含全部应用层级的状态,至此他便作为‘唯一数据源(SSOT)’の3而存在,这就意味着,每个应用将仅仅包含一个store实例,单一状态树让我们直接定义任何的状态片段,在调试的过程中也能轻松的取得当前的应用状态的快照

单状态树与模块化并不冲突。

在Vue组件中获得Vuex状态

那么我们如何展示状态呢?由于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辅助函数

当一个组件需要获取多个状态时,将这些状态都声明为就算属性会有些重复和冗余,为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性

    import {mapState} from 'Vuex'

    export default{
        computed:mapState(['count'])
    }

对象展开运算符

mapState函数返回一个对象,我们如何将他与局部计算属性混合使用呢?通常需要使用工具函数将对象合并为一个,以使我们可以将最终的对象传给computed属性,但是自从有了对象展开运算符,我们可以极大的简化写法

    computed:{
        localComputed(){},
        ...mapState(['state'])
    }

webpack-Plugins

插件[Plugins]

插件是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

Vuex-Getter

Getter

有时候我们需要从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'
    ])

ES6--Promise学习

Promise

  • 1、Promise的含义
  • 2、基本用法
  • 3、Promise.prototype.then()
  • 4、Promise.prototype.catch()
  • 5、Promise.all()
  • 6、Promise.race()
  • 7、Promise.resolve()
  • 8、Promise.reject()
  • 9、两个有用的附加方法
  • 10、应用
  • 11、Promise.try()

含义

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.prototype.then()

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

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.all()方法用于将多个Promise实例,包装成一个新的Promise实例。

var p = Promise.all([p1,p2,p3])

上面代码中,Promise.all方法接受一个数组作为参数,p1 p2 p3都是Promise实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理

P的状态由P1 P2 P3决定,分为两种情况

  • (1)只有P1 P2 P3的状态都是fulfilled,p的状态才会变成fulfilled,因此P1 p2 p3的返回值组成一个数组,传递给p的回调函数
  • (2)只有p1 p2 p3之中一个被rejected,p的状态变为rejected,此时第一个被rejected的实例的返回值,会传递给p的返回值
//生成一个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.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.resolve()

有时候需要将现有的对象转化为Promise对象,Promise.resolve方法起到这个作用

var JSPromise = Promise.resolve($.ajax('/whatever.json'))

上面代码转化成Promise对象

Promise.resolve('foo')
//等价于
new Promise(resolve=>resolve('foo'))

Promise.resolve方法的参数分为四种

  • (1)如果参数是Promise实例,那么Promise.resolve将不做任何修改,原封不动的返回这个实例
  • (2)参数是一个thenable对象
  • (3)参数不是具有then方法的对象,或根本不是对象
  • (4)不带有什么参数

Promise.reject()

Promise.reject(reason)方法也返回一个新的Promise实例,该状态为rejected

done 和 finally

  • done()总是处于回调链的尾端,保证抛出任何可能出现的错误
  • finally() 方法用于指定不管Promise对象最后返回的状态,会执行的操作,他与done的方法最大的区别,他接受一个普通的回调函数作为参数,该参数不管怎样都必须执行

Promise应用

加载图片

我们可以将图片加载写成一个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
    })
}

Generator函数与Promise的结合

看完Genertor再写

Promise.try()

实际开发中会遇到一种情况,不知道或者不想区分,函数是同步还是异步操作,但是想用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进行异步操作

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.