-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 86.1 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 86.1 KB
1
{"meta":{"title":"Hexo","subtitle":"","description":"","author":"jetBn","url":"http://jetBn.github.io","root":"/"},"pages":[{"title":"关于","date":"2020-04-29T02:52:27.761Z","updated":"2020-04-29T02:52:27.761Z","comments":false,"path":"about/index.html","permalink":"http://jetbn.github.io/about/index.html","excerpt":"","text":"个人简介"},{"title":"404 Not Found:该页无法显示","date":"2020-04-28T09:38:58.115Z","updated":"2020-04-28T09:38:58.115Z","comments":false,"path":"/404.html","permalink":"http://jetbn.github.io/404.html","excerpt":"","text":""},{"title":"友情链接","date":"2020-04-28T09:38:58.119Z","updated":"2020-04-28T09:38:58.119Z","comments":true,"path":"links/index.html","permalink":"http://jetbn.github.io/links/index.html","excerpt":"","text":""},{"title":"书单","date":"2020-04-28T09:38:58.118Z","updated":"2020-04-28T09:38:58.118Z","comments":false,"path":"books/index.html","permalink":"http://jetbn.github.io/books/index.html","excerpt":"","text":""},{"title":"分类","date":"2020-04-28T09:38:58.119Z","updated":"2020-04-28T09:38:58.119Z","comments":false,"path":"categories/index.html","permalink":"http://jetbn.github.io/categories/index.html","excerpt":"","text":""},{"title":"Repositories","date":"2020-04-28T09:38:58.120Z","updated":"2020-04-28T09:38:58.120Z","comments":false,"path":"repository/index.html","permalink":"http://jetbn.github.io/repository/index.html","excerpt":"","text":""},{"title":"标签","date":"2020-04-28T09:38:58.121Z","updated":"2020-04-28T09:38:58.121Z","comments":false,"path":"tags/index.html","permalink":"http://jetbn.github.io/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"深拷贝与浅拷贝","slug":"贝与浅拷贝","date":"2021-07-05T05:40:40.000Z","updated":"2021-07-05T05:42:44.537Z","comments":true,"path":"2021/07/05/贝与浅拷贝/","link":"","permalink":"http://jetbn.github.io/2021/07/05/%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D/","excerpt":"","text":"深拷贝与浅拷贝 关于JavaScript中的数据类型 JavaScript中的数据类型分为两种大类: 基础类型: undefined、null、String, Number、Boolean以及ES6引入的Symbol、ES10中的BigInt 引用类型:Object 相关的JavaScript对变量都是存储在栈内存、堆内存中的,而基础类型存在栈内存中,引用类型存在堆内存中。相关在的读写数据的时候,对于基础类型的是直接读写栈内存中的数据,引用类型是将一个内存地址保存在栈内存中,读写都是修改栈内存指向堆内存的地址。比如以下代码 12345let obj = { name: 'joy', age: 20}let num = 200 这里我们的obj就是存在栈内存中而指向一个堆内存中的地址,num就是直接存在栈内存中的。 12let obj1 = objconsole.log(obj1 === obj) //true 重新声明一个变量等于obj我们发现两个是相等的 这里我们就是可以发现内存中只是新增一个栈内存指向堆内存中的地址而已,新的这个栈内存还是指向之前的堆内存地址,这种就是浅拷贝。当我们改变obj中的name发现obj1中的name也被改变了,所以我们其实就是改变堆内存的变量而已,又因为两个obj与obj1都是指向这个堆内存地址,所以就是我要改变一个另一个不会发生变化,深度的复制一个堆内存地址和不单单引用地址这种才叫深拷贝。 浅拷贝 由上述可知,浅拷贝只是复制了堆内存的引用地址,通常我们在业务中出现的浅拷贝是指复制引用对象的第一层,也就是基本类型复制新值,引用类型复制引用地址。我们使用的方案有循环赋值、扩展运算符、Object.assign() 由上图代码示意结构我们发现我们分别使用`Object.assign()`和扩展运算符,浅拷贝了一份`obj`,分别为`obj1`和`obj2`当我们改变`obj`的第一层`name`的时候发现剩下的两个对象的`name`不会随这变化,当我们改变`obj`的第二层属性`child`的`name`的时候发现这三个对象都改变了,这就验证了上述所说的,浅拷贝就是单单复制了引用对象的第一层,而更深层次就没复制。 深拷贝 同过上述的浅拷贝,我们应该能了解到深拷贝的含义,就是拷贝对象以及更深层次的子对象,并且拷贝后两个对象互不影响。基本类型复制新值,引用类型开辟新的堆内存,可使用JSON.parse(JSON.stringify(obj))、循环赋值。 使用JSON.parse(JSON.stringify(obj)) 12345678910111213141516171819let obj = { name: 'joy', friends: ['tom', 'lili'], sex: Symbol('male'), age: undefined, child: { num: 100 }, date: new Date(), reg: /\\.*/g}let obj1 = JSON.parse(JSON.stringify(obj))console.log(obj === obj1) // falseobj.child.num = 200console.log(obj.child.num, obj1.child.num) // 200, 100 这里我们分别打印child中的num值发现互不影响, 这说明了我们的拷贝成功了。最后我们看下分别打印obj和obj1 <img src ="https://raw.githubusercontent.com/jetBn/blog/master/assets/md_images/ndeep2.png"/> 发现了拷贝忽略我们的undefined、symbol、正则表达式,日期类型、方法。所以这也是这个方法拷贝不足的地方。 手写一个deepClone 为了解决上述中的问题实现,把里面的单独项分别出来,分别复制里面的单独项。 123456789101112131415161718function deepClone(obj) { if(obj === null || !obj) return obj if(typeof obj !== 'object') return obj if(obj instanceof RegExp) return new RegExp(obj) if(obj instanceof Date) return new Date(obj) let result = Array.isArray(obj)?[]:{}; for(let key in obj){ if(obj.hasOwnProperty(key)){ if(obj[key] && typeof obj[key] === "object"){ result[key] = deepClone(obj[key]); }else{ result[key] = obj[key]; } } } return result;} 然后测试一下手写的代码,如图 发现已经解决了 上面JSON.stringify()方法的不足, 就是undefined、symbol的无法拷贝。自己实现的深拷贝在一些复杂的对象中可能会出现一些问题,慢慢完善吧。但是基本的数据类型还都是可以实现拷贝的。","categories":[],"tags":[],"author":"jetBn"},{"title":"Event Loop","slug":"Event-Loop","date":"2021-07-05T03:54:44.000Z","updated":"2021-07-05T04:01:00.853Z","comments":true,"path":"2021/07/05/Event-Loop/","link":"","permalink":"http://jetbn.github.io/2021/07/05/Event-Loop/","excerpt":"","text":"Event Loop 关于javascirpt javascript是一门单线程的语言, 虽然HTML5提出了web-work这样多线程解决方案,但是依旧没有改变javascript是单线程的本质。 > 关于HMLT5的web-work 它其实就是将一些大量的计算代码交给web work去处理运行而不冻结用户界面,就是阻塞UI的渲染,但是子线程是完全受主线程的控制的,而且不能和页面进行交互,如获取元素, alert等,但是线程之间是可以进行传递数据的。 javascript的事件循环 由于js是单线程的,就是同一个时间只能做同一个事情,那么就引出问题了,我们访问一个页面的时候,比如有很多的图片、视频等等资源,加载的时候我们肯定不是在那等的,所以就引入两种任务 同步任务与异步任务。 同步和异步任务分别进入不同的场所分支。执行同步的任务都在主线程上执行,形成一个执行栈,而异步任务进入Event Table中注册并且返回回调函数 当这个异步任务有了运行的结果,Event Table会将这个回调函数移入Event Queue中进入等待的一种状态。 当主线程同步的任务执行完毕, 会去Event Queue中读取对应的函数,并且结束它的等待状态,让它进入主线程 行执行。 主线程不断重复着上面的3个步骤,也就是常说的Event loop事件循环。 那怎么知道主线程执行栈为空的? 其实js引擎在monitoring process 监控进程,不会不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。 任务队列解释 >1. \"任务队列\"是一个事件的队列(也可以理解成消息队列), IO设备完成一项任务,就在\"任务队列\"中添加一个事件,表示相关的异步任务可以进入\"执行栈\"了。主线程读取\"任务队列\",就是读取里面有哪些事件 。 >2. \"任务队列\"中的事件,除了IO设备的事件以为,还包括一些用户产生的事件(比如鼠标点击,页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入\"任务队列\",等待主线程的读取。 >3. 所谓\"回调函数\"(callback), 就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。 > 4. \"任务队列\"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上自动的, 只要执行栈一清空,\"任务队列\"上的第一位事件就自动进入主线程。但是,由于存在后文提到的\"定时器\"功能,主线程首先要检查一下执行时间,某些时间只有到了规定的时间,才能返回主线程。 > 5. 读取到一个异步任务,首先是将异步任务放进事件表格(Event table)中,当放进事件表格中的异步任务完成某种事情或者达成某些条件(如setTimeout事件到了,鼠标点击了, 数据文件获取到了)之后,才将这些异步任务推入事件队列(Event Queue)中,这时候的异步任务才是执行栈中空闲的时候才能读取到的异步任务。 宏任务和微任务 宏任务(macro-task)包括:setTimeout, setInterval, setImmediate, I/O, UI交互事件,可以理解是每次执行栈执行的代码就是一个宏任务 微任务(micro-task)包括: Promises, Object.observe, MutationObserver, process.nextTick, 且process.nextTick优先级大于promise.then。可以理解是在当前 task 执行结束后立即执行的任务; 宏任务、微任务的执行顺序 主要的来讲就是先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务的队列中,当所有的同步代码执行完毕后,再将异步微任务从队列中调出来进入主线程执行,微任务执行完毕后再将异步宏任务从队列中调取出来到主线程中执行,一直循环直至所有任务执行完毕。 一道面试题代码分析 123456789101112131415161718192021222324252627282930313233343536console.log('1');async function async1() { console.log('2'); await async2(); console.log('3');}async function async2() { console.log('4');}process.nextTick(function() { console.log('5');})setTimeout(function() { console.log('6'); process.nextTick(function() { console.log('7'); }) new Promise(function(resolve) { console.log('8'); resolve(); }).then(function() { console.log('9') })})async1();new Promise(function(resolve) { console.log('10'); resolve();}).then(function() { console.log('11');});console.log('12'); 第一轮时间循环流程: 整体script代码作为一个宏任务进入主线,遇到console.log,输出1 遇到async1、async2函数声明, 声明暂时不用管 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中,我们记为process1 遇到setTimeout, 它的回调被分发到宏任务Event Queue中。我们暂记为setTimeout1 执行async1,遇到console.log,输出2 当使用async定义的函数,当它被调用时,它返回的是一个Promise对象而当await后面的表达式是一个Promise时,它的返回值实际上是Promise的回调reslove的参数 遇到await async2()调用,发现async2也是一个定义async的函数,所以直接执行返回4,同时返回了一个Promise。(这里返回的Promise被分配到微任务Event Queue中, 我们记为await1。await会让出线程,接下来就会跳出async1函数继续往下执行) 遇到Promise, new Promise直接执行,输出10。then被分发到微任务Event Queue中。记为then1 遇到console.log 输出12 宏任务Event Queue 微任务Event Queue setTimeout1 process1 await1 then1 这是第一轮事件循环结束时候各个Event Queue的情况,此时已经输出 1 2 4 10 12 这时候存在process1,await1,then1三个微任务,进行依次执行 执行process1输出5 取到await1, 就是async1放进去的Promise,执行Promise时候发现了遇到了他的reslove函数,(这里这个reslove函数又会被放入微任务Event Queue中,记为await2,然后再次跳出async1函数继续执行下一个微任务) 执行then1输出11 宏任务Event Queue 微任务Event Queue setTimeout1 await2 到这里输出1 2 4 10 12 5 11,然后我们的第一次微任务执行完毕了 新加入了一个await2的微任务在微任务Event Queue中。 此时我们进行执行await2微任务,它是async1放进去Promise的reslove回调,执行它(因为async2并没有返回值, 所以这个reslove的参数是undefined),此时await定义的这个Promise已经执行完毕并且返回了结果, 所以可以继续往下执行async1函数后面的任务了,那就是console.log(3), 输出 3 现在到这里我们第一轮事件循环结束了,此时,输出为 1 2 4 10 12 5 11 3 现在微任务执行完毕,进行第二轮的事件循环从setTimeout1宏任务开始 遇到console.log,输出6 遇到process.nextTick(),被分发到微任务Event Queue中去,记为process2 遇到new Promise立即执行后输出8,然后then被分发到微任务Event Queue,记为then2 宏任务Event Queue 微任务Event Queue process2 then2 上述表格为二轮事件循环结束各个Event Queue的情况,这时候的情况是存在两个微任务process2和then2 执行process2, 输出7 执行then2,输出9 第二轮事件循环结束,第二轮结果为 6 8 7 9 所以最后的输出结果为1 2 4 10 12 5 11 3 6 8 7 9 最后 通过题目与理论的结合更深刻的理解javascirpt中Event loop","categories":[],"tags":[],"author":"jetBn"},{"title":"defineProperty与Proxy","slug":"efineProperty与Proxy","date":"2021-07-05T03:54:31.000Z","updated":"2021-07-05T03:56:31.594Z","comments":true,"path":"2021/07/05/efineProperty与Proxy/","link":"","permalink":"http://jetbn.github.io/2021/07/05/efineProperty%E4%B8%8EProxy/","excerpt":"","text":"defineProperty与Proxy 简述介绍 defineProperty是ES5对象Object的api,而Proxy则是ES6实现的的api都是可以实现数据监听与劫持,Proxy的表面意思上更像是一种代理的意思,它比ES5的 defineProperty更友好的监听对象中的属性,它代理的是整个对象,实现的监听,而defineProperty是遍历对象的每个属性,而且还不能监听数组的变化。Vue2.x的数据绑定就是用defineProperty()实现的,现在Vue3.x版本也是改用了Proxy来实现,所以就更深层次理解下这两个api。 Object.defineProperty()引用MDN上的相关介绍该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 Object.defineProperty(obj, prop, descriptor) 参数obj表示定义的对象, 参数prop表示要定义或修改的属性名称 参数descriptor表示要定义或修改的属性的相关描述符 相关的属性描述符分别为configurable、enumerable、value 、 writable、get 、set这里就不一一单的说明了,重点还是set、get这两个。更详细的MDN上有很详细的介绍文。然后对数据的劫持都是由对象属性触发getter与setter,以及对象的属性发生变化的时候就行特定的操作。 ProxyProxy就像名称一样理解成代理的意思,它其实就在目标对象之前架设了一层“拦截”,就是访问该对象都必须通过该拦截。因此提供了一种机制,可以对目标对象访问进行过滤和改写。 const proxyObj = new Proxy(target, handler) 参数target表示要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler一个通常以函数作为属性的对象,各属性中的函数分别定义了再执行各种操作时代理proxyObj的行为。 相关handler对象的方法有set、get、apply、definePropery、has、construct等等,更详细的MDN上有详细的介绍。我们主要是对比与Object.defineProperty的数据劫持。 总结Proxy与Object.defineProperty()都能实现对数据劫持,对比而下而Proxy更优,它可以直接监听对象而非属性,可以直接监听数组的变化,有很多的拦截方法,实例返回的是一个新对象,操作这个对象就可以实现对原对象数据的改变。而Object.defineProperty()只能遍历对象的属性进行修改操作。","categories":[],"tags":[],"author":"jetBn"},{"title":"理解call()、apply()、bind()用法与区别","slug":"call-、apply-、bind-用法与区别","date":"2021-07-05T03:53:26.000Z","updated":"2021-07-05T03:55:26.957Z","comments":true,"path":"2021/07/05/call-、apply-、bind-用法与区别/","link":"","permalink":"http://jetbn.github.io/2021/07/05/call-%E3%80%81apply-%E3%80%81bind-%E7%94%A8%E6%B3%95%E4%B8%8E%E5%8C%BA%E5%88%AB/","excerpt":"","text":"理解call()、apply()、bind()用法与区别 相关介绍这三个函数都是在JavaScript中改变函数调用的this指向的,具体的可能就是参数传递不同。 call()方法该方法使用一个指定的this值和给出一个或多个参数来调用一个函数,他们都是Function对象的方法,每个函数都能调用。它可传递两个参数第一个为在function函数运行时使用的this值,第二个为指定的参数。 具体的使用方法 1234567891011121314151617181920let obj = {age: 11}function fun(arg) { console.log(arg) console.log(this.age)}fun('lili') //直接调用 此时的this指向window所以 第一行打印`lili`第二行打印`undefined`// 使用call改变函数调用运行时的thisfun.call(obj, 'lili') // 分别打印 lili 11// 多个参数function fun(arg) { console.log(arguments) console.log(this.age) }fun.call(obj, 'lili', '20') // 此时的打印分别是arguments中两个参数 lili以及 20,以及11 apply()方法该方法其实跟call()方法是一样的就是传递的参数不一样,它的第二个参数为一个数组(或类似数组对象) 具体的使用方法 12345678let obj = {age: 11}function fun() { console.log(arguments) console.log(this.age)}fun.apply(obj, ['lili', '20']) //此时打印个跟上述call传递多个参数的时候是一致的 通过此两个案例我们可以清楚发现,call()和apply()函数的第一个参数都是改变this的指向,而call()和apply()的区别仅仅就是call()传递参数需要一项一项的传, 但是apply()直接通过一个数组传递就好了。 bind()方法该方法与apply()和call()很相似,也是可以改变函数体内的this指向的,它是创建一个新的函数, 在bind()被调用的时候,这个新函数的this被指定为bind()的第一个参数,而其余的的参数将作为新函数的参数,供调用时使用。 具体使用代码 1234567891011let obj = {age: 11}function fun() { console.log(this.age)}fun() // undefinedlet newFun = fun.bind(obj)newFun() // 11 由上面的运行代码在bind()函数改变this的指向之前我们打印的是undefined,因为我此时的this指向是window在此里面没有定义age的属性,所以我们答应的是undefined,当我们通过bind()函数改变this传入obj此时又新生成一个newFun()函数,当我们调用此函数的时候,此时的this指向就是obj了,所以我们打印了 11。 bind(), apply(), call()的共同和不同点: 三个都是Fucntion对象的方法都是改变函数的this对象的指向的; 三个函数的第一个参数都是this要指向的对象,也就是想指定的上下文,上下文就是指调用函数的那个对象。(如果没有传以及null都是指向window); 三个都可以传递参数,apply()是数组,call()与bind()都是有序传入参数; bind是返回函数,便于稍后调,而apply()与call()都是立即执行 最后分别手动实现 call()函数 1234567891011121314151617Function.prototype.myCall = function(context, ...args) { if(typeof context === 'object') { context = context || window } else { context = Object.create(null) } let fn = Symbol() // 保证fn的唯一性 context[fn] = this let result = context[fn](...args) delete context[fn] return result} apply()函数 12345678910111213141516Function.prototype.myApply = function(context, arg) { if(typeof context === 'object') { context = context || window } else { context = Object.create(null) } let fn = Symbol() // 保证fn的唯一性 context[fn] = this let result = context[fn](...args) delete context[fn] return result} bind()函数 123456789101112Function.prototype.myBind = function(context) { const that = this const args = [...arguments].slice(1) const tempFun = function() {} const resultFun = function() { const nextArgs = [...arguments] return that.apply(this instanceof resultFun ? this : context, [...args, ...nextArgs]) } tempFun.prototype = that.prototype resultFun.prototype = new tempFun() return resultFun}","categories":[],"tags":[],"author":"jetBn"},{"title":"原型与原型链","slug":"型与原型链","date":"2021-07-05T03:52:54.000Z","updated":"2021-07-05T03:53:01.675Z","comments":true,"path":"2021/07/05/型与原型链/","link":"","permalink":"http://jetbn.github.io/2021/07/05/%E5%9E%8B%E4%B8%8E%E5%8E%9F%E5%9E%8B%E9%93%BE/","excerpt":"","text":"原型 前言介绍每个函数都有prototype属性, 除了Function.prototype.bind(),该属性最终指向原型。每个对象都有_proto_属性, 指向了创建该对象构造函数的原型,其实这个属性指向了[[prototype]],但是由于[[prototype]]是内部属性,所以并不能访问到,所以使用了_proto_来访问。对象可以通过_proto_来寻找不属于改对象的属性,_proto_将对象连接起来组成了原型链。 prototype属性这个是一个显式的原型属性,只用函数才拥有,基本上所有函数都有这个属性,但是有一个另外 1const fun = Function.prototype.bind() 这个fun函数的创建就是没有prototype属性的。 prototype的产生 当我们声明一个函数的时候自动被创建的。 1function fun() {} 通过方为fun.prototype我们能看到有一个constructor方法,其就是对应的构造函数也就是fun 12fun.prototype{constructor: ƒ} constructor constructor是一个公有的而且不可枚举的属性, 一旦我们改变了函数的prototype, 那么新的对象就没有这个属性了, 但是我们可以通过原型链取到。 这个属性的作用: 让实例对象知道是什么函数构造了它 如果想给某些类库中的构造函数增加一些自定义的方法,就可以通过 xx.constructor.method 来扩展 _proto_属性这是每个对象有的隐式原型属性,指向了创建该对象的构造函数的原型。其实这个属性指向了[[prototype]],但是[[prototype]]是内部的属性,我们并不能访问到,所以使用了_proto_来访问。 因为在js中没有类的概念,为了实现类似的继承方式, 通过_proto_将对象和原型联系起来组成原型链,得以让对象可以访问到不属于自己的属性。 _proto_的产生从顶部的图我们看到, 当我是用new关键字的时候生成的对象就有了_proto_属性了。 12345functon Fun() {}// 这个函数是Function的实例对象// functon其实就是一个语法糖// 内部调用了 new Function() 所以可以说, 在new的过程中就是向定对象添加了_proto_属性并且链接到构造函数的原型上了 一个new的过程 新生成了一个对象 链接到原型 绑定this 返回新对象 手动实现一个new的过程 123456789101112function cusNew() { // 创建一个对象 let obj = new Object() // 获取构造函数 const Con = [].shift.call(arguments) // 链接到原型 obj._proto_ = Con.prototype // 绑定this执行构造函数 let result = Con.apply(obj, arguments) return typeof result === 'function' ? result : obj} 对于实力对象来说,都是通过new来产生的,不论是 funciton Fun()还是 let obj = { name:'js' } 对于创建一个对象来说,创建最好都是用字面量的方式来创建。 因为当你 new Object()的方式创建对象需要通过作用域链一层层找到Object,但是你使用字面的方式就没有这个问题,性能更高。 Function._proto_ === Function.prototype 对于对象来说,xx._proto_.contrcutor 是该对象的构造函数,但是在上面的图中我们发现Function._proto_ === Function.prototype,难道这代表Function自己创建了自己? 答案是否定的。Function.prototype是由引擎来创建的。 从上图中我们可以发现,所有的对象都可以通过原型链最终找到Object.prototype,虽然Object.prototype也是一个对象,但是这个对象不是Object创造的,而是引擎自己创建了Object.prototype。所以可以这样说,所以实例都是对象,但是对象不一定都是实例。 我们通过Function.prototype在浏览器将这个对象打印出来,会发现其实是一个函数。 12Function.prototypeƒ () { [native code] } 由于Function.prototype和Object.prototype都是是由引擎创建的,它首先创建了Function.prototype,然后创建Object.prototype并且通过_proto_将两者联系起来。这里就很好解释了为什么let fun = Function.prototype.bind()创建没有prototype属性。因为Function.prototype是由于引擎创建出来的对象,引擎认为不需要给这个对象添加prototype属性。 所以,不是所有函数都是new Function()产生的。 有了Function.prototype以后才有了fucntion Function(),然后其他的构造函数都是fucntion Function()生成的。 所以Function.__proto__ === Function.prototype这个问题理解就是,其他所有构造函数都可以通过原型链找到Function.prototype, 并且function Function()本质也是一个函数,为了不产生混乱就将function Function()的__proto__联系到了Function.prototype上。 总结 Object是所有对象的爸爸,所有对象都可以通过_proto_找到它。 Function是所有函数的爸爸,所有函数都可以通过_proto_找到它。 Function.prototype和Object.prototype是两个特殊的对象, 是由引擎创建的。 除了以上两个对象,其他对象都是通过构造器new出来的。 函数的prototype是一个对象,也就是原型。 对象的_proto指向原型,_proto_将对象和原型连接起来组成了原型链。","categories":[],"tags":[],"author":"jetBn"},{"title":"数组常用方法","slug":"组常用方法","date":"2020-07-28T08:53:00.000Z","updated":"2020-07-28T08:55:50.637Z","comments":true,"path":"2020/07/28/组常用方法/","link":"","permalink":"http://jetbn.github.io/2020/07/28/%E7%BB%84%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95/","excerpt":"","text":"JavaScript中数组常用方法 前言介绍在日常开发中我们经常都会使用到数组的一些操作,比如增、删、循环等等。自己对此数组的一些方法(常用以及不常用的)整理一遍,特此记录一下。 arr.push()从数组的后面添加元素, 返回值为数组的长度 123let arr = []console.log(arr.push(1)) // 1console.log(arr) // [1] arr.pop()从数组的后面删除元素,只能删除一个,返回值是删除的元素 123let arr = [1, 2, 3]console.log(arr.pop()) // 3console.log(arr) // [1, 2] arr.shift()从数组前面删除一个元素,只能删除一个,返回值是删除的元素 123let arr = [1, 2, 3]console.log(arr.shift()) // 1console.log(arr) // [2, 3] arr.unshift()从数组前面添加一个元素,返回值是数组的长度 123let arr = [2, 3]console.log(arr.unshift(1)) // 3console.log(arr) // [1, 2, 3] arr.concat()连接两个数组,返回值为连接后的新数组(其实就是合并)参数可以数值或者数组 123let arr1 = [1, 2]let arr2 = [3, 4]console.log(arr1.concat(arr2)) // [1, 2, 3, 4] arr.splice(i, n, item1, item2, ... ) 删除从i(索引)开始之后的那个元素。返回值删除的元素的数组 参数: i 表示索引,n表示个数(可选参数), item1, item2, ... 向数组添加项(可选参数) 12345let arr = [1, 2, 3, 4, 5, 6, 7]console.log(arr.splice(2,1)) // [3]console.log(arr) // [1, 2, 4, 5, 6, 7]console.log(arr.splice(2,1, 3, 4)) // [3, 4]console.log(arr) // [1, 2, 3,4, 4, 5, 6, 7] str.split(s, n)将字符串分割成数组 参数s字符串或正则表达式(必需),n返回数组的最大长度(可选参数),不传则不规定数组的长度,根据字符分割。 12let str = '1234'console.log(str.split('')) // [1, 2, 3, 4] arr.join(separator)将数组的所有元素(或者类数组)的所有元素链接成一个字符串并且返回这个字符串参数:separator(可选)指定一个字符串分隔每个元素,默认为逗号(,) 1234let arr = ['jon', 'tom', 'wang']console.log(arr.join()) // jon,tom,wangconsole.log(elements.join('')) // jontomwangconsole.log(elements.join('-')); // jon-tom-wang arr.sort()将数组进行排序,返回值是排好的数组,默认是按照最左边的数字进行排序,不是按照数组大小排序。 12345678let arr = [2, 10, 1, 4, 22, 3]console.log(arr.sort()) // [1, 10, 2, 22, 3, 4]let arr1 = arr.sort((a, b) => a - b)console.log(arr1) // [1, 2, 3, 4, 10, 22]let arr2 = arr.sort((a, b) => a - b)console.log(arr2) // [22, 10, 4, 3, 2, 1] arr.reverse()将数组进行反转,返回值是反转后的数组 123let arr = [1, 2, 3, 4]console.log(arr.reverse()) // [4, 3, 2, 1]console.log(arr) // [4, 3, 2, 1] arr.slice(start, end)切去索引开始值start到索引值end的数组(浅拷贝),不包含end的索引的值,返回值是切出来的数组, 原始数组不会被改变。 参数start(可选),表示开始的索引值(不传为0),end(可选)表示结束的索引值(不传为数组的最后一项)。 123let arr = [1, 2, 3, 4, 5, 6]console.log(arr.slice(1, 3)) // [2, 3]console.log(arr) // [1, 2, 3, 4, 5, 6] arr.forEach(callback)遍历数组, 无return即使有也不会返回任何值,并且不会影响原来的数组callback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选)123456789101112131415161718192021222324252627282930let arr = [1, 2, 3, 4]arr.forEach( (value, index, array)=>{ console.log(`value:${value} index:${index} array:${array}`)})/* value:1 index:0 array:1,2,3,4 value:2 index:1 array:1,2,3,4 value:3 index:2 array:1,2,3,4 value:4 index:3 array:1,2,3,4 */let arr = [1, 2, 3, 4]arr.forEach((value, index, array) => { value = value * 2 console.log(`value:${value} index:${index} array:${array}`)})/* value:2 index:0 array:1,2,3,4 value:4 index:1 array:1,2,3,4 value:6 index:2 array:1,2,3,4 value:8 index:3 array:1,2,3,4 */let arr = [1, 2, 3, 4]let resArr = arr.forEach((value, index, array) => { arr[index] = value * 2 return arr})console.log(resArr) // undefinedconsole.log(arr) // [2, 4, 6, 8] arr.map(callback)映射数组(遍历数组),有return返回一个新数组callback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选)12345678910111213141516171819let arr = [1, 2, 3, 4]arr.map((value, index, array)=> { value= value * 2 console.log(`value:${value} index:${index} array:${array}`)})/* value:2 index:0 array:1,2,3,4 value:4 index:1 array:1,2,3,4 value:6 index:2 array:1,2,3,4 value:8 index:3 array:1,2,3,4 */let arr1 = [1, 2, 3, 4]let resArr = arr1.map((value, index, array)=> { item = item * 2 return item})console.log(arr1) // [1, 2, 3, 4]console.log(resArr) // [2, 4, 6, 8] arr.forEach()是和 for循环一样的,是代替for。arr.map()是修改数组其中的数据,并返回新的数据。 arr.forEach()没有return 返回值,而arr.map()有 arr.filter(callback)过滤数组,返回一个满足条件的数组(数组中的每一个元素都执行一遍callback)callback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选) 123let arr = [1, 2, 3, 4]let resArr = arr.filter((value, index) => value < 3)console.log(resArr) // [1, 2] arr.every(callback)依据判断条件,数组的元素是否全满足,若满足返回truecallback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选)123let arr = [1, 2, 3, 4]let result = arr.every((value, index) => value < 3) // falselet result = arr.every((value, index) => value < 6) // true arr.some()依据条件判断,数组的元素是否有一个满足,若有一个满足则返回truecallback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选)123let arr = [1, 2, 3, 4, 5]let result = arr.some((value, index) => value < 3) // truelet result = arr.some((value, index) => value > 6) // false arr.reduce(callback, initialValue)迭代数组的所有项,累加器。数组中每个值(从左到右)合并,最终计算为一个值。callback的参数: previousValue –上一次调用回调返回的值,或者是提供的初始值(initialValue)(必选) currentValue –数组中当前被处理的数组项 (必选) index – 当前数组项在数组中的索引值(可选) array –原数组 (可选) initialValue参数:初始值(可选)具体的实行:回调函数第一次执行时,previousValue和currentValue可以是一个值,如果initialValue在调用reduce()时被提供,那么第一个值previousValue等于initialValue,并且currentValue等于数组中的第一个值,如果initialValue未被提供,那么previousValue就等于数组中的第一个值。 123let arr = [1, 2, 3, 4, 5]let result = arr.reuce((preValue, curValue)=> prevValue + cuValue)console.log(result) // 15 previousValue currentValue index array 返回值 第一次回调 1 2 1 [1, 2, 3, 4, 5] 3 第二次回调 3 3 2 [1, 2, 3, 4, 5] 6 第三次回调 6 4 3 [1, 2, 3, 4, 5] 10 第四次回调 10 5 4 [1, 2, 3, 4, 5] 15 12let arr2 = arr.reduce((preValue,curValue)=>preValue + curValue,3)console.log(arr2) previousValue currentValue index array 返回值 第一次回调 3 1 0 [1, 2, 3, 4, 5] 4 第二次回调 4 2 1 [1, 2, 3, 4, 5] 6 第三次回调 6 3 2 [1, 2, 3, 4, 5] 9 第四次回调 9 4 3 [1, 2, 3, 4, 5] 13 第五次回调 13 5 4 [1, 2, 3, 4, 5] 18 reduce的高级用法 计算数组中每个元素出现的次数 12345678910let arr = ['joi', 'tom', 'tom', 'mary', 'peter', 'mary']let num = arr.reduce((preVal, curVal) => { if(curVal in preVal) { preVal[curVal]++ } else { preVal[curVal] = 1 }}, {})console.log(num) // {jio: 1, tom: 2, mary: 2, peter: 1} 数组去重123456789let arr = [1,2,3,4,2,1,1,5]let resArr = arr.reduce((preVal, curVal)=> { if(!preVal.includes(curVal)) { return preVal.concat(curVal) } else { return preVal }}, [])console.log(resArr) // [1, 2, 3, 4, 5] 将多维数组转为一维数组123let arr = [[0, 1], [2, 3], [4,[5,6,7]]]const replaceArr = (arr) => arr.reduce((preVal, curVal) => preVal.concat(Array.isArray(curVal) ? replaceArr(curVal): curVal), [])console.log(replaceArr(arr)) // [0, 1, 2, 3, 4, 5, 6, 7] arr.reduceRight(callback, initialValue)与arr.reduce一样,不同的是,reduceRight是从数组的末尾向前将数组中数组项做累加callback的参数: previousValue –上一次调用回调返回的值,或者是提供的初始值(initialValue)(必选) currentValue –数组中当前被处理的数组项 (必选) index – 当前数组项在数组中的索引值(可选) array –原数组 (可选) initialValue参数:初始值(可选)具体的实行:回调函数第一次执行时,previousValue和currentValue可以是两个值之一,如果initialValue在调用reduceRight()时被提供,那么第一个值previousValue等于initialValue,并且currentValue等于数组中的最后一个值,如果initialValue未被提供,那么previousValue就等于数组中的最后一个值。 123let arr = [1, 2, 3, 4, 5]let result = arr.reuce((preValue, curValue)=> prevValue + cuValue)console.log(result) // 15 previousValue currentValue index array 返回值 第一次回调 5 4 3 [1, 2, 3, 4, 5] 9 第二次回调 9 3 2 [1, 2, 3, 4, 5] 12 第三次回调 12 2 1 [1, 2, 3, 4, 5] 14 第四次回调 14 1 0 [1, 2, 3, 4, 5] 15 12let arr2 = arr.reduce((preValue,curValue)=>preValue + curValue,3)console.log(arr2) previousValue currentValue index array 返回值 第一次回调 3 5 4 [1, 2, 3, 4, 5] 8 第二次回调 8 4 3 [1, 2, 3, 4, 5] 12 第三次回调 12 3 2 [1, 2, 3, 4, 5] 15 第四次回调 15 2 1 [1, 2, 3, 4, 5] 17 第五次回调 17 1 0 [1, 2, 3, 4, 5] 18 arr.indexOf()查找某个元素的索引值,若有重复的,则返回第一个查到的,若查不到的-1 123let arr = [1, 2, 3]console.log(arr.indexOf(2)) // 1console.log(arr.indexOf(5)) // -1 arr.lastIndexOf()功能和indexOf()一样,不同的是它是从数组尾端开始往前找 123let arr = [1, 2, 3, 4, 5]console.log(arr.lastIndexOf(3)) // 2console.log(arr.lastIndexOf(5)) // -1 Array.from()将伪数组转成数组,就是只要有length的就可以转化成数组 1234let str = '12345'console.log(Array.from(str)) // ["1", "2", "3", "4", "5"]let obj = {0: 'a', 1:'b', length: 2}console.log(Array.from(obj)) // ["a", "b"] Array.of()将一值转为数组,类似于声明数组 12let str = '11'console.log(Array.of(str)) // ['11'] 等价于 1console.log(new Array('11') new Array()有缺点,就是参数问题引起的重载 12console.log(new Array(2)) //[empty × 2] 是个空数组console.log(Array.of(2)) // [2] arr.copyWithin()在当前数组内部,将定时位置的数组复制到其他位置,会覆盖原来数组项,返回当前数组参数: target –索引从该位置开始替换数组项(必选) start –索引从该位置开始读取数组项,默认为0,如果为负值,则从右往左读取。(可选) end –索引到改位置停止读取的数组项,默认是Array.length,如果负数,表示倒数。1234567let arr = [1, 2, 3, 4, 5, 6, 7]let arr1 = arr.copyWithin(1) console.log(arr1) // [1, 1, 2, 3, 4, 5, 6]let arr2 = arr.copyWithin(1, 2)console.log(arr2) // [1, 3, 4, 5, 6, 7, 7]let arr3 = arr.copyWithin(1, 2, 4)console.log(arr3) // // [1, 3, 4, 4, 5, 6, 7] arr.findInex(callback)找到一个符合条件的数组成员的索引值callback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选)123let arr = [1, 2, 3, 4]let index = arr.findIndex((value, index, array) => value > 3)console.log(index) // 3 arr.find(callback)找到第一个符合条件的数组成员callback的参数: value –当前索引的值, index –索引, (可选) array –原数组 (可选)123let arr = [1, 2, 3, 4]let item = arr.find((value, index, array)=> value > 2)console.log(item) // 3 arr.fill(target, start, end)是用给定的值,填充一个数组,填完会改变原数组参数: target –待填充的元素, start –开始填充的位置索引, (可选) end –结束填充元素的索引 (可选)1234567let arr = [1, 2, 3, 4] console.log(arr.fill(4)) // [4, 4, 4, 4]console.log(arr.fill(4, 1)) // [1, 4, 4, 4]console.log(arr.fill(4, 1, 2)) // [1, 4, 3, 4] arr.includes()判断数中是否包含给定的值 1234let arr = [1, 2, 3, 4, 5, NaN]console.log(arr.includes(5)) // trueconsole.log(arr.includes(8)) // falseconsole.log(arr.includes(NaN)) // true includes()与indexOf()的区别 indexOf()返回的是数值,而includes()返回的是布尔值 indexOf()不能判断NaN,返回为 -1 ,includes()可以判断 arr.keys() 获取数组的所有键名(遍历) 12345let arr = [1, 2, 3, 4]let arr2 = arr.keys()for (let key of arr2) { console.log(key); // 0,1,2,3} arr.values() 获取数组键值(遍历) 12345let arr = ['apple', 'banner']let arr1 = arr.values()for(let val of arr1) { console.log(val) // apple, banner} arr.entries() 获取数组的键名以及键值(遍历) 1234567891011let arr = [1, 2, 3, 4]let arr1 = arr.entries()for(let [key, val] of arr1) { console.log(key, val)}/* key:0 value:1 key:1 value:2 key:2 value:3 key:3 value:4*/ arr.toString() 将数组元素返回一个字符串,表示指定的数组及其元素 12let arr = [1, 2, 3, 4]console.log(arr.toString()) // 1,2,3,4","categories":[],"tags":[],"author":"jetBn"},{"title":"Git常用命令记录","slug":"t常用命令记录","date":"2020-05-09T07:34:00.000Z","updated":"2020-05-09T07:37:48.640Z","comments":true,"path":"2020/05/09/t常用命令记录/","link":"","permalink":"http://jetbn.github.io/2020/05/09/t%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E8%AE%B0%E5%BD%95/","excerpt":"","text":"git相关命令常用 git checkout -b sub_main origin/sub_main 拉取远程分支到本地分支 并且切换到该分支 git branch 查看本地所有分支 git branch -a 查看远程本地所有分支 git branch -d <分支名称> 查看远程本地所有分支 git pull 拉取最新的代码都本地 git commit -m 进行commit操作 git add . 进行添加到暂存区 git push origin <分支名称>:<远程分支名称> 将本地分支推送到远程分支 git push -u origin mybranch 将本地分支推送到远程分支 $ git push -u origin master 上面命令将本地的master分支推送到origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。","categories":[],"tags":[],"author":"jetBn"},{"title":"JS节流与防抖","slug":"与防抖","date":"2020-05-09T06:52:00.000Z","updated":"2020-05-09T07:28:46.539Z","comments":true,"path":"2020/05/09/与防抖/","link":"","permalink":"http://jetbn.github.io/2020/05/09/%E4%B8%8E%E9%98%B2%E6%8A%96/","excerpt":"","text":"前言     在日常开发中进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。这时候我们更希望把多次计算合并成一次,只操作一个精确点来减少调用频率,同时又不影响实际效果。 JS把这种方式称为debounce(防抖)和throttle(节流)。 节流    高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率 应用场景 1. DOM元素的拖拽功能实现(mousemove) 2. 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)3. 计算鼠标移动的距离(mousemove) 4. 搜索联想(keyup) 5. 滚动事件scroll,(只要页面滚动就会间隔一段时间判断一次) 代码实现 12345678910111213 function debounce(fn, delay, scope) { let timer = null; // 返回函数对debounce作用域形成闭包 return function () { // setTimeout()中用到函数环境总是window,故需要当前环境的副本; let context = scope || this, args = arguments; // 如果事件被触发,清除timer并重新开始计时 clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay); }} 代码解读 1. 第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码; 2. 当第二次调用该函数时,它会清除前一次的定时器并设置另一个; 3. 如果前一个定时器已经执行过了,这个操作就没有任何意义; 4. 然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器;目的是只有在执行函数的请求停止了delay时间之后才执行。 防抖    触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 应用场景 1. 手机号、邮箱输入检测 2. 搜索框搜索输入(只需最后一次输入完后,再放松Ajax请求) 3. 窗口大小resize(只需窗口调整完成后,计算窗口大小,防止重复渲染) 4. 滚动事件scroll(只需执行触发的最后一次滚动事件的处理程序) 5. 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,(停止输入后)验证一次就好 防抖实现 利用时间戳简单实现 123456789101112function throttle(fn, threshold, scope) { let timer; let prev = Date.now(); return function () { let context = scope || this, args = arguments; let now = Date.now(); if (now - prev > threshold) { prev = now; fn.apply(context, args); } }} 利用定时器简单实现 123456789101112function throttle2(fn, threshold, scope) { let timer; return function () { let context = scope || this, args = arguments; if (!timer) { timer = setTimeout(function () { fn.apply(context, args); timer = null; }, threshold) } }} 总结 防抖和节流有很多实现的方法,其大致思路基本是这样的。 防抖和节流是不同的,因为节流虽然中间的处理函数被限制了,但是只是减少了频率,而防抖则把中间的处理函数全部过滤掉了,只执行规判定时间内的最后一个事件。 防抖和节流各自有自己特点,需要在不同场景进行选择,不能混合乱使用。","categories":[{"name":"Web前端","slug":"Web前端","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/"}],"tags":[],"author":"jetBn"},{"title":"你不知道的CSS ","slug":"不知道的CSS","date":"2020-04-30T05:42:00.000Z","updated":"2020-04-30T07:22:17.122Z","comments":true,"path":"2020/04/30/不知道的CSS/","link":"","permalink":"http://jetbn.github.io/2020/04/30/%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84CSS/","excerpt":"","text":"前言     CSS的世界是神奇的。当今所以不管移动端还是PC端网页都是离不开CSC(层叠样式表),像类选择,id选择等等这些选择器 我们工作都有用到,但是难免忽略一些,CSS中更强大的属性我们很少用到,特此记录一些。 用~/ +兄弟选择器来美化表单元素 ~选择器:查找某一个元素的后面的所有兄弟元素 +选择器:查找某一个元素的后面紧邻的兄弟元素 用font-size:0来清除间距     inline-block的元素之间会受空白区域的影响,也就是元素之间差不多会有一个字符的间隙。如果在同一行内有4个25%相同宽度的元素,会导致最后一个元素掉下来(如图)。你可以利用元素浮动float,或者压缩html,清除元素间的空格来解决。但最简单有效的方法还是设置父元素的font-size属性为0。 用 overflow 來清除浮动(创建块级格式化上下文) 通过触发BFC方式,实现清除浮动(父元素添加overflow:hidden) 12345.fahter{ width: 400px; border: 1px solid deeppink; overflow: hidden; } 用pointer-event来禁用事件 阻止任何点击动作的执行 使链接显示为默认光标(cursor:default) 阻止触发hover和active状态 阻止JavaScript点击事件的触发 123 //使用该类,任何点击事件将无效.disabled { pointer-events: none; } 用max-width来防止图片撑破容器 针对内容性的文案,图片大小都是未知的,为了防止图片过大而撑破容器,可以通过设置图片的max-width:100%来处理。 1234img { display:inline-block; max-width: 100%; } 实现三角形 新建一个元素,将它的宽高都设置为0;然后通过设置border属性来实现三角形效果 12345678910111213141516 .arrow{ width: 0; height: 0; /*直角三角形*/ /* border-width: 50px 50px 0 0; border-style: solid; border-color: transparent #000 transparent transparent; */ /*向下三角形*/ /* border-width: 100px 100px 0 100px; border-style: solid; border-color: brown transparent transparent transparent; */} 移动端禁止用户长按文字选择功能 12345678 .unselect { -webkit-touch-callout:none; -webkit-user-select:none; -khtml-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none} 文字模糊 1234 .blur { color: transparent; text-shadow: 0 0 5px rgba(0, 0, 0, 0.5)} 超出N行显示省略号 123456 .hide-text-n { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: n; overflow: hidden} 硬件加速 写transition、animation时,请用transform代替left、top等属性,从而使动画更流畅 1234567 cube { -webkit-transform: translateZ(0); -moz-transform: translateZ(0); -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0)} 移动端屏幕旋转时,字体大小不改变 12345 html, body, form, p, div, h1, h2, h3, h4, h5, h6 { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; text-size-adjust: 100%} 锚点跳转平滑过渡 123body { scroll-behavior: smooth; } 最后一个元素不需要边框、边距等123ul > li:not(:last-child) { border-bottom: 1px solid #c5b7b7} 清除浮动(适应伪类)1234567.clearfix::after { content: ''; display: block; height: 0; visibility: hidden; clear: both} CSS选择器选基数项、偶数项、倍数分组项12345678910/* 基数 */.selector:nth-child(2n-1) {}/* 偶数 */.selector:nth-child(2n) {}/* 倍数分组项 */.selector:nth-child(3n+1) {} /* 匹配第1、4、7、10... */.selector:nth-child(3n+5) {} /* 匹配第5、8、11、14... */.selector:nth-child(5n-1) {} /* 匹配第4、9、13、17... */ 当输入框的value的长度不为0时,显示搜索按钮用到placeholder-shown伪类,简单来说就是当input标签有了placeholder属性并且内容不为空时,会触发该状态,但是当input标签有了value值之后,就会消除该状态,也要配合:not选择器 html 1234<div class="input-line"> <input type="text" placeholder="请输入关键字进行搜索"> <button type="button" class="search-btn">搜索</button></div> css 12345678.search-btn { opacity: 0; transition: all .5s ease-in-out} input:not(:placeholder-shown)~.search-btn { opacity: 1 }","categories":[{"name":"Web前端","slug":"Web前端","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/"}],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://jetbn.github.io/tags/CSS/"}],"author":"jetBn"},{"title":"Vue3.0尝试","slug":"Vue3-0尝试","date":"2020-04-29T02:59:17.000Z","updated":"2020-04-30T02:09:54.002Z","comments":true,"path":"2020/04/29/Vue3-0尝试/","link":"","permalink":"http://jetbn.github.io/2020/04/29/Vue3-0%E5%B0%9D%E8%AF%95/","excerpt":"","text":"前言     有关vue2的相关相关语法我相信大家都已经的语法我相信大家都已经会了,现在都已经2020年了vue作为前端这么热门的框架, 不去学一波对的自己是做前端的吗?哈哈哈哈🤣🤣🤣。    一路就开始折腾,那是一路曲折啊,重不会到会也不知道自己经历了什么,到现在vue3.x马上又有开始了所以自己也是先试试水,玩了一波相关的语法。     废话不多说直接上代码记录下(由于本人没有用到TS所以都是进行与ES6进行) 开始 在vue-cli3.0下安装 composition-api 123npm install @vue/composition-api --save# ORyarn add @vue/composition-api 在使用任何 @vue/composition-api 提供的能力前,必须先通过 Vue.use() 进行安装 1234import Vue from 'vue'import VueCompositionApi from '@vue/composition-api'Vue.use(VueCompositionApi) 安装完成后就可项目中使用相关composition-api来开发了 setup(入口函数)    setup()函数是vue3中,专门为组件提供的新属性,感觉就跟是跟vue2.x中的data一样需要返回一个Object中包含了自己定义的function, computed, watch以及属性和生命周期。 setup 函数会在 beforeCreate 之后、created 之前执行。 1234setup(props, context) { const state = reactive({count: 0}) // 创建数据 return state // 返回页面中使用} 接收props数据 在props中定义当前组件允许外界传递过来的参数名称: 123 props: { name: String} 通过setup函数的第一个形参,接收 props 数据: 123setup(props) { console.log(props.name)} context形参 setup函数的第二个形参是一个上下文对象,就是vue2.x中的this,在vue 3.x中,它们的访问方式如下 12345 setup(props, context) { context.slots context.emit context.refs} reactive与ref(响应式数据源) reactive()函数接收一个普通的对象,返回一个响应的数据对象。 这等价于vue2.x中的Vue.observable()函数,vue3.x然后就是可以直接用reactive()函数直接创建响应的数据对象。 按需导入reactive相关函数 import { reactive } from '@vue/composition-api' 在setup()的函数中调用reactive()函数,创建对应的响应式数据对象 12345setup() { // 这个类似于vue2中的data()返回的响应式对象 const state = reactive({ count: 0 }) return state} 在template中访问响应式数据 123<template> <span>当前的count值为:{{count}}</span></template> ref()函数用来根据给定值创建一个响应式的数据对象,ref()函数的调用返回值是一个对象,这个对象上只包含一个value属性。 导入相关ref相关函数 import { ref } from '@vue/composition-api' 创建响应式对象 1234567setup() { const count = ref(0) return { count, name: ref('vue') }} 在template中访问响应式数据123<template> <span>{{count}}--- {{name}}</span></template> isRef的使用 ,isRef()函数主要用来判断某个值是否为ref()创建出来的对象;应用场景:当需要展开某个可能为ref()创建出来的值得时候,例如: 123import { isRef } from '@vue/composition-api'const fooData = isRef(foo) ? foo.value : foo toRefs的使用,toRefs()函数可以将reactive()创建出来的响应式对象,转为普通对象,只不过这个对象上的属性节点都是以ref()类型的像是数据, 最常见应用场景 12345678910111213141516171819202122import { toRefs, reactive } from '@vue/composition-api'setup() { // 定义响应式数据对象 const state = reactive({ count: 0 }) // 定义页面上可用的事件处理函数 const increment = () => { state.count++ } // 在 setup 中返回一个对象供页面使用 // 这个对象中可以包含响应式的数据,也可以包含事件处理函数 return { // 将 state 上的每个属性,都转化为 ref 形式的响应式数据 ...toRefs(state), // 自增的事件处理函数 increment }} 在template中就直接可以使用count属性和相对应的increment方法了,如果没有使用roRefs直接返回state那么就得通过state.xx来访问数据 123456<template> <div> <span>当前的count值为:{{count}}</span> <button @click=\"increment\">add</button> </div></template> computed与watch(计算属性与监听) computed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入: 1import { computed } from '@vue/composition-api' 创建只读的计算属性,在调用computed()函数的时候,传入一个function函数,可以得到一个只读的计算属性。 123456789// 创建一个响应式数据const count = ref(1) // 根据count的值创建一个响应式的计算属性, 它会根据ref自动计算并且返回一个新的refconst computedCount = computed(() => count.value + 1 ) console.log(computedCount.value) // 打印 2computedCount.value++ //报错 创建可读可写的计算属性 在调用computed()函数的时候传入一个包含get和set的对象,就可以得到一个可读可写的计算属性了。 123456789101112// 创建一个响应式数据const count = ref(1) // 根据count的值创建一个响应式的计算属性, 它会根据ref自动计算并且返回一个新的refconst computedCount = computed({ get: () => count.value + 1, set: (val) => count.value = val - 1} ) computedCount.count = 6console.log(count.value) // 打印 5 2.watch() 函数用来监听数据的变化,跟vue2.x中的是一样的不过在这得像computed的使用方法一样导入相关api方法 使用前导入 import { watch } from '@vue/composition-api' 基本用法 1234567891011const count = ref(0)// 定义watch只要count的值变化,就会触发watch的回调// watch 会在创建的时候自动调用一次watch(() => console.log(count.value))setTimeout(() => { count.value++}, 1000) 监听指定数据 12345678910111213/ 定义reactive数据源const state = reactive({ count: 0 })// 监视 state.count 这个数据节点的变化watch(() => state.count, (now, prev) => { console.log(now, prev)})/ 定义ref数据源const count = ref(0)// 监视count这个数据的变化watch(count, (now, prev) => { console.log(now, prev)}) LifeCycle Hooks(生命周期)在新版中的生命周期需要按需导入,并且只能写setup()函数中。 使用onBeforeMount, onMounted, updated相关生命周期,使用前导入相关api方法 1import { onBeforeMount, onMounted, updated } from '@vue/composition-api' 1234567891011setup () { onBeforeMount(() => { console.log('onBeforeMount!') }) onMounted(() => { console.log('onMounted!') }) updated(() => { console.log('updated!') }) } 相关每个生命周期方法都是传入一个function函数。 vue2.x与新版Composition API之间的映射关系 beforeCreate -> setup created -> setup beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted errorCaptured -> onErrorCaptured provide & inject(数据共享)provide()和 inject()可以实现嵌套组件之间的数据传递。这个两个函数只能在setup()函数中使用。父级组件中使用provide()函数可以使数据向下传递,子组件中使用inject()接收上层传过来的数据。 实现代码: 根组件(父组件)parent.vue 1234567891011121314151617181920212223<template> <div> <child-one></child-one> <child-two></child-two> </div></template><script> import { provide } from '@vue/composition-api' import ChildOne from '@/components/Child' import ChildTwo from '@/components/Child' export default { components: { ChildOne, ChildTwo }, setup() { // provide('要共享的数据名称', 被共享的数据) provide('globalName', 'vue') } }</script> 子组件1 ChildOne.vue 12345678910111213141516171819<template> <div> <!--页面展示数据globalName --> {{globalName}} </div></template><script> import { inject } from '@vue/composition-api' export default { name: 'ChildOne', setup() { const globalName = inject('globalName') 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据 return { globalName } } }</script> 子组件2 ChildTwo.vue 12345678910111213141516171819<template> <div> <!--页面展示数据globalName --> {{globalName}} </div></template><script> import { inject } from '@vue/composition-api' export default { name: 'ChildTwo', setup() { const globalName = inject('globalName') 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据 return { globalName } } }</script> provide函数被共享的数据可以使ref和reactive定义的响应式数据,用法类似 template refs(元素组件)我们知道在vue2.x中获取页面元素的DOM可以通过ref写在页面元素上然后在js中通过$refs.x来拿取当前元素的DOM元素信息,操作DOM,在composition-api中我们通过提供的ref方法传入一个null并且定义与页面元素上ref相对应。 代码实现: 12345678910111213141516171819202122232425262728<template> <div> <h1 ref=\"h1Ref\">Hello Word</h1> </div></template><script>import { ref, onMounted } from '@vue/composition-api'export default { setup() { // 创建一个 DOM 引用 const h1Ref = ref(null) // 在 DOM 首次加载完毕之后,才能获取到元素的引用 onMounted(() => { // 为 dom 元素设置字体颜色 // h1Ref.value 是原生DOM对象 console.log(h1Ref.value) }) // 把创建的引用 return 出去 return { h1Ref } }}</script> 结尾    感觉composition-api都是以导入的形式使用,感觉没有直接vue2.x中直接使用的方便哈😂。虽然都是说按需引入,使用想用的方法。对比了与vue2.x的各种写法感觉突然转过来有很大不适应,写的各种数据方法都要在setup的入口函数中导出才能在页面上使用。虽然vue2.x定义的数据也需要通过data函数返回值然后在页面中使用,但是方法不用啊,什么计算属性监听也都是不用的啊。可能是自己适应了vue2.x的各种写法,感觉用起来各种爽。转变到vue3.x也就是现在的composition-api还需要点时间。 最后自己还是有个问题没有解决有大佬给我思路吗?就是子父组件的通信怎么写?之前版本是通过$emit进行,我发现这setup函数中写了父组件拿不到。 最后贴上composition-api官方文档 https://vue-composition-api-rfc.netlify.com","categories":[{"name":"Web前端","slug":"Web前端","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/"},{"name":"Vue","slug":"Web前端/Vue","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://jetbn.github.io/tags/Vue/"}]},{"title":"Shopify插件开发入坑体验","slug":"Shopify插件开发入坑体验","date":"2020-04-29T02:58:15.000Z","updated":"2020-04-30T02:39:34.782Z","comments":true,"path":"2020/04/29/Shopify插件开发入坑体验/","link":"","permalink":"http://jetbn.github.io/2020/04/29/Shopify%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%85%A5%E5%9D%91%E4%BD%93%E9%AA%8C/","excerpt":"","text":"前言    由于公司的需求开发Shopify的应用插件自己特地记录下。简单的介绍下Shopify,它是由托比亚斯·卢克创办的加拿大电子商务软件开发商,总部位于加拿大首都渥太华,其提供的服务软件Shopify是一个SaaS领域的购物车系统,适合跨境电商建立独立站,用户支付一定费用即可在其上利用各种主题/模板建立自己的网上商店。     由于是国外的官网文档也都是英文,看起来有点吃力哦(小生英文水平有限🤣🤣)。不过之后应该有中文了现在很多页面有中文了已经。     该应该用插件官方推荐是用React的next.js服务端渲染框架以及node.js作为后端语言使用GraphQL开发。作为没接触过GraphQL的我赶紧补了一波知识。后来我一路跌跌撞撞终于现在是了解了一个大概。最终我还是使用node作为主入口程序,验证应用插件获取相关的access_token和商店地址。剩下的请求什么Shopify接口也没有用GraphQL而是使用了ResfulApi让后端工作人员去操作了,然后我请求后端接口进行一系列操作。(最后熟悉了,什么验证都是丢给后端操作了,前端我也就可以使用各种框架了而不是单单只能使用next.js)。 注册获取开发前提要素 创建Shopify开发者账号如图在https://developers.shopify.com/网站注册相关的账号。 在相关partners的页面创建商店(以供后面开发应用使用)以及应用 在创建应用的有自定义应用和公共应用如图: 一般我相信我我们都是创建的公共应用,到目前我开发的应用也都是公共类型,在创建应用的时候URL和相关的重定向URL都是必须写的因为我也没有注册域名啥的吧,所以此时是用了ngrok内网穿透,在官方的开发介绍中也是使用这个。在此我们填写的URL就要和你使用ngrok暴露出去的地址对应了,不过使用node的koa框架有个专门的中间件也是官方使用的重定向地址都是域名后加上了shopify/auth 例如: URL:https://30aca829.ngrok.io, 重定向URL:https://30aca829.ngrok.io/shopify/auth/(电脑重启重新暴露出去这个连个地址都是要重新填一遍,然后koa的中间件就会跳转到https://30aca829.ngrok.io/shopify/auth/进行相关的验证操作。(这在后面的入口)。序中会上代码,然后在后期我们没有用koa作为入口这个地址也是可以自己想怎么填就怎么填)。 创建完成(拿取相关的密钥很重要!!很重要!!作为开发读取数据和请求官方api使用) 差不多了前提就这些步骤,注册成功之后填写好接下来就是一系列代码操作了。 编写开发环境程序 创建项目目录(sample-app),并使用npm初始化项目目录 1npm init -y 安装相关依赖 1npm install --save react react-dom next 因为是用next.js所以不熟悉的还得看看官方文档 https://nextjs.frontendx.cn/ 创建文件pages并在下面新建index.js 1234567const Index = () => ( <div> <p>Sample app using React and Next.js</p> </div>);export default Index; 添加相关运行命令打开package.json文件添加 1234567891011{ "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1", "dev": "next", "build": "next build", "start": "next start" }}``` 运行开发环境 npm run dev 123 到这里应该是创建一个`next.js`项目然后接下来对接到`Shopify`4. 使用`ngrok`暴露出去自己的`3000`端口因为`next.js`启动的默认是`3000`端口 ngrok http 3000 12345  然后在自己创建的应用中设置中添加`对应的url`(得用`https`的)就在上面所说的填写`url` 5. 使用`node`的 `koa`来进行渲染页面操作 1. 创建`env`环境变量文件并写入在上面创建的应用的`KEY` SHOPIFY_API_KEY='YOUR API KEY FROM SHOPIFY PARTNERS DASHBOARD' SHOPIFY_API_SECRET_KEY='YOUR API SECRET KEY FROM SHOPIFY PARTNERS DASHBOARD' 12. 安装相关`Shopify`的验证`koa`中间件 npm install --save koa @shopify/koa-shopify-auth dotenv koa-session isomorphic-fetch 13. 创建`server.js`写入相关验证代码 require('isomorphic-fetch'); const dotenv = require('dotenv'); const Koa = require('koa'); const next = require('next'); const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth'); const { verifyRequest } = require('@shopify/koa-shopify-auth'); const session = require('koa-session'); dotenv.config(); // graphql的相关中间件 const { default: graphQLProxy } = require('@shopify/koa-shopify-graphql-proxy'); const { ApiVersion } = require('@shopify/koa-shopify-graphql-proxy'); const port = parseInt(process.env.PORT, 10) || 3000; const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); const { SHOPIFY_API_SECRET_KEY, SHOPIFY_API_KEY } = process.env; // 环境变量里读取 api-key与api-secret-key app.prepare().then(() => { const server = new Koa(); server.use(session(server)); server.keys = [SHOPIFY_API_SECRET_KEY]; server.use( createShopifyAuth({ apiKey: SHOPIFY_API_KEY, secret: SHOPIFY_API_SECRET_KEY, scopes: ['read_products', 'write_products'], //填写相关应用api相关请求的权限 afterAuth(ctx) { const { shop, accessToken } = ctx.session; // 通过session拿取相关商店地址以及请求api需要的accessToken ctx.cookies.set('shopOrigin', shop, { httpOnly: false }); ctx.redirect('/'); // 重定向到index首页 }, }), ); server.use(verifyRequest()); server.use(async (ctx) => { await handle(ctx.req, ctx.res); ctx.respond = false; ctx.res.statusCode = 200; return }); server.use(graphQLProxy({version: ApiVersion.October19})) // 这里填写相关api的版本 server.listen(port, () => { console.log(`> Ready on http://localhost:${port}`); // 监听端口 }); }); 14. 修改`package.json`文件使用我们的`server.js`来启动项目 { "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1", "dev": "node server.js", "start": "NODE_ENV=production node server.js", "build": "next build", } } 1234现在我们启动项目并且用`ngrok`暴露出去的域名就能看到这里还要在域名的`shop`填写我们的商店地址例如我自己的: https://e44132cd.ngrok.io/auth/inline?shop=jetbn.myshopify.com 1234567891011 一切填写就绪之后一`enter`页面就自动跳转了。 最后展示的页面: ## 折腾其他框架尝试&ensp;&ensp;&ensp;&ensp;前前后后都是使用`React`的`next.js`开发了两三个应用,感觉就是开发的时候太麻烦了,总要内网穿透,而且展示的页面都是`Shopify`的的自己平台上,而且开发写完代码等它响应过来还很慢。就这原因我又开始折腾了寻求其他的方案看看有没有能在自己开发完了再到它那上面,而且让验证`Shopify`的一系列操作让我们的后端小哥来操作。最终我前端选择`Vue`并且验证都放后端了,这样我就能像平常开发`Vue`项一样了。下面介绍下我纯前端进行` Shopify`的验证操作使用`vue`。(前提要素内网穿透,不过开发的时候不需要)1. 添加安装应用路由 { path: '/shopify/install', beforeEnter(to, _from, next) { if (to.query.shop) { //要在域名后添加开发的商店地址 const shop = to.query.shop, scopes = 'read_orders,read_products,write_products', // api权限 // 重定向地址就是在创建应用的时候填写的第二个(重定向URL可以自己随意写了),我这里是域名加/shopify/auth redirect_uri = 'https://' + process.env.VUE_APP_ROOT_URL + '/shopify/auth', // 拼接安装应用地址需要SHOPIFY_API_KEY我填写在我的.env文件中了 install_url = 'http://' + shop + '/admin/oauth/authorize?client_id=' + process.env.VUE_APP_SHOPIFY_API_KEY + '&scope=' + scopes + '&redirect_uri=' + redirect_uri // 本地跳转安装地址 window.location = install_url } else { next({ path: '/error' }) } } }, 12 2. 重定向验证路由 { path: '/shopify/auth', beforeEnter(to, _from, next) { // 通过回调的url获取相关的参数 const shop = to.query.shop, hmac = to.query.hmac, code = to.query.code // 使用SHOPIFY_API_SECRET_KEY验证 并且之后拿取access_token(这步没写) if (shop && hmac && code) { const map = Object.assign({}, to.query) delete map['signature'] delete map['hmac'] const message = querystring.stringify(map) const encrypted = crypto.createHmac('sha256', process.env.VUE_APP_SHOPIFY_API_SECRET_KEY) .update(message) .digest('hex') // const providedHmac = Buffer.from(hmac, 'utf-8') // const generatedHash = Buffer.from(encrypted, 'utf-8') let hashEquals = false try { // later: Auth fails with `crypto.timingSafeEqual` // hashEquals = crypto.timingSafeEqual(generatedHash, providedHmac) hashEquals = hmac === encrypted } catch (e) { hashEquals = false } if (!hashEquals) { next({ path: '/error' }) } else { next('/') } } else { next({ path: '/error' }) } } } `Vue`这相关的验证方案也是从`Github`上面捞的,特地记录下。具体地址忘了,有需要自己可以搜搜。🤣 总结    经过一段时间的熟悉,自己也算又掌握一项新东西了。刚开始这个Shopify插件应用开发真的是巨坑啊,虽然官方有文档流程,但是都是英文的而且基础的搭建都是api都是用GraphQL然后我自己尝试了都是各种超时操作,请求不到最后就自己弃用了,用ResfulApi。然后就是遇到问题啊啥的都跟本搜不到,我想着这也太难了我,然后只能硬着头皮去官方文档里找,最后到现在也算是起起伏伏终于都熟悉了,搞懂了。      这之后发现了英文水平太重要了。虽然开发文档基本内容能看懂,但是都是半猜半疑的,然后翻译一波。(各种不准确),然后开始补英文吧。🤞      最后放上几个Shopify开发常用的官方文档地址:     Shopify Partners(创建应用查看应用): https://vue-composition-api-rfc.netlify.com     Shopify Developers(官方开发文档): https://developers.shopify.com(搭建参考文档)     Shopify Polaris(官方UI框架): https://developers.shopify.com","categories":[{"name":"Web前端","slug":"Web前端","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/"},{"name":"React","slug":"Web前端/React","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/React/"}],"tags":[{"name":"Shopify","slug":"Shopify","permalink":"http://jetbn.github.io/tags/Shopify/"},{"name":"React","slug":"React","permalink":"http://jetbn.github.io/tags/React/"},{"name":"Next.js","slug":"Next-js","permalink":"http://jetbn.github.io/tags/Next-js/"}]},{"title":"mpvue入坑记录","slug":"mpvue入坑记录","date":"2020-04-29T02:57:09.000Z","updated":"2020-04-30T02:39:23.172Z","comments":true,"path":"2020/04/29/mpvue入坑记录/","link":"","permalink":"http://jetbn.github.io/2020/04/29/mpvue%E5%85%A5%E5%9D%91%E8%AE%B0%E5%BD%95/","excerpt":"","text":"使用MpVue开发小程序记录        当下轻应用,遍布各大互联网公司。像百度、支付宝、微信、字节都是有自己的小程序形式,就现在QQ也推出了小程序的形式,像这种轻应用又不需要安装,就能打开,而且也类似了app形式。当然相对于形式也不能说不会开发吧,该学的还是得学。所以现在也有很多基于框架延伸而出的打包生成多个平台的小程序,例如 基于react的taro、还有wepy、mpvue目前应该这个三个最火热了,然后我尝试了mpvue开发了过程不说坑还是有的。自己也是一步步解决,所以决定记录一下。 首先就搭建了项目使用相关环境自己也是全部尝试了一遍安装了scss环境以及vuex,好像有解决的vue-router自己没有尝试. 1.1 首先讲下scss的mixin我在全局的时候引入在页面页面中能用,为什么在组件中用不了这是啥我也不是清楚。还有就是我在scss中使用font字体的问题,在此中我通过本地的方式引入发现就是加载不了,后来寻找方案在交流群中得知是得将文件放在static文件下并且使用绝对路径的方式引用,是有效不报错了。但是小程序打包又上传了不了,后来我就直接干脆放服务器上了,再通过引用,这下总没问题了。 1.2 再次就是vuex在main.js中引用在vue的原型上加而不是像vue-cli中使用一样 直接写在new VUE中。 在main.js中定义 12345678910import Vue from 'vue'import App from './App'import store from './store'Vue.config.productionTip = falseApp.mpType = 'app'Vue.prototype.$store = storeconst app = new Vue(App)app.$mount() 在页面中使用 1this.$store.state 也可以使用vuex的mapState引用某个state 12345678// 头部导入import { mapState } from 'vuex'// 在计算属性中...mapState([ 'carBrandCityPrev' // 对应stae中定义的]), 自定义TabBar自己刚开始使用也是一头水雾,微信小程序更新的很快。我之前写的一个小程序用的是原生开发的时候,那时候还没有什么定义TabBar啊、导航栏、组件啊什么的。后来我也是看了下官方的demo以及github中mpuve项目的issue中找解决方案,最终还是直接上手搞了一波。中途也是坎坎坷坷。 贴个自定义tabbar地址: https://github.com/jetBn/mpvue-custom-tab-bar 小程序中获取地理位置授权问题,只有在第一次进入的会提示是否授权如果拒绝了授权第二进入都不会授权,都会走授权位置的fail方法,所以这个原因问题。我们只能是通过微信的获取用户的当前设置检查 wx.getSettingapi去检查当前是否授权,通过此方法放在fail方法中检测当前是否开启授权地理位置,如果没有重新调用地理位置授权的方法。 相关检查是否授权方法: 12345678910111213141516171819202122232425262728293031323334353637383940414243// fn指相关重新授权的方法 export function checkGetLocationPermison (fn) { wx.getSetting({ success: (res) => { const status = res.authSetting if (!status['scope.userLocation']) { wx.showModal({ title: '是否授权当前位置', content: '需要获取您的地理位置,请确认授权,否则地图功能将无法使用获取不了优惠券信息', success: (subRes) => { if (subRes.confirm) { wx.openSetting({ success: (subRes) => { if (subRes.authSetting['scope.userLocation'] === true) { wx.showToast({ title: '授权成功', icon: 'success', duration: 1000 }) fn() } else { wx.showToast({ title: '授权失败', icon: 'success', duration: 1000 }) } } }) } } }) } }, fail: () => { wx.showToast({ title: '调用授权窗口失败', icon: 'success', duration: 1000 }) } }) } 在mpvue是axios全局处理请求返回值以及请求头设置等等。在npm i axios qs后新建request.js文件,引入qs处理axios中post请求,然后微信小程序中是使用wx.request发送请求的并不是直接使用axios就可以的,所以使用axios的adapter 属性进行我们的请求操作。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// 创建axios实例const Axios = axios.create({ baseURL: '', // 设置请求域名地址 timeout: 1000 * 60 * 10, // 设置请求超时时间 responseType: 'json' // 设置返回值类型})// 然后使用Axios实例的adapter进行封装微信请求Axios.defaults.adapter = (config) => { return new Promise((resolve, reject) => { let data = config.method === 'get' ? config.params : qs.stringify(config.params) wx.request({ url: config.url, method: config.method, header: {'Content-type': 'application/x-www-form-urlencoded'}, data, success: (res) => (resolve(res)), fail: (err) => (reject(err)) }) })}// 请求拦截Axios.interceptors.request.use((request) => { return request}, (error) => { return Promise.reject(error)})// 响应拦截Axios.interceptors.response.use((response) => { return response}, (error) => { return Promise.reject(error)})// 导出响应的方法export function fetch (options) { return new Promise((resolve, reject) => { Axios(options) .then(response => { resolve(response) }) .catch(error => { reject(error) }) })} 相关自定义头部导航栏根据相关设备判断设置高度,相关计算是顶部状态栏的高度加上相关定义的高度。 12345678910111213141516//微信获取相关设备信息的APIwx.getSystemInfo({ success (system) { // console.log(`system:`, system) self.statusBarHeight = system.statusBarHeight self.platform = system.platform const windowHeight = system.windowHeight let platformReg = /ios/i if (platformReg.test(system.platform)) { self.titleBarHeight = 44 // iso的高度 } else { self.titleBarHeight = 48 //android 的高度 } self.navBarHeight = self.statusBarHeight + self.titleBarHeight }}) mpvue中使用组件式封装 然后是内容使用插槽形式,在使用组件的使用插槽内的内容数据更新页面不会实时更新 。(父组件插槽内容块中使用的也是组件封装的组件)(问题1)。然后我使用了 不封装组件 直接写页面样式点击组件的事件会触发插槽中的事件???(问题2) 事件跟插槽中的事件不在同一个div中。最后就是不用插槽才解决了上述问题1,2 npm run dev 会卡在node build/dev-server.js wx 最后我自己也是在官方的github仓库中的issu中找到解决方案在build文件夹的build.js中require('./check-versions')()注释掉然后也就能正常运行了。具体的好像是在经过check-versions函数的时候执行特别的慢。        最后总结下MpVue这个相对于vue针对于小程序的框架,大部分都是使用vue相关语法所以对于熟悉vue的来说还是很容易上手的。开发效率也还可以。感觉会使用vue再次熟悉微信小程序官方的API开发效率还是很高的,不过还是有挺多坑的,我这边坑可能是没有遇到很多。很幸运,具体的相关的语法情况可以对应官方网站的介绍情况。        最后附上相关网站地址:        官方网站:http://mpvue.com        github相关仓库地址: https://github.com/Meituan-Dianping/mpvue","categories":[{"name":"Web前端","slug":"Web前端","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/"},{"name":"Vue","slug":"Web前端/Vue","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/Vue/"},{"name":"小程序","slug":"Web前端/Vue/小程序","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/Vue/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"}],"tags":[{"name":"小程序","slug":"小程序","permalink":"http://jetbn.github.io/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"}]}],"categories":[{"name":"Web前端","slug":"Web前端","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/"},{"name":"Vue","slug":"Web前端/Vue","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/Vue/"},{"name":"React","slug":"Web前端/React","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/React/"},{"name":"小程序","slug":"Web前端/Vue/小程序","permalink":"http://jetbn.github.io/categories/Web%E5%89%8D%E7%AB%AF/Vue/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"}],"tags":[{"name":"CSS","slug":"CSS","permalink":"http://jetbn.github.io/tags/CSS/"},{"name":"Vue","slug":"Vue","permalink":"http://jetbn.github.io/tags/Vue/"},{"name":"Shopify","slug":"Shopify","permalink":"http://jetbn.github.io/tags/Shopify/"},{"name":"React","slug":"React","permalink":"http://jetbn.github.io/tags/React/"},{"name":"Next.js","slug":"Next-js","permalink":"http://jetbn.github.io/tags/Next-js/"},{"name":"小程序","slug":"小程序","permalink":"http://jetbn.github.io/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"}]}