- iphoneX兼容之自定义底部菜单
当我们需要自定义底部导航栏时 首先要解决iphoneX的底部大横条对这个兼容 通常不设置兼容 都会被挡住 如何编写 在你要编写的底部菜单中插入 样式 [代码]padding-bottom[代码][代码]: calc(env(safe-area-inset-[代码][代码]bottom[代码][代码]) / [代码][代码]2[代码][代码]) 即可兼容 [代码] [代码] 例如:css中插入[代码] [代码]@supports ([代码][代码]bottom[代码][代码]: constant(safe-area-inset-[代码][代码]bottom[代码][代码])) or ([代码][代码]bottom[代码][代码]: env(safe-area-inset-[代码][代码]bottom[代码][代码])) {[代码][代码] [代码][代码].fixed-wrap {[代码][代码] [代码][代码]height[代码][代码]: calc(env(safe-area-inset-[代码][代码]bottom[代码][代码]) / [代码][代码]2[代码][代码]);[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码] [代码][代码]}[代码] [代码] [代码][代码].fixed-pay {[代码][代码] [代码][代码]padding-bottom[代码][代码]: calc(env(safe-area-inset-[代码][代码]bottom[代码][代码]) / [代码][代码]2[代码][代码]);[代码][代码] [代码][代码]}[代码] [代码]}[代码]其中 [代码]env(safe-area-inset-[代码][代码]bottom[代码][代码]) 是计算兼容的高度 通常一半即可 [代码] calc 是计算css 你也可以加入高度 假设有第二层 底部固定栏【即底部导航栏上面还有一层固定栏】 可如下编写 view.footer { bottom: calc(100rpx + env(safe-area-inset-bottom)); } 这样轻轻松松解决兼容 不需要写js代码 <-------------大横条-------------> [图片]
2019-05-28 - 深入解析JS的异步机制
1. JavaScript定义 JavaScript 是一种单线程编程语言,这意味着同一时间只能完成一件事情。也就是说,JavaScript 引擎只能在单一线程中处理一次语句。 优点:单线程语言简化了代码编写,因为你不必担心并发问题,但这也意味着你无法在不阻塞主线程的情况下执行网络请求等长时间操作。 缺点:当从 API 中请求一些数据。根据情况,服务器可能需要一些时间来处理请求,同时阻塞主线程,让网页无法响应。 2. 异步运行机制 CallBack,setTimeOut,ajax 等都是通过**事件循环(event loop)**实现的。 2.1 什么是Event Loop? 主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。 2.2 流程整体示意图 [图片] 2.3 总结异步运行到整体机制 主线程在运行的时候,将产生堆(heap)和栈(stack),栈中的代码会调用各种外部API,它们将在"任务队列"中根据类型不同,分类加入到相关任务队列中,如各种事件等。只要栈中的代码执行完毕,主线程就会去读取"任务队列",根据任务队列的优先级依次执行那些事件所对应的回调函数。这就是整体的事件循环。 2.4 任务队列的优先级 微任务队列中的所有任务都将在宏队列中的任务之前执行。也就是说,事件循环将首先在执行宏队列中的任何回调之前清空微任务队列。 ** 举例: ** [代码] console.log('Script start'); setTimeout(() => { console.log("setTimeout 1"); }, 0); setTimeout(() => { console.log("setTimeout 2"); }, 0); new Promise ((resolve, reject) => { resolve("Promise 1 resolved"); }) .then(res => console.log(res)) .catch(err => console.log(err)); new Promise ((resolve, reject) => { resolve("Promise 2 resolved"); }) .then(res => console.log(res)) .catch(err => console.log(err)); console.log('Script end'); [代码] 运行结果是: Script start Script end Promise 1 Promise 2 setTimeout 1 setTimeout 2 通过上述例子可以看到无论宏队列的位置在何方,只要微队列尚未清空,一定会先清空微队列后,在去执行宏队列。下面介绍微队列任务中比较典型的几个API,通过相关举例,让你更深入理解JS的异步机制。 3. 微任务队列 3.1 Promise(ES6) Promise,就是一个对象,用来传递异步操作的消息。 3.1.1 基础用法: [代码] var promise = new Promise(function(resolve, reject) { //异步处理逻辑 //处理结束后,调用resolve返回正常内容或调用reject返回异常内容 }) promise.then(function(result){ //正常返回执行部分,result是resolve返回内容 }, function(err){ //异常返回执行部分,err是reject返回内容 }) .catch(function(reason){ //catch效果和写在then的第二个参数里面一样。另外一个作用:在执行resolve的回调时,如果抛出异常了(代码出错了),那么并不过报错卡死JS,而是会进入到这个catch方法中,所以一般用catch替代then的第二个参数 }); [代码] 缺点: 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。再次,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 优点: Promise能够简化层层回调的写法,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。 3.1.2 用法注意点 - 顺序: [代码] new Promise((resolve, reject) => { resolve(1); console.log(2); }).then(r => { console.log(r); }); [代码] 运行结果是: 2 1 说明: 立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。也就是resolve(1)和console.log(2)是属于同步任务,需要全部执行完同步任务后,再去循环到resolve的then中。 3.1.3 用法注意点 - 状态: [代码] const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000); }); const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000); }); const p3 = new Promise(function (resolve, reject) { setTimeout(() => resolve(new Error('fail')), 1000); }); p2 .then(result => console.log("1:", result)) .catch(error => console.log("2:",error)); p3 .then(result => console.log("3:", result)) .catch(error => console.log("4:",error)); [代码] 运行结果是: 3: Error: fail at setTimeout (async.htm:182) 2: Error: fail at setTimeout (async.htm:174) 说明: p1是一个 Promise,3 秒之后变为rejected。p2和p3的状态是在 1 秒之后改变,p2 resolve方法返回的是 p1, p3 resolve方法返回的是 抛出异常。但由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。 而p3返回的是自身的resolve,所以触发then中指定的回调函数。 3.1.4 用法注意点 - then链的处理: [代码] var p1 = p2 = new Promise(function (resolve){ resolve(100); }); p1.then((value) => { return value*2; }).then((value) => { return value*2; }).then((value) => { console.log("p1的执行结果:",value) }) p2.then((value) => { return value*2; }) p2.then((value) => { return value*2; }) p2.then((value) => { console.log("p2的执行结果:",value) }) [代码] 运行结果是: p2的执行结果: 100 p1的执行结果: 400 说明: p2写法中的 then 调用几乎是在同时开始执行的,而且传给每个 then 方法的 value 值都是 100。而p1中写法则采用了方法链的方式将多个 then 方法调用串连在了一起,各函数也会严格按照 resolve → then → then → then 的顺序执行,并且传给每个 then 方法的 value 的值都是前一个promise对象通过 return 返回的值。 ###3.1.4 用法注意点 - catch的处理: [代码] var p1 = new Promise(function (resolve, reject){ reject("test"); //throw new Error("test"); 效果同reject("test"); //reject(new Error("test")); 效果同reject("test"); resolve("ok"); }); p1 .then(value => console.log("p1 then:", value)) .catch(error => console.log("p1 error:", error)); p2 = new Promise(function (resolve, reject){ resolve("ok"); reject("test"); }); p2 .then(value => console.log("p2 then:", value)) .catch(error => console.log("p2 error:", error)); [代码] 运行结果是: p2 then: ok p1 error: test 说明: Promise 的状态一旦改变,就永久保持该状态,不会再变了。不会即抛异常又会正常resolve。 3.2 async/await(ES7) 3.2.1 async基础用法: async 用于申明一个 function 是异步的,返回的是一个 Promise 对象。 [代码] async function testAsync() { return "hello async"; } var result = testAsync(); console.log("1:", result); testAsync().then(result => console.log("2:", result)); async function mytest() { //"hello async"; } var result1 = mytest(); console.log("3:", result1); [代码] 运行结果是: 1: Promise {<resolved>: “hello async”} 3: Promise {<resolved>: undefined} 2: hello async 说明: async返回的是一个Promise对象,可以用 then 来接收,如果没有返回值的情况下,它会返回 Promise.resolve(undefined),所以在没有 await 的情况下执行 async 函数,它会立即执行,并不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。 3.2.2 await基础用法: await 只能出现在 async 函数中,用于等待一个异步方法执行完成(实际等的是一个返回值,强调 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果)。 [代码] function getMyInfo() { return Promise.resolve("hello 2019!"); } async function testAsync() { return "hello async"; } async function mytest() { return Promise.reject("hello async"); } async function test() { try { const v1 = await getMyInfo(); console.log("getV1"); const v2 = await testAsync(); console.log("getV2"); const v3 = await mytest(); console.log(v1, v2, v3); } catch (error) { console.log("error:", error); } } test(); [代码] 运行结果是: getV1 getV2 error: hello async 说明: await等到的如果是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。 放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。 3.2.3 async/await的优势: 很多情况下,执行下一步操作是需要依赖上一步的返回结果的,如果当嵌套层次较多的时候,(举例3层的时候): [代码] const getRequest = () => { return promise1().then(result1 => { //do something return promise2(result1).then(result2 => { //do something return promise3(result1, result2) }) }) } [代码] 从上例可以看到嵌套内容太多。此时如果用async写法,可写成如下: [代码] const getRequest = async () => { const result1 = await promise1(); const result2 = await promise2(result1); return promise3(result1, result2); } [代码] 说明: async / await 使你的代码看起来像同步代码,它有效的消除then链,让你的代码更加简明,清晰。 总结:以上就是对JS的异步机制及相关应用的整体总结,如有需要欢迎交流~
2019-03-12 - 从源码看微信小程序启动过程
一、写作背景 接触小程序一年多,真实体验就是小程序开发门槛相对而言确实比较低。不过小程序的开发方式,一直是开发者吐槽的,如习惯了 Vue,React 开发的开发者经常会吐槽小程序一个 Page 必须由多个文件组成,组件化支持不完善或者说不能非常愉快的开发组件。在以前小项目中没太大感觉,从加入有赞,参与有赞微商城小程序的开发,是真切的体会到对于大型小程序项目开发的复杂性。 有赞从微信小程序内测就开始开发小程序,在不支持自定义组件的时代,只能通过 import 的形式拆分模块或实现组件。在业务复杂的页面,可能会 import 非常多的模块,而相应的 wxss 也需要 import 样式,除了操作繁琐,有时候也难免遗漏。 作为开发者,我们当然希望可以让工作更简单,更愉快,也希望改善我们的开发方式。所以希望能够更了解微信小程序框架,减少不必要的试错,于是有了一次对小程序框架的 debug 之旅。(基础库 1.9.93) 通过三周空余时间的 debug,也算对小程序框架有了一些浅显的认识,达到了最初的目的;对小程序启动,实例,运行等有了真切的体会。这篇文章记录了小程序框架的基本代码结构,启动流程,以及程序实例化过程。 本文的目的是希望把我看到的分享给对小程序感兴趣或者正在开发小程序的读者,主要解答“框架对传入的对象等到底做了什么”。 二、从启动流程一窥小程序框架细节 在开发者工具中使用 help() 方法,可以查看一些指令和方法。使用其中的 openVendor 方法可以打开微信开发者工具在小程序框架所在目录。其中以包括以基础库命名的目录和其他帮助文件,如其中有两个工具 wcc,wcsc。wcc 可把 wxml 转换为对应的 JS 函数 —— $gwx(path, global),wcsc 可将 wxss 转换为 css。而基础库目录包括 WAService.js 和 WAWebview.js 文件。小程序框架在开发者工具中以 WAService.js 命名(WAWebview.js 不知其作用,听说在真机环境使用该文件)。 在开发中工具命令行使用 document.head 可以查看到小程序的启动流程大致如下: [图片] 以小节的方式分别介绍这些流程,小程序是如何处理的(小节编号与图中编号相同)。 1、初始化全局变量 下图是小程序启动是初始化的一些全局的变量: [图片] 那些使用“__”开头,未在文档中提及可使用变量是不建议使用的,wxAppCode 在开发者工具中分为两类值,json 类型和 wxml 类型。以 .json 结尾的,其 key 值为开发者代码中对应的 json 文件的内容,.wxml 结尾的,其 key 值为通过调用 $gwx(’./pages/example/index.wxml’) 将得到一个可执行函数,通过调用这个函数可得到一个标识节点关系的 JSON 树。 [图片] 2、加载框架(WAService.js) 使用工具对 WAService.js 进行格式化后进行 debug。可以发现小程序框架大致由: WeixinJSBridge、 NativeBuffer、 wxConsole、 WeixinWorker、 JavaScript兼容(这部分为猜测)、 Reporter、 wx、 exparser、 virtualDOM、 appServiceEngine 几部分组成。 其中除了 wx 和 WeixinJSBridge 这两个基础 API 集合, exparser, virtualDOM, appServiceEngine 这三部分作为框架的核心, appServiceEngine 提供了框架最基本的接口如 App,Page,Component; exparser 提供了框架底层的能力,如实例化组件,数据变化监听,view 层与逻辑层的交互等;而 virtualDOM 则起着链接 appServiceEngine 和 exparser 的作用,如对开发者传入 Page 方法的对象进行格式化再传入 exparser 的对应方法处理。 框架对外暴露了以下API:Behavior,App,Page,Component,getApp,getCurrentPages,definePlugin,requirePlugin,wx。 3、业务代码的加载 在小程序中,开发者的 JavaScript 代码会被打包为 [代码]define('xxx.js', function(require, module, exports, window, document, frames, self, location, navigator, localStorage, history, Caches, screen, alert, confirm, prompt, fetch, XMLHttpRequest, WebSocket, webkit, WeixinJSCore, Reporter, print, WeixinJSBridge) { 'use strict'; // your code }) [代码] 这里的 define 是在框架中定义的方法,在框架中提供了两个方法:require 和 define 用来定义和使用业务代码。其方式有些像 AMD 规范接口,通过 define 定义一个模块,使用 require 来应用一个模块。但是也有很大区别,首先 define 限制了模块可使用的其他模块,如 window,document;其次 require 在使用模块时只会传入 require 和 module,也就是说参数中的其他模块在定义的模块中都是 undefined,这也是不能在开发者工具中获取一些浏览器环境对象的原因。 在小程序中,JavaScript 代码的加载方式和在浏览器中也有些不同,其加载顺序是首先加载项目中其他 js 文件(非注册程序和注册页面的 js 文件),其次是注册程序的 app.js,然后是自定义组件 js 文件,最后才是注册页面的 js 代码。而且小程序对于在 app.js 以及注册页面的 js 代码都会加载完成后立即使用 require 方法执行模块中的程序。其他的代码则需要在程序中使用 require 方法才会被执行。 下面详细介绍了 app.js,自定义组件,页面 js 代码的处理流程。 4、加载 app.js 与注册程序 在 app.js 加载完成后,小程序会使用 require(‘app.js’) 注册程序,即对 App 方法进行调用,App 方法是对 appServiceEngine.App 方法的引用。 下图是框架对于 App 方法调用时的处理流程: [图片] App 方法根据传入的对象实例化一个 app 实例,其生命周期函数 onLaunch 和 onShow 因为使用不同的方式获取 options的参数。在有些需要根据场景值来实现需求的,或许使用 onShow 中的场景值更合适。 在实际开发过程中发现,在微信顶部唤起小程序和在小程序列表唤起的 options 也是不一样的。在该案例中通过点击分享的小程序进入后,关闭小程序,再通过不同方式进入小程序,通过顶部唤起的还是 options 的 path 属性还是分享出来的 path,但是通过列表中打开直接回到了首页,这里 App 中的 onShow 就会获取到不同的 options。 5、加载自定义组件代码以及注册自定义组件 自定义组件在 app.js 之后被加载,小程序会在这个过程中加载完所有的自定义组件(分包中自定义组件没有有测试过),并且是加载完成后自动注册,只有注册完成后才会加载下一个自定义组件的代码。 下图是框架对于 Component 方法处理流程: [图片] 图中介绍了框架如何对传入 Component 方法的对象的处理,其后面还有很多深入的对于组件实例化的步骤没有在图中表示出来,具体可以在文章最后的附件中查看。 自定义组件在小程序中越来越完善,其拥有的能力也比 Page 更强大,而后面会提到在使用自定义组件的 Page 中,Page 实例也会使用和自定义组件一样的实例化方式,也就是说,他拥有和自定义组件一样的能力。 6、加载页面代码和注册页面 加载页面代码的处理流程和加载自定义组件一样,都是加载完成后先注册页面,然后才会加载下一个页面。 下图是注册一个页面时框架对于 Page 方法的处理流程: [图片] Page 方法会根据是否使用自定义组件做不同的处理。使用自定义组件的 page 对象会被处理为和自定义组件的结构,并在页面实例化时使用不同的处理流程进行实例化。当然对于开发而言没任何不同。 从图中可以发现 Page 传入的(生命周期)代码并不会在这里被执行,可以通过下面小节了解 Page 实例化的详细过程。 7、等待页面 Ready 和 Page 实例化 还记得上面介绍的启动流程中最后一步等待页面 Ready?严格来讲是等待浏览器 Ready,小程序虽然有部分原生的组件,不过本质上还是一个 web 程序。 在小程序中切换页面或打开页面时会触发 onAppRoute 事件,小程序框架通过 wx.onAppRoute 注册页面切换的处理程序,在所有程序就绪后,以 entryPagePath 作为入口使用 appLaunch 的方式进入页面。 下图是处理导航的程序流程: [图片] 从图中可以看出页面的实例化是在进入页面时进行,下图是具体的实例化过程: [图片] 下图是最终可得到 Page 实例: [图片] 可以发现其中多了 onRouteEnd API,实际该接口不会被调用。其中以 component 标记的表示只有在使用了自定义组件时才会有的方法和属性。在前面第 5 小节提到了对于使用自定义组件的页面会按照自定义组件方式解析,这些属性和方法与自定义组件表现一致。 8、关于 setData 小程序框架是一个以数据驱动的框架,当然不能少了对他如何实现数据绑定的探索,下图是 Page 实例的 setData 执行流程: [图片] 其中 component:setData 表示使用自定义组件的 Page 实例的 setData 方法。 三、写在最后 这是一次不完全的小程序框架探索,是在微信开发工具中 debug 的结果。虽然对于实际开发没有什么太大的帮助,但是对框架如何对开发的 js 代码进行处理有了一个很明确的认识,在使用一些 js 特性时可以有明确的感知。如果你还疑惑“小程序框架对传入的对象等到底做了什么”那一定是我表达能力太差,说声对不起。 通过这一次 debug ,也给我引入了新的问题,还希望能够有更多的讨论: · 自定义组件太多启动时会耗时处理自定义组件 · 文件太多会耗时读文件 · 合理的设计分包很重要 当然最后对于框架中已有的能力,还是非常希望微信可以开放更多稳定的接口,并在文档中告知开发者,让开发变得简单一些。
2019-03-05