vue进阶笔记 --001

165 阅读13分钟

一、 @click.stop与@click.prevent

1.1 @click.stop

问题:父元素中添加了一个click事件,其下面的子元素中也添加了click事件,此时,我想点击子元素获取子元素的点击事件,但却触发的是父元素的事件:

此时,我们就需要使用@click.stop:阻止事件冒泡方法来解决这个问题

<view class="footer-box" @click="clickCard">
    <view @click.stop="footerClick('喜欢')"><text class="footer-box__item">喜欢</text></view>
    <view @click.stop="footerClick('评论')"><text class="footer-box__item">评论</text></view>
    <view @click.stop="footerClick('分享')"><text class="footer-box__item">分享</text></view>
</view>

1.2 @click.prevent

@click.prevent:阻止事件的默认行为,例如:在代码里写入一个a标签,点击会跳转到目标链接网页中

<view class="example-body">
    <a href="http://www.baidu.com">百度</a>
</view>

如果我们不想让它跳转但还想使用a标签的话,此时就需要使用@click.prevent方法:

<view class="example-body">
    <a href="http://www.baidu.com" @click.prevent='notLink'>百度</a>
</view>

二、 ES6 对象解构

2.1 使用对象解构处理动态名称属性

将键作为参数传递时,可以编写一个返回User对象属性值的函数。使用[]来接受参数,js会根据这个键对从对象中检索!

const User = {
  name: 'No Silver Bullet',
  age: '18',
  contact:{
    phone:'10',
  }
}

function getPropertyValue(key) {
    const { [key]: returnValue } = User;   
    return returnValue;
}
const contact = getPropertyValue('contact');
const name = getPropertyValue('name');
console.log(contact, name); // 空  No Silver Bullet

2.2 在循环中使用对象解构

若遍历数组并想要使用每个员工对象的属性值。

const staff = [
  { 
       'name': '爱分享的Alex',
   	'age': 16
  },
  { 
      'name': '搞前端的Alex',
       'age': 18
  },
  { 
        'name': '敲代码的Alex',
   	'age': 20
  }
];

for(let {name, age} of staff) {
  console.log(`${name} 今年${age}岁!!!`);
}

三、build 过程取消 console、debugger

3.1 配置文件修改

修改build/webpack.prod.conf.js配置文件,找到UglifyJsPlugin配置,在compress中添加如下代码即可。

new UglifyJsPlugin({
  uglifyOptions: {
    compress: {
      warnings: false,
      // 打包的时候移除console、debugger
      drop_debugger: true, // 移除debugger
      drop_console: true, // 移除console
      pure_funcs: ['console.log','console.info']
    }
  },
  sourceMap: config.build.productionSourceMap,
  parallel: true
}),

更优的配置方式如下:

new UglifyJsPlugin({
  uglifyOptions: {
    compress: {
      warnings: false,
      // 打包的时候移除console、debugger
      drop_debugger: process.env.NODE_ENV=== 'production', // 移除debugger
      drop_console: process.env.NODE_ENV=== 'production', // 移除console
      warnings: process.env.NODE_ENV=== 'production', // 移除告警信息
      pure_funcs: ['console.log','console.info']
    }
  },
  sourceMap: config.build.productionSourceMap,
  parallel: true
}),

四、 js map根据value获取key

paramsMap: {
  orderType: {
    '0': '咨询',
    '1': '投诉',
    '2': '举报',
    '3': '建议',
    '4': '求助',
    '5': '表扬',
  },
  subjectType: {
    'LB': '劳保',
    'XW': '消委',
    'GA': '公安',
    'GT': '国土',
    'CG': '城管',
    'GJJ': '公积金',
    'ZH': '综合',
  },
},

4.1 根据key获取value

getParamValue(paramType, code) {
  if (!Object.prototype.hasOwnProperty.call(this.paramsMap, paramType)) {
    return '参数类型错误';
  }
  return this.paramsMap[paramType][code];
},
this.getParamValue('orderType', '1');

4.2 根据value获取key

getParamCode(paramType, value, compare = (a, b) => a === b) {
  if (!Object.prototype.hasOwnProperty.call(this.paramsMap, paramType)) {
    return '参数类型错误';
  }
  return Object.keys(this.paramsMap[paramType]).find(k => compare(this.paramsMap[paramType][k], value))
}

this.getParamCode('subjectType', '公安');

五、 集合 Set 和 Map

5.1 集合的概念

集合是由一组无序且唯一(元素不能重复)的项组成的。这个数据结构使用了与有限集合相同的数学概念,应用在计算机的数据结构中。特点:keyvalue相同,没有重复的value.

5.2 Set集合

ES6提供了数据结构set,它类似于数组,但是成员的值都是唯一的,没有重复的值 。Set 本身是一个构造函数,用来生成 Set 数据结构。

5.21 Set类的方法:

  • Set.add(value)——Set.add(value) 添加一个数据,返回Set结构本身
  • Set.delete(value) set.delete(value) 删除指定数据,返回一个布尔值,表示删除是否成功
  • Set.has(value)——判断该值是否为set的成员,返回一个布尔值
  • Set.clear()——清除所有的数据,没有返回值

5.22 Set的遍历器

  • keys() 返回键名的遍历器,
  • values() 返回键值的遍历器,
  • entries() 返回键值对的遍历器

5.23 forEach() 使用回调函数遍历每个成员

const s = new Set([1,2,3,4]);

console.log(s.keys());  // SetIterator { 1, 2, 3, 4 }
console.log(s.values());   //SetIterator { 1, 2, 3, 4 }
console.log(s.entries());   //SetIterator { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ], [ 4, 4 ] }


//该方法可以接收三个参数,分别是:键值,健名,set本身
s.forEach(function(value,key,set){
    console.log(value+'lina');
    console.log(set)
})

5.24 利用Set为数组去重——直接将要给数组放入到,set构造方法中即可:

const arr = [1,2,3,45,2,3,4,13,5,7,1,3,2]

const s = new Set(arr);

console.log(s);  // Set { 1, 2, 3, 45, 4, 13, 5, 7 }
console.log(arr);   //[ 1, 2, 3, 45, 2, 3, 4, 13, 5, 7, 1, 3, 2 ]

console.log([...s]);//将set转成数组,结果是[ 1, 2, 3, 45, 4, 13, 5, 7 ]

5.3 Map数据结构

Map数据结构概述 字典:是用来存储不重复key的Hash结构,不同于集合(Set)的是,字典使用的是[键,值]的形式来存储数据的

5.31 Map类的方法:

  • set(key,value) 设置键名key对应的键值为value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就生成该键
  • get(key)——get方法读取key对应的键值,如果找不到key,返回undefined
  • delete(key) 删除某个键,删除成功放回true,如果删除失败,返回false
  • has(key) 方法返回一个布尔值,表示某个键是否在当前Map对象之中
  • clear() 清楚Map结构中的所有数据,没有返回值

Map的遍历器 keys() 返回键名的遍历器;values() 返回键值的遍历器;entries() 返回键值对的遍历器;forEach() 使用回调函数遍历每个成员

5.32 Map的遍历器

  • keys() 返回键名的遍历器;
  • values() 返回键值的遍历器;
  • entries() 返回键值对的遍历器;

5.33 forEach() 使用回调函数遍历每个成员

const  m = new Map([
    ['a',1],
    ['b',2],
    ['c',3]
]);

console.log(m.keys());//MapIterator { 'a', 'b', 'c' }
console.log(m.values()); //MapIterator { 1, 2, 3 }
console.log(m.entries());  //MapIterator { [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] }

//该方法可以接收三个参数:值,键,Map本身
m.forEach(function (value, key, Map) {
    console.log(value);
    console.log(key);
})

5.34 Map注意事项:

  • 两个NaN本身是不相等的,但在Map结构中,NaN作为键的话,视为相等的(同一个键)
  • 如果Map结构中的key是一个对象的情况下,每个对象都是不同的键,即使是两个空的对象,因为两个对象的地址值不同,以后引用别人的插件,使用对象作为键,就能避免同名碰撞的情况
  • Map 结构转为数组结构——比较快速的方法是使用扩展运算符()。
  • 数组结构转为Map结构——将数组传入 Map 构造函数,就可以转为 Map
  • Map结构转为对象——如果所有 Map 的键都是字符串,它可以转为对象
const myMap = new Map();

myMap.set('yes', true)
myMap .set('no', false);
console.log(myMap);   //Map { 'yes' => true, 'no' => false }

const obj = strMapToObj(myMap);
console.log(obj); //{ yes: true, no: false }

function strMapToObj(strMap) {
    let obj = Object.create(null);
    for (let [k,v] of strMap) {
        obj[k] = v;
    }
    return obj;
}
  • 对象转为Map结构
function objToStrMap(obj) {
    let strMap = new Map();
    for (let k of Object.keys(obj)) {
        strMap.set(k, obj[k]);
    }
    return strMap;
}

const map = objToStrMap({yes: true, no: false});
console.log(map);  //Map { 'yes' => true, 'no' => false }
  • Map结构转为JSON对象 Map 转为 JSON 要区分两种情况。

一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。 需要先将其转成对象

function strMapToJson(strMap) {
    return JSON.stringify(strMapToObj(strMap));
}


function strMapToObj(strMap) {
    let obj = Object.create(null);
    for (let [k,v] of strMap) {
        obj[k] = v;
    }
    return obj;
}

let myMap = new Map().set('yes', true).set('no', false);
console.log(strMapToJson(myMap));  //{"yes":true,"no":false}

另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON

function mapToArrayJson(map) {
    return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
console.log(mapToArrayJson(myMap));  //[[true,7],[{"foo":3},["abc"]]]
JSON转为Map结构——JSON 转为 Map,正常情况下,所有键名都是字符串

function jsonToStrMap(jsonStr) {
    return objToStrMap(JSON.parse(jsonStr));
}

function objToStrMap(obj) {
    let strMap = new Map();
    for (let k of Object.keys(obj)) {
        strMap.set(k, obj[k]);
    }
    return strMap;
}

console.log(jsonToStrMap('{"yes": true, "no": false}'));  //Map { 'yes' => true, 'no' => false }

有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为 JSON 的逆操作。

function jsonToMap(jsonStr) {
    return new Map(JSON.parse(jsonStr));
}

console.log(jsonToMap('[[true,7],[{"foo":3},["abc"]]]')); //Map { true => 7, { foo: 3 } => [ 'abc' ] }

六、 JavaScript 浮点数精度计算

6.1 浮点数精度问题

6.11 判断浮点数是否相等

//通过isEqual工具方法判断数值是否相等
function isEqual(number1, number2, digits){
  digits = digits == undefined? 10: digits; // 默认精度为10
  return number1.toFixed(digits) === number2.toFixed(digits);
}
console.log(isEqual(1.0-0.7, 0.3));  //true
//原型扩展方式,更喜欢面向对象的风格
Number.prototype.isEqual = function(number, digits){
  digits = digits == undefined? 10: digits; // 默认精度为10
  return this.toFixed(digits) === number.toFixed(digits);
}
console.log((1.0-0.7).isEqual(0.3)); //true

6.12 浮点数的运算

目录:/common/base.js

//加法函数,用来得到精确的加法结果
//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
//调用:accAdd(arg1,arg2)
//返回值:arg1加上arg2的精确结果
const accAdd = (arg1,arg2) => {
    var 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
  }
  //给Number类型增加一个add方法,调用起来更加方便。
  Number.prototype.add = function (arg){
    return accAdd(arg,this);
  }
   
  //减法函数,用来得到精确的减法结果
  //说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的减法结果。
  //调用:accSub(arg1,arg2)
  //返回值:arg1减去arg2的精确结果
  const accSub = (arg1,arg2) => {
    var r1,r2,m,n;
    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));
    //last modify by deeka
    //动态控制精度长度
    n=(r1>=r2)?r1:r2;
    return ((arg1*m-arg2*m)/m).toFixed(n);
  }
   
  //除法函数,用来得到精确的除法结果
  //说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
  //调用:accDiv(arg1,arg2)
  //返回值:arg1除以arg2的精确结果
  const accDiv = (arg1,arg2) => {
    var t1=0,t2=0,r1,r2;
    try{t1=arg1.toString().split(".")[1].length}catch(e){}
    try{t2=arg2.toString().split(".")[1].length}catch(e){}
    while(Math){
      r1=Number(arg1.toString().replace(".",""))
      r2=Number(arg2.toString().replace(".",""))
      return (r1/r2)*Math.pow(10,t2-t1);
    }
  }
  //给Number类型增加一个div方法,调用起来更加方便。
  Number.prototype.div = function (arg){
    return accDiv(this, arg);
  }
   
  //乘法函数,用来得到精确的乘法结果
  //说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
  //调用:accMul(arg1,arg2)
  //返回值:arg1乘以arg2的精确结果
  const accMul = (arg1,arg2) => {
    var 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)
  }
  //给Number类型增加一个mul方法,调用起来更加方便。
  Number.prototype.mul = function (arg){
    return accMul(arg, this);
  }

页面引用:

import { accAdd, accSub, accMul, accDiv } from '@/common/base.js';

console.log(accAdd(1.79, 0.12));  //1.91
console.log(accSub(1.79, 0.12));  //1.67
console.log(accMul(1.79, 0.12));  //0.2148
console.log(accDiv(1.79, 0.12));  //14.916666666666666

6.2 整数精度问题

在 JavaScript 中 Number类型统一按浮点数处理,整数是按最大54位来算最大(253 - 1,Number.MAX_SAFE_INTEGER,9007199254740991) 和最小(-(253 - 1),Number.MIN_SAFE_INTEGER,-9007199254740991)安全整数范围的。所以只要超过这个范围,就会存在被舍去的精度问题。

6.21、解决方案

6.211 类库

通常这种对精度要求高的计算都应该交给后端去计算和存储,因为后端有成熟的库来解决这种计算问题。

前端有几个不错的类库:

  • Math.js

Math.js 是专门为 JavaScript 和 Node.js 提供的一个广泛的数学库。它具有灵活的表达式解析器,支持符号计算,配有大量内置函数和常量,并提供集成解决方案来处理不同的数据类型 像数字,大数(超出安全数的数字),复数,分数,单位和矩阵。 功能强大,易于使用。

官网

  • decimal.js

为 JavaScript 提供十进制类型的任意精度数值。

官网

  • 4.1.3 big.js

官网

七、# $OPTIONS

vue实例属性$options用来获取定义在data外的数据和方法。

<script>
export default {
  name: "optionsTest",
  data() {
    return {
    };
  },
  //在data外面定义的属性和方法通过$options可以获取和调用
  name: "CSDN",
  age: 12,
  testMethod() {
    console.log("shq5785");
  },
  created() {  
    console.log(this.$options.name);  // CSDN
    console.log(this.$options.age);  //12
    this.$options.testMethod();  // shq5785
  
  },
</script>

八、 应用 qs 插件实现参数格式化

8.1 在vue项目开发过程中,使用axios请求后台时,后台无法获取前端传参数据。

8.2 qs 是一个增加了一些安全性查询字符串解析序列化字符串的库。

8.21

安装qs

npm install qs

8.22 在组件中应用

import qs from 'qs'

或定义为全局组件:

main.js引入qs

import qs from  'qs'  

全局属性配置,在任意组件内可以使用this.$qs获取qs对象

Vue.prototype.$qs = qs

8.23 具体应用

主要使用qs.parse(),qs.string。

  • qs.parse()是将URL解析成对象形式;
  • qs.stringify()是将对象序列化成URL的形式,以&进行拼接;

例子:

this.username = 'alex';
this.password = '123456';
let data = qs.stringify({
    "username":this.username,
    "password":this.password
});

序列化后的结构如下:

username=alex&password=123456

8.24 解析

8.241 解析对象

//解析对象
let res = this.$qs.parse('query[name]=zhangsan')
console.log(res)
//控制台输出结果:{query:{name:'zhangsan'}}

//解析嵌套对象
res = this.$qs.parse('query[name][nickname]=小三儿')
console.log(res)
//控制台输出结果:{query:{name:{nickname:'小三儿'}}}

//当使用嵌套对象时,qs 在默认情况下最多解析到的深度是第五层(注:从第一个方括号到算起,到第五个方括号)
res = this.$qs.parse('a[b][c][d][e][f][g][h][i]=j')
console.log(res)
//控制台输出结果:
// a: {
//   b: {
//     c: {
//       d: {
//         e: {
//           f: {
//             '[g][h][i]': 'j'
//           }
//         }
//       }
//     }
//   }
// }

// depth 参数指定解析深度
//当 qs 用于解析用户输入的时候,解析深度的限制有助于减轻用户的滥用行为
res = this.$qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 })
console.log(res)
//控制台输出结果:
// a: {
//     b: {
//       {[c][d][e][f][g][h][i]:'j'}
//     }
//   }
// }

// ignoreQueryPrefix忽略查询字符串开头的 ?
res = this.$qs.parse('?a=b&c=d', { ignoreQueryPrefix: true })
console.log(res)
//控制台输出结果:{a:'b':c:'d'}

8.242 解析数组

//用[]解析数组
let res = this.$qs.parse('a[]=b&a[]=c')
console.log(res)
//控制台输出结果:{a:['b','c']}

//指定数组索引
res = this.$qs.parse('a[0]=b&a[1]=c')
console.log(res)
//控制台输出结果:{a:['b','c']}

//要将字符串解析成数组而不是对象,那么[]之间的值必须是一个数字
//在创建具有特定索引的数组时,qs会将稀疏数组压缩为仅保留其顺序的现有值
res = this.$qs.parse('a[1]=b&a[15]=c')
console.log(res)
//控制台输出结果:{a:['b','c']}

//空字符串也是一个值,并将被保留
res = this.$qs.parse('a[]=&a[]=b')
console.log(res)
//控制台输出结果:{a:['','c']}
res = this.$qs.parse('a[0]=b&a[1]=&a[2]=c')
console.log(res)
//控制台输出结果:{a:['b',','c']}

//qs 限制数组最大索引为 20,任何索引大于20的数组成员都将被转换为以索引为键的对象
res = this.$qs.parse('a[100]=b')
console.log(res)
//控制台输出结果:{a:['100','b']}

//arrayLimit 选项可以修改默认限制
res = this.$qs.parse('a[1]=b', { arrayLimit: 0 })
console.log(res)
//控制台输出结果:{a:['1','b']}

//设置 parseArrays 为 false,字符串不解析成数组
res = this.$qs.parse('a[1]=b', { parseArrays: false })
console.log(res)
//控制台输出结果:{a:{'1','b'}}

//如果混合使用两种格式,qs 会将字符串解析为对象:
res = this.$qs.parse('a[0]=b&a[b]=c')
console.log(res)
//控制台输出结果:{a:{'0':'b',b:'c'}

//创建元素为对象的数组
res = this.$qs.parse('a[][b]=c')
console.log(res)
//控制台输出结果:{a:[{b:'c'}]}

8.243 序列化

//对象序列化后
let res = this.$qs.stringify({ a: 'b' })
console.log(res)
//控制台输出结果:a=b

//对象序列化后进行URI编码后输出
res = this.$qs.stringify({ a: { b: 'c' } })
console.log(res)
//控制台输出结果:a%5Bb%5D=c

res = this.$qs.parse(res)
console.log(res)
//控制台输出结果:{a:{b:c}}

//设置 encode 为 false,禁止URI编码
res = this.$qs.stringify({ a: { b: 'c' } },{encode:false})
console.log(res)
//控制台输出结果:a[b]=c

//设置 encodeValuesOnly 为 true,禁止URI编码
res = this.$qs.stringify( { a: 'b', c: ['d', 'e=f'], g: [['h'], ['i']] }, { encodeValuesOnly: true })
console.log(res)
//控制台输出结果:a=b&c[0]=d&c[1]=e%3Df&g[0][0]=h&g[1][0]=i

//当 encode 被设置为true时,设置encoder 选项自定义编码方式
res = this.$qs.stringify( { a: { b: 'c' } },{encoder:function (str) {
      return "a-b-c"
  }})
console.log(res)
//控制台输出结果:a-b-c=a-b-c

//当数组被序列化时,默认显示索引
res = this.$qs.stringify({ a: ['b', 'c', 'd'] })
console.log(res)
//控制台输出结果:a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d   即:a[0]=b&a[1]=c&a[2]=d

//设置 indices 为 false 不显示索引
res = this.$qs.stringify({ a: ['b', 'c', 'd'] },{indices:false})
console.log(res)
//控制台输出结果:a=b&a=c&a=d

//设置 arrayFormat 选项指定数组输出格式
res = this.$qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
console.log(res)
//控制台输出结果:a%5B0%5D=b&a%5B1%5D=c  即:a[0]=b&a[1]=c
res = this.$qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
console.log(res)
//控制台输出结果:a%5B%5D=b&a%5B%5D=c  即:a[]=b&a[]=c
res = this.$qs.stringify({ a: { a: ['b', 'c'] }},{ arrayFormat: 'repeat' })
console.log(res)
//控制台输出结果:a%5Ba%5D=b&a%5Ba%5D=c   即:a[a]=b&a[a]=c

//对象序列化时,默认使用 []
res = this.$qs.stringify({ a: { b: { c: 'd', e: 'f' } } })
console.log(res)
//控制台输出结果:a%5Bb%5D%5Bc%5D=d&a%5Bb%5D%5Be%5D=f   即:a[b][c]=d&a[b][e]=f

//空字符串和null值将被省略,但是=会保留
//值为 undefined 的属性将会被完全忽略
//默认情况下,null 值被视为空对象
res = this.$qs.stringify({ a:'',b: null, c: undefined })
console.log(res)
//控制台输出结果:a=&b=

//没有值的键将什么也不返回(例如空对象或数组)
res = this.$qs.stringify({ a: []  } )
console.log(res)
//控制台输出结果:
res = this.$qs.stringify({ a: {} } )
console.log(res)
//控制台输出结果:
res = this.$qs.stringify({ a: [{}] } )
console.log(res)
//控制台输出结果:
res = this.$qs.stringify({  a: { b: []} } )
console.log(res)
//控制台输出结果:
res = this.$qs.stringify({ a: { b: {}} } )
console.log(res)
//控制台输出结果:

//ddQueryPrefix 设置为 true可以在查询字符串前面加 ?
res = this.$qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true })
console.log(res)
//控制台输出结果:?a=b&c=d

//序列化日期对象
var date = new Date(7);
res = this.$qs.stringify({ a: date })
console.log(res)
//控制台输出结果:a=1970-01-01T00%3A00%3A00.007Z  //%3A对应的字符为:
res = this.$qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } })
console.log(res)
//控制台输出结果:a=7


//使用 sort 选项来修改键的顺序
function alphaSort(a, b) {
  return a.localeCompare(b);
}
res = this.$qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphaSort })
console.log(res)
//控制台输出结果:a=c&b=f&z=y

九、 父子组件元素获取、方法互相调用

9.1 父组件访问子组件(推荐 $refs)

9.11 使用 $children

在父组件中使用 this.$children 拿到的是一个数组类型,它包含所有子组件实例。

<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <button @click="btnClick">按钮</button>
</div>

<template id="cpn">
  <div>
    <h1>我是子组件</h1>
  </div>
</template>

<script>
  let vm = new Vue({
    el: "#app",
    data: {},
    methods: {
      btnClick() {
        //1.拿到所有子组件,是一个数组
        console.log(this.$children);

        //2.拿到一个组件实例,可以直接访问子组件中的方法和 data 中的数据
        this.$children[0].showMessage();
        console.log(this.$children[0].name);
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: 'webchang'
          }
        },
        methods: {
          showMessage() {
            console.log('我是子组件');
          }
        }
      }
    }
  });
</script>

9.12 使用 $refs

使用$children 的缺陷如下:

通过 $children 访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。

但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。

有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用 $refs

$refs 的使用 通过设置子组件的ref,父组件通过this.$refs.xxx.method_name(data)调用子组件方法,data参数可选。

$refsref 指令通常是一起使用的。

首先,我们在子组件上添加一个 ref 属性,相当于给某一个子组件绑定一个特定的ID。

其次,this.$refs 拿到的是所有标有 ref 属性的子组件(如果一个子组件实例没有 ref 属性,通过这种方式是拿不到的),最后拿到的是一个对象,属性名是子组件实例的 ref 属性,属性值是该组件实例。

通过 this.$refs.ID 就可以访问到该组件。

示例代码:

<div id="app">
  <cpn ref="child1"></cpn>
  <cpn ref="child2"></cpn>
  
  <!-- 这个子组件实例没有 ref 属性,通过 this.$refs 方式拿不到这个组件实例 -->
  <cpn></cpn>
  <button @click="btnClick">按钮</button>
</div>

<template id="cpn">
  <div>
    <h1>我是子组件</h1>
  </div>
</template>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  let vm = new Vue({
    el: "#app",
    data: {
      message: "hello"
    },
    methods: {
      btnClick() {
        console.log(this.$refs)
        console.log(this.$refs.child1)
        console.log(this.$refs.child1.name)
        this.$refs.child1.showMessage('父组件')
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: 'webchang'
          }
        },
        methods: {
          showMessage(value) {
            console.log("子组件方法被调用,调用者:" + value)
          }
        }
      }
    }
  });
</script>

9.2 子组件调用父组件方法(推荐 $emit)

9.21 方法一:this.$parent.event

直接在子组件中通过this.$parent.event来调用父组件的方法。示例代码:

父组件

<template>
  <div>
    <child></child>
  </div>
</template>
<script>
  import child from './components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod(value) {
        console.log("父组件方法被调用,调用者:" + value)
      }
    }
  };
</script>

子组件

<template>
  <div>
    <button @click="childMethod()">点击</button>
  </div>
</template>
<script>
  export default {
    methods: {
      childMethod() {
        this.$parent.fatherMethod('子组件');
      }
    }
  };
</script>

9.22 方法二: $emit

在子组件里用$emit向父组件触发一个事件,父组件监听这个事件。

父组件

<template>
  <div>
    <child @fatherMethod="fatherMethod"></child>
  </div>
</template>
<script>
  import child from '~/components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod(id) {
        console.log('测试传值',id);
      }
    }
  };
</script>

子组件

<template>
  <div>
    <button @click="childMethod()">点击</button>
  </div>
</template>
<script>
  export default {
    methods: {
      childMethod() {
        this.$emit('fatherMethod',id:1);
      }
    }
  };
</script>

9.23 方法三:方法传参

父组件把方法传入子组件中,在子组件里直接调用这个方法。

父组件

<template>
  <div>
    <child :fatherMethod="fatherMethod"></child>
  </div>
</template>
<script>
  import child from '~/components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod() {
        console.log('测试');
      }
    }
  };
</script>

子组件

<template>
  <div>
    <button @click="childMethod()">点击</button>
  </div>
</template>
<script>
  export default {
    props: {
      fatherMethod: {
        type: Function,
        default: null
      }
    },
    methods: {
      childMethod() {
        if (this.fatherMethod) {
          this.fatherMethod();
        }
      }
    }
  };
</script>

十、 Vue 项目调试技能

JetBrains系列WebStormVue项目进行调试的2种方法:debuggerVue-devtools

10.1 debugger

debugger是谷歌浏览器提供的调试语句,其主要是通过停止JS的执行,相当于设置断点。它的使用方法很简单, 只需要在我们的JS语句中, 插入一行debugger; 即可。

在JS代码编写的过程中,我们都会通过浏览器的调试模式(F12)来检查代码逻辑是否正确,大多数我们都是通过设置断点来进行调试。

应用debugger调试Vue项目,需要在项目中需要的位置写debugger,项目运行后,打开浏览器按F12,在chrome sources页签中就会直接进入断点,至此可以进行单步、跳步调试了。

10.2 Vue-devtools

该调试工具为针对Chrome浏览器而设计的开源调试工具(Github地址),可以自行将该项目下载下来然后编译,并将生成后的chrome插件安装至chrome中,步骤如下: 找到谷歌浏览器的扩展程序功能,勾选开发者模式,然后我们将插件文件夹里的shells>chorme文件夹直接拖到页面中,完成安装。

image.png

  1. 应用devtools调试工具,还需要在vue项目中man.js配置:
Vue.config.devtools = true;
  1. 安装后, 需要关闭浏览器, 再重新打开, 才能使用;
  2. 如果调试插件安装后,vue面板未出现,再到vue-devtools文件夹下执行一遍npm run dev