Vue (preventReClick)防暴点 +防抖(debounce)和节流(throttle)函数
1. 防暴点(preventReClick)
- export default {
- install(Vue) {
- Vue.directive('preventReClick', {
- inserted: function (el, binding) {
- console.log(el.disabled)
- el.addEventListener('click', (e) => {
- if (!el.disabled) {
- el.disabled = true
- setTimeout(() => {
- el.disabled = false
- }, binding.value || 3000)
- //binding.value可以自行设置。如果设置了则跟着设置的时间走
- //例如:v-preventReClick='500'
- } else { // disabled为true时,阻止默认的@click事件
- e.preventDefault()
- e.stopPropagation()
- }
- })
- }
- }),
- }
- // 防止多次点击
- import preventReClick from '@/util/preventReClick '
- Vue.use(preventReClick);
- <div class="comment-btn" @click="submitMes()" v-preventReClick="3000">发送</div>
- <el-button @click="submitMes()" v-preventReClick="3000">发送</el-button>
2. 防抖(debounce)
- /**
- * @description 函数防抖 触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。
- * @param {Function} func 需要执行的函数
- * @param {Number} wait 间隔时间 默认200ms
- * @param {Boolean} immediate 是否立即执行 true(默认) 表立即执行,false 表非立即执行
- * @return {*}
- */
- export function VueDebounce(func, wait = 200, immediate = true) {
- let timeout = null; // 定时器
- return function () {
- let that = this, // this对象
- args = arguments; // 参数
- if (timeout) clearTimeout(timeout);
- if (immediate === true) { // 立即执行
- var callNow = !timeout;
- timeout = setTimeout(() => {
- timeout = null;
- }, wait)
- if (callNow) {
- // func.apply(that, args); // 普通用法
- that[func](...args); // vue用法
- }
- }
- else { // 非立即执行
- timeout = setTimeout(() => {
- // func.apply(this, args); // 普通用法
- that[func](...args); // vue用法
- }, wait);
- }
- }
- }
- import {VueDebounce} from "@/util/index"
- methods: {
- /**
- * 点击事件 函数防抖
- * 用法:<el-button @click="debounceHandel">点击测试</el-button>
- */
- debounceHandel: VueDebounce("handlerFunc"),
- /**
- * 点击事件:真正执行的函数
- */
- handlerFunc(type) {
- console.log("测试防抖事件");
- this.$emit("click","这是参数"); // 如果用普通用法,则这里会找不到$emit,因为this还往上继承了vue的对象
- },
- }
- * @description 函数节流
- * @param {Function} func 函数
- * @param {Number} wait 延迟执行毫秒数,默认200
- * @param {Number} type 1 表时间戳版,2 表定时器版
- */
- xport function VueThrottle(func, wait=200 ,type) {
- if(type===1){
- let previous = 0;
- }else if(type===2){
- let timeout;
- }
- return function() {
- let that= this;
- let args = arguments;
- if(type===1){
- let now = Date.now();
- if (now - previous > wait) {
- // func.apply(that, args); // 普通用法
- that[func](...args); // vue用法
- previous = now;
- }
- }else if(type===2){
- if (!timeout) {
- timeout = setTimeout(() => {
- timeout = null;
- // func.apply(that, args)
- that[func](...args); // vue用法
- }, wait)
- }
- }
- }
- import {VueThrottle} from "@/util/index"
- methods: {
- /**
- * 点击事件 函数防抖
- * 用法:<el-button @click="throttleHandel">点击测试</el-button>
- */
- throttleHandel: VueThrottle("handlerFunc"),
- /**
- * 点击事件:真正执行的函数
- */
- handlerFunc(type) {
- console.log("测试防抖事件");
- this.$emit("click","这是参数"); // 如果用普通用法,则这里会找不到$emit,因为this还往上继承了vue的对象
- },
- }
Mock的基本语法及使用
- //定义/mock/index.js
- import Mock from 'mockjs'
- Mock.mock("/api/login", {
- code: 200,
- msg: "登陆成功",
- user: {
- name: "zeng8",
- score: 2048,
- rank: 10
- },
- token: "kjkjalsdiiuioayeuryqowierqiwerqowiery"
- })
- // url可以使用正则匹配
- // 拦截get请求,返回评论数据
- Mock.mock(/\/api\/feed/, "get", function(config) {
- //console.log("config" , config );
- //通过config可以获取到前端发给服务器的数据
- var po = config.url.indexOf("?"); //获取问号的位置
- if (po != -1) {
- var query = config.url.slice(po + 1); //获取到查询参数current=4转换为{current: 4}
- var arr = query.split("&"); //按&分割为数组
- var search = {}; //定义个对象
- arr.forEach(item => {
- var temp = item.split("="); //把数组每个元素按等于分割[current,4]
- search[temp[0]] = temp[1]; //search[ "current"] =4
- })
- }
- // 返回一个随机数据
- return Mock.mock({
- "code": 0,
- "data|4": [{
- id: "@id",
- msg: "@cparagraph(2,3)", //段落2-3行
- name: "@cname", //随机中文名
- date: "@datetime" //随机日期
- }],
- "pagnation": {
- "total|10-25": 1,
- "size": 4,
- "pageTotal|4-10": 1,
- "current": search.current,
- }
- })
- })
- Mock.mock("/api/test", Mock.mock({
- name: "@cname", //随机中文名
- "age|100-200": 1, //100-200的随机数
- "price|10-50.2-5": 1, //10-50随机数 小数点2-5位
- "live|1-2": true, //随机true或false
- "start|1-5": "⭐", //把字符串重复1-5次
- "friend|2": ["小红", "小明", "小刚", "小白", "小蓝"], //1是挑一个大于1是重复n次
- "props|1-3": {
- name: "mm",
- age: 22,
- leg: 2,
- job: "web",
- eye: 2
- },
- "tel": /13\d{9}/,
- "des": function() {
- return `大家好,我的名字是${this.name},今年${this.age}岁`
- },
- "data|10": [{
- "id|+1": 1024,
- "ID": "@id",
- date: "@date",
- time: "@time",
- datetime: "@datetime",
- name: "@cname",
- pic: "@dataImage('200x100')",
- description: "@cparagraph(1,3)",
- title: "@ctitle(8,12)",
- url: /http:\/\/www\.[a-z]{2,8}\.(com|cn|org)/,
- email: /[4-8]@\.(126|qq|163)\.(com|cn|org)/,
- address: "@county(true)", //省市区地址
- }]
- }))
main.js 文件
- /*** sleep ***/
- Vue.prototype.$sleep = time => {
- return new Promise((resolve, reject) => {
- window.setTimeout(() => {
- resolve(true)
- }, time)
- })
- }
增加测试页面
- <!--
- * @Description: npage.vue
- * @Version: 1.0
- * @Author: Jesse Liu
- * @Date: 2022-08-01 20:32:06
- * @LastEditors: Jesse Liu
- * @LastEditTime: 2023-09-07 13:24:11
- -->
- <template>
- <div class="wscn-http404-container">
- <h2 style="font-weight: normal; line-height: 160%;">userInfo ========= {{ userInfo }}</h2>
- <h2 style="font-weight: normal; line-height: 160%;">configInformation ========= {{ configInformation }}</h2>
- <h1 style="line-height:300px; text-align:center">npage.vue (新测试文件)</h1>
- <div>time:</div>
- </div>
- </template>
- <script>
- import { mapState } from "vuex";
- import { parseTime } from '@/utils'
- import { downloadFile } from "@/utils/getFile";
- export default {
- name: 'PageTest',
- computed: {
- ...mapState("user", ["userInfo","configInformation"]),
- message() {
- return 'PageTest'
- },
- parseTime: parseTime
- },
- mounted() {
- this.$store.dispatch("case/casesList/downloadFile",{}).then((res)=>{
- // let data = res.data;
- // let headers = res.headers;
- // /*** 调用下载文件 ***/
- // downloadFile(data, headers)
- })
- /*** 多定时器同时调用 ***/
- this.getStateA();
- this.getStateB();
- /*** 业务请求接口调用示例 ***/
- this.casePage();
- },
- methods: {
- /*** 定时器演示方法 ***/
- async getStateA(){
- /*** 定时器只管计算时间钩子标记 ***/
- console.log('sleepStateA ~~~ 开始')
- /*** 多定时处理任务A ***/
- console.time('sleepStateA');
- let sleepStateA = await this.$sleep(5000);
- console.log('sleepStateA ~~~ 结束', sleepStateA)
- console.timeEnd('sleepStateA');
- return sleepStateA;
- },
- /*** 定时器演示方法 ***/
- async getStateB(){
- /*** 定时器只管计算时间钩子标记 ***/
- console.log('sleepStateB ~~~ 开始')
- /*** 多定时处理任务B ***/
- console.time('sleepStateB');
- let sleepStateB = await this.$sleep(10000);
- console.log('sleepStateB ~~~ 结束', sleepStateB)
- console.timeEnd('sleepStateB');
- return sleepStateB;
- },
- /*** 模拟示例业务的状态 ***/
- async getCaseState(){
- /*** 定时器只管计算时间钩子标记 ***/
- let sleepStateCase = await this.$sleep(10000);
- return sleepStateCase;
- },
- /*** 模拟示例业务主方法 ***/
- async casePage(state = true, n = 0){
- /*** 定义及获取数据状态 ***/
- let runling = true;
- let getCaseState = await this.getCaseState();
- /*** 业务逻辑处理及使用 ***/
- if(state && getCaseState) {
- /*** 请求接口拿状态和后端约定状态码(根据状态码去更改runling的布尔值) ***/
- // 写你的vuex调用方法,如:this.$store.dispatch('user/record', {})
- /*** 以下代码是为了演示模拟3次请求后,状态变更,供参考 ***/
- /*** 假设后端接口告诉你可以终止执行调用的状态 (比如后端调用3次后就更改了状态) ***/
- let index = n;
- index +=1;
- console.log('第['+ index + ']次调用后端接口~~');
- if(index >= 3) {
- runling = false;
- }
- this.casePage(runling, index);
- }
- else{
- console.log('===========================================\n定时器已经结束关停,累计调用共: ' + n + '次~~');
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- </style>
获取JSON对象key及vulue
- var arr = [
- {
- "原告性别": "女",
- "出生日期": "XX1年1月1X日",
- "居住地址": "北京市丰台区XXXXX",
- "所属民族": "汉族",
- "电话号码": "XXXXXX78",
- "原告姓名": "XXX"
- },
- {
- "原告性别": "男",
- "出生日期": "1992年6月10日",
- "居住地址": "北京市通州区XXXXX",
- "所属民族": "汉族",
- "电话号码": "18701519435",
- "原告姓名": "xxx"
- }
- ]
- /*** 遍历数组中的JSON对象 ***/
- for (var i=0; i<arr.length; i++){
- var keys=Object.keys(arr[i]);
- console.log('keys:', keys);
- }
- //keys: (6) ['原告性别', '出生日期', '居住地址', '所属民族', '电话号码', '原告姓名']
JS 中如何优雅的使用多层嵌套属性而不会发生报错
平时在写js的时候遇到多层属性嵌套,一般大家敢直接用点使用吗?
- if(obj.a && obj.a.b && obj.a.b.c && obj.a.b.c.d){
- // do something
- }
- function checkNested(obj /*, level1, level2, ... levelN*/) {
- var args = Array.prototype.slice.call(arguments),
- obj = args.shift();
- for (var i = 0; i < args.length; i++) {
- if (!obj || !obj.hasOwnProperty(args[i])) {
- return false;
- }
- obj = obj[args[i]];
- }
- return true;
- }
- var test = {level1:{level2:{level3:'level3'}} };
- checkNested(test, 'level1', 'level2', 'level3'); // true
- checkNested(test, 'level1', 'level2', 'foo'); // false
方法4:
- var myURL;
- if (Bro(app).doYouEven('config.environment.buildURL')) {
- myURL = app.config.environment.buildURL('dev');
- }
方法5:
- var object = { 'a': [{ 'b': { 'c': 3 } }] };
- _.get(object, 'a[0].b.c');
- // => 3
- _.get(object, ['a', '0', 'b', 'c']);
- // => 3
- _.get(object, 'a.b.c', 'default');
- // => 'default'
Object.getOwnPropertyNames()
- // 类数组对象
- const obj = { 0: "a", 1: "b", 2: "c" };
- console.log(Object.getOwnPropertyNames(obj).sort());
- // ["0", "1", "2"]
- Object.getOwnPropertyNames(obj).forEach((val, idx, array) => {
- console.log(`${val} -> ${obj[val]}`);
- });
js比较俩个对象是否相等
一、使用Object.getOwnPropertyNames()
该方法和Object.keys()功能一样,不同的地方在于Object.getOwnPropertyNames返回对象本身全部的属性,而Object.keys返回对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)(即返回enumerable为false的属性)
共同点是俩者都不会返回自身原型链上的属性。
- var obj = {"name":"liuxinxiu","age":18,"info":"1234"}
- var obj2 = obj;
- var obj3 = {"name":"liuxinxiu","age":18,"info":"123"}
- function checkInfo(obj1, obj2){
- //判断是否指向同一内存
- if (obj1 === obj2) return true;
- let bankInfo = Object.getOwnPropertyNames(obj1),
- oldBankInfo = Object.getOwnPropertyNames(obj2)
- //判断长度不相同直接返回不同
- if(bankInfo.length !== oldBankInfo.length) return false
- for(let i=0,max=bankInfo.length; i<max; i++){
- let prop_name = bankInfo[i]
- if(obj1[prop_name] !== obj2[prop_name]){
- return false
- }
- }
- return true
- }
- console.log(checkInfo(obj,obj3))
二、使用Object.keys()或者Object.entries().toString()
Object.keys()是将对象中键先取出来组成数组,然后先比较键。而后通过键在比较值
Object.entries()是将键和值分别组成俩个数组。然后使用toString将键值数组转化为字符串去比较
- checkInfo() {
- // object.keys
- let bankInfo = Object.keys(this.params.bankInfo),
- oldBankInfo = Object.keys(this.params.oldBankInfo)
- if (oldBankInfo.length !== bankInfo.length) return false
- for (let i = 0; i <= bankInfo.length - 1; i++) {
- let key = bankInfo[i];
- if (!oldBankInfo.includes(key)){
- return false
- }
- if (this.params.oldBankInfo[key] !== this.params.bankInfo[key]){
- return false
- }
- }
- // object.entries
- console.log(Object.entries(this.params.bankInfo).toString() === Object.entries(this.params.oldBankInfo).toString()) //true
- console.log(Object.entries(this.params.bankInfo).toString()) //accountType,2,nationality,CHN,bankName,,bankCode,
- console.log(Object.entries(this.params.oldBankInfo).toString()) //accountType,2,nationality,CHN,bankName,,bankCode,
- }
三、使用JSON.stringify()或。适用于俩个对象属性顺序相同
- let bankInfo = {
- accountType : 2,
- nationality:'CHN', //国籍
- bankName : '', //银行名称
- bankCode : '', //银行code
- }
- let oldBankInfo = {
- accountType : 2,
- nationality:'CHN', //国籍
- bankName : '', //银行名称
- bankCode : '', //银行code
- }
- let flag2 = JSON.stringify(bankInfo) == JSON.stringify(oldBankInfo)
- console.log(flag2)
快速对字符转义,避免跨站攻击XSS
XSS已经成为非常流行的网站攻击方式,为了安全起见,尽量避免用户的输入。可是有些情况下不仅不避免,反而要求鼓励输入,比如写博客。博客园开放性很高,可以运行手写的JS。之前比较著名的例子就是,凡是看到某一篇文章的,都自动关注他。
如果避免跨站攻击的话,我们就得对用户的输入,进行转义。例如<script type='text/javascript'>alert('hello world')</script>。如果直接保存这个字符串的话,然后再输出的话,就会运行JS了。我们需要将这个字符串转义成"<script type='text/javascript'>alert('hello world')</script>"。
转义,就是一个个字符的匹配,然后转换。看着不难,但是需要转义的字符也不少。另外当字符数量大的时候,效率成为一个问题。下面我写一个函数,让浏览器底层帮我们做到。
- function stringEncode(str){
- var div=document.createElement('div');
- if(div.innerText){
- div.innerText=str;
- }else{
- div.textContent=str;//Support firefox
- }
- return div.innerHTML;
- }
xss漏洞以及防御实现
XSS三种类型
- 存储型XSS:数据库中存在XSS攻击的数据,若数据未经过任何转义,返回给客户端。被浏览器渲染,就可能导致XSS攻击
- 反射型XSS:将用户输入的存在XSS攻击的数据,发送给服务端,服务端并未对数据进行存储,也未经过任何转义,直接返回给客户端。被浏览器渲染。就可能导致XSS攻击
- 纯粹发生在客户端的XSS攻击
XSS攻击演示
假设用户输入的参数为:
- <script>alert(xss)</script>
如果后台没有对该数据做任何过滤直接显示到前端<div>标签中的话,源代码就变成了这样:
- <div><script>alert(xss)</script><div>
那么在前端显示出来的是一个写着xss的弹窗,就发生了xss攻击
防御方案
我在项目中的主要的防御方案是对数据的过滤,对于数据中的危险字符进行相应的转义:
- &----->&
- <-----><
- >----->>
- "----->"
- '----->'
- /----->/
iPhone所有手机型号屏幕尺寸(2020-04-17更新)
手机型号 | 尺寸(对角线) | 物理点 | 宽长比例 | 像素点 | 倍数 | 状态栏高度 | 底部安全距离 | 导航栏高度 | tabbar高度 |
---|---|---|---|---|---|---|---|---|---|
iPhone 4/4S | 3.5英寸 | 320x480 | 0.667 | 640x960 | @2x | 20 | - | 44 | 49 |
iPhone 5/5S/5C | 4英寸 | 320x568 | 0.563 | 640x1136 | @2x | 20 | - | 44 | 49 |
iPhone SE | 4英寸 | 320x568 | 0.563 | 640x1136 | @2x | 20 | - | 44 | 49 |
iPhone 6 | 4.7英寸 | 375x667 | 0.562 | 750x1334 | @2x | 20 | - | 44 | 49 |
iPhone 6 Plus | 5.5英寸 | 414x736 | 0.563 | 1242x2208 | @3x | 20 | - | 44 | 49 |
iPhone 6S | 4.7英寸 | 375x667 | 0.562 | 750x1334 | @2x | 20 | - | 44 | 49 |
iPhone 6S Plus | 5.5英寸 | 414x736 | 0.563 | 1242x2208 | @3x | 20 | - | 44 | 49 |
iPhone 7 | 4.7英寸 | 375x667 | 0.562 | 750x1334 | @2x | 20 | - | 44 | 49 |
iPhone 7 Plus | 5.5英寸 | 414x736 | 0.563 | 1242x2208 | @3x | 20 | - | 44 | 49 |
iPhone 8 | 4.7英寸 | 375x667 | 0.562 | 750x1334 | @2x | 20 | - | 44 | 49 |
iPhone 8 Plus | 5.5英寸 | 414x736 | 0.563 | 1242x2208 | @3x | 20 | - | 44 | 49 |
iPhone X | 5.8英寸 | 375x812 | 0.462 | 1125x2436 | @3x | 44 | 34 | 44 | 83 |
iPhone XS | 5.8英寸 | 375x812 | 0.462 | 1125x2436 | @3x | 44 | 34 | 44 | 83 |
iPhone XS Max | 6.5英寸 | 414x896 | 0.462 | 1242x2688 | @3x | 44 | 34 | 44 | 83 |
iPhone XR | 6.1英寸 | 414x896 | 0.462 | 828x1792 | @2x | 44 | 34 | 44 | 83 |
iPhone 11 | 6.1英寸 | 414x896 | 0.462 | 828x1792 | @2x | 44 | 34 | 44 | 83 |
iPhone 11 Pro | 5.8英寸 | 375x812 | 0.462 | 1125x2436 | @3x | 44 | 34 | 44 | 83 |
iPhone 11 Pro Max | 6.5英寸 | 414x896 | 0.462 | 1242x2688 | @3x | 44 | 34 | 44 | 83 |
iPhone SE 2020款 | 4.7英寸 | 375x667 | 0.562 | 750x1334 | @2x | 20 | - | 44 | 49 |