曾广营

曾广营 查看完整档案

厦门编辑华中科技大学  |  光学与电子信息学院 编辑六度畅想  |  小游戏项目经理 编辑填写个人主网站
编辑

学习unity、laya、3dmax中

个人动态

曾广营 发布了文章 · 2019-11-26

unity 运动相关学习记录

unity部分文章为学习记录,理解不是很深刻,主要用于自我学习总结,如果有纰漏请大佬轻喷。


一、通过position属性实现物体的运动

1、update循环中调用Vector3.movetowards,直接设置物体的position

输入的参数终点位置、速度
使用场景知道终点坐标
运动类型匀速运动

示例代码:

//target [Vector3]  终点的position
//Speed  [float]  匀速移动的速度,可以设为5.0f  

void Update(){
    transform.position = Vector3.MoveTowards(transform.position, target, Time.deltaTime * Speed);
}

2、update 循环中调用Vector.Lerp,直接设置物体的position

输入的参数终点位置、速度
使用场景知道终点坐标
运动类型运动会越来越慢
//target [Vector3]  终点的position
//Speed  [float]  匀速移动的速度,可以设为5.0f   

void Update(){
    transform.position = Vector3.Lerp(transform.position, target, Time.deltaTime * Speed);
}

3、transform的translate方法

输入的参数运动的方向向量、速度
使用场景不知道终点坐标,会一直运动下去
运动类型匀速运动
//direction [Vector3]  运动的方向,可以设为(0,0,1)
//Speed  [float]  匀速移动的速度,可以设为5.0f 

void Update(){
    transform.Translate(direction * Speed * Time.deltaTime);
}

二、基于刚体,通过力、加速度实现物体的运动

正在学习中

查看原文

赞 2 收藏 0 评论 0

曾广营 赞了文章 · 2018-04-12

前端初学基础知识 3

canvas动画

       动画的形成:先画出一幅图,改变其中的一些参数,重新绘制图片...不断的重复形成动画。

  • fillStyle:设置或返回填充绘画的颜色,渐变或模式
  • strokeStyle:设置或返回用于笔触的颜色,渐变或模式
  • fill(): 填充当前路径(如果路径未关闭则会从路径结束点到开始点之间添加一条线)
  • stroke():绘制已定义的路径。
  • moveTo():把路径移动到画布中的指定点,不创建线条
  • beginPath():起始一条路径或重置当前路径
  • closePath():创建从当前点回到起始点的路径
  • lineTo():添加一个新点,创建一条连接至该点的线条
  • clearRect():清除指定矩形内的像素
  • arc():创建弧或曲线
  • rotate():创建两点之间的弧或曲线
  • translate():重新映射画布上的(0,0)位置
  • drawImage():绘制图像、画布或视频
  • save():保存当前环境状态
  • restore():返回之前保存过的路径和状态
  • getContext():返回一个用于在画布上绘图的环境
  • requestAnimationFrame():回调例程调用 requestAnimationFrame()。根据浏览器设备的绘制限制,来更新动画,回调的次数常是每秒60次。

canvas学习——树镜

整体逻辑
  1. 创建一个类,实例化根元素,递归创建分支直到层数,每层分支长度(length)递减,直至层数>=5;
  2. 调用drawBranch生成一张图片
  3. 改变树枝展开角度以及位置执行requestA nimationFrame();重复执行;形成动画
  4. 将12个不同旋转角度的动画canvas添加到canvas2.围成一圈。
  5. 鼠标移入则停止自动改变树枝状态,转为由鼠标的横纵坐标控制。
关键代码理解
class Branch {
    /**
     * 分枝类,以下为参数,都带有默认值
     * 位置 position
     * 长度 length
     * 分支位置 divergeAt
     * 展开角度 angle
     * 层数 depth
     * 分支展开角度变化量 spread
     */
  constructor(position = {x : 0, y: 0}, length = 100, divergeAt = 0.5, angle = 0, depth = 0, spread = 45 * TO_RADIAN) {
    this.position = position;
    this.length = length;
    this.divergeAt = divergeAt;
    this.angle = angle;
    this.depth = depth;

    this.color = '#000';
    this.spread = spread;
    this.branches = [];

    this.grow();
  }

  grow() {
      /**
       * 创建分支,如果canBranch = true(未达到最大分支数量)
       * 新分支长度为父级的0.75,深度加1
       * 展开角度变化spread
       * 由于构造方法中调用了grow方法,所以会不断重复创建上述过程
       */
    if (!this.canBranch) {
      return;
    }

    this.branches = [];

    const nextLength = this.length * 0.75;
    const nextDepth = this.depth + 1;

    this.branches.push(
      new Branch(
        this.growPosition,
        nextLength,
        this.divergeAt,
        this.angle + this.spread,
        nextDepth,
        this.spread
      ),
      new Branch(
        this.growPosition,
        nextLength,
        this.divergeAt,
        this.angle - this.spread,
        nextDepth,
        this.spread
      )
    );
  }

  update(spread, divergeAt) {
    this.spread = spread;
    this.divergeAt = divergeAt;

    this.grow();
  }

  get growPosition() {
    const dl = this.length * this.divergeAt;

    return {
      x: this.position.x + (Math.cos(this.angle) * dl),
      y: this.position.y + (Math.sin(this.angle) * dl),
    };
  }

  get canBranch() {
    return this.depth < maxDepth;
  }
}
    /**
     * 保存当前状态
     * 根据branch类中的数据画图(颜色,直线和圆点)
     * 递归对分支进行绘图
     */
const drawBranch = (branch, phase) => {
  const h = ~~(200 + (160 * phase));
  const l = 50 + ~~(((branch.depth / (maxDepth + 1))) * 50);

  const endX = branch.length;
  const endY = 0;
  const r = 2;

  ctx.save();

  ctx.strokeStyle = `hsl(${h}, 100%, ${l}%)`;
  ctx.translate(branch.position.x, branch.position.y);
  ctx.rotate(branch.angle);

  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(endX, endY);
  ctx.stroke();
  ctx.closePath();

  ctx.beginPath();
  ctx.fillStyle = `hsl(${h}, 100%, 50%)`;
  ctx.arc(endX, endY, r, 0, PI * 2, false);
  ctx.fill();
  ctx.closePath();

  ctx.restore();

  branch.branches.forEach((b) => {
    drawBranch(b, phase);
  });
};
const loop = () => {
    /**
     * 改变类中参数的值
     * 将第一个canvas添加到在第二个canvas中循环12次每次旋转2PI/12的角度,形成一个环状
     * 调用requestAnimationFrame() 重新根据类中的数据画图
     */
  let phase = rootBranch.spread / maxSpread;

  clear(phase);

  if (autoAnimate) {
    phase = map(Math.sin(autoTick), -1, 1, 0, 1);

    spread = phase * maxSpread;
    divergeAt = map(Math.sin(autoTick), -1, 1, 0, 0.5);

    autoTick += autoSpeed;
  }

  rootBranch.update(spread, divergeAt);

  drawBranch(rootBranch, phase);

  const numSegments = 12;
  const angleInc = PI * 2 / numSegments;
  let angle = tick;

  for (let i = 0; i < numSegments; i++) {
    ctx2.save();
    ctx2.translate(midX, midY);
    ctx2.rotate(angle);
    ctx2.drawImage(canvas, -w / 2, -h / 2);
    ctx2.restore();
    angle += angleInc;
  }

  tick += 0.002;

  requestAnimationFrame(loop);
};

element-ui

安装

  • npm安装: npm i element-ui -s
  • cdn

引入

完整引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
按需引入

需要安装 babel-plugin-component:

npm install babel-plugin-component -D

在.babelrc中添加:

"plugins": [
    "transform-vue-jsx", "transform-runtime",
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
import {Loading, Tabs, TabPane,} from 'element-ui';

Vue.use(Loading);
Vue.use(Tabs);
Vue.use(TabPane);

自定义主题

在项目中直接改变scss变量

/* 改变主题色变量 */
$--color-primary: teal;

/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";

命令行主题工具

npm i element-theme -g

之后步骤为:

  1. 安装主题
  2. 初始化变量文件
  3. 修改变量
  4. 编译成css
  5. 引入

JavaScript 对象的深拷贝

       在星盘改版的时候遇到了相关的问题,数据从父组件传递到子组件,当时没考虑到这方面的问题,只是对数据进行了一层的拷贝,因为当时传进来的数据是有很多层的,所以还是会导致源数据改变。所以总结一下,写成一个方法,这个应该会比较常用。

       Object属于引用类型,对它进行简单赋值(obj1 = obj2)的话只是创建一个指针指向原数据的地址,改变它的值也会改变源数据的值,会造成很多问题。

基本的深拷贝方法

数组

concat,slice 等方法,es6 新增运算符‘...’

对象

思路是把对象拆开分别赋值,同样可以使用es6 新增运算符‘...’,for循环等。

深拷贝的实现

运用递归逐层拷贝。

function depCopy(obj){
    let value;
    if(typeof obj === "object"){
      if(Object.prototype.toString.call(obj).slice(8,-1)==='Array'){
        // debugger;
        value = [];
        for(let i = 0, length = obj.length; i<length; i++){
          value.push(depCopy(obj[i]));
        }
      }
      else{
        value = {};
        for(let j in obj){
          value[j] = depCopy(obj[j]);
        }
      }
    }
    else{
      value = obj;
    }
    return value;
  }
查看原文

赞 1 收藏 0 评论 0

曾广营 关注了专栏 · 2018-04-04

前端早早聊

技术经用得上、听得懂、抄得走,加微 codingdreamer 围观

关注 595

曾广营 关注了用户 · 2018-04-04

Scott @codingdream

Scott,10 年工程师生涯,坚信创业是场修行。

曾任职阿里巴巴前端工程师、Moveha|CR CTO,宋小菜前端团队负责人,从 6 人到 20 人搭建团队,专注供应链大前端的跨端工具工程化,专注年轻工程师的成长与潜力发掘。

关注 1002

曾广营 赞了回答 · 2018-03-30

vue中图片路径问题

/static/img表示从你项目根目录下开始,根目录下的static下的img下的..而不写/的话应当查找相对该文件的路径,与该文件同级的叫static的文件夹下的img..,./的作用也是从当前目录开始查找

关注 4 回答 3

曾广营 关注了用户 · 2018-03-30

范洋 @fanyang_5aaa1cff40bc5

关注 18

曾广营 赞了文章 · 2018-03-30

初学vue.js记录(二)

前言

上一篇我大致讲述了vue2.0文档中的一些基础内容,而本章我将稍微介绍一下上一篇提到的其他3个方面的内容。

正文

vue-router

  • 路由嵌套

    最近选择了一个网易云音乐的app来模仿着练习,目前才开始不久~
    在刚开始时先把静态的主要几个页面搭建起来,在这过程中我发现单单一个主页面的路由显示区域貌似不太够用,因为有时需要在不改变大页面的情况下去改变当前页面其中的一部分区域,这时就需要用到路由嵌套了。大致有以下几个步骤:
    (1)在控制当前页面的组件内需要嵌入路由的位置添加router-view:

       <head-tab></head-tab>
       <router-view></router-view>
       <my-foot></my-foot>

    (2)新建组件,这是在嵌套的路由中显示的内容,然后导出;这些子页面的存放位置看个人喜好,不过最好还是新建一个与主组件同名的文件夹,把子组件都放进去。
    (3)在配置路由的js文件中找到主页面对应的对象,在对象内添加一个叫children的数组,数组内存放下一级的路由名字及控制的组件,这样当访问子路由的路径时不会渲染在大的router-view上,而只在对应页面的对应router-view内改变。

     path: '/index',
     name: 'homePage',
     component: homePage,
     children:[
         {
             path:'/index/music',
             component:music
         },
         {
             path:'/index/social',
             component:social
         }
     ]

    关于子路由的命名,官方文档内是直接/name,我个人觉得最好是把它的上一级路由也写上更好,如/index/name。当然,还是看个人喜好~~

  • 编程式导航
    在路由跳转上,有两种方式可以跳转:声明式导航与编程式导航。个人认为编程式导航比较简便~
    在js内可通过this.$route来控制路由,而属性值内写js的话使用$router来控制。这里需要介绍一下操作路由的几种方法:

    router.push('/index')   //像数组方法一样,将一个新的路由插入到路由对象的最后一个,也就是显示最新的路由
    router.push({name:'user',params:{userId:123}}) //带参路由,参数会加入到路由名后,/user/123
    router.push({path:'user',query:{username:'jack'}})  //带参路由,参数会像get请求拼接参数,/user?username=jack
    router.replace()  //与push方法类似,区别在于,它是用新的路由替换当前路由,如果需要返回上一页,只能返回上一页的上一页
    router.go()  //参数为整数,表示在访问历史记录中前进或者后退的步数,如-1:上一页,1:下一页
  • 路由重定向
    当访问一些不同的路由的时候我们却想让它跳转到同一个页面,这时就要用到它了。
    在routes数组对象中添加对象,需要2个参数:path和redirect,如下:

    {path:'/',redirect: "/index"}
    
    

axios

如果想用axios的话,如果是在正常的html文档内,可直接用script标签引入链接,如果是cmd内使用vue命令生成的项目可以在命令行内输入cnpm install axios,添加模块到项目内。
下面就讲讲基本的使用。

import axios from 'axios'  //首先从依赖文件内引入axios    
axios.get('/static/music.json')    //这是请求一个本地的json文件的例子
.then((data)=>{
   console.log(data)
   this.dataList=data.data.music   //成功后把数据存到vue对象的data里去 
})

不过在这里有遇到问题,我第一次写的是:

.then(function(data){
    this.dataList=data.data.music
    console.log(data)
    console.log(this)
})

如果我使用function的话this指针为空,如果使用es6的那种写法的话this指针能指向外部的vue对象,于是我就想data数据有了要不return出去吧,再用一个变量接收,但是打印出来之后:

Promise {<pending>}
__proto__
:
Promise
[[PromiseStatus]]
:
"resolved"
[[PromiseValue]]
:
Array(5)

是个这样的promise对象,我是百思不得其解,不知道如何获取里面的数据。
后来我查了下网上的es6箭头函数介绍,原来如此:

this指向
function传统定义的函数,this指向随着调用环境的改变而改变,而箭头 函数中的指向则是固定不变,一直指向定义环境的。

至于后面那个问题也得到了解释,promise对象也是es6里面的东西,它代表了异步执行可以得到的结果,里面存的是某个未来才会结束的事件,只是它是在未来才能得到。而外部得到的就是这么一个对象,虽然看着有数据但是并没有什么用,它无法被改变,无法被获取。
promise对象具有3种状态,pending(进行中),fulfilled(已成功)和rejected(已失败)。只有从pending状态变为fulfilled或者rejected,状态才会凝固,这时称为resolved(已定型),这时再对promise添加回调函数就能立即得到结果,而所谓回调函数就是then。promise实例都具有then方法和catch方法,这两个方法是定义在原型上的,使用then捕获成功后的结果,而使用catch捕获失败的结果。
更具体的内容可以到这看:http://es6.ruanyifeng.com/#do...

scss

SCSS 是 Sass 3 引入新的语法,其语法完全兼容 CSS3,并且继承了 Sass 的强大功能。也就是说,任何标准的 CSS3 样式表都是具有相同语义的有效的 SCSS 文件。
如果你想使用scss,需要先做一些准备:
(1)sass基于Ruby语言开发而成,因此安装sass前需要安装Ruby
(2)安装完成后需测试安装有没有成功,运行CMD输入以下命令:

ruby -v
//如安装成功会打印
ruby 2.5.0p0 (2017-12-25 revision 61468) [x64-mingw32]

(3)如上已经安装成功。但因为国内网络的问题导致gem源间歇性中断因此我们需要更换gem源。(使用淘宝的gem源https://ruby.taobao.org/)如下:

//1.删除原gem源
gem sources --remove https://rubygems.org/

//2.添加国内淘宝源
gem sources -a https://ruby.taobao.org/

//3.打印是否替换成功
gem sources -l

//4.更换成功后打印如下
*** CURRENT SOURCES ***
https://ruby.taobao.org/

如果要在vue组件里的style内写scss需要声明:

<style lang="scss" scoped="scoped">

scoped是限制此页内的样式不会影响到其他页面的样式。
这时运行vue项目会发现缺少两个依赖:
图片描述图片描述

这时到项目的目录下运行cmd,手动地安装这2个依赖项:

cnpm install sass-loader
cnpm install node-sass

完成之后,重新npm run dev项目,就能正常使用了。
相比普通的css,使用scss来书写能减少代码量,增强结构性,想想,把css写的跟html结构代码似的有很强的结构性,这样一看就知道哪些代码控制哪个元素。比如:

.search-box{
    .top{
        width: 100%;
        height: 2.5rem;
        text-align: left;
        background-color: #C62F2F;
        .back{
            display: inline-block;
            transform: rotate(90deg);
            color: #fff;
            line-height: 2.5rem;
            margin-left: 0.7rem;
        }
    }
}

示例写的结构很简单,看起来和普通的css貌似没有太大区别,但是如果页面内容很多,css代码一长串时,当你想找跟某个元素相关的所有样式时,你会发现,太累了,眼花缭乱,自从我用了scss写样式之后老眼昏花啥的都没有出现过~~

  • 变量定义与使用
    在scss中,是可以定义变量的。比如要在很多地方用到一个相同的颜色,这时定义一个颜色:

     $highlight-color:#f90;
     .txt{
         color: $highlight-color;
     }

    这样是不是方便很多?只要记住想要的变量名,不用再去复制粘贴具体是什么样式了~

  • 嵌套规则
    上面已经说过了~
  • 父选择器标志符&
    在嵌套的样式规则中,可以用&符号来代表嵌套到当前层次的所有父选择器,可用于伪类选择器,如:

     article a{
         color: blue;
         &:hover{color: red;}
     }
  • 群组选择器
    scss也能像css一样用群组选择器
  • 子组合选择器和同层组合选择器

     >:离的最近的一代子元素
     +:同级最近的下一个兄弟
     ~:选择所有同层的元素
     均可应用到sass的规则嵌套中。可以把它们放在外层选择器后边,或里层选择器前边
     article{
         ~ article{border-top:1px solid #ccc}
         > section{background:#eee}
         dl>{
             dt{color:#333;}
             dd{color: #555;}
         }
     }
  • 属性嵌套

     nav{
         border:{
             style:solid;
             width: 1px;
             color: #ccc;
         }
     }
  • 设置默认值

     //在某些值被重复设置时,可设置一个默认值,若有新的声明则用新的,若没有则使用默认值
     $fancybox-width:400px !default;
     .fancybox{
         width: $fancybox-width;
     }
  • 嵌套导入

     //有一个叫_blue-theme.scss的局部文件,在导入时会直接嵌在导入的位置,还可省略下划线
     .blue-theme{@import "blue-theme"}
  • 混合器mixin

     //若网站中有几处样式类似,可使用统一变量来处理。但若有大量复杂的代码时,可使用混合器
     //混合器以@mixin标识符定义,这个标志符给一大段样式赋予一个名字,就可以使用这个名字重复这段样式。
     @mixin rounded-corners{
         -moz-border-radius: 5px;
         -webkit-border-radius: 5px;
         border-radius: 5px;
     }
     //然后使用@include来引用,它会直接把内容嵌入相应的位置
     .notice{
         background-color: green;
         border:2px solid #00aa00;
         @include rounded-corners;
     }

    注意:若能为混合器内的样式取一个好的名字,那么往往能构建一个合适的混合器,若不能,也许这段样式不适合写成混合器。

     //混合器还可以传值,这样混合器生成的代码也不一定一样
     @mixin link-colors($normal,$hover,$visited){
         color: $normal;
         &:hover{color: $hover;}
         &:visited{color: $visited;}
     }
     a{
         @include link-colors(blue,yellow,red);
     }
  • 继承样式

     //一个选择器可以继承另一个选择器的样式,可以精简代码
     .error{
         border: 1px solid red;
         background-color: #fdd;
     }
     .seriousError{
         @extend .error;
         border-width: 3px;
     }
     

心得体会

基础知识差不多也就这么多了,接下来就是不断的钻研。本文意在帮助和我一样的新手童鞋上路,高手请绕道o(╯□╰)o

查看原文

赞 5 收藏 4 评论 2

曾广营 关注了用户 · 2018-03-30

不快乐的程序员 @zhangweiqing

关注 11

曾广营 赞了文章 · 2018-03-30

从头实现一个koa框架

欢迎去我的博客观看:https://github.com/mly-zju/blog/issues/9

koa.js是最流行的node.js后端框架之一,有很多网站都使用koa进行开发,同时社区也涌现出了一大批基于koa封装的企业级框架。然而,在这些亮眼的成绩背后,作为核心引擎的koa代码库本身,却非常的精简,不得不让人惊叹于其巧妙的设计。

在平时的工作开发中,笔者是koa的重度用户,因此对其背后的原理自然也是非常感兴趣,因此在闲暇之余进行了研究。不过本篇文章,并不是源码分析,而是从相反的角度,向大家展示如何从头开发实现一个koa框架,在这个过程中,koa中最重要的几个概念和原理都会得到展现。相信大家在看完本文之后,会对koa有一个更深入的理解,同时在阅读本文之后再去阅读koa源码,思路也将非常的顺畅。

首先放出笔者实现的这个koa框架代码库地址:simpleKoa

需要说明的是,本文实现的koa是koa 2版本,也就是基于async/await的,因此需要node版本在7.6以上。如果读者的node版本较低,建议升级,或者安装babel-cli,利用其中的babel-node来运行例子。

四条主线

笔者认为,理解koa,主要需要搞懂四条主线,其实也是实现koa的四个步骤,分别是

  1. 封装node http Server
  2. 构造resquest, response, context对象
  3. 中间件机制
  4. 错误处理

下面就一一进行分析。

主线一:封装node http Server: 从hello world说起

首先,不考虑框架,如果使用原生http模块来实现一个返回hello world的后端app,代码如下:

let http = require('http');

let server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world');
});

server.listen(3000, () => {
    console.log('listenning on 3000');
});

实现koa的第一步,就是对这个原生的过程进行封装,为此,我们首先创建application.js实现一个Application对象:

// application.js
let http = require('http');

class Application {

    /**
     * 构造函数
     */
    constructor() {
        this.callbackFunc;
    }

    /**
     * 开启http server并传入callback
     */
    listen(...args) {
        let server = http.createServer(this.callback());
        server.listen(...args);
    }

    /**
     * 挂载回调函数
     * @param {Function} fn 回调处理函数
     */
    use(fn) {
        this.callbackFunc = fn;
    }

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            this.callbackFunc(req, res);
        };
    }

}

module.exports = Application;

然后创建example.js:

let simpleKoa = require('./application');
let app = new simpleKoa();

app.use((req, res) => {
    res.writeHead(200);
    res.end('hello world');
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

可以看到,我们已经初步完成了对于http server的封装,主要实现了app.use注册回调函数,app.listen语法糖开启server并传入回调函数了,典型的koa风格。

但是美中不足的是,我们传入的回调函数,参数依然使用的是reqres,也就是node原生的request和response对象,这些原生对象和api提供的方法不够便捷,不符合一个框架需要提供的易用性。因此,我们需要进入第二条主线了。

主线二:构造request, response, context对象

如果阅读koa文档,会发现koa有三个重要的对象,分别是request, response, context。其中request是对node原生的request的封装,response是对node原生response对象的封装,context对象则是回调函数上下文对象,挂载了koa request和response对象。下面我们一一来说明。

首先要明确的是,对于koa的request和response对象,只是提供了对node原生request和response对象的一些方法的封装,明确了这一点,我们的思路是,使用js的getter和setter属性,基于node的对象req/res对象封装koa的request/response对象。

规划一下我们要封装哪些易用的方法。这里在文章中为了易懂,姑且只实现以下方法:

对于simpleKoa request对象,实现query读取方法,能够读取到url中的参数,返回一个对象。

对于simpleKoa response对象,实现status读写方法,分别是读取和设置http response的状态码,以及body方法,用于构造返回信息。

而simpleKoa context对象,则挂载了request和response对象,并对一些常用方法进行了代理。

首先创建request.js:

// request.js
let url = require('url');

module.exports = {

    get query() {
        return url.parse(this.req.url, true).query;
    }

};

很简单,就是导出了一个对象,其中包含了一个query的读取方法,通过url.parse方法解析url中的参数,并以对象的形式返回。需要注意的是,代码中的this.req代表的是node的原生request对象,this.req.url就是node原生request中获取url的方法。稍后我们修改application.js的时候,会为koa的request对象挂载这个req。

然后创建response.js:

// response.js
module.exports = {

    get body() {
        return this._body;
    },

    /**
     * 设置返回给客户端的body内容
     *
     * @param {mixed} data body内容
     */
    set body(data) {
        this._body = data;
    },

    get status() {
        return this.res.statusCode;
    },

    /**
     * 设置返回给客户端的stausCode
     *
     * @param {number} statusCode 状态码
     */
    set status(statusCode) {
        if (typeof statusCode !== 'number') {
            throw new Error('statusCode must be a number!');
        }
        this.res.statusCode = statusCode;
    }

};

也很简单。status读写方法分别设置或读取this.res.statusCode。同样的,这个this.res是挂载的node原生response对象。而body读写方法分别设置、读取一个名为this._body的属性。这里设置body的时候并没有直接调用this.res.end来返回信息,这是考虑到koa当中我们可能会多次调用response的body方法覆盖性设置数据。真正的返回消息操作会在application.js中存在。

然后我们创建context.js文件,构造context对象的原型:

// context.js
module.exports = {

    get query() {
        return this.request.query;
    },

    get body() {
        return this.response.body;
    },

    set body(data) {
        this.response.body = data;
    },

    get status() {
        return this.response.status;
    },

    set status(statusCode) {
        this.response.status = statusCode;
    }

};

可以看到主要是做一些常用方法的代理,通过context.query直接代理了context.request.querycontext.bodycontext.status代理了context.response.bodycontext.response.status。而context.requestcontext.response则会在application.js中挂载。

由于context对象定义比较简单并且规范,当实现更多代理方法时候,这样一个一个通过声明的方式显然有点笨,js中,设置setter/getter,可以通过对象的__defineSetter____defineSetter__来实现。为此,我们精简了上面的context.js实现方法,精简版本如下:

let proto = {};

// 为proto名为property的属性设置setter
function delegateSet(property, name) {
    proto.__defineSetter__(name, function (val) {
        this[property][name] = val;
    });
}

// 为proto名为property的属性设置getter
function delegateGet(property, name) {
    proto.__defineGetter__(name, function () {
        return this[property][name];
    });
}

// 定义request中要代理的setter和getter
let requestSet = [];
let requestGet = ['query'];

// 定义response中要代理的setter和getter
let responseSet = ['body', 'status'];
let responseGet = responseSet;

requestSet.forEach(ele => {
    delegateSet('request', ele);
});

requestGet.forEach(ele => {
    delegateGet('request', ele);
});

responseSet.forEach(ele => {
    delegateSet('response', ele);
});

responseGet.forEach(ele => {
    delegateGet('response', ele);
});

module.exports = proto;

这样,当我们希望代理更多request和response方法的时候,可以直接向requestGet/requestSet/responseGet/responseSet数组中添加method的名称即可(前提是在request和response中实现了)。

最后让我们来修改application.js,基于刚才的3个对象原型来创建request, response, context对象:

// application.js
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

class Application {

    /**
     * 构造函数
     */
    constructor() {
        this.callbackFunc;
        this.context = context;
        this.request = request;
        this.response = response;
    }

    /**
     * 开启http server并传入callback
     */
    listen(...args) {
        let server = http.createServer(this.callback());
        server.listen(...args);
    }

    /**
     * 挂载回调函数
     * @param {Function} fn 回调处理函数
     */
    use(fn) {
        this.callbackFunc = fn;
    }

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            this.callbackFunc(ctx).then(respond);
        };
    }

    /**
     * 构造ctx
     * @param {Object} req node req实例
     * @param {Object} res node res实例
     * @return {Object} ctx实例
     */
    createContext(req, res) {
        // 针对每个请求,都要创建ctx对象
        let ctx = Object.create(this.context);
        ctx.request = Object.create(this.request);
        ctx.response = Object.create(this.response);
        ctx.req = ctx.request.req = req;
        ctx.res = ctx.response.res = res;
        return ctx;
    }

    /**
     * 对客户端消息进行回复
     * @param {Object} ctx ctx实例
     */
    responseBody(ctx) {
        let content = ctx.body;
        if (typeof content === 'string') {
            ctx.res.end(content);
        }
        else if (typeof content === 'object') {
            ctx.res.end(JSON.stringify(content));
        }
    }

}

可以看到,最主要的是增加了createContext方法,基于我们之前创建的context 为原型,使用Object.create(this.context)方法创建了ctx,并同样通过Object.create(this.request)Object.create(this.response)创建了request/response对象并挂在到了ctx对象上面。此外,还将原生node的req/res对象挂载到了ctx.request.req/ctx.reqctx.response.res/ctx.res对象上。

回过头去看我们之前的context/request/response.js文件,就能知道当时使用的this.res或者this.response之类的是从哪里来的了,原来是在这个createContext方法中挂载到了对应的实例上。一张图来说明其中的关系:

构建了运行时上下文ctx之后,我们的app.use回调函数参数就都基于ctx了。

下面一张图描述了ctx对象的结构和继承关系:

clipboard.png

最后回忆我们的ctx.body方法,并没有直接返回消息体,而是将消息存储在了一个变量属性中。为了每次回调函数处理结束之后返回消息,我们创建了responseBody方法,主要作用就是通过ctx.body读取存储的消息,然后调用ctx.res.end返回消息并关闭连接。从方法中知道,我们的body消息体可以是字符串,也可以是对象(会序列化为字符串返回)。注意这个方法的调用是在回调函数结束之后调用的,而我们的回调函数是一个async函数,其执行结束后会返回一个Promise对象,因此我们只需要在其后通过.then方法调用我们的responseBody即可,这就是this.callbackFunc(ctx).then(respond)的意义。

然后我们来测试一下目前为止的框架。修改example.js如下:

let simpleKoa = require('./application');
let app = new simpleKoa();

app.use(async ctx => {
    ctx.body = 'hello ' + ctx.query.name;
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

可以看到这个时候我们通过app.use传入的已经不再是原生的function (req, res)回调函数,而是koa2中的async函数,接收ctx作为参数。为了测试,在浏览器访问localhost:3000?name=tom,可以看到返回了'hello tom',符合预期。

这里再插入分析一个知识概念。从刚才的实现中,我们知道了this.context是我们的中间件中上下文ctx对象的原型。因此在实际开发中,我们可以将一些常用的方法挂载到this.context上面,这样,在中间件ctx中,我们也可以方便的使用这些方法了,这个概念就叫做ctx的扩展,一个例子是阿里的egg.js框架已经把这个扩展机制作为一部分,融入到了框架开发中。

下面就展示一个例子,我们写一个echoData的方法作为扩展,传入errno, data, errmsg,能够给客户端返回结构化的消息结果:

let SimpleKoa = require('./application');
let app = new SimpleKoa();

// 对ctx进行扩展
app.context.echoData = function (errno = 0, data = null, errmsg = '') {
    this.res.setHeader('Content-Type', 'application/json;charset=utf-8');
    this.body = {
        errno: errno,
        data: data,
        errmsg: errmsg
    };
};

app.use(async ctx => {
    let data = {
        name: 'tom',
        age: 16,
        sex: 'male'
    }
    // 这里使用扩展,方便的返回utf-8格式编码,带有errno和errmsg的消息体
    ctx.echoData(0, data, 'success');
});

app.listen(3000, () => {
    console.log('listenning on 3000');
});

主线三:中间件机制

到目前为止,我们成功封装了http server,并构造了context, request, response对象。但最重要的一条主线却还没有实现,那就是koa的中间件机制。

关于koa的中间件洋葱执行模型,koa 1中使用的是generator + co.js执行的方式,koa 2中则使用了async/await。关于koa 1中的中间件原理,我曾写过一篇文章进行解释,请移步:深入探析koa之中间件流程控制篇

这里我们实现的是基于koa 2的,因此再描述一下原理。为了便于理解,假设我们有3个async函数:

async function m1(next) {
    console.log('m1');
    await next();
}

async function m2(next) {
    console.log('m2');
    await next();
}

async function m3() {
    console.log('m3');
}

我们希望能够构造出一个函数,实现的效果是让三个函数依次执行。首先考虑想让m2执行完毕后,await next()去执行m3函数,那么显然,需要构造一个next函数,作用是调用m3,然后作为参数传给m2

let next1 = async function () {
    await m3();
}

m2(next1);

// 输出:m2,m3

进一步,考虑从m1开始执行,那么,m1的next参数需要是一个执行m2的函数,并且给m2传入的参数是m3,下面来模拟:

let next1 = async function () {
    await m3();
}

let next2 = async function () {
    await m2(next1);
}

m1(next2);

// 输出:m1,m2,m3

那么对于n个async函数,希望他们按顺序依次执行呢?可以看到,产生nextn的过程能够抽象为一个函数:

function createNext(middleware, oldNext) {
    return async function () {
        await middleware(oldNext);
    }
}

let next1 = createNext(m3, null);
let next2 = createNext(m2, next1);
let next3 = createNext(m1, next2);

next3();

// 输出m1, m2, m3

进一步精简:

let middlewares = [m1, m2, m3];
let len = middlewares.length;

// 最后一个中间件的next设置为一个立即resolve的promise函数
let next = async function () {
    return Promise.resolve();
}
for (let i = len - 1; i >= 0; i--) {
    next = createNext(middlewares[i], next);
}

next();

// 输出m1, m2, m3

至此,我们也有了koa中间件机制实现的思路,新的application.js如下:

/**
 * @file simpleKoa application对象
 */
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('.//response');

class Application {

    /**
     * 构造函数
     */
    constructor() {
        this.middlewares = [];
        this.context = context;
        this.request = request;
        this.response = response;
    }

    // ...省略中间 

    /**
     * 中间件挂载
     * @param {Function} middleware 中间件函数
     */
    use(middleware) {
        this.middlewares.push(middleware);
    }

    /**
     * 中间件合并方法,将中间件数组合并为一个中间件
     * @return {Function}
     */
    compose() {
        // 将middlewares合并为一个函数,该函数接收一个ctx对象
        return async ctx => {

            function createNext(middleware, oldNext) {
                return async () => {
                    await middleware(ctx, oldNext);
                }
            }

            let len = this.middlewares.length;
            let next = async () => {
                return Promise.resolve();
            };
            for (let i = len - 1; i >= 0; i--) {
                let currentMiddleware = this.middlewares[i];
                next = createNext(currentMiddleware, next);
            }

            await next();
        };
    }

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let fn = this.compose();
            return fn(ctx).then(respond);
        };
    }

    // ...省略后面 

}

module.exports = Application;

可以看到,首先对app.use进行改造了,每次调用app.use,就向this.middlewares中push一个回调函数。然后增加了一个compose()方法,利用我们前文分析的原理,对middlewares数组中的函数进行组装,返回一个最终的函数。最后,在callback()方法中,调用compose()得到最终回调函数,并执行。

改写example.js验证一下中间件机制:

let simpleKoa = require('./application');
let app = new simpleKoa();

let responseData = {};

app.use(async (ctx, next) => {
    responseData.name = 'tom';
    await next();
    ctx.body = responseData;
});

app.use(async (ctx, next) => {
    responseData.age = 16;
    await next();
});

app.use(async ctx => {
    responseData.sex = 'male';
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

// 返回{ name: "tom", age: 16, sex: "male"}

例子中一共三个中间件,分别对responseData增加了name, age, sex属性,最后返回该数据。

至此,一个koa框架基本已经浮出水面了,不过我们还需要进行最后一个主线的分析:错误处理。

主线四:错误处理

一个健壮的框架,必须保证在发生错误的时候,能够捕获错误并有降级方案返回给客户端。但显然现在我们的框架还做不到这一点,假设我们修改一下例子,我们的中间件中,有一个发生错误抛出了异常:

let simpleKoa = require('./application');
let app = new simpleKoa();

let responseData = {};
app.use(async (ctx, next) => {
    responseData.name = 'tom';
    await next();
    ctx.body = responseData;
});

app.use(async (ctx, next) => {
    responseData.age = 16;
    await next();
});

app.use(async ctx => {
    responseData.sex = 'male';
    // 这里发生了错误,抛出了异常
    throw new Error('oooops');
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

这个时候访问浏览器,是得不到任何响应的,这是因为异常并没有被我们的框架捕获并进行降级处理。回顾我们application.js中的中间件执行代码:

// application.js
// ...
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let fn = this.compose();
            return fn(ctx).then(respond);
        };
    }
// ...

其中我们知道,fn是一个async函数,执行后返回一个promise,回想promise的错误处理是怎样的?没错,我们只需要定义一个onerror函数,里面进行错误发生时候的降级处理,然后在promise的catch方法中引用这个函数即可。

于此同时,回顾koa框架,我们知道在错误发生的时候,app对象可以通过app.on('error', callback)订阅错误事件,这有助于我们几种处理错误,比如打印日志之类的操作。为此,我们也要对Application对象进行改造,让其继承nodejs中的events对象,然后在onerror方法中emit错误事件。改造后的application.js如下:

/**
 * @file simpleKoa application对象
 */

let EventEmitter = require('events');
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

class Application extends EventEmitter {

    /**
     * 构造函数
     */
    constructor() {
        super();
        this.middlewares = [];
        this.context = context;
        this.request = request;
        this.response = response;
    }

    // ...

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let onerror = (err) => this.onerror(err, ctx);
            let fn = this.compose();
            // 在这里catch异常,调用onerror方法处理异常
            return fn(ctx).then(respond).catch(onerror);
        };
    }

    // ... 

    /**
     * 错误处理
     * @param {Object} err Error对象
     * @param {Object} ctx ctx实例
     */
    onerror(err, ctx) {
        if (err.code === 'ENOENT') {
            ctx.status = 404;
        }
        else {
            ctx.status = 500;
        }
        let msg = err.message || 'Internal error';
        ctx.res.end(msg);
        // 触发error事件
        this.emit('error', err);
    }

}

module.exports = Application;

可以看到,onerror方法的对异常的处理主要是获取异常状态码,当err.code为'ENOENT'的时候,返回的消息头设置为404,否则默认设置为500,然后消息体设置为err.message,如果异常中message属性为空,则默认消息体设置为'Internal error'。此后调用ctx.res.end返回消息,这样就能保证即使异常情况下,客户端也能收到返回值。最后通过this.emit出发error事件。

然后我们写一个example来验证错误处理:

let simpleKoa = require('./application');
let app = new simpleKoa();

app.use(async ctx => {
    throw new Error('ooops');
});

app.on('error', (err) => {
    console.log(err.stack);
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

浏览器访问'localhost:3000'的时候,得到返回'ooops',同时http状态码为500 。同时app.on('error')订阅到了异常事件,在回调函数中打印出了错误栈信息。

关于错误处理,这里多说一点。虽然koa中内置了错误处理机制,但是实际业务开发中,我们往往希望能够自定义错误处理方式,这个时候,比较好的办法是在最开头增加一个错误捕获中间件,然后根据错误进行定制化的处理,比如:

// 错误处理中间件
app.use(async (ctx, next) => {
    try {
        await next();
    }
    catch (err) {
        // 在这里进行定制化的错误处理
    }
});
// ...其他中间件

至此,我们就完整实现了一个轻量版的koa框架。

结语

完整的simpleKoa代码库地址为:simpleKoa,里面还附带了一些example。

理解了这个轻量版koa的实现原理,读者还可以去看看koa的源码,会发现机制和我们实现的框架是非常类似的,无非是多了一些细节,比如说,完整koa的context/request/response方法上面挂载了更多好用的method,或者很多方法中容错处理更好等等。具体在本文中就不展开讲了,留给感兴趣的读者去探索吧~。

查看原文

赞 40 收藏 75 评论 7

曾广营 赞了文章 · 2018-03-29

前端初学基础知识 2

1.SCSS语法

变量申明

  • $+变量名+:+变量值 例$width:200px
  • $width:200px 普通变量
  • $width:200px !default 默认变量即可覆盖

选择器嵌套

<header>
    <nav>
        <a href="#">home</a>
        <a href="#">page</a>
    </nav>
</header>

scss

nav {
  a {
    color: red;
    
    header {
      color:green;
    }
  }  
}

属性嵌套

css:

.box {
     font-size: 12px;
     font-weight: bold;
}

scss:

.box {
  font: {
   size: 12px;
   weight: bold;
  }  
}

伪类嵌套

.clearfix{
&:before,
&:after {
    content:"";
    display: table;
  }
&:after {
    clear:both;
    overflow: hidden;
  }
}

声明混合宏(可带参数)

申明:

@mixin border-radius {
    -webkit-border-radius: 5px;
    border-radius: 5px;
}

调用:

button {
    @include border-radius;
}

sass 继承

.btn {
  border: 1px solid #ccc;
  padding: 6px 10px;
  font-size: 14px;
}
.btn-primary {
  background-color: #f36;
  color: #fff;
  @extend .btn;
}
  • sass占位符%:用占位符声明的代码,如果不被@extend调用就不会被编译。
  • sass:支持加减乘除

2.JavaScript获取元素父节点、子节点、兄弟节点

  • el.parentNode:获取元素父节点
  • el.parentElement:获取元素父节点,目前没发现与parentNode的区别在哪里
  • el.childNodes:获取元素子节点,会计算text,回车也算!
  • el.children获取元素子节点,不计算text.
  • el.nextSibling:后一个节点
  • el.previousSibling: 前一个节点

3.flex布局

  • display: flex
  • display: inline-flex
  • webkit内核浏览器加上-webkit前缀

基本概念

       容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size

容器的属性

  • flex-direction:项目排列方向row、column、row-reverse、column-reverse
  • flex-wrap:nowrap、wrap、wrap-reverse
  • flex-flow:flex-direction和flex-wrap的简写形式
  • justify-content:主轴上的对齐方式flex-start、flex-end、center、space-between、space-around
  • align-items:交叉轴上的对齐方式flex-start、flex-end、baseline、strentch
  • align-content:多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

项目属性

  • order:项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow:项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  • flex-shrink:项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
  • flex-basis:配多余空间之前,项目占据的主轴空间(main size)。
  • flex:flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
  • algin-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。

4.vuex 状态管理模式

       核心概念:vuex应用的核心是store,里面包含大部分的state,vuex的状态存储是响应式的,state中的状态不能直接更改

  • state
  • gettter
  • mutation
  • action
  • module
/*vueStore.js*/
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './moduleA.js'

Vue.use(Vuex);

let state = {
  a1: 12,
  data: ['a','a','a','a','a']
};

let getters = {
  printData: state => {
    console.log(state.data);
    return state.data;
  }
};

let mutations = {
  setData(state, data){
    state.data = data;
  }
};

let actions = {
  setData({ commit },n){
    commit('setData', n);
  }
};

export default new Vuex.Store({
    strict: true,
    state,
    getters,
    mutations,
    actions,
    modules: {
    moduleA
  }
});
/*moduleA.js*/
let state = {
  data: ['A', 'A',' A', 'A', 'A']
};

let getters = {
  printDataA: state => {
    return state.data;
  }
};

let mutations = {
  setDataA(state, data) {
    state.data = data;
  }
};

let actions = {
  setDataA({commit}, n) {
    commit('setDataA', n);
  }
};

export default ({
  strict: true,//严格模式
  namespaced: true,
  state,
  getters,
  mutations,
  actions
})

state

       在根实例中注册store选项,该store就会注入到下面的所有组件,子组件通过this.$store能访问到

    computed: {
        count () {
        return this.$store.state.data //['a','a','a','a','a']
        }
    }

getter

       getter类似于计算属性,它的返回值会根据它的依赖被缓存起来,只有当它它的依赖值发生改变才会重新计算,也可以接受其他get特然作为第二个参数

getter会暴露store。getter对象

methods:{
    getData(){
        this.$store.getters.printData; //['a','a','a','a','a']
    }
}

通过方法访问

getters: {
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false 

mutation

       vuex中更改store中的状态的唯一方法就是提交mutation,它接受state作为第一个参数,触发mutation的方法徐调用store.commit,我们可以向store.commit转入额外的参数,即mutation的载荷(payload)

methods:{
      send(){
        this.$store.commit('setData', [0,0,0,0,0]);
        console.log(this.$store.state.data); //[0,0,0,0,0]
      }

mutation必须是同步函数;

action

action类似于mutation,不同在于:

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

       action接受一个与store实例具有相同方法和属性的context对象,context.commit来提交一个mutation、context.state、context.getters

Action 通过 store.dispatch 方法触发:

methods:{
      send(){
        this.$store.dispatch('setData', [0,0,0,0,0]);
        console.log(this.$store.state.data); //[0,0,0,0,0]
      }
    }

module

       Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

store.state.moduleA //moduleA的状态
store.commit('setDataA',[0,0,0,0,0]) //触发moduleA的mutation中的setDataA
store.dispatch('setDataA',[0,0,0,0,0]) //moduleA  actions
store.getters.printDataA; //getter

命名空间
       默认情况下模块内部的action、mutation、getter是注册在全局命名空间的,所以多个模块能够对同一mutation、action做出响应。添加namespaced: true的方式使其成为命名空间模块,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

store.state.moduleA //moduleA的状态
store.commit('moduleA/setDataA',[0,0,0,0,0]) //触发moduleA的mutation中的setDataA
store.dispatch('moduleA/setDataA',[0,0,0,0,0]) //moduleA  actions
store.getters['moduleA/printDataA']; //moduleA  getter

5.axios

一、请求的方式

1、通过配置发送请求

axios(config);
axios(url[,config]);

axios({
    method:"POST",
    url:'/user/a',
    data:{
        msg: 'helloWorld'
    }
});

2、通过别名发送请求

axios.request(config);

axios.get(url[,config]);

axios.delete(url[,config]);

axios.head(url[,config]);

axios.post(url[,data[,config]]);

axios.put(url[,data[,config]])

axios.patch(url[,data[,config]])

3、并发请求

axios.all(params)
axios.spread(callback) ; //callback要等到所有请求都完成才会执行

4、创建axios实例

axios.create([config])
实例方法

axios#request(config)
axios#get(url[,config])
axios#delete(url[,config])
axios#head(url[,config])
axios#post(url[,data[,config]])
axios#put(url[,data[,config]])
axios#patch(url[,data[,config]])

二、请求的配置参数

  • url: 请求地址
  • method:请求方式默认get
  • baseURL:相对地址
  • transformRequest:选项允许我们在请求发送到服务器之前对请求的数据做出一些改动
  • transformResponse:选项允许我们在数据传送到then/catch方法之前对数据进行改动
  • headers:自定义请求头信息
  • params:项是要随请求一起发送的请求参数----一般链接在URL后面
  • data:选项是作为一个请求体而需要被发送的数据,该选项只适用于方法:put/post/patch
  • timeout:如果请求花费的时间超过延迟的时间,那么请求会被终止
  • responseType:返回数据的格式
  • onUploadProgress:下载进度的事件
  • ...

获取响应信息

/*search.js*/
import axios from 'axios';

export default function (keywords, type) {
  const require = new Promise((resolve, reject) => {
    axios.get('http://47.94.16.170:3000/search',{
      params:{
        keywords: keywords,
        type: type
      },
    }).then((data)=> {
      resolve(data);
    })
  });
  return require;
}


/*调用*/
import search from '@/api/search';

let that = this;
search(this.searchText, this.searchType).then(function (data) {
    that.content = data.result;
})

三、默认配置

1.全局默认配置

axios.defaults.baseURL = 'http://api.exmple.com';

2.自定义的实例默认设置

var instance = axios.create({
    baseURL: 'https://api.example.com'
});

instance.defaults.headers.common["Authorization"] = AUTH_TOKEN;

instance.get('/longRequest',{
  timeout: 5000
});

3.配置优先级

lib/defaults.js < 实例中的默认配置 < 请求中的默认配置

四、拦截器

//添加一个请求拦截器
axios.interceptors.request.use(function(config){
  //在请求发出之前进行一些操作
  return config;
},function(err){
  //Do something with request error
  return Promise.reject(error);
});
//添加一个响应拦截器
axios.interceptors.response.use(function(res){
  //在这里对返回的数据进行处理
  return res;
},function(err){
  //Do something with response error
  return Promise.reject(error);
})
查看原文

赞 3 收藏 0 评论 3

认证与成就

  • 获得 390 次点赞
  • 获得 8 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-03-16
个人主页被 2.1k 人浏览