阅读骑手Weex代码有感

干玲昌

学习背景

目前,在开发中会遇到这样的问题,每次项目出现一点问题,无论问题大小,都需要重新发布新版本,这样太不灵活方便,且给用户带来极差的体验。用户的感知是,这软件做的太差,经常更新。基于这样的缺点,公司决定使用weex来开发,至少在发布新版本这一方面,可以做到用户无感知。至于其他的优点,这里暂不讨论。

理解语法

首先,阅读代码都是从上往下的。那么,我也从上往下讲起。先来看一段代码。

import 作用与大多数的语言类似,作用就是导入其他模块,实现代码和功能的复用。一个模块是一个单独的文件,我们如果想要使用里面的变量,函数这种的话,就需要将这些东西导出导入。这里提一嘴,在js中导入模块的方式。在ES6之前,js通过一些模块加载方案实现模块功能,主要是CommonJS和AMD。其中CommonJs 是通过require关键字实现的。如下代码:

require vs import

那为什么有require 还要弄出import这种东西呢?是这样的,ES6模块的设计思想是为了尽量的静态化,使得编译时就能确定模块的依赖关系。因为静态加载或者称编译时加载效率要比CommonJS(运行时加载)效率要高。通常require可以写在代码块中,但是import写在代码块中会报错,因为这样就不能做静态优化了。

export default vs export , import vs import{}

上面聊了require和import的区别,下面回到第一张图,讲讲import和import{}的区别。有导入相对的就要有导出的方法,在js中导出主要有两种方式,一种是export default 和 export。

先记住,export 对应的导入方法是import{},export default对应的导入方法是import。

一般来说,export 都是导出单个元素,一个文件里面可以有多个export,但是只能有一个export default。下图的文件是名为string.js的模块

由于使用了export导出,因此,在我们导入的时候,只能使用import{},我们在使用到string.js的地方可以看到,就如图一红色方框标记的地方。这里需要注意的一点是,import导入的时候一般情况下与导出的时候名字要一样。要是有特殊需求需要改变的话,可以通过import {xxx as yyy} 的方式改掉名称。但是在使用export default的时候,名字可以不与导出的时候相同,在使用的时候可以通过 .xx 的方式获取到不同的函数或者变量,有点类似对象获取属性的方式。而且在使用export导出的时候,其值是动态绑定的。如下代码:

export var foo = 'bar';  
setTimeout(() => foo = 'baz', 500);  

即使在导出语句之后改变变量的值,导出结果会相应的改变掉。可以认为其结果与变量的值是绑定的。

const var let

变量声明,同样是最常用的.不过在ES6新加了两种声明变量的方式。const 和 let。为什么要新增这两种声明的方式?先来两段代码

var a = "1";  
if(true){  
    var a = "2";
}
console.log(a);  
var a = 0;  
for (var a = 0; a < 10; a++) {

}
console.log(a);  

猜到结果了吗?答案是2和10。为什么呢?因为在ES6之前,js是没有块级作用域的概念,同时js只有var声明变量,var的作用域有两种,一种是全局作用域,另一种是函数作用域,因此当在代码块中使用var 声明变量的作用域就是全局的,这样带来的问题就是很容易会影响到外部的变量。使得第一段代码中的a = "1" 变成了 "2",第二段代码的 var = 0 变成了10。要是不注意代码块中var的作用域,很容易就会出现问题。同时var还存在变量提升的问题。

console.log(a);  
var a = 1;//不报错

console.log(b);  
let b = 1;//报错  

因为不声明变量直接使用的话,代码可读性比较差,不够规范。基于这些原因,let 和 const出现了。建议在写代码的时候尽量使用let 和 const 替代掉 var 。因为let与var作用相同,但是却没有“副作用”。const的作用与java中的final有些类似,表示的是修饰的变量指向的地址不可更改。对于简单的数据类型,变量的值就存储在地址中,因此地址和值都不会改变,但是对于对象数据类型,地址存储的是一个指针,指向的是对象数据类型的值,const能保证的是指向的指针不变,但是不能保证指针指向的值是否不变。

箭头函数

基本语法

//箭头函数
(参数)=>{
方法体
}
--------------------
//普通函数
function(参数){  
  方法体
}

1.当参数只有一个的时候可以不加括号。

2.当方法体超过一行代码的时候,需要加return指定返回的对象。当方法体只有一行的时候,{}可省略。

3.当返回的是对象的时候,需要在{}外面加()。因为{}代表的是代码块,会报错。

4.通常箭头函数的作用是简化回调函数,一般不用箭头函数直接声明一个对象的方法。

Promise

promise的作用是异步编程,那怎么实现异步编程呢?首先,promise 存在 三种状态 pending rejected resolved。由pending状态变为rejected状态的时候,会执行reject方法,由pending 状态变为resolved状态的时候,会执行resolve方法。

//伪代码
new Promise(function(resolve,reject){  
    if(success){
        resolve(); 
    }else{
        reject();
    }
})

同时,promise有一个then方法,可用来链式编程。then 接受两个参数。第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。我们来看下项目中的代码。

下图是common模块中的servePhone方法

从上面两种图片,我们知道,common.servePhone返回了一个promise对象,当promise状态有由pending变为resolve的时候,会执行then中的第一个回调函数,得到了客服的电话。 promise和箭头函数有很好的结合。

this

在阅读代码的时候,经常能看到会有var that = this的语句。这条代码是干啥的? 首先,this牵扯到一个问题,this指向的对象是会变化的。一般来说,哪个对象调用函数,这个this指向的就是这个对象。但是箭头函数不是这样的,箭头函数中的this指向的是定义它的对象。还是上面那个获取客服电话的例子。我们把箭头函数改成普通的函数。

运行代码,发现客服电话并不能显示出来了,那这里就是有问题的。于是,我在代码前面增加了一行let that = this,并在使用this的地方用that替代掉。

运行一行代码,发现客服电话又显示出来了。为了弄清楚这个原因,我分别打印出箭头函数和普通函数中的this,来看看他们到底指向的对象是不是一样的。 其中分别输出的结果是:

很明显,箭头函数和普通函数中的this指向的对象不一样。那我们再来看下他们到底指向的是哪个对象呢?我在settingView.vue这个文件初始化create方法中打印出这个this。

其结果和箭头函数的结果一样!前面说过,箭头函数的this指向的是定义它的对象,普通函数的this指向的是调用它的对象。我们再看普通函数的this指向哪个对象。我们根据调用的关系,common文件中,打印出this。

可以看到,结果与普通函数中打印出的this结果相同,印证了前面提到的结论。其实原来代码中写var that = this,是因为原来写的都是普通函数,后来改成了箭头函数,这个也跟同事验证过了。当写成了箭头函数的时候,这段代码就不需要了。在普通函数中需要将this对象,保存下来,否则,在上面的调用过程中,this会改变掉了,这样会造成错误。

ES6语法规范参考

Airbnb JavaScript Style Guide

以上是在阅读骑手代码的时候遇到的一些高频的ES6语法,花了点时间弄懂了,记录在这里。有错误的地方欢迎指出。