前端小白随手笔记

前端小白的入门笔记

关于JS和ES6的问题

1.模板字符串

模板字符串是用反双引号来标识的**()**,可以用来当作普通字符串来使用,也可以用来嵌套变量和其他模板字符串,模板字符串里面的空格和换行是会一样显示在页面上的,下面是使用方法。

	let str=`helloword`
	let str2=`helloword    123`
	let str3=`abc${5+5}  +123`
	const num1= 123
	const num2 = 456
	const result = `${num1*num2+str2}`
	console.log(str)//helloword
	console.log(str2)//helloword    123
	console.log(str3)//abc10
	console.log(result)//56088helloword    123

2.扩展运算符

扩展运算符(spread)是三个点(…)。将一个数组转为用逗号分隔的参数序列。
用法一:函数中的数组传参(替代函数的 apply 方法 )。

// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

用法二:于把一个数组添加到另一个数组当中

// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

用法三:复制数组

const a1 = [1, 2];
const a2 = [...a1];

用法四:合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
[...arr1, ...arr2, ...arr3](浅拷贝,如果修改了原数组的成员,会同步反映到新数组。)

用法五:扩展运算符还可以将字符串转为真正的数组。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。

3.解构赋值

用法一:从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();
console.log([a,b,c]) //[1, 2, 3]

// 返回一个对象

function example2() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example2();
console.log({foo,bar})//{foo: 1, bar: 2}  注意接受的变量名和对象的key应该保持一致

用法二:函数参数的默认值

    function example(a={aaa:'ccc'},b=2){
        console.log(a,b)
    }
    example()//{aaa: "ccc"} 2
    example(a=3)//3 2

4.for…of 循环

 ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。

for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。(目前JS的数据集合有四种:数据,对象,Map,Set)
for…of循环可以代替数组实例的forEach方法。

const arr = ['red', 'green', 'blue'];

arr.forEach(function (element, index) {
  console.log(element); // red green blue
  console.log(index);   // 0 1 2
});


const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

JavaScript 原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for…of循环,允许遍历获得键值。

var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}
//for in 不支持的对象
for (let a of arr) {
  console.log(a); // a b c d
}

Set 和 Map 结构
Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for…of循环。

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

上面代码演示了如何遍历 Set 结构和 Map 结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

forEach 和 map 的区别:map方法返回的是一个新数组
另外:利用 filter方法进行数组的去重

const arr1 = [10,10,30,40,10,20,60,40,10]
const arr2 = arr1.filter( (element, index, self) => {
    return self.indexOf(element)===index
});

console.log( arr2 );// [10, 30, 40, 20, 60]

5.async和await(处理异步的问题)

在ES6以前,我们进行异步处理都是通过回调函数的方式进行,但这种方式会有回调深渊的问题出现。其中异步处理包括:一个请求接口的函数并且返回一个值,下一个语句要用到这个值,这个时候往往函数还没有执行完毕,就要调用这个返回值。现在我们可以用两种方法来处理这种问题

原问题:
const boo = apiCustomerCheck(pars).then(res => {
            return res.data.data
          })
          if (boo) {
            this.error_msg = ''
            this.error_msg = '该用户名称已经存在'
            return false
          }//执行 if 语句的时候,上面的接口函数还没有执行完毕。
解决办法:
方法一:把 if 语句放在接口函数内(不是每次情况都合适)
apiCustomerCheck(pars).then(res => {
           const boo = res.data.data
           if (boo) {
             this.error_msg = ''
             this.error_msg = '该用户名称已经存在'
             return false
           }
方法二:asyncawait
   async save (formName) {
     // 验证手机号是否唯一
     const phonePar = {
       agencyId: JSON.parse(localStorage.entityInfo).agency_id,
       mobilePhone: this.ruleForm.phone
     }
     const phonePass = await checkmobilePhoneUnique(phonePar.agencyId, phonePar.mobilePhone).then(res => {
       return res.data.data.flag
     })//注意 async 和 await 要成对出现,没有 await 的情况下执行 async 函数,
     //它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
     //await 应该在async 对应的函数体内,不能在async 对应的函数里的嵌套函数。
     
     

顺序问题:
//async await 的异步操作

async function helloword(){
	console.log(1)
	await waiting ()
	
	console.log(3)
}
function waiting(){
	console.log(2)
}
setTimeout(function () {
    console.log(4);
},0);
function saysometing(){
	console.log(5)
}

helloword()
saysometing()
console.log(6)

代码从setTimeout开始,遇到setTimeout直接丢到等待队列的最末端(其实是另一个优先级较低的等待队列的队首,姑且认为是promise等待队列的最末端)。,然后到helloword() ,首先打印 1 ,遇到 await waiting (),输出2,在执行过后马上将后面的代码(console.log(3))全部放到promise等待队列中。再执行同步代码中的saysomething()和console.log(6).
接下来执行异步代码
首先执行await后面的代码(console.log(3)),再执行settimeout().
记住,await 跟着settimeout函数会放到异步中,甚至是等待队列的最后。

6.Object对象方法

(1). Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
const target = { a: 1, d: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 ,d: 2}

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 ,d: 2}

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。

String类型和 Symbol 类型的属性都会被拷贝。

在出现错误的情况下,例如,如果属性不可写,会引发TypeError,如果在引发错误之前添加了任何属性,则可以更改target对象。

注意,Object.assign 不会在那些source对象值为 null 或 undefined 的时候抛出错误。

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。
(2). Object.definePropety() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

Object.defineProperty(obj, prop, descriptor)
参数:

obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptor
将被定义或修改的属性描述符。

该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for…in 或
Object.keys 方法), 这些属性的值可以被改变,也可以被删除。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用
Object.defineProperty() 添加的属性值是不可修改的。

var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

// 对象o拥有了属性a,值为37

// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

o.b = 38;
// 对象o拥有了属性b,值为38

// o.b的值现在总是与bValue相同,除非重新定义o.b

// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
  value: 0x9f91102, 
  get: function() { 
    return 0xdeadbeef; 
  } 
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors

考虑特性被赋予的默认特性值非常重要,通常,使用点运算符和Object.defineProperty()为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示。

var o = {};

o.a = 1;
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : true,
  configurable : true,
  enumerable : true
});


// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : false,
  configurable : false,
  enumerable : false
});

7.原型链

每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为原型链(prototype chain)。

根据规范不建议直接使用 proto,推荐使用 Object.getPrototypeOf(),不过为了行文方便逻辑清晰,下面都以 proto 代替。

注意上面的说法,原型上的方法和属性被 继承 到新对象中,并不是被复制到新对象。

prototype 和 proto

其中原型对象 prototype 是构造函数的属性,proto 是每个实例上都有的属性,这两个并不一样,但 foo.proto 和 Foo.prototype 指向同一个对象。
原型链的构建依赖于 proto,如上图通过 foo.proto 指向 Foo.prototype,foo.proto.proto 指向 Bichon.prototype,如此一层一层最终链接到 null。

实现instanceof

function instance_of(L,R){
	let Y = R.prototype
	let Z = L.__proto__
		if(Z===null)
			return false
		if(Z===Y)
			return true
		Z = Z.__proto__
	
}
function C(){} 
function D(){} 

var o = new C();

console.log(instance_of(o, C))

总结

每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为**原型链 **

当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。

原型链的构建依赖于 proto,一层一层最终链接到 null。

tnstanceof 原理就是一层一层查找 proto,如果和 constructor.prototype 相等则返回 true,如果一直没有查找成功则返回 false。

原型链继承的本质是重写原型对象,代之以一个新类型的实例。

8.call 和 apply 的用法

call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。

call() 和 apply()的区别在于,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。

作用一 合并数组
let arr1 = ['Mike','John','Amry','jack']
let arr2 = ['jay','lucy']
Array.prototype.push.apply(arr1,arr2)
console.log(arr1) //["Mike", "John", "Amry", "jack", "jay", "lucy"]
作用二 找出数组中的最大值
let arr = [100,200,300]
console.log(Math.max.apply(Math,arr))//300
console.log(Math.max.call(Math,...arr))//300
为什么要这么用呢,因为数组 numbers 本身没有 max 方法,但是 Math 有呀,
所以这里就是借助 call / apply 使用 Math.max 方法。

9.关于前端跨域的问题

(1)什么是跨域:

1、以下三种情况出现一种都属于跨域:协议不同、域名不同、端口号不同
2、跨域这个概念来自一个叫 “同源策略” 的东西。同源策略是浏览器(注意是浏览器,跟通信协议无关)上为了安全考虑实施的非常重要的安全机制。
3、Ajax 默认只能获取到同源的数据,对于非同源的数据,Ajax是获取不到的。

(2)解决跨域的办法有哪些

1、解决跨域的方法简单的有三种:反向代理,JSONP,CORS。
2、如果访问的别人服务器的资源,并且未设置JSONP,服务器也没有设置CORS接口,那么唯一的选择就是使用自己的服务器进行反向代理。
3、反向代理:就是使用自己的服务器,在后端请求目标服务器的数据,然后返回给客户端。反向代理服务器,最常用的就是Nginx。
但是作为前端代码实现的Node.js也可以搭建反向代理服务器。
比如我有一个后端接口:http://39.105.136.190:3000/zhuiszhu/goods/getList,可以获取一些商品列表数据,但是我运行的node项目是在 localhost:3000 下的,明显构成跨域。
我们根据项目使用的框架不同,处理的方式也不同。
我们经常在vue开发项目的时候,会用webpack作为前端自动化构建工具的话,也会使用到webpack-dev-server的插件,那么可以引用webpack-dev-server来进行配置跨域方案。
webpack-dev-server是一个小型的nodejs服务器,是基于express框架的,用于实时监听和打包编译静态资源。其中里面有一个属性是proxy,是专门来配置代理请求接口的。
你只需要在webpack.config.js中 devServer中如下设置即可:
devServer: {
port: 3000,
inline: true,
proxy: {
“/zhuiszhu”: {
target: “http://39.105.136.190:3000/”,
changeOrigin: true //必须配置为true,才能正确代理
}
}
},
4、JSONP
JSONP基本思想是,网页通过添加一个“<s c r i p t删除线格式 >”元素,向服务器请求JSON数
据,这种做法不受同源政策限制;服务器收到请求后,将数据作为参数放在一个指定名字的回调函数里传回来,这个回调函数的名字我们需要通过js定义好。
比如:当页面资源加载完毕时候,获取跨域的数据,并且制定回调函数的名字为为foo:
window.onload = function () {
var script = document.createElement(“script”);
script.setAttribute(“type”,“text/javascript”);
script.src = "http://bbb.com?callback=foo;
document.body.appendChild(script);
};
foo(data) {
console.log(data); // data即为跨域获取到的数据
}
使用JSONP需要注意:
必须后端配置相应回调函数。
只能发送GET请求。
5、CORS全称“ Cross-origin resource sharing ”(跨域资源共享),相比JSONP, CORS允许任何类型的请求 。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信

10.关于 js 中 this 指向的问题

1、 主要分为两类,分别是显式绑定和隐式绑定。
2、现实是绑定的主要包括 apply、call、bind等方法。
3、这里主要讨论的是隐式绑定。
4、主要是以下隐式绑定的场景讨论:
全局上下文
直接调用函数
对象.方法的形式调用
DOM事件绑定(特殊)
new构造函数绑定
箭头函数

  1. 全局上下文
    全局上下文默认this指向window, 严格模式下指向undefined。
  2. 直接调用函数
    比如:
let obj = {
  a: function() {
    console.log(this);
  }
}
let func = obj.a;
func();  //这里的打印结果还是 window ,应该是看函数在什么地方被调用。
  1. 对象.方法的形式调用
    还是刚刚的例子,我如果这样写:
obj.a(); //这就是对象.方法的情况,this指向这个对象
  1. new+构造函数
    此时构造函数中的this指向实例对象。
  2. 箭头函数?
    箭头函数没有this, 因此也不能绑定。里面的this会指向当前最近的非箭头函数的this,找不到就是window(严格模式是undefined)。比如:
let obj = {
  a: function() {
    let do = () => {
      console.log(this);
    }
    do();
  }
}
obj.a(); // 找到最近的非箭头函数a,a现在绑定着obj, 因此箭头函数中的this是obj

优先级: new > call、apply、bind > 对象.方法 > 直接调用。

11.关于 js 中 深拷贝和浅拷贝的问题

首先 先明白什么是拷贝:

let arr = [1, 2, 3];
let newArr = arr;
newArr[0] = 100;

console.log(arr);//[100, 2, 3]

这是直接赋值的情况,不涉及任何拷贝。当改变newArr的时候,由于是同一个引用,arr指向的值也跟着改变。
现在进行浅拷贝:

let arr = [1, 2, 3];
let newArr = arr.slice();
newArr[0] = 100;

console.log(arr);//[1, 2, 3]

当修改newArr的时候,arr的值并不改变。什么原因?因为这里newArr是arr浅拷贝后的结果,newArr和arr现在引用的已经不是同一块空间啦!
这就是浅拷贝。
但是这又会带来一个潜在的问题:

let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;

console.log(arr);//[ 1, 2, { val: 1000 } ]

咦!不是已经不是同一块空间的引用了吗?为什么改变了newArr改变了第二个元素的val值,arr也跟着变了。
这就是浅拷贝的限制所在了。它只能拷贝一层对象。如果有对象的嵌套,那么浅拷贝将无能为力。但幸运的是,深拷贝就是为了解决这个问题而生的,它能
解决无限极的对象嵌套问题,实现彻底的拷贝。
下面一起看看实现浅拷贝的几种方式:

Object.assign()
但是需要注意的是,Object.assgin() 拷贝的是对象的属性的引用,而不是对象本身。

let obj = { name: 'sy', age: 18 };
const obj2 = Object.assign({}, obj, {name: 'sss'});
console.log(obj2);//{ name: 'sss', age: 18 }

concat 浅拷贝数组

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);//[ 1, 2, 3 ]

slice浅拷贝

let arr = [1, 2, 3];
let newArr = arr.slice();
newArr[0] = 100;

console.log(arr);//[1, 2, 3]

…展开运算符

let arr = [1, 2, 3];
let newArr = [...arr];//跟arr.slice()是一样的效果

关于vue的问题

1.父子组件的传值方式

(1).父组件向子组件传值
父组件:
<template>
  <div>
    <input v-model="name"/>
    <child :sendmsg="name"></child>
  </div>
</template>
<script>
import child from "./child";
export default {
  components: {
    child
  },
  data () {
    return {
      name:''
    }
  }
}
</script>

<style scoped>
</style>
子组件:
<template>
  <div>
    <span>{{sendmsg}}</span>
  </div>
</template>
<script>
export default {
  props:{
    sendmsg:String,
    require:true
  }
  
}
</script>

<style scoped>
</style>
(2).子组件向父组件传值
子组件:
<template>
  <div>
    <span>{{msg}}</span>
    <input type="button" value="点击我向父组件传值" @click="sendmsg">
  </div>
</template>
<script>
export default {
 data () {
   return {
     msg:'Helloword'
   }
 },
  methods: {
     sendmsg(){
       this.$emit('func',this.msg)
     }
   }
}
</script>

<style scoped>
</style>
父组件:
<template>
  <div>
    <span>{{msg}}</span>
    <child @func="getmsgfromchild"></child>
  </div>
</template>
<script>
import child from "./child"
export default {
  components: {
    child
  },
 data () {
   return {
    
   }
 },
  methods: {
     getmsgfromchild(data){
       this.msg=data
     }
   }
}
</script>

<style scoped>
</style>
(3)vue中ref的作用

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
在这里插入图片描述在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
在这里插入图片描述

(4).vuex 状态管理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上。

4.1 Vuex各个模块

(1)state:用于数据的存储,是store中的唯一数据源

// 定义
new Vuex.Store({
    state: {
        allProducts: []
    }
    //...
})
// 组件中获取
this.$store.state.allProducts

(2)getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算。

// 定义
getters: {
    cartProducts(state, getters, rootState) 
        => (getters.allProducts.filter(p => p.quantity))
}
// 组件中获取
this.$store.getters.cartProducts

(3)mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件(重点!!!)

// 定义
mutations: {
    setProducts (state, products) {
        state.allProducts = products
    }
}

// 组件中使用
this.$store.commit('setProducts', {//..options})

(4)actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作

// 定义(shop为api)
actions: {
    getAllProducts ({ commit }, payload) {
        shop.getProducts((res) => {
            commit('setProducts', res)
        })
    }
}

// 组件中使用
this.$store.dispatch('getAllProducts', {//..payload})

(5)modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

// 定义
const moduleA = {
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
}

const moduleB = {
    state: { ... },
    mutations: { ... },
    actions: { ... }
}

const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    }
})

// 组件中使用
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
4.2 辅助函数

在组件中使用store中的数据或方法时,按照上面的说法,每次都要this.$store.的方式去获取,有没有简单一点的方式呢?辅助函数就是为了解决这个问题

// 组件中注册
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    computed: {
        // 数组形式,当映射的计算属性的名称与 state 的子节点名称相同时使用
        ...mapState(['allProducts'])
        // 对象形式,可重命名 state 子节点名称
        ...mapState({
            products: state => state.allProducts
        })
        // 下面为了简便,均以数组形式使用
        ...mapGetters(['cartProducts'])
    },
    methods: {
        ...mapMutations(['setProducts']),
        ...mapActions(['getAllProducts'])
    }
}

// 组件中使用
// 变量
this.allProducts
this.products
// 方法
this.setProducts()
this.getAllProducts()

附件:store 结构使用如下方式

store
    ├── index.js             # 导出 store 的地方
    ├── state.js             # 根级别的 state
    ├── getters.js           # 二次包装state数据
    ├── actions.js           # 根级别的 action
    ├── mutations.js         # 根级别的 mutation
    ├── mutation-types.js    # 所有 mutation 的常量映射表
    └── modules              # 如果有.
        ├── ...
4.3 vuex 安装

(1)在项目中安装Vuex:

npm install vuex --save

(2)在src目录下新建store/index.js,其中代码如下:

import Vue from 'vue'
import Vuex from 'vuex'
// 修改state时在console打印,便于调试
import createLogger from 'vuex/dist/logger'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

const state = {}
const getters = {}
const mutataions = {}
const actions = {}

export default new Vuex.Store({
    state,
    getters,
    mutataions,
    actions,
    // 严格模式,非法修改state时报错
    strict: debug,
    plugins: debug ? [createLogger()] : []
})

(3)在入口文件main.js中添加:

// ...
import router from './router'
import store from './store'

new Vue({
    el: '#app',
    router,
    store,
    // ...
})

可以对比vue-router和vuex的安装方式:它们均为vue插件,并在实例化组件时引入,在该实例下的所有组件均可由this. r o u t e r 和 t h i s . router和this. routerthis.store的方式查询到对应的插件实例

2.路由跳转的方式

  1. router-link
  2. 不带参数
<router-link :to="{name:'home'}"> 
<router-link :to="{path:'/home'}"> //name,path都行, 建议用name 
// 注意:router-link中链接如果是'/'开始就是从根路由开始,如果开始不带'/',则从当前路由开始。

2.带参数

<router-link :to="{name:'home', params: {id:1}}"> 

// params传参数 (类似post)
// 路由配置 path: "/home/:id" 或者 path: "/home:id" 
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留

// html 取参 $route.params.id
// script 取参 this.$route.params.id


<router-link :to="{name:'home', query: {id:1}}"> 

// query传参数 (类似get,url后面会显示参数)
// 路由可不配置

// html 取参 $route.query.id
// script 取参 this.$route.query.id
 
  1. this.$router.push() (函数里面调用)
  2. 不带参数
this.$router.push('/home')
this.$router.push({name:'home'})
this.$router.push({path:'/home'})
  1. query传参
this.$router.push({name:'home',query: {id:'1'}})
this.$router.push({path:'/home',query: {id:'1'}})

// html 取参 $route.query.id
// script 取参 this.$route.query.id
  1. params传参
this.$router.push({name:'home',params: {id:'1'}}) // 只能用 name

// 路由配置 path: "/home/:id" 或者 path: "/home:id" ,
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留

// html 取参 $route.params.id
// script 取参 this.$route.params.id
  1. query和params区别
    query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在

params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失

  1. this.$router.replace() (用法同上,push)
  2. this. r o u t e r . g o ( n ) ( ) t h i s . router.go(n) () this. router.go(n)()this.router.go(n)
    向前或者向后跳转n个页面,n可为正整数或负整数
    ps : 区别

this. r o u t e r . p u s h 跳 转 到 指 定 u r l 路 径 , 并 想 h i s t o r y 栈 中 添 加 一 个 记 录 , 点 击 后 退 会 返 回 到 上 一 个 页 面 t h i s . router.push 跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面 this. router.pushurlhistory退this.router.replace
跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)

this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数

3.Element UI库的问题

(1)向在dialog弹窗中设置高度,避免内容过多时,弹窗高度太大
<style lang='scss'>
.el-dialog__body{
    height: calc(70vh - 100px);
    overflow: auto;
  }
</style>

注意这个没有加上 scoped 所以要在前面加个div的类名或者id来控制避免全局污染

(2)Element UI中 组件的回车事件要加上.native修饰符
<el-button icon="el-icon-plus" @keyup.enter.native="pressEnter()">
	添加
</el-button>
(3)Element UI中 table组件

在 table 组件中设置了 height 或者 max-height min-height之后,会固定表头,其中height设置auto,表身高度随着内容的变化而变化。

 <el-table
          :data="orderList"
          stripe
          max-height="200px"
          style="width: 100%;">
(4)按需引入 Element UI库

1、借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的:

 npm install babel-plugin-component -D

2、更改.babelrc文件

    "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]

3、在 main.js 中引入需要的组件

import { Select, Option, OptionGroup, Input, Tree, Dialog, Row, Col } from 'element-ui'
Vue.use(Select)
(5)iview 组件中表格本身的单击事件和表格中某个按钮的点击事件一起触发的问题:

解决方法: 我们只需要在按钮的点击事件中加上一行就可以解决这个问题

delectTableData(nowid) {
       event.stopPropagation();
	       this.$Modal.confirm({
	             title: "删除提示",
	             content: "<p>确定要删除吗?</p>",
	             // 确定删除
	             onOk :async () => {
	               const { data: res } = await this.$http.delete(
	                   "xxxx" + id
	               );
	               if (res.code !== 200) {
	                   return this.$Message.error("删除失败");
	               } else {
	                 this.$Message.success("删除成功");
	               }
	             }
	       });
	 },

在删除按钮事件里面添加event.stopPropagation();,来阻止事件冒泡就可以解决了。

4.vue.js中报错: TypeError: Cannot read property ‘indexOf’ of undefined

问题分析:1、可能是变量名写错,导致未定义报错
2、可能是异步的问题,需要用到 async 和 await 去解决
3、最后是网速慢的问题,导致变量未返回,这时候需要结合 watch 和 if(indexOf !== undifined){} ,来进行解决。

5.vue中样式的用法

(1)在标签中写入 :style=" “, 如果” “里面的是一个字符串,假如这个字符串是sty_color,sty_color应该写在data上,并且是一个对象;如果” "里面的是一个对象,那么这个对象的值应该是data上的value。
(2)在标签中写入 :class=" “,” "里面的是一个对象,对象的键是一个类,写在style标签中;对象的值是一个布尔值,用来决定这个类是否应用,这个值可以写在data上面。
	<style>
		.red{
			color:cornflowerblue
		}
	</style>
<body>

	<div id="app">
		<!-- 对象就是无序键值对的集合 -->
		<h1 :style="{color:bluecolor}">这是一个和h1</h1>
		<h1 :style="styleobj1">这是2个和h1</h1>
		<h1 :class="{'red':flag===true}">这是第三个h1</h1>
	</div>
	<script>
		var vm = new Vue({
			el: '#app',
			data: {		
				styleobj1: { color: 'red', 'font-weight': 800},
				styleobj2: {'font-style': 'italic'}	,
				bluecolor:'blue',
				flag:true
			},
			methods: {
				
			}
		});

效果图

6.vue-cli配置项目接口地址

使用 Vue-cli 创建的项目,开发地址是 localhost:8080,需要访问非本机上的接口http://10.1.0.34:8000/queryRole。不同域名之间的访问,需要跨域才能正确请求。跨域的方法很多,通常都需要后台配置,不过 Vue-cli 创建的项目,可以直接利用 Node.js 代理服务器,通过修改vue proxyTable接口实现跨域请求。在vue-cli项目中的config文件夹下的index.js配置文件中。
修改前的dev:

dev: {  
    env: require('./dev.env'),  
    port: 8080,  
    autoOpenBrowser: true,  
    assetsSubDirectory: 'static',  
    assetsPublicPath: '/',  
    proxyTable: {}, 
    cssSourceMap: false  
  }

只要修改里面的proxyTable: {}项:

proxyTable: {  
    '/api': {  //代理地址  
        target: 'http://10.1.0.34:8000/',  //需要代理的地址  
        changeOrigin: true,  //是否跨域  
        secure: false,    
        pathRewrite: {  
            '^/api': '/'   //本身的接口地址没有 '/api' 这种通用前缀,所以要rewrite,如果本身有则去掉  
        }
    }
}

7.vue中的兄弟组件之间的通信在这里插入图片描述

可以直接在项目中的 main.js 初始化 EventBus :

// main.js
Vue.prototype.$EventBus = new Vue()
<template>
    <button @click="increment()">+</button>
</template>

<script> import { EventBus } from "../event-bus.js";
    export default {
        name: "IncrementCount",
        data() {
            return {
                num: 1,
                deg:180
            };
        },
        methods: {
            increment() {
                EventBus.$emit("incremented", {
                    num:this.num,
                    deg:this.deg
                });
            }
        }
    };
 </script>
//在兄弟组件接收值
 mounted() {
            EventBus.$on("incremented", ({num,deg}) => {
                this.fontCount += num
                this.$nextTick(()=>{
                    this.backCount += num
                    this.degValue += deg;
                })
            });
            EventBus.$on("decreased", ({num,deg}) => {
                this.fontCount -= num
                this.$nextTick(()=>{
                    this.backCount -= num
                    this.degValue -= deg;
                })
            });
        }

现在我们已经创建了 EventBus ,接下来你需要做到的就是在你的组件中加载它,并且调用同一个方法,就如你在父子组件中互相传递消息一样。

8.关于 vue 的其他使用:

1、在默认情况下,父组件向子组件传值,假如子组件修改父组件传过来的值,会产生报错。那如何对 prop 进行双向绑定,Vue提供了sync修饰符(即通过 this.emit(‘update:要更新的父传过来的数据’, 更新后的数据, 父组件的要加上修饰符.sync)更新父组件的值,),举个例子:

<!--html代码-->
<div id="app">
  <span>{{title}}</span>
  <text-document v-bind:title.sync="title"></text-document>
</div>
Vue.component('text-document', {
  props: ['title'],
  template: `<button @click="change">change</button>`,
  methods: {
    change () {
      this.$emit('update:title', 'change')
    }
  },
})
new Vue({
  el: '#app',
  data() {
    return {
      title: 'default'
    }
  }
})

2、关于爷爷组件和孙子组件通信的问题
首先这两个组件通信的方法有很多,如 vuex、eventBus、通过儿子组件向上向下传递等等,现在介绍的是一种比较简便的方法:$attrs 和 $listeners。(详细介绍看https://www.jianshu.com/p/ce8ca875c337)

<template>
    <div style="padding:50px;">
        <childcom :name="name" :age="age" :sex="sex"></childcom> // 把要传给子组件、孙子组件的变量写在子组件的属性上
    </div>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data(){
        return {
            'name':'张三',
            'age':'30',
            'sex':'男'
        }
    },
    components:{
        'childcom':{
            template:`<div>
                <div>{{name}}</div>
                <grandcom v-bind="$attrs"></grandcom> 
            </div>`, // 在孙子组件的标签上绑定 $attrs v-bind = "$attrs",这时候 attrs是一个对象,包括了 爷爷组件传下来的变量减去 props 声明了的(在这里就是age,sex 传给了孙子组件)
            props:['name'],
            components: {
                'grandcom':{
                    template:`<div>{{$attrs}}</div>`,
                } // 就是{age: 30, sex: 男}
            }
        }
    }
}
</script>

如果现在孙子组件要改变爷爷组件的方法,(组件传值都是单向流,子孙组件不能直接改变父组件的传过来的值),方法有很多,这里介绍的是 $listeners

<template>
    <div>
        <childcom :name="name" :age="age" :sex="sex" @testChangeName="changeName"></childcom>
    </div>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data(){
        return {
            'name':'张三',
            'age':'30',
            'sex':'男'
        }
    },
    components:{
        'childcom':{
            props:['name'],
            template:`<div>
                <div>我是子组件   {{name}}</div>
                <grandcom v-bind="$attrs" v-on="$listeners"></grandcom>
            </div>`, // 孙子组件加上监听 $listeners (v-on="$listeners"),就可像改变父组件一样改变爷爷组件
           
            components: {
                'grandcom':{
                    template:`<div>我是孙子组件-------<button @click="grandChangeName">改变名字</button></div>`,
                    methods:{
                        grandChangeName(){
                           this.$emit('testChangeName','kkkkkk')
                        }
                    }
                }
            }
        }
    },
    methods:{
        changeName(val){
            this.name = val
        }
    }
}
</script>

最后讲的是 inheritAttrs 属性
在父组件写的子组件的属性在设置子组件设置 inheritAttrs :true 的时候会被子组件的根元素继承下来(有冲突的时候会选用在父组件写的属性),在子组件设置 inheritAttrs :false 的时候,子组件的根元素不会继承 父组件页面写的 子组件标签上的属性。

3、Vue的异常捕获

 // vue的异常捕获, 在异步请求中捕获异常需要 catch(error => {
  // 	this.$throw(error)
  // })
  // 在 main.js 写入异常捕获
 const function errorHandler = (error, vm) => {
 	console.error('抛出异常')
	console.error(vm)
	console.error(error)
}
 Vue.config.errorHandler = errorHandler
Vue.prototype.$throw = function(error){
 	return errorHandler(error, this)
}

关于CSS的问题

1.两个 div 水平摆放,顶部对齐

方法一:当两个 div 设置了 inline-block 之后,两个 div 的底部会对齐,如果想顶部对齐,就要设置两个 div 的vertical-align: top
方法二:只有左侧div设置为 float:left ,右侧div设置 overflow:auto ;,右侧div将会占据整个右侧剩余宽度。同样这两个 div 会顶部对齐 。

2.设置首字母的大写

在 css2中,样式 text-transform ,可以设置首字母大写,全体大写,全体小写。他的参数有:capitalize: 文本中的每个单词以大写字母开头。
uppercase: 仅有大写字母。
lowercase: 无大写字母,仅有小写字母。

	<style>
		.upperCase{
			text-transform: capitalize
		}
	</style>
</head>
<body>
	<span class="upperCase">fsdsd是个v第十九届dvnj+vrv  vnrvu </span>
</body>//Fsdsd是个V第十九届Dvnj+Vrv Vnrvu

3.验证表单控件

这里的验证表单控件仅仅操作css,还不会涉及js。操作方法用到了 HTML5 关于 的新属性 —— pattern( 检查控件值的正则表达式 )。下面举个例子:

input[type="text"]:invalid ~ input[type="submit"] {
    display: none
}

<div class="form-css">
    <input type="text" name="tel" placeholder="输入手机号码" pattern="^1[3456789]\d{9}$" required><br>
    <input type="text" name="smscode" placeholder="输入验证码" pattern="\d{4}" required><br>
    <input type="submit" ></input>
</div>

invalid 伪类和 vaild 伪类
valid 伪类,匹配通过 pattern 验证的元素
invalid 伪类,匹配未通过 pattern 验证的元素

4.布局问题

(1)左右居中

行内元素: text-align: center

定宽块状元素: 左右 margin 值为 auto

不定宽块状元素: table布局,position + transform

/* 方案1 */
.wrap {
  text-align: center
}
.center {
  display: inline;
  /* or */
  /* display: inline-block; */
}
/* 方案2 */
.center {
  width: 100px;
  margin: 0 auto;
}
/* 方案2 */
.wrap {
  position: relative;
}
.center {
  position: absulote;
  left: 50%;
  transform: translateX(-50%);
}

5.less 和 calc 存在冲突的问题

如果直接写

<style lang="less">
  width:calc(100vw - 56px);

会存在问题,甚至会报错。其实他们两者是可以兼容的
解决方法也挺简单,加上~用""包起来。

.class {
  height: calc(~"100% - 50px");
}

可如果要用变量怎么用呢?也不复杂,像下面这样就搞定啦。

.class {
  @cap: 50px;
  height: calc(~"100% - @{cap}");
}

6.border-radius 会被内部的背景颜色覆盖的问题

当大 div (设置了 border-radius)的背景颜色 和内部的 div 的背景颜色不相同时,内部的 div 会覆盖 圆角,这时候需要在大 div 中设置一个 overflow: auto, 即可解决问题。

注意问题

1、不能在数组的循环当中删除数组item,这样会导致数组的下标出错,执行出问题。

2、锁定 npm 依赖包版本的问题,首先,版本号前面的标记意思:^意味着所下载的包有可能会有更高的次版本号(第二位)或者修订版本号(第三位),而~意味着有可能会有更高的修订版本号。

怎么锁定依赖包的依赖包版本的问题:使用npm shrinkwrap命令。会生成npm-shrinkwrap.json文件,和package.lock.json是一模一样。当别的地方或者同事执行 npm i 命令的时候就会按照 npm-shrinkwrap.json文件的指定依赖包来安装。区别是package.lock.json在发布时不会上传到npm仓库中.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值