CODESHIF - JS/CSS https://www.codeshif.com/category/js-css/ js/css技巧 Web前端登录的检测方式 https://www.codeshif.com/archives/47.html 2020-08-05T12:15:00+08:00 登录的检测方式1、组件内的检测检测登录第一种方式,最垃圾,是从组件内部的created方法进行检测,每个需要登录的组件都需要写一遍,很烦created() { const openid = localStorage.getItem("openid"); if (!openid) { this.$router.push("/login"); } },2、使用组件的继承使用组件的继承,从父组件实现检测登录,当前组件继承父组件,将会自动调用登录检测父组件:IsLogin.vue<script> export default { data() { // 当前组件会被子组件继承,那么该data内的openid依然也会被继承,则在子组件内可直接使用this.openid来获取登录用户状态 return { openid: null }; }, created() { const openid = localStorage.getItem("openid"); // 将openid赋值到组件数据data内,子组件继承后可直接使用 this.openid = openid; if (!openid) { this.$router.push("/login"); } }, }; </script>子组件:// 导入父组件 import IsLogin from "@/components/IsLogin"; export default { // 组件的继承关系,并不是父子组件的包含关系,而是父子继承后是一体的,父子组件包含关系不是一体的。 // 组件的继承就像类的继承一样,而父子组件包含并不是继承。 extends: IsLogin }3、路由检测在路由检测登录状态,但是缺陷很明显,路由会中断跳转,不会进入到组件内部,所以不能实现跳转返回例如:从A页面跳转到B页面,B页面又需要登录状态,会自动跳转到Login页面,此时Login登录后返回时,将无法获取到B页面的链接不能实现B页面的登录后跳转返回(原因是根本就没有进入到B页面组件内,在路由处就掐死了)router/index.jsconst router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) router.beforeEach((to, from, next) => { // 不管多少个页面,只要你需要用户登录状态的页面,你要跳转到需要用户登录的页面之前 // 进行判断,是不是登录了呢?登录的条件源自于:有没有OpenId,如果有的话,那么正常放行 // 否则呢,你就需要让他先登录 let paths = [ '/mine', '/order' ]; // paths.includes(to.path) // 相当于: // if (to.path == '/mine' || to.path == '/order') if (paths.includes(to.path)) { let openid = localStorage.getItem('openid'); // 判断有无登录状态,如果有,放行;如果没有,就让用户去登录 if (openid) { next(); } else { // next({ path: '/login' }); next('/login') } } else { // 如果不是你要检测的URL,那么直接放行 next(); } })以上三种登录检测的通用页面(路由内守卫)login.vue// 获取即将跳转至login页的 链接 // 跳转之login页的上一个页面 // 路由内守卫 beforeRouteEnter(to, from, next) { // 我们想要在这里得到上个页面的链接地址,并在登录成功之后,跳转回去。↓ // 这里的VM指的是,当前组件的this实例 next((vm) => { vm.returnURL = from.path; }); }, methods: { onSubmit(values) { console.log(this.returnURL); axios({ url: "/login.php", method: "GET", params: { username: values.username, password: values.password, }, }).then((res) => { let data = res.data; if (data.status == false) { // Toast("密码错误,请重试"); Notify({ type: "danger", message: "密码错误,请重试" }); } else { Notify({ type: "success", message: "登录成功" }); let openid = data.openid; localStorage.setItem("openid", openid); setTimeout(() => { // 这里的returnURL是从当前组件的路由守卫获取的 this.$router.replace(this.returnURL); }, 3000); } }); }, },登录的过程1、制作登录页面需要做好登录页面、并且处理好登录逻辑,包括登录后的返回处理(登录之后需要返回、跳转到登录之前的页面)2、登录状态的检测构思哪些页面需要用到登录后的状态,在这些页面内进行登录检测,如果没有登录状态(没登录),那么就让用户去登录页面先登录;对于登录的检测,我们推荐使用第二种方式:父子继承判断登录状态,获取登录信息。3、根据令牌获取用户数据已经有登录的令牌了,根据令牌,去获取用户的数据(拿登录的令牌去获取已登录的用户信息和已登录的用户数据)登录后的跳转因为登录成功之后需要跳转到登录前的页面,所以我们需要在登录页面获取到从哪里跳转来的链接,然后返回回去。路由内的守卫就可以获取到登录前的页面,我们先将该链接保存到组件实例内的data:第一种方式:// 获取即将跳转至login页的 链接 // 跳转之login页的上一个页面 // 路由内守卫 beforeRouteEnter(to, from, next) { // 我们想要在这里得到上个页面的链接地址,并在登录成功之后,跳转回去。↓ // 这里的VM指的是,当前组件的this实例 next((vm) => { vm.returnURL = from.path; // 跳转回去的路径,有可能带query参数如:/order?id=1111,如果你不处理query,那么直接根据path跳转回去是:/order而不是/order?id=1111,所以我们需要加入query // 第一种方式,可以记录from.query vm.returnQuery = from.query; }); },当登录成功后,可以根据组件实例this内的data保存的链接地址,进行返回。返回该链接时:setTimeout(() => { // 这里的returnURL是从当前组件的路由守卫获取的 this.$router.replace({ path: this.returnURL, query:this.returnQuery }); }, 3000);第二种方式: beforeRouteEnter(to, from, next) { next((vm) => { // 跳转回去的路径,有可能带query参数如:/order?id=1111,如果你不处理query,那么直接根据path跳转回去是:/order而不是/order?id=1111,所以我们需要加入query // 第二种方式,直接使用fullPath,就不用写path和query了 vm.returnURL = from.fullPath; }); }, 返回链接:setTimeout(() => { // 这里的returnURL是从当前组件的路由守卫获取的 this.$router.replace(this.returnURL); }, 3000);推荐使用第二种方式 我的ES6总结教程 https://www.codeshif.com/archives/48.html 2020-08-05T12:15:00+08:00 [TOC]解构数组解构// 赋值 let [a, b, c] = [1, 2, 3]; // 还可以解构基于Iterator的数据结构 let [x, y, z] = new Set(['a', 'b', 'c']); // 默认值 x='a', y='b let [x, y = 'b'] = ['a', undefined]; 对象解构// 变量必须与属性同名,才能取到正确的值 let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; const { log } = console; log('hello') // hello字符串解构const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"函数参数解构[[1, 2], [3, 4]].map(([a, b]) => a + b); // [ 3, 7 ] function move({x = 0, y = 0} = {}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]圆括号作用:// 错误的写法 let x; {x} = {x: 1}; // 正确的写法 let x; ({x} = {x: 1}); 解构唯一可以使用圆括号:赋值语句的非模式部分(左侧部分),可以使用圆括号。只有在赋值,并且没有声明语句时可用。否则全部不可用![(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确函数的扩展rest 参数function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10 // 注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 // 报错 function f(a, ...b, c) { // ... }箭头函数var f = v => v; // 等同于 var f = function (v) { return v; }; // 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。 var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; }; // 如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。 let fn = () => void doesNotReturn();箭头函数有几个使用注意点。(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。注意:由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。数组的扩展拓展运算符作用:扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列。Math.max(...[14, 3, 77]) // 复制数组 const a1 = [1, 2]; // 写法一 解开并重新成组 const a2 = [...a1]; // 写法二 const [...a2] = a1; // 合并数组 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' ] // 转换Iterator结构为数组 let nodeList = document.querySelectorAll('div'); let array = [...nodeList]; // 转换Map Set为数组 let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3] 对象的拓展属性表达式let lastWord = 'last word'; const a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world"扩展运算符 // 拷贝作用 let z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 } // 分解数组为对象 let foo = { ...['a', 'b', 'c'] }; foo // {0: "a", 1: "b", 2: "c"}链判断运算符a?.b // 等同于 a == null ? undefined : a.b a?.[x] // 等同于 a == null ? undefined : a[x] a?.b() // 等同于 a == null ? undefined : a.b() a?.() // 等同于 a == null ? undefined : a()null判断运算符??行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。const headerText = response.settings.headerText ?? 'Hello, world!'; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true; ??和||的区别a ?? b a !== undefined && a !== null ? a : b对于undefined和null, ??操作符的工作原理与||操作符相同> undefined ?? 'default' 'default' > null ?? 'default' 'default' 除了 undefined 和 null的其它虚值,?? 不会返回默认值。> false ?? 'default' false > '' ?? 'default' '' > 0 ?? 'default' 0参考文档:传送门**注意:双竖线判断的是逻辑性的(相当于两个等号==,而不是三个等号===),双问号的判断是为空判断的(相当于三个等号===,会进行类型判断)。**Symbol概念:Symbol是新的数据类型,表示独一无二的值。它是JavaScript语言的第七种数据类型。凡是属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。 let s1 = Symbol('foo'); let s2 = Symbol('bar'); s1 // Symbol(foo) s2 // Symbol(bar) s1.toString() // "Symbol(foo)" s2.toString() // "Symbol(bar)" 定义属性名在对象里面定义属性时,要使用[]的方式进行定义,不能使用.操作符定义,因为会被js把.操作符理解为字符串类型。let mySymbol = Symbol(); // 第一种写法 let a = {}; a[mySymbol] = 'Hello!'; // 第二种写法 let a = { [mySymbol]: 'Hello!' }; // 第三种写法 let a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上写法都得到同样结果 a[mySymbol] // "Hello!" // 不可使用.操作符 const mySymbol = Symbol(); const a = {}; // 使用.操作符会让js理解为是字符串 a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"属性名的遍历使用Object.getOwnPropertySymbols()方法,才可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。(返回Symbol属性数组)Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。const obj = {}; let a = Symbol('a'); let b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; const objectSymbols = Object.getOwnPropertySymbols(obj); objectSymbols // [Symbol(a), Symbol(b)]其它方式:Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]Symbol.for(),Symbol.keyFor()Symbol.for和Symbol的区别:Symbol的参数为该对象的描述而已,所以即便参数的字符串相同,但是对象是不一样的。// 没有参数的情况 let s1 = Symbol(); let s2 = Symbol(); s1 === s2 // false // 有参数的情况 let s1 = Symbol('foo'); let s2 = Symbol('foo'); s1 === s2 // falseSymbol.for()的参数会搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值这里可以用用户名来理解:注册一个用户名,比如叫admin,用户名是唯一性的。Symbol.for()就是用于获取有没有注册过该用户名,如果有就提取出来使用,如果没有则新建这个用户名并保存。let s1 = Symbol.for('foo'); let s2 = Symbol.for('foo'); s1 === s2 // trueSymbol.keyFor()Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。只支持由Symbol.for()注册的对象。let s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" // 没有使用Symbol.for注册的所以获取不到 let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefinedSymbol.hasInstance // 狗类 // function Dog(){ // } // 狗类 class Dog{ // 用中括号,包括起来之后,它就形成了一个方法,可以进行控制instanceof操作符的返回结果 // 重写了Dog类被使用instanceof的方法,可以操纵返回值。 // 参数就是instanceof操作符前面的对象 // dog instanceof Dog 里面的dog [Symbol.hasInstance](obj){ // 返回一个bool类型的值,代表instanceof的返回值(逻辑型) return obj instanceof Array; } } var dog = new Dog(); // 判断当前的对象是否属于Dog类 if (dog instanceof Dog){ // true } // True [1,2,3] instanceof Dog Symbol.iterator使当前的类,或对象(Object)支持遍历,类似于数组,支持for..of。 let obj = { // 用来实现该对象,支持遍历迭代。 *[Symbol.iterator](){ yield 1; yield 2; } } for(let a of obj){ console.log(a); } // 1 // 2yield类似于return,但是不会终止当前方法的执行,它只是为了迭代使用时,返回当前所属的一个值。Iterator(遍历器,迭代器)Iterator是一种接口,任何数据结构只要实现 Iterator 接口,就可以使用遍历、迭代。Iterator里含有一个next方法,每次调用它,都会将游标向下移动一次(可以理解为数组下标+1)。next方法每次调用会有返回值,返回一个对象,其中有value和done的属性。value是值,done是bool代表是否遍历完成。let obj = next(); obj: { value:1, done: true/false }在next方法中返回的done和value缺一不可:class RangeIterator { constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() { return this; } next() { var value = this.value; if (value < this.stop) { this.value++; return {done: false, value: value}; } return {done: true, value: undefined}; } } function range(start, stop) { return new RangeIterator(start, stop); } // 使用range方法可以返回一个RangeIterator对象,该对象支持迭代。 for (var value of range(0, 3)) { console.log(value); // 0, 1, 2 }原生具备 Iterator 接口的数据结构如下。ArrayMapSetStringTypedArray函数的 arguments 对象NodeList 对象注意:解构和拓展运算符,都需要支持Iterator接口实现Iterator的重要代码 generator和iterator+yieldyield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。我们要实现一个支持遍历的方法或对象,先要实现它的Iterator接口,并使用generator和yield提供的简写方式,这样会变得更简单。// 当你使用yield的实现时,即无需实现next方法,它将自动实现, // 但是yield必须要在generator里才能使用 let generator = function* () { yield 1; // yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。 yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: 3, done: false } iterator.next() // { value: 4, done: false } iterator.next() // { value: 5, done: false } iterator.next() // { value: undefined, done: true } for(let x of iterator){ console.log(x); } 遍历器对象的 return(),throw()next()方法是必须部署的,return()方法和throw()方法是否部署是可选的。如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。function readLinesSync(file) { return { [Symbol.iterator]() { return { next() { return { done: false }; }, return() { file.close(); return { done: true }; } }; }, }; }GeneratorGenerator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; }yield的返回值yield可以返回值,这个值,就是从next方法传递进去的参数。function* f() { for(var i = 0; true; i++) { // 在这里我们可以看到yield返回了一个bool类型的值, // 这个值来自于下面next的参数。 // g.next(true) var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }对任意对象Object或class实现迭代,务必要实现Symbol.iterator接口方法,才可以实现迭代#号代表私有成员属性class Cat{ constructor(){ this.#index = 0; } add(item){ this[this.#index] = item; this.#index++; } *[Symbol.iterator](){ for(let i = 0; i < this.index; i++){ yield this[i] } } }使用privateclass Cat{ #index = 0 add(item){ this[this.#index] = item; this.#index++; } *[Symbol.iterator](){ for(let i = 0; i < this.#index; i++){ yield this[i] } } } JS特殊语法 https://www.codeshif.com/archives/49.html 2020-08-05T12:15:00+08:00 JS特殊语法[TOC]event.preventDefault()方法// $0.onclick = (e)=>{e.preventDefault();alert(1);} // html A tag id : #aaa var a = document.getElementById('aaa'); // 这里的参数e代表当前所触发的鼠标事件内的,鼠标信息,比如说当前鼠标所在屏幕、页面位置、点击的左键还是右键等等 a.onclick = function(e){ // 取消一些DOM元素的默认事件,比如说A标签的链接跳转功能 e.preventDefault(); }event.stopPropagation()方法// 假设box是个框框,当它点击的时候输出1111 var box = document.getElementById('box'); box.onclick = ()=>{ console.log(1111) } // 给body一个点击事件,输出2222 document.querySelector('body').onclick = ()=>{console.log(2222)} // 当点击了#box之后,实际上会输出两个内容:1111,2222 // 因为,当儿子元素响应事件之后,还会传递到它的父亲,所以body也会输出内容。 // 当使用event.stopPropagation()方法之后,则不会传递到上一层内容之中。 box.onclick = (e)=>{ e.stopPropagation(); console.log(1111) } event.target什么意思比如我们有两个按钮按钮A 按钮B当你给按钮A制作一个click事件,并点击它的时候,如你所见,target就是self,因为是自己的事件内出发的该事件。可是呢,当你在按钮A的click事件内,主动去调用按钮B的点击事件,此时,按钮B的click也会被出发,不过,target就不是自己啦。a.onclick = function(e){ // 这里如果我主动点击,那么e.target就是我自己。 console.log(111); // 是不是就变了?所以b的click内的e:event的target就不是自己了,会显示是a元素调用的 b.click(); } b.onclick = function(e){ // 但是a调用了b内的点击事件,那么e.target就不是b自己了,而是a触发的。 console.log(222); } oncontextmenu这个是鼠标右键的默认菜单,想要屏蔽它,需要将当前元素取消默认的oncontextmenu返回值就OK了box.oncontextmenu = function(e){ return false; } box.oncontextmenu = e => falseonselectstart可以不让别人选择文本box.onselectstart = e => falseoncopy可以禁止复制文本box.oncopy = e => false::selection可以控制选择的文本背景色和被选中的文字颜色::selection { color:#ff0000; background: #088; }如何制作鼠标右键的菜单1、建立HTML,弹出的鼠标菜单选项(position:absolute;z-index:9999;background:#000)2、你需要屏蔽掉用于弹出菜单的层的鼠标右键:oncontextmenu = e => false3、在用于弹出菜单的层,右键事件中,对准备好的弹出菜单显示,位置用鼠标事件中的e.pageX与e.pageY来决定弹出菜单的top和left的样式。a6 => <div id="a6" style="display:none; position: absolute; background: rgb(0, 0, 0); z-index: 9999;"><ul><li>我们的祖国</li><li></li><li>我们的祖国</li><li></li><li>我们的祖国</li><li></li></ul></div> a6 = document.getElementById('a6'); box.onmousedown = e =>{ if(e.button == 2){ a6.style.left = e.pageX + 'px'; a6.style.top = e.pageY + 'px';a6.style.display:block }}其他内容1、闭包闭包就是前置执行,在方法体前和方法体后,加入一组小括号,代表成为一个整体,该整体就叫做闭包,用于分离开变量的作用域。(function(){}); // 如何调用呢? (function(){})(); var abc = (function(){ return 123;})();闭包下的作用域,返回JSON/Object类型var obj = (function(a,b,c){ // private members return { // public members; } })(1,2,3);返回Function类型,返回的一个基于Function类型的对象(可以理解为类)var Book = (function(a,b,c){ var age = 16; this.name = 111; return function(){ // private members var a1,a2,a3; // public members; this.isbn = "N12309128398"; } })(1,2,3); var b = new Book(); b.isbn; // N12309128398var Dog = function(){ // private members var a1,a2,a3; // public members; this.getAge = function(){ return 123; } } var d = new Dog(); d.getAge(); // 1232、JS变态写法||或者运算:var b = undefined; var a = b || 123; // 现在a的值是123为什么呢?注意,在JS中,或者||可以判断左侧的变量是否为null或者undefined,如果是的话,就取右侧的值,赋值给左侧变量。例子:function abc(a,b){ a = a || 123; console.log(a); } abc(); // 123&&与运算:var b = true; b && console.log(123); // 结果:123如果&&与符号前面表达式为真,那么后面就会被执行。相反如果前面为假,后面就不会被执行。连写:var username="admin", password = "admin"; if(username == "admin" && password == "admin"){ alert("登录成功") }注意:var声明变量的这一行,必须用分号结尾之后才可以执行自定义语句,不能写在一行。使用三元运算符,可以区分逻辑再执行var username="admin", password = "admin"; (username == "admin" && password == "admin") ? alert("登录成功") : alert("登录失败");注意:括号内的逗号,可以隔开多条待执行语句var username="admin", password = "admin"; (username == "admin" && password == "admin") ? (alert("登录成功"), location.href = "http://www.baidu.com") : alert("登录失败");forEach遍历注意:forEach在JS中,是异步的,不是同步,for循环才是同步的。也就是说,forEach的后面的语句,不会等待forEach执行完毕后再去执行。var arr = [1,2,3,4,5]; // for的做法 for(var i = 0; i < arr.length; i++){ arr[i]++; // arr[i] += 1; } // 这个会等待for语句执行完毕后,会打印111 console.log(1111); // forEach的做法,它是不能有返回值的 arr.forEach((item, index) => { // 在这里修改item的值,只能在当前方法大括号内被改变,不会影响到arr数组内的值 item += 100000; }) // 这里的1111,不一定在forEach执行完毕后才被打印,所以forEach是异步的,非同步。 console.log(1111);Map它是数组的拓展方法,意思是将数组遍历一次,并将遍历时,元素计算后的结果返回。var arr = [1,2,3,4,5]; for(var i = 0; i < arr.length; i++){ arr[i]++; // arr[i] += 1; } // Map // Map最需要注意的是返回值,必须要return // 要求:它不是过滤用的,必须每一个元素都需要返回。 var arr2 = arr.map((item, index) => { item += 100; return item // return当前元素你要干什么之后改变的值 }) // arr2 => [101,102,103,104,105]Filter过滤器它是数组的过滤器,如果你感觉某个元素不符合你的要求,可以将它排除。var arr = [1,2,3,4,5]; for(var i = 0; i < arr.length; i++){ if(i < 3){ arr.splice(i,1) } } // filter的做法 var arr2 = arr.filter((item, index) => { // 你这里需要返回,你认为达到标准的元素,当表达式为真则返回该元素。 return item > 3; })3、ES6难度较大:1、数组解构、对象解构2、函数的rest参数3、数组的拓展运算符4、对象的拓展运算符比较简单:5、Set 和 Map 数据结构6、Iterator 和 for...of 循环难度较大:7、Promise 对象(工作常用)8、async 函数(工作常用)9、Class 的基本语法10、Class 的继承Promise非同步,异步,异步的东西转变为同步才使用await/async。网络请求的地方使用await,只要你使用了await,就必须在当前的方法外侧声明为async。只有返回值为promise对象的方法才可以使用await所以,必须会Promise。// 取得用户详情信息 function getProfile(){ // 这里去请求网络... // 在返回值的时候,你也不知道网络请求成功了没有,所以你就返回了一个带有没有的状态。这个状态就是promise。 // return new Promise(这里啊是个回调函数) return new Promise((resolve,reject) => { // resolve是执行成功了,reject是失败了 resolve(data); // 比如说data就是网络请求的数据。 }); // 所以promise其实就是一个等待数据来的函数,但是数据很可能还没来呢。 }// 不使用await需要使用then,来获取数据到达 getProfile().then( res => { // res就相当于返回的data数据,但是注意,这里是resolve来的地方 }, error => { // 这里是reject来的地方。所以就发生了错误 }) // 这里啊还可以使用catch方法截获错误,那就不用使用reject的回调函数了 getProfile().then( res => { }).catch(err => { })// 既然使用await,就必须加上async,才能用,不然报错 async function wrapProfile(){ // 你的data肯定是resolve来的数据,但是请问reject在哪? let data = await getProfile(); // 最好的办法是使用try-catch语法来解决这样的错误和问题 try{ let data = await getProfile(); // 所以我们在这里进行判断data是不是null就可以了 if(data != null){ // 就行了 } }catch(error){ // 一旦出现错误就会来到这里 // 在这里可以处理错误信息 } }Promise.all()就是当多个含有promise的对象都处理完成后才会进入回调函数。Promise.any()就是数组内多个含有promise的对象,只要有一个处理完成,就会进入回调函数。Setset就相当于一个没有重复元素的数组,只不过添加数据的方式是使用add,而不是push。let s = new Set(); s.add(1).add(2).add(2); // 注意2被加入了两次 s.size // 2 s.has(1) // true s.has(2) // true s.has(3) // false s.delete(2); s.has(2) // falseMap键值对。key => value,相当于PHP中的array。const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false4、JS基础语法拓展call、apply、bind的作用于区别方法内的this默认指向为Windowcall是改变方法内的this指向的目标,且立即执行。apply是改变方法内的this指向的目标,且立即执行,和call的区别是第二个参数为一个数组。bind是改变方法内的this指向的目标,但是不会立即执行,需要自己调用才能执行。function get(){ // this => Window // 当被改变之后 this => set this.setTimeout(function(){console.log(111);},1500); } function set(){ this.setTimeout = function(){ console.log("小强") } } var s = new set(); // 默认 get(); // call和apply区别: get.call(s,1,2,3,4); get.apply(s,[1,2,3,4]); // 委托/代理 var proxy = get.bind(s,1,2,3,4); proxy(); // 打印的结果: // 小强 // 小强 // 小强 // undefined // 111prototype是什么// 我们定义一个Dog类,想要让它有个叫做say的成员方法 // 第一种实现方法 function Dog(){ this.say = function(){ console.log("汪汪汪") } } // new Dog().say(); // 第二种实现方法 function Dog(){ } // 可以使用prototype,在类的原型链中动态添加新方法 // 也被可以称之为:拓展方法 Dog.prototype.say = function(){ console.log("汪汪汪") } // new Dog().say(); for..in和for..of的区别for..in会取出对象、类中的原型链上的内容,而for..of则不会。for..in可以遍历任何类、对象,但是for..of只能遍历实现了iterator遍历器实现的类、对象。for...in 循环:只能获得对象的键名,不能获得键值,而for...of 循环:允许遍历获得键值// arr为数组,则是Array的对象 let arr = [1, 2, 3]; for(let x in arr){ console.log(x); } // 1 // 2 // 3 // 在原有的Array类上添加一个方法,就是在Array的原型链上进行拓展 Array.prototype.something = function(){ } for(let x in arr){ console.log(x); } // 1 // 2 // 3 // something // 注意,for..of方法,它不会取得对象原型链上的内容 for(let x of arr){ console.log(x); } // 1 // 2 // 3 // 所以我们认为,for..of是相对于for..in来说较为安全的。 // 因为for..in会打印出来一些我们并不需要的内容, // 但是for..of需要对象实现了iterator接口。总之,for...in 循环主要是为了遍历对象而生,不适用于遍历数组for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象浅拷贝:值传递和引用传递var a = 1,b = a; a = 2; // b = ? b = 1 // 为什么呢?因为你要赋值的操作是值类型的(数字,字符串,布尔),是直接拷贝(复制)过来。 var a = [1,2,3,4,5], b = a; b[0] = 99; // a = ? // a = [99,2,3,4,5] // 为什么呢?因为你要赋值的操作是引用类型的(基于对象),所以是取得它的地址而交给赋值左侧的变量。 Vue实现购物车 https://www.codeshif.com/archives/50.html 2020-08-05T12:15:00+08:00 购物车的数据:[]localstorage用来持久化数据存数据​ 组件去存,就是把当前页面的data、state保存到localstorage取数据​ 在actions里面取数据加、减:​ 加:​ 从actions传递过来一个购物车数据,然后传递到mutations,在mutation里面去判断有没有这个购物车数据,如果有的话,就+1,如果没有的话,push到state保存购物车的数组里。​ 减:​ 从actions传递过来一个购物车数据,然后传递到mutations,在mutation里面去判断有没有这个购物车数据,如果有的话呢,就-1,如果只有1份,就删除。方法:​ 1、判断购物车有没有某项数据​ 2、购物车增加数据​ 3、购物车减少数据​ 4、清空购物车​ 5、能够获取到购物车的项目的数量Actions功能:​ 1、是需要从保存在localstorage里面的数据,用在页面上。​ 2、接受增加、减少、清空的功能Mutations的功能:​ 1、对state的修改​ 2、Mutations有增加和删除两个功能,增加时对state里的购物车数组增加,减少时对state的购物车数组内减少或删除。如果需要用到exist的话用[].findIndex来查找自定义的数据。组件的功能:​ 1、控制显示的内容​ 2、调用Actions的方法实现增加,减少。​ 3、把处理好的state的购物车数组,保存到localstorage里面。Vuex购物车.zip Vue-Router我的教学文档 https://www.codeshif.com/archives/53.html 2020-08-05T12:15:00+08:00 Vuex我的教学文档 https://www.codeshif.com/archives/58.html 2020-08-05T12:15:00+08:00 微信小程序wx.addCard添加会员卡爬坑记录 https://www.codeshif.com/archives/28.html 2019-01-26T16:12:00+08:00 微信官方给的SDK叫simple.zip,其中文件jssdk.php 52行$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";我们需要把这个链接中的jsapi修改为wx_card才可以使用微信小程序添加卡券进来。注意在获取ACCESS_TOKEN的时候,要使用微信公众号的APPID和APP_SERCRT,不要使用小程序的来获取卡券,不然会出现错误。参考链接:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1499332673_Unm7Vhttps://developers.weixin.qq.com/miniprogram/dev/api/wx.addCard.htmlhttps://developers.weixin.qq.com/community/develop/doc/00020408e80528a75c08d4a3451004https://www.jianshu.com/p/7d1f08f3a460https://www.jianshu.com/p/a0863e704405 JavaScript substr、substring和slice的区别 https://www.codeshif.com/archives/24.html 2018-12-24T21:15:00+08:00 区别:substr第2参数是从第1参数后第几位,也就是Length如:s='abcdefg'; s.substr(0,3) // "abc" s.substr(2,3) // "cde"substring第2参数是实际字符串的第几位,但是!substring也是以两个参数中较小一个作为起始位置,较大的参数作为结束位置。如:s.substring(2,3) // "c" s.substring(2,5) // "cde" // 注意:有坑 s.substring(5,2) // "cde" slice和substring很像,但是区别在于负数slice的负数从尾部开始算,我们正常人类逻辑,但substring的负数干脆直接转为0!!如:s.slice(0,3) // "abc" s.slice(2,3) // "c" s.slice(2,-2) // 从开始2,到结尾2 // "cde" s.substring(2,-2) // 负数直接干成0,然后从0作为最小,2作为最大 // "ab" s.slice(-2,2) // "" s.substring(-2,2) // 负数直接干成0,然后从0作为最小,2作为最大 // "ab" s.substr(-2,3) // substr的第二参数是从第一开始数,所以根本不能为负数! // "fg" 总结substr和slice区别substr在于第二参数,也就是结束地址,substr结束地址是从第一参数后面开始数第几个!slice在于第二参数,也就是结束地址,slice结束地址是整个字符串的位置!substr和substring的区别substr第二参数的结束位置从第一参数开始数第几个,但是不能为负数!substring参数哪个小用哪个做开始,用大的做结束,且参数为负数直接变为0!!slice和substring区别slice在于第二参数,也就是结束地址,slice结束地址是整个字符串的位置!第二参数可为负数!substring参数哪个小用哪个做开始,用大的做结束,且参数为负数直接变为0!!不可为负数!完全总结substring 参数负数变0,且哪个小,就用哪个做起点,哪个大,就用做终点!substr 第二参数不能为负数,因为从第一参后开始数slice 第二参数可以是负数,他的第二参数是绝对地址 CSS3 clip-path polygon在线工具 https://www.codeshif.com/archives/20.html 2018-11-09T07:41:00+08:00 http://tools.jb51.net/static/api/css3path/index.htmlhttp://betravis.github.io/shape-tools/polygon-drawing/http://github.com/betravis/shape-tools CSS3旋转和拉伸差别巨大 https://www.codeshif.com/archives/19.html 2018-11-09T07:39:00+08:00 记住旋转是transform: rotate(-41deg);拉伸是transform: skewX(-26deg);可别搞错了,另外background-image: linear-gradient(to right,#fff 3px, #d3d5e6 1px); background-size: 4px 100%;这样的组合可以实现纹理线条