暂时未有相关云产品技术能力~
最近这几天一直看Vue视频 ,看的黑马的视频,讲的特别杂,既有Vue基础知识,Webpack,VueUi框架.....等等吧,很乱,学起来也特别费力,而且我还不知道,在视频后面的API也过期了(去了官方才知道),只能学到一半不能继续了,是不是特别坑!最近找了一套去哪儿网页APP,这个还讲的可以,今天可算把子组件父组件可搞明白了,以及子组件与父组件之间 的传值,还有零碎的知识点,做个总结,以便忘了,也希望能帮到你。废话不多说,代码走起!什么是子组件?父组件?看了很多文章始终没明白,看了讲解子父组件视频,可算搞懂了.其实很简单,最重要的时它们父与子之间的传值子组件当你创建一个组件时,那个组件名就是子组件var option = Vue.extend({ props:['content','index'], template:'<li @click="sendFather">{{content}}</li>' }) Vue.component('item',option)//这个item就是子组件父组件当你创建一个Vue实例时,el需绑定div,那么这个div就是父组件<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="Vue.js"></script> <title>Document</title> </head> <body> <div id="fade"> <!-- fade就是父组件 --> </div> </body> </html> <script> var ss = new Vue({ el:"#fade" }) </script> 组件的注册第一种方法Vue.component('组件名',{ props://这个位置是为子组件定义属性 // 在子组件中定义data时,data必须是一个函数,返回一个对象 data:function(){ return(){ } } })第二种方法//通过Vue.extend({})创建子组件构造器(extend) 传参, Vue.component('item',option) //然后通过component(组件名,通过extend创建的变量引入 var option = Vue.extend({ props:['content','index'], template:'<li @click="sendFather">{{content}}</li>' }) Vue.component('item',option) 另外,组件创建还有两种创建方式:全局注册(组件可以在页面中任何位置都可以使用)局部注册(组件只能在Vue实例里面使用,不可以在别的Vue实例中使用)template的作用<!-- template 的作用只是起包裹元素,它不会被渲染到页面上去 --> <template v-for="dd of xiaomi"> <h1>?{{dd.name}}-----------?{{dd.prise}}}</h1> </template>动态改变对象数据的方法第一种方法:改变引用<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="Vue.js"></script> <title>Document</title> </head> <body> <div id="fade"> <template > <h1 v-for="list of obj">{{list}}</h1> <!-- 添加一个方法,当点击button时,就会增加一个属性 --> <button @click="add">添加对象属性</button> </template> </div> </body> </html> <script> var ss = new Vue({ el:"#fade", data:{ obj:{ name:"张三", age:20 } }, methods:{ add:function(){ //直接复制原有的基础上,+新的属性 ss.obj={ name:"张三", age:20, hobby:"打?" } } } }) </script>第二种方法:Vue.set(实例.对象,'新增的属性',‘值’)<script> var ss = new Vue({ el:"#fade", data:{ obj:{ name:"张三", age:20 } }, methods:{ add:function(){ //通过这行代码新增一个属性 Vue.set(ss.obj,"hobby","打?") } } }) </script>第三种方法:实例.$set(实例.对象,'新增的属性',‘值’)<script> var ss = new Vue({ el:"#fade", data:{ obj:{ name:"张三", age:20 } }, methods:{ add:function(){ //通过这行代码新增一个属性 ss.$set(ss.obj,"hobby","打?") } } }) </script>动态改变数组数据的方法1.Vue.set(实例,数组,索引,替换内容) 2.实例.$set(实例,数组,索引,替换内容) 3.数组的一些方法pop....... 4.改变引用 子组件向父组件传值1.在子组件种通过this.$emit(A事件)向父组件传递值 2.然后父组件在子组件标签中绑定监听事件A, A事件=“B事件” 3.然后通过在父组件的methods中处理B事件,**获取子组件内容**如何获取子组件内容呢?this.$refs.这个位置是在子组件中定义的ref名.要获取的值r+this.$refs.这个位置是在子组件中定义的ref名.要获取的值; // 求子组件的和 this.count = this.$refs.one.number+this.$refs.two.number;如何获取DOM节点呢?通过在元素身上定义 ref='xxxxx', 然后通过this.$refs.xxxx 获取节点内容this.$refs.ref名.innerHTML获取子组件值this.$refs.ref名.要获取的值组件注意细节有些时候模板渲染会出问题,在table,ul,select中,使用组件时,你可以在便签上 写 is=“组件名”,这样它在页面显示就不会出现问题!<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="Vue.js"></script> <title>Document</title> </head> <body> <div id="fade"> <table > <!-- is 解决子组件在table,ul,select中显示标签的问题 --> <tr is="item"></tr> </table> </div> </body> </html> <script> Vue.component('item',{ template:"<tr><td>Hello</td><td>world</td><tr>" }) var ss = new Vue({ el:"#fade" }) </script>
由于最近学习Vue,它的语法规范大部分使用的ES6的语法,所以来补充补充ES6知识吧,另外现在很多流行的框架都是基于ES6语法开发的,可知ES6的语法重要性。 废话不多说,知识点走起! 变量(let)与常量定义(const)let的定义和使用var b =3; { //b =3 //ReferenceError: b is not defined 暂时性死区 let b; b=4 console.log(b) // 它的结果是4, } console.log(b) // 它的结果是3什么是暂时性死区? 在一个块级作用域中,使用let/const声明变量时,只要变量还没有声明完成前就使用,就会报错,这就时暂时性死区。暂时性死区的作用?它主要是为了减少运行时错误,防止在声明变量未完成之前使用。let不允许重复声明function check(){ let a = 0 var a =0 console.log(a) } check() // 报错 //let不允许在相同作用域内,重复声明同一个变量。 ES6 明确规定,如果区块中存在let 和 const 命令,这个区块对这些命令声明 的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量就会报错。### const定义和使用const Arr = 2; console.log(Arr); // Arr = 3 它会报错,常量不可以更改值,常量在定义并且赋值后,就不可以在修改值了 const Array = [1,3,4]; console.log(Array) Array[3] = 333; console.log(Array) // [1, 3, 4, 333] //Array = [22,55,88]; 它会报错, 变量本身不可以更改,但可以通过索引或这数组方法来改变元素的值 Array.push(555); console.log(Array) //[1, 3, 4, 333, 555]## 变量和对象解构赋值 ### 解构分类: 1.数组解构 2.对象解构 3.字符串解构 4.函数参数解构 什么是解构赋值?ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。// 字符串解构-----------字符串会被转换成一个类型数组的对象,每个对象都有length属性 let {length:len} = 'sayHello' console.log(len)// 会输出sayHello的长度 8 // 数组赋值 let [a,b,c] = [1,2,3] console.log(a,b,c) // 1 2 3 console.log("------------另一种用法--------------") let [d,e,...other] = [1,2,3,4,5,6] console.log(d,e,other)// other是1,2后面的数,他们组成一个新的数组 1 2 [ 3,4 5 6] //对象赋值----------常用于函数传参 let {name,age} = {name:"张三",age:20} console.log(name,age) //张三 20 // 函数赋值 function check(){ return [100,200] } let [A,B] = check(); console.log(A,B) // 100 200 //函数参数名指定 function fun({x=1,y=2}){ return x-y; } console.log(fun({}))//-1 函数不传参数时,它会指定默认值进行计算 console.log(fun({x:22}))//20 解构函数传参不限制传参个数 console.log(fun({x:22,y:88}))//-66 let [ x = 1,bn=4] = [undefined,888] //默认值生效:只要将等会右边对应的设置为undefinded,就会生效 console.log(x+"-------"+bn) // 1 888解构赋值用途?1. 交换变量的值 2. 交换变量的值(将它们放在数组或对象里返回) 3. 函数参数的定义 4. 提取 JSON 数据 5. 函数参数的默认值 6. 遍历 Map 结构进制的转化0b---------二进制 0o---------十进制 0x----------十六进制console.log('10的二进制'+0b10) console.log('10的十进制'+0o10) console.log('10的十六进制'+0x10)let num = 20; console.log(num.toString(8))// 将num转换成8进制 console.log(num.toString(14))// 将num转换成14进制 // 变量数值.toString(数字)-----------数字是指定多少进制的字符串模板let name = '小红'; console.log(`我是${name}`) // 字符串中嵌入变量使用 let str = `你的?爱好是啥了? // 多行输出 打? ` console.log(str)//你的?爱好是啥了? //打? 扩展运算符(...)function long(...arg){ console.log(arg) } long(1,2,3,4,5,6) // 可变长参数定义格式: 在函数参数部分 (...参数) //它可以给函数参数传递多个参数,也可以传递一个值扩展运算符的应用1.复制数组// 以下是错误的复制数组,它只是执向同一份数据的另一个指针,修改a2,a1的值就会发生改变 const a1 = [1, 2]; const a2 = a1; a2[0] = 2; a1 // [2, 2] // 正确的克隆复制数组 // 写法一 //const a2 = [...a1]; // 写法二 const [...a2] = a1; a2[0] = 22; console.log(a1) //[1, 2] console.log(a2) //[22, 2] 2.合并数组const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]## 箭头函数箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种只包含一个表达式,连{ ... }和return都省略掉了。 还有一种可以包含多条语句,这时候就不能省略{ ... }和return:使用格式(参数1, 参数2, …, 参数N) => { 函数声明 } (参数1, 参数2, …, 参数N) => 表达式(单一) //相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; } // 当只有一个参数时,圆括号是可选的: (单一参数) => {函数声明} 单一参数 => {函数声明} // 没有参数的函数应该写成一对圆括号。 () => {函数声明} //加括号的函数体返回对象字面表达式: 参数=> ({foo: bar}) //支持剩余参数和默认参数 (参数1, 参数2, ...rest) => {函数声明} (参数1 = 默认值1,参数2, …, 参数N = 默认值N) => {函数声明} //同样支持参数列表解构 let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f(); // 6// 两个参数 var add = (x,y) =>{ return x+y } console.log(add(6,8)) // 14 //没有参数 var check = ()=>'没有参数' console.log(check()) // 没有参数 //可变参数 var change = (a,b, ...c) =>{ c.forEach(function(val){ console.log(val) }) } change(5,5,6,8,10,55) // 6,8,10,55 //箭头函数也可以使用三元运算符 var arr = [ 9,8,4,2,11] var s = arr.filter(newArr => newArr%2==0?newArr:0) console.log(s) // 箭头函数也可以使用闭包: var Add = (i=0) => {return (() => (++i) )}; var v = Add(); v(); //1 v(); //2 //因为仅有一个返回,return 及括号()也可以省略 var Add = (i=0)=> ()=> (++i);箭头函数的作用:更简短的函数并且不绑定this,并且没有自己的this,arguments,super或 new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。---箭头函数使用注意:1.箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。有关this使用的说明箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this---译者注),他们的第一个参数会被忽略。var adder = { base : 1, add : function(a) { var f = v => v + this.base; return f(a); }, addThruCall: function(a) { var f = v => v + this.base; var b = { base : 2 }; return f.call(b, a); } }; console.log(adder.add(1)); // 输出 2 console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3 ——译者注)3.箭头函数不能用作构造器,和 new一起用会抛出错误。4.箭头函数没有prototype属性。Symbol什么是Symbol?:symbol 是一种基本数据类型, 该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的Symbol格式:Symbol([description]) 参数:可选的字符串。symbol的描述,可用于调试但不能访问symbol本身 // Symbol 作为常量 const Java = Symbol(); let a = Java; if(a === Java){ //true console.log("a 和 java相等") } // Symbol作为属性 let s1 = Symbol('aa') let s2 = Symbol('AK47') // s1 s2 作为对象属性的标记来使用 var arr = {} arr[s1]="nihao" arr[s2]="Hello" console.log(arr) console.log(arr[s1]) console.log(arr[s2]) // 半隐藏属性:特点:知道的人可以使用,不知道的不能使用 const My = Symbol(); class User{ constructor(name,age,key){ this[My] = key; this.name = name; this.age = age; } CheckKey(key) { return this[My] === key; } } let user = new User("老王",20,3333) // 知道有user[My] 属性才可以使用,不知道不能使用, 通过输出对象,就能知道是否有定义的Symbol半隐藏属性】 console.log(user.name+"----"+user.age+"-----"+user[My]); //Object.keys()----会返回一个对象的属性数组 //它不会返回半隐藏属性,因为隐藏了 console.log(Object.keys(user)) //输出对象,可以得知对象的所有属性 console.log(user) console.log(user.CheckKey(222))*Symbol主要用来作为对象属性的标识符*for-of和for-in的区别在ES6之前,迭代元素都使用for-in去遍历,但是它有一个缺点,就是当你遍历数组时,你只想获取数组的值,它会遍历跟数组 相关的一切属性(除了Array的length属性却不包括在内)! ES6的出现,for-of大大解决了这一缺点,它只会获取数组的值。 ES6之前 var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { console.log(x); // '0', '1', '2', 'name' }//2.for ... of循环则完全修复了这些问题,它只循环集合本身的元素: var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x of a) { console.log(x); // 'A', 'B', 'C' }类定义// 不能用类实例化对象,通过 类.方法 就可以访问该方法 class Car{ constructor(name,price){------------构造器 this.name=name; this.price=price; } run(){ console.log(`${this.name}车的价格为${this.price}`) } static action(){---------------------静态方法 // let force = (this.price>100000&this.price<150000)? // '该车的动力功能一般':'该车的动力功能可以'; // console.log(force) if(`${this.price}`<100000){ console.log('该车的动力功能一般') }else{ console.log('该车的动力功能可以') } } } let sedan = new Car('捷达-263',2000); console.log(sedan.name,sedan.price); console.log(sedan); //sedan.action();--------会报错,类实例化对象不可以访问静态方法 sedan.run(); Car.action();setter和getter// setter/getter 功能相当于----------java中(set和get)设置方法中内容 取得方法中内容 class People{ constructor(name){ this.name=name } get age(){ return this._age; } set age(val){ this._age=val; } show(){ console.log(`名字:${this.name},年龄:${this.age}`) } } let A = new People('People'); console.log(A) A.age=22; console.log(A.age) console.log(A)## 类继承 其实JavaScript 和 Java 创建类,继承挺相似的!class Animal{ constructor(name){ this.name = name; } show(){ console.log(`该动物是${this.name}`) } } class Dog extends Animal{ constructor(name,trait){ super(name) this.trait=trait; } type(){ switch(this.trait){ case '吃肉': return '食肉动物' case '吃素': return '食草动物' default: throw new Error('没有该类型动物') } } } let dog = new Dog('哈士奇','吃肉'); dog.show(); console.log(dog.type())Set与Map什么是Set?ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。//Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。 var mySet = new Set(); mySet.add(1); mySet.add("foo"); var a = new Set([11,22,33,44,55]); console.log([...a]) //11,22,33,44,55Set 四个方法add(value):添加某个值,返回Set结构本身 delete(value):删除某个值,返回一个布尔值,表示删除是否成功 has(value):返回一个布尔值,表示该值是否为Set的成员 clear():清除所有成员,没有返回值mySet.size; // 2 mySet.has("foo"); // true //mySet.clear(); mySet.size; // 0 mySet.has("bar") // false // 用forEach迭代 mySet.forEach(function(value) { console.log(value); }); // Set和Array互换 //第一种方法 mySet2 = new Set([1,2,3,4]); mySet2.size; // 4 console.log([...mySet2]) //第二种方法 var myArray = ["value1", "value2", "value3"]; // 用Set构造器将Array转换为Set var mySet = new Set(myArray); mySet.has("value1"); // returns true // 用...(展开操作符)操作符将Set转换为Array console.log([...mySet]); // 与myArray完全一致 //Set对象去重 console.log([...new Set([1,1,2,3,5,88,999,22,88,88,88,1111])])Set 结构有四个遍历方法: 1.keys()返回键名的遍历器 2.values()返回键值的遍历器 3.entries()返回键值对的遍历器 4.forEach()使用回调函数遍历每个成员 let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]什么是Map?ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。 //Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。 let map = new Map(); map.set('name','张三') // 通过set 添加键值对 map.set('age',22) // 一些Map常用方法 console.log(map.size)//-----------map的大小 console.log(map.has("name"))//------------------map对象是否包含name //map.clear()//-----------------------清空map对象 console.log(map.size) //0Map结构有四个遍历方法: 1.keys()返回键名的遍历器 2.values()返回键值的遍历器 3.entries()返回键值对的遍历器 以上3个方法都是通过 for(let of )遍历 4.forEach()使用回调函数遍历每个成员 Map结构转数组结构,通过扩展运算符(...) const map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); [...map.keys()] // [1, 2, 3] [...map.values()] // ['one', 'two', 'three'] [...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']] [...map] // [[1,'one'], [2, 'two'], [3, 'three']]Map转化为数组结构后,它就可以使用数组的方法,可以实现更多的方法,例如数组的过滤filter..........JSON转Map Map转JSONJS模块化以上是关于ES6的知识点,可能不全,但是最常用的,平常遇到不会的知识点,可以去阮一峰 网站去看ES6,特别不错,讲的特别细。接下来,补充一下JS高级知识,开始Vue,做项目?
目前 按照 APP 开发分类,分为以下三大类原生 APP [ Android Swift ]WEB APPHybrid App [混合 APP ]在找工作的当中,很多岗位 要求 会开发 H5 App ,那到底什么是H5 APP 呢?一开始我也有点疑惑,没接触这块,按自己理解 就是 采用 HTML5 技术开发出的页面应用 跑在移动端当中。下面引用 阮一峰对 H5 开发解释。真正理解 H5 开发,需要先搞清楚什么是原生 App、什么是 Web App,因为混合 App 是在它们的基础上诞生的H5 这个词,可以理解成就是混合 App 模型,只不过它特指混合 App 的前端部分。因为混合 App 的前端就是 HTML5 网页,所以简称 H5。这个词是国内独有的,基本上都是前端程序员在用,国外不用这个词,就直接叫混合 App。来对比一下 三种开发模式区别原生APP在这里就不讨论原生APP的优点了,想必大家都知道。主要围绕缺点来说需要 开发 两套 代码 Android 和 IOS旧版本出现 bug 无法更新修改,必须用户 下载 更新发版审核时间长,无法随时更新Web APP优点入门简单,成本低 (前端三件套)可以同步更新可以跨平台缺点不能直接访问设备硬件和离线存储,功能受限( 相机,蓝牙.......)音视频体验不好混合APP优点开发效率高更新和部署方便,不需要审核,只需要在服务器端部署代码维护方便,版本更新快,成本低缺点需要了解 原生开发 才能更好的开发 H5 。需要熟知 原生开发 与 H5 的各种通信和兼容性问题。 什么是 Hybrid AppHybrid App [ 混合 APP] 指 原生 APP 和 WEB APP 的结合体。它主要是以 JavaScript + Native [ APP 原生] 两者结合相互调用使用。混合 App 的原生外壳称为"容器",内部隐藏的浏览器,通常使用系统提供的网页渲染控件(即 WebView 控件),也可以自己内置一个浏览器内核。结构上,混合 App 从上到下分成三层:HTML5 网页层、网页引擎层(本质上是一个隔离的浏览器实例)、容器层。 为什么要采用 Hybrid AppHybrid App 主要是用来优化 原生APP 和 WEB APP 的缺点诞生的新技术,但也有自己的不足。优点跨平台Web 技术是跨平台的,开发者只写一次页面,就能支持多个平台。也就是说,混合 App 只需要一个团队就够了,开发成本较低。灵活性混合 App 的灵活性大,很容易集成多种功能。一方面,混合 App 很容易加载外部的 H5 页面,实现 App 的插件结构;另一方面,Web 页面可以方便地调用外部的 Web 服务。开发方便Web 页面的调试和构建,远比原生控件简单省时。页面的更新也容易,只要在服务器上发布新版本,触发容器内更新就可以了。另外,Web 开发人员也比较容易招聘,传统的前端程序员可以承担开发任务。缺点性能不如 原生 APP , 但相对原生 轻量 。页面跨平台,无法保证多平台统一。需要 前端人员有 原生开发(IOS/Android) 经验,才能完美的上手开发出体验比较好的 混合APP。 什么时候 采用 Hybrid App 应用对于原生性能要求没那么高企业会根据团队前端技术进行选型...... 混合开发任务分配原则业务关联性强的 H5 做H5 和 原生 都能做的,尽量使用 H5 来做H5 做不了的, 原生 做交互性强的 原生 做 [ 体验佳 ] 原生 与 H5 交互原生 与 H5 交互主要是采用 JSBridge它给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)。JSBridge 的功能不止调用 Native 功能这么简单宽泛。实际上,JSBridge 就像其名称中的Bridge的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。双向通信的通道:JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。 最后 相信看到这里的朋友,对于 APP 技术选型 有 大概了解了,每项技术都有优缺点,主要看这项技术是否满足当前项目业务大部分场景,小部分单独优化处理。关于 APP 开发,你有何看法?
前言 年前完工了做了半年的铁路后台管理系统,系统整体业务比较复杂,这也是我到公司从 0 到 1 的 一个完整系统实践,做这个系统过程中踩了不少坑,也学到了很多。 做完这个系统没多久,紧接着又一个系统来了,没及时总结,惭愧哈!其实我们在做的后台管理系统大多数基础框架都一样,后台管理系统 主要的 是 角色权限管理 , 按钮权限管理 和 菜单管理 , 其它的业务主要围绕在这个基础之上进行扩展,最终 构成了 符合业务的后台管理系统. 由于我司的项目都是采用 Vue 技术栈,那么该文章也是讲解 Vue 如何进行权限管理 进行讲解。结尾有彩蛋哦!权限授权登录任何一个后台管理系统都是 首先从登录开始,登录后返回用户基本信息,以及token。token :存入 sessionStronge / localStronge 中,然后加入到 封装好的 Axios 的 请求头中,每次请求携带token.用户基本信息登录成功后同时要做很多事情,具体业务具体对待。后台管理系统 登录成功后会请求当前用户的菜单权限接口,来获取用户的可访问的路由(动态路由),获取成功后, Vue Router 是不能直接使用的,必须得解析成符合 Vue Router 可识别的格式 .登录 handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { this.loading = true; login(this.loginForm) .then(res => { if (res.code === 200) { // 存放token sessionStorage.setItem("tokens", res.data.token); // 触发Vuex 来 加载 获取当前用户的菜单,并解析路由 store.dispatch("setMenuList"); this.$message({ message: "登录成功", type: "success", duration: 1000 }); this.$router.replace({ path: "/dashboard" }); } }) .catch(() => { this.loading = false; }); } else { console.log("error submit!!"); return false; } }); }获取当前用户菜单,解析路由登录成功后,本文通过 Vuex 来获取当前用户菜单和解析路由的。 store.dispatch("setMenuList");/* * @Description: * @Author: ZhangXin * @Date: 2021-02-02 16:10:59 * @LastEditTime: 2021-02-23 23:03:30 * @LastEditors: ZhangXin */ // getMenu 解析后台路由 import { getMenu } from '../../utils/getMenu' // 引入路由 和 静态路由 import router, { constantRoutes } from '../../router/index' const state = { routerType: '', // 菜单路由 meunList: [] } const mutations = { SET_ROUTER_TYPE(state, type) { state.routerType = type }, SET_ROUTER_MENULIST(state, list) { // 静态路由 + 动态路由 合并 完整路由 const array = constantRoutes.concat(list) state.meunList = array router.options.routes = array router.addRoutes([...array]) } } const actions = { setMenuList({ commit, state }) { // 接收返回来的 路由数组 return new Promise((resolve, reject) => { getMenu().then(res => { commit('SET_ROUTER_TYPE', '') commit('SET_ROUTER_MENULIST', res) resolve(res) }) }) } } export default { state, mutations, actions }解析后端返回来路由(重点)封装好的解析后端返回来的路由,这块主要是为了在 Vuex 中使用。/* * @Description: * @Author: ZhangXin * @Date: 2021-02-02 16:03:48 * @LastEditTime: 2021-02-23 23:09:02 * @LastEditors: ZhangXin */ import Layout from '@/layout' import {getUserAuthMenu} from '@/api/user' /** * @description: 解析后端返回来的菜单树 * @param {*} data 后端返回来的路由树 * @param {*} arr 菜单 * @return {*} */ function tree(data, arr) { data.forEach((datas, index) => { arr.push({ path: datas.path, name: datas.name, types: datas.types, hidden: datas.hidden == 'true' ? true : false, // 当时这块踩坑了 component: datas.component === 'Layout' ? Layout : resolve => require([`@/views/${datas.component}.vue`], resolve), meta: { title: datas.meta.title, icon: datas.meta.icon, // 用来存放按钮权限 button: datas.meta.button }, // redirect: datas.redirect, id: datas.id, // 子路由 children: [] }) if (datas.children) { const childArr = tree(datas.children, []) arr[index].children = childArr } }) return arr } /** * @description: 获取当前登录用户的菜单 * @param {*} * @return {*} */ export function getMenu() { return new Promise(function (resolve, reject) { getUserAuthMenu().then(res => { if(res.code === 200){ const datas = res.data // 调用 tree 来解析后端返回来的树 resolve(tree(datas, [])) } }) }) } 后端接收路由格式前端接收到的真实菜单树页面刷新,路由丢失到此为止,已经实现了 Vue 动态权限控制 ,别高兴的太早,哈哈,一刷新页面,页面就进入了 404 页面 。这是为什么呢 ?因为存入 Vuex 中的数据,一刷新页面,就会清空,那么当然找不到当前路由,就进入 404 页面了 .如何处理呢?一、 可以 将 静态和 动态 构成的完整路由 存放在 sessionStronge / localStronge 中,然后页面刷新时,通过在 全局入口文件 App.vue 的 生命周期 created 中 ,将 router = sessionStronge / localStronge 存入的完整的路由,页面在刷新时,它会重新加载完整的路由。 二、如果是使用Vuex来获取和解析用户菜单的话, 那么你可以在全局入口文件 App.vue 的 生命周期 created 中 ,再次执行 Vuex Action 来重新加载用户菜单我这块直接在 App.vue 的 生命周期 created 中 , 再次执行了 Vuex 来进行加载和解析,没有做其它操作。 当然了,具体业务具体对待。<template> <div id="app"> <router-view v-if="isRouterAlive" /> </div> </template> <script> import store from "@/store"; export default { name: "App", provide() { return { reload: this.reload }; }, data() { return { isRouterAlive: true }; }, methods: { reload() { this.isRouterAlive = false; this.$nextTick(() => (this.isRouterAlive = true)); } }, created() { //只要刷新页面,就会重新加载路由树,保证了路由不会丢失数据 store.dispatch("setMenuList"); } }; </script> 总结核心思想1.定义符合 当前项目业务路由格式,前后端按这个接收传递2.前端解析后端返回的动态路由,生成Vue Router 可识别格式,最后拼接完整路由3.刷新路由丢失处理<br/>按钮权限控制1.当前组件 路由 携带可使用的 按钮权限,存入数组中,通过v-if 来判断是否显示2.登录时,单独获取整个系统的按钮权限,将获取到的所有按钮 存入一个数组中,放入全局中,然后,通过 v-if 来判断是否显示3. ............
写在前面的话❝ 网上已经有很多不错 有关HTTP 文章, 此文为记录学习HTTP 最近在项目开发中,对于HTTP 这块比较懵,来补补基础吧! 学习HTTP的同时,有必要了解下TCP/IP协议族。 通常使用的网络都是在TCP / IP 协议族的基础上运作,而且HTTP 属于TCP/IP的一个子集。 ❞TCP / IPTCP/IP 协议族❝ 不同硬件与操作系统之间通信有需要有一定规则来约束,这样可以统一沟通的条件。T TCP / IP 是互联网相关各协议族的总称,它不只是 单纯的 指 TCP 和 IP协议, 它有 FTP , SNMP , HTTP , PPPoE........ 组成的 协议族。 ❞TCP/ IP 分层❝ TCP / IP 协议族 按层次分为 4 层: 应用层 , 传输层 , 网络层, 数据链路层 ❞应用层❝ 作用: 应用层决定了向用户提供应用服务时通信的活动。 TCP / IP 协议族内预存了各类通用的应用服务。 例如: FTP(文件传输协议) DNS(域名系统)HTTP ❞传输层❝ 作用: 两台计算机之间的数据传输,服务于应用层。 在传输层有两个性质不同的协议: TCP(传输控制协议) UDP(用户数据协议) ❞网路层❝ 作用: 用来处理在网络上流动的数据包,以何种方式传递给对方。 数据包是网络传输的最小数据单位。 网络层 决定了 通过什么样的方式把数据包传递给对方。 网络层 最重要的功能是: 路由数据包。它会根据网络上路由IP, 网络拥塞情况,选择一条最合适的路由路线,以最快的速度将数据包传递给对方。 ❞数据链路层❝ 作用: 用来处理连接网络的硬件部分。 硬件上的范畴均在链路层的作用范围之内 ❞TCP / IP 通信传输流❝ 采用 TCP / IP 协议进行网络通信时, 客户端(发送端) 会从应用层 往下走, 一层 一层的传输, 每进入一层,被被打上标记序号以及端口号, 当客户端 走完 自己的 4 层时, 会进入 服务端(接收端) ,它是按 链路层 然后 一层一层 往上走, 每进入一层, 取消一个 标记序号, 直到顶层(应用层),这时, 服务端才接收到了 客户端 向 服务端 发送的请求。 服务端 向 客户端 发送请求,和之前的顺序相反。 服务端(应用层----------> 数据链路层) 客户端(数据链路层 ---------------> 应用层) ❞HTTP 相关的协议负责传输的IP协议❝ IP : 它是网际协议,它位于网络层。 作用: 把各种数据包传递给对方。 要保证可以 把数据包准确的传递给对方,有两个条件: IP 地址: 它标明了要传递到对方的 ip 地址。 MAC 地址: 它是指网卡的固定地址。 IP 和 MAC 是验证 一个身份的 关键 IP 地址 可以 和 MAC 地址 进行匹配,IP 地址可以更换,但MAC 地址基本不会更换。 计算机于计算机之间通信时,它不会直接查找到对方IP的,中间过程会经历 设备中转,在进行中转的同时,它 会 采用 ARP 协议 来 推出 IP 地址的 MAC 地址,经过多次中转最终找到对方的 MAC 地址, MAC 地址 于 IP 地址匹配, 成功的建立了连接。 ARP : 它是一用来解析地址的协议。根据对方IP地址可以反查出对应的MAC 地址 ❞确保可靠性TCP 协议❝ TCP 位于传输层, 提供可靠的字节流服务。 字节流服务 : 为了方便传输,它会将大块数据分割成 以报文段为单位的数据进行管理。 为了可靠的安全的可以传递给对方数据, TCP 采用 三次握手策略, SYN 同步序列编号 ACK 确认字符 第一次握手 : 发送端会发送一个带有 SYN 标志的数据包给对方。 第二次握手 : 对方(接收端)收到发送端 发送过来的 SYN 包, 回传一个带有 SYN / ACK 标志的包 来标识接收成功。 第三次握手 : 发送端 回传一个 ACK 包 表示 握手接收 ❞TCP 为何要三次握手呢?❝ 第一次握手: 客户端 向 服务端 发送请求 第二次握手 : 服务端 接收 客户端发来的请求,进行处理。 前 两次握手 已经实现 请求处理过程,那么为什么还是需要 建立第三次握手呢? 如果不进行 第三次握手 , 假如,第一次握手 , 客户端由于网络原因,请求延迟发送, 你以为 这样就结束了请求,但网络节点正常后,它还会发送这个请求,这是一个早已失效的报文;第二次握手,服务端 接收到 客户端发送过来的 延迟请求,但此时客户端没有发送任何数据,而服务端还在等候 客户端, 造成了资源浪费。 第三次握手是为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误。 ❞让三次握握手 更生动❝ 在恋爱关系中,就是 确定 一下, A 是否 为 单相思, 如果不三次握手, B 早已和别的男朋友........ 而 A 还在 苦苦 等待 B 的接受。 干嘛不三次握手嘛 A 向 B 表白了; B 收到 A 的 表白了, B 要回应 A ,我不喜欢你 A 收到 了 B 回应, 让他 脱离了 单相思 ❞负责域名解析DNS 服务❝ DNS 服务 和HTTP 协议 都位于应用层协议。 作用 : 提供域名 到 IP 地址解析服务。 DNS 协议提供通过域名查找IP 地址, 也可以通过IP 地址反查 域名 ❞HTTP 与 各种协议之间的关系❝ HTTP 与上面各个协议之间 串联起来 理解各个协议个作用。 ❞图片来源 HTTP图解URI 与 URL什么是URI URL❝ URI 统一资源标识符。 URI 是 Uniform Resource Identifier 的缩写 Uniform 统一格式 Resource 可标识资源 Idetifier 可标识对象,标识符 URI 用字符串 标识 互联网资源, 而URL 表示资源地点。 URL 统一资源定位符, 这个相信大家已经很了解了。 ❞URI 格式❝ 到现在,我们了解了TCP / IP 协议族 的组成以及分层,还有最重要的 三次握手,做一个不再 单相思的小男孩;还有有关HTTP 相关知识。 这只是刚开始,路漫漫其修远兮吾将上下而求索. 写此文,为记录学习 HTTP, 大手请手下留情。 ❞ 原创不易,觉得不错分享点赞
HTTP 定义HTTP(超文本传输协议) 是 客户端 与 服务端 之间信息交流的 桥梁。在信息交流之前必须要做的就是 客户端通过连接TCP/IP协议 80 端口 ,以便 服务端侦听HTTP请求。3.HTTP 是 一种通用的 , 无状态的应用层协议,基于标准客户机/服务器模型。HTTP 特点1.采用 “请求/响应”的交互模式, 客户端发送请求,服务端接受请求,处理请求,并将处理结果返回给客户端。服务端不会主动发送请求。<br/>2.协议设计灵活,拓展性好,HTTP可以通过扩展新的请求方法实现新的功能。<br/>3.无状态: 协议对于事务处理没有存储功能,意思就是如果上次响应的结果在该请求中需要用,那么是用不了的。<br/><br/>缺点:<br/>每次连接的数量增大。<br/><br/> 优点:<br/>1.服务器处理速度快,效率高<br/> 2.避免0了集群特点间状态同步的开销。<br/><br/>4.持久连接: 连接可以重复使用,提高了网络连接使用效率。 持久连接 在HTTP1.1中已 经是默认选项。5.支持内容协商HTTP 请求/响应交互模型HTTP 常用请求方法GET 方法1.GET 方法 是 客户端 向服务端 获取资源时使用的,资源类型有图片,音频,HTML.....<br/>2.服务器在处理GET请求时,它会根据客户端发送过来的url上具体参数进行返回结果处理。<br/>3.当用GET请求获取数据量较大时,可能会出现传输过程中断情况,HTTP协议提供了断点续传机制,通过GET 方法获取资源时可以指定获取的起始点。<br/>POST 方法1.POST 方法主要是 客户端向服务端发送数据资源。<br/><br/>2.POST 和 GET 方法区别:POST 请求会包含信息体,信息体中携带了要发送给服务端的数据。HEAD 方法HEAD 方法 和 GET 方法 POST方法类似<br/>区别在于:<br/> GET方法返回的请求URL标识资源内容本身<br/> HEAD方法仅仅返回相关响应头信息,不返回资源内容<br/><br/>3.HEAD 方法 主要用于 测试资源是否存在,是否被删除或修改PUT 方法PUT方法用请求有效载荷替换目标资源的所有当前表示。DELETEDELETE方法删除指定的资源。HTTP URIURI1.定义URI,通一资源标志符(Uniform Resource Identifier, URI),表示的是web上每一种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个URI进行定位的。 2.URI的结构组成:①访问资源的命名机制;②存放资源的主机名;③资源自身的名称。3.实例https://xxx.xxx.com/details/1①这是一个可以通过https协议访问的资源,②位于主机 xxx.xxx.com上,③通过“/details/1”可以对该资源进行唯一标识(注意,这个不一定是完整的路径)URI 构成URL 统一资源定位符统一资源名称URL1.定义URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位 符”。 2.URL的一般格式为(带方括号[]的为可选项):protocol :// hostname[:port] / path / ;parameters#fragment3.URL的格式由三部分组成: ①第一部分是协议(或称为服务方式)。②第二部分是存有该资源的主机IP地址(有时也包括端口号)。③第三部分是主机资源的具体地址,如目录和文件名等。第一部分和第二部分用“://”符号隔开,第二部分和第三部分用“/”符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略。URL 和 URI 区别URI:统一资源标志符(Uniform Resource Identifier)URL:统一资源定位符(uniform resource location) 说白了,URI与URL都是定位资源位置的,就是表示这个资源的位置信息,就像经纬度一样可以表示你在世界的哪个角落。URI是一种宽泛的含义更广的定义,而URL则是URI的一个子集,就是说URL是URI的一部分。 换句话说,每个URL都是URI,但是不是每个URI都是URL的。 HTTP 发送请求HTTP 响应请求HTTP 状态码100Continue继续。客户端应继续其请求101Switching Protocols切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议200OK请求成功。一般用于GET与POST请求201Created已创建。成功请求并创建了新的资源202Accepted已接受。已经接受请求,但未处理完成203Non-Authoritative Information非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档205Reset Content重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域206Partial Content部分内容。服务器成功处理了部分GET请求300Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI303See Other查看其它地址。与301类似。使用GET和POST请求查看304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源305Use Proxy使用代理。所请求的资源必须通过代理访问306Unused已经被废弃的HTTP状态码307Temporary Redirect临时重定向。与302类似。使用GET请求重定向400Bad Request客户端请求的语法错误,服务器无法理解401Unauthorized请求要求用户的身份认证402Payment Required保留,将来使用403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面405Method Not Allowed客户端请求中的方法被禁止406Not Acceptable服务器无法根据客户端请求的内容特性完成请求407Proxy Authentication Required请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权408Request Time-out服务器等待客户端发送的请求时间过长,超时409Conflict服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突410Gone客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置411Length Required服务器无法处理客户端发送的不带Content-Length的请求信息412Precondition Failed客户端请求信息的先决条件错误413Request Entity Too Large由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息414Request-URI Too Large请求的URI过长(URI通常为网址),服务器无法处理415Unsupported Media Type服务器无法处理请求附带的媒体格式416Requested range not satisfiable客户端请求的范围无效417Expectation Failed服务器无法满足Expect的请求头信息422Conflict表明由于所提供的的作为请求部分的数据非法,创建或修改操作不能被完成429TooManyRequests表明超出了客户端访问频率的限制或者服务端接收到多于它能处理的请求。建议客户端读取相应的Retry-After 首部,然后等待该首部指出的时间后重试。500Internal Server Error服务器内部错误,无法完成请求501Not Implemented服务器不支持请求的功能,无法完成请求502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中504Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求505HTTP Version not supported服务器不支持请求的HTTP协议的版本HTTP 状态码分类1** ------------------------------------> 信息,服务器收到请求,需要请求者继续执行2** ------------------------------------> 成功,操作被成功接收并处理3** ------------------------------------> 重定向,需要进一步的操作以完成请求4** ------------------------------------> 客户端错误,请求包含语法错误或无法完成请求5** ------------------------------------> 服务器错误,服务器在处理请求的过程中发生了错误彩蛋环节结语❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章
目前 按照 APP 开发分类,分为以下三大类原生 APP [ Android Swift ]WEB APPHybrid App [混合 APP ]在找工作的当中,很多岗位 要求 会开发 H5 App ,那到底什么是H5 APP 呢?一开始我也有点疑惑,没接触这块,按自己理解 就是 采用 HTML5 技术开发出的页面应用 跑在移动端当中。下面引用 阮一峰对 H5 开发解释。真正理解 H5 开发,需要先搞清楚什么是原生 App、什么是 Web App,因为混合 App 是在它们的基础上诞生的H5 这个词,可以理解成就是混合 App 模型,只不过它特指混合 App 的前端部分。因为混合 App 的前端就是 HTML5 网页,所以简称 H5。这个词是国内独有的,基本上都是前端程序员在用,国外不用这个词,就直接叫混合 App。来对比一下 三种开发模式区别原生APP在这里就不讨论原生APP的优点了,想必大家都知道。主要围绕缺点来说需要 开发 两套 代码 Android 和 IOS旧版本出现 bug 无法更新修改,必须用户 下载 更新发版审核时间长,无法随时更新Web APP优点入门简单,成本低 (前端三件套)可以同步更新可以跨平台缺点不能直接访问设备硬件和离线存储,功能受限( 相机,蓝牙.......)音视频体验不好混合APP优点开发效率高更新和部署方便,不需要审核,只需要在服务器端部署代码维护方便,版本更新快,成本低缺点需要了解 原生开发 才能更好的开发 H5 。需要熟知 原生开发 与 H5 的各种通信和兼容性问题。 什么是 Hybrid AppHybrid App [ 混合 APP] 指 原生 APP 和 WEB APP 的结合体。它主要是以 JavaScript + Native [ APP 原生] 两者结合相互调用使用。混合 App 的原生外壳称为"容器",内部隐藏的浏览器,通常使用系统提供的网页渲染控件(即 WebView 控件),也可以自己内置一个浏览器内核。结构上,混合 App 从上到下分成三层:HTML5 网页层、网页引擎层(本质上是一个隔离的浏览器实例)、容器层。 为什么要采用 Hybrid AppHybrid App 主要是用来优化 原生APP 和 WEB APP 的缺点诞生的新技术,但也有自己的不足。优点跨平台Web 技术是跨平台的,开发者只写一次页面,就能支持多个平台。也就是说,混合 App 只需要一个团队就够了,开发成本较低。灵活性混合 App 的灵活性大,很容易集成多种功能。一方面,混合 App 很容易加载外部的 H5 页面,实现 App 的插件结构;另一方面,Web 页面可以方便地调用外部的 Web 服务。开发方便Web 页面的调试和构建,远比原生控件简单省时。页面的更新也容易,只要在服务器上发布新版本,触发容器内更新就可以了。另外,Web 开发人员也比较容易招聘,传统的前端程序员可以承担开发任务。缺点性能不如 原生 APP , 但相对原生 轻量 。页面跨平台,无法保证多平台统一。需要 前端人员有 原生开发(IOS/Android) 经验,才能完美的上手开发出体验比较好的 混合APP。 什么时候 采用 Hybrid App 应用对于原生性能要求没那么高企业会根据团队前端技术进行选型...... 混合开发任务分配原则业务关联性强的 H5 做H5 和 原生 都能做的,尽量使用 H5 来做H5 做不了的, 原生 做交互性强的 原生 做 [ 体验佳 ] 原生 与 H5 交互原生 与 H5 交互主要是采用 JSBridge它给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)。JSBridge 的功能不止调用 Native 功能这么简单宽泛。实际上,JSBridge 就像其名称中的Bridge的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。双向通信的通道:JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。 最后 相信看到这里的朋友,对于 APP 技术选型 有 大概了解了,每项技术都有优缺点,主要看这项技术是否满足当前项目业务大部分场景,小部分单独优化处理。关于 APP 开发,你有何看法?
JavaScript 设计模式 之旅设计模式开篇 日常开发中,我们都很注重开发技巧,好的开发 技巧可以事半功倍得解决此刻的问题。 那么这些技巧如何来得呢? 我的理解: 经过不断踩坑,解BUG,总结出来一些处理对应问题解决方案,这就所谓的 技巧。 说起设计模式,其实我们日常开始中也经常用到,只是你不知道用的解决方案方案对应的设计模式名称. 学习设计模式的作用 在软件设计中,模式是一些经过了大量实际项目验证的优秀解决方案。熟悉这些模式的程序员,对某些模式的理解也会自然的形成条件反射。当遇到合适的场景出现时,可以快速找到对应的模式来处理当前的问题。 单例模式 定义: 保证 类 仅有 一个实例,并可以全局访问这个实例. 全局变量 不是 单例模式,但是在JavaScript 中, 我们经常把单例模式当作全局变量使用。 因为它满足单例模式的两点: 创建的全局变量是独一无二的它可以全局访问这个变量实例// login.js var loginInfo = { username: '', token: '', ....... } //login.vue import logins from './login.js' logins.name = this.username 但是它也有缺点,容易造成命名空间污染。 定义的全局变量多了, 会覆盖掉之前定义的全局变量,这样会造成不必要的BUG. 如何处理命名空间污染呢? 如何处理呢? 1.使用命名空间2.使用闭包封装私有变量命名空间 对象自变量的形式: // login.js export default var loginInfo = { names:'' , token: '', setName: function (name) { this.names = name }, getName: function () { return this.name } } //login.vue import logins from './login.js' logins.token = this.token 使用闭包封装私有变量 把一些变量封装在闭包内部,只暴露一些接口跟外界通信。 外界是访问不到 内部定义的私有变量的,这样就避免了全局命令污染。 var user = (function () { //外界是访问不了 _name _age var _name = '张三', _age = 22; return { //这块留给外界通信用 getUserInfo: function () { console.log(`姓名为:${_name},年龄为:${_age}`) } } })() user.getUserInfo() 通用的惰性单例模式 在该执行的情况下,执行操作步骤 / DOM. 优点: 节约了性能。 场景1 有时候,例如登陆弹窗,在加载首页的同时,它会渲染这个页的全部DOM,如果首页DOM 内容多,加载速度也会相应的很慢,有很多不需要DOM提前渲染。 这时,可以通过惰性单例模式来解决此问题,例如单击了登陆按钮,才会创建登陆弹窗的DOM,并且记录此次点击状态,如果下次还要打开,只是更改 DOM 的 style 的 display 属性即可。 这样节约了首页加载时间,提升页面性能。 // 定义全局通用单例模式 var getSingle = function (fn) { var result; return function () { return result || (result = fn.apply(this, arguments)); } } // 创建登陆窗口 var createLoginLayer = function() { var div = document.createElement('div') div.innerHTML = '登陆框' div.style.display = 'none' document.body.appendChild(div) return div } // 创建单例模式 登陆框 var createSingleLoginLayer = getSingle(createLoginLayer); document.getElementById('btn').onclick = () => { // 获取单例模式中 返回得登陆框 var loginLayer = createSingleLoginLayer(); // 改变样式 loginLayer.style.display = 'block' } 场景2 创建唯一的 iframe 用于加载第三方页面 var createSingleIframe = getSingle( function() { var iframe = document.createElement('iframe'); document.body.appendChild(iframe) return iframe }) document.getElementById('redirect').onclick = () => { var iframeLayer = createSingleIframe(); iframeLayer.src = 'http://www.baidu.com' } 参考资料 <> 本文内容如有错的地方,欢迎各位大佬指正,学习了
前言金九银十,现在正是跳槽的季节,我给大家带来一篇面试常考 Vue 路由守卫 文章来助你拿到 offer,觉得不错的欢迎转发! Vue Router 路由守卫导图目录路由守卫分类全局路由守卫单个路由守卫组件路由守卫路由守卫执行的完整过程<hr/>路由守卫分类全局路由单个路由独享组件内部路由每个路由守卫的钩子函数都有 3 个参数: to : 进入的目标路由 from : 离开的路由next : 控制路由 在跳转时进行的操作,一定要执行。它有 4 个行为: next() : 钩子都执行完了,进入到下一个路由当中。 next(false): 中断路由进入下一个路由。 next('/') : 根据你路由跳转判断条件来进入对应的路由, / 为路由的 path 。 next(new Error) : 中断路由跳转,错误会被传递给 router.onError() 注册过的回调。全局路由守卫 beforeEach(to,from, next) beforeResolve(to,from, next) afterEach(to,from)全局路由直接挂载到 router 实例上。//全局验证路由 const router = createRouter({ history: createWebHashHistory(), routes }); // 白名单, 不需要验证的路由 const whiteList = ['/','/register'] router.beforeEach((to,from,next)=>{ if(whiteList.indexOf(to.path) === 0) { // 放行,进入下一个路由 next() } else if(!(sessionStorage.getItem('token'))){ next('/'); } else { next() } })beforeEach使用场景路由跳转前触发作用常用于登录验证 beforeResolve使用场景在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。 afterEach使用场景发生在beforeEach和beforeResolve之后,beforeRouteEnter之前。路由在触发后执行单个路由独享它只有一个 钩子函数, beforeEnter(to,from,next) beforeEnter使用场景在beforeEach之后执行,和它功能一样 ,不怎么常用 { path:'/superior', component: Superior, meta:{ icon:'el-icon-s-check', title:'上级文件' }, beforeEnter:(to,form,next) =>{ } }组件路由守卫特点:组件内执行的钩子函数钩子函数: beforeRouteEnter(to,from,next) beforeRouteUpdate(to,from,next) beforeRouteLeave(to,from,next) beforeRouteEnter使用场景:路由进入之前调用。不能获取组件 this 实例 ,因为路由在进入组件之前,组件实例还没有被创建。执行顺序 beforeEach 和独享守卫 beforeEnter之后,全局 beforeResolve和全局afterEach之前调用. beforeRouteUpdate使用场景:在当前路由改变时,并且该组件被复用时调用,可以通过this访问实例。当前路由query变更时,该守卫会被调用。 beforeRouteLeave使用场景:导航离开该组件的对应路由时调用,可以访问组件实例this路由守卫执行的完整过程导航被触发执行 组件内部路由守卫: beforeRouteLeave执行 全局路由守卫 beforeEach 在重用组件内部路由守卫钩子 beforeRouteUpdate执行 路由中的钩子 beforeEnter在被激活的组件里调用 beforeRouteEnter执行 全局的 beforeResolve 守卫 。执行 全局的 afterEach 钩子beforeCreatecreatedbeforeMountmounted执行 beforeRouteEnter的next的回调 ,创建好的组件实例会作为回调函数的参数传入。结语❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章金
前言聊聊为何要学习TypeScript?从开发角度来讲, TypeScript 作为强类型语言,对属性有类型约束。在日常开发中少了减少了不必要的因参数类型造成的BUG,当你在使用同事封装好的函数时,类型不知道怎么传递,在开发TS 时,它会有很友好类型的提示,此时你就知道该函数需要传递哪些参数,并且参数类型是什么类型。从项目结构来讲, 使用TypeScript 可以很好的管控项目,通过建立各个模块的类型管理,通过interface 或者 类型断言 来管理项目中类型,在对应的模块使用类型,当项目需要迭代更新时,只需要进行对对应的模块类型进行更改。从前端全局观来讲,社区三大框架早已结合TypeScript 了, 社区也很完善,已经过了 该不该学习TypeScript 的阶段了。去年还有很多朋友犹豫用不用学习TypeScript , 很多被社区朋友发的文章误导, TypeScript 就是 AnyScript。TypeScript 对没有接触过 强类型语言的朋友有点学习成本,而且在使用TypeScript 进行开发时,前期可能代码会很臃肿,让你觉得看起来有很多 无用的代码 , 不过在后期,你就可以感觉到 TypeScript 给你带来的乐趣了。学会 TypeScript 也对你在职场竞争有点优势,在跳槽时,假如你已经使用 TypeScript 结合 框架 做过一些项目,面试官也会优先考虑你的,薪水从而也提升了。前端之路还有很长,新的技术/框架更新频率很快, 最终还是离不开 JavaScript。下面,我们一起来看看 TypeScript,本文是对标 TypeScript 文档进行来讲解,更加大白话的简单讲述如何使用TypeScript .入手导图TypeScript一,安装环境#npm install -g typescript1.1 VSCode 配置自动编译文件#1. 在目录下 tsc --init 自动生成 tsconfig.json tsconfig.json 下 outdir 是输出的路径 #2. 任务--- 运行任务 监视 tsconfig.json 二,基本语法2.1 数组定义使用// 第一种定义方法 let 数组名:类型[] = [] var arr:number[] = [1,2,3]; console.log(arr); // 第二种定义方法 let 数组名:Array[类型] = [] var newArr:Array<number> = [1,2,3]; console.log(newArr)2.2 元组它表示 已经 元素的个数和元素类型的数组,各个元素类型可以不一样。访问元组长度 和 元素var strArr:[number,string,boolean] = [22,'测试',false] console.log(strArr.length) console.log(strArr[0]) #它只能按类型的优先顺序输入内容,否则报错2.3 枚举 enumenum类型是对JavaScript标准数据类型的一个补充。如果没有给枚举指定索引的话,默认为 0 , 通过 枚举对象[索引] 可以获取值如果指定了枚举索引为字符串的话,通过 枚举.属性 获取的 它的值enum Sex {Man,Woman} let m:Sex = Sex.Man; console.log(m) //0 let w: string = Sex[1] console.log(w) //Woman enum Animal {Dog = 3, Cat, Tiger}; console.log(Animal[5]) //Tiger enum info {student = '学生', teacher = '教师', parent = '家长' }; console.log(info.teacher) //教师2.4 任意类型 anyany 为 任意类型,一般在获取dom 使用// 任意类型 const newArrs:any = ['测试不同数据 ',222,false] console.log(newArrs) # 输出结果为[ '测试不同数据 ', 222, false ] # 使用场景: 当你不知道类型 或 一个对象 或数据 需要多个类型时,使用any2.5 undefined 类型let num:number | undefined ; console.log(num) // 输出 undefined, 但是不会报错 let newNum:number | undefined = 33; console.log(newNum) // 输出 33 2.6 never 类型never 代表不存在的值类型,常用作为 抛出异常或者 无限循环的函数返回类型# 应用场景 #1. 抛错误 const errDate = (message:string): never => { throw new Error(message) } #2. 死循环 const date_for = (): never => { while(true) {} } # never 类型是任意类型的子类型,没有类型是never 的子类型 别的类型不能赋值给never类型, 而 never 类型可以赋值给任意类型2.7 void 类型void 为 函数没有类型,一般用在没有返回值的函数# 如果方法类型为number, 则必须返回内容, 内容且必须为数字 function add():number{ return 2323; } # 如果方法类型为void,不需要返回内容 function getAdd():void{ console.log('测试') } # 如果方法类型为any,则可以返回任意类型 function getAny():any{ return 999 + 'Hello TypeScript' } console.log(getAny())//999 'Hello TypeScript'三,类型断言什么是类型断言?有时候你在定义一个变量时,起初是不知道是什么类型,但在使用过程中知道是什么类型,这时就会用到类型断言了。3.1第一种写法 尖括号const str = '测试' const resLength : number = (<string>str).length3.2第二种写法 asconst str = '测试' const resLength : number = (str as string).length四,接口TypeScript的核心原则之一是对值所具有的结构进行类型检查。验证类型时,顺序不影响验证。简单的来说,它是类型约束的定义,当你使用这个定义接口时,它会一一匹对接口中定义的类型。只要不满足接口中的任何一个属性,都不会通过的。4.1 接口可选属性有时候,接口属性不是必须全部需要的,满足某些条件才会需要,这时,就可以采用可选属性格式 : 属性 ?: 类型interface Login{ userName: string, password: string, auth ?: string } function getLogin(obj: Login) { if(obj.auth == '管理员') { console.log('可以查看所有菜单') } else { console.log('您的权限比较低,目前不能查看') } } getLogin({ userName:'zhangsanfeng', password: '12121121sd', auth: '管理员' }) //可以查看所有菜单 getLogin({ userName:'zhangsanfeng', password: '12121121sd' }) //您的权限比较低,目前不能查看 4.2 接口 只读属性只读属性: 意味着给属性赋值了后,不可改变。格式: readonly 属性 : 类型 interface Menus { readonly title?:string, icon?:string, readonly path?:string, readonly Layout?:string } function getMenuInfo(data:Menus){ console.log(data) data.icon = '修改图标' // 可以修改 // data.path = '/home' 报错,禁止修改,接口属性为只读 console.log(data) } getMenuInfo({ title: '主页', icon:'homes', path:'/home', Layout: 'Layput' }) 4.3 接口函数类型用来约束函数传递参数类型函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。格式: (参数1: 类型,参数2:类型) : 返回值类型 // 获取用户信息 interface UserInfo { (name: string,adress: string,phone: number) : string } let getUsefInfo:UserInfo = function(name,adress,phone){ return `${name}住在${adress},手机号为${phone}` } console.log(getUsefInfo('张锋','天津南开区xx小区',188888888))4.4 接口可索引类型在定义一个数组时,可以定义一个 索引类型接口,这样就约束了它必须传递哪些类型的值。访问: 通过 变量[索引]interface Code{ [index : number] : string } let errCode : Code = ['200 成功','301 重定向', '400 客户端错误', '500 服务端出错'] console.log(errCode[3]) //500 服务端出错4.5 类型接口实现接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。interface Animals { eye: number, leg: number, } class Dog implements Animals { eye: number; leg: number; kind: string constructor(eye: number, leg: number, kind: string) { this.eye = eye this.leg = leg this.kind = kind } getDogInfo(){ console.log(`品种为${this.kind},有${this.eye}只眼,${this.leg}条腿`) } } let hashiqi = new Dog(2,4,'哈士奇'); hashiqi.getDogInfo() //品种为哈士奇,有2只眼,4条腿4.6 接口继承(多合一)接口之间可以互相继承,这样可以更灵活地将接口分割到可重用的模块里。interface Shape1 { data: string } interface Shape2 extends Shape1{ code: number // Shape2 具有 Shape1 的特征 } class Message implements Shape2 { code: number; data: string; constructor(code : number,data: string) { this.code = code; this.data = data } getCodeInfo(){ console.log(`状态码为${this.code},返回信息为${this.data}`) } } let err = new Message(500,'服务端错误') err.getCodeInfo() //状态码为500,返回信息为服务端错误4.7 接口继承类当接口继承了一个类,那么接口也会拥有类的属性和方法。当别的类 实现这个 接口时,会同时实现 接口的属性和方法, 继承类的属性和方法class Log { time: string = '2020-11-2'; getLog(){ console.log('测试') } } interface Shape3 extends Log{ message : string } class ErrLog implements Shape3 { message: string ; time: string; constructor(message: string, time: string) { this.message = message; this.time = time } getLog(): void { console.log("Method not implemented."); } } let errs = new ErrLog('测试','2020-11-2') errs.getLog() //Method not implemented. 五,泛型接触过JAVA 的同学,应该对这个不陌生,非常熟了。作为前端的我们,可能第一 次听这个概念。 通过 字面意思可以看出,它指代的类型比较广泛。作用: : 避免重复代码,代码冗余但是它和 any 类型 还是有区别的。any 类型: 如果一个函数类型为any,那么它的参数可以是任意类型,一般传入的类型与返回的类型应该是相同的。如果传入了一个 string 类型的参数,那么我们也不知道它返回啥类型。泛型 : 它可以使 返回类型 和 传入类型 保持一致,这样我们可以清楚的知道函数返回的类型为什么类型。5.1 泛型接口泛型接口可以这样理解: 当你需要给接口指定类型时,但目前不知道属性类型为什么时,就可以采用泛型接口你可以给接口指定参数为多个泛型类型,也可以单个;当使用时,明确参数类型即可。 interface User <T,S,Y> { name: T; hobby: S; age: Y; } class People implements User<String,String,Number> { name: String; hobby: String; age: Number; constructor(name:string,hobby:string,age:number){ this.name = name; this.hobby = hobby; this.age = age; } getInfo(){ console.log(this.name+"------------------"+this.hobby) console.log(`${this.name}的年龄为${this.age}`) } } let xiaoZhou = new People('小周','敲代码',22) xiaoZhou.getInfo() //小周------------------敲代码 // 小周的年龄为22 5.2 泛型函数定义泛型函数,可以让 传入参数类型参数 和 返回值类型保持一致。泛型 标志一般用字母大写,T 可以随意更换格式 : 函数名<T> (参数1:T) : 返回值类型 Tfunction genericity<T> (data: T) : T { console.log(data) return data } genericity("测试") genericity(666) genericity(['前端','后端','云端']) 5.3 泛型类什么是泛型类它规定了类中属性和方法的 类型,而且必须和类型定义的类型保持一致。泛型类的作用可以帮助我们确认类的所有属性都在使用相同的类型使用格式class 类名<T> { name!: T; hobby!: T; } # 这样这个类的所有类型为 number let 实例 = new 类名<number>(); class GenericityA<X>{ sex!: X; age!: X; } let gen = new GenericityA<number>(); // gen.sex = '测试' 报错 gen.age = 3 console.log(gen.age)5.4 泛型约束接口约束通过定义接口, 泛型函数继承接口,则参数必须实现接口中的属性,这样就达到了泛型函数的约束类约束通过给类的泛型指定为另一个类,这样就规定了类泛型的类型都为另一个类# 第一种 // 定义接口 interface DataInfo{ title: string, price: number } // 泛型函数 继承接口,进行对参数类型约束, 如果传入的参数中,没有包含接口属性,则编译不通过 function getDataInfos< T extends DataInfo> (obj: T) : T { return obj } let book = { title: '前端进阶', price: 50, author: '小新' } console.log(getDataInfos(book)) //{ title: '前端进阶', price: 50, author: '小新' }# 第二种 // 通过类来约束 class Login{ username: string; password: string; constructor(username: string,password:string){ this.username = username this.password = password } } class Mysql<T>{ login<T>(info:T):T{ return info } } let x = new Login('admin','12345'); let mysql = new Mysql<Login>(); console.log(mysql.login(x)) //Login { username: 'admin', password: '12345' }<hr/>六,类 Class说到类,做后端的朋友应该都了解,前端 在ES6 中,才出现了 类 Class 这个关键词。Class 有哪些特征属性构造器方法继承 extends属性 / 方法 修饰符静态属性抽象类存取器 getters/setters6.1 修饰符public 共有的当属性 / 方法 修饰符为 public 时, 如果前面没有,默认会加上,我们可以自由的访问程序里定义的成员。class Fruit { public name: string; price: number; // 以上为等价 constructor(name: string, price: number) { this.name = name; this.price = price } getFruitInfo(){ console.log(`您要购买的水果为${name},价格为${this.price}`) } }private 私有的当成员被标记成 private时,它就不能在声明它的类的外部访问。class Fruit { public name: string; private price: number; // 以上为等价 constructor(name: string, price: number) { this.name = name; this.price = price } getFruitInfo(){ console.log(`您要购买的水果为${name},价格为${this.price}`) } } const apple = new Fruit('苹果',22) // console.log(apple.price) 报错, 实例不可以访问私有属性 protected 受保护的protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问,不可以通过实例来访问受保护的属性。class A { protected name : string; protected age : number; constructor(name: string , age: number) { this.name = name; this.age = age } getA(){ console.log('A') } } class B extends A { protected job : string; constructor(name: string, job: string,age: number) { super(name,age) this.job = job } getB(){ console.log(`B 姓名为${this.name} && 年龄为${this.age} && 职业为${this.job},`) } } let b = new B('小飞','前端工程师',22) b.getA() //A b.getB() //B 姓名为小飞 && 年龄为22 && 职业为前端工程师, // console.log(b.name) 报错,访问不了,protected成员只能在派生类中可以访问,不能通过实例来访问。6.2 静态属性类的静态成员(属性 和 方法) 只能通过 类来可以访问。定义: static 属性 / static 方法class Food { public name: string; private price: number; static adress: string = '四川'; // 以上为等价 constructor(name: string, price: number) { this.name = name; this.price = price } getFruitInfo(){ console.log(`您要购买的东西为${name},价格为${this.price}`) } } const spicy = new Food('辣条',3) console.log(Food.adress) //四川 // console.log(spicy.adress) 报错 类的实例对象不可以访问 类的静态属性。 只可以通过类.属性来访问 6.3 继承 extend继承的本意很好理解,当子类继承了父类,那么子类就拥有了父类的特征(属性) 和 行为(方法),class T { name:string; constructor(name:string){ this.name = name } getNames(){ console.log('继承类T') } } class S extends T { constructor(name:string){ // 派生类拥有T属性和方法 super(name) } getName(){ console.log(this.name) } } let ss = new S('测试继承') ss.getName() ss.getNames() // 测试继承 // 继承类T6.4 抽象类抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。abstract class E{ abstract name: string; abstract speak():void; abstract play():void; } class F implements E { name: string; constructor(name:string){ this.name = name } // 派生类 F 必须实现 抽象类E 的方法和属性 speak(): void { console.log('具有聊天功能') } play(): void { console.log('具有娱乐功能') } get(){ console.log(this.name) } } let f = new F('测试'); f.play() //具有娱乐功能 f.get() // 测试 f.speak() //具有聊天功能七,TS 中的函数函数类型包括 参数类型 和 返回值类型7.1 函数添加返回值类型每个参数添加类型之后再为函数本身添加返回值类型.TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。下面会介绍在TS 中,两种写函数的格式// 第一种写法 let getInterFaceInfo : (obj:object) => void = function(obj){ console.log(obj) } let infos: object = { code: 200, message: '发送成功' } getInterFaceInfo(infos) // 第二种写法 function getCode(code: number, message:string) : void { console.log(`code为${code},message为${message}`) } getCode(200,'接受成功') 7.2 函数可选参数 / 默认参数JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。在TypeScript里我们可以在参数名旁使用 ?实现可选参数的功能。可选参数必须放在必须参数后面。格式 : 函数名(变量名?:类型):类型 {} 默认参数,在传递参数时,指定默认值格式 : 函数名(变量名 :类型 = "xx"):类型 {} // 可选参数 function getNetWork(ip:string,domain:string,agreement?:string){ console.log(`ip地址为:${ip},域名为${domain},协议为${agreement}`) } getNetWork('127.0.0.1','www.xiaomi.com') //ip地址为:127.0.0.1,域名为www.xiaomi.com,协议为undefined // 默认参数 function getNetWorks(ip:string,domain:string,agreement:string = 'http'){ console.log(`ip地址为:${ip},域名为${domain},协议为${agreement}`) } getNetWorks('127.0.0.1','www.xiaomi.com') //ip地址为:127.0.0.1,域名为www.xiaomi.com,协议为http 7.3 函数剩余参数有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在JavaScript里,你可以使用 arguments来访问所有传入的参数。在TypeScript 中,可以把所有参数集中在一个变量中,前面加上... 表示 剩余参数。注意直接通过变量访问也可以通过索引访问只能定义一个剩余参数,且位置在 默认参数和可选参数后面function getNumberInfo(num:number,...peopleArray: string []) { console.log(`人员个数为${num},成员为${peopleArray}`) // 也可以通过索引来获取元素 console.log(`人员个数为${num},成员为${peopleArray[1]}`) } getNumberInfo(4,'小明','小李','小红','小张') //人员个数为4,成员为小明,小李,小红,小张 //人员个数为4,成员为小李<hr/>八,枚举枚举可以清晰地表达一组对应关系。TypeScript支持数字的和基于字符串的枚举。8.1 数字枚举默认枚举的顺序以 0 开头,然后自动递增。枚举顺序也可以指定 值, 指定后,它前面第一个还是以0 递增访问通过 枚举名.属性 访问到的是 序号通过 枚举名[序号] 访问到的是 属性名enum Sex { x, man = 4, woman } console.log(Sex.x) //0 console.log(`小红的性别为${Sex[5]}`) //小红的性别为woman console.log(`后端接受小红的性别ID ${Sex.woman}`) //后端接受小红的性别ID 58.2 字符串枚举enum Job { frontEnd = '前端', backEnd = '后端' } console.log(Job) //{ frontEnd: '前端', backEnd: '后端' } 九,高级类型9.1 交叉类型它指 可以将多个类型合并为一个类型。标识符为 & , 当指定一个变量类型为 交叉类型时,那么它拥有交叉类型的所有属性,也就是并集。interface DonInterface { run():void; } interface CatInterface { jump():void; } //这里的pet将两个类型合并,所以pet必须保护两个类型所定义的方法 let pet : DonInterface & CatInterface = { run:function(){}, jump:function(){} } 9.2 联合类型联合类型表示一个值可以是几种类型之一。用竖线( |)分隔每个类型。一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。 function getMenus(info: string | number) { console.log(info) } getMenus("测试") getMenus(2) // getMenus(false) 报错十,模块模块: 定义的变量,函数,类等等,只能在自身的作用域里使用。 如果想在外部访问使用,那么必须使用export 将其导出即可。使用模块: 通过 import 将模块内容导入即可使用。模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。10.导出10.1 导出声明任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出。导出可以对任何声明 进行重命名,防止命名冲突, 通过 as 来修改# 模块A 文件 // 导出接口 export interface A { getList() : void } // 导出变量 export const GET_METHOD = "get" // 导出类 export class S implements A { getList(): void { console.log("导出类") } } function getQueryData():void { console.log("获取分页数据") } // 导出模块 变量重命名 export { getQueryData as getQuery} # 文件B import {getQuery,S,A} from './模块A'; // 使用模块中的函数 getQuery() // 实例模块中类的对象 const a = new S(); a.getList() // 输出导出类 // 实现模块中的 A 接口 class Y implements A { getList(): void { throw new Error('Method not implemented.'); } } 10.2 组合模块使用通常一个大的模块是多个子模块组成的。那么我们可以通过 在大的模块中导入多个子模块。格式: export * from "模块" 使用组合模块: import * as 重命名变量 from ‘组合模块路径’ # 模块C // 导出变量 export const GET_METHOD = "get"# 模块B export const str: string = "B模块" export function getContent():void{ console.log("我是模块B的内容") }#组合模块 const res : object = { code: 200, message: "请求成功" } export function getRes(): void { console.log(res) } # 导出子模块 export * from "./modulesC" export * from "./moduleB"10.3 使用组合模块import * as T from "./modulesA"; // C 模块中的 console.log(T.GET_METHOD) // B 模块中的内容 console.log(T.str) //B模块 T.getContent() //我是模块B的内容 // A 模块中的内容 T.getRes() //{ code: 200, message: '请求成功' } 10.4 默认导出每个模块都可以有一个default导出。 默认导出使用 default关键字标记;并且一个模块只能够有一个default导出。#模块 export interface K { name:string; birth:string; } export default class Student implements K { name: string; birth: string; constructor(name:string,birth:string){ this.name = name; this.birth = birth; } getStudentInfo(){ console.log(this.name+this.birth) } }#文件A import D,{K} from './modulesD' // 使用默认导出 const d = new D('小明','1998') d.getStudentInfo() // 参数类型为接口K function getMessage(obj: K): void { console.log(obj) } let obj = { name:"小红", birth: "1998" } getMessage(obj);10.5 export = 和 import = require()CommonJS和AMD的环境里都有一个exports变量,这个变量包含了一个模块的所有导出内容。CommonJS和AMD的exports都可以被赋值为一个对象 exports 和 export default 用途一样,但是 export default 语法不能兼容CommonJS和AMD的exports。在TypeScript 中,为了达到这样效果,可以这样写:导出: export = 等于 exports 导入: import module = require("module")# 模块 // 相当于默认导出 export = class Mongodbs{ host:string; user:string; password:string; port:number; databaseName:string; constructor(host:string,user:string,password:string,port:number,databaseName:string) { this.host = host; this.user = user; this.password = password; this.port = port; this.databaseName = databaseName } query(table:string){ console.log(`select * from ${table}`) } } #使用模块 import MogoDb = require("./modulesE") const mogodb = new MogoDb('1270.0.1','admin','123456',3006,'TypeScript') mogodb.query('Vue') //select * from Vue 十一, 命名空间定义“内部模块”称为“命名空间”“外部模块”称为“模块”作用减少命名冲突,将代码组织到一个空间内,便于访问。使用格式通过 namespace 空间名 { } ,内部通过 export 导出来使用内部成员namespace XiaoXin { export interface GetData{ name: string; price: number; getInfo(obj:object):any; } export interface GetMessage { code: number; message: string; } export class Book implements GetData{ name: string; price: number; constructor(name:string,price:number){ this.name = name; this.price = price } getInfo(obj: object) { throw new Error("Method not implemented."); } buyBook(obj: GetMessage) { console.log(obj) } } } const fontEnd = new XiaoXin.Book("前端开发手册",99) var obj = { code: 200, message:"购买成功" } fontEnd.buyBook(obj) //{ code: 200, message: '购买成功' } function test(obj:XiaoXin.GetMessage){ console.log(obj) } test(obj) //{ code: 200, message: '购买成功' } 11.1 拆分命名空间当应用变得越来越大时,我们需要将代码分离到不同的文件中以便于维护。我们可以将命名空间文件拆分成多个文件,但是它们的命名空间名还是使用的同一个,各个文件相互依赖使用。但是必须文件最开头引入 命名空间文件。格式: /// <reference path="MS1.ts"/> # 根命名空间 namespace School { export const schoolName = "清华大学" }# 子命名空间1 /// <reference path="MS1.ts" /> namespace School{ export class Teacher { faculty:string; name:string; age:number; constructor(faculty:string,name:string,age:number){ this.faculty = faculty; this.name = name; this.age = age } getInfo(){ console.log(`${this.name}为${this.faculty},年龄为${this.age}`) } getSchool(schoole:string){ console.log(`${this.name}老师就职于${schoole}`) } } }# 子命名空间2 /// <reference path="MS1.ts" /> namespace School{ export class Student{ name:string; age:number; hobby:string; constructor(name:string,age:number,hobby:string) { this.name = name; this.age = age; this.hobby = hobby; } getInfo(){ console.log(`${this.name}是一个学生,年龄为${this.age},爱好是${this.hobby}`) } } } # 使用合并的命名空间 导入命名空间 /// <reference path="MS1.ts" /> /// <reference path="MS2.ts" /> /// <reference path="MS4.ts" /> let teacher = new School.Teacher('计算机教授','张博士',34); teacher.getInfo() //张博士为计算机教授,年龄为34 teacher.getSchool(School.schoolName) //张博士老师就职于清华大学 let students = new School.Student('张三',17,'玩LOL'); students.getInfo() //张三是一个学生,年龄为17,爱好是玩LOL编译命名空间文件第一种方法: 会编译为 一个js文件 tsc --outFile sample.js Test.ts 第二种方法: 会编译为多个js文件,然后通过 <script> src 引入js文件即可 tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts 十二,装饰器装饰器是一种特殊类型的声明,它能够附加到类声明、方法、访问符、属性、类方法的参数上,以达到扩展类的行为。自从 ES2015 引入 class,当我们需要在多个不同的类之间共享或者扩展一些方法或行为的时候,代码会变得错综复杂,极其不优雅,这也是装饰器被提出的一个很重要的原因。12.1 修饰器分类类装饰器属性装饰器方法装饰器参数装饰器修饰器写法: 1. 普通修饰器 (不传参数) 2. 装饰器工厂 (传参数)12.2 类装饰器类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。使用场景:应用于类构造函数,可以用来监视,修改或替换类定义。const extension = (constructor: Function):any => { constructor.prototype.coreHour = '10:00-15:00' constructor.prototype.meeting = () => { console.log('重载:Daily meeting!'); } } @extension class Employee { public name!: string public department!: string constructor(name: string, department: string) { this.name = name this.department = department } meeting() { console.log('Every Monday!') } } let e: any = new Employee('装饰器', '测试') console.log(e) //Employee { name: '装饰器', department: '测试' } console.log(e.coreHour) // 10:00-15:00 e.meeting() // 重载:Daily meeting! 12.3 类属性装饰器作用于类属性的装饰器表达式会在运行时当作函数被调用,传入下列3个参数 target、name、descriptor:target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象name: 成员的名字descriptor: 成员的属性描述符执行顺序: 当调用有装饰器的函数时,会先执行装饰器,后再执行函数。通过修饰器完成一个属性只读功能,其实就是修改数据描述符中的 writable 的值 :function readonly(value: boolean){ return function(target:any,name:string,descriptor:PropertyDescriptor) { descriptor.writable = value } } class Student{ name:string; school:string = '社会大学' constructor(name:string) { this.name = name } @readonly(false) getDataInfo(){ console.log(`${this.name}毕业于${this.school}`) } } let sss = new Student('小李子') // 报错, 只能读,不能修改 // sss.getDataInfo = () => { // console.log("测试修改") // } sss.getDataInfo()十三,TS + Vue 环境搭建13.1 升级Vue-cli1. 先 卸载旧的版本 npm uninstall -g @vue/cli #or yarn global remove @vue/cli 2.安装最新版本 npm install -g @vue/cli # OR yarn global add @vue/cli 13.2 创建项目vue create projectname 项目名必须小写13.3 关闭变量未声明报错打开 package.json ,修改rules 内容,改为如下: "rules": { "no-unused-vars": 0 } 这样就关闭了更多精彩文章在主页最后文中如有错误,欢迎码友在评论区指正,如果对你有所帮助,欢迎点赞和关注~~~
前言我们在日常开发中,与接口打交道最多了,前端通过访问后端接口,然后将接口数据二次处理渲染到页面当中。 二次处理的过程是 考验 Coder 对 Array 是否熟练 以及 在 何种 场景下使用哪种方法处理最优 。小编,在最近开发中就遇到了 Array 问题, 在处理复杂的业务需求时,没想到Array 有类似的方法,然后将方法 组合起来解决当下问题。文章用下班时间肝了一周才写完,看完点赞、转发 是对我最大的支持。遍历数组方法不会改变原数组的遍历方法forEach()forEach() 方法按照升序为数组中每一项执行一次给定的函数。语法arr.forEach(callback(currentValue , index , array) ,thisArg)currentValue : 数组当前项值index : 数组当前项索引arr : 数组对象本身thisArg : 可选参数。当执行回调函数 callback 时,用作 this 的值。注意如果使用 箭头函数表达式来传入函数参数, thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。every 不会改变原数组。//防盗贴:微信公众号: 前端自学社区 const arr = [2,3,4,1,44] arr.forEach(val =>{ console.log(`值为${val*2}`) }) console.log(`原数组为${arr}`); // 值为4 // 值为6 // 值为8 // 值为2 // 值为88 // 原数组为2,3,4,1,44reduce()reduce()数组元素累计器,返回一个合并的结果值。语法arr.reduce(callback(accumulator, currentValue, index, array), initialValue)accumulator : 累计器,默认为数组元素第一个值currentValue : 当前值index : 当前元素索引 可选array : 数组 可选initialValue : 初始值 可选reduce 有两个参数,一个是回调函数,一个是初始值。它有两种取值情况:当提供了 initialValue 初始值时, 那么accumulator 的值为 initialValue , currentValue 的值为 数组第一个值当没有提供 initialValue 初始值时, 那么 accumulator 的值 为 数组第一个值, currentValue 为第二个值。注意如果数组为空,且没有提供initialValue 初始值时,会抛出 TypeError .如果数组有一个元素,且没有提供initialValue 或者 提供了initialValue ,数组为空,那么唯一值被返回不会执行 callback 回调函数。求和//防盗贴:微信公众号: 前端自学社区/ const arr = [1, 2, 3, 4] const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 10) console.log(sum) //20 // accumulator 累计器 // currentValue 当前值 // initialValue 累计 初始值 为10 //10 + 1 + 2 + 3 + 4 ## 注意 // 回调函数第一次执行时,accumulator 和currentValue的取值有两种情况: // 如果调用reduce()时提供了initialValue,accumulator取值为initialValue,currentValue取数组中的第一个值; // 如果没有提供 initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值。 计算对象中的值要累加对象数组中包含的值,必须提供初始值,以便各个item正确通过你的函数。/* * @Description: * @Author: 微信公众号: 前端自学社区 * @Date: 2021-08-07 00:53:51 * @LastEditTime: 2021-08-07 00:53:51 * @LastEditors: Do not edit */ const data = [ { date: '2021-8-1', income: 200 }, { date: '2021-8-2', income: 400 }, { date: '2021-8-3', income: 300 }, ] console.log(`总收入: ${data.reduce( (pre,currentValue) => pre + currentValue.income,0)}`); //总收入: 900二维数组转一位数组const array = [[1,2],[3,4]] console.log(array.reduce((a,b) => a.concat(b))); //[ 1, 2, 3, 4 ]find()find() 返回满足特定条件的元素对象或者元素值, 不满足返回 undefined 语法arr.find((element,index,array), thisArg)element : 当前元素index : 当前元素索引 可选array : 数组本身 可选thisArg : 执行回调时用作this 的对象。 可选// 从数据中找出第一个满足特定条件的对象 const data = [ { name:'张三', article: 3 }, { name:'老王', article: 9 }, { name:'老李', article: 10 } ] console.log(data.find(item => item.article > 9 )); // { name: '老李', article: 10 }findIndex()findIndex() 返回数组中符合条件的第一个元素的索引,没有,则返回 -1 。语法arr.findIndex((element,index,array), thisArg)element : 当前元素index : 当前元素索引 可选array : 数组本身 可选thisArg : 执行回调时用作this 的对象。 可选const arr = [22,33,44,55] console.log(arr.findIndex(val => val > 33)); //2 console.log(arr.findIndex(val => val > 99)); //-1key()key() 返回一个新的Array Iterator对象,该对象包含数组中每个索引的键。语法keys()注意如果数组中有空原元素,在获取key 时, 也会加入遍历的队列中。const inputModal = [ { name:'' }, { age:'' }, { hobby:'' } ] for(const key of inputModal.keys()){ console.log(key) } // 0 // 1 // 2 const arr = [1,2,,3] for(const key of arr.keys()){ console.log(key); } // 0 // 1 // 2 // 3 //Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组 // 所以 Object.keys(arr) = [ '0', '1', '3' ] for(const key of Object.keys(arr)){ console.log(key); } // 0 // 1 // 3values()values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值。语法arr.values()const Color = ['red','yelloe','orange'] for(val of Color.values()){ console.log(val); } // red // yelloe // orange<hr/>返回 布尔值every()every 用来判断数组内所有元素是否符合某个条件,返回 布尔值语法arr.every(callback(currentValue , index , array) ,thisArg)currentValue : 数组当前项值 必须index : 数组当前项索引 可选arr : 数组对象本身可选thisArg : 可选参数。当执行回调函数 callback 时,用作 this 的值。可选注意当所有的元素都符合条件才会返回trueevery 不会改变原数组。若传入一个空数组,无论如何都会返回 true。//防盗贴:微信公众号: 前端自学社区 const arr = [2,3,4,1,44] console.log(arr.every(val => val > 0 )); //true console.log(arr.every(val => { val > 2 })) //false some()some() 用来判断数组元素是否符合某个条件,只要有一个元素符合,那么返回 true.语法arr.some(callback(currentValue , index , array) ,thisArg)currentValue : 数组当前项值 必须index : 数组当前项索引 可选arr : 数组对象本身可选thisArg : 可选参数。当执行回调函数 callback 时,用作 this 的值。可选注意some() 被调用时不会改变数组。如果用一个空数组进行测试,在任何情况下它返回的都是false。some() 在遍历时,元素范围已经确定,在遍历过程中添加的元素,不会加入到遍历的序列中。const arr = [2,3,4,1,44] console.log(arr.some(val => val > 2)) //true console.log([].some(val => val > 2 )); //false const newList = [11,22,33,44] console.log(newList.some(val => { newList.push(55) newList.push(66) val > 55 })); //false不改变原有数组,形成新的数组filter()filter() 用来遍历原数组,过滤拿到符合条件的数组元素,形成新的数组元素。语法arr.some(callback(currentValue , index , array) ,thisArg)currentValue : 数组当前项值 必须index : 数组当前项索引 可选arr : 数组对象本身可选thisArg : 可选参数。当执行回调函数 callback 时,用作 this 的值。可选注意filter 不会改变原数组,它返回过滤后的新数组。filter() 在遍历时,元素范围已经确定,在遍历过程中添加的元素,不会加入到遍历的序列中。const arr = [11,22,33,44,55,66] console.log(arr.filter(val => val > 44 )) console.log(`原数组为${arr}`); // [ 55, 66 ] // 原数组为11,22,33,44,55,66map()map() 创建一个新的数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。语法arr.map(callback(currentValue , index , array) ,thisArg)currentValue : 数组当前项值 必须index : 数组当前项索引 可选arr : 数组对象本身可选thisArg : 可选参数。当执行回调函数 callback 时,用作 this 的值。可选注意map 不修改调用它的原数组本身map() 在遍历时,元素范围已经确定,在遍历过程中添加的元素,不会加入到遍历的序列中。const arr = [1,2,3,4] console.log(arr.map(val => val*3 )) // [ 3, 6, 9, 12 ] console.log(arr) // [ 1, 2, 3, 4 ]<hr/>数组 CRUD改变原数组方法reverse()reverse() 方法将数组中元素的位置颠倒,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。该方法会改变原数组。const arr = [1,2,3] console.log(arr.reverse(11,22,33)) //[ 3, 2, 1 ]sort()sort() 方法采用 原地算法进行排序并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列原地算法是一个使用辅助的数据结构对输入进行转换的算法。但是,它允许有少量额外的存储空间来储存辅助变量。当算法运行时,输入通常会被输出覆盖。原地算法仅通过替换或交换元素来更新输入序列。const arr = [23,11,33,44,1] console.log(arr.sort()) //[ 1, 11, 23, 33, 44 ] const arr = [23,11,33,44,1000000000] console.log(arr.sort()) // [ 1000000000, 11, 23, 33, 44 ]删除元素shift()shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。语法arr.shift()注意从数组中删除的元素; 如果数组为空则返回undefined const data = [ { id:1, name:'前端' }, { id:2, name:'后端' }, { id:3, name:'移动端' }, { id:4, name:'嵌入式开发' }, ] const deleObj = data.shift() console.log('==============删除后的元素======================'); console.log(data); console.log('=================删除后的元素==================='); console.log('===============被删除的元素====================='); console.log(deleObj); console.log('================被删除的元素===================='); // ==============删除后的元素====================== // [ // { id: 2, name: '后端' }, // { id: 3, name: '移动端' }, // { id: 4, name: '嵌入式开发' } // ] // =================删除后的元素=================== // ===============被删除的元素===================== // { id: 1, name: '前端' } // ================被删除的元素====================pop()pop()方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。用法和 shift 类似。语法arr.pop()注意从数组中删除的元素; 如果数组为空则返回undefined const data = [ { id:1, name:'前端' }, { id:2, name:'后端' }, { id:3, name:'移动端' }, { id:4, name:'嵌入式开发' }, ] const deleObj = data.pop() console.log(data); // [ // { id: 1, name: '前端' }, // { id: 2, name: '后端' }, // { id: 3, name: '移动端' } // ] console.log(deleObj); // { id: 4, name: '嵌入式开发' }splice()splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。语法array.splice(start,deleteCount, [item1,item2....])start : 开始的索引deleteCount : 删除的个数 可选[item1,item2 .....] ;从开始的索引进行 添加的增加和替换的元素, 可选注意由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。如果只传递了开始的索引位置,则会删除索引后的所有元素对象const data = [ { id:1, name:'前端' }, { id:2, name:'后端' }, { id:3, name:'移动端' }, { id:4, name:'嵌入式开发' }, ] data.splice(1) console.log(data) // [ { id: 1, name: '前端' } ]从索引为 2 开始, 删除 1 个数组元素对象,添加两个数组元素对象const data = [ { id:1, name:'前端' }, { id:2, name:'后端' }, { id:3, name:'移动端' }, { id:4, name:'嵌入式开发' }, ] data.splice(2,1,...[{id:5,name:'人工智能'},{id:6,name:'大数据开发'}]) console.log(data); // [ // { id: 1, name: '前端' }, // { id: 2, name: '后端' }, // { id: 5, name: '人工智能' }, // { id: 6, name: '大数据开发' }, // { id: 4, name: '嵌入式开发' } // ]增加元素splice()上面已经有介绍push()push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。语法arr.push(element1, ..., elementN)const data = [ { id:1, name:'前端' }, { id:2, name:'后端' }, ] console.log(data.push({id:3,name:'移动端'})) //3合并数组const data = [ { id:1, name:'前端' }, { id:2, name:'后端' }, ] var obj = [ { id:4, name:'嵌入式开发' }, ] // 相当于 data.push({id:4,name:'嵌入式开发'}); Array.prototype.push.apply(data, obj); console.log(data); [ { id: 1, name: '前端' }, { id: 2, name: '后端' }, { id: 4, name: '嵌入式开发' } ]unshift()unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度。const arr = [1,2,3] console.log(arr.unshift(11,22,33)) //6 console.log(arr) //[ 11, 22, 33, 1, 2, 3 ]不改变原数组元素方法indexOf()indexOf()方法返回可以在数组中找到给定元素的第一个索引,如果不存在,则返回 -1。语法indexOf(searchElement) indexOf(searchElement, fromIndex)searchElement : 要查找的元素fromIndex : 按指定的索引进行查找出现的指定元素的第一个索引。 可选如果索引大于或等于数组的长度,则返回-1如果提供的索引值为负数,则将其视为距数组末尾的偏移量如果提供的索引为负数,仍然从前到后搜索数组如果提供的索引为 0,则将搜索整个数组。默认值:0(搜索整个数组)。const arr = [1,1,2,3,4,5,4,4,6] console.log(arr.indexOf(3)); //3 console.log(arr.indexOf(9)); //-1 console.log(arr.indexOf(3,4)); //-1 //从索引为 4 的元素进行查找 3, 显然后面没有3 , 返回 -1数组去重创建一个新的空数组,通过indexOf 来判断空数组是否第一次存在某个元素,不存在则返回 [ < 0 ] ,push 到空数组中.const newArr = [] arr.forEach(val => { if(newArr.indexOf(val) < 0){ newArr.push(val) } }) console.log(newArr); // [ 1, 2, 3, 4, 5, 6 ] lastIndexOf()lastIndexOf() 查找数组中元素最后一次出现的索引,如未找到返回-1。如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。语法arr.lastIndexOf(searchElement, fromIndex)searchElement : 要查找的元素fromIndex : 按指定的索引进行查找出现的指定元素的第一个索引。 可选从指定的索引位置 逆向 查找默认为数组的长度减 1(arr.length - 1),即整个数组都被查找。如果该值大于或等于数组的长度,则整个数组会被查找。如果为负值,数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。注意lastIndexOf 使用的是 严格相等 === 比较 searchElement 和数组中的元素。const arr = [1,1,2,3,4,5,4,4,6] console.log(arr.lastIndexOf(4)); //7 console.log(arr.lastIndexOf(4,11)); //7 指定的查找的索引 大于 数组的长度, 会进行整个数组查找 console.log(arr.lastIndexOf(4,-33)); // -1 指定的索引为负数,且绝对值大于数组长度, 则返回 -1 console.log(arr.lastIndexOf(4,-5)); //4 指定的索引为负数,且绝对值小于数组长度, 则会 从向前进行查找inCludes()includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。语法arr.includes(searchElement, fromIndex)searchElement : 要查找的元素查找时,区分大小写fromIndex : 按指定的索引进行查找出现的指定元素的第一个索引。 可选从指定的索引进行查找如果为负值,则按升序从 array.length + fromIndex 的索引开始搜如果 fromIndex 大于等于数组的长度,则会返回 false,且该数组不会被搜索。默认为0const arr = [1,1,2,3,4,5,4,4,6] console.log(arr.includes(4)); //true console.log(arr.includes(4,66)); //false console.log(arr.includes(1,-1)); //falseconcat()concat() 方法用于合并两个或多个数组。语法var new_array = old_array.concat([arr1][arr2])注意concat方法不会改变this或任何作为参数提供的数组,而是返回一个浅拷贝,它包含与原始数组相结合的相同元素的副本对象引用(而不是实际对象):concat将对象引用复制到新数组中。 原始数组和新数组都引用相同的对象。 也就是说,如果引用的对象被修改,则更改对于新数组和原始数组都是可见的。 这包括也是数组的数组参数的元素。数据类型如字符串,数字和布尔(不是String,Number和 Boolean) 对象):concat将字符串和数字的值复制到新数组中。let arr1 = [1,2,3] let arr2 = [4,5,6] let arr3 = [[1,2],[3,4]] console.log(arr1.concat(arr2)); //[ 1, 2, 3, 4, 5, 6 ] // 嵌套合并 console.log(arr1.concat(arr2).concat(arr3)); // [ 1, 2, 3, 4, 5, 6, [ 1, 2 ], [ 3, 4 ] ] let obj1 = [{a:1},{b:2}] let obj2 = [{c:3},{d:4}] let obj3 = obj1.concat(obj2) console.log(obj3); //[ { a: 1 }, { b: 2 }, { c: 3 }, { d: 4 } ] obj1[0].a = 4 //改变obj[0]对象值,会直接影响合并后的数组,因为是浅拷贝 console.log(obj3); //[ { a: 4 }, { b: 2 }, { c: 3 }, { d: 4 } ]toString()toString() 返回一个字符串,表示指定的数组及其元素。当一个数组被作为文本值或者进行字符串连接操作时,将会自动调用其 toString 方法。对于数组对象,toString 方法连接数组并返回一个字符串,其中包含用逗号分隔的每个数组元素。语法arr.toString()const arr = [1,2,3] console.log(arr.toString()); //1,2,3join()join()方法通过连接数组元素用逗号或指定的分隔符字符串分隔,返回一个字符串。如果数组只有一项,则将在不使用分隔符的情况下返回该项。语法join() join(separator)separator : 指定的分割的 字符 可选const arr = ['2021','08','08'] console.log(arr.join()); //2021,08,08 console.log(arr.join('-')); //2021-08-08 console.log(arr.join('/')); //2021/08/08slice()slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。语法arr.slice(begin, end)begin : 指定截取的开始索引 可选默认从0 开始如果begin 为负数,则以数组末尾开始 的 绝对值开始截取 slice(-2) 末尾第2个元素如果 begin 超出原数组的索引范围,则会返回空数组。end : 指定截取的结束索引 可选如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。如果 end 为负数, 则它表示在原数组中的倒数第几个元素结束抽取。const arr = [11,22,33,44,55,66,77,88] console.log(arr.slice(1,4)); // 应该返回 索引 1 - 3 的数组元素 // [ 22, 33, 44 ] console.log(arr.slice(-4,2)) //[] console.log(arr.slice(-4)); //[ 55, 66, 77, 88 ] console.log(arr.slice(0,-1)); // [ // 11, 22, 33, 44, // 55, 66, 77 // ]参考文献Array - JavaScript | MDN最后文章用下班时间肝了一周,写作不易,文中的内容都是 参考 MDN 文档 。 如果喜欢的话可以点赞关注,支持一下,希望大家可以看完本文有所收获 !
Spring Boot在了解 Spring Boot 之前,我们应该先了解一下 什么是 Spring , 因为是 Spring Boot 是 Spring 简化配置。什么是 SpringSpring 是 一个开源框架,2003年 火起来的一个 轻量级 Java 开发框架。 作者:Rod Johnson.Spring 是为了解决企业级应用开发的复杂性而创建的,简化开发。Spring 是 如何简化开发的基于 POJO 的轻量级和最小侵入性编程。通过 IOC, 依赖注入(DI) 和 面向接口实现松耦合。基于切面 (AOP) 和 惯例进行声明式编程。通过切面和模板减少样式代码。什么是Spring BootSpring Boot 是简化 Spring 应用开发,约定大于配置。Spring Boot是由Pivotal团队打造的,并在2014年发布的一个全新框架,其设计目标就是用来简化Spring应用的搭建、开发及部署。Spring Boot采用了特定的方式进行配置,从而达到使开发人员不在需要配置繁琐的xml文件。简单的说其实Spring Boot并不是什么新的框架,就是摒弃了xml的配置方式,使用了一种新的配置方式;然后在maven中使用starter的方式整合了很多第三方框架,同时提供了很多默认的配置参数。Spring Boot主要特点可以快速的搭建一个 Spring 项目默认使用 嵌入式 的 Servlet 容器,应用无需打成 WAR 包有很多 starters 自动依赖和版本控制 的 启动器。 [ 类似于 npm 依赖包]自动化配置,简化开发,可以配置修改默认值无需配置XML ,无代码生成,开箱即用。生产环境的运行应用监控与云计算的集成主要简化配置,构建项目快,入门门槛低微服务什么是微服务微服务是一种 架构风格 , 它要求我们在开发一个应用时,将这个应用构建成一系列小服务的组合,客套通过 HTTP 的方式进行互通。微服务架构 就是 打破之前 单体架构all in one), 把每个功能元素独立处理。把独立出来的功能元素动态组合,需要的功能元素才拿来组合,需要多一些时可以整合多个功能元素。 微服务架构是对功能元素进行复制,而没有对整个应用进行复制。采用微服务架构开发的系统,每一个独立的业务单元被划分为一个单独的模块,而每一个模块可以被独立的部署在服务器中,提供相应的业务服务。最终所有模块提供的服务组合在一起,就可以形成一套完整的业务流程,成为一个系统。微服务架构主要特点它主要解决了单体架构中存在的问题。模块之间相互独立,通过接口完成模块间的通信,有效降低了代码的耦合度。在开发新增的业务功能时,你只需要从代码库中下载你需要的模块,并不需要下载所有的代码,开发和测试将会更加简单,并且新功能不会对原有的系统产生任何影响,系统的可扩展性得到了有效地提升。节省了调用资源每个功能元素的服务都是一个可替换的、可独立升级的什么是单体架构?单体架构比较初级,典型的三级架构,前端(Web/手机端)+中间业务逻辑层+数据库层。所有功能都部署在同一个服务器中的系统,采用的架构就是单体架构。单体架构的主要特点优点部署 / 测试 容易开发简单,集中式管理功能都在本地,没有分布式的管理和调用消耗缺点复杂性高复杂性高,当项目迭代时间长,模块代码会变臃肿,耦合度高,难以维护扩展能力受限体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩。稳定性差一个微小的问题,都可能导致整个应用挂掉不灵活构建时间长,任何小修改都要重构整个项目,耗时效率低开发都在同一个项目改代码,相互等待,冲突不断
简介: 笔者前在某国企担任前端开发,两年半经验,某平台签约作者。我走上 前端这条路也是很有趣的,其实我接触的第一门编程语言是 Java ,那是在大一的时候,跟着学校的编程课学习,学习了半年多到 I O 流,整个过程下来又漫长又枯燥,那会不懂得自主找资料和视频学习,走了很多弯路。大一过后,我在大二上半学期刚开始,不断尝试各种编程语言,知道当下每门热门语言是干什么的,并写了一些实际 demo ,最终在 JavaScript 落脚了,没错,这就是我走上前端的开始,后来在大学里直接扎进这个方向学习了,毕业后顺利的从事了 前端开发工作。 从事前端的这两年中,也接触了后端领域,像 PHP Node, 并且都用它们做过一些 demo 和 私活 ,站在职业发展道路上来讲,了解后端是好的,自己可以熟练前后的开发流程,整条链路下来,很清晰,懂了后端,在自己以后创业或者接私活,都是不错的。 而且现在在Web后台领域, Java 是 老大哥了,Spring 全家桶走遍天下,于是最近又重拾 Java 了,从前端迈向全栈这是我最近花了一周的时间复盘的 Java 面向对象 总结,希望能帮助到你. 导读目录学习路线这份学习路线 是 我跟着 某up主 视频总结的,很全面重学Java一、面向对象 OOP1. 1 关于 super 和 this 区别supersuper 调用父类的构造方法,必须在子类构造方法中第一个super 必须只能出现在子类的方法 或者 构造方法 中super 和 this 不能同时出现在调用构造方法this代表的对象不同:this : 本身调用者这个对象super : 代表父类对象应用1.2 多态同一方法可以根据发送对象的不同而采用多种不同的行为方式。一个对象的实际类型是确定的,但可以指向对象的引用类型有很多。多态存在条件有继承关系子类重写父类的方法父类引用指向了子类对象注意子类如果重写了父类方法,父类对象是调用的子类重写后的方法类型转换类型转换目的: 方便方法调用,降级,升级,减少代码重复性,但也有缺点,丢失精度性向上转型 (子 转 父 ) 低转高 可直接转向下转型 (父 转 子) 高转低 需要强制转换高 转 低 后, 然后再进行强制转换, 子类型 子对象 = (子类型) 父对象 Person per = new Student() Student student = (Student) per // ((Student) per).方法/属性 per.子属性和方法1.3 static1.3.1 static 用途在没有创建对象的情况下调用方法和属性(静态的)。static 可以用来修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能。1.3.2 static 静态方法static 方法一般称作静态方法,静态方法不依赖于任何对象就可以进行访问, 因此对于静态方法来说,是没有this的。在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。在非静态方法中是可以访问静态成员方法/变量的。1.3.3 static 静态变量静态变量和非静态变量的区别是静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。1.3.4 static 代码块static关键字来形成静态代码块以优化程序性能,。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。可以把方法中常量放入到 静态块中,这样不至于,每次调用,又重新创建,造成空间浪费。public class Test { private static Date date ; private static SimpleDateFormat formatter; static { formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); date = new Date(System.currentTimeMillis()); } public static void main(String[] args) { getTime(); } public static void getTime(){ System.out.println(formatter.format(date)); } }以上例子是 用来获取当前时间,在调用getTime 静态方法时,它不会重复创建Date SimpleDateFormat执行顺序: 优于构造器1.3.5 static 注意static关键字不能会改变类中成员的访问权限, 能改变访问权限只有 private、public、protected.通过 this 可以在非静态方法中是可以访问静态方法和静态成员的public class Test { private static Date date ; private static SimpleDateFormat formatter; private static String name = "前端自学社区"; public static void main(String[] args) { Test test = new Test(); test.getStatic(); } public static void func(){ System.out.println("我是静态方法"); } public void getStatic(){ System.out.println(this.name); this.func(); } }例子中,在非静态方法中,可以直接通过 this 来进行访问的静态方法和静态成员 ,然后通过类实例化后,对象即可通过非静态方法来获取到静态方法中得静态属性和方法。1.4 抽象类 abstract我们先来了解一下抽象方法,抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:abstract int getNum();1.4.1何为 抽象类 呢 ?被 abstract关键词修饰的类就是抽象类,抽象类含有属性和抽象方法。抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。当 子类 继承 了 抽象类 ,那么必须的实现抽象类的抽象方法,同时 子类的实例化对象拥有抽象类的属性。public abstract class Animal { abstract String name; abstract void eat(); }1.4.2 抽象类作用?抽象类主要用来抽离事物的共性,抽离出属性和方法,子类 继承 抽象类,提高代码效率。1.4.3 抽象类与普通类区别抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。抽象类不能用来创建对象;如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。1.5 接口 interface1.5.1 什么是接口?它是对行为的抽象,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。当一个类需要用到这些抽象行为的话,需要 实现接口 implements , 而且必须实现接口中方法。public interface person { void study(){} }1.5.2 接口特性接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。 也可不写,默认就是public abstract 。public interface Aniaml{ public abstract void eat(); // 抽象方法 == 等同于 void eat(); }接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。1.5.4 接口的作用使用接口可以继承超过一个以上的来源,这样可以为不同的需求组合出不同的继承层次。接口规范了实现类,必须实现的方法和属性。1.6 接口与抽象类在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类, 它俩有很多相似之处。1.6.1 接口与抽象 区别抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;一个类只能继承一个抽象类,而一个类却可以实现多个接口。interface 接口 是针对事物的 行为 [ 方法 ] 来进行抽象的, 而 抽象类 是针对事物的共性来 对 行为 和 属性 来进行抽象的。1.6.2 例子接口 interfacepublic interface PeopleInterface { void eat(); void study(); void play(); } 抽象类 abstractpublic abstract class People { String name = "人类"; abstract void watch(); }子类public class Test extends People implements PeopleInterface { public static void main(String[] args) { Test test = new Test(); // 子类拥有抽象类的属性 System.out.println(test.name); test.eat(); } @Override public void eat() { System.out.println("吃的功能"); } @Override public void study() { System.out.println("学习功能"); } @Override public void play() { System.out.println("玩的功能"); } @Override void watch() { System.out.println("看的功能"); } }上面例子中讲了 类 与 接口 抽象类之间关系。 首先 Test 类 继承了 People 抽象类,然后又 实现了 PeopleInterface 接口。子类继承 抽象类的同时,必须实现抽象类的抽象方法,子类实例化时,拥有抽象类的属性。子类实现 接口的同时, 必须实现接口中所有的接口行为。结语以上是对Java 面向对象 的总结分享,希望能对你有所启发,欢迎提出意见!
2022年12月
2022年07月
2021年09月
2021年08月
2021年07月