类数组对象(Array-like)

  1. 具有指向对象元素的数字索引下标以及 length 属性,告诉我们对象的元素个数
  2. 不具有诸如 push 、forEach 以及 indexOf 等数组对象具有的方法

常见的类数组对象

  • document.getElementsByClassName()
  • document.getElementsByTagName()
  • document.getElementsByName()
  • document.stylesheets
  • parentNode.chidlNodes
  • arguments
  • document.querySelectorAll()
  • 具有属性和长度的对象
1
2
3
4
5
var arrayObj = {
0 : 'Benjamin01',
1 : 'Benjmain02',
length : 2
};

转换为数组对象

slice()方法可以将一个类数组(Array-like)对象/集合转换成一个数组. 你只需要用数组原型上的slice方法call这个对象,即Array.prototype.slice.call(Array-like); 同时也可以简单的使用 [].slice.call(arguments)来代替。 MDN slice

兼容

IE<9中不能使用Array.prototype.slice,对于NodeList等并非一个JavaScript object,IE9 中Array.prototype.slice.call(NodeList) 已经不再抛异常了,可以使用其将NodeList等转换为数组

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var
parent = document.getElementById("parent"),
//此处childNodes在不同浏览器中的返回值不同
//Chrome、FF、IE9+ 长度为7(4个text + 3个div)
//IE6-IE8 长度为3(3个div)
nodes = parent.childNodes,
slice = Array.prototype.slice,
convertToArray = function(nodes){
var arr = [];
try{
arr = slice.call(nodes);
console.log("AAA");
}catch(ex){
for(var i = 0,ilen = nodes.length; i < ilen; i++){
arr.push(nodes[i]);
}
}
return arr;
}
console.log(convertToArray(nodes));

my demo

1
2
3
4
5
6
7
8
function makeArray(obj){
return [].slice.call(obj);
}
var div_list = document.querySelectorAll('p'); // 返回 NodeList
var div_array = Array.prototype.slice.call(div_list); // 将 NodeList 转换为数组
div_array.push(123);
console.log(div_array);

参考链接

AMD

AMD规范,全称”Asynchronous Module Definition”,称为异步模块加载规范。一般应用在浏览器端。流行的浏览器端异步加载库RequireJS(中文网站)实现的就是AMD规范。

下面是使用AMD规范定义一个名为foo模块的方式,此模块依赖jquery,

//    filename: foo.js
define(['jquery'], function ($) {
    //    methods
    function myFunc(){};

    //    exposed public methods
    return myFunc;
});

AMD讲究的是前置执行。稍微复杂的例子如下,foo模块有多个依赖及方法暴漏,

//    filename: foo.js
define(['jquery', 'underscore'], function ($, _) {
    //    methods
    function a(){};    //    private because it's not returned (see below)
    function b(){};    //    public because it's returned
    function c(){};    //    public because it's returned

    //    exposed public methods
    return {
        b: b,
        c: c
    }
});

define是AMD规范用来声明模块的接口,示例中的第一个参数是一个数组,表示当前模块的依赖。第二个参数是一个回调函数,表示此模块的执行体。只有当依赖数组中的所有依赖模块都是可用的时,AMD模块加载器(比如RequireJS)才会去执行回调函数并返回此模块的暴露接口。

注意,回调函数中参数的顺序与依赖数组中的依赖顺序一致。(即:jquery->$,underscore->_)

当然,在这里我可以将回调函数的参数名称改成任何我们想用的可用变量名,这并不会对模块的声明造成任何影响。

除此之外,你不能在模块声明的外部使用$或者_,因为他们只在模块的回调函数体中才有定义。

我之前有转载阮一峰老师的模块化文章,有关于requirejs的具体教程

CMD

CMD规范,全称”Common Module Definition”,称为通用模块加载规范。一般也是用在浏览器端。浏览器端异步加载库Sea.js实现的就是CMD规范。

下面是使用AMD规范定义一个名为foo模块的方式,此模块依赖jquery,

define(function (require, exports, module) {
    // load dependence
    var $ = require('jquery');

    //    methods
    function myFunc(){};

    //    exposed public methods
    return myFunc;
})

CMD规范倾向依赖就近 ,稍微复杂一点例子

define(function (requie, exports, module) {
    // 依赖可以就近书写
    var a = require('./a');
    a.test();

    // ...
    // 软依赖
    if (status) {
        var b = requie('./b');
        b.test();
    }
});

关于AMD和CMD的区别,可参考这篇文章,AMD/CMD与前端规范

CommonJS

根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。

如果你在Node.js平台上写过东西,你应该会比较熟悉CommonJS规范。与前面的AMD及CMD规范不一样的是,CommonJS规范一般应用于服务端(Node.js平台),而且CommonJS加载模块采用的是同步方式(这跟他适用的场景有关系)。同时,得力于Browserify这样的第三方工具,我们可以在浏览器端使用采用CommonJS规范的js文件。

下面是使用CommonJS规范声明一个名为foo模块的方式,同时依赖jquery模块,

1
2
3
4
5
6
7
8
9
// filename: foo.js
// dependencies
var $ = require('jquery');
// methods
function myFunc(){};
// exposed public method (single)
module.exports = myFunc;

稍微复杂一点的示例如下,拥有多个依赖以及抛出多个接口,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// filename: foo.js
var $ = require('jquery');
var _ = require('underscore');
// methods
function a(){}; // private because it's omitted from module.exports (see below)
function b(){}; // public because it's defined in module.exports
function c(){}; // public because it's defined in module.exports
// exposed public methods
module.exports = {
b: b,
c: c
};

UMD

因为AMD,CommonJS规范是两种不一致的规范,虽然他们应用的场景也不太一致,但是人们仍然是期望有一种统一的规范来支持这两种规范。于是,UMD(Universal Module Definition,称之为通用模块规范)规范诞生了。

客观来说,这个UMD规范看起来的确没有AMD和CommonJS规范简约。但是它支持AMD和CommonJS规范,同时还支持古老的全局模块模式。

我们来看个示例,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// methods
function myFunc(){};
// exposed public method
return myFunc;
}));

个人觉得UMD规范更像一个语法糖。应用UMD规范的js文件其实就是一个立即执行函数。 函数有两个参数,第一个参数是当前运行时环境,第二个参数是模块的定义体。在执行UMD规范时,会优先判断是当前环境是否支持AMD环境,然后再检验是否支持CommonJS环境,否则认为当前环境为浏览器环境(window)。当然具体的判断顺序其实是可以调换的。

下面是一个更加复杂的示例,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'), require('underscore'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// methods
function a(){}; // private because it's not returned (see below)
function b(){}; // public because it's returned
function c(){}; // public because it's returned
// exposed public methods
return {
b: b,
c: c
}
}));

Tips: 如果你写了一个小工具库,你想让它及支持AMD规范,又想让他支持CommonJS规范,那么采用UMD规范对你的代码进行包装吧,就像这样

最近用手机浏览我的博客的时候发现主页宽度总是有问题,文章却没有问题,今天用chrome看了一下,发现应该是hexo-theme-next这个主题的bug,bug主要有两个

`` bug

table bug

在hexo-next-theme中 table的样式为

table {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    border: 1px solid #ddd;
    font-size: 14px;
}

由于table元素把整个页面撑开,造成手机端宽度变大,看起来就像被缩放了

解决办法

改table的父元素为改为可滚动元素,如下:

.posts-expand .post-body {
    text-align: justify;
    overflow: scroll;
}

参考:

处理一个集合中每一项是很常见的操作。JavaScript 提供了好几种方法来遍历一个集合,从简单的 for 和 for each 循环至 map(),filter() 和 array comprehensions。 迭代器和生成器,在 JavaScript 1.7 中, 迭代器的概念属于核心语言中的重点,并提供了一种机制来定义 for…of 循环。

迭代器

迭代器是一个对象,知道如何存取物品从一个集合一次取出一项, 而跟踪它的当前序列所在的位置。在 JavaScript 中 迭代器是一个对象,它提供了一个 next() 方法返回序列中的下一个项目。当这个序列消亡时这个方法可以随时抛出一个StopIteration exception(异常)。

一旦创建,迭代器对象可以显式地通过不断调用 next(),或隐式地使用 JavaScript 的 for…in 和 for each 构造。

简单的迭代器对象和数组可以使用 Iterator() 函数创建(下面是一个简单的对像)。

生成器(Generators): 一个更好的方法来构建遍历器EDIT

虽然迭代器是一个有用的工具,但是由于需要显示地维持他们的内部状态,所以他们的构造需要仔细的规划(看得我眼花呀)。生成器给你提供了一个强大的选择:它允许你通过写一个可以保存自己状态的的简单函数来定义一个迭代算法。

一个生成器其实是一种特殊类型的函数(这个函数作为一个为迭代器工作的工厂),一个函数如果它里面包含了一个或一个以上的yield表达式,那么这个函数就成为一个生成器了。

当一个生成器函数被一个函数体调用时并不会马上执行;相反,它会返回一个generator-iterator对象。每次调用generator-iterator的next()方法将会执行这个函数体至下一个yield表达式并且返回一个结果。当函数执行完或者执行到一个return时抛出一个StopIteration exception异常。

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

高级生成器

生成器按照要求产生values,这样可以高效的表示序列,这点对于计算机来说很重要,上面的无穷序列就是一个很好的证明。

除了next()方法,generator-iterator对象也有一个send()方法可以用来修改生成器的内部状态。传递一个值给send()将被视为最后一个yield表达式的结果(生成器暂停)。你必须在你使用send(参数)前通过调用next()来启动生成器。

这里是斐波那契生成器使用send()来重新启动序列:

function fibonacci(){
  var fn1 = 1;
  var fn2 = 1;
  while (1){
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    var reset = yield current;
    if (reset){
        fn1 = 1;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
print(sequence.next());     // 1
print(sequence.next());     // 1
print(sequence.next());     // 2
print(sequence.next());     // 3
print(sequence.next());     // 5
print(sequence.next());     // 8
print(sequence.next());     // 13
print(sequence.send(true)); // 1
print(sequence.next());     // 1
print(sequence.next());     // 2
print(sequence.next());     // 3

生成器拥有一个 close() 方法来强制生成器结束。结束一个生成器会产生如下影响:

  • 所有生成器中有效的 finally 字句将会执行
  • 如果 finally 字句抛出了除 StopIteration 以外的任何异常,该异常将会被传递到 close() 方法的调用者
  • 生成器会终止

function*

function* 声明(function关键字后跟一个星号)定义一个generator(生成器)函数,返回一个Generator对象。

描述

生成器是一种可以从中退出并在之后重新进入的函数。生成器的环境(绑定的变量)会在每次执行后被保存,下次进入时可继续使用。

调用一个生成器函数并不马上执行它的主体,而是返回一个这个生成器函数的迭代器(iterator)对象。当这个迭代器的next()方法被调用时,生成器函数的主体会被执行直至第一个yield表达式,该表达式定义了迭代器返回的值,或者,被 yield*委派至另一个生成器函数。next()方法返回一个对象,该对象有一个value属性,表示产出的值,和一个done属性,表示生成器是否已经产出了它最后的值。

示例

简单示例

function* idMaker(){
  var index = 0;
  while(index<3)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
// ...

yield*的示例

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i){
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

yield

yield 关键字用来暂停和继续一个生成器函数 (function* or legacy generator).

yield [[expression]];

expression
用作返回值. 如果忽略, 将返回 undefined .

最近在阅读阮一峰老师的 ECMAScript 6 入门 下面进行简单的总结,大部分内容是摘抄自上面链接,具体内容还需要深入阅读理解

let和const

let

ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

for(let i = 0; i < arr.length; i++){}
console.log(i)
//ReferenceError: i is not defined

下面的代码如果使用var,最后输出的是10。

var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = function () {
       console.log(i);
    };
}
a[6](); // 10

上面代码中,变量i是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。

利用闭包解决该问题

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function(num){ 
    return function () {
     console.log(num);
    }
  }(i);
}
a[6](); // 6

使用let解决该问题

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

Tips

  • 不存在变量提升: let不像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
  • 暂时性死区: 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
  • let不允许在相同作用域内,重复声明同一个变量。
  • let实际上为JavaScript新增了块级作用域。

const

const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

解构

解构的含义简单点就是自动解析数组或者对象中值,解析出来之后一次赋值给一系列的变量。

利用这个特性,我们可以让一个函数返回一个数组,然后利用解构赋值得到数组中的每一个元素.

数组的解构赋值

var [a, b, c] = [1, 2, 3];

对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

字符串的解构

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

数值和布尔值的解构赋值

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

函数参数的解构赋值

函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

箭头操作符

其实箭头操作符其实就是使用=>语法来代替函数。

var arr = [1, 2, 3];

// 传统写法
arr.forEach(function (v) {
    console.log(v);
});

// 使用箭头操作符
arr.forEach( v => console.log(v));

Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

Proxy和Reflect

Proxy

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2

Reflect

Reflect对象的设计目的有这样几个。

  1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。

  2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。

    // 老写法
    try {
      Object.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    }
    
  3. 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

    var loggedObj = new Proxy(obj, {
      get(target, name) {
        console.log('get', target, name);
        return Reflect.get(target, name);
      },
      deleteProperty(target, name) {
        console.log('delete' + name);
        return Reflect.deleteProperty(target, name);
      },
      has(target, name) {
        console.log('has' + name);
        return Reflect.has(target, name);
      }
    });
    

    上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。

    有了Reflect对象以后,很多操作会更易读。

    // 老写法
    Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
    
    // 新写法
    Reflect.apply(Math.floor, undefined, [1.75]) // 1
    

二进制数组

这个接口的原始设计目的,与WebGL项目有关。所谓WebGL,就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像C语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。

Set和Map数据结构

Set

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成Set数据结构。

var s = new Set();

[2,3,5,4,5,2,2].map(x => s.add(x))

for (let i of s) {console.log(i)}
// 2 3 5 4

上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。

Set函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。

var set = new Set([1, 2, 3, 4, 4])
[...set]
// [1, 2, 3, 4]

var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

function divs () {
  return [...document.querySelectorAll('div')]
}

var set = new Set(divs())
set.size // 56

// 类似于
divs().forEach(div => set.add(div))
set.size // 56

WeakSet

WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。

首先,WeakSet的成员只能是对象,而不能是其他类型的值。

其次,WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。

Map

JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是只能用字符串当作键。这给它的使用带来了很大的限制。

var data = {};
var element = document.getElementById("myDiv");

data[element] = metadata;
data["[Object HTMLDivElement]"] // metadata

上面代码原意是将一个DOM节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串[Object HTMLDivElement]。

为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。

var m = new Map();
var o = {p: "Hello World"};

m.set(o, "content")
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

WeakMap

WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。

Iterator和for…of循环

JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。

Iterator的遍历过程是这样的。

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

for…of

ES6借鉴C++、Java、C#和Python语言,引入了for…of循环,作为遍历所有数据结构的统一的方法。一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。

for…of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。

JavaScript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值。

var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // a b c d
}

Generator 函数

Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上,Generator函数是一个普通函数,但是有两个特征。一是,function命令与函数名之间有一个星号;二是,函数体内部使用yield语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。

yield语句

由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

  1. 遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。

  3. 如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

  4. 如果该函数没有return语句,则返回的对象的value属性值为undefined。

需要注意的是,yield语句后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能

function* gen() {
  yield  123 + 456;
}

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。

Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。

function* f() {
  console.log('执行了!')
}

var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);

for…of循环

for…of循环可以自动遍历Generator函数,且此时不再需要调用next方法。

function *foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

Promise对象

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。

下面是一个用Promise对象实现的Ajax操作的例子。

var getJSON = function(url) {
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

    function handler() {
      if ( this.readyState !== 4 ) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

异步操作和Async函数

ES6诞生以前,异步编程的方法,大概有下面四种。

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise 对象

ES6将JavaScript异步编程带入了一个全新的阶段,ES7的Async函数更是提出了异步编程的终极解决方案。


回调函数

JavaScript语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。它的英语名字callback,直译过来就是”重新调用”。

读取文件进行处理,是这样写的。

fs.readFile('/etc/passwd', function (err, data) {
  if (err) throw err;
  console.log(data);
});

上面代码中,readFile函数的第二个参数,就是回调函数,也就是任务的第二段。等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行。

一个有趣的问题是,为什么Node.js约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)?原因是执行分成两段,在这两段之间抛出的错误,程序无法捕捉,只能当作参数,传入第二段。

Promise

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。

fs.readFile(fileA, function (err, data) {
  fs.readFile(fileB, function (err, data) {
    // ...
  });
});

不难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就称为“回调函数噩梦”(callback hell)。

Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。采用Promise,连续读取多个文件,写法如下。

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function(data){
  console.log(data.toString());
})
.then(function(){
  return readFile(fileB);
})
.then(function(data){
  console.log(data.toString());
})
.catch(function(err) {
  console.log(err);
});

上面代码中,我使用了fs-readfile-promise模块,它的作用就是返回一个Promise版本的readFile函数。Promise提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。

可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。

Generator函数

协程
传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做”协程”(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程。它的运行流程大致如下。

  1. 第一步,协程A开始执行。
  2. 第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
  3. 第三步,(一段时间后)协程B交还执行权。
  4. 第四步,协程A恢复执行。

上面流程的协程A,就是异步任务,因为它分成两段(或多段)执行。

举例来说,读取文件的协程写法如下。

function *asnycJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

上面代码的函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。

协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

异步任务的封装

下面看看如何使用 Generator 函数,执行一个真实的异步任务。

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

上面代码中,Generator函数封装了一个异步操作,该操作先读取一个远程接口,然后从JSON格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。

执行这段代码的方法如下。

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

上面代码中,首先执行Generator函数,获取遍历器对象,然后使用next 方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next 方法。

可以看到,虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

async函数

ES7提供了async函数,使得异步操作变得更加方便。async函数是什么?一句话,async函数就是Generator函数的语法糖。

有一个Generator函数,依次读取两个文件。
var fs = require(‘fs’);

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

写成async函数,就是下面这样。

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比较就会发现,async函数就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进,体现在以下四点。

  1. 内置执行器。Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

    var result = asyncReadFile();
    

    上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像Generator函数,需要调用next方法,或者用co模块,才能得到真正执行,得到最后结果。

  2. 更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

  3. 更广的适用性。 co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

  4. 返回值是Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。

正常情况下,await命令后面是一个Promise对象,否则会被转成Promise。

Class

JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。

function Point(x,y){
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
}

上面这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样。

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
typeof Point // "function"
Point === Point.prototype.constructor // true

Module

在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。

// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
// ES6模块
import { stat, exists, readFile } from 'fs';

tip

另外还有 修饰器(Decorator)这一点,会另开一篇文章讲。

转载自 http://gejiawen.github.io/2015/07/28/es6-new-feature/#ES6新特性列表

新增特性 关键词 用法 描述
箭头操作符 Arrows v => console.log(v) 类似于部分强类型语言中的lambda表达式
类的支持 Classes - 原生支持类,让javascript的OOP编码更加地道
增强的对象字面量 enhanced object literals - 增强对象字面量
字符串模板 template strings ${num} 原生支持字符串模板,不再需要第三方库的支持
解构赋值 destructuring [x, y] = [‘hello’, ‘world’] 使用过python的话,你应该很熟悉这个语法
函数参数扩展 default, rest, spread - 函数参数可以使用默认值、不定参数以及拓展参数了
let、const let、const - javascript中可以使用块级作用域和声明常量了
for…of遍历 for…of for (v of someArray) { … } 又多了一种折腾数组、Map等数据结构的方法了
迭代器和生成器 iterators, generator, iterables - ES6较为难以理解的新东西,后面会有相关文章
Unicode unicode - 原生的unicode更加完美的支持
模块和模块加载 modules, modules loader - ES6中开始支持原生模块化啦
map, set, weakmap, weakset - - 新的数据结构
监控代理 proxies - 我们可以监听对象发生了哪些事,并可以自定义对应的操作
Symbols - - 我们可以使用symbol来创建一个不同寻常的key,七种数据类型(null,undefine,int,String,bolloen,object,Symbol)
Promises - - 这家伙经常在讨论异步处理流程时被提到
新的API math, number, string, array, object - 原生的功能性API就是方便些
内置对象可以被继承 subclassable built-ins - 可以基于内置对象,比如Array,来生成一个类
二进制、八进制字面量 - - 可以直接在es6中使用二进制或者八进制字面量了 (for WebGL)
Reflect API - - 反射API?
尾调用 tail calls - ES6中会自动帮你做一些尾递归方面的优化

jCanvas

A free and open source jQuery-based library for the HTML5 Canvas API.

cssGram

Instagram filter library in Sass and CSS.

利用css做滤镜 https://github.com/una/CSSgram

medium-editor

This is a clone of medium.com inline editor toolbar.

MediumEditor has been written using vanilla JavaScript, no additional frameworks required.

github地址

chrome新属性:MediaRecorder

The MediaRecorder API enables you to record audio and video from the Web. It’s available now in Firefox and in Chrome for Android and desktop.

voxel.css

makes 3D rendering easy
github地址

babel+webpack+gulp

So far we’ve stepped through a quick evolution of using babel and webpack’s CLIs to build our library, then moved on to use gulp (and related plugins) to handle the build for us. The code related to this post includes an example/ directory with both a browser- and node-based example of our working transpiled module.

SamsaraJS

SamsaraJS is a functional reactive library for animating layout. It provides a language for positioning, orienting and sizing DOM elements and animating these properties over time. Everything in SamsaraJS — from the user input to the rendering pipeline — is a stream. Building a user interface becomes the art of composing streams.github地址

在家快冻疯了,打字好冷,最近在看 手淘年货节舞龙揭幕动画实战 发现animation做动画很简单

下面直接转载一篇讲steps的文章,文章地址 https://idiotwu.me/understanding-css3-timing-function-steps/#toc-5

不堪回首的过往

在应用 CSS3 渐变/动画时,有个控制时间的属性 。它的取值中除了常用到的 三次贝塞尔曲线 以外,还有个让人比较困惑的 steps() 函数。在许多相关文章里,关于这个函数的解释都比较含糊其辞,比如:

steps() 第一个参数 number 为指定的间隔数,即把动画分为 n 步阶段性展示,第二个参数默认为 end,设置最后一步的状态,start 为结束时的状态,end 为开始时的状态。

又如:

steps 有两个参数

第一个肯定是分几步执行完
第二个有两个值
start 第一帧是第一步动画结束
end 第一帧是第一步动画开始

年少无知的我轻易就相信了大家的说法,每次应用 steps() 函数时都要先考虑一番:嗯,start 对应末态,end 对应初态,末态是 OOOO,初态是 XXXX……卧槽!跑起来不对!

一探究竟

被坑得团团转之后,只好向组织求助。于是查到了这样的规定:

steps: specifies a stepping function, described above, taking two parameters. The first parameter specifies the number of intervals in the function. It must be a positive integer (greater than 0). The second parameter, which is optional, is either the value ‘start’ or ‘end’, and specifies the point at which the change of values occur within the interval. If the second parameter is omitted, it is given the value ‘end’.

粗略翻译如下:steps 函数指定了一个阶跃函数,第一个参数指定了时间函数中的间隔数量(必须是正整数);第二个参数可选,接受 start 和 end 两个值,指定在每个间隔的起点或是终点发生阶跃变化,默认为 end。

这样理解起来可能还是有点抽象,我们来个实例:

#demo {
  animation-iteration-count: 2;
  animation-duration: 3s;
}

这是一个 3s * 2 的动画,我们分别对它应用 steps(3, start) 和 steps(3, end),做出阶跃函数曲线如下:

steps(3, start)

Alt

steps() 第一个参数将动画分割成三段。当指定跃点为 start 时,动画在每个计时周期的起点发生阶跃(即图中空心圆 → 实心圆)。由于第一次阶跃发生在第一个计时周期的起点处(0s),所以我们看到的第一步动画(初态)就为 1/3 的状态,因此在视觉上动画的过程为 1/3 → 2/3 → 1 。

如果翻译成 JavaScript,大致如下:

var animateAtStart = function (steps, duration) {  
    var current = 0;
    var interval = duration / steps;
    var timer = function () {
        current++;
        applyStylesAtStep(current);
        if (current < steps) {
            setTimeout(timer, interval);
        }
    };
    timer();
};

steps(3, end)

Alt

当指定跃点为 end,动画则在每个计时周期的终点发生阶跃(即图中空心圆 → 实心圆)。由于第一次阶跃发生在第一个计时周期结束时(1s),所以我们看到的初态为 0% 的状态;而在整个动画周期完成处(3s),虽然发生阶跃跳到了 100% 的状态,但同时动画结束,所以 100% 的状态不可视。因此在视觉上动画的过程为 0 → 1/3 → 2/3(回忆一下数电里的异步清零,当所有输出端都为高电平的时候触发清零,所以全为高电平是暂态)。

同样翻译成 JavaScript 如下:

var animateAtEnd = function (steps, duration) {  
    var current = 0;
    var interval = duration / steps;
    var timer = function () {
        applyStylesAtStep(current);
        current++;
        if (current < steps) {
            setTimeout(timer, interval);
        }
    };
    timer();
};

如果这样的解释还是让你觉得云里雾里,可以参考 交互 DEMO

实际应用

虽然写了这么多,但还是不得不说一句 timing-function: steps() 在实际设计中的应用少之又少,但是配合一些奇淫技巧还是能做出一些不错的效果:

定时遮罩

在CSS3 环形进度条这篇文章里,我们曾用到 timing-function 来使半圆环定时显示/隐藏:

$precent: 5; // 进度百分比
$duration: 2s; // 动画时长
@keyframes toggle {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
.progress-right {
    // 初态为 opacity: 0; 动画周期结束后为 opacity: 1;
    opacity: 1;
    animation: toggle ($duration * 50 / $precent) step-end; // step-end = steps(1, end)
}
.progress-cover {
    // 初态为 opacity: 1; 动画周期结束后为 opacity: 0;
    opacity: 0;
    animation: toggle ($duration * 50 / $precent) step-start; // step-start = steps(1, start)
}

这里的关键是使用了 step-start 与 step-end 控制动画,因为动画只有两个关键帧,参考上文可以得出:

step-start:动画一开始就跳到 100% 直到周期结束

step-end:保持 0% 的样式直到周期结束

要注意的是,timing-function 是作用于每两个关键帧之间,而不是整个动画(the ‘animation-timing-function’ applies between keyframes, not over the entire animation),所以就有了张鑫旭老师在小tip: CSS3 animation渐进实现点点点等待提示效果一文中得出的结论:

step-start, 顾名思意,“一步一步开始”,表现在动画中就是一帧一帧播放、一顿一顿画面

Sprite 精灵动画

在CSS3 实现奔跑动画这篇文章里,我们使用 CSS3 Animation 来实现游戏开发中的精灵动画:

$spriteWidth: 140px; // 精灵宽度 
@keyframes run {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: -($spriteWidth * 12) 0; // 12帧
  }
}
#sprite {
  width: $spriteWidth;
  height: 144px;
  background: url("../images/sprite.png") 0 0 no-repeat;
  animation: run 0.6s steps(12) infinite;
}

其原理是:使用一张含有多帧静态画面的图片,通过切换 background-position 使其变为连续的动画。

demo

http://jsbin.com/daluqov/1

用闭包

闭包

阮一峰老师讲解闭包的原理
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

example

//error
var a = new Array();   
var i;   
for(i=0;i<3;i++){   
//此处表示a[i] = object(function是一个对象)
  a[i] = function(num){
    return num;
  }; 
}   
console.log(a[2]);

//right
var a = new Array();   
var i;   
for(i=0;i<3;i++){ 
// 自执行函数
  a[i] = function(num){
    return num;
  }(i); 
}   
console.log(a[2]);

存在的问题

settimeout/setInterval无法直接传递参数,如下代码

var temp = 'success';
var foo = function(test) {
    alert(test);
};
setTimeout(foo(temp), 3000);

上面的代码执行时,会立即执行foo函数,并将返回值作为setTimeout函数的参数传递,显然是不正确的

解决办法

字符串

setTimeout("foo(temp)",3000);

闭包

定义了一个函数_foo,用于接收一个参数,并返回一个不带参数的函数,在这个函数内部使用了外部函数的参数,从而对其调用,不需要使用参数

var temp = 'success';
function _foo(temp){
  return function(){
    alert(temp);
  };
}
setTimeout(_foo(temp), 3000);

最近一直在优化悠客生鲜以及我自己的网站,发现之前字体一直出问题比较丑,决定深入一下字体方面的知识

tip

中文字体尽量不要使用@font-face,主要是中文字体的文件一般比较大,使用起来不是很划算,西文字体还是可以用的,毕竟只有26个字符

建议使用字体

mac

  • 冬青黑体 Hiragino Sans GB
  • 黑体-简 Heiti SC
  • 华文黑体 STHeiti

Windows

  • 中易宋体 SimSun
  • 微软雅黑 Microsoft YaHei

Linux

  • 文泉驿微米黑 WenQuanYi Microhei

iOS

  • 黑体-简 Heiti SC
  • 华文黑体 STHeiti

android

  • 思源黑体 Noto Sans CJK SC
  • Droid Sans Fallback

使用方法

font-family: Helvetica, Tahoma, Arial, STXihei, " 华文细黑 ", "Microsoft YaHei", " 微软雅黑 ", sans-serif;
  1. 对于英文字符,首先查找Helvetica(Mac),然后查找Tahoma(Win),都找不到就用Arial(Mac&Win);若是以上三者都缺失,则使用当前默认的sans-serif字体 (操作系统或浏览器指定);
  2. 对于中文字体,我们已经了解其规则了。华文细黑(Mac),微软雅黑(Win) 是这两个平台的默认中文字体。

参考文献

这两篇文章讲的非常好

转载的github地址 https://github.com/kahn1990/web_develop_standard

规范目的:


  • 使开发流程更加规范化。

通用规范:


  • TAB键用两个空格代替(WINDOWS下TAB键占四个空格,LINUX下TAB键占八个空格)。
  • CSS样式属性或者JAVASCRIPT代码后加“;”方便压缩工具“断句”。
  • 文件内容编码均统一为UTF-8
  • CSSJAVASCRIPT中的非注释类中文字符须转换成unicode编码使用,以避免编码错误时乱码显示。

文件规范:


  • 文件名用英文单词,多个单词用驼峰命名法。
  • 一些浏览器会将含有这些词的作为广告拦截,文件命名、ID、CLASS等所有命名避免以上词汇。
    ad、ads、adv、banner、sponsor、gg、guangg、guanggao等。

html书写规范:


  • 为每个HTML页面的第一行添加标准模式(standard mode)的声明,确保在每个浏览器中拥有一致的展现。

    <!DOCTYPE html>     
    

    文档类型声明统一为HTML5声明类型,编码统一为UTF-8。

    <meta charset="UTF-8">
    

    <HEAD>中添加信息。

    <meta name="author" content="smile@kang.cool">//作者
    <meta name="description" content="hello">//网页描述
    <meta name="keywords" content="a,b,c">//关键字,“,”分隔
    <meta http-equiv="expires" content="Wed, 26 Feb 1997 08:21:57 GMT">//设定网页的到期时间。一旦网页过期,必须到服务器上重新调阅
    <meta http-equiv="Pragma" content="no-cache">//禁止浏览器从本地机的缓存中调阅页面内容
    <meta http-equiv="Window-target" content="_top">//用来防止别人在框架里调用你的页面
    <meta http-equiv="Refresh" content="5;URL=http://kahn1990.com/">//跳转页面,5指时间停留5秒 网页搜索机器人向导。用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引
    <meta name="robots" content="none">//content的参数有all,none,index,noindex,follow,nofollow,默认是all
    <link rel="Shortcut Icon" href="favicon.ico">//收藏图标
    <meta http-equiv="Cache-Control" content="no-cache, must-revalidate">//网页不会被缓存
    

    IE支持通过特定<meta>标签来确定绘制当前页面所应该采用的IE版本。除非有强烈的特殊需求,否则最好是设置为edge mode ,从而通知IE采用其所支持的最新的模式。

    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    
  • 非特殊情况下CSS样式文件外链至HEAD之间,JAVASCRIPT文件外链至页面底部。

    <!DOCTYPE html>
    <html>
    <head>
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <!-- 逻辑代码 -->
        <!-- 逻辑代码底部 -->
        <script src="lib/jquery/jquery-2.1.1.min.js"></script>
    </body>
    </html>
    

    引入JAVASCRIPT库文件,文件名须包含库名称及版本号及是否为压缩版。

    jQuery-1.8.3.min.js
    

    引入JAVASCRIPT插件, 文件名格式为库名称+.+插件名称。

    jQuery.cookie.js
    
  • HTML属性应当按照以下给出的顺序依次排列,来确保代码的易读性。

    class
    id 、 name
    data-*
    src、for、 type、 href
    title、alt
    aria-*、 role
    

    编码均遵循XHTML标准,
    标签、属性、属性命名由小写英文、数字和_组成,且所有标签必须闭合,属性值必须用双引号"",
    避免使用中文拼音尽量简易并要求语义化。

    CLASS --> nHeadTitle --> CLASS遵循小驼峰命名法(little camel-case)
    ID --> n_head_title --> ID遵循名称+_
    NAME --> N_Head_Title --> NAME属性命名遵循首个字母大写+_
    <div class="nHeadTitle" id="n_head_title" name="N_Head_Title"></div>
    

    JAVASCRIPT获取单个元素时,通常使用document.getElementById来获取dom元素,document.getElementById兼容所有浏览器,但IE浏览器会混淆元素的ID和NAME属性,所以要区分ID和NAME命名。

    <input type="text" name="test">
    <div id="test"></div>
    <button onclick="alert(document.getElementById('test').tagName)"></button>
    <!-- ie6下为INPUT -->
    
  • 特殊符号应使用转意符。

    <    -->    &lt;
    

    –> >

    空格  -->    &nbsp;
    
  • 含有描述性表单元素(INPUTTEXTAREA)添加LABEL

    <p>
        <label for="test">测试</label>
        <input type="text" id="test" />
    </p>
    
  • 多用无兼容性问题的HTML内置标签,比如SPAN、EM、STRONG、OPTGROUP、LABEL等,需要自定义HTML标签属性时,首先考虑是否存在已有的合适标签可替换,如果没有,可使用须以“data-”为前缀来添加自定义属性,避免使用其他命名方式。
  • 语义化HTML
  • 尽可能减少<DIV>嵌套。
  • 书写链接地址时避免重定向。

    href="http://www.kahn1990.com/" //即在URL地址后面加“/”
    
  • HTML中对于属性的定义,确保全部使用双引号,绝不要使用单引号

css书写规范:


  • 为了欺骗W3C的验证工具,可将代码分为两个文件,一个是针对所有浏览器,一个只针对IE。即将所有符合W3C的代码写到一个文件中,而一些IE中必须而又不能通过W3C验证的代码(如:cursor:hand;)放到另一个文件中,再用下面的方法导入。

    <!-- 放置所有浏览器样式-->
    <link rel="stylesheet" type="text/css" href="">
    <!-- 只放置IE必须,而不能通过w3c的-->
    <!--[if IE]
        <link rel="stylesheet" href="">
    <![endif]-->
    
  • CSS样式新建或修改尽量遵循以下原则。

    根据新建样式的适用范围分为三级:全站级、产品级、页面级。
    尽量通过继承和层叠重用已有样式。
    不要轻易改动全站级CSS。改动后,要经过全面测试。
    
  • CSS属性显示顺序。

    显示属性
    元素位置
    元素属性
    元素内容属性
    
  • CSS书写顺序。

    .header {
    /* 显示属性 */
        display || visibility
        list-style
        position top || right || bottom || left
        z-index
        clear
        float
    /* 自身属性 */
        width max-width || min-width
        height max-height || min-height
        overflow || clip
        margin
        padding
        outline
        border
        background
    /* 文本属性 */
        color
        font
        text-overflow
        text-align
        text-indent
        line-height
        white-space
        vertical-align
        cursor
        content
    };
    

    兼容多个浏览器时,将标准属性写在底部。

    -moz-border-radius: 15px; /* Firefox */
    -webkit-border-radius: 15px; /* Safari和Chrome */
    border-radius: 15px; /* Opera 10.5+, 以及使用了IE-CSS3的IE浏览器 *//标准属性
    
  • 使用选择器时,命名比较短的词汇或者缩写的不允许直接定义样式。

    .hd,.bd,.td{};//如这些命名
    

    可用上级节点进行限定。

    .recommend-mod .hd
    

    多选择器规则之间换行,即当样式针对多个选择器时每个选择器占一行。

    button.btn,
    input.btn,
    input[type="button"] {…};
    

    优化CSS选择器。

    #header a { color: #444; };/*CSS选择器是从右边到左边进行匹配*/
    

    浏览器将检查整个文档中的所有链接和每个链接的父元素,并遍历文档树去查找ID为header的祖先元素,如果找不到header将追溯到文档的根节点,解决方法如下。

    避免使用通配规则和相邻兄弟选择符、子选择符,、后代选择符、属性选择符等选择器
    不要限定id选择符,如div#header(提权的除外)
    不要限定类选择器,如ul.recommend(提权的除外)
    不要使用 ul li a 这样长的选择符
    避免使用标签子选择符,如#header > li > a
    
  • 使用z-index属性尽量z-index的值不要超过150(通用组的除外),页面中的元素内容的z-index不能超过10(提示框等模块除外但维持在150以下),不允许直接使用(999~9999)之间大值。
  • 尽量避免使用CSS Hack。

    property:value; /* 所有浏览器 */
    +property:value; /* IE7 */
    _property:value; /* IE6 */
    *property:value; /* IE6/7 */
    property:value\9; /* IE6/7/8/9,即所有IE浏览器 */
    
    * html selector { … }; /* IE6 */
    *:first-child+html selector { … }; /* IE7 */
    html>body selector { … }; /* 非IE6 */
    @-moz-document url-prefix() { … }; /* firefox */
    @media all and (-webkit-min-device-pixel-ratio:0) { … }; /* saf3+/chrome1+ */
    @media all and (-webkit-min-device-pixel-ratio:10000),not all and (-webkit-min-device-pixel-ratio:0) { … }; /* opera */
    @media screen and (max-device-width: 480px) { … }; /* iPhone/mobile webkit */
    

    避免使用低效的选择器。

    body > * {…};
    ul > li > a {…};
    #footer > h3 {…};
    ul#top_blue_nav {…};
    searbar span.submit a { … }; /* 反面示例 */
    
  • 六个不要三个避免一个使用。

    不要在标签上直接写样式
    不要在CSS中使用expression
    不要在CSS中使用@import
    不要在CSS中使用!important
    不要在CSS中使用“*”选择符
    不要将CSS样式写为单行
    避免使用filter
    避免使用行内(inline)样式
    避免使用“*”设置{margin: 0; padding: 0;}
    使用after或overflow的方式清浮动
    
  • 减少使用影响性能的属性。

    position:absolute;
    float:left;//如这些定位或浮动属性
    
    减少在`CSS`中使用滤镜表达式和图片repeat,
    尤其在body当中,渲染性能极差, 如果需要用repeat的话,
    图片的宽或高不能少于8px。
    

javaScript书写规范:


  • 命名规范。

    常量名
        全部大写并单词间用下划线分隔
        如:CSS_BTN_CLOSE、TXT_LOADING
    
    对象的属性或方法名
        小驼峰式(little camel-case)
        如:init、bindEvent、updatePosition
        示例:Dialog.prototype = {
                init: function () {},
                bindEvent: function () {},
                updatePosition: function () {}
                 …
            };
    类名(构造器)
        -->小驼峰式但首字母大写
        -->如:Current、DefaultConfig
    函数名
        -->小驼峰式
        -->如:current()、defaultConfig()
    变量名
        -->小驼峰式
        -->如:current、defaultConfig
    私有变量名
        -->小驼峰式但需要用_开头
        -->如:_current、_defaultConfig
    变量名的前缀
        -->续
    
  • 代码格式。

    "()"前后需要跟空格
    "="前后需要跟空格
    ","后面需要跟空格
    JSON对象需格式化对象参数
    if、while、for、do语句的执行体用"{}"括起来
    

    “{}”格式如下。

    if (a==1) {
        //代码
    };
    

    避免额外的逗号。

    var arr = [1,2,3,];
    

    for-in循环体中必须用hasOwnProperty方法检查成员是否为自身成员,避免来自原型链上的污染。

  • 长语句可考虑断行。

    TEMPL_SONGLIST.replace('{TABLE}', da['results'])
        .replace('{PREV_NUM}', prev)
        .replace('{NEXT_NUM}', next)
        .replace('{CURRENT_NUM}', current)
        .replace('{TOTAL_NUM}', da.page_total);
    

    为了避免和JSLint的检验机制冲突,“.”或“+”这类操作符放在行尾。

    TEMPL_SONGLIST.replace('{TABLE}', da['results']).
        replace('{PREV_NUM}', prev).
        replace('{NEXT_NUM}', next).
        replace('{CURRENT_NUM}', current).
        replace('{TOTAL_NUM}', da.page_total);
    

    如果模块代码中,使用其它全局变量想跳过JSLint的检查,可以在该文件中加入/*global*/声明。

    /*global alert: true, console: true, top: true, setTimeout: true */
    
  • 使用严格的条件判断符。用===代替==,用!==代替!=,避免掉入==造成的陷阱
    在条件判断时,这样的一些值表示false。

    null
    undefined与null相等
    字符串''
    数字0
    NaN
    

    在==时,则会有一些让人难以理解的陷阱。

    (function () {
        var undefined;
        undefined == null; // true
        1 == true; //true
        2 == true; // false
        0 == false; // true
        0 == ''; // true
        NaN == NaN;// false
        [] == false; // true
        [] == ![]; // true
    })();
    

    对于不同类型的 == 判断,有这样一些规则,顺序自上而下:

    undefined与null相等
    一个是number一个是string时,会尝试将string转换为number
    尝试将boolean转换为number
    0或1
    尝试将Object转换成number或string
    

    而这些取决于另外一个对比量,即值的类型,所以对于0、空字符串的判断,建议使用===
    ===会先判断两边的值类型,类型不匹配时为false

  • 下面类型的对象不建议用new构造。

    new Number
    new String
    new Boolean
    new Object //用{}代替
    new Array //用[]代替
    

    引用对象成员用obj.prop代替obj["prop"],除非属性名是变量。

  • 从number到string的转换。

    /** 推荐写法*/
    var a = 1;
    typeof(a); //"number"
    console.log(a); //1
    var aa=a+'';
    typeof(aa); //"string"
    console.log(aa); //'1'
    /** 不推荐写法*/
    new String(a)或a.toString()
    

    从string到number的转换,使用parseInt,必须显式指定第二个参数的进制。

    /** 推荐写法*/
    var a = '1';
    var aa = parseInt(a,10);
    typeof(a); //"string"
    console.log(a); //'1'
    typeof(aa); //"number"
    console.log(aa); //1
    

    从float到integer的转换。

    /** 推荐写法*/
    Math.floor/Math.round/Math.ceil
    /** 不推荐写法*/
    parseInt
    

    字符串拼接应使用数组保存字符串片段,使用时调用join方法。避免使用+或+=的方式拼接较长的字符串,每个字符串都会使用一个小的内存片段,过多的内存片段会影响性能。

    /**推荐的拼接方式array的push、join*/
    var str=[],
        list=['测试A','测试B'];
    for (var i=0 , len=list.length; i < len; i++) {
        str.push( '<div>'+ list[i] + '</div>');
    };
    console.log(str.join('')); //<div>测试A</div><div>测试B</div>
    /** 不推荐的拼接方式+=*/
    var str = '',
        list=['测试A','测试B'];
    for (var i = 0, len = list.length; i< len; i++) {
        str+='<div>' + list[i] + '</div>';
    };
    console.log(str); //<div>测试A</div><div>测试B</div>
    
  • 尽量避免使用存在兼容性及消耗资源的方法或属性。

    不要使用with,void,evil,eval_r,innerText
    
  • 注重HTML分离, 减小reflow, 注重性能。

图片规范:


  • 命名应用小写英文数字_组合,便于团队其他成员理解。

    header_btn.gif
    header_btn2.gif
    
  • 页面元素类图片均放入img文件夹,测试用图片放于img/testimg文件夹,psd源图放入img/psdimg文件夹。
  • 图片格式仅限于gifpngjpg等。
  • png图片做图片时,
    要求图片格式为png-8格式,若png-8实在影响图片质量或其中有半透明效果,请为ie-6单独定义背景,并尽量避免使用半透明的png图片。
  • 背景图片请尽可能使用sprite技术, 减小http请求。

注释规范:


  • JAVASCRIPTCSS文件注释需要标明作者、文件版本、创建/修改时间、重大版本修改记录、函数描述、文件版本、创建或者修改时间、功能、作者等信息。

    /* * 注释块 */
    

    中间可添加如下信息。

     @file 文件名
    @addon 把一个函数标记为另一个函数的扩张,另一个函数的定义不在源文件中
    @argument 用大括号中的自变量类型描述一个自变量
    @author 函数/类作者的姓名
    @base 如果类是继承得来,定义提供的类名称
    @class 用来给一个类提供描述,不能用于构造器的文档中
    @constructor 描述一个类的构造器
    @deprecated 表示函数/类已被忽略
    @exception 描述函数/类产生的一个错误
    @exec @extends 表示派生出当前类的另一个类
    @fileoverview 表示文档块将用于描述当前文件,这个标签应该放在其它任何标签之前
    @final 指出函数/类
    @ignore 让jsdoc忽视随后的代码
    @link 类似于@link标签,用于连接许多其它页面
    @member 定义随后的函数为提供的类名称的一个成员
    @param 用大括号中的参数类型描述一个参数
    @private 表示函数/类为私有,不应包含在生成的文档中
    @requires 表示需要另一个函数/类
    @return 描述一个函数的返回值
    @see 连接到另一个函数/类
    @throws 描述函数/类可能产生的错误
    @type 指定函数/成员的返回类型
    @version 函数/类的版本号
    

开发及测试工具约定:


  • 编码格式化,三码统一。
  • 测试工具: 前期开发仅测试FireFox & IE6 & IE7 & IE8 & IE9 & Opera &
    Chrome & Safari

参考和借鉴了大家的经验,收集整理了这一篇开发规范,感谢所有的原作者,众人拾柴火焰高,技术无国界,持续更新中。

环境

有两台服务器,一台测试服务器,一台生产服务器,一台编写代码的PC,在代码库只上传核心代码,但是由于有可能核心代码可能增多就出现问题,直接修改.gitignore之后,add+commit之后另一台服务器下载的过程中会覆盖原来的代码,强制执行会error,这个时候需要进行如下操作

具体操作

If you want remove all local changes from your working copy, simply stash them:

git stash save --keep-index
git pull

If you don't need them anymore, you now can drop that stash:

git stash drop

git stash可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

具体链接请看廖雪峰老师的博客 http://www.liaoxuefeng.com/

参考链接

想要看为什么要使用Flexible以及代码原理可以看上面’大漠’的文章,这篇写的很清楚

使用方法

载入js文件

建议对于js做内联处理,在所有资源加载之前执行这个js。

执行这个js后,会在html(也就是document.documentElement)上增加一个data-dpr属性,以及font-size样式。

之后页面中的元素,都可以用rem单位来设置。html上的font-size就是rem的基准像素。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>flexible</title>
    <link rel="stylesheet" href="index.css">
    <script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
</head>
<body>
<div class="ex">
    <img src="1.png" alt="">
</div>

<script type="text/javascript">
// alert(lib.flexible.rem);

</script>   
</body>
</html>

把视觉稿中的px转换成rem

首先,目前视觉稿大小分为640,750以及,1125这三种。

当前方案会把这3类视觉稿分成100份来看待(为了以后兼容vh,vw单位)。每一份被称为一个单位a。同时,1rem单位认定为10a。拿750的视觉稿举例:

1a = 7.5px
1rem = 75px

因此,对于视觉稿上的元素的尺寸换算,只需要原始px值除以rem基准px值即可。例如240px 120px的元素,最后转换为3.2rem 1.6rem。

字体不使用rem的方法

字体的大小不推荐用rem作为单位。所以对于字体的设置,仍旧使用px作为单位,并配合用data-dpr属性来区分不同dpr下的的大小。

例如:

div {
    width: 1rem; 
    height: 0.4rem;
    font-size: 12px; // 默认写上dpr为1的fontSize
}

[data-dpr="2"] div {
    font-size: 24px;
}

[data-dpr="3"] div {
    font-size: 36px;
}

一些常用APIs

[Number] lib.flexible.dpr

当前页面的dpr值

[Number] lib.flexible.rem

当前页面的rem基准值

[Number|String] lib.flexible.rem2px([Number|String digital])

把rem转换为px

[Number|String] lib.flexible.px2rem([Number|String digital])

把px转换为rem

lib.flexible.refreshRem()

刷新当前页面的rem基准值

栅格系统

还有栅格系统,我暂时没用到,需要使用的可以去上面链接的github

lib-flexible原理

原理其实很简单

通过检查当前设备,动态的生成meta标签,并且修改html标签样式

iphone retina的动态生成的meta标签

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

Android的meta标签

<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

获取html font-size方法如下

function refreshRem(){
    //获取页面宽度
    var width = docEl.getBoundingClientRect().width;
    if (width / dpr > 540) {
        width = 540 * dpr;
    }
    var rem = width / 10;
    docEl.style.fontSize = rem + 'px';
    flexible.rem = win.rem = rem;
}

需要更改设计稿尺寸

lib.flexible.rem

分析 http://m.taobao.com

  • 无法上下滑动,点击等等
  • 使用的是640px的设计稿
  • 加载速度很快,感觉部分图片使用的是懒加载,再往下拉到另外一个模块的时候是异步加载
  • 没有使用图片精灵,感觉因该是图片经常需要修改

其他

今天突然想了想发现有其他用处,由于html的font-size是根据页面宽度制定的,所以整个页面的宽度是已知的,在手机端布局过程中宽度一般用百分比表示,而子元素的padding/margin又是根据父元素的宽度来制定的,所以为了使得上下左右的宽度一致可以使用rem+百分比的方法

整个后面会做一个测试页面进行实践

参考MDN以及 http://segmentfault.com/a/1190000002407912

meta

HTML Meta 元素 (<meta>) 用来表达任何其他 HTML 元相关元素 (<base>, <link>, <script>, <style> 或者 <title>) 等无法表达的信息。

通过设置不同的属性,元数据可以分为以下几种:

  • 如果设置了 name , 一个 document 级的元素数据 将附着在整个页面上(例如alert(viewport.content)可以看到该属性)。
  • 如果设置了 http-equiv , 网络服务器将会给出一个 pragma directive, 即,指示页面应如何承载的指令信息。
    如果设置了 charset,将对网页使用的字符集作出声明 HTML5
    如果设置了 itemprop,将定义一个用户自定义的元数据,也因此语义声明,这个定义将对用户代理完全开放

seo优化

页面关键词,每个网页应具有描述该网页内容的一组唯一的关键字。

使用人们可能会搜索,并准确描述网页上所提供信息的描述性和代表性关键字及短语。标记内容太短,则搜索引擎可能不会认为这些内容相关。另外标记不应超过 874 个字符。

<meta name="keywords" content="your tags" />

页面描述,每个网页都应有一个不超过 150 个字符且能准确反映网页内容的描述标签

<meta name="description" content="150 words" />

搜索引擎索引方式,robotterms是一组使用逗号(,)分割的值,通常有如下几种取值:none,noindex,nofollow,all,index和follow。确保正确使用nofollow和noindex属性值

<meta name="robots" content="index,follow" />
<!--
    all:文件将被检索,且页面上的链接可以被查询;
    none:文件将不被检索,且页面上的链接不可以被查询;
    index:文件将被检索;
    follow:页面上的链接可以被查询;
    noindex:文件将不被检索;
    nofollow:页面上的链接不可以被查询。
 -->

页面重定向和刷新:content内的数字代表时间(秒),既多少时间后刷新。如果加url,则会重定向到指定网页(搜索引擎能够自动检测,也很容易被引擎视作误导而受到惩罚)

<meta http-equiv="refresh" content="0;url=" />

其他

<meta name="author" content="author name" /> <!-- 定义网页作者 -->
<meta name="google" content="index,follow" />
<meta name="googlebot" content="index,follow" />
<meta name="verify" content="index,follow" />

移动设备

viewport:能优化移动浏览器的显示。如果不是响应式网站,不要使用initial-scale或者禁用缩放。大部分4.7-5寸设备的viewport宽设为360px;5.5寸设备设为400px;iphone6设为375px;ipone6 plus设为414px。

<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"/>
<!-- `width=device-width` 会导致 iPhone 5 添加到主屏后以 WebApp 全屏模式打开页面时出现黑边  -->
  1. width:宽度(数值 / device-width)(范围从200 到10,000,默认为980 像素)
  2. height:高度(数值 / device-height)(范围从223 到10,000)
  3. initial-scale:初始的缩放比例 (范围从>0 到10)
  4. minimum-scale:允许用户缩放到的最小比例
  5. maximum-scale:允许用户缩放到的最大比例
  6. user-scalable:用户是否可以手动缩 (no,yes)
  7. minimal-ui:可以在页面加载时最小化上下状态栏。(已弃用)

注意,很多人使用initial-scale=1到非响应式网站上,这会让网站以100%宽度渲染,用户需要手动移动页面或者缩放。如果和initial-scale=1同时使用user-scalable=no或maximum-scale=1,则用户将不能放大/缩小网页来看到全部的内容。

WebApp全屏模式:伪装app,离线应用。

<meta name="apple-mobile-web-app-capable" content="yes" /> <!-- 启用 WebApp 全屏模式 -->

隐藏状态栏/设置状态栏颜色:只有在开启WebApp全屏模式时才生效。content的值为default | black | black-translucent 。

<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

添加到主屏后的标题

<meta name="apple-mobile-web-app-title" content="标题">

Alt

忽略数字自动识别为电话号码

<meta content="telephone=no" name="format-detection" /> 

忽略识别邮箱

<meta content="email=no" name="format-detection" />

添加智能 App 广告条 Smart App Banner:告诉浏览器这个网站对应的app,并在页面上显示下载banner(如下图)。参考文档

<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">

Alt

其他

<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">

网页相关

申明编码

<meta charset='utf-8' />

优先使用 IE 最新版本和 Chrome

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 关于X-UA-Compatible -->
<meta http-equiv="X-UA-Compatible" content="IE=6" ><!-- 使用IE6 -->
<meta http-equiv="X-UA-Compatible" content="IE=7" ><!-- 使用IE7 -->
<meta http-equiv="X-UA-Compatible" content="IE=8" ><!-- 使用IE8 -->

浏览器内核控制:国内浏览器很多都是双内核(webkit和Trident),webkit内核高速浏览,IE内核兼容网页和旧版网站。而添加meta标签的网站可以控制浏览器选择何种内核渲染。参考文档

<meta name="renderer" content="webkit|ie-comp|ie-stand">

国内双核浏览器默认内核模式如下:

  1. 搜狗高速浏览器、QQ浏览器:IE内核(兼容模式)
  2. 360极速浏览器、遨游浏览器:Webkit内核(极速模式)

禁止浏览器从本地计算机的缓存中访问页面内容:这样设定,访问者将无法脱机浏览。

<meta http-equiv="Pragma" content="no-cache">
Windows 8
<meta name="msapplication-TileColor" content="#000"/> <!-- Windows 8 磁贴颜色 -->
<meta name="msapplication-TileImage" content="icon.png"/> <!-- Windows 8 磁贴图标 -->
站点适配:主要用于PC-手机页的对应关系。
<meta name="mobile-agent"content="format=[wml|xhtml|html5]; url=url">
<!--
[wml|xhtml|html5]根据手机页的协议语言,选择其中一种;
url="url" 后者代表当前PC页所对应的手机页URL,两者必须是一一对应关系。
 -->

转码申明:用百度打开网页可能会对其进行转码(比如贴广告),避免转码可添加如下meta

<meta http-equiv="Cache-Control" content="no-siteapp" />

参考阮一峰及其他博客

MVC

  • 视图(View):用户界面。
  • 控制器(Controller):业务逻辑
  • 模型(Model):数据保存

各部分之间的通信方式如下

Alt

  1. View 传送指令到 Controller
  2. Controller 完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到 View,用户得到反馈

原来我一直理解错了

MVP

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

Alt

  1. 各部分之间的通信,都是双向的。
  2. View 与 Model 不发生联系,都通过 Presenter 传递。
  3. View 非常薄,不部署任何业务逻辑,称为”被动视图”(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

Alt

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。

现在很多前端框架都是用的MVVM架构,如上图所示实际上应该是

view-view-model-controller-model

在后端获取到的数据发送给前端框架,前端框架将数据存储起来变为其内部的model,然后转换为虚拟view蹭,最后转换为正正的view层