前端开发记录

295 阅读4分钟

1. pdf文件下载

需求:服务端返回blob二进制流,前端实现pdf打印操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.20.0-0/axios.min.js"></script>

</head>
<body>

<div>
    <button onclick="handleDwonLoadPdf()">下载pdf</button>
</div>

<script>
	window.onload = function(){
    	function handleDwonLoadPdf() {
        	let data = {
            	pageSize: 10,
                sportId: 'sr:sport:1',
                producer: 3,
                country: 3
            }
            axios({
            	method: 'get,
                url: 'http://127.0.0.1:9090/api/xxx/print/download',
                params: data,
                responseType: 'blob'
            }).then(res=>{
            	let fileName = res.headers['content-disposition'].split('=')[1];
                downloadFile(res.data, fileName);
            })
        }
        function downloadFile(data,fileName){
        	if(!data) return;
            let url = window.URL.createObjectURL(new Blob([data],{type: 'application/pdf'}));
            const a = document.createElement('a');
            a.style.display = 'none';
            a.download = fileName;
            a.href = url;
            a.click();
            if(document.body.contains(a)){
            	document.body.removeChild(a);
            }
        }
    }
</script>
</body>
</html>

2. 1px边框

需求:解决移动端1px边框显示异常问题

div {
    height:1px;
    background:#000;
    -webkit-transform: scaleY(0.5);
    -webkit-transform-origin:0 0;
    overflow: hidden;
}

/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
}

/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.33);
        transform: scaleY(0.33);
    }
}

3. oninput事件和onchange事件区别

需求:搜索功能,失去焦点时等待2s自动进行搜索

<body>
<input type="text" onchange="console.log(this.value);" />
</body>
<script>
/*
  事件对比:
  oninput事件:在域内容更改时触发(严格是用户输入时触发,事件会频繁调用)
  onchange事件:在域内容更改时触发,元素失去焦点时触发
  onChange触发条件:
  当input博活到焦点后,系统储存当前值;当input焦点离开后,帕努单当前值与之前存储的值是否不等,若为true则触发onchange事件
*/
</script>

4. html打印设置页边距

虽然css控制了html的margin,padding为0,但打印时会默认为页面添加页边距margin值

@page {
    margin:1.0in .75in 1.0in .75in; /* 设置页边距 */
    mso-header-margin:.5in; /* 设置页眉高度 */
    mso-footer-margin:.5in; /* 设置页脚高度 */
    mso-page-orientation:landscape; /* 设置打印方向 */
    }

5. js按钮防止重复点击、防止点击过快

防止重复点击可设置开关,点击后重置开关

<body>
<button onClick="click()"></button>
</body>
<script>
var isloading = false;
function click(){
	if(isloading) return;
    if(!isloading) {
		isloading = true;
    	// 指向操作
        // 操作结束
    	isloading = false;
    }
}
</script>

6. 理解单向数据流和双向绑定

单项数据流(子组件只能接受父组件数据,却不能直接修改该数据)和双向绑定(对数据的监听)没有任何关系

tips:
  + vue, react都属于单向数据流,vue包含双向绑定,但react需要通过setState()才能修改数据,不同于vue 直接修改data的数据,即可触发视图更新
  + v-show、 v-if、 {{}}、 v-model 都属于双向绑定
  + {} react插值表达式也属于双向绑定

7. new绑定

new操作符可以实现改变this指向,及操作:

  • 创建一个新对象,将this绑定到新创建的对象
  • 使用传入的参数调用构造函数
  • 将创建的对象的_propto_指向构造函数的prototype
  • 如果构造函数没有显式返回一个对象,则返回创建的新对象(即this对应的对象),否则返回返回显式对象
function Foo(name) {
	this.name=name;
}
let person = new Foo('xiaowang');
console.log(person); // Foo {name: "xiaowang"}

// 显示返回对象
function NewFoo(name) {
	this.name = name;
    return {
    	color: name
    }
}
let newFoo = new NewFoo('red');
console.log(newFoo); // {color: "xiaowang"}

8. 节流函数优化scroll事件

直接绑定scroll事件,会触发比较频繁,需要使用节流延迟执行回调方法

  • 节流函数
function throttle(fn, delay) {
    let timer, prevTime;
    return function(...args) {
    	const currTime = Date.now();
        const context = this;
        if(!prevTime) prevTime = currTime;
        clearTimeout(timer);
        
        if(currTime - prevTime > currTime) {
        	prevTime = currTime;
            fn.apply(context, args);
            clearTimeout(timer);
            return;
        }
        
        timer = setTimeout(function() {
        	prevTime = Date.now();
            timer = null;
            fn.apply(context, args);
        }, delay);
    }
}

  • scroll调用
window.addEventListener('scroll', throttle(lazyload,200));

9. promise

一个promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态
  • fulfilled: 意味着操作成功完成。状态:pending => fulfilled
  • rejected: 意味着操作失败。状态:pending => rejected
var p1 = new Promise((resolve,reject)=>{});
console.log(p1); // pending
var p2 = new Promise((resolve,reject)=>{
	resolve('成功');
});
console.log(p2); // fulfilled
var p3 = new Promise((resolve,reject)=>{
	reject('失败');
});
console.log(p3); // reject

注意 :promise状态是不可逆的

new Promise((resolve,reject)=>{
	var num = 100;
    resolve(num);
    num = 999;
    resolve(num); // resolve 也不会改变已传出去的num 100
    console.log(num); // 999
}).then(result => {
	console.log(result); // 100
});

10. Generator(ES6)

介绍:

  • Generator函数式一个状态机,封装了多个内部状态
  • 执行Generator函数会返回一个遍历器对象,可以一次遍历Generator函数内部的每一个状态
  • Generator函数只有调用next()方法才会遍历下一个内部状态

关键标识和关键字

  • function*: 关键标识
  • yield: 暂停执行
  • yield*: 语法糖,在Generator函数中指向另一个Generator函数
Generator.prototype.next()	// 返回一个yield表达式生成的值
Generator.prototype.return()	// 返回给定的值并结束生成器
Generator.prototype.throw()	// 向生成器抛出一个错误

yield表达式

  • yield 表示暂停指向
  • yield表达式后面的表达式,只有当调用next()、内部指针指向该语句时才会执行
  • yield表达式的值会作为返回的对象的value属性值
  • 调用next()之前,yield前面的语句不会执行
function* hellWorldGenerator() {
	console.log('aaa')
    yield 'hello';
    console.log('bbb');
    yield 'world';
    console.log('ccc')
    return 'ending';
}

var hw = hellWorldGenerator();
console.log(hw); // helloWorldGenerator {<suspended>} 状态: suspended

hw.next(); // aaa {value: 'hello', done: false}

hw.next(); // bbb { value: 'world', done: false }

hw.next(); // ccc { value: 'ending', done: true }

hw.next(); // { value: undefined, done: true }

console.log(hw); // helloWorldGenerator {<closed>}   状态:closed

【补充】js异步请求发展进化:callback -> promise -> generator -> async + await (async/await 的实现,就是将 Generator 函数和自动执行器,包装在一个函数里)

async/await的优点

  • async/await 相对于Promise,优点是处理 then 的调用链,能够更清晰准确的写出代码并且也能优雅地解决回调地狱问题

  • async/await 对 Generator 函数的改进,体现在:

    • 内置执行器
    • 更好的语义
    • 更广的适用性
    • 返回值是 Promise 对象

async/await的缺点

如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。代码没有依赖性的话,可以使用 Promise.all 的方式替代

11. vue相同路由切换组件时无效

触发原因: xx/detail/1切换到xx/detail/2,组件不变,因为vue-rotuer会识别这连个路由使用相同的组件进行复用,并不会创建组件,因此组件的生命周期钩子不会触发

<!--router-view配置不同的key即可触发视图重绘 -->
<router-view :key="$route.fullPath"></router-view>

12. storage封装调用

  • storage封装
// storage.js
 const storageMap = new Map();

 export default function createStorageModel(key, storage = localStorage) {
    // 相同key返回单例
    if (storageMap.has(key)) {
      return storageMap.get(key);
    }

    const model = {
      key,
      set(val) {
        storage.setItem(this.key, JSON.stringify(val));
      },
      get() {
        let val = storage.getItem(this.key);
        return val && JSON.parse(val);
      },
      remove() {
        storage.removeItem(this.key);
      }
    };
    storageMap.set(key, model);
    return model;
  }
  • 缓存数据枚举
// cacheModel.js
import createStorageModel from '@/utils/storage.js';
// 列举出所有需要缓存操作的变量
export const themeModel = createStorageModel('local_theme',localStorage);
export const utcSourceModel = createStorageModel('utm_source',sessionStorage);
  • 缓存数据调用
import {themeModel,utcSourceModel} from '@/utils/cacheModel.js';
// 数据写入
themeModel.set({test: '123'}); // 设置数据
utmSourceModel.set({time: 12321123});
// 数据读取
let themeData = themeModel.get(); // 读取数据
let utmSourceData = utmSourceModel.get();
console.log(themeData, '读取数据');
console.log(utmSourceData, '读取数据');

13. 获取当前时区

// 获取当地时区
export function currentTimeRegion() {
  let nowTime = Number(moment().format('HH'));
  let utcTime = Number(moment.utc().format('HH'));
  return nowTime - utcTime > 0 ? `+${nowTime - utcTime}` : `${nowTime - utcTime}`.toString();
}

14. (typeof)判断数据类型

typeof undefined === 'undefined' // true
typeof true === 'boolean' // true
typeof 42 === 'number' // true
typeof '42' === 'string' // true
let obj = {life: 42};
typeof obj === 'object' && obj && !obj.length // true
let arr = [1,2,3];
typeof arr === 'object' && arr && arr.length // true
typeof Symbol() === 'symbol' // true
let a = null;
(!a && typeof a) === 'object' // true
typeof function a(){} === 'function' // true

14. 构造函数 vs class

1. 私有属性(方法)和静态属性(方法)

  • 构造函数写法
// 构造函数写法
function Person(name, age) {
  let _selfName = '123456'; // 构造函数私有属性,无法被外部直接访问
  let _selfFn = () => { // 构造函数私有方法,无法被外部直接访问
    return _selfName;
  }
  this.name = name; // 实例的私有属性
  this.age = age;
  this.sayHi = function () { // //实例的私有方法
    console.log(_selfFn())
  }
}

Person.prototype = { // 实例的共有方法
  constructor: Person,
  fn1() {
  },
  fn2() {
  }
}
Person.prop1 = 'staticProp1'; // 构造函数的静态属性
Person.prop2 = 'staticProp2';
Person.staticFn = function () { // 构造函数的静态方法

}
const p = new Person('jack', 23);
  • class写法
// class写法
class Person {
  name = ''; // 这里也可以写实例的属性
  age = '';
  static prop1 = 'staticProp1'; // 构造函数静态属性
  static prop2 = 'staticProp2';
  #selfName = '123456';
  #selfFn = () => { // 构造函数私有方法,无法被外部直接访问
    return this.#selfName;
  }

  constructor(name, age) {
    this.name = name; // 实例的私有属性
    this.age = age;
  };

  fn1() { // 实例的共有方法
  };

  fn2() {
  }

  sayHi = function () {
    console.log(this.#selfFn()) // 构造函数的私有方法
  }
}
const p = new Person('jack', 23);

2. 继承

// 构造函数
// 1. 在子类的构造函数中,调用父类的构造函数
function Super(name) {
  this.name = name;
}

function Sub(name, age) {
  Super.call(this, name);
  this.age = age;
}

// 2. 让子类的原型指向父类的原型,这样子类就可以继承父类原型
Super.prototype.sayHi = function () {
  console.log('hi')
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
const p = new Sub('qiuqiu', 20);

// class写法
class Person {
  name;
  age;

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHi() {
    console.log(this.name);
  }
}

class Man extends Person {

  constructor(name, age, prop) {
    super(name, age);
    this.prop = prop;
  }
}

const a = new Man('qiuqiu', 20, 'shuai');

15. swiper一屏显示3个半

<!-- 
	显示n个半,需要以下步骤:
    1. slidesPerView: "auto"
    2. 设置swiper-slide尺寸为百分比 (如 width: 18.77%;)
-->

<!-- 模板结构 -->
<template>
 <swiper :options="swiperOption" class="swiper-tab" id="sportSideMenu">
    <swiper-slide v-for="(v,i) in sideMenu" :key="i">
      <div class="swiper-item" @click="handleClickMenu({val:v,idx:i})">
        <div :class="['icon',i==activeIdx?'active':'']">
          <i :class="['iconfont',v.cname]"></i>
        </div>
        <div class="name">{{ v.name }}</div>
      </div>
    </swiper-slide>
  </swiper>
<template>
<!-- data配置 -->
<script>
  data() {
    return {
      swiperOption: {// swiper配置项
        slidesPerView: "auto",
        // spaceBetween: 60,
        speed: 600
      },
    }
  },

  components: {
    swiper,
    swiperSlide,
  },
</script>
<style scoped lang="less">
.swiper-tab {
  box-sizing: border-box;
  border-bottom: 2px solid #F2F3F5;
  padding: 36px 0 28px 0;

  /deep/ .swiper-slide {
    width: 18.77%;
  }
}

.swiper-item {
  display: flex;
  flex-direction: column;
  align-items: center;

  .icon {
    width: 72px;
    height: 72px;
    line-height: 72px;
    text-align: center;
    border-radius: 50%;
    background: #F2F3F5;

    &.active {
      background-color: #FFE60F;
    }

    .iconfont {
      font-size: 40px;
      color: #32333D;
    }
  }

  .name {
    font-size: 22px;
    font-family: Roboto-Regular, Roboto;
    font-weight: 400;
    color: #32333D;
    line-height: 28px;
    text-align: center;
  }
}
</style>

16. require引入静态图片资源添加变量

let imgSrc = 'a.jpg';
const context= require.context('../../',true,/.jpg$/);
var imageUrl = context("./"+imgSrc);

17. [] == ![]

/*
    []在比较之前会先执行[].toString() = '';
    ![]在比较之前转为false
*/
   [] == ![]; // true