星野

星野 查看完整档案

厦门编辑  |  填写毕业院校前端有道  |  公众号 编辑 qqlcx5.github.io/ 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

星野 发布了文章 · 4月1日

Javascript 高效开发工具库

nutils-js 是我封装的一个模块化、高性能的 JavaScript 实用工具库。

前端开发中经常会遇到ArrayObjectStringNumber等数据处理,或者是防抖节流函数等性能优化亦或是 URL 参数处理、类型判断等等操作,为了提高开发效率,我将这些常见公共方法进行抽离并封装好,发布在 npm 上,后续如有更优的写法也会做进一步的更新。另外读者朋友们如果有好的建议或者想为本项目贡献一份力的话,欢迎为本项目提交 pr,一起探讨和交流。

安装

$ npm i --save nutils-js

使用

const nutils = require('nutils-js')
nutils.multArray([1, 2, 3], 2)

API文档

数组

对象

函数

字符串

数字

浏览器

环境

后续的更新,请点击GitHub仓库进行查看

如果对本文有啥疑问或建议,欢迎加我微信qqlcx55一起学习哈

Github地址

一、数组

multArray二维数组转换

将数组(array)拆分成多个子数组,并将这些子数组组成一个新数组。

multArray(array, count)

参数

  • array需要处理的数组
  • count = 8子数组需要的长度

示例

multArray([1, 2, 3, 4, 5, 6, 7], 2)
=> [[1, 2], [3, 4], [5, 6], [7]]

multArray(['a', 'b', 'c', 'd'], 3)
=> [['a', 'b', 'c'], ['d']]

源码

function multArray(arr, count = 8) {
    let pages = []
    arr.forEach((item, index) => {
        const page = Math.floor(index / count)
        if (!pages[page]) pages[page] = []
        pages[page].push(item)
    })
    return pages
}

flatten扁平化数组

将多层嵌套数组(array)拆分成一个数组

flatten(array)

参数

  • array多层嵌套数组

示例

flatten([1, [2], [3], [4, 5]])

// [1, 2, 3, 4, 5]

源码

// 扁平化  Map 方法
const flatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? flatten(v) : v)))

// 扁平化  reduce 方法
const flatten = arr => arr.reduce((a, c) => a.concat(Array.isArray(c) ? flatten(c) : c), [])

flattenDeep指定层级扁平化数组

将多层嵌套数组(array)拆分成指定层级数组

flattenDeep(array, depth)

参数

  • array多层嵌套数组 depth = 减少的嵌套层级数

示例

flattenDeep([1, [2, [3, [4]], 5]], 1)
// => [1, 2, [3, [4]], 5]

// ES6方法 `flat(depth)`
;[1, [2, [3, [4]], 5]].flat(1)
// => [1, 2, [3, [4]], 5]

源码

const flattenDeep = (arr, depth = 1) => arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), [])

isArrayEqual检查两个数组各项相等

比较两个数组内的各项值是否相等,返回一个Boolean

isArrayEqual(array, array)

参数

  • array 要检查的数组
  • array 要检查的数组

示例

isArrayEqual([6, 5, 2, 4, 1, 3], [1, 2, 3, 4, 5, 6])
// => true

isArrayEqual([6, 5, 2, 7, 1, 3], [1, 2, 3, 4, 5, 6])
// => false

源码

const isArrayEqual = (a, b, has = true) => {
    if (a.length !== b.length) return (has = false)
    const s = new Set(b)
    if (a.find(x => !s.has(x))) return (has = false)
    return has
}

allEqual检查数组各项相等

allEqual(array)

参数

  • array 要检查的数组

示例

allEqual([1, 2, 3, 4, 5, 6])
// => false

allEqual([1, 1, 1, 1])
// => true

源码

const allEqual = arr => arr.every(val => val === arr[0])

diffArray具有唯一array值的数组

创建一个具有唯一 array 值的数组,每个值不包含在其他给定的数组中

diffArray(array, array2)

参数

  • array 要检查的数组
  • array2要排除的数组

示例

diffArray([1, 2, 6, 7], [1, 2, 9, 5])
// => [ 6, 7 ]

源码

const diffArray = (a, b) => {
    const s = new Set(b)
    let arr = a.filter(x => !s.has(x))
    return arr
}

haveArr具有共同array值的数组

创建一个具有共同 array 值的数组,每个值包含在其他给定的数组中

haveArr(array, array2)

参数

  • array 要检查的数组
  • array2要包含的数组

示例

haveArr([1, 2, 6, 7], [1, 2, 9, 5])
// => [ 1, 2 ]

源码

const haveArr = (a, b) => {
    const s = new Set(b)
    return a.filter(x => s.has(x))
}
// ES6 includes
const haveArr = (arr, values) => arr.filter(v => values.includes(v))

uniqueArray数组去重

创建一个去重后的 array 数组副本

uniqueArray(array)

参数

  • array 要去重的数组

示例

uniqueArray([1, 2, 2, 3, 4, 4, 5])
// => [ 1, 2, 3, 4, 5 ]

源码

const uniqueArray = (...arr) => [...new Set(arr)]

const uniqueArray = (...arr) => Array.from(new Set(arr))

uniqueArrayObject数组对象去重

创建一个去重后的 array 数组对象副本

uniqueArrayObject(array)

参数

  • array 要去重的数组
  • key 要去重的对象属性值

示例

const responseList = [
    { id: 1, a: 1 },
    { id: 2, a: 2 },
    { id: 3, a: 3 },
    { id: 1, a: 4 },
    { id: 2, a: 2 },
    { id: 3, a: 3 },
    { id: 1, a: 4 },
    { id: 2, a: 2 },
    { id: 3, a: 3 },
    { id: 1, a: 4 },
    { id: 2, a: 2 },
    { id: 3, a: 3 },
    { id: 1, a: 4 },
]

uniqueArrayObject(responseList, 'id')

// => [ { id: 1, a: 1 }, { id: 2, a: 2 }, { id: 3, a: 3 } ]

源码

const uniqueArrayObject = (arr, key) => {
    return arr.reduce((acc, cur) => {
        const ids = acc.map(item => item[key])
        return ids.includes(cur[key]) ? acc : [...acc, cur]
    }, [])
}

treeData生成树结构数据

该函数传入一个数组, 每项id对应其父级数据parent_id,返回一个树结构数组

treeData(array, id, parent_id)

参数

  • array 要生成树结构的数组
  • id 自定义属性名
  • parent_id 父级自定义属性名

示例

const comments = [
    { id: 1, parent_id: null },
    { id: 2, parent_id: 1 },
    { id: 3, parent_id: 1 },
    { id: 4, parent_id: 2 },
    { id: 5, parent_id: 4 },
]

treeData(comments)

// => [ { id: 1, parent_id: null, children: [ [Object], [Object] ] } ]

源码

const treeData = (arr, id = null, link = 'parent_id') => arr.filter(item => item[link] === id).map(item => ({ ...item, children: treeData(arr, item.id) }))

ascArr数组升序

返回升序后的新数组

sort()方法会改变原数组,默认按 unicode 码顺序排列
ascArr(array)

参数

  • array 要检查的排序数组

示例

ascArr([3, 2, 3, 4, 1])
// => [ 1, 2, 3, 3, 4 ]

源码

// 通过ES6 ...展开运算符浅拷贝一份新数组
const ascArr = arr => [...arr].sort((a, b) => a - b)

descArr数组降序

返回降序后的新数组

descArr(array)

参数

  • array 要检查的排序数组

示例

descArr([3, 2, 3, 4, 1])
// => [ 1, 2, 3, 3, 4 ]

源码

const descArr = arr => [...arr].sort((a, b) => b - a)

shuffle随机排序

创建一个随机的数组,使用Fisher-Yates算法随机排序数组的元素

shuffle(array)

参数

  • array 要随机的数组

示例

shuffle([2, 3, 1])
// => [3, 1, 2]

源码

const shuffle = ([...arr]) => {
    let m = arr.length
    while (m) {
        const i = Math.floor(Math.random() * m--)
        ;[arr[m], arr[i]] = [arr[i], arr[m]]
    }
    return arr
}

takeArray截取数组开始指定的元素

从 array 数组的最开始一个元素开始提取 n 个元素

takeArray(array, n)

参数

  • array要检索的数组。
  • n=要提取的元素n个数。

示例

takeArray([2, 3, 1], 2)
// => [2, 3]

源码

const takeArray = (arr, n = 1) => arr.slice(0, n)

takeLastArray截取数组最后指定的元素

从 array 数组的最后一个元素开始提取 n 个元素

takeLastArray(array, n)

参数

  • array要检索的数组。
  • n=要提取的元素n个数。

示例

takeArray([2, 3, 1], 2)
// => [3, 1]

源码

const takeLastArray = (arr, n = 1) => arr.slice(0, -n)

cloneArray克隆数组

浅拷贝一份数组副本

cloneArray(array)

参数

  • array要复制的数组

示例

cloneArray([1, 24])
// => [1, 24]

源码

// ES6 ...
const cloneArray = arr => [...arr]

// ES6 Array.from
const cloneArray = arr => Array.from(arr)

// concat()
const cloneArray = arr => [].concat(arr)

// map()
const cloneArray = arr => arr.map(x => x)

cloneArray([1, 24]) // [1, 24]

maxArray数组中最大值

过滤原数组中所有的非假值元素,返回数组中的最大值

maxArray(array)

参数

  • array待处理的数组

示例

maxArray([0, -1, -2, -3, false])
// => 0

源码

const maxArray = arr => Math.max(...arr.filter(v => Boolean(v) || v === 0))

minArray数组中最小值

过滤原数组中所有的非假值元素,返回数组中的最小值

minArray(array)

参数

  • array待处理的数组

示例

minArray([0, -1, -2, -3, false])
// => -3

源码

const minArray = arr => Math.min(...arr.filter(v => Boolean(v) || v === 0))

validArray去除数组中的无效值

创建一个新数组,包含原数组中所有的非假值元素。例如false, null,0, "", undefined, 和 NaN 都是被认为是“假值”。

validArray(array)

参数

  • array待处理的数组

示例

minArray([0, 1, false, 2, '', 3])
// => [1, 2, 3]

源码

const validArray = arr => arr.filter(Boolean)

二、对象

isObjectEqual检查两个对象各项值相等

isObjectEqual(object, object2)

参数

  • object待检索的对象
  • object2待检索的对象

示例

isObjectEqual({ a: 2, b: 4 }, { b: 4, a: 2 })
// => true
isObjectEqual({ a: 2, b: 4, c: 6 }, { b: 4, a: 2 })
// => false

源码

function isObjectEqual(obj1, obj2, has = true) {
    // 判断类型
    const o1 = obj1 instanceof Object
    const o2 = obj2 instanceof Object
    if (!o1 || !o2) return obj1 === obj2
    // 判断长度
    const keys1 = Object.getOwnPropertyNames(obj1)
    const keys2 = Object.getOwnPropertyNames(obj2)
    if (keys1.length !== keys2.length) return false
    // 各项对比
    for (let o in obj1) {
        let t1 = obj1[o] instanceof Object
        let t2 = obj2[o] instanceof Object
        if (t1 && t2) {
            has = diffByObj(obj1[o], obj2[o])
        } else if (obj1[o] !== obj2[o]) {
            has = false
            break
        }
    }
    return has
}

cloneObject克隆对象

浅拷贝一份对象副本

cloneObject(object)

参数

  • object要复制的对象

示例

const a = { x: 1, y: 1 }
const b = cloneObject(a)
// => a !== b

源码

// ES6 ...
const cloneObject = (obj, temp = {}) => (temp = { ...obj })

// Object.assign()
const cloneObject = obj => Object.assign({}, obj)

三、函数

debounce函数防抖

在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。

debounce(fn, wait)

参数

  • fn 要防抖动的函数
  • wait=500需要延迟的毫秒数

示例

debounce(()=> { console.log('debounce') }, 1000)
// => 1秒后打印'debounce'

源码

/** *
 * 防抖
 * @parmas fn 回调函数
 * @parmas time 规定时间
 */
const debounce = (function () {
    let timer = {}
    return function (func, wait = 500) {
        let context = this // 注意 this 指向
        let args = arguments // arguments中存着e
        let name = arguments[0].name || 'arrow' //箭头函数
        if (timer[name]) clearTimeout(timer[name])
        timer[name] = setTimeout(() => {
            func.apply(this, args)
        }, wait)
    }
})()

throttle函数节流

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

throttle(fn, wait)

参数

  • fn 要节流的函数
  • wait=500需要延迟的毫秒数

示例

throttle(() => {
    console.log('throttle')
}, 1000)
// 1秒多次触发打印一次`throttle`

源码

/** *
 * 节流(规定的时间才触发)
 * @parmas fn 结束完运行的回调
 * @parmas delay 规定时间
 */
export const throttle = (function () {
    let timeout = null
    return function (func, wait) {
        let context = this
        let args = arguments
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null
                func.apply(context, args)
            }, wait)
        }
    }
})()

throttle(fn, 300)

typeFn类型判断

判断是否是 ArrayObjectStringNumber类型

typeFn.type(value)

参数

  • type 数据类型
  • value要检验的值

示例

typeFn.String('1')
typeFn.Number(1)
typeFn.Boolean(false)
typeFn.Null(null)
typeFn.Array([1, 2])
typeFn.Object({ a: 1 })
typeFn.Function(() => {})

// => true

源码

let typeFn = {}
const curring = (fn, arr = []) => {
    let len = fn.length
    return (...args) => {
        arr = arr.concat(args)
        if (arr.length < len) {
            return curring(fn, arr)
        }
        return fn(...arr)
    }
}

function isType(type, content) {
    return Object.prototype.toString.call(content) === `[object ${type}]`
}
;['String', 'Number', 'Boolean', 'Null', 'Array', 'Object', 'Function'].forEach(type => {
    typeFn[type] = curring(isType)(type)
})

calcFn加减乘除运算

因为 JavaScript 遵循 IEEE 754 数学标准,使用 64 位浮点数进行运算。在进行十进制运算时会导致精度丢失。

calcFn.add(value1, value2, value3)

参数

  • addsubmuldiv运算符
  • value要计算的值

示例

解决 0.1+0.2 !== 0.3 问题
//加法
calcFn.add(0.1, 0.2) // 0.3

//减法
calcFn.sub(0.1, 0.2) // 0.1

//乘法
calcFn.mul(0.2, 0.3) // 0.06

// 乘法
calcFn.add(0.1, 0.2) // 0.5

源码

const calcFn = {
  add() {
      let arg = Array.from(arguments);
      return arg.reduce((total, num) => {
          return accAdd(total, num);
      });
  },
  sub() {
      let arg = Array.from(arguments);
      return arg.reduce((total, num) => {
          return accAdd(total, -num);
      });
  },
  mul() {
      let arg = Array.from(arguments);
      return arg.reduce((total, num) => {
          return accMul(total, num);
      });
  },
  div() {
      let arg = Array.from(arguments);
      return arg.reduce((total, num) => {
          return accDiv(total, num);
      });
  }
}

function accAdd(arg1, arg2) {
  let r1, r2, m;
  try {
      r1 = arg1.toString().split(".")[1].length;
  } catch (e) {
      r1 = 0;
  }
  try {
      r2 = arg2.toString().split(".")[1].length;
  } catch (e) {
      r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2));
  return (arg1 * m + arg2 * m) / m;
}

function accMul(arg1, arg2) {
  let m = 0,
      s1 = arg1.toString(),
      s2 = arg2.toString();
  try {
      m += s1.split(".")[1].length;
  } catch (e) {}
  try {
      m += s2.split(".")[1].length;
  } catch (e) {}
  return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
}

function accDiv(arg1, arg2) {
  let t1 = 0,
      t2 = 0,
      r1, r2;
  try {
      t1 = arg1.toString().split(".")[1].length;
  } catch (e) {}
  try {
      t2 = arg2.toString().split(".")[1].length;
  } catch (e) {}
  r1 = Number(arg1.toString().replace(".", ""));
  r2 = Number(arg2.toString().replace(".", ""));
  return (r1 / r2) * Math.pow(10, t2 - t1);
}

四、字符串

isNil值是否是nullundefined

isNil(value)

参数

  • value 要检验的值

示例

isNil(null)
isNil(undefined)
// => true

源码

const isNil = val => val === undefined || val === null

padStart遮住字符串

padStart(value, n, maskChar)

参数

  • value 要遮住字符串
  • n = 4 填充的长度
  • maskChar 填充字符

示例

padStart('18659808664')
// => 1865*******

源码

const padStart = (str, n = 4, maskChar = '*') => str.slice(0, n).padStart(str.length, maskChar)

thousands数字每隔三位数加分号

thousands(number)

参数

  • number 数字或者浮点数

示例

thousands(12255552323)
// => 12,255,552323

源码

const thousands = num => num.toString().replace(num.toString().indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(\d{3})+$)/g, '$1,')

五、数字

randomNumber指定范围的随机整数

randomNumber(min, max)

参数

  • min 指定范围最小值
  • max 指定范围最大值

示例

randomNumber(0, 10)
// => 7
// => 2

源码

const randomNumber = (min = 0, max = 10) => Math.floor(Math.random() * (max - min + 1)) + min

average求平均值

average(value1, value2, value3)

参数

  • value 数字

示例

average(...[1, 2, 3])
average(1, 2, 3)
// => 2

源码

const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length

averageBy检查数组对象各项相等

averageBy(array, fn)

参数

  • array 要迭代的数组
  • fn 迭代函数

示例

averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], o => o.n)
averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n')
// => 5

源码

const averageBy = (arr, fn) => arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) / arr.length

aboutEqual两个值是否约等于

传入两个数字是否大致相等,误差在可接受范围内

aboutEqual(n1, n2, epsilon)

参数

  • n1 n2 要比较的数字
  • epsilon 误差可接受范围内

示例

aboutEqual(25, 2, 0.06)
// => true

源码

const aboutEqual = (n1, n2, epsilon = 0.001) => Math.abs(n1 - n2) < epsilon

getLineSize计算两点之间的距离

勾股定理计算两点之间的距离

getLineSize = (x1, y1, x2, y2)

参数

  • x1 y1 x2 y2坐标点

示例

getLineSize(0, 0, 3, 4)
// => 5

源码

const getLineSize = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)

sum数组中值总和

sum(value1, value2, value3)

参数

  • value1 value2 value3要迭代的数字

示例

sum(1, 2, 3, 4)
sum(...[1, 2, 3, 4])
// => 10

源码

const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0)

六、浏览器

copyTextH5复制文本

copyText(content, callback)

参数

  • content要复制文字
  • callback 回调用户提示

示例

copyText(content, text => {
    this.$toast(text)
})

源码

function copyText(content, callback) {
    if (!document.queryCommandSupported('copy')) {
        //为了兼容有些浏览器 queryCommandSupported 的判断
        console.log('浏览器不支持')
        return
    }
    let textarea = document.createElement('textarea')
    textarea.value = content
    textarea.readOnly = 'readOnly'
    document.body.appendChild(textarea)
    textarea.select() // 选择对象
    textarea.setSelectionRange(0, content.length) //核心
    let result = document.execCommand('copy') // 执行浏览器复制命令
    callback && callback(result ? '复制成功~~' : '复制失败~~')
    textarea.remove()
}

getCurrentURL获取当前的 URL 地址

该函数返回当前页面的 URL 地址。

示例

getCurrentURL()
// =>

源码

const getCurrentURL = () => window.location.href

scrollToTop返回顶部

平滑地滚动到当前页面的顶部。

示例

scrollToTop()
// => 当前页面的顶部

源码

const scrollToTop = () => {
    const c = document.documentElement.scrollTop || document.body.scrollTop
    if (c > 0) {
        window.requestAnimationFrame(scrollToTop)
        window.scrollTo(0, c - c / 8)
    }
}

smoothScroll平滑滚动页面

平滑滚动到浏览器窗口的可见区域

示例

smoothScroll('#fooBar');
// => 平滑滚动到ID为fooBar的元素
smoothScroll ('.fooBar' );
// => 使用fooBar类平滑滚动到第一个元素

源码

const smoothScroll = element =>
    document.querySelector(element).scrollIntoView({
        behavior: 'smooth',
    })

5.isCurrentPage是否是当前页面

浏览器的选项卡是否是用户在浏览

示例

isCurrentPage()
// => true

源码

isCurrentPage = () => !document.hidden

7.环境

1.isBrowser是否是浏览器

返回当前运行时环境是否为浏览器

示例

isBrowser()
// => true (browser)
// => false (Node)

源码

const isBrowser = () => ![typeof window, typeof document].includes('undefined')

2.isWechatBrowser判断微信浏览器还是普通h5

示例

isWechatBrowser()
// => true

源码

const isWechatBrowser = (() => {
    let ua = navigator.userAgent.toLowerCase()
    return /micromessenger/.test(ua)
})()

3.isMobile判断是否是移动端

示例

isMobile()
// => true

源码

const isMobile = () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)

参考资料

查看原文

赞 12 收藏 10 评论 0

星野 发布了文章 · 3月30日

浏览器存储那些事

前言

随着移动网络的发展与演化,我们手机上现在除了有原生 App,还能跑“WebApp”——它即开即用,用完即走。一个优秀的 WebApp 甚至可以拥有和原生 App 媲美的功能和体验。WebApp 优异的性能表现,有一部分原因要归功于浏览器存储技术的提升。cookie存储数据的功能已经很难满足开发所需,逐渐被Web Storage、IndexedDB所取代,本文将介绍这几种存储方式的差异和优缺点。

一、cookie

1.cookie的来源

cookie 的本职工作并非本地存储,而是“维持状态”。因为HTTP协议是无状态的,HTTP协议自身不对请求和响应之间的通信状态进行保存,通俗来说,服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么,于是就诞生了cookie。它就是用来绕开HTTP的无状态性的“额外手段”之一。服务器可以设置或读取cookie中包含信息,借此维护用户跟服务器会话中的状态。

在刚才的购物场景中,当用户选购了第一项商品,服务器在向用户发送网页的同时,还发送了一段cookie,记录着那项商品的信息。当用户访问另一个页面,浏览器会把cookie发送给服务器,于是服务器知道他之前选购了什么。用户继续选购饮料,服务器就在原来那段Cookie里追加新的商品信息。结帐时,服务器读取发送来的cookie就行了。

2.什么是cookie

cookie指某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。 cookie是服务端生成,客户端进行维护和存储,存储在内存或者磁盘中。通过cookie,可以让服务器知道请求是来源哪个客户端,就可以进行客户端状态的维护,比如登陆后刷新,请求头就会携带登陆时response header中的Set-Cookie,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状态。

简而言之,cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。

cookie 主要用于以下三个方面:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

3.cookie的原理及其构成

简单的HTTP请求过程

第一次访问网站的时候,浏览器发出请求,服务器端生成 cookie在响应中通过Set-Cookie头部告知客户端(允许多个Set-Cookie头部传递多个值),客户端得到 cookie后,后续请求都会自动将 cookie头部携带至请求中发送给服务器(见下面例子),另外,cookie的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。

// 一个HTTP响应:
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value
Other-header: other-header-value

这个HTTP响应会设置一个名为"name",值为"value"的cookie。名和值在发送时都会经过URL编码。浏览器会存储这些会话信息,并在之后的每个请求中都会通过HTTP头部cookie再将它们发回服务器,比如:

GET /index.jsl HTTP/1.1
Cookie: name=value
Other-header: other-header-value

cookie在浏览器中是由以下参数构成的:

  • name:唯一标识cookie的名称。cookie名不区分大小写,因此myCookie和MyCookie是同一个名称。不过,实践中最好将cookie名当成区分大小写来对待,因为一些服务器软件可能这样对待它们。cookie名必须经过URL编码
  • value:存储在cookie里的字符串值。这个值必须经过URL编码
  • Domain:cookie有效的域。发送到这个域的所有请求都会包含对应的cookie。如果不指定,默认为文档来源(由协议、域名和端口共同定义),不包含子域名。如果指定了Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。但是,当子域需要共享有关用户的信息时,这可能会有所帮助。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。
  • Path:请求URL中包含这个路径才会把cookie发送到服务器。
// 例如,设置 Path=/docs,则以下地址都会匹配:
/docs
/docs/Web/
/docs/Web/HTTP
  • Expires/Max-Age:设置cookie过期时间(Expires)或有效期(Max-Age)(即什么时间之后就不发送到服务器了)。简单名/值对形式的cookie只在当前会话期间存在,用户关闭浏览器就会丢失。如果想让cookie的生命周期超过单个浏览对话,那就指定Expires/Max-Age,max-age优先级高于expires。
  • Secure:设置之后,只在使用SSL安全连接的情况下才会把cookie发送到服务器。例如,请求https://www.wrox.com会发送cookie,而请求http://www.wrox.com则不会。
  • HttpOnly:设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由 Document.cookie 属性、XMLHttpRequest 和 Request APIs 进行访问,以防范跨站脚本攻击(XSS)。
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
Other-header: other-header-value

这里创建的cookie对所有wrox.com的子域及该域中的所有页面有效(通过path=/指定)。不过,这个cookie只能在SSL连接上发送,因为设置了secure标志。

要知道,域、路径、过期时间和secure标志用于告诉浏览器什么情况下应该在请求中包含cookie。这些参数并不会随请求发送给服务器,实际发送的只有cookie的名/值对

4.Javascript 中的cookie

一般说来,cookie的生成方式主要有两种,一种是上文提到的在响应中通过Set-Cookie头部告知客户端;另外一种就是在JavaScript中可以通过document.cookie可以读写cookie,如下:

//读取浏览器中的cookie
console.log(document.cookie);
//写入cookie
document.cookie='myname=langlixingzhou;path=/;domain=.baidu.com';

在JavaScript中处理cookie比较麻烦,因为接口过于简单,只有BOM的document.cookie属性。在设置值时,可以通过document.cookie属性设置新的cookie字符串。这个字符串在被解析后会添加到原有cookie中。设置document.cookie不会覆盖之前存在的任何cookie,除非设置了已有的cookie。要为创建的cookie指定额外的信息,只要像Set-Cookie头部一样直接在后面追加相同格式的字符串即可:

document.cookie = encodeURIComponent("name") + "=" +
                  encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";
// 使用encodeURIComponent()对名称和值进行编码

5.cookie的缺陷

  • cookie 不够大

每个cookie的大小为4KB(名字和值都包含在这4KB之内),对于复杂的存储需求来说是不够用的。当 cookie 超过 4KB 时,它将面临被裁切的命运。这样看来,cookie 只能用来存取少量的信息。此外很多浏览器对一个站点的cookie个数也是有限制的(一般来说不超过300个cookie)。

  • 过多的 cookie 会带来巨大的性能浪费

cookie是与特定域绑定的。同一个域名下的所有请求,都会携带 cookie。大家试想,如果我们此刻仅仅是请求一张图片或者一个 CSS 文件,我们也要携带一个 cookie 跑来跑去(关键是 cookie 里存储的信息并不需要),这是一件多么劳民伤财的事情。cookie 虽然小,但随着请求的叠加,这样的不必要的 cookie 带来的开销将是无法想象的。

cookie是用来维护用户信息的,而域名(domain)下所有请求都会携带cookie,但对于静态文件的请求,携带cookie信息根本没有用,此时可以通过CDN(存储静态文件的)的域名和主站的域名分开来解决。

  • 由于在HTTP请求中的cookie是明文传递的,所以安全性成问题,除非用HTTPS。

6.cookie与安全

有两种方法可以确保 cookie 被安全发送,并且不会被意外的参与者或脚本访问:Secure 属性和HttpOnly 属性。

标记为 Secure 的 cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端,因此可以预防中间人攻击。但即便设置了 Secure 标记,敏感信息也不应该通过 cookie 传输,因为 cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障, 例如,可以访问客户端硬盘的人可以读取它。

从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用cookie的 Secure 标记。

JavaScript  Document.cookie API 无法访问带有 HttpOnly 属性的cookie;此类 cookie 仅作用于服务器。例如,持久化服务器端会话的 cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS)攻击。

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2019 07:28:00 GMT; Secure; HttpOnly

对cookie的限制及其特性决定了cookie并不是存储大量数据的理想方式,让“专业的人做专业的事情”,Web Storage 出现了。

HTML5中新增了本地存储的解决方案----Web Storage,这样有了Web Storage后,cookie能只做它应该做的事情了—— 作为客户端与服务器交互的通道,保持客户端状态。

二、Web Storage

Web Storage的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题。Web Storage API包含了两个对象:localStorage和sessionStorage,本质上是映射字符串键和值的对象化。localStorage是永久存储机制,sessionStorage是跨会话的存储机制。这两种浏览器存储API提供了在浏览器中不受页面刷新影响而存储数据的两种方式。

1、Storage 对象

Window 对象的localStorage 和 sessionStorage 属性引用的是 Storage对象。Storage对象用于保存名/值对数据,直至存储空间上限(由浏览器决定)。一般来说,客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数据存储空间。不同浏览器给localStorage和sessionStorage设置了不同的空间限制,但大多数会限制为每个源5MB。

Storage对象定义了如下方法:

  • clear():删除所有值;不在Firefox中实现。
  • getItem(name):取得给定name的值。
  • key(index):取得给定数值位置的名称。
  • removeItem(name):删除给定name的名/值对。
  • setItem(name, value):设置给定name的值。

Storage 对象中的键值对总是以字符串的形式存储,这意味着数值类型会自动转化为字符串类型。

2、sessionStorage

sessionStorage对象只存储会话数据,这意味着数据只会存储到浏览器关闭。这跟浏览器关闭时会消失的会话cookie类似。存储在sessionStorage中的数据不受页面刷新影响,可以在浏览器崩溃并重启后恢复(取决于浏览器,Firefox和WebKit支持,IE不支持)。

sessionStorage 特别应该注意一点就是,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 sessionStorage 数据便无法共享。

localStorage 与 sessionStorage 在 API 方面无异,这里我们以 sessionStorage 为例:

  • 存储数据:setItem()
sessionStorage.setItem('user_name', 'juejin')
  • 读取数据: getItem()
sessionStorage.getItem('user_name')
  • 删除某一键名对应的数据: removeItem()
sessionStorage.removeItem('user_name')
  • 清空数据记录:clear()
sessionStorage.clear()

虽然Web Storage存储数据会带来诸多便利,但实际开发中使用它也有不便之处:

  • sessionStorage本身有API,但是只是简单的 key/value形式
  • sessionStorage只存储字符串,需要转换成json对象

基于上面两点,开发过程中会对它进行封装后再调用:

 // 碍于文章篇幅,并未将完整代码展示出来
 // 想要获取完整的代码,可以加wx:qqlcx55
 // 将属性存储在某一模块下
  setItem(key,value,module_name){
    if (module_name){
      let val = this.getItem(module_name);
      val[key] = value;
      this.setItem(module_name, val);
    }else{
      let val = this.getStorage();
      val[key] = value;
      window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(val));
    }
  },
  // 获取某一个模块下面的属性
  getItem(key,module_name){
    if (module_name){
      let val = this.getItem(module_name);
      if(val) return val[key];
    }
    return this.getStorage()[key];
  },
  getStorage(){
    return JSON.parse(window.sessionStorage.getItem(STORAGE_KEY) || '{}');
  }

3、localStorage

localStorage 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;而当页面会话结束(即当页面被关闭时),存储在 sessionStorage 的数据会被清除 。要访问同一个localStorage对象,页面必须来自同一个域(子域不可以)、在相同的端口上使用相同的协议。

考虑到 localStorage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串:

Base64 格式的图片字符串

有的网站还会用它存储一些不经常更新的 CSS、JS 等静态资源。

4、Web Storage与cookie 之间的区别

我们先来说说两者的共同点,然后再细说下哪些地方有区别:

  • 共同点:都是保存在浏览器端,且都遵循同源策略。
  • 不同点:在于生命周期与作用域的不同

作用域:localStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据。不过sessionStorage比localStorage更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下


生命周期:localStorage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 sessionStorage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。

说到底,Web Storage 是对 Cookie 的拓展,它只能用于存储少量的简单数据。当遇到大规模的、结构复杂的数据时,Web Storage 也爱莫能助了。这时候我们就要清楚我们的终极大 boss——IndexedDB!

四、IndexedDB

Indexed Database API简称IndexedDB,是浏览器中存储结构化数据的一个方案。IndexedDB背后的思想是创造一套API,方便JavaScript对象的存储和获取,同时也支持查询和搜索。

IndexedDB是类似于MySQL或Web SQL Database的数据库。与传统数据库最大的区别在于,IndexedDB使用对象存储而不是表格保存数据。IndexedDB数据库就是在一个公共命名空间下的一组对象存储,类似于NoSQL风格的实现。既然是数据库了,那就不是 5M、10M 这样小打小闹级别了。理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250M)。

1.IndexedDB的特点

  • 键值对储存。

IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

  • 异步

IndexedDB的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。绝大多数IndexedDB操作要求添加onerror和onsuccess事件处理程序来确定输出。IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 localStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

  • 支持事务。

IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

  • 同源限制

IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

  • 储存空间大

IndexedDB 的储存空间比 localStorage 大得多,一般来说不少于 250MB,甚至没有上限。

  • 支持二进制储存。

IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

2.IndexedDB 使用流程

在IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式。

接下来,通过一个基本的 IndexedDB 使用流程,旨在对 IndexedDB 形成一个感性的认知:

  • 打开/创建一个 IndexedDB 数据库(当该数据库不存在时,open 方法会直接创建一个名为 admin 新数据库)
  // 后面的回调中,我们可以通过event.target.result拿到数据库实例
  let db
  // 参数1位数据库名,参数2为版本号
  const request = window.indexedDB.open("admin", 1)
  // 使用IndexedDB失败时的监听函数
  request.onerror = function(event) {
     console.log('无法使用IndexedDB')
   }
  // 成功
  request.onsuccess  = function(event){
    // 此处就可以获取到db实例
    db = event.target.result
    console.log("你打开了IndexedDB")
  }
  • 创建一个 object store(object store 对标到数据库中的“表”单位)
// onupgradeneeded事件会在初始化数据库/版本发生更新时被调用,我们在它的监听函数中创建object store
request.onupgradeneeded = function(event){
  let objectStore
  // 如果同名表未被创建过,则新建test表
  if (!db.objectStoreNames.contains('test')) {
    objectStore = db.createObjectStore('test', { keyPath: 'id' })
  }
}  
  • 构建一个事务来执行一些数据库操作,像增加或提取数据等
  // 创建事务,指定表格名称和读写权限
  const transaction = db.transaction(["test"],"readwrite")
  // 拿到Object Store对象
  const objectStore = transaction.objectStore("test")
  // 向表格写入数据
  objectStore.add({id: 1, name: 'juejin'})
  • 通过监听正确类型的事件以等待操作完成。
  // 操作成功时的监听函数
  transaction.oncomplete = function(event) {
    console.log("操作成功")
  }
  // 操作失败时的监听函数
  transaction.onerror = function(event) {
    console.log("这里有一个Error")
  }

3.Web Storage、cookie 和 IndexedDB之间的区别

有了这些存储手段,就可以在客户端通过使用JavaScript存储可观的数据。因为这些数据没有加密,所以要注意不能使用它们存储敏感信息。

总结

正是浏览器存储、缓存技术的出现和发展,为我们的前端应用带来了无限的转机。近年来基于存储、缓存技术的第三方库层出不绝,此外还衍生出了 PWA 这样优秀的 Web 应用模型。总结下本文几个核心观点:

  • Cookie 的本职工作并非本地存储,而是“维持状态”。
  • Web Storage定义了两个对象用于存储数据:sessionStorage和localStorage。前者用于严格保存浏览器一次会话期间的数据,因为数据会在浏览器关闭时被删除。后者用于会话之外持久保存数据。
  • IndexedDB是类似于SQL数据库的结构化数据存储机制。不同的是,IndexedDB存储的是对象,而不是数据表。
本文首发于公众号《前端有道》,如果对本文有啥疑问或建议,欢迎加我微信qqlcx55一起学习哈

参考文章

查看原文

赞 14 收藏 5 评论 0

星野 发布了文章 · 2020-07-14

GitHub 通过 jsdelivr CDN加持

GitHub 通过 jsdelivr CDN加持

两种引用方式:

  • 存入仓库分支里面,直接引用
  • 创建版本号后,在引用

在这是不是很好奇,为什么引用要弄成这么麻烦。网上我看过类似教程,他们总是忽略一个关键的原因避而不谈,因为这点也原因差点坑了我。超过50M jsdelivr就无法访问。为了能免费撸羊毛,建议使用发布版本号方案,只要单次版本号的大小不超过50M即可,多次版本号就可以突破50M限制。

程序包大小超出了配置的50 MB限制。

很好奇为什么我会发现这个问题。我作了一个假设,如果仓库存放超多的图片的资源,且能够访问的话,那么这种教程,作为一种免费撸羊毛的手段在中国应该被大佬推荐,不至于到现在还不温不火。终其原因我尝试上传几张超大的图片,就被提示无法访问,分成两次版本号后(单次不超过50M)总大小超过50M却能正常访问。

1. 直接引用

格式为:

https://cdn.jsdelivr.net/gh/<用户名>/<仓库名>/<文件及路径>

例:

GitHub

https://github.com/qqlcx5/figure-bed/blob/master/img/20200710230327.jpg

转成 jsdelivr

https://cdn.jsdelivr.net/gh/qqlcx5/figure-bed/img/20200710230327.jpg

总结:

github.com`替换成cdn.jsdelivr.net/gh

/blob/master 删除

版本号

版本号用@符链接。格式:

https://cdn.jsdelivr.net/gh/<用户名>/<仓库名>@[版本号]/<文件及路径>

例:

GitHub

https://github.com/qqlcx5/figure-bed/blob/1.0/img/20200710230327.jpg

转成 jsdelivr

https://cdn.jsdelivr.net/gh/qqlcx5/figure-bed@1.0/img/20200710230327.jpg

总结:

github.com`替换成cdn.jsdelivr.net/gh

/blob/ 替换成 @ 
注:1.0 创建的版本号

已发布的版本不会受到仓库内容变化的影响
如何按版本号引用。链接相对稳定也是我特别推荐的原因。

订阅

WeChat

查看原文

赞 1 收藏 1 评论 0

星野 发布了文章 · 2020-07-14

GitHub + jsDelivr + PicGo打造免费图床

GitHub + jsDelivr + PicGo 打造稳定快速、高效免费图床

git

目前图床优劣

  1. 微博等博客图床:以前用的人比较多,从 2019 年 4 月开始开启了防盗链,凉凉。
  2. SM.MS:运营四年多了,也变得越来越慢了,到了晚上直接打不开图片,速度堪忧。
  3. 其他小众图床:随时有挂掉的风险。
  4. Imgur等国外图床:国内访问速度太慢,随时有被墙的风险。
  5. 优质储存服务:例如七牛云、又拍云、腾讯云COS、阿里云OSS等,操作繁琐,又是实名认证又是域名备案的等。说真的以后网站的访问量大的话,还的买带宽等服务,按月收费,费用高。
  6. GitHub 图床:免费,但是国内访问速度慢(利用 jsDelivr 提供的免费的 CDN 加速,鸡贼)

搭建教程

  1. 新建 GitHub 仓库
  2. 创建个人访问令牌
  3. 配置 PicGo 设置
  4. 上传图片,转成 jsDelivr 地址

新建 GitHub 仓库

创建一个仓库,用来存放图片。

-w1059

生成个人访问令牌Token

前往设置

作用:授权仓库的操作权限,通过API实现自动化。

-w1139

然后填写 Token 描述,勾选 repo、write、read然后点击 Generate token 生成一个 Token

token

因为 Token 只会显示一次,所以先保存笔记本等。

-w1030

配置 PicGo

前往PicGo官网

安装好后开始配置图床

  1. 设定仓库名:按照 用户名/仓库名 的格式填写(就是刚才创建的仓库)
  2. 设定分支名:master
  3. 设定 Token:粘贴之前叫你保存的Token。
  4. 设定自定义域名:它的的作用是,在图片上传后,PicGo 会按照自定义域名+上传的图片名的方式生成访问链接,放到粘贴板上。

https://cdn.jsdelivr.net/gh/用户名/仓库名

-w800

上传图片测试

上传图片后,在相册可以看到刚才的图片,点击复制的URL就是通过jsdelivr CDN加速的。

-w800

此外 PicGo 还有相册功能,可以对已上传的图片进行删除,修改链接等快捷操作,PicGo 还可以生成不同格式的链接、支持批量上传、快捷键上传、自定义链接格式、上传前重命名等,更多功能自己去探索吧!

最后,以上步骤只是通过工具提升效率罢了,了解github地址转化成jsDelivr地址规则,其实就能愉快的撸羊毛。

某分支和版本号大小不能超过50M会直接被jsDelivr屏蔽。所以单某个分支接近临界值,换分支继续就得了。

相关文章

GitHub 通过 jsdelivr CDN加持

订阅

WeChat

参考链接:

https://www.cnblogs.com/sitoi/p/11848816.html

查看原文

赞 0 收藏 0 评论 0

星野 发布了文章 · 2020-07-14

解决GitHub下载慢的方法

解决GitHub下载慢的方法总结

目前已知:

1. 搭建科学上网
2. 码云中转下载
3. cnpmjs镜像
4. hosts
5. GitClone 缓存加速(新)

如果有更好的方法,在评论区见。

科学上网

全局上网后如果下载速度并没有显著提升的话,搭配代理Proxifier等工具。

码云

登录码云官网后点击 + 选择从GitHub导入仓库。

-w477

然后进入这个页面。

-w750

导入成功,复制url或者地址,用法和github一样。

-w1052

cnpmjs 镜像

这个方法更加的简单,只需要修改你的路径github.comgithub.com.cnpmjs.org

git clone https://github.com/qqlcx5/editor.git

#改为
git clone https://github.com.cnpmjs.org/qqlcx5/editor.git

hosts

解决办法

直接找出github域名所对应的IP地址,直接添加在本地host中。这样每次请求gihub时就无须在向DNS查询地址了。

DNS查找IP地址

将下列的IP地址替换成点击对应的地址获取IP地址。

例:

199.232.69.194 github.global.ssl.fastly.net 
140.82.112.3 github.com
52.217.82.236 github-cloud.s3.amazonaws.com

window

在我的电脑复制下列地址回车键,看到hosts文件,然后复制我的桌面。

C:\Windows\System32\drivers\etc

将刚才的地址复制进去保存,然后将保存的文件拖拽到刚才的地址进行替换。(需要管理权限)

MAC

按快捷键 shift + option + G弹出对话框,粘贴 /etc/hosts,回车键,看到host复一份修改,然后替换原来文件,类似上面window操作。

通过工具快捷修改hosts

SwitchHosts开源地址: https://github.com/oldj/SwitchHosts/releases

MacOS下载懒人链接:
[SwitchHosts._macOS_3.5.4.5517.dmg](
https://github.com/oldj/Switc...

Windows下载懒人链接: SwitchHosts._windows_portable_3.5.4.5517.exe

刷新 DNS 缓存

在终端或CMD中,执行以下命令:

Window: ipconfig/flushdns
mac执行:sudo dscacheutil –flushcache

GitClone

当开发者经gitclone.com中转clone github上的代码库时,gitclone.com会对代码库进行mirror缓存,以后有开发者clone时,将直接从mirror缓存中获取数据,mirror将在每天夜间从github.com同步。

设置git的超时参数


​ 首先要设置git的超时参数,防止项目较大时,服务器端mirror时间过长导致git报504(超时)

git config --global http.lowSpeedLimit 0
git config --global http.lowSpeedTime 999999 

方法1:修改URL

该仓库总大小30M左右,拿来作为实验对象。

github
https://github.com/qqlcx5/editor

GitClone
git clone https://gitclone.com/github.com/qqlcx5/editor

大概在10秒内下载完成,觉得不错。

方法二 修改git配置

git config --global url."https://gitclone.com/github.com/".insteadOf https://github.com/

在这补充一点,官网推荐 git config --global url.“https://gitclone.com/".insteadOf https://,个人觉得不是友好,毕竟还有很多仓库,如果上面这样写的话,估计也会走gitclone通道。估计引发XX问题。

了解更多前往gitclone官网

订阅

WeChat

参考链接

https://www.jianshu.com/p/0493dcc15d6f

查看原文

赞 0 收藏 0 评论 1

星野 回答了问题 · 2019-07-04

解决关于vue组件scope样式的穿透写法>>>,请问该如何解决此编译报错问题?

我也遇到这问题,查了谷歌一分钟就解决这问题,less 应该是不支持深度选择器写法(>>> ),记得stylus并不会报错,以前写项目弄过。
less 的解决方法就是添加新的style标签写就行

<style scoped>
.nav-theme-dark >>> .logo {
  color: #ffffff;
}
</style>

关注 4 回答 3

星野 评论了文章 · 2019-05-14

2019.05.08webstorm破解,持续更新中。。。

clipboard.png

起因

2017年末,入门前端行业,2018.3月左右webstorm 网上的注册码频频失效后,由于不熟悉其他编辑器,导致工作不顺。

后续研究各种方法破解webStorm,为了避免在遇到此情况,分享出来,让webstorm爱好者少入坑。

华而至简

第一步

打开软件安装目录,复制 binlib 文件夹进行替换

第二步

打开bin里面的 webstorm64.exe.vmoptionswebstorm.exe.vmoptions 文件,添加以下代码

-javaagent: 路径、破解补丁名称(注意:软件开不了,可能安装的路径不同,导致的,重新排除一下)

拿我安装目录为例:

-javaagent:D:\Program Files\JetBrains\WebStorm\bin\JetbrainsIdesCrack-4.2-release.jar

如果我想安装在D:\sofeware目录下

-javaagent:D:\sofeware\JetBrains\WebStorm\bin\JetbrainsIdesCrack-4.2-release.jar

第三步

打开软件选择 activation code,复制下面代码进去。OK

56ZS5PQ1RF-eyJsaWNlbnNlSWQiOiI1NlpTNVBRMVJGIiwibGljZW5zZWVOYW1lIjoi5q2j54mI5o6I5p2DIC4iLCJhc3NpZ25lZU5hbWUiOiIiLCJhc3NpZ25lZUVtYWlsIjoiIiwibGljZW5zZVJlc3RyaWN0aW9uIjoiRm9yIGVkdWNhdGlvbmFsIHVzZSBvbmx5IiwiY2hlY2tDb25jdXJyZW50VXNlIjpmYWxzZSwicHJvZHVjdHMiOlt7ImNvZGUiOiJJSSIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IkFDIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiRFBOIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiUFMiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJHTyIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IkRNIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiQ0wiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJSUzAiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJSQyIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IlJEIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiUEMiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJSTSIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IldTIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiREIiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJEQyIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IlJTVSIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9XSwiaGFzaCI6IjEyMjkxNDk4LzAiLCJncmFjZVBlcmlvZERheXMiOjAsImF1dG9Qcm9sb25nYXRlZCI6ZmFsc2UsImlzQXV0b1Byb2xvbmdhdGVkIjpmYWxzZX0=-SYSsDcgL1WJmHnsiGaHUWbaZLPIe2oI3QiIneDtaIbh/SZOqu63G7RGudSjf3ssPb1zxroMti/bK9II1ugHz/nTjw31Uah7D0HqeaCO7Zc0q9BeHysiWmBZ+8bABs5vr25GgIa5pO7CJhL7RitXQbWpAajrMBAeZ2En3wCgNwT6D6hNmiMlhXsWgwkw2OKnyHZ2dl8yEL+oV5SW14t7bdjYGKQrYjSd4+2zc4FnaX88yLnGNO9B3U6G+BuM37pxS5MjHrkHqMTK8W3I66mIj6IB6dYXD5nvKKO1OZREBAr6LV0BqRYSbuJKFhZ8nd6YDG20GvW6leimv0rHVBFmA0w==-MIIElTCCAn2gAwIBAgIBCTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTE4MTEwMTEyMjk0NloXDTIwMTEwMjEyMjk0NlowaDELMAkGA1UEBhMCQ1oxDjAMBgNVBAgMBU51c2xlMQ8wDQYDVQQHDAZQcmFndWUxGTAXBgNVBAoMEEpldEJyYWlucyBzLnIuby4xHTAbBgNVBAMMFHByb2QzeS1mcm9tLTIwMTgxMTAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxcQkq+zdxlR2mmRYBPzGbUNdMN6OaXiXzxIWtMEkrJMO/5oUfQJbLLuMSMK0QHFmaI37WShyxZcfRCidwXjot4zmNBKnlyHodDij/78TmVqFl8nOeD5+07B8VEaIu7c3E1N+e1doC6wht4I4+IEmtsPAdoaj5WCQVQbrI8KeT8M9VcBIWX7fD0fhexfg3ZRt0xqwMcXGNp3DdJHiO0rCdU+Itv7EmtnSVq9jBG1usMSFvMowR25mju2JcPFp1+I4ZI+FqgR8gyG8oiNDyNEoAbsR3lOpI7grUYSvkB/xVy/VoklPCK2h0f0GJxFjnye8NT1PAywoyl7RmiAVRE/EKwIDAQABo4GZMIGWMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGEpG9oZGcfLMGNBkY7SgHiMGgTcMEgGA1UdIwRBMD+AFKOetkhnQhI2Qb1t4Lm0oFKLl/GzoRykGjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBggkA0myxg7KDeeEwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQAF8uc+YJOHHwOFcPzmbjcxNDuGoOUIP+2h1R75Lecswb7ru2LWWSUMtXVKQzChLNPn/72W0k+oI056tgiwuG7M49LXp4zQVlQnFmWU1wwGvVhq5R63Rpjx1zjGUhcXgayu7+9zMUW596Lbomsg8qVve6euqsrFicYkIIuUu4zYPndJwfe0YkS5nY72SHnNdbPhEnN8wcB2Kz+OIG0lih3yz5EqFhld03bGp222ZQCIghCTVL6QBNadGsiN/lWLl4JdR3lJkZzlpFdiHijoVRdWeSWqM4y0t23c92HXKrgppoSV18XMxrWVdoSM3nuMHwxGhFyde05OdDtLpCv+jlWf5REAHHA201pAU6bJSZINyHDUTB+Beo28rRXSwSh3OUIvYwKNVeoBY+KwOJ7WnuTCUq1meE6GkKc4D/cXmgpOyW/1SmBz3XjVIi/zprZ0zf3qH5mkphtg6ksjKgKjmx1cXfZAAX6wcDBNaCL+Ortep1Dh8xDUbqbBVNBL4jbiL3i3xsfNiyJgaZ5sX7i8tmStEpLbPwvHcByuf59qJhV/bZOl8KqJBETCDJcY6O2aqhTUy+9x93ThKs1GKrRPePrWPluud7ttlgtRveit/pcBrnQcXOl1rHq7ByB8CFAxNotRUYL9IF5n3wJOgkPojMy6jetQA5Ogc8Sm7RG6vg1yow==

汉化

将resources_cn.jar文件复制到安装目录的 lib 目录下,重启软件。完事

(发牢骚)尝试几个汉化包,体验后觉得还是这个汉化包是目前兼容性最好,范围广,没有遇到Bug。有些汉化包引入造成有些功能直接无法使用。坑爹啊

护目色主题

WebStorm软件打开后==>右上角文件==>导入设置==>选择settings.jar==>重启即可

如果对你有帮助,还请帮忙打赏下,万分感谢

求打赏

查看原文

星野 评论了文章 · 2019-05-08

2019.05.08webstorm破解,持续更新中。。。

clipboard.png

起因

2017年末,入门前端行业,2018.3月左右webstorm 网上的注册码频频失效后,由于不熟悉其他编辑器,导致工作不顺。

后续研究各种方法破解webStorm,为了避免在遇到此情况,分享出来,让webstorm爱好者少入坑。

华而至简

第一步

打开软件安装目录,复制 binlib 文件夹进行替换

第二步

打开bin里面的 webstorm64.exe.vmoptionswebstorm.exe.vmoptions 文件,添加以下代码

-javaagent: 路径、破解补丁名称(注意:软件开不了,可能安装的路径不同,导致的,重新排除一下)

拿我安装目录为例:

-javaagent:D:\Program Files\JetBrains\WebStorm\bin\JetbrainsIdesCrack-4.2-release.jar

如果我想安装在D:\sofeware目录下

-javaagent:D:\sofeware\JetBrains\WebStorm\bin\JetbrainsIdesCrack-4.2-release.jar

第三步

打开软件选择 activation code,复制下面代码进去。OK

56ZS5PQ1RF-eyJsaWNlbnNlSWQiOiI1NlpTNVBRMVJGIiwibGljZW5zZWVOYW1lIjoi5q2j54mI5o6I5p2DIC4iLCJhc3NpZ25lZU5hbWUiOiIiLCJhc3NpZ25lZUVtYWlsIjoiIiwibGljZW5zZVJlc3RyaWN0aW9uIjoiRm9yIGVkdWNhdGlvbmFsIHVzZSBvbmx5IiwiY2hlY2tDb25jdXJyZW50VXNlIjpmYWxzZSwicHJvZHVjdHMiOlt7ImNvZGUiOiJJSSIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IkFDIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiRFBOIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiUFMiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJHTyIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IkRNIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiQ0wiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJSUzAiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJSQyIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IlJEIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiUEMiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJSTSIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IldTIiwicGFpZFVwVG8iOiIyMDIwLTAzLTEwIn0seyJjb2RlIjoiREIiLCJwYWlkVXBUbyI6IjIwMjAtMDMtMTAifSx7ImNvZGUiOiJEQyIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9LHsiY29kZSI6IlJTVSIsInBhaWRVcFRvIjoiMjAyMC0wMy0xMCJ9XSwiaGFzaCI6IjEyMjkxNDk4LzAiLCJncmFjZVBlcmlvZERheXMiOjAsImF1dG9Qcm9sb25nYXRlZCI6ZmFsc2UsImlzQXV0b1Byb2xvbmdhdGVkIjpmYWxzZX0=-SYSsDcgL1WJmHnsiGaHUWbaZLPIe2oI3QiIneDtaIbh/SZOqu63G7RGudSjf3ssPb1zxroMti/bK9II1ugHz/nTjw31Uah7D0HqeaCO7Zc0q9BeHysiWmBZ+8bABs5vr25GgIa5pO7CJhL7RitXQbWpAajrMBAeZ2En3wCgNwT6D6hNmiMlhXsWgwkw2OKnyHZ2dl8yEL+oV5SW14t7bdjYGKQrYjSd4+2zc4FnaX88yLnGNO9B3U6G+BuM37pxS5MjHrkHqMTK8W3I66mIj6IB6dYXD5nvKKO1OZREBAr6LV0BqRYSbuJKFhZ8nd6YDG20GvW6leimv0rHVBFmA0w==-MIIElTCCAn2gAwIBAgIBCTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTE4MTEwMTEyMjk0NloXDTIwMTEwMjEyMjk0NlowaDELMAkGA1UEBhMCQ1oxDjAMBgNVBAgMBU51c2xlMQ8wDQYDVQQHDAZQcmFndWUxGTAXBgNVBAoMEEpldEJyYWlucyBzLnIuby4xHTAbBgNVBAMMFHByb2QzeS1mcm9tLTIwMTgxMTAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxcQkq+zdxlR2mmRYBPzGbUNdMN6OaXiXzxIWtMEkrJMO/5oUfQJbLLuMSMK0QHFmaI37WShyxZcfRCidwXjot4zmNBKnlyHodDij/78TmVqFl8nOeD5+07B8VEaIu7c3E1N+e1doC6wht4I4+IEmtsPAdoaj5WCQVQbrI8KeT8M9VcBIWX7fD0fhexfg3ZRt0xqwMcXGNp3DdJHiO0rCdU+Itv7EmtnSVq9jBG1usMSFvMowR25mju2JcPFp1+I4ZI+FqgR8gyG8oiNDyNEoAbsR3lOpI7grUYSvkB/xVy/VoklPCK2h0f0GJxFjnye8NT1PAywoyl7RmiAVRE/EKwIDAQABo4GZMIGWMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGEpG9oZGcfLMGNBkY7SgHiMGgTcMEgGA1UdIwRBMD+AFKOetkhnQhI2Qb1t4Lm0oFKLl/GzoRykGjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBggkA0myxg7KDeeEwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQAF8uc+YJOHHwOFcPzmbjcxNDuGoOUIP+2h1R75Lecswb7ru2LWWSUMtXVKQzChLNPn/72W0k+oI056tgiwuG7M49LXp4zQVlQnFmWU1wwGvVhq5R63Rpjx1zjGUhcXgayu7+9zMUW596Lbomsg8qVve6euqsrFicYkIIuUu4zYPndJwfe0YkS5nY72SHnNdbPhEnN8wcB2Kz+OIG0lih3yz5EqFhld03bGp222ZQCIghCTVL6QBNadGsiN/lWLl4JdR3lJkZzlpFdiHijoVRdWeSWqM4y0t23c92HXKrgppoSV18XMxrWVdoSM3nuMHwxGhFyde05OdDtLpCv+jlWf5REAHHA201pAU6bJSZINyHDUTB+Beo28rRXSwSh3OUIvYwKNVeoBY+KwOJ7WnuTCUq1meE6GkKc4D/cXmgpOyW/1SmBz3XjVIi/zprZ0zf3qH5mkphtg6ksjKgKjmx1cXfZAAX6wcDBNaCL+Ortep1Dh8xDUbqbBVNBL4jbiL3i3xsfNiyJgaZ5sX7i8tmStEpLbPwvHcByuf59qJhV/bZOl8KqJBETCDJcY6O2aqhTUy+9x93ThKs1GKrRPePrWPluud7ttlgtRveit/pcBrnQcXOl1rHq7ByB8CFAxNotRUYL9IF5n3wJOgkPojMy6jetQA5Ogc8Sm7RG6vg1yow==

汉化

将resources_cn.jar文件复制到安装目录的 lib 目录下,重启软件。完事

(发牢骚)尝试几个汉化包,体验后觉得还是这个汉化包是目前兼容性最好,范围广,没有遇到Bug。有些汉化包引入造成有些功能直接无法使用。坑爹啊

护目色主题

WebStorm软件打开后==>右上角文件==>导入设置==>选择settings.jar==>重启即可

如果对你有帮助,还请帮忙打赏下,万分感谢

求打赏

查看原文

星野 发布了文章 · 2019-03-10

CSS文字交错滑动效果-001

项目展示

项目展示

技术难点:

引用MDN解释:content: attr(data-text);是CSS中引用的HTML元素的属性名称。

实例:

HTML

p data-foo="hello">world</p>

CSS

[data-foo]::before {
  content: attr(data-foo) " ";
}
输出 //hello world

初始样式:基本每个人都会(忽略)

效果展示1

第一步:通过CSS3 的transform属性移动文字,样式如下

        .box span:nth-child(odd) {
            transform: translateY(-100%);
        }

        .box span:nth-child(even) {
            transform: translateY(100%);
        }

效果展示2

第二步:通过content 的arr属性引用的HTML元素的属性名称

        <span data-text="N">N</span>      //html
        .box span::before {
        content: attr(data-text);
        position: absolute;     // 脱离文档流
        color: red;
    }
        .box span:nth-child(odd)::before {
        transform: translateY(100%);
    }

    .box span:nth-child(even)::before {
        transform: translateY(-100%);
    }

效果展示3

第三步:鼠标经过,修改transform属性就行

    .box:hover span {
        transform: translateY(0);
    }

项目源码

了解更多,个人博客

求打赏

查看原文

赞 3 收藏 2 评论 0

星野 关注了用户 · 2019-03-09

山与海 @shanyuhai

关注 5

认证与成就

  • 获得 50 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2018-02-05
个人主页被 916 人浏览