艾欢欢

艾欢欢 查看完整档案

杭州编辑  |  填写毕业院校匿名  |  前端工程师 编辑填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

艾欢欢 发布了文章 · 7月31日

巧用 concat 和 apply 将对象或二维数组转化为一维数组

场景:

在用node写接口的时候,做了 SQL 防注入,并且封装了一个能返回 修改数据库sql语句 的方法,由于使用了占位符,所以需要将传入的对象按照 key-value 的方式一个个的对应上去。

实现:

先介绍一下这两个方法

concat 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。 如果该方法的参数是一个元素,该元素会被直接插入到新数组中;如果参数是一个数组,该数组的各个元素将被插入到新数组中。

apply 方法会引用调用 apply 的函数,并指向第一个参数,apply方法的第二个参数(一个数组,或类数组的对象)会作为被调用对象的arguments值,也就是说第二个参数的各个元素将会依次成为被调用函数的参数。

const obj = {
    a: 'aaa',
    b: 'bbb',
}
const d_arr = Object.entries(obj)  // [["a", "aaa"], ["b", "bbb"]]
const arr = Array.prototype.concat.apply([], d_arr) // ["a", "aaa", "b", "bbb"]
查看原文

赞 0 收藏 0 评论 0

艾欢欢 发布了文章 · 7月13日

Nodejs写接口时配置静态文件路径

Nodejs写接口时配置静态文件路径

需要使用 express

关键代码

const express = require('express');
const app = express();
app.use(express.static(__dirname + '/public'));

现在就可以加载public目录下的静态文件了:

http://127.0.0.1:8100/images/someimg.jpg

Express 会在静态资源目录下查找文件,所以不需要把静态目录作为URL的一部分。

虚拟静态目录

如果要给静态资源文件创建一个虚拟的文件前缀(实际上文件系统中并不存在) ,可以使用 express.static 函数指定一个虚拟的静态目录,语法如下:

app.use('/static', express.static(__dirname + '/public'));

现在可以使用 /static 作为前缀来加载 public 文件夹下的文件了:

http://127.0.0.1:8100/static/images/someimg.jpg

添加多个静态目录

可以通过多次使用 express.static 中间件来添加多个静态资源目录:

app.use(express.static('public'));
app.use(express.static('files'));
查看原文

赞 0 收藏 0 评论 0

艾欢欢 发布了文章 · 5月29日

由React构造函数中bind引起的this指向理解(React组件的方法为什么要用bind绑定this)

React文档源码

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

为什么要用bind重新绑定?

首先一点,React这是使用的ES6的 class ,它只是一种语法糖(只要它能实现的,ES5也能实现)。

而使用 class 创建的对象,在没有通过 new 关键字去实例化的之前,它的内部方法this是无绑定状态的。

也就是说上面的代码,handleClick 方法如果不做绑定,那么这个方法的 this 会指向 undefined

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    //  this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this) // 输出是 undefined
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

那为什么 render 函数里面的 this 指向的不是 undefined

解释:JSX代码经过 Babel 编译之后变成 React 的表达式,这个表达式在render函数被调用的时候通过 React 的 createElement 方法生成一个element,在这个方法中,this指向了被创建的类(也就是相应的React组件)。

看看上面那段JSX代码转换成 JS 代码是什么样子

代码

主要看 _createClass 方法,第一个参数是被创建的类,第二个参数是一个数组,数组里面是这个被创建类中的方法。

很显然,代码中 handleClick 输出的this, 肯定是undefined。

render 方法返回的是 React.createElement ,在这个方法中,this被指向了 _createClass 方法的第一个参数,也就是 Toggle

附:经过 Babel 编译之后 React 表达式的完整源码

"use strict";

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

var Toggle = /*#__PURE__*/function (_React$Component) {
  _inherits(Toggle, _React$Component);

  var _super = _createSuper(Toggle);

  function Toggle(props) {
    var _this;

    _classCallCheck(this, Toggle);

    _this = _super.call(this, props);
    _this.state = {
      isToggleOn: true
    }; // 为了在回调中使用 `this`,这个绑定是必不可少的
    // this.handleClick = this.handleClick.bind(this);

    return _this;
  }

  _createClass(Toggle, [{
    key: "handleClick",
    value: function handleClick() {
      console.log(this); // 输出是 undefined
    }
  }, {
    key: "render",
    value: function render() {
      return /*#__PURE__*/React.createElement("button", {
        onClick: this.handleClick
      }, this.state.isToggleOn ? 'ON' : 'OFF');
    }
  }]);

  return Toggle;
}(React.Component);
查看原文

赞 0 收藏 0 评论 0

艾欢欢 发布了文章 · 3月20日

js中监听事件addEventListener第三个参数的理解(事件的冒泡与捕获)

js中监听事件addEventListener第三个参数的理解(事件的冒泡与捕获)

js中,可以给一个dom对象添加监听事件,像下面这样:

domElement.addEventListener("click", function(){}, true);
第一个参数是事件类型,比如点击(click)、双击(dbclick)

第二个参数就是函数,触发事件后,需要执行的函数。

而第三个参数就是事件的捕获与冒泡, 为true时捕获,false时冒泡。

三个参数介绍完了,第三个参数怎么理解,看下面

在这里插入图片描述

这里有三个叠放在一起的div,并且三个div都绑定了click事件

function div1SayHello() {
  console.log('hello, i am div1');
}
function div2SayHello() {
  console.log('hello, i am div2');
}
function div3SayHello() {
  console.log('hello, i am div3');
}
// 第三个参数为true,则为捕获
document.getElementById('div1').addEventListener('click', div1SayHello, true)
document.getElementById('div2').addEventListener('click', div2SayHello, true)
document.getElementById('div3').addEventListener('click', div3SayHello, true)

那么,由于事件穿透,我点击div3,也相当于点击了div1和div2,那么,事件触发顺序是什么呢?

也就是说,谁先sayHello

而这就跟监听事件的第三个参数有关系了,也就是事件的冒泡和捕获。

冒泡:从dom树的最下面往上面一层层的执行事件, sayHello的顺序是 div3、div2、div1。

在这里插入图片描述

捕获:从dom树的最上面往下面一层层的执行事件, sayHello的顺序是 div1、div2、div3。

在这里插入图片描述

可以给三个事件的第三个参数随便设置true或false,根据结果就能更好的理解这两个概念了。

查看原文

赞 1 收藏 0 评论 0

艾欢欢 发布了文章 · 1月3日

vue-cli3项目打包优化

原始包大小

在这里插入图片描述

以下主要操作都在文件 vue.config.js 下进行,如没有此文件,就在项目根目录下新建。

1.去掉 .map 文件

.map 文件的作用:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。
有了 .map 就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。

操作:

module.exports = {
  // ...
  productionSourceMap: false,
}

包大小:

在这里插入图片描述

2.图片压缩

此操作会压缩图片质量,选择使用

操作:

npm install image-webpack-loader --save-dev
module.exports = {
  // ...
  chainWebpack: config => {
    // ...
    config.module
      .rule('images')
      .use('image-webpack-loader')
        .loader('image-webpack-loader')
        .options({
          bypassOnDebug: true
        })
        .end()
  },
}

包大小:

在这里插入图片描述

3.去除console

代码中 console.log 越多,效果越明显,可选择使用。

操作:

npm install uglifyjs-webpack-plugin --save-dev
module.exports = {
  // ...
  chainWebpack: config => {
    if (process.env.NODE_ENV !== 'production') {
      config
        .plugin('uglifyjs-plugin')
        .use('uglifyjs-webpack-plugin', [{
          uglifyOptions: {
            warnings: false,
            compress: {
              drop_console: true,
              drop_debugger: false,
              pure_funcs: ['console.log']
            }
          }
        }])
        .end()
    }
  }
}

包大小:

在这里插入图片描述

4.CDN加速

维护上不受控制,所以只把那些不可能改动的代码或者库分离出来,通过CDN加速加载。

为了避免使用CDN遇到坑,建议使用可靠的CDN。

此项选择使用。不建议使用。

操作:

module.exports = {
  // ...
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // 分离不常用资源库
      config.externals = {
        'vue': 'Vue',
        'vue-router': 'VueRouter'
      }
    }
  }
}
<!-- CDN示例 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script data-original="https://unpkg.com/vue-router/dist/vue-router.js"></script>

包大小:

在这里插入图片描述

5.开启Gzip压缩

gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度,可以减小60%以上的体积。

注意:这个需要服务器配合开启Gzip,也可打包时不配置,直接在服务器端进行Gzip压缩。

操作:

npm install compression-webpack-plugin --save-dev
module.exports = {
  // ...
  chainWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      config
        .plugin('gzip-plugin')
        .use('compression-webpack-plugin', [{
          filename: '[path].gz[query]',
          algorithm: 'gzip',
          test: /\.js$|\.html$|\.json$|\.css$|\.ttf$/,
          threshold: 0, // 只有大小大于该值的资源会被处理
          minRatio:0.8, // 只有压缩率小于这个值的资源才会被处理
          deleteOriginalAssets: true // 删除原文件
        }])
        .end()
    }
  }
}

包大小:

在这里插入图片描述

查看原文

赞 3 收藏 2 评论 0

艾欢欢 发布了文章 · 2019-11-01

移动端h5监听键盘弹出和收起

  1. 在ios中软键盘弹起时,仅会引起 $('body').scrollTop 值改变,但是我们可以通过输入框的获取焦点情况来做判断,但也只能在ios中采用这个方案,因为在android中存在主动收起键盘后,但输入框并没有失焦,而ios中键盘收起后就会失焦;
  2. 在android中软键盘弹起或收起时,会改变window的高度,因此监听window的 onresize 事件;

一、Android端监听

//获取原窗口的高度
let originalHeight = document.documentElement.clientHeight || document.body.clientHeight
window.οnresize = function() {
  //键盘弹起与隐藏都会引起窗口的高度发生变化
  let resizeHeight = document.documentElement.clientHeight || document.body.clientHeight
  if (resizeHeight < originalHeight) {
    //当键盘弹起,在此处操作
  } else {
    //当键盘收起,在此处操作
  }
}

二、IOS端监听

/**
 * 二、ios
 * 
 * focusin和focusout支持冒泡,对应focus和blur, 
 * 使用focusin和focusout的原因是focusin和focusout可以冒泡,focus和blur不会冒泡,
 * 这样就可以使用事件代理,处理多个输入框存在的情况。
 */
if (isIos) {
  document.body.addEventListener('focusin', () => {
    //软键盘弹出的事件处理
  })
  document.body.addEventListener('focusout', () => {
    //软键盘收起的事件处理
  })
}
查看原文

赞 0 收藏 0 评论 0

艾欢欢 发布了文章 · 2019-09-30

前端js/vue下载后台传过来的流文件(如excel)并设置下载文件名

这里介绍两种方法,使用 Blob对象 和 使用 js-file-download

这两种方法下载的文件都不会乱码,但是 不管使用哪种方法,发送请求时都要设置 responseType

如果不打算了解直接使用,请通过目录或者直接点击跳转 四、主要完整代码

方法一:使用Blob对象

Blob对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File接口基于Blob,继承了blob的功能并将其扩展使其支持用户系统上的文件。

一、Blob() 构造函数

摘自:Blob() 构造函数

语法

var aBlob = new Blob( array, options );

参数

  • array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。
  • options 是可选的,它可能会指定如下两个属性:

    • type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型也就是设置文件类型。
    • endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持blob中保存的结束符不变。

二、URL对象

通过创建URL对象指定文件的下载链接。

// 创建新的URL表示指定的File对象或者Blob对象。
let objectURL = window.URL.createObjectURL(blob); 
window.URL.revokeObjectURL(objectURL); // 释放内存
在每次调用createObjectURL()方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL()方法来释放。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

三、利用a标签自定义文件名

const link = document.createElement('a'); // 生成一个a标签。
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
link.download = fileName; // dowload属性指定文件名
link.click(); // click()事件触发下载
download 属性设置文件名时,可以直接设置扩展名。如果没有设置,则浏览器将自动检测正确的文件扩展名并添加到文件 。

四:主要完整代码

  • 普通下载

    axios.post(postUrl, params, {responseType: 'arraybuffer'}).then(res => {
        // 创建Blob对象,设置文件类型
        let blob = new Blob([res.data], {type: "application/vnd.ms-excel"})
        let objectUrl = URL.createObjectURL(blob) // 创建URL
        location.href = objectUrl;
        URL.revokeObjectURL(objectUrl); // 释放内存
    })
  • 自定义下载后的文件名

    // 利用a标签自定义下载文件名
    const link = document.createElement('a')
    
    axios.post(postUrl, params, {responseType: 'arraybuffer'}).then(res => {
        // 创建Blob对象,设置文件类型
        let blob = new Blob([res.data], {type: "application/vnd.ms-excel"})
        let objectUrl = URL.createObjectURL(blob) // 创建URL
        link.href = objectUrl
        link.download = 'xxx' // 自定义文件名
        link.click() // 下载文件
        URL.revokeObjectURL(objectUrl); // 释放内存
    })
    :下载指定扩展名的文件只需要对照MIME 参考手册设置type即可。

方法二:使用 js-file-download

  • 安装

    npm install js-file-download --save
  • 使用

    import fileDownload from 'js-file-download'
    
    axios.post(postUrl, params, {responseType: 'arraybuffer'}).then(res => {
        fileDownload(res.data, 'xxx.xls')
    })
查看原文

赞 3 收藏 3 评论 2

艾欢欢 发布了文章 · 2019-09-29

vue项目中点击非刷新按钮,页面刷新并且路由多了个问号解决方案

问题描述

在vue项目开发过程中,点击查询或重置按钮,结果页面刷新了一遍

后来发现路径变成了 localhost:8080/?#/advanced

原因

这是因为在 form 表单里,点击了button 按钮,触发了表单的默认事件,也就是触发了提交行为。

解决方案

  1. 使用 @click.prevent 阻止默认事件即可

    <button @click.prevent="handleCheck">查询</button>
  2. 或者不要 form 标签
查看原文

赞 0 收藏 0 评论 0

艾欢欢 发布了文章 · 2019-09-18

vue本地打包build之后dist文件下的index.html不显示内容报错Failed to load resource

【vue-cli踩坑】Failed to load resource: net::ERR_FILE_NOT_FOUND或者build之后dist文件下的index.html不显示内容

这里会介绍 vue-cil@2 和 vue-cil@3 两种方式创建项目的解决方案

场景

通过vue-cli创建的项目

npm run dev 运行开发环境可以看到效果,

但是 npm run bulid 之后,生成的dist文件下的index.html直接打开不显示内容,看不到效果,报错如下:报错

vue-cli@2 解决方案:

在 webpack.prod.conf.js 中的 output 添加参数publicPath:'./'

具体代码:

在 webpack.prod.conf.js 里

output: {
    publicPath: process.env.NODE_ENV === 'production'
      ? './' +config.build.assetsPublicPath
      : './' + config.dev.assetsPublicPath,
      // 上面是添加代码
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  }

这时候再打包,打开 dist 下的 index.html 就显示内容了

vue-cli@3 解决方案:

  1. 在项目根目录新建文件 vue.config.js 并写如下代码:

    // vue.config.js
    module.exports = {
      publicPath: './'
    }
  2. 然后,关键的一步,在 src/router.js 中删去 mode: 'history'

    // ...
    export default new Router({
      // mode: 'history',  // 有这句的删掉,没有就不用管
      base: process.env.BASE_URL,
      routes: [
        {
          path: '/',
          name: 'home',
          component: Home
        }
      ]
    })

    现在再打包就可以了

查看原文

赞 1 收藏 0 评论 2

艾欢欢 发布了文章 · 2019-08-31

火狐浏览器图形验证码刷新不生效的问题(图片src重新赋值不生效的问题)

场景之一

图形验证码刷新

刷新方式:点击一次图片,就重新给src赋值一次,从而进行刷新。

// 这种方式,谷歌浏览器正常刷新,火狐浏览器不会刷新
ImageCodeSrc = BASE_URL + "/login/captcha.jpg"

这样写在谷歌浏览器中是正常显示的,没有问题。但是火狐就会出现不刷新的问题。

原因

【由于指定的src与原来图片的src相同,所以在ie7、火狐浏览器下验证码不会刷新】

问题就出在上面的赋值方式,导致每次src的路径都是一样。

解决方法

加一个时间变量来让每次的src都不一样

代码修改如下:

// 这种方式,谷歌和火狐都能成功刷新
ImageCodeSrc = BASE_URL + "/login/captcha.jpg?d=" + new Date().getTime()

现在每次刷新都传递了不同的参数,火狐浏览器才会认为img的src发生变化,然后重新生产验证码。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 87 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-04-24
个人主页被 961 人浏览