主题线路
- 小程序产生的原因。
- 小程序与普通网页对比,找到不同点。
- 分析每个不同点背后的技术原理。
这篇文章没有具体讲如何写小程序,而是介绍小程序背后的一些技术原理,然后给大家一个对小程序整体的轮廓概念,然后具体的开发大家还得查看文档。。。
首先声明
这篇文章大多观点依然来自微信小程序官方文档,我只是结合自己的理解重新说明了一下,但更简练,更通俗些,有些东西我也是点到为止,因为咱们的主线是小程序,主线以外的东西,大家自己再补充吧。。。
为何产生小程序?
黑格尔说过:存在即合理。因此小程序的存在也是为了解决某些问题而出现的,那都有哪些问题呢? 前端web页面和原生页面对比一下就能知道个大概,如下:
- 首先静态UI界面差别不大
- 前端页面交互卡顿,不如原生顺畅
- 前端页面切换生硬,不如原生丝滑
- 前端页面加载白屏,不如原生体验好
再来看看这些问题导致的大致原因:
问题2,主要是因为前端web页面基于dom,而生成和操作dom都很费时且耗性能,现在的浏览器对于网页渲染都是单线程处理,包括布局,渲染,js执行(还会有阻塞问题),图像解码等,而浏览器重绘网页的频率是60FPS(即16ms/帧),如果16ms内不能完成dom操作就会跳帧,另外网页很少有GPU图形加速,这些都会导致界面不流畅。。。所以现在像vue,react等框架都推崇虚拟dom,这都是在间接减少dom操作频率。
原生页面api基本都是直接调用手机系统的接口(如网络,摄像头,定位等),还有很多GUI工具包也是直接使用手机系统的(如按钮,输入区,菜单栏等),而web页面无法直接调用系统api,同时读取本地系统文件能力弱。。。
其实这里再谈及语言层面,也是有很大差距的,比如js是解释性语言,是边编译边执行,而原生代码直接是二进制码或者优化后的字节码,没有实时编译的开销,这样也能节省很大的性能开销,进而交互效果更好。
这里再给大家科普一点关于asm.js与WebAssembly的知识
游戏的性能要求非常高,一些大型游戏连 PC 跑起来都很吃力,更不要提在浏览器的沙盒模型里跑了!但是,尽管很困难,许多开发者始终没放弃,希望让浏览器运行 3D 游戏。
2012年,Mozilla 的工程师 Alon Zakai 在研究 LLVM 编译器时突发奇想:许多 3D 游戏都是用 C / C++ 语言写的,如果能将 C / C++ 语言编译成 JavaScript 代码,它们不就能在浏览器里运行了吗?众所周知,JavaScript 的基本语法与 C 语言高度相似。
于是,他开始研究怎么才能实现这个目标,为此专门做了一个编译器项目 Emscripten。这个编译器可以将 C / C++ 代码编译成 JS 代码,但不是普通的 JS,而是一种叫做 asm.js 的 JavaScript 变体。一旦 JavaScript 引擎发现运行的是 asm.js,就知道这是经过优化的代码,可以跳过语法分析这一步,直接转成汇编语言。另外,浏览器还会调用 WebGL 通过 GPU 执行 asm.js,即 asm.js 的执行引擎与普通的 JavaScript 脚本不同。这些都是 asm.js 运行较快的原因。据称,asm.js 在浏览器里的运行速度,大约是原生代码的50%左右。
大家可能听过WebAssembly,它也能将c / c++ 转成js引擎可以运行的代码。它和asm.js的功能基本一致,但转出来的代码不一样,asm.js是文本,而webassembly是二进制字节码,因此运行速度更快,体积更小。从长远来看,webassembly的前景更加光明。
问题3,在没有单页应用(spa)之前,多页切换时会重新初始化webview,而初始化webview是耗性能的。spa应用因为是在一个webview里面模拟客户端原生多页面切换,并辅助以过渡动画处理,因此效果会好一些。。。
现在的混合开发(hybird),实质是初始化一个”浏览器”的原生程序,通过包装好的接口来调用手机系统的api。而这里的浏览器就是webview。。。
webview是一个基于webkit内核,显示web页面的控件,WebView是手机中内置了一款高性能 webkit 内核浏览器,在 SDK 中封装的一个组件。没有提供地址栏和导航栏,WebView只是单纯的展示一个网页界面。
问题4,主要是因为web资源都放在远程服务器,每次需要联网才能获取到数据,做缓存可以缓解,但仍然有白屏问题,同时缓存更新策略对开发要求较高。。。
最开始,小程序团队想用JS-SDK配合缓存来解决这些问题,JS-SDK可以增强网页的能力,缓存可以缓解加载白屏的问题,但复杂页面仍然会白屏且更新策略复杂,同时无法解决交互卡顿及页面切换生硬的问题。
微信小程序的出现,就是想要解决以上这些问题的。。。
基于以上的问题,微信就需要设计一个新的系统,使得所有的开发者在微信中都能获得比较好的体验,这个问题是之前JS-SDK所处理不了的,需要一个全新的系统来完成,它使得所有的开发者都能做到:
- 快速的加载
- 更强大的能力
- 原生的体验
- 易用且安全的微信数据开发
- 高效和简单的开发
知道了为何会产生小程序,我们看看小程序与普通网页有哪些区别,以及每个区别后面的技术原因及原理?
小程序和普通网页的区别?
先来说说普通的网页:
- 页面结构需要html,样式需要css,交互需要js,
- 渲染需要浏览器,同时浏览器渲染是单线程的,UI渲染和js解析是互斥的
- 因为有浏览器,所以有BOM,DOM,window等相关的api及第三方库(如jquery等)
- 浏览器又分IE,Chrome,safari,其他等
再来看看小程序:
- 页面结构wxml(其实wx就是weixin的缩写),样式需要wxss,交互依然是js,配置文件json
- 渲染需要webview配合原生,UI渲染和JS解析分别在两个线程中执行
- 没有浏览器,所以所有与BOM,DOM,window相关的api及库在小程序中无法直接使用(后续可以借助框架)
- 运行环境主要是ios和Android的微信客户端
可以先看看下图(先大概了解):
首先说说为什么要改渲染方式,之前是纯浏览器,现在是浏览器配合原生?
小程序渲染方式的选择?
因为小程序诞生的目的之一就是要快。。。渲染快,加载快,各种快。。。
我们知道渲染页面一般分为以下几种情况:
- 纯客户端原生技术来渲染
- 纯web技术来渲染
- 客户端与web技术混合渲染
情况1,小程序的宿主是微信,因此用纯原生渲染不太现实,因为那样意味着小程序的代码需要绑定到微信app里面发布,这样不现实。
情况2,如果用纯 Web 技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在 Web 技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行(也就是同一时间要么执行ui渲染,要么执行js),这就容易导致一些逻辑任务抢占UI渲染的资源。
情况3,这样一来只剩下客户端和web技术混合渲染了,而这种混合渲染技术在过去有PhoneGap,Cordova(前身是PhoneGap),还有RN,还有像微信网页里的JS-SDK,其实这些移动应用开发框架或库无非是说前端代码通过web技术实现,而页面最终渲染的方式不同罢了。。。
PhoneGap,Cordova和JS-SDK更像一些,归根结底是浏览器内核来渲染页面。。。而RN虽然是web技术编写代码,同时利用js解释执行的特性,但RN在渲染底层是客户端原生渲染的!!!
RN的渲染方式是小程序之前预选方案之一,但因为以下几点导致微信最终没有选择RN
- RN支持的样式是css的子集,会满足不了日益增长的需求(其实小程序支持的样式也是css子集)
- RN现在仍有问题,比如性能,bug等,RN将所有的渲染工作都交给了客户端原生渲染,其实有些简单的页面元素用web技术来渲染完全足够。
- 一些其他的不可控因素,如许可协议
最终,小程序选择类似于微信 JS-SDK 这样的 Hybrid 技术,即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时小程序内置一些组件,这些组件是客户端原生渲染的,因此性能会更好。。。
另外每个小程序页面都是用不同的webview去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个webview的任务过于繁重。。。
再来说说为什么取消了浏览器环境并改为双线程模型?
基于web技术来渲染小程序有很多不可控的因素和安全风险,这是因为web技术太开放灵活,可以通过js来跳转或任意修改页面上的内容。。。
另外小程序定义一套统一的组件系统(代码与微信客户端绑定,也就是基础库),需要将小程序代码包下载到本地,如果此时开发者通过js将渲染小程序的webview跳转到其他网页,体验就会变遭。。。
除此之外,小程序也提供一种可以展示敏感数据的组件(这些数据只能被展示,开发者并不能拿到数据),若开发者可以通过JavaScript 操作界面(DOM树),从而直接获取这些敏感数据,那小程序毫无安全可言。
为了解决管控与安全问题,微信必须阻止开发者使用一些浏览器提供的,诸如跳转页面、操作DOM、动态执行脚本的开放性接口。假设一个一个禁止,那势必会进入一个攻防战,这是因为 JavaScript 的灵活性以及浏览器接口的丰富性,我们很容易遗漏一些危险的接口,而且就算被我们找到所有危险的接口,也许在下一次浏览器内核更新而新增了一个可能会在这套体系下产生漏洞的接口,这样还是无法完全避免。
因此,要彻底解决这个问题,我们必须提供一个沙箱环境来运行开发者的JavaScript 代码。这个沙箱环境不能有任何浏览器相关接口,只提供纯JavaScript 的解释执行环境,那么像HTML5中的ServiceWorker、WebWorker特性就符合这样的条件,这两者都是启用另一线程来执行 JavaScript。但是考虑到小程序是一个多 WebView 的架构,每一个小程序页面都是不同的WebView 渲染后显示的,在这个架构下我们不好去用某个WebView中的ServiceWorker去管理所有的小程序页面。
得益于客户端系统有JavaScript 的解释引擎(在iOS下是用内置的 JavaScriptCore框架,在安卓则是用腾讯x5内核提供的JsCore环境),我们可以创建一个单独的线程只执行 JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码,也就是我们前面一直提到的逻辑层。而界面渲染相关的任务全都在WebView线程里执行,通过逻辑层代码去控制渲染哪些界面,那么这一层当然就是所谓的渲染层。这就是小程序双线程模型的由来。
由小程序双线程能想到什么?页面渲染需要用到多个环境(比如js引擎,渲染引擎等),因此这些部分并不是完全绑定在一起的,可以通过自己构建环境还最终达到页面渲染的效果。。。比如小程序的js引擎只解析js,没有浏览器相关的一些接口。
上面提到了逻辑层和渲染层,那这两个层应该如何理解呢?
在小程序中wxml和wxss分别相当于普通网页开发中的html和css,因此这两个工作在渲染层,而js脚本则运行在逻辑层。
来看一个简单的模型:
//xxx.wxml
<view> { { msg } }</view>
//xxx.js
Page({
onLoad: function () {
this.setData({ msg: 'Hello World' })
}
})
- 渲染层通过绑定变量数据
- 逻辑层(xxx.js)负责产生,处理数据
- 逻辑层通过Page实例原型对象上的setData()方法将数据变化传递至渲染层。
其中第一点就是我们常说的数据驱动视图,而第三点就是小程序的通信模型。
先来说说小程序的通信模型?
小程序的渲染层和逻辑层分别由两个线程控制,渲染层使用webview来渲染,而逻辑层采用JsCore线程运行js脚本。一个小程序存在多个webview,所以会存在多个webview线程,逻辑层与渲染层通信需要通过微信客户端(也就是Native端)中转,逻辑层发送网络请求也经由Natvie转发,通信模型如下图:
在每个小程序页面的生命周期中,存在若干次页面数据通信,逻辑层向视图层发送页面数据(data和setData中的内容),视图层向逻辑层反馈用户事件。
那页面初始化时数据都是怎么通信的呢?
在小程序启动或一个新的页面被打开时,页面的初始数据(data)和路径相关信息会从逻辑层发送给视图层,用于视图层的初始渲染,Native层会将这些数据直接传递给视图层,同时向用户展示一个新的页面层级,视图层在这个页面层级上进行界面绘制。另外视图层接收到相关数据后,根据页面路径来选择合适的wxml结构,wxml结构与初始数据结合,便得到页面的第一次渲染结果。如下图:
从图中可以看到,页面初始化的时间大致由:页面初始数据通信时间和初始渲染时间两部分构成,其中前者是逻辑层开始组织数据到视图层完全接受数据完毕的时间,数据量小于64kb时总时长可以控制在30ms内,因此减少传输数据量是降低数据传输时间的有效方式。(这里数据传输与时间并不是正相关关系)
初始渲染完毕后,视图层可以在开发者调用setData后执行页面更新(其他方式修改数据不会触发视图更新),逻辑层会将setData设置的数据字段与data合并,这样便可以在页面内使用this.data读取到变更后的数据。
因此知道了初始数据传递和更新数据都会消耗时间,因此适当的注意一些问题可以提高性能:
- 不要频繁调用setData,可以考虑将多次调用合并成一次setData调用
- 数据通信和数据量大小有关,因此如果一些字段不在界面中展示且数据结构比较复杂或较长字符串,不要使用setData来设置这些数据
- 与界面无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下。(其实这些问题在vue项目里也可以稍加注意)
上面知道了,数据通信的大概过程,咱再来看看具体的数据处理过程?
再来说说小程序的数据驱动过程?
通常界面视图和变量状态是相互关联的,如果有什么方法能够将视图和状态绑定在一起(即状态变更时,视图会自动变更),那我们就会省去很多工作。。。
通常html或wxml可以等价于dom树,然后js也可以表达dom树的结构,因此就有了下图:
其实就是说可以先将wxml和data合并转换为js对象,然后再渲染出真正的dom树(其实这里就可以理解为虚拟dom),如下:
那当数据发生变化之后,会发生什么呢?
通过setData把msg数据从“Hello World”变成“Goodbye”,其实此时数据便从逻辑层经过Native层到达渲染层,然后渲染层产生新的JS对象对应的节点就会发生变化(宿主环境负责转化),此时可以对比前后两个JS对象变化的部分,然后把这个差异应用到原来的Dom树上,从而达到更新UI的目的,这就是数据驱动 ,其实这个对比算法类似diff算法
上面说了具体的通信过程,那既然有通信,就需要耗费时间,也就是延时。。。
小程序天生的延时?
小程序是基于双线程模型,任何数据传递都是线程之间的通信,因此都会有一定的延时,这不像传统Web那样,当界面需要更新时,通过调用更新接口UI就会同步地渲染出来。在小程序架构里,这一切都会变成异步。
比如在首屏渲染的时候,逻辑层和渲染层会同时开始初始化工作,但渲染层需要有逻辑层的数据才可以,如果渲染层速度较快就需要等待逻辑层处理完才可以进行下一步操作。。。因此逻辑层与渲染层需要有一定的机制保证时序正确,这就是生命周期。。。
其实渲染层和逻辑层之间通信有延时,逻辑层与Native层通信也有延时。。。比如开发者的代码是在逻辑层这个线程上,而客户端原生是跑在微信主线程之上,所以当给逻辑层注册有关客户端能力的接口时,实际上也是跟微信主线程之间的通信,同样意味着延时,所以小程序提供的大多接口都是异步。。。
上面提到生命周期,咱再说说生命周期。。。
小程序,页面,组件等的生命周期?
小程序由多个页面组成,但只有一个程序级实例对象,类似vue中的app.vue。
首先宿主环境(微信客户端提供)会提供App()构造器来注册一个程序级App,App实例是单例对象,在其他js脚本中可以通过宿主环境提供的getApp()来获取程序实例,进而操作实例对象上的数据,这其实也是一种多页面之间数据传递的方式之一,即全局变量。。。
小程序实例的生命周期:
App()构造器,接受一个Object对象作为参数,如下:
App({
onLaunch: function(options) {},//当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
onShow: function(options) {}, //小程序启动或从后台进入前台时触发
onHide: function() {}, //小程序从前台进入后台触发
onError: function(msg) {}, //脚本或api调用错误时触发,携带错误信息
globalData: 'I am global data' //自定义字段,比如全局变量
})
globalData字段可以定义一些全局数据,其他页面通过getApp()获取实例对象,进而获取全局对象字段,然后做一些操作。。。
初次进入小程序时,微信客户端初始化好宿主环境,同时从网络下载或者从本地缓存中拿到小程序的代码包,把它注入到宿主环境,初始化完毕后,微信客户端会给App实例派发onLaunch事件,App构造器参数所定义的onLaunch方法会被调用。
注意:当右上角关闭或手机home键返回主页时,小程序并没有直接销毁,而是进入后台运行,并触发onHide方法。微信客户端会根据内存占用情况自动清理关闭后的小程序,一般5分钟后小程序会自动退出。
另外微信赋予小程序很多的能力,其一就是可以知道用户从那种渠道打开的小程序,比如从群聊会话中,从小程序列表,通过微信扫一扫等,这为数据分析提供了依据,这些参数可以在onLaunch,onShow生命周期函数的参数中拿到。。。
小程序页面的生命周期:
同样,宿主环境提供Page()构造器来注册一个小程序页面,Page()在页面脚本page.js中调用,参数依然是Object,如下:
Page({
data: { text: "This is page data." },//页面初始数据
onLoad: function(options) { }, //监听页面加载,早于onShow和onReady
onReady: function() { }, //监听页面初次渲染完成
onShow: function() { }, //监听页面显示,触发事件早于onReady
onHide: function() { }, //监听页面隐藏
onUnload: function() { }, //监听页面卸载
onPullDownRefresh: function() { }, //监听用户下拉动作
onReachBottom: function() { }, //页面上拉触底事件的处理函数
onShareAppMessage: function () { }, //右上角转发
onPageScroll: function() { } //页面滚动事件的处理函数
})
务必要注意:onLoad在页面初次加载时只执行一次,直到页面销毁。onReady在页面初次渲染完成时触发,也是在销毁前只执行一次,而onShow每次切换页面都会触发,因此对页面数据实时性要求较高的页面,考虑放在onShow里调用接口。。。
组件的生命周期:
Component构造器用于定义组件,参数同样是对象,如下:
Component({
behaviors: [],//类似vue中mixin
properties: {//类似vue中的props
myProperty: { // 属性名
type: String, // 类型(必填),
value: '', // 属性初始值
observer:function(newVal,oldVal,changedPath){},//属性变化时执行,类似vue中watch
},
},
data: {}, // 私有数据,可用于模版渲染
created:function(){},//组件进入页面节点树时执行,此时不能调用setData
attached: function(){},//组件进入页面树时执行,可调用setData
ready:function(){},//组件布局完成后执行,可获取节点信息(SelectorQuery)
moved: function(){},//在组件实例被移动到节点树另一个位置时执行
detached: function(){},//在组件实例被从页面节点树移除时执行
methods: {},//组件的方法
relations:{},//组件间关系定义
externalClasses:String/Array,//组件接受的外部样式表
options:Object/Map,//组件选项
})
组件是比较常用的,在组件内绑定的事件,可以通过triggerEvent发布出去,进而在调用该组件的页面监听这个发布出去的事件,从而实现页面与组件的通信,类似vue的$emit。。。其实还可以通过selectComponent("#id")
或selectAllComponents("class-name")
选择器拿到组件实例,进而操作组件实例上的对象,这也不失为一种页面与组件间通信的方式(但是建议还是用发布监听模式传递数据,这样利于跟踪)。
上面说了程序,页面,组件的生命周期,其实还有类似vue的mixins,不过在小程序里叫behaviors(暂时我还没用,大家自己看文档吧),但这些都是相对自身而言的。。。
那当以上这些组合起来后,又该如何理解他们之间的关系呢?
页面栈及导航:
一个小程序拥有多个页面,每个页面又可能引入多个组件,当页面跳转时,我们可以通过wx.navigateTo
打开一个新的页面,此时页面层级就会多一层,我们叫这个页面层级为页面栈。
上图是使用两次wx.navigateTo
后的页面栈。其实这个就类似浏览器中的history,每增加一个浏览记录,在history对象里就多一条记录数据,因此也就有了与histroy相似的几个api,如下:
wx.navigateTo() //打开新页面,页面栈增加一级
wx.redirectTo() //重定向,销毁当前页面栈信息,重新载入新页面栈信息
wx.navigateBack() //销毁当前页面栈信息,返回上一级
wx.switchTab() //清空当前页面栈,载入新的页面栈
wx.reLaunch() //销毁整个小程序实例,重新打开小程序
//由于小程序只允许页面栈层级最高为10级,因此页面之间跳转,不能无限制用
wx.navigateTo() //这种只增加页面栈层级而不减少的api,要合理使用各个导航api。
既然页面栈是一个对象,那就可以获取到这个对象,进而操作每个页面栈里的数据,即:
var pages = getCurrentPages(); //整个页面栈对象
var currPage = pages[pages.length - 1]; //当前页面Page对象
var prevPage = pages[pages.length - 2]; //上一个页面Page对象
这其实也是一种页面间通讯的方式(依然不太建议这种不便跟踪的修改模式)。。。
页面间,组件间传递数据的方式:
页面间或组件间传值,或许是大家开发过程中应用比较多的地方,咱再总结一下:
- 全局变量模式
- 组件发布订阅
- 组件properties(类似props)
- 获取页面栈
- 选择器获取组件
1,全局变量模式
其实就是在app.js定义一个全局变量字段,然后在每个页面里通过getApp()方法获取app实例,进而拿到定义的全局变量字段,如下:
//app.js
App({
//全局变量
globalData: {
reqRoot:'http://xxx',
userName:'xxx'
}
})
//page.js
let app = getApp()
console.log(app.globalData.userName) // 输出xxx
2,组件发布订阅模式和组件properties
//search.js,组件内可以这样定义
Component( {
properties: {
myProps:{
type:Object,
value:{
width:600,
desc:'手机号/进件号',
hasSolt:true,
slot:'搜索'
}
},
boxWidth:{
type:Number,
value:690
}
},
methods:{
// 每次输入都触发,同时将输入的值通过tempDetail传递给父组件
searchInput(e){
const tempDetail = e.detail.value
this.triggerEvent('childInputEvent', tempDetail, {})
},
// 获取焦点时触发,父组件监听childFocusEvent事件即可
searchFocus(e){
this.triggerEvent('childFocusEvent', 'focus', {})
},
} )
在引入组件的页面内,可以这样通过properties传参,另外通过bindchildInputEvent(必须是bind开头
)来监听组件内部的事件,=后面的searchProps便是传递给子组件的值,另外getInputVal便是具体的处理函数了。大家可以在自己的页面内定义(名字可以自取)
<view class="search-box">
<c-search my-props="" bindchildInputEvent="getInputVal"></c-search>
</view>
4,获取页面栈方式,
//page.js
// 下面是在onReady生命周期里调用的,其实在其他生命周期钩子里也可以
Page( {
onReady: function () {
let pages = getCurrentPages();
let currPage = pages[ pages.length - 1 ]; //当前页面
let prevPage = pages[ pages.length - 2 ]; //上一个页面
//直接调用上一个页面的setData()方法,把数据存到上一个页面中去
currPage.setData( {
searchResult : prevPage.data.searchResult
})
console.log(prevPage.data.searchResult);
},
})
5,选择器获取组件方式
<view wx:if="">
<c-base-result id="cBaseResult" data-item="baseResult" bindchildBtnTapEvent="handlechildBtnTapEvent"></c-base-result>
</view>
在页面的js文件里就可以直接获取这个组件,并操作组件内的数据了,如下:
Page({
onReady(){
let tempComponent = this.selectComponent("#cBaseResult")
console.log( tempComponent )
}
})
以上就是小程序中几种传值的方式,推荐大家用组件发布订阅模式,后两者不太建议大家使用,目前小程序还没有像vuex那样的状态管理器,大家先根据业务需求自行调整自己的传值方式,后续借助框架的话,或许会好些。。。
注意:另外因为小程序存在销毁的问题,因此存储在变量里的数据,在实例销毁后变量也会销毁,因此若想存储一些永久存在的变量值,需要借助本地缓存。
其实上面说的小程序与客户端的通信主要还是基于android,而在ios上略有不同,咱再大概捋捋。。。
小程序与客户端的通信?
我们前面知道了,小程序的渲染方式是web技术结合部分原生渲染方式,而这部分原生渲染方式渲染的就是一些内置组件,既然web页面需要客户端原生提供的能力,那就会涉及视图层与客户端的交互通信。。。
在android上,我们知道原生先初始化一个webview控件,然后在webview的window对象上注入原生方法,然后封装成WeiXinJSBridge这样一个兼容层,然后暴露出调用(invoke)和监听(on)这两种方法。
iOS 是利用了WKWebView 的提供 messageHandlers 特性。 参考:原生与h5交互
实际上,在视图层与客户端的交互通信中,开发者只是间接调用的,真正调用是在组件的内部实现中。开发者插入一个原生组件,一般而言,组件运行的时候在被插入到 DOM 树的生命周期里,就会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。
对于逻辑层与客户端的通信,逻辑层与客户端原生通信机制与渲染层类似,不同在于,iOS平台可以往JavaScripCore框架注入一个全局的原生方法,而安卓方面则是跟渲染层一致的。
小程序,网页,nodejs中js的不同点?
首先我们要知道,ECMAScript是一种由Ecma国际通过ECMA-262标准化的脚本程序设计语言, JavaScript 是 ECMAScript 的一种实现而已。。。
一般说到的浏览器中的js包括:ECMAScript,DOM,BOM
而nodejs中的js包括:ECMAScript,NPM,Native(使用一些原生的模块,如FS,HTTP,OS等来拥有语言本身所不具备的能力)
小程序中的js包括:ECMAScript,小程序框架,小程序API。。。因为没有浏览器环境,所以与浏览器相关的类库jquery,zepto无法直接使用,同样缺少Native模块而无法加载原生库,没有npm也无法直接使用大部分的npm包。。。
注意:当然这些问题,都是在没有使用第三方框架之前有的,使用第三方框架如wepy等小程序框架,这些问题或多或少可以解决。。。后续会考虑框架。
既然小程序执行脚本是js,那小程序里的js执行环境都有哪些呢?
- ios平台,包括ios9,ios10,ios11(暂时在ios8上兼容很差)
- Android平台
- 小程序IDE
截止到现在,ECMAScript一共有7个版本,我们常说的ES6是2015年发布的,之后发布分别是ECMAScript2016,ECMAScript2017,今年的还没发布。而我们现在开发中大多使用的都是ES5和ES6标准,而ES6标准也并不是所有的平台都完全实现兼容了,这也是为什么我们常常需要一些转换ES版本的工具库的原因(比如Babel)。。。体现在小程序里,主要就是ios9和ios10所使用的运行环境没有完全兼容ES6,因此也需要ES降级处理。。。
另外在小程序的js文件也是有执行顺序的,程序启动的时候会执行app.js里的代码,之后才依次执行pages对象里定义的文件。。。
微信开发者工具:
是一个基于nw.js,使用node.js,chromium以及系统api来实现底层模块,使用react,redux等前端技术框架来搭建用户交互层,实现同一套代码跨mac和windows平台使用
NW.js基于Chromium和Node.js ,NW.js能够通过页面技术开发桌面应用 , 同时可以调用Node.js代码以及模块 。未来 , 你可以使用NW.js轻松将页面应用制作成桌面应用 .
Chromium 是 Google 的 chrome 浏览器背后的引擎,其目的是为了创建一个安全、稳定和快速的通用浏览器。
一些链接:
https://nwjs-cn.readthedocs.io/zh_CN/latest/Base/Getting%20Started/index.html
https://www.kancloud.cn/chandler/web_app/98298