Movi Blog

志当存高远


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 公益404

前端知识查缺补漏

发表于 2021-10-18 | 分类于 前端 |
字数统计: 1,373 字 | 阅读时长 ≈ 6 分钟

原型链

1
2
3
4
5
6
//所有对象都有**proto**属性,指向其构造函数的 prototype 对象,
person.**proto** === Person.prototype
//每个原型都有一个 constructor 属性指向关联的构造函数
Person === Person.prototype.constructor
//终极原型
Object.prototype.**proto** === null

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//原型继承 把父相关属性和方法挂在子实例的prototype上
//缺点:1.引用类型的属性被所有实例共享 2.在创建 Child 的实例时,不能向Parent传参
function Parent(){
this.name='movi'
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child () {

}
Child.prototype = new Parent()
var child1 = new Child();

//构造函数继承 把父相关属性和方法挂在子实例对象上
//优点:1.避免了引用类型的属性被所有实例共享 2.可以在 Child 中向 Parent 传参
//缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。
function Parent () {
this.name = ['kevin', 'daisy'];
}

function Child () {
Parent.call(this);
this.getName = function () {
console.log(this.name)
}
}
var child1 = new Child();

//组合继承
//缺点:会调用两次父构造函数。子实例里面会有继承来的属性和方法 原型对象prototype里面也会有属性和方法,会重复
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');

//寄生组合式继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
/*
function createObj(o){
function F(){}
F.prototype = o
return new F()
}
function extend(child, parent){
var proto = createObj(parent.prototype)
proto.constructor = parent
child.prototype = proto
}
*/
或者简单点
function F(){}
F.prototype = Parent.prototype
Child.prototype = new F();

变量提升

全局上下文的变量对象初始化是全局对象
函数上下文的变量对象初始化只包括 Arguments 对象
在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
在代码执行阶段,会再次修改变量对象的属性值

js 校验数据类型

校验数据类型

bind 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myBind = function(context){
//利用函数闭包 记录传过来的参数 不包含上下文 以及执行对象
var args = Array.prototype.slice.call(arguments, 1),
that = this,
fEBind = function(){},
fBind = function(){
//获取传参并和第一次传参做合并
var bindAgrs = Array.prototype.slice.call(arguments, 1);
//如果是作为普通函数 this 指向 window
return that.apply( this instance fBind ? this : context, args.concat(bindArgs))
};
//实例就可以继承绑定函数的原型中的值
fEBind.prototype = this.prototype;
fBind.prototype = new fEbind();
return fBind;
}

函数柯里化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//就是将调用一个有多个入参的函数改造为调用多个只有一个入参的函数
function curry(fn, args) {
var length = fn.length; //3

args = args || []; //[]

return function() {

var _args = args.slice(0), // []

arg, i;

for (i = 0; i < arguments.length; i++) { //arguments = [a,b,c]

arg = arguments[i];

_args.push(arg);

}
if (_args.length < length) { //_args = [a,b,c]
return curry.call(this, fn, _args);
}
else {
return fn.apply(this, _args);
}
}

}

var fn = curry(function(a, b, c) {
console.log([a, b, c]);
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

V8 引擎的垃圾回收机制

1、V8 引擎采用分代式垃圾回收机制,分为新生代、老生代、大对象区等。大量新创建的对象都存放在新生代区,该区的垃圾回收较为频繁,新生代分为 from 和 to 空间,from 空间存放刚创建的变量对象
等,垃圾回收时,如果 from 空间的对象没有被引用,则直接回收,如果被引用,则复制到 to 空间。下次垃圾回收时,检查 to 空间的对象如果 to 空间的对象没有被引用,则直接回收,如果被引用,则复制
到 from 空间。如果对象经过一次 Scavenge 算法,且 To 空间的内存占比已经超过 25%,则将对象直接晋升到老生代区。老生代区管理着大量的存活对象,因此采用标记清除算法,早期的引用计数法无法解
决循环引用的问题导致内存溢出已被弃用。将老生代区活动对象标记并移至一端,然后清除剩下的对象。

2、避免内存溢出的方法:
2.1 减少全局变量的定义,因为标记清除法是以根节点也就是全局对象出发标记的
2.2 及时清除定时器、延时器等
2.3 少用闭包
2.4 清除对 dom 的引用

new 操作符的模拟实现

1
2
3
4
5
6
7
function myNew(){
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.**proto** = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeOf ret == 'object' ? ret : obj
}

事件循环机制

校验数据类型

项目总结

发表于 2021-10-15 | 分类于 项目总结 |
字数统计: 3,013 字 | 阅读时长 ≈ 14 分钟

手机端地图缩放和饿了么 tab 切换组件左右滑动切换会相互影响,因此引入 hammer.js 做手势控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 控制手势滑动切换距离
bindEvents() {
let that = this;
let hammerTest = new Hammer(document.querySelector("body"));
hammerTest.on("swipeleft swiperight", function (ev) {
if (ev.distance > 100) {
switch (ev.type) {
case "swipeleft":
that.active++;
break;
case "swiperight":
that.active--;
break;
default:
break;
}
}
});
},

echarts 中纵坐标有负值如何倒圆角

series-bar 的 data 项可以针对具体项做操作,所以这里的思路就是对原始数据遍历,对每个数据都做个性化处理,以此解决倒圆角的问题
可参考echarts-柱状图实现正负坐标倒圆角设置及 bar 颜色设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
series: [
{
type: "bar",
showBackground: true,
barWidth: 10,
// 循环数组,为一个bar配置单个属性,处理负值的倒角问题
data: arr.map((item) => {
return {
value: item,
label: {
normal: {
show: false,
position: "outside",
fontSize: 12,
},
},
itemStyle: {
normal: {
barBorderRadius: item > 0 ? [6, 6, 0, 0] : [0, 0, 6, 6],
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 0, color: _this[_this.type].x1_1 },
{ offset: 1, color: _this[_this.type].x1_2 },
]),
},
},
};
}),
},
]

模拟支付宝查看基金可左滑查看更多项,也可以下滑

整体布局分为左右两块,左侧和右侧均分为上下,左侧和右侧上面绝对定位固定,布局代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<div class="jj-table-wrap">
<div class="jj-table-mask" v-if="scrollLeft > 0"></div>
<div class="jj-table-left">
<div class="jj-table-left-top">产品名称</div>
<ul class="jj-table-left-bottom">
<li>
...
</li>
...
</ul>
</div>
<div class="jj-table-right">
<ul class="jj-table-right-top">
<li style="width: 120px">数据更新日期</li>
<li style="width: 120px">产品成立日期</li>
</ul>
<ul class="jj-table-right-bottom">
<li
>
<div class="cell" style="width: 120px">
...
</div>
<div class="cell" style="width: 120px">
...
</div>
</li>
</ul>
</div>
</div>

.jj-table-wrap {
height: calc(100vh - 66px - 46px);
font-size: 12px;
overflow-x: hidden;
overflow-y: auto;
background: #fff;
.jj-table-mask {
position: absolute;
width: 190px;
height: 100%;
box-shadow: 0 3px 10px rgb(0 0 0 / 12%);
z-index: 99;
}
.jj-table-left {
float: left;
width: 190px;

.jj-table-left-top {
width: 190px;
position: absolute;
height: 42px;
color: #666666;
padding-left: 14px;
line-height: 42px;
background: #fff;
border-bottom: 1px solid #ebedf0;
z-index: 20;
box-sizing: border-box;
}
.jj-table-left-bottom {
margin-top: 42px;

}
}
.jj-table-right {
float: left;
width: calc(100vw - 190px);
overflow-x: auto;
overflow-y: hidden;
.jj-table-right-top {
width: calc(100vw - 190px);
position: absolute;
display: flex;
background: #fff;
border-bottom: 1px solid #ebedf0;
height: 42px;
overflow-y: auto;
box-sizing: border-box;
li {
flex: 0 0 auto;
float: left;
color: #666666;
padding-left: 14px;
line-height: 42px;
width: 100px;
box-sizing: border-box;
}
}
.jj-table-right-bottom {
width: 900px;
margin-top: 42px;
li {
height: 67px;
display: flex;
.cell {
flex: 0 0 auto;
font-size: 14px;
color: #242424;
font-weight: 600;
padding: 14px 0 10px 14px;
box-sizing: border-box;
width: 100px;
background: #fff;
border-bottom: 1px solid #eeeeee;
}
}
}
}
}

关键点在于向左侧滑动的时候,怎么联动右侧上面的标题,思路是监听右侧下面滑动事件,在右侧下面滑动的时候,手动让右侧上方的的标题栏同步滑动,代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
//横向滚动监听
let rowScrollDom = document.querySelector(".jj-table-right"),
targetScroolXDom = document.querySelector(".jj-table-right-top");

rowScrollDom.addEventListener("scroll", (e) => {
this.scrollLeft = e.srcElement.scrollLeft;
targetScroolXDom.scrollTo(e.srcElement.scrollLeft, 0);
});

targetScroolXDom.addEventListener("scroll", (e) => {
rowScrollDom.scrollTo(e.srcElement.scrollLeft, 0);
});

搜索历史参照京东交互

获取当前搜索历史列表下的所有历史节点,遍历循环获取其距离左侧视口的距离,从而判断属于第几行,然后进行截断操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//历史记录 页面超出三行截取数据
let count = 0;
setTimeout(() => {
let ulChid = document.querySelector(".history-list").childNodes; //获取父容器所有子节点
ulChid.forEach((i, index) => {
console.log(i + ":", i.offsetLeft);
if (i.offsetLeft === 13) {
count++;
if (count === 4) { //超过三行
this.idx = index;
this.hasMoreBtn = true;
}
}
});
// 通过超出三行元素的下标去截断
if (this.idx > 0) {
this.historySearchShow = this.historySearch.slice(0, this.idx);
} else {
this.historySearchShow = this.historySearch;
}
});

vue 后台管理项目用户动态菜单权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//利用路由守卫,在每个路由进入之前校验登录token是否有效,如果有效,表示登录状态有效,此时如果要去的路由是登录,则直接到首页home
if (hasToken) {
if (to.path === '/login') {
next({
path: '/home'
})
} else {
//登录了 则需要校验当前用户有没有对应的路由信息,有的话则直接跳转
if(personRoutes){
next()
}else{
//登录了但是没有对应路由信息,则获取对应路由信息
const routes = await getRouterInfo();
const accessRoutes = await generateRoutes();
//添加路由信息
router.addRoutes(accessRoutes);
next({
...to,
replace: true
})
}
}

}else{

//如果没有登录,则检查要去的路由是不是在白名单内,也就是无需校验的,在白名单内则直接访问,否则去到登录页
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
}

}

超过三级嵌套路由,keep-alive 无法缓存

1、把二级的路由 name 和三级路由 name 一块 放到 include 内。
举个例子本来是缓存[menu,menu3],要把二级路由也加上,也就是[menu,menu2,menu3]

2、偷懒一点的方法,去在路由守卫里面,每次跳转的时候根据一些规则把 to.match 里面二级路由去除掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function filterRoutes(routesArr) {
if (routesArr && routesArr.length) {
for (let i = 0; i < routesArr.length; i++) {
const element = routesArr[i]
if (element.name && element.name.indexOf('blank') != -1) {
routesArr.splice(i, 1)
filterRoutes(routesArr)
break;
}
}
}
}
filterRoutes(to.matched)
next()

3、这个方法比较靠谱,大致思路就是把多级嵌套路由拍平成二级嵌套路由,参考无限级路由缓存方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
[
{
path: '/menu1',
component: Layout,
hidden: true,
children: [{
path: 'menu1-1',
component: () => import('@/views/menu1-1'),
name: 'menu1-1',
children: [
{
path: 'menu1-1-1',
component: () => import('@/views/menu1-1-1'),
name: 'menu1-1-1',
meta: {
title: '项目详情1',
icon: '',
affix: false,
tooltip: true
}
},
{
path: 'menu1-1-2',
component: () => import('@/views/menu1-1-2'),
name: 'menu1-1-2',
meta: {
title: '项目详情2',
icon: '',
affix: false,
tooltip: true
}
},
]
}]
},
]

三级嵌套路由拍平成二级,变成这样
[
{
path: '/menu1',
component: Layout,
hidden: true,
children: [
{
path: 'menu1-1/menu1-1-1',
component: () => import('@/views/1-1-1'),
name: 'menu1-1-1',
},
{
path: 'menu1-1/menu1-1-2',
component: () => import('@/views/1-1-2'),
name: 'menu1-1-2',
},
]
},
]

//拍平代码如下

/**
* 生成扁平化机构路由(仅两级结构)
* @param {允许访问的路由Tree} accessRoutes
* 路由基本机构:
* {
* name: String,
* path: String,
* component: Component,
* redirect: String,
* children: [
* ]
* }
*/
function generateFlatRoutes(accessRoutes) {
const flatRoutes = []

for (const item of accessRoutes) {
let childrenFflatRoutes = []
if (item.children && item.children.length > 0) {
childrenFflatRoutes = castToFlatRoute(item.children, '')
}

// 一级路由是布局路由,需要处理的只是其子路由数据
flatRoutes.push({
name: item.name,
path: item.path,
component: item.component,
redirect: item.redirect,
children: childrenFflatRoutes
})
}

return flatRoutes
}
/**
* 将子路由转换为扁平化路由数组(仅一级)
* @param {待转换的子路由数组} routes
* @param {父级路由路径} parentPath
*/
function castToFlatRoute(routes, parentPath, flatRoutes = []) {
for (const item of routes) {
if (item.children && item.children.length > 0) {
if (item.redirect && item.redirect !== 'noRedirect') {
flatRoutes.push({
name: item.name,
path: (parentPath + '/' + item.path).substring(1),
redirect: item.redirect,
meta: item.meta
})
}
castToFlatRoute(item.children, parentPath + '/' + item.path, flatRoutes)
} else {
flatRoutes.push({
name: item.name,
path: (parentPath + '/' + item.path).substring(1),
component: item.component,
meta: item.meta
})
}
}

return flatRoutes
}

项目文件上传功能

避免产生废文档 需要先将文件对象缓存在数据中,然后在整个表单提交的时候一起提交

当elementUI 下拉框组件下拉项过多时会引起页面卡顿

思路是利用 filterable 属性和 filter-method=”handlerSelectFilter”,一开始只渲染50条,当用户搜索的时候再从全部选项里面去找,回显的时候需要注意一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
handlerSelectFilter(query = "") {
let oriOpts = this.options;

let arr = oriOpts.filter((item) => {
return item.label.includes(query) || item.value.includes(query);
});

if (arr.length > 50) {
this.dictOpts = arr.slice(0, 50);
} else {
this.dictOpts = arr;
}
},

回显的时候特殊处理下,找到选中项的条目加入到现有的选项中,区分下是多选还是单选

addValueOptions() {
let oriVal = this.value;
let oriOpts =this.options;

if (_.isArray(oriVal)) {
let choosedArr = _.filter(oriOpts, (item) => {
// 从大option中找到当前条
return _.includes(oriVal, item.value);
});
this.dictOpts = _.uniqBy(choosedArr.concat(this.dictOpts), item => item.value).slice(0,168);
} else {
let target = oriOpts.find((item) => {
// 从大option中找到当前条
return item.value === oriVal;
});
if (target) {
// 将当前条与小option比对,没有则加入
if (this.dictOpts.every((item) => item.value !== target.value)) {
this.dictOpts.unshift(target);
}
}
}
},

nvm 安装 node 指定版本报错

Node.js vnode.0.0 is only available in 32-bit.

这时以管理员身份运行 cmd,然后重新 nvm install 指定版本即可

vue 隔代传递数据如何响应式

当祖先组件使用 provide 提供数据源,子孙组件通 inject 获取数据,但是获取到的数据不是响应式的,也就是说当祖先组件数据源变更的时候,子孙组件获取到的数据不会变化,还是之前的值,为了实现动态响应,可参考 react 传递函数的思路,向子孙组件提供方法而不是直接提供数据,子组件通过 coputed 属性获取数据,还可以用 watch 属性观测,授之于鱼不如授之于渔,具体代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//父组件
provide() {
return {
getNodeData: () => this.nodeData,
};
},

//子孙组件
inject:['getNodeData'],
computed:{
nodeData(){
return this.getNodeData();
}
},
watch:{
nodeData(v){
//操作
...
}
}

upload组件form表单校验问题

点击提交检验时提示要上传,当上传完毕以后却没有清除表单校验信息,无论你的trigger配置的是blur还是change,都不会生效,这个时候需要upload组件上传成功回调里面,找到upload组件父组件form-item那一项,通过this.$parent或其他方式找到form-item组件实例,执行clearValidate(),实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 ...
<el-form-item prop='file'>
<el-upload
...
:on-success="successUpload"
>
...
</el-upload>
</el-form-item>
...

//js
successUpload(){
...
this.$parent.clearValidate();
}

注:也可通过validateField针对当前上传组件字段重新校验即可。

hexo 博客相关问题和坑

1、hexo deploy 失败

1
2
3
4
//报错信息如下
TypeError [ERR_INVALID_ARG_TYPE]: The "mode" argument must be integer.`

出现该问题的原因是node版本过高,切换版本以后重新安装hexo,再次生成和推送即可

2、克隆 hexo 博客项目以后,npm isntall 报错,大致意思就是某个插件下载不下来,就是这个hexo-asset-image,这个插件是展示图片用的,在 npm install 以前先 npm install hexo-asset-image –save,否则无法安装成功。

1
2
3
4
5
git clone git@github.com:ncumovi/blog-by-hexo.git
npm install hexo -g --save
npm install hexo-asset-image --save
npm install
hexo server

3、常用 hexo 命令

hexo new"postName" #新建文章

hexo new page"pageName" #新建页面

hexo generate #生成静态页面至public目录

hexo server #开启预览访问端口 本地服务

hexo deploy #将.deploy目录部署到GitHub

hexo help # 查看帮助

hexo version #查看Hexo的版本

3、hexo 本地图片无法显示

1
2
3
4
//是因为路径写的是相对路径,要改成绝对路径,之前是这样的
![校验数据类型](/img/front-engineer/typeOf.png)
要改成绝对路径
![校验数据类型](img/front-engineer/typeOf.png)

echarts-chinese-map-vue

发表于 2021-10-15 | 分类于 前端 |
字数统计: 3,704 字 | 阅读时长 ≈ 19 分钟

Echarts3 中国地图下钻至县级

涉及到省市县三级地图,点击可下钻,当前 vue 版本是参照 Echarts3 中国地图下钻至县级

首先要有全国、省市县的地图 json 文件
地图json文件

初始化

初始化 echarts 对象,获取地图 json 文件,注册地图,然后开始绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
initMap() {
//地图容器
let that = this;
that.chart = echarts.init(document.getElementById("chinaMap"), null, {
renderer: "svg",
});

this.bindEchartsEvent();
that.chart.showLoading();
//获取中国地图josn文件
that.axios.get("/map/china.json").then(function (res) {
that.chart.hideLoading();
that.chinaMapdata = res.data;

//注册地图
echarts.registerMap(that.mapName, that.chinaMapdata);
//绘制地图,样式,第一次渲染
that.renderMap(that.mapName, that.chinaMapdata);
});
},

下钻

绑定地图点击事件,执行下钻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
bindEchartsEvent() {
let that = this

//地图单击事件
that.chart.on("click", function (params) {
that.showMenuPop = true;
that.menuLeft =
params.event.offsetX + 150 > window.innerWidth
? params.event.offsetX - 170
: params.event.offsetX;
that.menuTop = params.event.offsetY - 50;
that.clickedMap = params;
that.showGoinBtn = true;
if (params.seriesName != "中国" || !params.value) {
// 有省id,市id才有下一级
that.showGoinBtn = false;
console.error("该地图没有下一级地区了");
return;
}

/**
* 绑定数据入栈事件
*/
let n = 1;
if (that.special.indexOf(params.seriesName) == -1) {
n = 2;
}
// FiXED: 2级下钻会有问题, 函数顶部加入下钻层级判断
if (that.mapStack.length < n) {
//将上一级地图信息压入that.mapStack
that.mapStack.push({
mapName: that.curMap.mapName,
mapJson: that.curMap.mapJson,
colorMax: that.curMap.colorMax,
sortData: that.curMap.sortData,
titledata: that.curMap.titledata,
});
console.log("数据入栈", that.mapStack);
}
});
},

下钻到省地图

获取省地图,然后注册,然后绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//获取省地图map.json
getSecondMap(params) {
let that = this;
that.axios
.get("/map/province/" + that.provinces[params.name] + ".json")
.then(function ({ data }) {
that.provinceMapData = data;
echarts.registerMap(params.name, data);
that.renderMap(params.name, that.provinceMapData);

if (params.data.id !== "undifiend") {
that.getCityNumber(params.name, params.data.id, data);
}
});
},

地图的渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
renderMap(mapTitle, mapJson, customerNum, colorMax = 1500) {
//地图配置参数,参数按顺序渲染
let option = {
backgroundColor: "#fff", //地图画布背景颜色 "#F7EED6"米黄色 "#efefef"灰色
title: {
//地图文本
// text: mapTitle,
// subtext: "右键返回上一级",
left: "center",
textStyle: {
color: "#000",
fontSize: 18,
fontWeight: "normal",
fontFamily: "Microsoft YaHei",
},
subtextStyle: {
color: "rgb(55, 75, 113)",
fontSize: 18,
fontWeight: "normal",
fontFamily: "Microsoft YaHei",
},
},

tooltip: {
//提示框信息
show: false,
trigger: "item",
formatter: "{b}\n{c}人",
},
toolbox: {
//工具box
show: true,
orient: "vertical",
left: "right",
top: "center",
right: 20,
feature: {
dataView: { readOnly: false, show: false },
restore: {
title: "",
},
saveAsImage: {
show: false,
},
dataZoom: {
show: false,
type: "inside",
preventDefaultMouseMove: false,
},
},
iconStyle: {
normal: {
color: "#fff",
},
},
},
// 左下角的颜色条
visualMap: {
show: false,
type: "piecewise",
min: 0,
max: colorMax,
left: "left",
top: "bottom",
text: ["高", "低"], // 文本,默认为数值文本
splitNumber: 6,
calculable: true,
pieces: [
// 自定义每一段的范围,以及每一段的文字
{ gte: 200, color: "rgb(205, 25, 47)" }, // 不指定 max,表示 max 为无限大(Infinity)。
{ gte: 100, lte: 199, color: "rgb(219, 76, 94)" },
{ gte: 30, lte: 99, color: "rgb(231, 134, 146)" },
{ gte: 10, lte: 29, color: "rgb(243, 192, 198)" },
{ gte: 1, lte: 9, color: "rgb(244, 198, 203)" },
{ lte: 0, color: "rgb(204, 204, 204)" }, // 不指定 min,表示 min 为无限大(-Infinity)。
],
dimension: 0,
},
grid: {
show: false,
left: -1300,
top: 100,
botton: 40,
width: "10%",
z: 0,
},
xAxis: [
{
position: "top",
type: "value",
boundaryGap: false,
splitLine: {
show: true,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
color: "#ff461f",
},
},
],
yAxis: [
{
type: "category",
data: this.titledata,
triggerEvent: false,
axisTick: {
alignWithLabel: false,
},
axisLine: {
show: false,
lineStyle: {
show: false,
color: "#2ec7c9",
},
},
},
],
series: [
{
name: mapTitle, //上面的下钻用到seriesName绑定下一级,换name绑定
type: "map",
map: mapTitle,
roam: "scale", //控制缩放是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成 'scale' 或者 'move'。设置成 true 为都开启
height: "100%",
zoom: 0.75,
z: 1,

scaleLimit: {
min: 0.5,
max: 3,
},
label: {
//地图上的文本标签
normal: {
show: true,
position: "inside", //文本标签显示的位置
textStyle: {
color: "#fff", //文本颜色
fontSize: 10,
},
formatter: "", //文本上显示的值 data:[{name: "地名", value: 数据}], {b}表示label信息,{c}代表value
},
emphasis: {
show: true,
position: "inside",
textStyle: {
color: "#fff",
fontSize: 10,
},
},
},
itemStyle: {
normal: {
areaColor: "#5ab1ef", //地图块颜色#DCE2F1 浅蓝#2B91B7
borderColor: "#EBEBE4", //#EBEBE4灰色
},
emphasis: {
areaColor: "rgb(254,153,78)", //s鼠标放上去,地图块高亮显示的颜色
},
},
data: customerNum,
},
{
name: mapTitle,
type: "bar",
z: 4,
label: {
normal: {
show: true,
},
empahsis: {
show: true,
},
},
itemStyle: {
emphasis: {
color: "rgb(254,153,78)",
},
},
data: customerNum,
},
],
// 初始动画的时长,支持回调函数,可以通过每个数据返回不同的 delay 时间实现更戏剧的初始动画效果:
animationDuration: 500,
animationEasing: "cubicOut",
// 数据更新动画的时长。
animationDurationUpdate: 500,
};

//渲染地图
this.chart.setOption(option);
//保存当前状态数据,用于入栈出栈
this.curMap = {
mapName: mapTitle,
mapJson: mapJson,
colorMax: colorMax,
sortData: [],
titledata: [],
};
this.restoreMap();
},

遇到的坑

1、roam: “scale” 这个属性一定要打开,否则无法缩放

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
<template>
<section class="china-map-wrap">
<div id="chinaMap" :style="chinaMapStyle"></div>
<div class="btn-group">
<span @click="renderProvince">中国</span>

<template v-if="curMap && curMap.mapName != '中国'">
<span>{{ curMap.mapName }}</span>
</template>
</div>
<div
v-if="showMenuPop"
class="map-popup"
:style="{ left: menuLeft + 'px', top: menuTop + 'px' }"
>
<section class="map-popup-top">
<p>
{{ clickedMap.name
}}<van-icon name="clear" @click="showMenuPop = false" />
</p>
<p>数量:{{ clickedMap.data.value }}</p>
</section>
<p class="map-popup-bottom-btn" v-if="showGoinBtn" @click="goInCity">
钻取
</p>
</div>
</section>
</template>
<script>
import * as echarts from "echarts/lib/echarts";

export default {
data() {
return {
chart: null,
curMap: null,
mapName: "中国",
chinaMapdata: [],
provinceMapData: [],
showMenuPop: false,
menuLeft: 0,
menuTop: 0,
clickedMap: null,
showGoinBtn: true,
chinaMapStyle: {
width: "100%",
height: "48vh",
},
//34个省、市、自治区的名字拼音映射数组
provinces: {
//23个省
台湾: "taiwan",
河北: "hebei",
山西: "shanxi",
辽宁: "liaoning",
吉林: "jilin",
黑龙江: "heilongjiang",
江苏: "jiangsu",
浙江: "zhejiang",
安徽: "anhui",
福建: "fujian",
江西: "jiangxi",
山东: "shandong",
河南: "henan",
湖北: "hubei",
湖南: "hunan",
广东: "guangdong",
海南: "hainan",
四川: "sichuan",
贵州: "guizhou",
云南: "yunnan",
陕西: "shanxi1",
甘肃: "gansu",
青海: "qinghai",
//5个自治区
新疆: "xinjiang",
广西: "guangxi",
内蒙古: "neimenggu",
宁夏: "ningxia",
西藏: "xizang",
//4个直辖市
北京: "beijing",
天津: "tianjin",
上海: "shanghai",
重庆: "chongqing",
//2个特别行政区
香港: "xianggang",
澳门: "aomen",
},
//直辖市和特别行政区-只有二级地图,没有三级地图
special: ["北京", "天津", "上海", "重庆", "香港", "澳门"],
timeout: null,
//未知变量
titledata: [],
sortData: [],
areaList: [],
mapStack: [],
};
},
props: ["numberData", "countByAreaQuery"],
watch: {
showMenuPop(v) {
if (v) {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.showMenuPop = false;
}, 2000);
}
},
numberData(number) {
if (this.curMap.mapName == "中国") {
this.chinaMapdata &&
this.renderPrimaryMap(
this.mapName,
this.numberData,
this.chinaMapdata
);
} else {
this.renderPrimaryMap(
this.curMap.mapName,
number,
this.provinceMapData
);
}
},
},
mounted() {
this.initMap();
},
activated() {
this.restoreMap();
},
methods: {
initMap() {
//地图容器
let that = this;
that.chart = echarts.init(document.getElementById("chinaMap"), null, {
renderer: "svg",
});

this.bindEchartsEvent();
that.chart.showLoading();
//获取中国地图josn文件
that.axios.get("/map/china.json").then(function (res) {
that.chart.hideLoading();
that.chinaMapdata = res.data;

//注册地图
echarts.registerMap(that.mapName, that.chinaMapdata);
//绘制地图,样式,第一次渲染
that.renderMap(that.mapName, that.chinaMapdata);
});
},
// //获取省份数据包括数量等级等 //获取省份数据(数量等级) 渲染全局中国地图
renderProvince() {
//需要更新表格城市数据为省份数据
this.getProvinceNumber();
},
getProvinceNumber() {
let that = this;
return countByArea(
Object.assign({}, that.countByAreaQuery, {
area: "",
})
)
.then(function ({ ret_data }) {
that.renderPrimaryMap(that.mapName, ret_data, that.chinaMapdata);
that.$emit("updateTableData", ret_data, "");
})
.catch(function (err) {
console.error("请求全国省级数据失败", err);
});
},
//获取省地图map.json
getSecondMap(params) {
let that = this;
that.axios
.get("/map/province/" + that.provinces[params.name] + ".json")
.then(function ({ data }) {
that.provinceMapData = data;
echarts.registerMap(params.name, data);
that.renderMap(params.name, that.provinceMapData);

if (params.data.id !== "undifiend") {
that.getCityNumber(params.name, params.data.id, data);
}
});
},
/**
* 获取城市数据 数量级
* @param {省份} name
* @param {省份id} id
* @param {地图json数据} data
*/
getCityNumber(name, id, provinceMapData) {
if (!id) {
return;
}
let that = this;

return countByArea(
Object.assign({}, that.countByAreaQuery, {
area: id,
})
)
.then(function ({ ret_data }) {
that.renderPrimaryMap(name, ret_data, provinceMapData);
that.$emit("updateTableData", ret_data, id);
})
.catch(function (err) {
that.renderPrimaryMap(name, provinceMapData, provinceMapData);
console.error("请求市级数据失败", err);
});
},
//钻取
goInCity() {
this.showMenuPop = false;
this.getSecondMap(this.clickedMap);
},
bindEchartsEvent() {
let that = this

//地图单击事件
that.chart.on("click", function (params) {
that.showMenuPop = true;
that.menuLeft =
params.event.offsetX + 150 > window.innerWidth
? params.event.offsetX - 170
: params.event.offsetX;
that.menuTop = params.event.offsetY - 50;
that.clickedMap = params;
that.showGoinBtn = true;
if (params.seriesName != "中国" || !params.value) {
// 有省id,市id才有下一级
that.showGoinBtn = false;
console.error("该地图没有下一级地区了");
return;
}

/**
* 绑定数据入栈事件
*/
let n = 1;
if (that.special.indexOf(params.seriesName) == -1) {
n = 2;
}
// FiXED: 2级下钻会有问题, 函数顶部加入下钻层级判断
if (that.mapStack.length < n) {
//将上一级地图信息压入that.mapStack
that.mapStack.push({
mapName: that.curMap.mapName,
mapJson: that.curMap.mapJson,
colorMax: that.curMap.colorMax,
sortData: that.curMap.sortData,
titledata: that.curMap.titledata,
});
console.log("数据入栈", that.mapStack);
}
});
},
/**
*
* @param {地图标题} mapTitle
* @param {客户数} customerNum
* @param {地图json数据} mapJson
* @param {最大颜色值} colorMax
*/
renderMap(mapTitle, mapJson, customerNum, colorMax = 1500) {
//地图配置参数,参数按顺序渲染
let option = {
backgroundColor: "#fff", //地图画布背景颜色 "#F7EED6"米黄色 "#efefef"灰色
title: {
//地图文本
// text: mapTitle,
// subtext: "右键返回上一级",
left: "center",
textStyle: {
color: "#000",
fontSize: 18,
fontWeight: "normal",
fontFamily: "Microsoft YaHei",
},
subtextStyle: {
color: "rgb(55, 75, 113)",
fontSize: 18,
fontWeight: "normal",
fontFamily: "Microsoft YaHei",
},
},

tooltip: {
//提示框信息
show: false,
trigger: "item",
formatter: "{b}\n{c}人",
},
toolbox: {
//工具box
show: true,
orient: "vertical",
left: "right",
top: "center",
right: 20,
feature: {
dataView: { readOnly: false, show: false },
restore: {
title: "",
},
saveAsImage: {
show: false,
},
dataZoom: {
show: false,
type: "inside",
preventDefaultMouseMove: false,
},
},
iconStyle: {
normal: {
color: "#fff",
},
},
},
// 左下角的颜色条
visualMap: {
show: false,
type: "piecewise",
min: 0,
max: colorMax,
left: "left",
top: "bottom",
text: ["高", "低"], // 文本,默认为数值文本
splitNumber: 6,
calculable: true,
pieces: [
// 自定义每一段的范围,以及每一段的文字
{ gte: 200, color: "rgb(205, 25, 47)" }, // 不指定 max,表示 max 为无限大(Infinity)。
{ gte: 100, lte: 199, color: "rgb(219, 76, 94)" },
{ gte: 30, lte: 99, color: "rgb(231, 134, 146)" },
{ gte: 10, lte: 29, color: "rgb(243, 192, 198)" },
{ gte: 1, lte: 9, color: "rgb(244, 198, 203)" },
{ lte: 0, color: "rgb(204, 204, 204)" }, // 不指定 min,表示 min 为无限大(-Infinity)。
],
dimension: 0,
},
grid: {
show: false,
left: -1300,
top: 100,
botton: 40,
width: "10%",
z: 0,
},
xAxis: [
{
position: "top",
type: "value",
boundaryGap: false,
splitLine: {
show: true,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
color: "#ff461f",
},
},
],
yAxis: [
{
type: "category",
data: this.titledata,
triggerEvent: false,
axisTick: {
alignWithLabel: false,
},
axisLine: {
show: false,
lineStyle: {
show: false,
color: "#2ec7c9",
},
},
},
],
series: [
{
name: mapTitle, //上面的下钻用到seriesName绑定下一级,换name绑定
type: "map",
map: mapTitle,
roam: "scale", //控制缩放是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成 'scale' 或者 'move'。设置成 true 为都开启
height: "100%",
zoom: 0.75,
z: 1,

scaleLimit: {
min: 0.5,
max: 3,
},
label: {
//地图上的文本标签
normal: {
show: true,
position: "inside", //文本标签显示的位置
textStyle: {
color: "#fff", //文本颜色
fontSize: 10,
},
formatter: "", //文本上显示的值 data:[{name: "地名", value: 数据}], {b}表示label信息,{c}代表value
},
emphasis: {
show: true,
position: "inside",
textStyle: {
color: "#fff",
fontSize: 10,
},
},
},
itemStyle: {
normal: {
areaColor: "#5ab1ef", //地图块颜色#DCE2F1 浅蓝#2B91B7
borderColor: "#EBEBE4", //#EBEBE4灰色
},
emphasis: {
areaColor: "rgb(254,153,78)", //s鼠标放上去,地图块高亮显示的颜色
},
},
data: customerNum,
},
{
name: mapTitle,
type: "bar",
z: 4,
label: {
normal: {
show: true,
},
empahsis: {
show: true,
},
},
itemStyle: {
emphasis: {
color: "rgb(254,153,78)",
},
},
data: customerNum,
},
],
// 初始动画的时长,支持回调函数,可以通过每个数据返回不同的 delay 时间实现更戏剧的初始动画效果:
animationDuration: 500,
animationEasing: "cubicOut",
// 数据更新动画的时长。
animationDurationUpdate: 500,
};

//渲染地图
this.chart.setOption(option);
//保存当前状态数据,用于入栈出栈
this.curMap = {
mapName: mapTitle,
mapJson: mapJson,
colorMax: colorMax,
sortData: [],
titledata: [],
};
this.restoreMap();
},
//还原地图
restoreMap() {
this.chart.dispatchAction({
type: "restore",
});
},
/**
* @description: 渲染一级地图,省
* @param {Array} result=后台返回的地区关联数据
* @param {Boolean} flag=不用后台数据渲染
* @return:
*/
renderPrimaryMap(mapName, result, originMapJsonData) {
let tmp = [],
that = this,
mapJsonData = []; // 组装临时数据,用于地图上 label和value的渲染
for (var i = 0; i < originMapJsonData.features.length; i++) {
mapJsonData.push({
id: originMapJsonData.features[i].id,
name: originMapJsonData.features[i].properties.name,
});
}

let proviceNumber = that.stringToJson(result);

/**通过id关联地图上对应位置的数据 */
mapJsonData.forEach(function (val) {
let count = 0;
proviceNumber.forEach(function (val2, index) {
if (val.id == val2.code) {
count = val2.count;
}
});
tmp.push({
id: val.id,
name: val.name,
value: count,
});
});

//获取最大值,并排序
let maxData = this.getMaxDataAndSort(tmp);
//绘制地图,拿到数据后再渲染一次
this.renderMap(mapName, originMapJsonData, tmp, maxData.maxData);
// getRegionPreMonthRatio(maxData.maxDataId, searchtime);
},

/**
*
* @param {未排序数据} originData
* @return {倒序排序的数据} maxData
*/
getMaxDataAndSort(originData) {
if (originData == "undefined") {
return;
}
this.titledata = [];
this.sortData = [];
this.sortData = originData.sort(function (a, b) {
return a["value"] - b["value"];
});
let maxData = this.sortData.slice(-1)[0]["value"];
let maxDataId = this.sortData.slice(-1)[0]["id"];
if (!maxDataId) {
maxDataId = this.sortData.slice(-1)[0]["cityid"]
? this.sortData.slice(-1)[0]["cityid"]
: this.sortData.slice(-1)[0]["areaid"];
}
for (let i = 0; i < this.sortData.length; i++) {
this.titledata.push(this.sortData[i].name);
}
this.areaList = [...this.sortData].reverse();
return {
maxDataId,
maxData,
};
},
stringToJson(data) {
let result = _.cloneDeep(data);
if (!_.isObject(result)) {
try {
result = JSON.parse(result);
} catch {
throw TypeError("数据转换成JSON出错");
}
}
return result;
},
},
};
</script>

jQuery核心功能函数之callback

发表于 2019-11-17 | 分类于 源码分析 |
字数统计: 786 字 | 阅读时长 ≈ 3 分钟

jQuery内部核心函数callback

$.callback可以通过add添加回调函数,fire执行回调函数。初始化创建callback实例的时候,可以传递三个参数的组合,stopOnfalse表示如果其中一个回调函数的返回值是false,则后续的回调函数不再执行;once表示只执行一次,即add进去的回调函数只会在第一次fire的时候会执行,如果后面再继续调用fire,回调函数不会再执行;memory表示再调用fire函数以后,后面再继续调用add添加函数的时候,该回调函数会立即执行,且之前执行过的回调不会再被执行,以下是代码的完整简易实现,要点以及个人理解都在代码注释里面。

var cb = $.callbacks('stopOnfalse once memory');

cb.add(function(){
    console.log(1)
    return false
}, function(){
    console.log(2)
});

cb.add(function(){
    console.log(5)
});

cb.fire();

cb.add(function(){
    console.log('3')
})

callback之once

var cb = $.callbacks('once');

cb.add(function(){
    console.log(1)
});

cb.fire(); //输出1
cb.fire(); //没有输出

callback之stopOnfalse

var cb = $.callbacks('once');

cb.add(function(){
    console.log(1)
    return false
}, function(){
    console.log(2)
});

cb.fire(); //输出1 

callback之memory

var cb = $.callbacks('stopOnfalse once memory');

cb.add(function(){
    console.log(1)
});

cb.add(function(){
    console.log(5)
});

cb.fire(); //输出1和5

//会立即执行
cb.add(function(){
    console.log(3)
})

以上会分别输出1,5,3

callback完整代码的简易实现

...
callbacks: function(option){
    //回调函数列表 
    var list = [],
    //执行回调函数的下标 默认从第一个开始
        index,
    //手动设置执行回调函数列表的下标    
        start,
    //回调函数列表的长度
        listLength,
    //回调函数是否执行过
        hasRun;
    //当前回调队列策略 参数 once stopOnfalse memory
    option = typeof option === 'string' ? (optionCach[option] || createOptionCach(option)) : {};
    //当传进来的参数、模式不在缓存对象里面时候 需要重新创建
    function createOptionCach(option){
        //将空对象、缓存对象的对象引用给obj 并返回
        var obj = optionCach[option] = {};
        option.split(/\s+/).forEach(function(opt){
            obj[opt] = true;
        });
        return obj;
    }
    //循环执行回调函数列表里面的回调函数
    var fire = function(data){
        index = start || 0;
        hasRun = true;
        for(index; index < list.length; index++){
            //如果回调函数的返回值是false 同时又配置了stopOnfalse的时候就跳出循环
            if(list[index].call(data[0], data[1]) == false && option.stopOnfalse){
                break;
            };
        }
    }
    var self = {
        add:function(){
            var args = Array.prototype.slice.call(arguments);
            //每次添加回调函数的时候 记录当前回调函数列表的长度
            listLength = list.length;
            //循环遍历args将add进来的回调函数全部放到事件列表list
            args.forEach(function(fn){
                //如果传进来的参数是函数对象 则添加到回调函数列表
                if(toString.call(fn) === '[object Function]'){
                    list.push(fn);
                }
            })
            //当配置的memory的同时回调函数还没执行过 则将下次执行回调函数的下标置为当前事件列表的长度 避免执行的时候重复执行
            if(option.memory && hasRun){
                start = listLength;
                self.fire(arguments);
            }

        },
        fireWith:function(context, arg){
            var args = [context, arg];
            //如果是配置了只执行一次 或者还没执行过才执行
            if(!option.once || !hasRun){
                fire(args)
            }

        },
        fire:function(){
            //把当前jQuery实例对象以及传递的参数通过fireWith传递
            self.fireWith(this, arguments);
        }
    }
    //这里返回self以实现链式调用
    return self;
}

jQuery核心功能函数揭秘

发表于 2019-11-01 | 分类于 源码分析 |
字数统计: 2,079 字 | 阅读时长 ≈ 9 分钟

jQuery无new化构建

我们平时使用jQuery的时候使用的$,然后就会返回一个jQuery的实例,如何做到在调用的时候不使用new也可以得到jQuery的实例呢

(function(global){

    //类型校验 防错处理
    if(typeof type!='string'){
        throw new Error('type must be a string');
    }

    var jQuery = function (){
        //返回jQuery原型对象上init对象的实例 不可直接返回new jQuery(),否则在创建自身实例的时候执行jQuery构造函数,重复执行陷入死循环
        return new jQuery.prototype.init();
    }

    //jQuery原型对象别名fn
    jQuery.fn = jQuery.prototype = {
        init: function(){

        },

        css:function(){

        }
    }

    //让jQuery和init共享原型 让$返回的对象享有jQuery原型上的所有方法
    jQuery.fn.init.prototype = jQuery.fn;


    global.$ =  global.jQuery = jQuery;

})(this)

共享原型设计:

共享原型设计的核心在于创建jQuery实例的时候,构造函数里面返回的是另一个和jQuery共享原型对象的init构造方法的实例

...
var jQuery = function (){
    //返回jQuery原型对象上init对象的实例 不可直接返回new jQuery(),否则在创建自身实例的时候执行jQuery构造函数,重复执行陷入死循环
    return jQuery.prototype.init();
}
...
//让jQuery和init共享原型 让$返回的实例对象享有jQuery原型上的所有方法
jQuery.prototype.init.prototype = jQuery.prototype;
...

核心函数extend

...
//extend
jQuery.fn.extend = jQuery.extend = function(){
    var target = arguments[0] || {};
    var length = arguments.length;
    var i = 1;
    var deep = false;
    var options, name, copy, src, copyIsArray, clone;

    //如果传的第一个值是boolean值 则说明是深拷贝 
    if(typeof target === 'boolean'){
        deep = target;
        target = arguments[1];
    }

    //扩展的第一个对象不是对象的时候 创建空对象复制给他
    if(typeof target != 'object'){
        target = {};
    }
    //参数的个数 如果只有一个则需要拿到当前调用对象 jQuery或者jQuery实例
    if(length === i){ //本身扩展
        target = this; //指向本身
        //把下标变成0,这样就可以在扩展自己本身的时候拿到需要扩展进去的对象   {name:'movi'}
        //$.extend({name:'movi'})
        i--;
    }

    //浅拷贝
    for(; i <length; i++){
        if((options = arguments[i]) != null){
            //options 代表要扩展的对象 {name:'max' ,list:{age:'20'}}    $.extend({}, {name:'max',list:{age:'20'}}); 
            for(name in options){
                copy = options[name]; // name:'max'  list:{age:'20'}
                src = target[name]; // undefined
                //如果是深拷贝 则需要判断当前要扩展的对象是数组或者是对象
                if(deep && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))){
                    if(copyIsArray){
                        //被扩展的源对象本身不是数组或者没有就创建空数组给他,否则就是自己本身
                        copyIsArray = false; //需要重置 保证每次循环都是最新值
                        clone = src && jQuery.isArray(src) ? src : [];
                    }else{
                         //被扩展的源对象本身不是对象或者没有 就创建空对象给他
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }
                    target[name] = jQuery.extend(deep, clone, copy)
                }else if(copy != undefined){ 
                    target[name] = copy;
                }

            }
        }
    }

    return target;
}
...

提取标签元素名 并创建dom

//用正则捕获标签内的元素名字 '<a>' => 'a'
var  rejectExp = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
...
//将标签解析为具体的元素名 然后创建该dom对象
parseHtml: function(selector, context){
    if(!selector || typeof selector != 'string'){
        return null
    }
    //过滤掉尖括号 获得元素名字 '<a>' => 'a'
    var parse = rejectExp.exec(selector); //[<p>, p]
    //创建dom
    return [context.createElement(parse[1])];
}

merge合并数组到对象

//将数组或者类数组对象和并到this即jQuery实例对象上
merge: function(first, second){
    var l = first.length;
    var j = second.length;
    var i = 0;
    //如果传进来的第二个参数具有length属性 则遍历second数组 将值赋给first对象 下班从first length++开始
    if(typeof j === 'number'){
        for(i; i < j; i++){
            first[l++] = second[i];
        }
    }else{
        //如果second是类数组 不具备length属性 则直接用while循环 将second每一项值赋给second
        while(second[i] != undefined){
            first[l++] = second[i++];
        }
    }
    //这里将自增以后的l值作为传进来first的length属性值
    first.length = l;
    return first;
},

jQuery初始化方法init 创建dom或者查询dom

init: function(selector, context){
    context = context || document;
    var match;
    //如果没有传selctor值 则返回当前jQuery实例对象
    if(!selector){
        return this;
    }
    if(typeof selector === 'string'){
        //如果传进来的是字符串 且是类似"<a>"这种带标签的 则表示创建dom节点
        if(selector.charAt(0) === '<' && selector.charAt(selector.length-1) === '>' && selector.length === 3){
            match = [selector];
        }
        if(match){
            //创建dom
            //将创建的dom对象数组 [dom] 与当前jQuery实例合并在一起
            jQuery.merge(this, jQuery.parseHtml(selector, context));
        }else{
            //查询dom 使用querySelectorAll api查询到所有的dom节点
            var ele = document.querySelectorAll(selector);
            //将查询到的dom类数组对象转换为数组对象 并遍历dom数组对象 将他们挂载到jQuery实例对象上
            var eles = Array.prototype.slice.call(ele);
            var len = eles.length;
            for(var i = 0; i < len; i++){
                this[i] = eles[i];
            }
            this.selector = selector;
            this.context = context;
            this.length = len;
        }
    }else if(selector.nodeType){
        //传过来的是对象且具有nodeType属性 则把当前对象作为当前实例对象的context
        this.context = this[0] = selector;
        this.length = 1;
    }
}

完整代码

(function(global){
    //正则捕获标签内的元素名字
    var  rejectExp = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;

    var jQuery = function (selector, context){
        //返回jQuery原型对象上init对象的实例
        return new jQuery.prototype.init(selector, context);
    }

    jQuery.fn = jQuery.prototype = {
        length:0,
        selector:'',
        init: function(selector, context){
            context = context || document;
            var match;
            //如果没有传selctor值 则返回当前jQuery实例对象
            if(!selector){
                return this;
            }
            if(typeof selector === 'string'){
                //如果传进来的是字符串 且是类似"<a>"这种带标签的 则表示创建dom节点
                if(selector.charAt(0) === '<' && selector.charAt(selector.length-1) === '>' && selector.length === 3){
                    match = [selector];
                }
                if(match){
                    //创建dom
                    //将创建的dom对象数组 [dom] 与当前jQuery实例合并在一起
                    jQuery.merge(this, jQuery.parseHtml(selector, context));
                }else{
                    //查询dom 使用querySelectorAll api查询到所有的dom节点
                    var ele = document.querySelectorAll(selector);
                    //将查询到的dom类数组对象转换为数组对象 并遍历dom数组对象 将他们挂载到jQuery实例对象上
                    var eles = Array.prototype.slice.call(ele);
                    var len = eles.length;
                    for(var i = 0; i < len; i++){
                        this[i] = eles[i];
                    }
                    this.selector = selector;
                    this.context = context;
                    this.length = len;
                }
            }else if(selector.nodeType){
                //传过来的是对象且具有nodeType属性 则把当前对象作为当前实例对象的context
                this.context = this[0] = selector;
                this.length = 1;
            }
        }
    }

    //extend
    jQuery.fn.extend = jQuery.extend = function(){
        var target = arguments[0] || {};
        var length = arguments.length;
        var i = 1;
        var deep = false;
        var options, name, copy, src, copyIsArray, clone;

        if(typeof target === 'boolean'){
            deep = target;
            target = arguments[1];
        }

        //扩展的第一个对象不是对象的时候 创建空对象复制给他
        if(typeof target != 'object'){
            target = {};
        }
        //参数的个数 如果只有一个则需要拿到当前调用对象 jQuery或者jQuery实例
        if(length === i){ //本身扩展
            target = this; //指向本身
            //把下标变成0,这样就可以在扩展自己本身的时候拿到需要扩展进去的对象   {name:'movi'}
            //$.extend({name:'movi'})
            i--;
        }

        //浅拷贝
        for(; i <length; i++){
            if((options = arguments[i]) != null){
                //options 代表要扩展的对象 {name:'max' ,list:{age:'20'}}    $.extend({}, {name:'max',list:{age:'20'}}); 
                for(name in options){
                    copy = options[name]; // name:'max'  list:{age:'20'}
                    src = target[name]; // undefined
                    //如果是深拷贝 则需要判断当前要扩展的对象是数组或者是对象
                    if(deep && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))){
                        if(copyIsArray){
                            //被扩展的源对象本身不是数组或者没有就创建空数组给他,否则就是自己本身
                            copyIsArray = false; //需要重置 保证每次循环都是最新值
                            clone = src && jQuery.isArray(src) ? src : [];
                        }else{
                            //被扩展的源对象本身不是对象或者没有 就创建空对象给他
                            clone = src && jQuery.isPlainObject(src) ? src : {};
                        }
                        target[name] = jQuery.extend(deep, clone, copy)
                    }else if(copy != undefined){ 
                        target[name] = copy;
                    }

                }
            }
        }

        return target;
    }

    //让jQuery和init共享原型 让$返回的对象享有jQuery原型上的所有方法
    jQuery.fn.init.prototype = jQuery.fn;

    jQuery.extend({
        //给jQuery本身扩展类型检测的方法
        isPlainObject: function(obj){
            return toString.call(obj) === '[object Object]';
        },
        isArray: function(obj){
            return toString.call(obj) === '[object Array]';
        },
        //将数组或者类数组对象和并到this即jQuery实例对象上
        merge: function(first, second){
            var l = first.length;
            var j = second.length;
            var i = 0;
            //如果传进来的第二个参数具有length属性 则遍历second数组 将值赋给first对象 下班从first length++开始
            if(typeof j === 'number'){
                for(i; i < j; i++){
                    first[l++] = second[i];
                }
            }else{
                //如果second是类数组 不具备length属性 则直接用while循环 将second每一项值赋给second
                while(second[i] != undefined){
                    first[l++] = second[i++];
                }
            }
            //这里将自增以后的l值作为传进来first的length属性值
            first.length = l;
            return first;
        },
        //将标签解析为具体的元素名 然后创建该dom对象
        parseHtml: function(selector, context){
            if(!selector || typeof selector != 'string'){
                return null
            }
            //过滤掉尖括号 获得元素名字 '<a>' => 'a'
            var parse = rejectExp.exec(selector); //[<p>, p]
            return [context.createElement(parse[1])];
        }

    }) 

    global.$ =  global.jQuery = jQuery;
})(this)    

关于js设计原则的简要总结

发表于 2019-10-14 | 分类于 理论 |
字数统计: 709 字 | 阅读时长 ≈ 3 分钟

(1)、单一职责原则

引起类的变更或者模块变更原因应该有且仅有一个(There must be one reason for a class to change)翻译过来的原因就是说,一个方法或者对象只做一件事情,其逻辑肯定比一个负责多项功能的方法简单的多,降低各方法之间的耦合性,避免改动其中一个就影响到其他模块的概率,降低变更引起的风险。Animal这个类就做了一件事情,就是喵喵叫,当传进来的动物时候猫的时候,没有任何问题,但是如果传进来的是狗,就有问题了,按照单一职责原则,这个时候需要重新写个类。

function Anmimal(){
    this.bark = function(animal){
        console.log(animal+'喵喵叫');
    }
}

var animal = new Anmimal();
animal.bark('猫');
animal.bark('狗');

function Anmimal1(){
    this.bark = function(animal){
        console.log(animal+'汪汪叫');
    }
}

var animal = new Anmimal1();
animal.bark('狗');

(2)迪米特法则

也叫最少知道原则,一个对象应该对其他对象有尽可能少的了解。一个对象内可以按照单一职责原则拆分很多功能模块,但是只对外暴露统一的借口或者调用方法,无须调用者知道被调用者内部其他方法功能模块等。

function  People(){
    run = () =>{
        console.log('run');
        talk();
    }

    talk = () => {
        console.log('talk');
    }

    return {
        runAndTalk: run,
    }
}

(3)依赖倒置原则

参考java里面的抽象类和借口,只负责定义基础方法但是不具体实现,具体实现交给子类去实现,如果你依赖的不是基础类,而是一个复杂的业务类,任何一项变更都将会导致一系列的变化,从而引发风险,维护成本也会增加。

(4)接口隔离原则

接口不能写的过于庞大和臃肿,尽可能的将拆分,将接口细化,个人理解类似一开始讲的单一职责原则,一个方法或者对象只负责一个功能,而不是一股脑的将所要功能写在一个方法里面,增加了耦合度。

(5)替换原则

子类要完全实现父类的方法,用父类地方可以完全用子类代替,子类可以扩展父类的功能,但是不能改变父类原有的功能,当功能扩展的时候,子类尽量不要去重写父类的方法,而是另写一个方法

(6)开闭原则

开闭原则其实讲的就是代码的可扩展性,当遇到需求变更时,我们只需要在原来的基础上调用方法扩展,而不是直接去修改原有代码逻辑,从而引发对原有逻辑的影响,例如Vue的mixin混入方法就是开闭原则的最好体现。

JS设计模式

发表于 2019-10-14 | 分类于 理论 |
字数统计: 1,638 字 | 阅读时长 ≈ 7 分钟

关于js设计模式的简要总结

按照功能我们将设计模式分为 创建型设计模式、结构性设计模式、行为设计模式、技巧型设计模式

创建型设计模式(创建对象的方式):

(1)、工厂模式:定义一个方法,返回给你对象。需要创建对象的时候,只需要调用和这个工厂方法即返回对象,分为单一工厂模式和建造者模式。

a、单一工厂模式:需要创建大量对象的时候,不关心创建过程,只关注创建结果,典型代表:JQuery

function factory(type){
    //类型校验 防错处理
    if(typeof type!='string'){
        throw new Error('type must be a string');
    }

    //如果调用的时候用new创建了新对象 则直接返回 无须再new 创建
    if(this instanceof factory){
        return this[type]();
    }else{
        return new factory(type);
    }
}
factory.prototype={
basketball:function(){
    this.name='basketball';
},
tennis:function(){
    this.name='tennis';
}
};

b、建造者模式:创建一个定制化的对象,关注创建的过程,典型代表:Vue

new Vue({
    data(){
        return {
            message:'txt'
        }
    },
    methods:{
        ...
    }
    ...
})

(2)、原型模式:创建一个共享的对象作为其他构造函数的原型对象,通过拷贝这个对象来创建新的类,用于创建重复的对象,带来性能上的提升。

var animalProto = {
    name:'animal',
    getName: function(){
        return this.name;
    }
}

function Animal(){

}
Animal.prototype = animalProto;

(3)、单例模式:保证全局只有一个对象,不重复创建对象

//自执行函数返回一个对象

var single=(function(){
    //定义私有变量
    function _a(){
        this.a=123;
    };
    var ob = new _a();
    return {
    get:function(name){
        return ob[name];
    }
    }
})();

结构型设计模式:功能模块的拆分

(1)、外观模式:为一组复杂的子系统接口提供一个更加高级的统一接口,在js中可以用于消除一些的底层的兼容性,和设计原则之迪米特法则相对应,对外暴露同意方法或接口,组装细分功能例如下例中的绑定事件,该方法中做了一系列的判断兼容处理,对外暴露的绑定事件方法无须关心底层是如何兼容的,直接调用即可

function addEvent(dom,type,fn){
    if(dom.addEventListener){ //支持dom2级操作
        dom.addEventListener(type,fn)
    }else if(dom.attachEvent){ //不支持dom2 但支持attach方法
        dom.attachEvent('on'+type,fn)
    }else{ //上面上个方法都不支持的方式
        dom['on'+type] = fn;
    }
}

(2)、适配器模式:把不适合的接口适配成另一个接口

var bizPublic = (function(){
    return newBizPublic.$bizMethod
})()

(3)、装饰器模式:把修改源码的行为改成扩展源码的行为,在不改变对象自身的基础上,动态的给某个对象添加额外的职责,不会影响原有接口的功能。一开始我们只有跑的功能,现在要求增加跳的功能,我们不是直接在run方法里面新增跳的功能,而是写一个装饰器函数,包含run和jump方法

function run(){
    console.log('run')
}

function decoratorFn(fn,wrapedFn){
    return function(){
        fn();
        wrapedFn();
    }
}
decoratorFn(run, function(){
    console.log('jump')
})

(4)、享元模式:分析对象内部方法和外部方法。然后把外部方法提取出来,内部方法继续保留。(重复的部分只做一次,if else只做差异性;只创建一次对象,类似于单例模式,共享这一个对象,个性化通过调用方法传不同的参数实现)

function uploadFile(){
    this.upload = function(fileName){
        ...
    }
}

var uploadFileObj = new uploadFile();
var arr = ['file1','file2', ...];
for(var i=0;i<arr.length;i++){
    uploadFileObj.upload(arr[i]);
}

行为设计模式:模块之间的沟通

(1)、观察者模式:两个模块之间无法直接沟通或者不方便沟通,多用于异步

//观察者
var observer = {
    eventObj:{

    },
    add:function(type, fn){
        this.eventObj[type] = fn;
    },
    remove:function(type){
        this.eventObj[type] = null;
    },
    fire(type){
        this.eventObj[type]();
    }
}
//注册事件
observer.add('test', function(){
    console.log('test');
});
//触发事件
observer.fire('test');

(2)、状态模式:封装了一个对象的不同状态,只针对自己的对象提供服务

function Mary {
    this._status = {
        run:function(){
            console.log('run');
        },
        jump:function(){
            console.log('jump');
        }
        ...
    }
}
Mary.prototype.changeStatus = function(status){
    if(Array.isArray(status)){
        for(let action of status){
            this._status[action]();
        }
    }else{
        this._status[status]();
    }
}

var maryObj = new Mary();
maryObj.changeStatus('run');
maryObj.changeStatus(['run','jump']);

(3)、策略模式:封装了多个策略的对象,可以为多个对象提供服务,和状态模式相同都是为了解决if else分支过多的问题。

function priceMethods(){
    //策略
    var methods = {
        percentage50(price){
            return price*0.5
        },
        percentage70(price){
            return price*0.7
        },
        percentage90(price){
            return price*0.9
        }
    };
    return function(methodName, args){
        methods[methodName] && methods[methodName].apply(this ,args)
    }
}

(4)、命令模式:只关注命令本身,用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

function setCommod(btn, fn){
    btn.onClick = function(){
        fn();
    }
}
//命令或者理解为策略
var refreshMenu = {
    refresh:function(){
        console.log('刷新菜单');
    }
}
setCommod(btn1 ,refreshMenu.refresh)


var msgCm = {
    type:'warn',
    msg:'警告',
}

function command(){
    function create(iconDom ,msg){
        var msgBox = document.createElement('div');
        msgBox.innerHTML = msg
        return iconDom + msg;
    }
    function display(content){
        var bodyDom = document.getElementsByTagName('body');
        bodyDom.appendChild(content);
    }
    function _excute(command){
    var state={
        wran:"<img src='imgage/wran.svg'/>",
        confirm:"",
    }
    display(create(state[command.type],command.msg));
    }
    return {
        excute:_excute
    }
}

command().excute(cm);

(5)、迭代器模式:对于集合内部结果常常变化各异,我们不想暴露其内部结构的话,但又想让客户代码透明地访问其中的元素,这种情况下我们可以使用迭代器模式。典型的就是forEach,map以及jQ的$each方法。

(6)、解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

技巧型设计模式:一种优化方式

(1)、链模式:可简单理解为链式调用

(2)、委托模式:给事件发生的上一层最近的父元素绑定事件就是典型的委托方式

(3)、节流模式:函数节流,设置延时器,一定时间以后执行,如果延时期间再次被触发,则清掉当前的延时器,重新设置新的延时器,直到最后只执行最后一次调用。

var timer;
function delay(){
    clearTimeout(timer);
    timer  = setTimeout(function(){
        console.log('1')
    },1000)
}
btn.onClick = function(){
    delay();
}

(4)、惰性模式:初始化的时候制定不变,下次初始化的时候再重新指定。减少每次代码 执行时重复的分支判断,通过对对象重新定义屏蔽原对象中的分支判断。

var type = 'warn';
var msg = (function (){
    if(type == 'warn'){
    return  function(){
            console.log('wran')
        }
    }else{
        return function(){
            console.log('error')
        }
    }
})()

(5)、等待者模式:可参考promise.all的实现原理

ideal启动tomcat(依赖marven插件)

发表于 2019-10-12 | 分类于 工具 |
字数统计: 394 字 | 阅读时长 ≈ 1 分钟

前情提要

此前写过一篇《ideal 启动 tomcat》的博客,这篇文章建立在此基础之上,这里只讲述差异之处,相同之处不再赘述,为方便阐述,接下来我会将《ideal 启动 tomcat》这篇文章简称为上篇文章。

marven 插件依赖选择

这里区别上篇文章所提到的 libraries 的配置。因为这里用的是 marven 插件库,故不用选择 libraies,可以按照如下操作:

1、建立在我们打开项目的基础之上,选择左上角 file 下面的 setting,找到 build Tools 选项,选择右侧的 marven,然后选择覆盖 marven 设置文件和仓库文件夹,这里默认的是远程仓库,我们要替换掉 user setting file 和 local respository。这里简要说明一下,settingFile 是 marven 设置文件,里面有远程仓库的账户和密码,有这个文件我们才能下载依赖,local respository 这个文件夹是用来放下载下来的依赖的。

选择marven1

选择marven2

2、我们需要在编辑器的右侧选择marven ,然后点击 + ,选择后台代码atom下面的pom.xml文件。

选择pom1

点击刷新按钮开始下载插件

选择pom2

配置 tomcat

这里的配置和上篇文章提到的配置是一样的,只对下图这个选项做个解释,这里表示是否有具有更新功能,默认是重新启动服务,但是我们有时候修改个别 class 不需要重启服务,所以选择更新资源,重新编译,避免时间的浪费。
选择p配置tomcatom

如何用js调用组件

发表于 2019-01-05 | 分类于 前端 |
字数统计: 518 字 | 阅读时长 ≈ 2 分钟

vue组件的正常使用

比如element-ui有个alert的组件,我们在使用的时候,需要先引入该组件,其次注册,并在template里面写相应的结构

...

 <el-alert
    title="成功提示的文案"
    type="success">
 </el-alert>

...

//引入组件
import { alert } from 'element-ui'

...

//注册组件
component:{
    ...
    alert
    ...
}

当我们多个页面要用这个组件的时候,我们不想多册引入该组件,注册组件,并写结构,如何实现通过js调用该组件呢

其实element-UI 里面的 messageBox组件就是通过js调用的

this.$alert('这是一段内容', '标题名称', {
  confirmButtonText: '确定',
  callback: action => {
    this.$message({
      type: 'info',
      message: `action: ${ action }`
    });
  }
});

如果是我们自己写了个messageBox组件或者其他组件,而且我也想只通过js调用,参考element-UI 源码,我写下了以下简陋的代码,实现此功能, Talk is cheap.Show you my code.

//index.js
import Vue from 'vue'; //引入vue
import message from './src/message'; //引入自己的组件
let Message = Vue.extend(message);

//核心就是拿到这个组件的实例,先new实例,然后拿到该组件的dom,并在body上append该dom节点
function init(options){

  let instance = new Message({
    // data: options
  });

  instance.vm = instance.$mount();
  document.body.appendChild(instance.vm.$el);

  defaultOptions = {
    hasMask:true,
    typeMessage:'alert',
    ownClass:'', 
    showMsgBox:true,
  }
  if(_.isString(options)){
    defaultOptions.messageText = options;
  }
  return instance
}

//以下就是把方法直接挂到vue原型上,方法里调用上面的init方法,然后再做something...
Vue.prototype.alert = function(options){
  let instance = init(options);
  let ownDefaultOptions = {
    confirmButtonText:'确认',
    confirmedAction:function(){console.log("可选的确认按钮点击事件")},
  }
  instance.messageOptions  = _.assign(defaultOptions,ownDefaultOptions,options);
}

Vue.prototype.confirm = function(options){
  let instance = init(options);
  let ownDefaultOptions = {
    typeMessage:'confirm',
    confirmButtonText:'确认',
    cancelButtonText:'取消', 
    confirmedAction:function(){console.log("可选的确认按钮点击事件")},
    canceledAction:function(){console.log("可选的取消按钮点击事件")}
  }
  instance.messageOptions  = _.assign(defaultOptions,ownDefaultOptions,options);
}

Vue.prototype.promote = function(options){
  let instance = init(options);
  let ownDefaultOptions = {
    hasInput:true,
    confirmButtonText:'确认',
    cancelButtonText:'取消',
    validType: 'email',
    confirmedAction:function(){console.log("可选的确认按钮点击事件")},
    canceledAction:function(){console.log("可选的取消按钮点击事件")}
  }
  instance.messageOptions  = _.assign(defaultOptions,ownDefaultOptions,options);
}

这样调用起来是不是方便很多呢,嘿嘿嘿。。。

ideal启动tomcat

发表于 2018-11-09 | 分类于 工具 |
字数统计: 514 字 | 阅读时长 ≈ 2 分钟

ideal 启动 tomcat 环境前置条件

前置条件就是你安装了 IDEA 编辑器、JDK 以及 Tomcat。同时本地要有一个 java 项目,否则拿什么去启动,当然你也可以本地新建一个,不过不在本次讲解范畴之内。。。

配置项目

打开 project structure 进行配置

打开项目结构进行设置

project 的配置

1、项目名称随便写,一般会默认当前项目名称

2、选择安装好的 jdk

3、选择项目语言级别,这里选 8,否则编译会报错,有兴趣的童鞋可以尝试一下

4、选择项目编译输出,项目在编译的时候会有一个输出项目,我们这里直接选择当前项目目录下的out文件夹,没有的话可以直接编辑成/当前项目/out,这里的 out 文件夹在后面编译的时候回生成。

project配置

module 的配置

module

1、点开 module,这里 ideal 已经自动帮我们加载好了我们的项目

2、点击 source 目录,然后选择我们要运行的 java 目录,也就时项目目录下src\main\java,将其作为编译的资源文件

配置module的source

这里选择第一个,也就是我们项目编译输出的目录out

配置module的path

module 里面的 dependencies 里面不用选择,ideal 会帮我们自动填好,直接 ok 行

配置module的dependencies

libraries 的配置

新增 java,选择当前项目运行所依赖的 jar 包

新增依赖
新增依赖
选择当前module

Facets 的配置

点击+新增 web

新增web

选择当前项目模块

注意web的路径

Artifacts 的配置

点击+新增,选择 Web Application:Exploded,然后选择 from modules

新增artifacts

选择当前项目

选择模块

配置 tomcat

点击 file,打开设置选项,搜索Application Server,然后选则 server,添加tomcat server,选择 tomcat 的安装路径

添加tomcat server

选择tomcat路径

点击 add configurations,配置 tomcat

add configurations

点击+选择 tomcat server,选择本地 local。

新增本地tomcat

输入 tomcat 的名称

输入tomat的名称

最后一步,需要配置 tomcat 的 Deployment,设置依赖

配置tomcat的Deployment

配置完成,大功告成。

点击run开启tomcat

如何在配置好启动本地服务后,用本地后台代码打包成 jar 包

本地编译打包1

本地编译打包2

然后就会在 target 文件夹下面生成 jar 包

123
MoviLee

MoviLee

心无所恃 随遇而安

25 日志
8 分类
60 标签
GitHub 微博 知乎 E-Mail
Links
  • Evans (另一个屌丝前端)
  • Cmk (技术架构工程师)
© 2022 MoviLee
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4