函数作用域和块作用域

一个作用域就犹如一个气泡,气泡可以层层嵌套,也可以如蜂窝一样整齐的排列。

一、函数中的作用域

在Javascript中每声明一个函数都会为其自身创建一个气泡。

function foo(a) { 
    var b = 2;
    // 一些代码
    function bar() { // ...
    }
    // 更多的代码 
    var c = 3;
}

在这片代码中,foo(…)的作用域气泡中包含了a、b、c和bar,无论标识符声明出现在作用域中的何处,这个标识符所代表的变量或者函数都附属于所处作用域的气泡。

全局作用域也有自己的气泡,它只包含了一个标识符:foo。

函数作用域的含义:属于这个函数的全部变量都可以在整个函数范围内使用及复用(嵌套的作用域中也可以使用)

二、隐藏内部实现

在软件设计中,有个原则叫最小授权或最小暴露原则,就是应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来。

在Javascript中可以通过函数来实现这一原则,在所写的代码中挑选出任意片段,然后用函数声明对它进行包装,实际上就是在这篇代码的周围创建了一个作用域气泡,这段代码中的任何声明(变量或函数)都将绑定在这个新创建的包装函数的作用域中。

function doSomething(a) {
    b = a + doSomethingElse( a * 2 );
  console.log( b * 3 );
}
function doSomethingElse(a) { 
    return a - 1;
}
var b;
doSomething( 2 ); // 15

这段代码中,变量b和函数doSomethingElse(…)应该是doSomething(…)内部具体实现的“私有”内容。给予外部作用域对b 和 doSomethingElse(…)的“访问权限”不仅没必要,而且可能是“危险”的。

function doSomething(a) { 
    function doSomethingElse(a) {
        return a - 1; 
    }
    var b = a + doSomethingElse( a * 2 );
  console.log( b * 3 );
}
doSomething( 2 ); // 15

现在变量b和函数doSomethingElse(…)都无法从外部被访问了。

规避冲突
“隐藏”作用域中的变量和函数可以避免同名标识符之间的冲突

function foo() { 
    function bar(a) {
        i = 3;
      console.log( a + i );
  }

    for (var i=0; i<10; i++) {
        bar( i * 2 );
    } 
}
foo();

上面的代码中,因为bar(...)内容的赋值表达式是 i = 3,在bar(…)作用域中找不到 i 变量的声明,顺着作用域链就会找到foo(…)作用域里的变量 i ,这样就导致了无限循环。

只要bar(…)内部的赋值操作改成声明一个本地变量来使用,如 var i = 3; 就可以满足这个需求(遮蔽变量)

1.全局命名空间

当程序中加载了多个第三库时,如果它们没有妥善的将内部私有的函数或变量隐藏起来,就会很容易发生冲突。

这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象 被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属 性,而不是将自己的标识符暴漏在顶级的词法作用域中。

var MyReallyCoolLibrary = { 
    awesome: "stuff", 
    doSomething: function() {
        // ... 
    },
  doAnotherThing: function() {
      // ...
    } 
};

2.模块管理

另外一种避免冲突的办法和现代的模块机制很接近,就是从众多模块管理器中挑选一个来 使用。使用这些工具,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器 的机制将库的标识符显式地导入到另外一个特定的作用域中。

三、函数作用域

在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,但这并不理想。

var a = 2;
function foo() { 
    var a = 3; 
    console.log( a ); // 3
} 
foo(); 
console.log( a ); // 2

因为首先必须声明一个具名函数 foo(),这意味着 foo 这个名称本身“污染”了所在的作用域。其次,必须显示地通过函数名(foo())调用这个函数才能运行其中的代码。

var a=2;
(function foo(){ 
    var a = 3;
    console.log( a ); // 3 
})();  
console.log( a ); // 2

只要 function 是声明中的第一个词,那么就是一个标准函数声明,否则就是一个函数表达式。(function foo(){ …. }) 作为函数表达式意味着 foo 只能在 … 所代表的位置中被访问。

1、匿名和具名

setTimeout( function() {
    console.log("I waited 1 second!");
}, 1000 );

匿名函数表达式,因为function() …. 没有名称标识符。函数表达式可以是匿名的,而函数声明不可以省略函数名。匿名函数用的最多的地方就是作为回调参数。

匿名函数表达式书写起来很方便,但却有几个缺点:

  1. 在栈追踪中不会显示出有意义的函数名,使得调试很困难。
  2. 没有函数名,在函数需要引用自身时只能使用已经过期的 arguments.callee 引用
  3. 匿名函数省略了对于代码可读性/可理解性很重要的函数名,一个描述性的名称可以让代码不言自明。

2、立即执行函数表达式

术语:IIFE(Immediately Invoked Function Expression)

var a = 2;
(function foo() { 
    var a = 3;
  console.log(a); // 3
})();
console.log(a); // 2

由于函数被包含在一对( )括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( ) 可以立即执行这个函数。

进阶用法:

var a = 2;
(function IIFE( global ) {
    var a = 3;
    console.log( a ); // 3 
    console.log( global.a ); // 2
})( window );
console.log( a ); // 2

可以从外部作用域传递任何你需要的东西,并将变量命名为任何你觉得合适的名字。

undefined = true; // 给其他代码挖了一个大坑!绝对不要这样做! 
(function IIFE( undefined ) {
    var a;
    if (a === undefined) {
        console.log( "Undefined is safe here!" );
    }
})();

上面的代码可以解决 undefined 标识符的默认值被错误覆盖导致异常。将第一个参数命名为undefined ,但对应的位置不传入任何值。

var a = 2;
(function IIFE( def ) { 
    def( window );
})(function def( global ) {
    var a = 3;
    console.log( a ); // 3 
    console.log( global.a ); // 2
});

倒置代码的运行顺序,将需要运行的函数放在第二位,在 IIFE 执行之后当做参数传递进去。

四、块作用域

for (var i = 0; i<10; i++) { 
    console.log( i );
}

上面这段代码中,通常我们只想在 for 循环内部的上下文中使用 i ,但其实 i 会被绑定在外部作用域(函数或全局)中。

var foo = true;
if (foo) { 
    var bar= foo * 2;
    bar = something( bar ); 
    console.log( bar );
}

这段代码中,尽管我们是在 if 声明的上下文中声明了 bar 变量。但是,当使用 var 声明变量时,它写在哪里都是一样的,因为它们最终都会属于外部作用域。

块级作用域的用处就是使变量的声明距离使用的地方越近越好,并最大限度地本地化。但可惜,表面上看 Javascript 并没有块级作用域。除非,深入研究,下面几种情况都会创建出块级作用域。

  1. with
  2. try/catch
  3. let(ES6引入的关键字)
  4. const (ES6引入的关键字,其值为常量)
2017/11/25 posted in  JavaScript

微信小程序开发

微信小程序是由微信打造出来的一种不用下载即可使用的应用框架,但只能在微信的应用里运行。

一、开发准备

  1. 需要申请一个小程序账号
  2. 浏览一遍开发文档
  3. 开发工具可以使用微信开发者工具或者其他的编辑器,但小程序上传到微信服务器,只能通过微信开发者工具。

二、开发

小程序开发主要分为两大块:

  1. 前端 主要开发前端的页面视图,需要使用微信提供的小程序开发框架。框架提供了自己的视图层描述语言 WXMLWXSS,以及基于 JavaScript 的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统。小程序的文件大小目前是限制在2MB
  2. 服务端 服务端主要用于处理前端的数据请求和响应,可以用任何后端语言开发。

1、文件结构

  1. app.json 全局配置文件
  2. app.js 全局逻辑,用App() 函数(函数接受一个 object 参数)来注册,在其他页面逻辑js文件里用 getApp() 函数获取。
  3. app.wxss 公共样式表
  4. pages 页面文件夹,所有页面必须在 app.json 里进行注册
//app.json
{
  "pages": [
    "pages/index/index"  //指向 /pages/index/index.wxml 文件
  ]
}

一个小程序页面由四个文件组成(四个文件的文件名必须具有相同的路径与文件名):

  1. *.js 页面逻辑
  2. *.wxml 页面结构(后面都称之为视图文件)
  3. *.wxss 页面样式表 (后面都称之为样式文件)
  4. *.json 页面配置

2、常用API

  1. wx.request(object) 发起请求,::客户端的 HTTPS TLS 版本为1.2,但 Android 的部分机型还未支持 TLS 1.2,所以请确保 HTTPS 服务器的 TLS 版本支持1.2及以下版本::
  2. wx.navigateTo(object) 保留当前页面,跳转到应用内的某个页面,使用wx.navigateBack可以返回到原页面。一个应用同时只能打开5个页面,当已经打开了5个页面之后,wx.navigateTo不能正常打开新页面。请避免多层级的交互方式,或者使用wx.redirectTo
  3. wx.setStorageSync(key,data) 将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。
  4. wx.getStorageSync(key) 从本地缓存中同步获取指定 key 对应的内容。
  5. wx.login(object) 调用接口获取登录凭证(code)进而换取用户登录态信息,包括用户的唯一标识(openid) 及本次登录的 会话密钥(session_key)。用户数据的加解密通讯需要依赖会话密钥完成。
  6. wx.getUserInfo(object) 获取用户信息,withCredentials 为 true 时需要先调用 wx.login 接口。通过该API返回的对象中包含了encryptedData 属性,该属性包括敏感数据在内的完整用户信息的加密数据,需要用加密数据解密算法解密。获取的用户信息可以用wx.setStorageSyncAPI保存在小程序本地。

3、页面逻辑

每个小程序页面都必须包含后缀为 .js 的页面逻辑文件,在这个文件里面,必须用 Page(object) 函数来注册页面,该函数接受一个object参数,其指定页面的初始数据、生命周期函数、事件处理函数等。在 JavaScript 文件中声明的变量和函数只在该文件中有效;不同的文件中可以声明相同名字的变量和函数,不会互相影响。

常用 object参数属性:

  • data 初始化数据,在视图文件中通过wxml绑定
<view>{{text}}</view>
<view>{{array[0].msg}}</view>
Page({
  data: {
    text: 'init data',
    array: [{msg: '1'}, {msg: '2'}]
  }
})
  • onLoad 生命周期函数--监听页面加载,一个页面只会调用一次,可以在 onLoad 中获取打开当前页面所调用的 query 参数
Page({
    onLoad(options){
        //options,其他页面打开当前页面所调用的 query 参数对象
    }
})
  1. onPullDownRefresh 页面相关事件处理函数--监听用户下拉动作,需要在config的window选项中开启enablePullDownRefresh
  2. onReachBottom 页面上拉触底事件的处理函数
  3. onShareAppMessage 用户点击右上角转发,只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮
  4. 事件处理函数,在渲染层(视图文件)可以在组件中加入事件绑定,当达到触发事件时,就会执行 Page 中定义的事件处理函数。 xml <view bindtap="viewTap"> click me </view> javascript Page({ viewTap: function() { console.log('view tap') } })

Page.prototype.setData()
setData 函数用于将数据从逻辑层发送到视图层,同时改变对应的 this.data 的值

<!--index.wxml-->
<view>{{text}}</view>
<button bindtap="changeText"> Change normal data </button>
<view>{{num}}</view>
<button bindtap="changeNum"> Change normal num </button>
<view>{{array[0].text}}</view>
<button bindtap="changeItemInArray"> Change Array data </button>
<view>{{object.text}}</view>
<button bindtap="changeItemInObject"> Change Object data </button>
<view>{{newField.text}}</view>
<button bindtap="addNewField"> Add new data </button>
//index.js
Page({
  data: {
    text: 'init data',
    num: 0,
    array: [{text: 'init data'}],
    object: {
      text: 'init data'
    }
  },
  changeText: function() {
    // this.data.text = 'changed data'  //直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致
    // 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。     
    this.setData({
      text: 'changed data'
    })
  },
  changeNum: function() {
    this.data.num = 1
    this.setData({
      num: this.data.num
    })
  },
  changeItemInArray: function() {
    // you can use this way to modify a danamic data path
    this.setData({
      'array[0].text':'changed data'
    })
  },
  changeItemInObject: function(){
    this.setData({
      'object.text': 'changed data'
    });
  },
  addNewField: function() {
    this.setData({
      'newField.text': 'new data'
    })
  }
})

4、WXML页面渲染

  • 数据绑定 > 数据绑定使用 Mustache 语法(双大括号)将变量包起来
    > 如果是组件属性、控制属性、关键字、简单的运算,都需要在双引号之内
<!--wxml-->
<view> {{message}} </view>
// page.js
Page({
  data: {
    message: 'Hello MINA!'
  }
})

在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。

默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item

<!--wxml-->
<view wx:for="{{array}}"> {{item}} </view>
// page.js
Page({
  data: {
    array: [1, 2, 3, 4, 5]
  }
})
  • 条件渲染 > 在框架中,我们用 wx:if="{{condition}}" 来判断是否需要渲染该代码块
<!--wxml-->
<view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view>
<view wx:elif="{{view == 'APP'}}"> APP </view>
<view wx:else="{{view == 'MINA'}}"> MINA </view>
// page.js
Page({
  data: {
    view: 'MINA'
  }
})

还可以使用hidden,使用hidden 组件始终会被渲染,只是简单的控制显示与隐藏

一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好

WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用

使用name属性,作为模板的名字。然后在