Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 提交作业 #3

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 62 additions & 30 deletions src/components/HelloWorld.vue
Original file line number Diff line number Diff line change
@@ -1,44 +1,76 @@
<template>
<div class="hello" data-spma="aa">
<span>show spm:{{spmText}}</span>
<div data-spmb="bb">
<button data-spmc="cc">Click it</button>
</div>
<div data-spmb="dd">
<button data-spmc="ff">Click it</button>
<span>show spm:{{ spmText }}</span>
<!-- 加了一层div,模拟非每层元素都有spm数据的场景 -->
<div>
<div data-spmb="bb">
<button data-spmc="cc">Click it</button>
</div>
<div id="inner" data-spmb="dd">
<button data-spmc="ff">Click it</button>
<InnerButton />
</div>
</div>
</div>
</template>

<script>
// TODO 利用事件代理实现一个简单的收集spm信息的方法,注意不是针对每一个按钮进行函数绑定。场景:考虑一下如果一个页面中有很多按钮,需要如何处理
export default {
name: 'HelloWorld',
data: ()=>{
return {
spmText: 'xx.xx.xx'
import { init, addHook, removeHook } from '../utils/spm'
import InnerButton from './InnerButton.vue'

// TODO 利用事件代理实现一个简单的收集spm信息的方法,注意不是针对每一个按钮进行函数绑定。场景:考虑一下如果一个页面中有很多按钮,需要如何处理
export default {
name: 'HelloWorld',
components: {
InnerButton
},
data: () => {
return {
spmText: 'xx.xx.xx'
}
},
mounted() {
const hook = spmText => {
this.spmText = spmText
}

// 初始化信息收集,并传入hook进行数据处理
// 建议在入口JS文件或入口组件进行初始化
// 此处为通过用例,在组件中初始化
init({
hooks: [hook]
})

// 可以移除hook,这样点击就无效
setTimeout(() => {
removeHook(hook)
}, 3000)

// 可以添加hook,让点击重新生效
setTimeout(() => {
addHook(hook)
}, 5000)
}
}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}

ul {
list-style-type: none;
padding: 0;
}

li {
display: inline-block;
margin: 0 10px;
}

a {
color: #42b983;
}
h3 {
margin: 40px 0 0;
}

ul {
list-style-type: none;
padding: 0;
}

li {
display: inline-block;
margin: 0 10px;
}

a {
color: #42b983;
}
</style>
24 changes: 24 additions & 0 deletions src/components/InnerButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<div>
<div>
{{ spmText }}
</div>
<button id="innerButton" data-spmc="gg">Click it</button>
</div>
</template>

<script>
export default {
name: 'inner-button'
}
</script>

<script setup>
import { ref } from 'vue'
import { addHook } from '../utils/spm'

const spmText = ref('xx.xx.xx')
addHook(spm => {
spmText.value = spm
})
</script>
144 changes: 144 additions & 0 deletions src/utils/spm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 缓存所有hook,并在click事件触发时依次运行
let hooks = []

// 为document.body绑定事件,获取spm值
export function init(params) {
// 支持自定义监听点击的元素
const element = params.element ?? document.body
// 初始化传入的hook
const hookArray = params.hooks ?? []
// 初始化时存储所有hook
hooks.push(...hookArray)

element.addEventListener('click', function (event) {
let spmTextArray = [] // 存储每个元素的spm值
getSpmText(event.target, event.currentTarget, spmTextArray)
// 生成spm字符串
const spmText = spmTextArray.reverse().join('.')

// 依次运行hook,并将spm字符串传入
hooks.forEach(hook => {
hook(spmText)
})
})
}

// 添加hook
export function addHook(hook) {
hooks.push(hook)
}

// 移除hook
export function removeHook(removeHookItem) {
const removeIndex = hooks.findIndex(hook => hook === removeHookItem)
hooks.splice(removeIndex, 1)
}

// 不断向父级元素查找,直到查找到绑定事件的元素
function getSpmText(target, currentTarget, arr) {
// 早点当前元素的spm
const targetSpmText = findSpmText(target)

// 如果有spm则存入数组
if (targetSpmText) {
arr.push(targetSpmText)
}

// 如果查找到绑定事件的元素,就将结果存储并退出循环
if (target === currentTarget) {
return
/*
为什么使用arr.push再arr.reverse().join('.')?
一、实际测试了以下几段代码:


1. 使用arr.unshift,再arr.join('.')

console.time('array unshift')
let arr = []

for (let i = 0; i < 100000000; i++) {
arr.unshift('aa')
}

let str = arr.join('.')
console.timeEnd('array unshift')

循环100W次
耗时: 1:55.289 (m:ss.mmm)

2. 使用arr.push再arr.reverse().join('.')

console.time('array push')
let arr = []

for (let i = 0; i < 100000000; i++) {
arr.push('aa')
}

let str = arr.reverse().join('.')
console.timeEnd('array push')

测试环境:Node.js v16.14.1
循环100W次
耗时: 61.79ms
循环1000W次
耗时: 672.56ms
循环1亿次
耗时: 8.396s

测试环境:Chrome 104.0.5112.101(正式版本) (arm64)
循环100W次
耗时: 52.5791015625 ms
循环1000W次
耗时: 392.364990234375 ms
循环1亿次
耗时: 3324.813232421875 ms

3. 使用String

console.time('string')
let str = ''

for (let i = 0; i < 100000000; i++) {
str = 'aa' + '.' + str
}
console.timeEnd('string')

测试环境:Node.js v16.14.1
循环100W次
耗时: 84.646ms
循环1000W次
耗时: 790.097ms
循环1亿次
耗时: 12.251s

测试环境:Chrome 104.0.5112.101(正式版本) (arm64)
循环100W次
耗时: 72.958984375 ms
循环1000W次
耗时: 751.48486328125 ms
循环1亿次
耗时: 6852.30615234375 ms

二、选用Array的原因
1. 从实际效果来看使用arr.push再arr.reverse().join('.'),和直接用字符串拼接性能差异不大。
2. 但考虑到用Array性能较高
3. 使用Array代码逻辑简单,不需要处理字符串拼接结果为aa.bb.cc. ,最后会多一个'.'的问题
*/
}

getSpmText(target.parentNode, currentTarget, arr)
}
// 查找当前元素是否有data-spmx属性,并且其有值
// 将data-spmx属性的值返回,无值返回''
function findSpmText(target) {
const dataset = target.dataset
const spmKey = Object.keys(dataset).find(key => key.startsWith('spm'))

if (spmKey && dataset[spmKey]) {
return dataset[spmKey]
}

return ''
}
26 changes: 18 additions & 8 deletions tests/unit/example.spec.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { shallowMount, mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue';
import { warn } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue'

jest.setTimeout(10000)
describe('HelloWorld.vue', () => {
it('校验第一个按钮的spm是否为aa.bb.cc', async () => {
const wrapper = shallowMount(HelloWorld, { attachTo: document.body });
const button = wrapper.findAll('button');
await button[0].trigger('click');
expect(wrapper.vm.spmText).toMatch('aa.bb.cc');
const wrapper = shallowMount(HelloWorld, { attachTo: document.body })
const button = wrapper.findAll('button')
await button[0].trigger('click')
expect(wrapper.vm.spmText).toMatch('aa.bb.cc')
})
})
describe('HelloWorld.vue', () => {
it('校验第一个按钮的spm是否为aa.dd.ff', async () => {
it('校验第二个按钮的spm是否为aa.dd.ff', async () => {
const wrapper = shallowMount(HelloWorld, { attachTo: document.body })
const button = wrapper.findAll('button')
await button[1].trigger('click')
expect(wrapper.vm.spmText).toMatch('aa.dd.ff')
})
})
})
describe('InnerButton.vue', () => {
it('校验第三个按钮的spm是否为aa.dd.gg', async () => {
const wrapper = mount(HelloWorld, { attachTo: document.body })
const button = wrapper.findAll('button')

// 触发InnerButton组件中button的click事件
// HelloWorld组件能监听到变化,并同步修改spmText的值
await button[2].trigger('click')
expect(wrapper.vm.spmText).toMatch('aa.dd.gg')
})
})