Zepto 与 jQuery 的差异总结

Zepto 是一个与 jQuery 高度相似的库,API 与 jQuery 一致,优点是体积小,核心模块压缩以后不到 10K。我们知道 jQuery 的缺点就是体积大,即便到了 2.x 版本,仍然有 80 多 K。而 Zepto 非常适合在移动端代替 jQuery,并且很多功能做成了模块,可以根据需要选用。

logo

虽然 API 与 jQuery 高度一致,但实际使用中,还是有着不少差异的,如下:

.css() 方法

在 jQuery 中的 .css() 方法非常强大,可以自动替你处理各个浏览器的 CSS 前缀,比如:

$('#el').css('transform', 'scale(0.5)');

如果浏览器不支持上面的属性,jQuery 可以自动帮你加前缀,比如 -webkit-transform 等。而这一点 Zepto 还做不到。你必须自己探测浏览器支持哪种前缀,然后使用 $('#el').css('-webkit-transform', 'scale(0.5)')

另外一点,.css() 方法支持驼峰式的属性名,比如 $('#el').css('backgroundColor', 'green'),jQuery 和 Zepto 可以转换为 background-color。但是对于带前缀的属性,Zepto 就不行了,如:

$('#el').css('webkitTransform', 'scale(0.5)');

在 jQuery 可以,但是 Zepto 会把属性名转换为 webkit-transform,少了前面的 - 号。

其他几处差异

  • 使用属性选择器,$('[attr="value"]')value 必须要加引号
  • 没有 innerWidth(), innerHeight(), outerWidth(), outerHeight() 四个方法,只有 width()height(),分别返回 offsetWidthoffsetHeight
  • offset() 方法返回元素在文档中的位置,jQuery 中只有 top, left 两个属性,Zepto 多了 widthheight
  • .clone() 不支持克隆元素上的事件
  • .animate() 的参数有区别
  • 使用自定义事件时,都支持命名空间,比如 $(el).on('click.myPlugin', fn),而 Zepto 不支持事件名称出现 . 号,可以换成冒号

额外功能

有一些 jQuery 常用的功能,Zepto 的核心模块里都没有,因为出于缩小体积的考虑。必须添加额外的模块才能使用这些功能:

  • .data() 方法默认只能存储字符串,如果想要存储对象,需要 data.js 模块
  • tapswipe 事件,需要 touch.js。Zepto 的 tap 事件不是很完美,经常导致穿透现象,建议改用 FastClick
  • pinch 事件需要 gesture.js
  • $.Deferred需要 deferred.js
  • .animate() 需要 fx.js。show/hide/toggle/fadeIn/fadeOut 需要 fx_methods.js
  • .end()andSelf() 方法,需要 stack.js

如何给前端工具设置代理

在天朝,当一个开发者非常苦逼,因为说到开源,就是相互交流,而有堵墙总是在阻断这个交流的过程,不断想要把我们困在笼子里。比如你查个技术文章,这时候“CONNECTION RESET”了,装个 npm 包,一直打转,死活装不上,这时候程序员的内心都是抓狂的。因此科学上网是大家必须掌握的技能,这篇文章不是探讨这个,假设你已经有了梯子,现在介绍下各个工具比如 Sublime Text、npm 等如何设置代理。

ss

假设本地开启的 http 代理为 http://127.0.0.1:1080。(Shadowsocks 默认就是这个地址)。ss 是一个很棒的工具,虽然作者被喝茶现在不维护了,但仍然可以由别人进行下去,在此向 clowwindy 的付出表示敬意。如果用的是 Mac 版 ShadowsocksX,默认只开启了一个 SOCKS5 代理,没有 http 代理,可以利用 polipo 来转换成 http 代理,简单介绍一下方法:

先安装 Homebrew,然后执行下面命令安装 polipo:

brew install polipo

然后,修改 /usr/local/opt/polipo/homebrew.mxcl.polipo.plist 文件,找到 <string>/usr/local/opt/polipo/bin/polipo</string> 这句,在下面添加一行:

<string>socksParentProxy=localhost:1080</string>

其实是把 http 流量导到 localhost:1080 的 SOCKS 代理上。第三步,设置开机启动:

ln -sfv /usr/local/opt/polipo/*.plist ~/Library/LaunchAgents

设置完成后,还需要手动启动一下 polipo:

launchctl load ~/Library/LaunchAgents/homebrew.mxcl.polipo.plist

这时就 OK 了,polipo 生成的 http 代理,默认地址为 localhost:8123。如果想要暂时终止 polipo,可以执行:

launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.polipo.plist

Sublime Text

这里其实是给 Package Control 设置代理,方法:

打开“Preferences -> Package Settings -> Package Control”-> Settings – User,写入下面键值:

{
  "http_proxy": "127.0.0.1:1080"
}

npm

npm 其实有两个办法,一是用 cnpm,这是淘宝提供的一个 npm 镜像,好像是每 15 分钟跟 npm 同步一次。安装好了以后,你需要用 cnpm 命令来代替 npm 命令使用。安装方法(先装好 npm):

npm install -g cnpm --registry=https://registry.npm.taobao.org

用了这个就不用担心很慢的问题了。

第二个办法是给 npm 设置代理,运行以下命令行:

npm config set proxy http://127.0.0.1:1080 --global
npm config set https-proxy http://127.0.0.1:1080 --global

注意 --global 标志,表示配置被写入系统配置文件。Windows 下位于 C:\Users\用户名\AppData\Roaming\npm\etc\npmrc,Mac 下位于 ~/.npmrc)

Atom

C:\Users\用户名\.atom 下建立 .apmrc 文件(Mac 对应 ~/.atom/.apmrc),写入:

proxy = http://127.0.0.1:1080
https-proxy = http://127.0.0.1:1080

当然也可以用类似上面的 npm 命令,只要把 npm 换成 apm 就行了。

阅读全文 »

ES6 快速上手:模块

es6-small

ES6 引入了原生的模块支持。

一个 ES6 模块就是一个 JS 文件,除了以下两个区别:

  • 会自动开启严格模式
  • 可以使用 importexport 关键字

ES6 模块还有一些特点:

  • 模块中声明的变量都是私有的,即使在最外层作用域声明
  • 在模块最外层,this 指向 undefined

模块中声明的变量,默认都是私有的。如果要暴露给其他模块使用,需要 export

// mod1.js
export function foo(a, b, c) {
  ...
}
export class Bar() {
  ...
}

export 可以用在 function, class, var, let, const 前。如果输出的是上例中的 functionclass 声明,则必须有函数名或类名,不能匿名。

如果想要一次性导出多个接口,也可以这样写:

export {foo, bar};

export 语句必须放在最外层,也就是,不能在语句块中 export

import 用来导入模块:

import {foo} from "mod1.js";

或者,导入多个接口:

import {foo, bar} from "mod1.js";

mod1.js会被提前导入并加载。注意导入以后生成的变量 foobar 都是常量,类似 const。所以你不能再声明一个同名变量,或者修改 foobar

你也可以将模块的所有接口整个导入:

import * as myModule from "mod1.js";

这样 mod1.js 的所有接口都被绑定在了 myModule 对象上。你可以在 myModule 上调用 myModule.foo()

不管被导入多少次,一个模块内的代码始终只执行一次:

import { foo } from "mod1.js";
import { bar } from "mod1.js";

重命名接口

导入时,可以给接口重命名:

import {foo as bar} from "mod1.js";

导出时也可以:

export {
  foo as foo1,
  bar as bar2
};

重命名以后,只能使用新的名字。

default

每个 ES6 模块可以有一个默认导出,使用的是 default 关键字:

export default function(a, b) {
  ...
}

export default 关键字后可以跟随任何值。使用 default 时,函数可以是匿名的。

你也可以指定某一个变量为 default

let myObject = {
  foo() {
    return "foo...";
  },
  bar: 66
};
export {myObject as default};

要将一个模块的默认导出 import 进来:

import myModule from "mod1.js";

注意 myModule 没有了花括号。此时在 myModule 上就可以调用 foobar 了。

这种设计是为了与已存在的 CommonJS 和 AMD 模块相兼容。以 jQuery 为例:

import $ from "jquery";

一个模块只能有一个默认导出。但也可以同时存在非默认的导出:

// mod2.js
export let a = 123;
export default function(b, c) {
  ...
}

可以这样把两者一同导入进来:

import foo, { bar } from "mod2.js";

注意非默认的导出总是有一对花括号。默认导出也可以放进括号里,下面语句是同样的作用:

import { default as foo, bar } from "mod2.js";

阅读全文 »

ES6 快速上手:生成器

es6-small

生成器又叫“Generators”,是 ES6 的一个重要的新特性。

生成器是一种特殊的函数,下面就是一个生成器:

function* demo(name) {
  yield "Hi " + name + "!";
  yield "Welcome!";
  yield "Goodbye!";
}

调用以后,它返回一个迭代器。里面的 yield 关键字,用来指定每次调用 .next() 所应该返回的值。调用一下:

var iterator = demo("William");

iterator.next(); //{ value: "Hi William!", done: false }
iterator.next(); //{ value: "Welcome!", done: false }
iterator.next(); //{ value: "Goodbye!", done: false }
iterator.next(); //{ value: undefined, done: true }

可以看到,生成器函数的一个特点,就是每次遇到 yield 关键字,函数的执行都会暂停,yield 后面的表达式的值被返回。再调用 .next() ,函数就往下执行,到下一个 yield

生成器也有表达式语法:

let myGenerator = function *(arg) {
  ...
};

生成器的语法要点就两部分:function*yield* 两边允许空格的存在,只要是在 function 和函数名之间就可以。 yield 后面可以跟任意的表达式。

生成器的主要用处是两个:作为迭代器,异步编程。

作为迭代器使用

生成器作为一种函数,它可以成为对象的属性。回想一下《for…of 和迭代器》中所讲的可遍历对象,只要对象拥有一个能返回迭代器的 [Symbol.iterator]() 方法,就能成为一个可遍历对象。既然生成器正好就返回一个迭代器,所以,生成器就是 [Symbol.iterator]() 方法的最佳选择。比如:

var o = {
  items: [1, 2, 3],
  [Symbol.iterator]: function *() {
    for(let i = 0; i < this.items.length; i++) {
      yield this.items[i];
    }
  }
};

用 ES6 新的简写语法,还可以这样写:

var o = {
  items: [1, 2, 3],
  *[Symbol.iterator]() {
    for(let i = 0; i < this.items.length; i++) {
      yield this.items[i];
    }
  }
};

生成器里也可以使用 return 来提前结束遍历:

function *foo() {
    yield 1;
    return;
    yield 2;
    yield 3;
}
let iterator = foo();

iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: undefined, done: true }

迭代器的 .next() 方法还可以传参,比如

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; // 4 + 2
    yield second + 3; // 5 + 3
}

let iterator = createIterator();

iterator.next(); // { value: 1, done: false }
iterator.next(4); // { value: 6, done: false }"
iterator.next(5); // { value: 8, done: false }"
iterator.next(); // { value: undefined, done: true }

传给 .next() 的参数,会变为上一个 yield 的返回值。因此,第一次调用 .next() 时不要传参数,传了也获取不到。

解析一下上例的执行过程:第一次调用 .next() 时,生成器内部的代码开始执行。第一句是一个赋值语句 let first = yield 1; 自右向左计算,所以先 yield 1,暂停了。第二次调用 .next() 并传入了 4,函数继续执行左边部分 let first = ...,因为 4 成为 yield 的返回值,所以 let first = 4。第二行同理,在 yield first + 2 也就是 yield 6 暂停。第三次调用 .next(5)以后,let second = 5,最后 yield second + 3 的结果是 8。

生成器另一大用处是在异步编程中,因为生成器有暂停的功能。执行一个异步函数时,可以 yield 暂停,等到异步任务完成,再调用 .next() 继续。

yield*

生成器还可以委托,说白了就是,在生成器中调用另一个生成器,例如:

阅读全文 »

ES6 快速上手:for…of 和迭代器

es6-small

for ... of 是 ES6 新增的循环语句,可以遍历一个集合(比如数组),用来代替普通的 for 循环或 forEach。一个例子:

var myArray = ["a", "b", "c"];
for (var value of myArray) {
  console.log(value);
}

那用 for ... of 有什么好处呢?显然用 forEach 也能遍历数组元素,但是 forEach 的缺陷是无法响应 break, continue, return 语句。所以,不能自由跳出,只能循环到底。另外有一个小提示,在大多数浏览器中 forEach 遍历都比 for 循环要慢。虽然 forEach 更简洁,但是从执行效率考虑应当使用 for

然后你会想到用 for...in,但 for...in 也存在一个问题,就是 for...in 给出的索引都是字符串,比如:

var myArray = ["a", "b", "c"];
for (var value in myArray) {
  console.log(value); //"0", "1", "2"
}

如果你直接拿这些索引来做运算,就会出错,比如 value + 10,会得到 "010" 而不是 10。而且,for...in 不光遍历数组内的元素,如果你往数组上添加了自定义的属性,也会被遍历出来。还有一点,最糟糕的情况下,for...in 还可能会不按 0、1、2……的顺序进行遍历。

for...of 可以克服以上这些缺点。它既支持 break, continue, return,同时,索引的类型和原来类型保持一致,对于数组,索引就是 Number 类型。

for...of 也可以遍历类数组对象,比如 NodeList 类型。

还能用来遍历字符串,会把它当作是字符序列:

for (var char of "(⊙ˍ⊙)") {
  alert(char);
}

还支持遍历 MapSet 对象。因为 Map 对象都是键值对,所以,需要使用解构赋值:

for (var [name, age] of persons) {
  console.log(name + "'s age is: " + age);
}

但是 for...of 并不支持遍历普通对象。你可以用 for...in 代替,或者借助 Object.keys

for ( var key of Object.keys(obj) ) {
  console.log(key + ": " + obj[key]);
}

除了上面提到的类型,在其他对象上调用 for...of 会抛出错误,包括nullundefined。为什么?因为 for...of 需要有迭代器的配合。

迭代器(iterators)

“迭代器”:简单来说,迭代器的核心就是一个 .next() 方法,每次调用 .next(),应该返回一个类似下面的对象:

{
  value: 100,
  done: false
}

假设 a[1, 2, 3] 的迭代器,那么每次调用迭代器的 .next(),应该是这样的结果:

a.next(); // { value: 1, done: false }
a.next(); // { value: 2, done: false }
a.next(); // { value: 3, done: false }
a.next(); // { value: undefined, done: true }

可以看出,每次返回的 value 就是从数组中抽出的一个元素,一直遍历到最后,done 表示状态,当它变为 true 时,遍历就结束了。

迭代器的作用就是用来遍历集合,再对里面的数据进行处理,就像我们经常用的 forEach 一样。迭代器的好搭档是 for...of

可遍历对象(iterables)

前面说了,只有在数组、NodeListSetMap、字符串上可以使用for...of,因为它们都是可遍历对象,称为“iterables”。在普通对象上使用 for...of 会抛出错误。

什么是可遍历对象?简单来说,只要对象上拥有一个 [Symbol.iterator]() 方法,该方法返回一个迭代器。那这个对象就是可遍历的,可以用 for...of 进行遍历。

[Symbol.iterator]() 所返回的迭代器又叫默认迭代器(default iterator)。上面所讲的那些类型,都内置了默认迭代器,所以能在 for...of 中使用。

阅读全文 »

ES6 快速上手:Symbol 类型

es6-small

ES5 的时候有六种类型:

  • undefined
  • null
  • Boolean
  • Number
  • String
  • Object

现在,ES6 增加了一种新类型:Symbol。下面是一个创建 symbol 的例子:

var mySymbol = Symbol();
typeof mySymbol; //"symbol"

调用 Symbol 函数就可以创建一个 symbol。因为 symbol 是一个原始类型,并不是对象,所以 new Symbol() 会报错。

symbol 的用处就是作为对象的键。用 symbol 作为对象的键可以避免传统的属性名冲突。比如,用上例的 mySymbol 作为键:

obj[mySymbol] = "ok!"; 

创建 symbol 时,可以传入一个字符串描述:

var sym = Symbol("hi");

即使你每次都传入相同的字符串,返回的 symbol 也互不相等。正因为有这个特性,所以 Symbol 类型可以很好地解决属性名冲突的问题。

什么是冲突呢?当多人合作编码的时候,经常会出现你往对象上加了一个某某属性(比如 $),他人正好也想到了这个名称,当你们同时用了这个名称作为属性,代码之间就会发生冲突,互相覆盖。而用 symbol,即使都用了相同的描述,也不是同一个 symbol。

再列出 Symbol 类型的一些特点:第一,以 symbol 为键的属性,不能用 obj.prop 形式访问,只能用方括号。

第二,for...in 循环、Object.keysObject.getOwnPropertyNames 在遍历一个对象时,都会忽略 symbol 键。

如果你想查看对象上有哪些 symbol 键,可以使用 ES6 的新 API:Object.getOwnPropertySymbols(),返回一个数组,里面都是对象所包含的 symbol。

如果想查看所有属性,用另一个 API:Reflect.ownKeys(obj),会同时返回字符串键和 symbol 键。

第三,in 操作符可以正常工作,比如上例,mySymbol in obj会返回 true。执行 delete obj[mySymbol] 也可以删除这个属性。

第四,symbol 类型不能被自动转换为字符串:

var sym = Symbol("hello");
alert("my symbol is " + sym); // TypeError

可以通过 String(sym) 或调用 sym.toString()

共享 symbol

由于每次调用 Symbol("hello") 返回的 symbol 都是不同的,但有时会有共享同一个 symbol 的需要,所以 ES6 提供了一个全局的 symbol 注册表。

如果你想要共享一个 symbol,用 Symbol.for() 来代替 Symbol()

let sym = Symbol.for("hello");

调用 Symbol.for() 时,如果传入相同的字符串,那么返回的就是同一个 symbol。如果该 symbol 还不存在,则会创建一个 symbol 返回。

Symbol.for() 创建的 symbol 上,还可以调用 Symbol.keyFor() 来查看它的描述。

let sym = Symbol.for("hello");
let sym2 = Symbol.for("hello");
Symbol.keyFor(sym); //"hello"
Symbol.keyFor(sym2); //"hello"

let sym3 = Symbol("hello");
Symbol.keyFor(sym3); // undefined

你可以看到在一个普通 Symbol() 创建的 symbol 上调用 Symbol.keyFor() 会发生什么。

内置 symbol

ES6 内置了一些 symbol,它们都作为 Symbol 函数的属性存在,比如后面关于迭代器文章里要提到的 Symbol.iterator。但是,调用 Object.getOwnPropertySymbols() 时,这些内置 symbol 不被计算在内。

ES6 快速上手:类

es6-small

ES6 终于引入了类。下面是一个类的例子:

class Person {
  constructor(age, country) {
    this._age = age;
    this._country = country;
  }

  get age() {
    return this._age;
  }

  get country() {
    return this._country;
  }

  toString() {
    return `${this.age} ${this.country}`;
  }
}

在上例中,constructor 代表类的构造函数。constructor 里面定义的属性,都是实例属性。定义类时,可以没有 constructor

agecountrytoString 都是原型属性和方法,相当于定义在了 Person.prototype 上。其中 agecountry 是只读的。

如你所见,属性和属性之间,不再需要分隔符了。你可以继续用逗号,或者用分号也可以。

类的语法

第一种是类的声明:

class MyClass {
 ...
}

语法要点:

  • 注意名字后面不能有括号。
  • 类的声明不会被提升到作用域顶部,所以必须先定义后使用。
  • classlet 一样,不能重复声明。

第二种是类表达式:

var MyClass = class {
  constructor() {
    //一个实例属性
    this.name = "demo";
  }
  method() {
    //一个原型方法
  }
}

这种语法下,class 后面也可以再加一个标识符:

var MyClass = class SameClass {
  ...
}

此时 MyClassSameClass 是一样的,可以互相替换。

创建实例还和以前一样:

var instance = new MyClass();

类的一些特点

如果你用 typeof MyClass,会发现其实它还是一个 function

如果没有 new,直接调用 MyClass 会抛出错误。

声明类的代码会全部在严格模式下执行,相当于自动加了 "use strict";

class 里面定义的所有方法都是不可枚举的。也就是说,不能通过 for...in 遍历到,Object.keys 也获取不到。

类也可以当作函数的参数:

function createObj(MyClass) {
  return new MyClass();
}

let obj = createObj(class {
  foo() {
    console.log("hello");
  }
});
obj.foo(); //"hello"

类也可以立即执行,只要在后面多加个括号:

let singleton = new class {
  constructor(foo) {
    this.foo = foo;
  }
  bar() {
    ...
  }
}("hello");

可以用来创建单例(singleton)。

访问器属性

类也可以添加访问器属性,像对象一样。访问器属性就是一对 getter/setter,语法如下:

class Person {
  constructor(name) {
    this.name = name;
  }

  get age() {
    return this.age;
  }
  set age(value) {
    this.age = value;
  }
}

如果省略 setter,属性就是只读的。

静态成员

class Person {
  static walk(a, b) {
     ...
  }
}

静态方法的调用:Person.walk(),其实等同于在 Person 上创建了 walk() 方法。

静态成员默认也是不可枚举的。

阅读全文 »

ES6 快速上手:解构赋值

es6-small

这个特性又叫做“destructuring”。其语法的一般形式为:

[ variable1, variable2, ..., variableN ] = array;

这是数组的解构赋值。后文还有对象的解构赋值。

假设有数组 value = [1, 2, 3],要把三个值分别赋给三个变量 a, b, c,在ES5中要这样写:

var a = value[0];
var b = value[1];
var c = value[0];

ES6 中使用解构赋值:

[a, b, c] = value;

如果需要同时声明变量:

let [a, b, c] = value;

当然换成 var, const 也同样可以。结合“rest”使用:

var [head, ...tail] = [1, 2, 3, 4];
console.log(tail); // [2, 3, 4]

也可以嵌套:

var value = [1, 2, [3, 4, 5]];
var [a, b, [c, d]] = value; //a,b,c,d分别为1,2,3,4

在指定位置省略:

var value = [1, 2, 3, 4, 5];
var [a, , c, , e] = value; //1,3,5

如果出现越界访问的情况,值是 undefined

var value = [1];
var [a, b] = value; //1,undefined

这时候可以指定默认值:

var value = [1];
var [a = 100, b = 2] = value; //1,2

注意的是默认值只对 undefined 值起作用:

var value = [null, null];
var [a = 1, b = 2] = value; //a,b还是null

用处:可以用来交换两个变量的值(没有比这更简单的了):

[el1, el2] = [el2, el1];

或者,从正则表达式的结果里取出分组:

var [, firstName, lastName] = "John Doe".match(/^(w+) (w+)$/);

对象的解构赋值:

var personA = { name: "Paul" };
var personB = { name: "Lily" };

var { name: nameA } = personA;
var { name: nameB } = personB;

console.log(nameA); // "Paul"
console.log(nameB); // "Lily"

对象的 name 属性分别赋给了 nameAnameB。如果用下面这种简洁的写法:

var personA = { name: "Paul" };
var { name } = personA;

那么会把 name 属性赋给同名的变量 name

深层嵌套的对象:

var person = {
  name: {
    firstName: "John", 
    lastName: "Doe"
  }
};
var {name: {firstName, lastName}} = person; //得到两个变量firstName,lastName,注意name未定义

对象里嵌套数组:

var person = {
  dateOfBirth: [1, 1, 1980]
};
var {dateOfBirth: [day, month, year]} = person; //得到三个变量day, month, year

发生嵌套时,一般后面跟了冒号的变量都是未定义的,如上例的 dateOfBirth。这点要注意。

如果要解构的属性不存在,得到的是 undefined。对象解构赋值也可以使用默认值:

var {firstName = "John", lastName: userLastName = "Doe"} = {};

默认值对于 null 同样不起作用。

注意对于下面的写法,会抛出错误:

{ blowUp } = { blowUp: 10 }; // SyntaxError 

因为JS中任何以 { 开始的语句都被解析为一个块语句。解决方案是将整个表达式用一对小括号包裹:

({ blowUp } = { blowUp: 10 });

用处:第一,函数参数的解构:

function myFunc(options) {
  if (options.a) ...
  if (options.b) ...
}

这种语句经常出现在我们的代码中。用 ES6 就看上去更简洁:

function myFunc( {a, b} ) {
  if (a) ...
  if (b) ...
}

阅读全文 »

ES6 快速上手:模板字符串

es6-small

ES6 引入了一种新型的字符串字面量语法,称为模板字符串(template strings)。使用反撇号(`)来代替普通字符串的引号。下面是一个例子:

let greeting = `Hello Jackie`!

有什么用呢?模板字符串为 JavaScript 提供了简单的插值功能(string interpolation)。例:

let name = "Jackie";
let greeting = `Hello ${name}`!

${name} 称为占位符(placeholder)。模板占位符可以是任意 JavaScript 表达式,比如函数调用、算数运算等,都可以作为占位符使用,甚至可以嵌套另一个模板字符串。

如果占位符计算出的值不是字符串,那么会调用 toString 方法。

如果要书写反撇号(`),或者 $,或者 {,都需要转义(加反斜杠)。

与普通字符串的一大区别是,模板字符串可以多行书写:

$("#warning").html(`
  <h1>你好!</h1>
  <p>我叫${name}。这是一个多行
  模板字符串的
  示例。</p>
`);

所有的空格、新行、缩进,都会原样输出在生成的字符串中。

但是,模板字符串并不会自动转义 HTML 字符(比如 < > 等),而且,不支持条件语句、遍历,比其他的模板库还是弱了些。为了突破这些限制,ES6 提供了标签模板(tagged templates)。只需要在起始的反撇号前加上一个额外的标签,如下面的 htmlspecial

var message = htmlspecial`<p>${htmlStr}</p>`;

上面的代码等效于

var message = htmlspecial(templateData, htmlStr);

templateData 是一个不可变数组,存储着模板所有的字符串部分,也就是除去占位符以后剩下的部分,比如上例中 ${htmlStr} 将模板分割为两部分,所以 templateData 数组内就是两个元素,形如 ["<p>", "</p>"]。同时这个数组是不能更改的,相当于用了 Object.freeze

htmlspecial 的一种实现(用来将 < > 等特殊字符转义为实体引用):

function htmlspecial(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
    var arg = String(arguments[i]);

    // 转义HTML特殊字符。
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // 拼接
    s += templateData[i];
  }
  return s;
}

函数返回的值就会当作模板字符串的输出。

ES6 快速上手:箭头函数

es6-small

ES6 添加了新的函数语法:箭头函数。语法像下面这样:

([param, param, ...]) => {
  函数体
}

几个例子:

() => { ... } //无参数
x => { ... } //一个参数
(x, y) => { ... } //多个参数
(x, y) => {
  if(x > 1) {
    ...
  }
}

可以这样用:

let foo = x => { return x + 10; }

等同于下面:

function foo(x) {
  return x + 10;
}

如果只有一个 return 语句的话,还可以简写成:

let foo = x => x + 10; // x + 10的值会被返回

当然,如果简写的话你需要注意,如果返回的是一个对象,比如一个空对象:

let foo = x => {}; //报错

这时需要外加一层小括号:

let foo = x => ({}); //OK
//或者老老实实写成:
let foo = x => {
  return {};
};

立即执行的函数(IIFE)在 ES5 中是这样:

(function(name) {
  var greetings = "Hello " + name;
  return greetings;
})("Ethan");

同样可以换成箭头函数:

(name => {
  let greeting = "Hello " + name;
  return greeting;
})("Ethan");

箭头函数有几个特性,首先,是 this 值固定。this 值取决于箭头函数定义时的上下文,与如何调用无关:

var person = {
  name: "Bob",
  sayHello: () => "Hello " + this.name;
};

var hello = person.sayHello;
hello(); //"Hello Bob"

并且,this 的值是无法改变的。一旦创建,this 值就已绑定。

第二,箭头函数不能用作构造函数,当和 new 一起使用时会报错。

第三,箭头函数没有自己的 arguments 对象。所以,在箭头函数内访问 arguments,这时访问的其实是外层函数的 arguments 对象。你可以使用 ES6 的新特性来代替 arguments

let foo = (...args) => {
  console.log(args); //和arguments一样
};

最后,箭头函数和普通函数一样,也会在脚本执行前被提升到作用域顶部。

flight