三省吾身丶丶

三省吾身丶丶 查看完整档案

杭州编辑监督管理局  |  体育学院 编辑  |  填写所在公司/组织 blog.guowenfh.com 编辑
编辑

兴趣遍地都是,专注和持之以恒才是真正稀缺的。

个人动态

三省吾身丶丶 发布了文章 · 2018-10-17

学习 Promise,掌握未来世界 JS 异步编程基础

其实想写 Promise 的使用已经很长时间了。一个是在实际编码的过程中经常用到,一个是确实有时候小伙伴们在使用时也会遇到一些问题。
Promise 也确实是 ES6 中 对于写 JS 的方式,有着真正最大影响的 API 特性之一。
本文是实际使用使用过程中的一个总结
看一下文件创建时间 2017-10-09,拖延症真是太可怕了。。。还是得增强执行力啊!不忘初心,加油吧!
博客原址

前言 && 基础概念

Promise 是解决 JS 异步的一种方案,相比传统的回调函数,Promise 能解决多个回调严重嵌套的问题。

Promise 对象代表一个异步操作,有三种状态: pending、fulfilled 或 rejected ,状态的转变只能是 pending -> fulfilled 或者 pending -> rejected ,且这个过程一旦发生就不可逆转

<!-- more -->

个人认为讲解 Promise 实际上需要分成两个部分

  1. 对于 Promise 构造函数的使用说明。
  2. Promise 原型对象上的一些方法。

Promise 构造函数

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve 函数的作用是将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled ),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected ),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

下面代码创造了一个 Promise 实例。

function request() {
  return new Promise((resolve, reject) => {
    /* 异步操作成功 */
    setTimeout(() => {
      resolve("success");
    }, 1000);
    // 取消注释这里可以体现,Promise 的状态一旦变更就不会再变化的特性
    // reject('error');
  });
}

接收

request()
  .then(result => {
    console.info(result);
  })
  .catch(error => {
    console.info(error);
  });

上述 new Promise() 之后,除去用 catch 去捕获错误之外,也可以用 then 方法指定 resolvereject 的回调函数
也能达到捕获错误的目的。

request().then(
  result => {
    console.info(result);
  },
  error => {
    console.info(error);
  }
);

原型上的方法

Promise.prototype.then()

p.then(onFulfilled, onRejected);

then 方法 是定义在 Promise.prototype 上的方法,如上面的例子一样,有两个参数,fulfilled 的回调函数和 rejected 的回调函数,第二个参数时可选的。

两个关键点:

  1. then 方法的返回值是一个新的 Promise 实例,所以对于调用者而言,拿到一个 Promise 对象,调用 then 后仍然返回一个 Promise ,而它的行为与 then 中的回调函数的返回值有关。如下:
  • 如果 then 中的回调函数返回一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  • 如果 then 中的回调函数抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 如果 then 中的回调函数返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的 Promise 的接受状态回调函数的参数值。
  • 如果 then 中的回调函数返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的 Promise 的拒绝状态回调函数的参数值。
  • 如果 then 中的回调函数返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
  1. 链式调用。把嵌套回调的代码格式转换成一种链式调用的纵向模式。

比如说回调形式: 一个回调地狱的例子

a(a1 => {
  b(a1, b1 => {
    c(b1, c1 => {
      d(c1, d1 => {
        console.log(d1);
      });
    });
  });
});

这样的横向扩展可以修改成(a,b,c,d)均为返回 Promise 的函数

a()
  .then(b)
  .then(c)
  .then(d)
  .then(d1 => {
    console.log(d1);
  });
//===== 可能上面的例子并不太好看 ===下面这样更直观
a()
  .then(a1 => b(a1))
  .then(b1 => c(b1))
  .then(c1 => d(c1))
  .then(d1 => {
    console.log(d1);
  });

这样的纵向结构,看上去清爽多了。

Promise.prototype.catch()

除了 then() ,在 Promise.prototype 原型链上的还有 catch() 方法,这个是拒绝的情况的处理函数。

其实 它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。 (事实上, calling obj.catch(onRejected) 内部 calls obj.then(undefined, onRejected)).

// 1.
request().then(
  result => {
    console.info(result);
  },
  error => {
    console.info(error);
  }
);

// 2.
request()
  .then(result => {
    console.info(result);
  })
  .catch(error => {
    console.info(error);
  });

如上这个例子:两种方式在使用,与结果基本上是等价的,但是 仍然推荐第二种写法,下面我会给出原因:

  1. 在 Promise 链中 Promise.prototype.then(undefined, onRejected)onRejected 方法无法捕获当前 Promise 抛出的错误,而后续的 .catch 可以捕获之前的错误。
  2. 代码冗余
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("reject");
  }, 1000);
})
  .then(
    result => {
      console.log(result + "1");
      throw Error(result + "1"); // 抛出一个错误
    },
    error => {
      console.log(error + ":1"); // 不会走到这里
    }
  )
  .then(
    result => {
      console.log(result + "2");
      return Promise.resolve(result + "2");
    },
    error => {
      console.log(error + ":2");
    }
  );
// reject1, Error: reject1:2

如果使用 .catch 方法,代码会简化很多,这样实际上是延长了 Promise 链

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("reject");
  }, 1000);
})
  .then(result => {
    console.log(result + "1");
    throw Error(result + "1"); // 抛出一个错误
  })
  .then(result => {
    console.log(result + "2");
    return Promise.resolve(result + "2");
  })
  .catch(err => {
    console.log(err);
  });
// reject1, Error: reject1:2

Promise.prototype.finally()

暂未完全成为标准的一部分,处于:Stage 4

finally() 方法返回一个 Promise,在执行 then()catch() 后,都会执行finally指定的回调函数。(回调函数中无参数,仅仅代表 Promise 的已经结束

等同于使用 .then + .catch 延长了原有的 Promise 链的效果,避免同样的语句需要在 then()catch() 中各写一次的情况。

mdn-Promise-finally

Promise 对象上的方法

Promise.all() 用来处理 Promise 的并发

Promise.all 会将多个 Promise 实例封装成一个新的 Promise 实例,新的 promise 的状态取决于多个 Promise 实例的状态,只有在全体 Promise 都为 fulfilled 的情况下,新的实例才会变成 fulfilled 状态。;如果参数中 Promise 有一个失败(rejected),此实例回调失败(rejecte),失败原因的是第一个失败 Promise 的结果。

举个例子:

Promise.all([
  new Promise(resolve => {
    setTimeout(resolve, 1000, "p1");
  }),
  new Promise(resolve => {
    setTimeout(resolve, 2000, "p2");
  }),
  new Promise(resolve => {
    setTimeout(resolve, 3000, "p3");
  })
])
  .then(result => {
    console.info("then", result);
  })
  .catch(error => {
    console.info("catch", error);
  });
// [p1,p2,p3]

Promise.all([
  new Promise(resolve => {
    setTimeout(resolve, 1000, "p1");
  }),
  new Promise(resolve => {
    setTimeout(resolve, 2000, "p2");
  }),
  Promise.reject("p3 error")
])
  .then(result => {
    console.info("then", result);
  })
  .catch(error => {
    console.info("catch", error);
  });
// p3 error

获取 cnode 社区的 精华贴的前十条内容

fetch("https://cnodejs.org/api/v1/topics?tab=good&limit=10")
  .then(res => res.json())
  .then(res => {
    const fetchList = res.data.map(item => {
      return fetch(`https://cnodejs.org/api/v1/topic/${item.id}`)
        .then(res => res.json())
        .then(res => res.data);
    });
    Promise.all(fetchList).then(list => {
      console.log(list);
    });
  });

Promise.race() 竞态执行

Promise.race 也会将多个 Promise 实例封装成一个新的Promise实例,只不过新的 Promise 的状态取决于最先改变状态的 Promise 实例的状态。

在前端最典型的一个用法是为 fetch api 模拟请求超时。

Promise.race([
  fetch("https://cnodejs.org/api/v1/topics?tab=good&limit=10").then(res =>
    res.json()
  ),
  new Promise((resolve, reject) => {
    setTimeout(reject, 1, "error");
  })
])
  .then(result => {
    console.info("then", result);
  })
  .catch(error => {
    console.info("catch", error); // 进入这里
  });

上述例子中只要请求 未在 1 毫秒内结束就会进入 .catch() 方法中,虽然不能将请求取消,但是超时模拟却成功了

Promise.resolve(value) && Promise.reject(reason)

这两个方法都能用来创建并返回一个新的 Promise , 区别是 Promise.resolve(value) 携带进新的 Promise 状态是 fulfilled。而 Promise.reject(reason) 带来的 rejected

有的时候可以用来简化一些创建 Promise 的操作如:

const sleep = (time = 0) => new Promise(resolve => setTimeout(resolve, time));
// 这里创建一个 睡眠,并且打印的链
Promise.resolve()
  .then(() => {
    console.log(1);
  })
  .then(() => sleep(1000))
  .then(() => {
    console.log(2);
  })
  .then(() => sleep(2000))
  .then(() => {
    console.log(3);
  });

有时也用来 手动改变 Promise 链中的返回状态 ,当然这样实际上和 直接返回一个值,或者是 使用 throw Error 来构造一个错误,并无区别。到底要怎么用 就看个人喜好了

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("resolve"); // 1.
  }, 1000);
})
  .then(result => {
    return Promise.reject("reject1"); // 2.
  })
  .then(
    result => {
      return Promise.resolve(result + "2");
    },
    err => {
      return Promise.resolve(err); // 3.
    }
  )
  .then(res => {
    console.log(res); // 4.
  })
  .catch(err => {
    console.log(err + "err");
  });
// reject1

几个例子

下面来看几个例子:

关于执行顺序,具体可搜索,js 循环

new Promise((resolve, reject) => {
  console.log("step 1");
  resolve();
  console.log("step 2");
}).then(() => {
  console.log("step 3");
});
console.log("step 4");

// step 1, step 2, step 4 , step 3

在使用 Promise 构造函数构造 一个 Promise 时,回调函数中的内容就会立即执行,而 Promise.then 中的函数是异步执行的。

关于状态不可变更

let start;
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    start = Date.now();
    console.log("once");
    resolve("success");
  }, 1000);
});
p.then(res => {
  console.log(res, Date.now() - start);
});
p.then(res => {
  console.log(res, Date.now() - start);
});
p.then(res => {
  console.log(res, Date.now() - start);
});

Promise 构造函数只执行一次,内部状态一旦改变,有了一个值,后续不论调用多少次then()都只拿到那么一个结果。

关于好像状态可以变更

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  }, 1000);
});

const p2 = p1.then((resolve, reject) => {
  throw new Error("error");
});

console.log("p1", p1);
console.log("p2", p2);

setTimeout(() => {
  console.log("p1", p1);
  console.log("p2", p2);
}, 2000);

观察这一次的打印
第一次打印出两个 Promise 的时候都是 pending ,因为 p2 是基于 p1 的结果,p1 正在 pending ,立即打印出的时候肯定是 pending ;第二次打印的时候,因为 p1 的状态为 resolved ,p2 为 rejected ,这个并不是已经为 fulfilled 状态改变为 rejected ,而是 p2 是一个新的 Promise 实例,then() 返回新的 Promise 实例。

关于透传

Promise.resolve(11)
  .then(1)
  .then(2)
  .then(3)
  .then(res => {
    console.info("res", res);
  });
//   11

给 then 方法传递了一个非函数的值,等同于 then(null),会导致穿透的效果,就是直接过掉了这个 then() ,直到符合规范的 then() 为止。

Promise 的串行调用

使用 Array.reduce 方法串行执行 Promise

const sleep = (time = 0) => new Promise(resolve => setTimeout(resolve, time));
[1000, 2000, 3000, 4000].reduce((Promise, item, index) => {
  return Promise.then(res => {
    console.log(index + 1);
    return sleep(item);
  });
}, Promise.resolve());
// 在分别的等待时间后输出 1,2,3,4

这篇文章到这里就基本上结束了,相信 如果能理解上面的内容,并且在实际项目中使用的话。应该会让工作更高效吧,对于新的异步使用应该也会更加的得心应手。Promise 的使用相对简单,可能后续再出一篇如何实现一个 Promise 吧

那些收集的 Promise 的优质文章。

查看原文

赞 46 收藏 38 评论 0

三省吾身丶丶 报名了讲座 · 2017-11-11

三省吾身丶丶 发布了文章 · 2017-06-06

ES6 简单特性学习记录

变量定义的新方式:let/ const

let 特性:

  1. 不允许重复声明

  2. 没有变量提升(预解析)

  3. 块级作用域(一对 {} 包括的区域称为一个代码块,let 声明的变量只在该代码块起作用)

例子1 :简单的打印数据

使用 var:

for(var i = 0; i<10 ; i++ ){
    setTimeout(()=>console.log(i)) // 执行10次,全都打印 10
}

使用 let:

for(let i = 0; i<10 ; i++ ){
    setTimeout(()=>console.log(i)) // 执行10次,打印 0 - 9
}

之前我们要实现这样的打印,必须使用闭包:

for(var i = 0; i<10;i++){
    (function(j){
        setTimeout(()=>console.log(j)) // 执行10次,打印 0 - 9
    })(i)
}

例子二:在网页中常常会有切换 tab ,展示对应的信息的需求,我们使用 var 来处理时,常常使用的自定义属性,来保存点击的索引。btns[i].index=i。用于找到对应的元素。:

html模板:

<style type="text/css">
    div{display:none}
    .show{display:block;}
    .active{background:red;}
</style>
<button class="active">1</button>
<button>2</button>
<button>3</button>
<div class="show">11111</div>
<div >22223</div>
<div >33333</div>

js:

var btns = document.querySelectorAll('button')
var divs = document.querySelectorAll('div')
for (var i=0 ;i<btns.length;i++){
    btns[i].index=i
    btns[i].onclick=function(){
        for(var j=0 ;j<btns.length;j++){
            btns[j].className=''
            divs[j].className=''
        }
        this.className='active'
        divs[this.index].className='show'
    }
}

使用 let

var btns = document.querySelectorAll('button')
var divs = document.querySelectorAll('div')
for (let i=0 ;i<btns.length;i++){
    /*可以看到这里少了保存的索引的操作*/
    btns[i].onclick=function(){
        for(let j=0 ;j<btns.length;j++){
            btns[j].className=''
            divs[j].className=''
        }
        this.className='active'
        divs[i].className='show'
    }
}

const 除了具备上述 let 的特性外,还有自己的一个特性:定义之后的值,是固定不变不能被修改的

值得注意的是下面这两种情况是不会报错的:

{
    const a = {value:1}
    a.value = 2
    console.log(a) // {value:2}

    const b = [1,2,3]
    b.push(4)
    console.log(b) // [1,2,3,4]
}

解构赋值

ES6 允许按照一定的模式,从数组和对象中提取值,这样就称为解构

数组:按照对应的顺序解构

{
    var arr = [[1,2,3],[4,5,6],[7,8,9]]
    var [a,b,c] = arr
    // a : [1,2,3]
    // b : [4,5,6]
    // c : [7,8,9]
    // 用法1
    var x = 1;
    var y = 2;
    [y,x] = [x,y]
    console.log(x,y) // 2 1
}

对象按照对应的名称一一对应进行解析:

{
    var obj={
        get:function(){
            return 'get'
        },
        value:1,
        data:[1,2,3],
        str:'string'
    }
    var {str,get,data} = obj
    console.log(str) // string
    console.log(get()) //get
    console.log(data) // [1,2,3]
}

模板字符串

模板字符串 是增强版的字符串,使用反引号(\`)作为标识 。可以当做普通字符串使用,也可以用来定义多行字符串(会保留换行)。或者在字符串中嵌入变量。

在模板字符串,需要引用变量使用 ${变量名} 的形式。在 {}可以进行运算,也可以引用对象属性。

{
    var name = 'xiaoming'
    var age = 19
    var str = `my name is ${name} ,my age is ${age}`
    console.log(str) //"my name is xiaoming ,my age is 19"
}

扩展

Array.from(arrayLike[, mapFn[, thisArg]])

  • arrayLike : 想要转换成真实数组的类数组对象或可遍历对象。

  • mapFn : 可选参数,如果指定了该参数,则最后生成的数组会经过该函数的加工处理后再返回。

  • thisArg : 可选参数,执行 mapFn 函数时 this 的值。方法用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象(包括 ES6 新增的数据结构 Set 和 Map )

{
    // NodeList对象
    let ps = document.querySelectorAll('p');
    Array.from(ps);
    // 将可迭代对象(Set 对象)转换成数组
    Array.from(new Set(["foo", window]));       // ["foo", window]
    // 使用 map 函数转换数组元素
    Array.from([1, 2, 3], x => x + x);      // [2, 4, 6]
    // 将类数组对象(arguments)转换成数组
    (function () {
        var args = Array.from(arguments);
        return args;
    })(1, 2, 3);                            // [1, 2, 3]
}

而在这之前,我们要转类数组对象,只能用这样的形式: [].slice.call(ps)

当然或许你根本不需要转,因为我们有 for of 了,只要有遍历接口的类型,它就可以进行遍历
Set,String,Array,NodeList等等)

{    
    // NodeList对象
    let ps = document.querySelectorAll('p');
    for (let v of ps){
        console.log(v)
    }
    //当然你可能同样需要下标: `arr.keys()`,`arr.values()`,`arr.entries()`
    for (let [i,item] of ps.entries()){
        console.log(i,item)
    }
}

Object.assign():拷贝源对象自身的可枚举的属性到目标对象身上

{
    var obj = { a: 1 };
    var copy = Object.assign({}, obj);
    console.log(copy); // { a: 1 }
}

值得注意的是, Object.assign()执行的是浅拷贝。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

{
    let a = { b: {c:4} , d: { e: {f:1}} }
    let g = Object.assign({},a)
    g.d.e = 32 // 设置 g.d.e 为 32
    console.log(g) // {"b":{"c":4},"d":{"e":32}}
    console.log(a) // {"b":{"c":4},"d":{"e":32}}
}

如果你需要的不是合并,而只是普通json对象的复制,建议使用 JSON.parse(JSON.stringify(a)),这样不会有上面的副作用产生。

函数参数默认值。定义默认值得参数必须是尾参数,因为函数形参定义默认值后该参数可以被忽略

{
    function fn(a,b=2){
        return {a,b}
    }
    console.info(fn(1)) //{a: 1, b: 2}
}

rest参数:用于获取获取函数的多余参数。与参数默认值一样,必须为尾参数

{
    function foo(a,b,...args){
        console.info(args)
    }
    foo(1,2,3,4,5,6) // [3, 4, 5, 6]
}

扩展运算符...:它好比 rest 参数的逆运算。可以将一个数组转为用逗号分隔的参数序列。

{
    // 更好的 apply 方法,例如我们在算最大值的时候:
    var arr = [1,2,3,4,5]
    console.info(Math.max.apply(null,arr))
    console.info(Math.max(...arr)) // 使用扩展运算符
    console.info(Math.max(1,2,3,4,5)) // 最终都会被解析成这样

    // 当然还能这样用
    var str = 'string'
    var arr = [...str,4,5] // ["s", "t", "r", "i", "n", "g", 4, 5]

}

箭头函数 Arrow Functions:箭头函数并不是用来替代现有函数而出现的,并且也无法替代。它是用来作为回调函数使用的,主要是为了简化回调函数的写法。
主要有三个特性:

  1. 箭头函数自身没有 this 。函数内的 this 指向箭头函数 定义时所在的对象 ,而不是使用时所在的对象。

  2. 箭头函数内部,不存在 arguments 对象

  3. 不可以当作构造函数,不可以使用 new 指令。

简单用法,简化回调:

{
    // 我们都知道数组的 sort 并不是根据数值大小来排序的,需要排序时,要通过回调函数的形式来确定排序方式 
    var arr = [7,8,9,10]
    arr.sort() // [10, 7, 8, 9]
    arr.sort(function(a,b){return a-b}) // [7, 8, 9, 10]
    arr.sort((a,b)=> a - b ) // 箭头函数简化。当仅有一条语句时,有一个隐式的 return 
}

没有 arguments

{
    var foo = (a,b,c)=>{
        console.log(a,b,c)
        console.log(arguments)
    };
    foo(1,2,3)
    // 1 2 3
    // Uncaught ReferenceError: arguments is not defined      
}

不要在对象的方法中使用箭头函数:

{
    window.name='window';
    var obj = {
        name:'obj',     
        getName: function(){
            console.log(this.name)
        }
    }
    obj.getName() // obj
    var getName = obj.getName
    getName() // window, this 总是指向调用者
    //-----------------
    var obj = {
        name:'obj',     
        getName: () =>{
            console.log(this.name)
        }
    }
    obj.getName() // window
    /**
        这里由于对象 a,并不能构成一个作用域。所以会再往上达到全局作用域,所以 this 指向 window..
     */
}
查看原文

赞 2 收藏 14 评论 0

三省吾身丶丶 发布了文章 · 2017-06-06

FlexBox 布局详解

很久没有写博客了,这里把之前学习 flex 布局的一篇笔记整理了一下。发布到博客上。赶一个五月的末班车吧。还是得坚持啊!!

flex 弹性布局
FlexBox 可控制子元素:

  • 水平或垂直排成一行

  • 控制子元素的对齐方式

  • 控制子元素的高度/宽度

  • 控制子元素的显示顺序

  • 控制子元素是否折行

display:flex; 创建 Flexbox 元素
在 flex 布局中必须理解的概念就是区分主轴和辅轴(侧轴):

<!--more-->

在项目中我们使用 display:flex; 创建 Flexbox 元素,那么该元素就成为了一个 flex container( 弹性的容器)。
其在文档流中的直接子元素将成为 flex item
flex item 子元素在容器内 排列的方向称为主轴,跟主轴垂直的方向称为 辅轴

方向相关属性

flex-direction

  • 设置子元素排列方向 (其实也就是主轴的排列方向)

  • 取值 row | row-reverse | column | column-reverse

  • 默认 row:

其中不同的设置,效果大致如下 :

flex-wrap

  • 元素在主轴方向排放时,能否换行

  • 取值:nowrap | wrap | wrap-reverse

  • 默认 nowrap,不换行

/*base css*/
.container{
    width: 400px;
    margin: 20px;
    line-height: 40px;
    font-size: 20px;
    color: #fff;
    display:flex;
}

.item{
    margin: 10px;
    width: 100px;
    line-height: 40px;
    text-align: center;
}

合并属性: flex-flow , 上面两个属性的缩写

  • <'flex-direction'> || <'flex-wrap'>

  • 默认: flex-flow: row nowrap;

这里直接结合两个属性看就好。

order

  • 指定摆放时的顺序,从小到大

  • 取值:默认 0 ,(支持负值和正值)

弹性相关属性,都是设置在子元素上的

flex-basis

  • 设置 flex item 的初始宽/高

  • 取值: main-size | <width>

  • 默认: main-size: 主轴方向的宽度 (根据 flex-direction设置,水平排列时,设置的是宽度;垂直排列时,设置的高度)

flex-grow

  • 定义每一个子元素在盒子内的弹性

  • 拓展盒子剩余空间的能力(空间富余时)

  • 取值: <number>

  • 取值:默认 0 ,整数小数都可

  • 剩余空间的分配规则 : flex-basis + flow-grow/sum(flow-grow)*remain remain 表示多余的空间

这里可以看到 只设置 flex-basis 相当与设置元素的 width



flex-shrink

  • 定义元素收缩的能力(空间不足时)

  • 取值: <number>

  • 取值 : 默认 1 ,平方(值为 0 时,不收缩)

  • 不足空间收缩的规则 : flex-basis + flow-grow/sum(flow-grow)*remain remain 表示不足的空间 (负值)



合并属性: flex

  • <'flex-grow'> || <'flex-shrink'> || <'flex-basis'>

  • 默认: flex: 0 1 main-size; 看上面


对齐 相关的属性

justify-content

  • 设置子元素在主轴方向上的对其方式

  • 取值: flex-start | flex-end | center | space-between | space-around

  • 默认 flex-start

例子:切换主轴方向时

align-items

  • 设置在辅轴上的对齐方式。

  • 取值: flex-start | flex-end | center | baseline | stretch

  • 默认 stretch

align-self 设置在子元素上

  • 单独设置子元素在辅轴方向的对齐方式

  • 取值: flex-start | flex-end | center | baseline | stretch

  • 默认 stretch

align-content

  • 多行内容 设置在辅轴方向上,行的对齐方式

    • 取值: flex-start | flex-end | center | space-between | space-around |stretch

  • 默认 stretch 拉伸

guowenfh
本文原地址: https://blog.guowenfh.com/201...

查看原文

赞 1 收藏 4 评论 0

三省吾身丶丶 发布了文章 · 2017-02-07

一张图学习 ES6 中的 React 生命周期与流程

赞 10 收藏 34 评论 0

三省吾身丶丶 赞了文章 · 2016-12-31

面对 2017 的轻狂和多疑

凌晨开始写, 我都有点梦游了, 有不对的地方请谅解.

过去一年互联网上看到过好多关于中国制造的赞誉的声音,
比如手机行业的崛起, 比如陆续披露的各种超级工程, 以及在国外的成就,
中国中低端制造业简直称霸全球了, 相比前一年的新闻非常乐观,
而美国和一些西方国家仍然占据高端部分, 比如手机芯片, 知识产权等等.
我就在想, 软件行业呢, 软件的制造和服务, 我们做到廉价和高效了吗?

早先国产的产品常常有山寨的名字, 比如说手机, 模仿苹果,
今年看来, 好多手机厂商开始跑在前头, 渐渐有一些惊喜出来.
在软件行业, 山寨国外网站商业模式甚至界面的事情也不少了,
但是到今年, 渐渐有一些 Facebook 模仿微信的声音,
我想制造业和软件行业应该会有相当多的共性, 于是就有前面这个问题,
当国产手机开始进攻国外市场, 制造做到了, 软件行业有这个能耐了么?

我做前端, 实际上我大部分时间在刷国外的社区的新闻, 而较少国内,
其实很容易对两者的差距有切身的体会, 新闻几乎都是国外的!
偶尔会有淘宝发布了什么, 或者腾讯, 但是平时都是国外的大厂,
我们的社区里在讨论哪个框架好, 而国外总是在生产着各种框架类库,
国内逐渐有成群体的程序员出现在知乎, 微博, 在各种论坛, 越来越大的人群,
但是很少看到前端程序员从编译原理层级讨论, 虽然英文社区也不是非常多,
我想说从中能看到两个群体的程序员之间不小的差距.

随着近些年社交媒体的发展, 网络社区发达, 逐渐增多的线下活动,
可以看到技术前沿的信息传播的延时在减小, 比如 Swift 文档神奇的翻译速度,
而且很多人乐忠于转载甚至翻译英文社区的技术资源到中文,
这些都在表明我们正在快速地追赶, 只是不知道多久才能追上,
同时技术在换代, 比如 WebAssembly 正在制定规范, 参与的好像都是外国人,
我们在追, 他们在跑, 究竟又是谁更快呢, 我也看不清说不清.
此外除了编程语言我们还有框架, 还有平台, 都需要追赶.

近些年我的思维改变也不小, 热衷于学习那些经济方面的网红,
比如某个用货币战争为线索梳理各种历史和国家战略的人,
或者某个每周三晚上评论一遍经济话题的人, 以及他的宠物,
或者某个做投资的每天更新一个对科技前沿的解读的人,
他们看待 IT 行业和经济时事的视角和方式让我极为钦佩,
带上这样的视角, 再回来看软件行业各个大厂的竞争, 简直暗流涌动.
我的知识量很难说可以把事情看清, 但看上去比一两年前条理多了.

制造业本身似乎跟人口红利有关, 庞大的人口和城市化带来的工人的规模,
也许还加上国人的吃苦耐劳, 解释了中低端制造业的产品的优势,
然而软件行业并不大一样, 有个段子说宁愿价钱招高手也不要分成招十个人,
更不用说 AI 了, AI 将会解放大量的人力. 机器和科技会弥补人口的差距,
而计算机领域的教育呢, 至少从圈子里的声音很难感觉什么时候能追上英文社区,
我在前端圈接触到好多人都是自学, 还有很多创业的人是辍学, 而不是本专业,
与之对比, 国外偶尔看到人说十岁开始学编程, 还有说编程三十年, 怎么比?
也许真的到我们这边从小写代码的人长大, 这个差距才能追上.

作为外行来看待制造业, 竞争力要看成本, 售价, 生产速度, 质量和售后,
去掉售价这个全球共通的因素, 就是成本, 速度, 质量了,
当然这些因素类比到软件行业, 总会涉及到各种复杂概念, 我不指望能弄清楚,
大概说这些吧, 底层工具的可靠性, 业务代码的可靠性, 时间人力成本, 设计,
说起来就是公司技术的积累和人才的素质, 这两个都有被拉开差距的吧.

公司层面比如华为再到现在靠的是这么多年巨大的科研投入,
新兴的厂商比如小米, 在短暂的时间内能做到什么程度, 我们还不知道,
但这不管怎样下定决心以后几年甚至十年的时间还是会要的吧?
假如说国内人品暴走做出了一个强大的 VR 操作系统, 怎么说,
VR 会有大量的计算速度的需求, 有大量防眩晕的需求, 还有重新定义人机交互,
中间都会有大量的工作, 要形成竞争力要多少技术积累, 一两年能做到吗?

然后是人的因素, 要解决比如 VR 这么多问题, 就对应到那么多的简历,
需要大量的交互经验, 高性能的图形开发, 3D 体感和空间计算, 还有更多,
已有的程序员通过什么途径获取知识? 博客, 聚会, 别人的代码, 工作经历,
是的我们学东西是非常快, 但是如果没有先例可以去学呢? 没有框架没有代码?
比如编程, 如果没有前人做过, 就需要去读 paper 写算法自己去做,
极端的还有自己去研究. 我们现在有这样的实力么? 精通操作系统和编译原理?

至少从前端开发的行业看来, 我感到非常地被动, 主流框架几乎都是国外的,
也许现在好多了, 我刚入行时候说国外某某公司怎么, 被人吐槽崇洋媚外过,
现在大家很清楚, Google 主导着浏览器, Facebook 主导着一个框架,
这个有着众多中文开发者的号称开放标准的平台, 谁最有话语权?
当然是开发浏览器的人, 制定 ECMAScript 规范的人, 那主要都是谁呢?
我们在说移动通信标准, 华为抢下 5G 的某个什么的时候. 编程领域我们有什么?
当然短期来说以我们的差距, 我们甚至是后发优势, 但是微软跟网景竞争总听过了吧,
假如我们追上, 到了那个量级, 别人手里捏着标准, 然后发生竞争, 会怎样?

我也考虑另外的可能性, 比如小种语言小众社区, 如果我们早点切入,
然后跟着社区成长, 我们有一席之地, 等到社区壮大, 就有我们说话的地方,
比如 Elixir, 这门语言新兴, 没几年, Conf 都只开了两届,
以我国的人口, 如果有那个几十个人活跃在当中, 对社区来说相当多了,
但是回到现实, 如果要深入 Elixir, 得有深入的 Erlang 的经验, 多少人有,
其他新兴的小众社区, Clojure, 甚至 Haskell, 门槛更高,
倒是 Go 社区这些年风风火火看到有不少的人, beego 也是风光了.
回到前端来, 这些年重大的创新, 有我们参与么, 算我们骄傲么?

我们需要有足够的高素质的程序员才能在这方面取得优势和好处,
呃.. 也许有点像我没睡醒在说梦话, 大家写代码主要是为的谋生才对,
我想说换个角度, 比如你公司上级创业, 缺人, 还有技术搞不定,
然后你上, 结果坑多得要死你完全忙不过来, 然后想挖人, 一看国内没人可挖, 坑吧.
或者你用了国外的代码有功能缺失, 费尽力气找到解决方案, 希望官方解决,
结果官方没时间 review 代码, 你只能一直用有问题的版本,
如果自己 fork, 还有面临新的代码跟官方的代码冲突的问题, 只能自己解决.
我只是想说这个事情真的是重要的, 否则会消耗很多时间金钱.

目前看来很难单纯依靠学校教育把整体的素质提高上去了,
前端以写教材都跟不上的速度在往前发展, 还不止往一个方向,
我们更不可能先不工作几年, 好好学习这些东西, 房价新闻也很多啊,
只剩下说建立社群, 尽量把学习技术的成本压缩下来, 让大家更快能赶上,
比如说, 尽快用检验完成的新技术替换掉旧技术, 效率落后的技术还学个什么劲,
还有通过社区的信息量夯实基础, 常见问题看别人接连犯错, 旁边干看也能看明白了,
以及扩大人群, 让真正聪明的人能进入到编程的领域, 带来技术的突破,
我们甚至需要学习的梯度, 因为高手写东西新手看不懂, 比如 Haskell 社区的情况.
就是说有个成熟的社区, 按说是可以很大程度降低学习成本的.

具体关于社区, 我从前也讨论了, 新闻, 文档, 通讯工具, 博客讨论等等,
我们需要论坛, 微博, 个人博客, 问答网站, IM, Wiki, 各种形式和各种人,
甚至还要有线下聚会, 要有擅长 PPT 和演讲的人, 还要交际花,
首先我们要掌握消化国外技术的能力, 然后想办法积累自己的技术,
到后面我们才能形成足够的竞争力. 至于跟 Google 同台竞技? 好吧我是不敢想.
以此为假想吧, 我们走到哪一步了? 我们遇到哪些瓶颈了? 接下来做什么?
我们有 V2ex 有知乎某些意见领袖了, 有 Go, Ruby, Node, React 各种中文论坛,
也有了 Segmentfault 这样综合性的纯技术网站, 以及一些年代早一些的社区.
接下来我们需要的是什么? 什么样的方案是有效的?

其中一些也是我这些年思考 React 社区的事情的逻辑, 首先我微博刷了大量新闻,
然后一起弄了 React 论坛, 在微博创建了抓新闻的机器人, 接着还有微信群,
同时更多我以外的人还弄了 QQ 群, 各种线下分享, 各种博客等等,
对我来说参与进中文社区的创建, 帮我认识了很多厉害的人, 还有他们做的事情,
我也看到微信群他们会聊复杂到我对付不了的各种技术细节, 真庆幸他们都是自己人,
同时, 我也明白这不够, 文档整理, 聊天室, 技术探索, 新人引导, 各种不足,
而很多超出我的能力了, 或者我其实也没那个决心或者那种资源.

到我个人吧, React 那么多问题, 明天 Facebook 会不会把 js 撤了放新语言上来?
会不会很快 React 被淘汰了, 这是更加困扰我的问题, 到时候我怎么办,
要注意, 我是自学的程序员, 如果技术换了, 那些年轻人肯定是学得比我快的,
Chrome 每个月都会更新, API 一直在调整, 连 js 都一年好几个版本,
所以我对 js 并不珍重, 我需要更可靠的技术栈. 熟悉我的人知道我在赌 Clojure.
年初休息的几个月我花大量时间写 cljs, 年底在 D2 做了一些分享, 我安稳了很多,
如果明年 Facebook 强推 Reason, 很多人也许头疼, 但我至少已经做了很多准备.

新技术其实还关系到一个成本的问题, 比如当年福特 T 型车压成本的事情,
当然现在还有今年国产千元机的事迹. 某种程度上软件行业更多压缩成本的例子,
从前写软件要大量的人一起协作, 现在一个人就能做出小的 app,
创业公司变得一个后端几个前端加移动端, 然后就能做产品了,
但是, 即便压缩成本, 科技本身的含量却是急剧上升, 实际上整个技术栈成本不是在降低,
甚至是在升高, 只是说, 在当前的环境中获取上游产品的成本降低了,
强行举例, 比如从前只有一家公司卖芯片, 很贵, 但现在大家都做, 就便宜了.

以此可以解释 FP 语言比如 Clojure 为什么我说成本低, 实际上却门槛更高.
随着更多人用 cljs 的理念编写界面, 我认为将会遭遇更少的问题, 也就是成本低,
但是学习 Clojure, 特别是没有足够中文资料和中文社区的情况下, 成本很大,
这么多年耳濡目染, js 已经信手拈来了, Grunt Gulp Webpack 花那么多力气学会了,
到 Clojure 语言变了, 编译工具变了, 社区认不出来, 都是英文, 合算么?
这笔账不好算. 但是站在我的角度, 我会 Clojure, 我看到 js 那么多坑, 我是忧心忡忡的.
比如 JSX, 当年 coffee 中完全不是问题, 而 ES6 大家还在吐槽 JSX 有问题. 让我怎么说好.

而且明年的 WebAssembly, 我看过别人社区的人吐槽 js 的各种言论,
我有理由担心几个大厂联合发动了产业升级, 要强行替代掉旧的技术,
js 成为热门技术最近十年的事情, 很多前端比如我, 入行也就四五年,
但是站在 Java 或者 Objective-C 的角度, js 就是搅局啊, 抢走了一大块蛋糕,
现在有别的技术来抢 js 的市场, 背后三个大厂撑腰, 会不会就搞成了? 很有可能啊.
当然 wasm 是底层技术, 有专门写编译器的职业分工, 一般人不用担心,
但是长远看呢, 你的 app 每天有千万用户, 竞争对手打开网页飞快, 你能安心继续 js 么?

过去一年还发生了很多事情, 大概是叫 O2O 吧, 我学会了订外卖, 手机打车,
我还体验了一次飞猪直接订宾馆, 还有扫描骑自行车, 手机买地铁票,
当然也怪我自己没能力, 但是如果去一个新的城市游玩, 这些都很成问题,
地图的问题早些年已经解决了, 各种生活化的服务, 还有信用方面, 仍然在完善中,
不管怎样我这种小城镇进城的死宅也能刷高铁出去玩, 至少没以前那么困扰了.
随着互联网行业的发力, 情况会越来越好, 就像我在新城商业视频里看到的那样,
不久以后我能从 app 上直接租赁别人空间的空间用来办公或者其他事情,
也许几年后真的另一个地区就像去上海另一个区那么方便也说不定.

还有按照某人的预言, 明年无线充电技术成熟会逐渐进入市场, 有的玩了,
我也记不大清楚哪些厂商承诺了要在 2017 年发布什么技术的,
最关心的是 Weex 和 WebAssembly, 一个关系到我工作, 一个关系到会不会丢工作,
Clojure 已经处于稳定状态, cljs 应该很快会有对 ES6 模块的支持, 算是重大利好,
Webpack 2 经历了一个季度的跳票, 终于迎来了 RC 版本, Larkin 晒娃也是看够了,
React Fiber 快成了, 不关心, 反而 Jordan 前天发 ReasonML 的 demo 暗藏杀机.
如果 ReasonML 真能成气候, FP 也许又会刷一波新闻, 我反正已经笨鸟先飞了.
后端语言 Go 和 Elixir 要盯两眼, Clojure 已经山寨了 Go, 不知道不会继续下手,
Vue 2 已经算是搞好了吧, 我后面就围观 Weex 方向怎么发力了, 小程序还没看.
对了 CoffeeScript 2.x 正在 alpha, 官网都好了, 就差 class 部分, 快了.

当然忧心的事情也很多, 说了几年的要学后端要学设计, 到现在没有可靠的路径,
由于我在 Clojure 和前端要花很多精力, 其实很难踏踏实实一点一点学,
某种程度上我担心以后我更难学东西了, 而且年龄增加本身就会削弱学习能力.
同时苍白的恋爱经历让我对自己的宅属性也感受到了越来越大的压力,
我不知道整天上网的人群之外的人都在想些什么, 玩些什么, 加上我本来就是独处的性格,
我也不熟悉杭州上海苏州之外其他地方, 但是我工作好几年了, 还能逃避多久?
我也没参加过一次 Clojure Conf, 社区的大神网上都不爱搭理我, 感觉很孤立.
仔细去想, 头疼的事情不少, 我也是躲着. 俗话说报喜不报忧吧, 点到为止.

2016 年对我来说是个转折, 前面三年写的单页面应用, 投入了大量的精力,
年初硬生生把自己变成了 Clojure 程序员, 工作方面也脱离了 React,
曾经的 React 中文站长, 在 Vue 程序员组里给移动 app 写 Weex, 的周边...
去年春节我曾询问 cljs 的工作, 但是新年将至我更加确信 Clojure 在国内的小众,
外人看我恐怕更像是为的代码洁癖才变身 Clojure 程序员, 属于过分激进,
而且对于 Clojure 实用性的判断, 推广的进度, 有很严重的时间上的误判,
结果反而导致 React 中文社区出现了投入不足, 正好是 Vue 风头正盛的时候.
好吧, 虽然我坚持不可变数据优于 Observable, 但 Vue 的成就和前景不容置疑.

也正好是晓松奇谈播放最后一集的时候, 就像其中女粉丝正忙着伤感那样
她说自己都已经成年了, 听了高晓松的节目, 价值观仍然在改变.
某种程度上我也是, 从宋鸿兵, 从高晓松, 从王煜全, 从吴晓波等等, 我学到好多东西,
那些东西我家长没能力教我, 我老师不曾教我, 我同学同事也不能教我,
甚至从书里我也学不到, 而这些深深改变着我的世界观, 改变着我对自己的期望,
他们教了我很多历史和当下, 让我在思考时有了更多的知识储备和视角,
就像高晓松说的, 如果都不知道世界是什么样, 历史是什么样, 还怎么形成独立思考?
而他们带给我的, 丝毫不逊于编程领域我投入那么多时间所学习到的,
世界依旧很大, 我们依然渺小.

查看原文

赞 8 收藏 6 评论 13

三省吾身丶丶 赞了文章 · 2016-12-01

whistle - 跨平台(Win/Mac/Linux)的 Fiddler

web调试代理工具是web开发人员必备的工具,它在发起web请求的客户端与服务器之间充当中间人角色,可以用于查看、修改或替换HTTP、HTTPS、Websocket请求(响应)数据,协助我们做本地开发调试、构造数据、定位问题等等,业界已有一些比较优秀的web调试代理工具,特别是Windows上的Fiddler,由于其出色的功能设计,俨然成为了web调试代理工具的代名词。但Fiddler只能在Windows系统上使用,当我们换上Mac或者开发环境限制必须使用Linux时,我们很难在这些平台上找到类似Fiddler的工具,这个时候可能被提起最多的是Charles,Charles是用Java实现的跨平台web调试代理工具,理论上可以在安装java虚拟机的桌面系统上使用,用过Fiddler的人普遍觉得Charles难用,不管是性能、体验、界面都无法跟Fiddler相提并论(事实上Fiddler本身也是很耗内存),更重要的是Charles不是免费的,价格也不菲,这也可能是因为在Mac、Linux平台上可以选择的web调试代理工具也不多,下面我要给大家详细介绍的是本文的主角,开源免费且持续在维护的whistle--全新的跨平台web调试代理工具。

Note: 想了解调试代理工具的原理可以看这篇文章:HTTP 代理原理及实现

whistle

Github: https://github.com/avwo/whistle

whistle是用NodeJS实现的跨平台web调试代理工具,可以安装部署在装有NodeJS的操作系统上(包括Windows、Mac、Linux等桌面或命令行系统),并可以通过Chrome浏览器访问本地或远程whistle的管理配置界面:

  • 查看、修改或替换HTTP、HTTPS、Websocket请求(响应)数据(包括url,host,方法,状态码,头部及内容等)

  • 设置延迟请求(响应)

  • 限制请求(响应)速度

  • 在Composer里面构造请求

  • 可以把请求代理到其它服务器(支持socks和http代理)

  • 查看请求列表的Timeline

  • 查看页面JS脚本执行过程中抛出的异常(可以用在调试终端页面或远程调试)

  • 查看JS中的console[info, log, warn, error, fatal](这些方法在IE也可以使用)打印出来的数据对象(可以用在调试终端页面或远程调试)

  • 利用whistle集成的weinre查看或修改页面的DOM结构(可以用在调试终端页面或远程调试)

  • 使用通过插件的形式扩展的功能(插件可以用来扩展whistle的功能,特别是集成本地开发环境,方便开发人员使用),如:处理combo请求

whistle借鉴了Fiddler的一些优秀的设计,比如请求列表及其详细内容的展示(即Network),但whistle不是Fiddler的复制品,除了请求列表的展示外,whistle在操作请求及功能扩展上有着自己独特的方式,让修改请求(响应)操作变得更简单,并集成了终端(远程)调试工具。

Note:为了让大家能对下面内容更好的理解及快速上手whistle,请先按步骤安装好whistle:安装node、安装whistle、配置代理、启动whistle

规则原理

Fiddler修改请求(响应)的数据时,需要对每个请求设置断点后-->手动修改-->手动放开请求,如果是同时修改多个请求或者多次修改某个请求时,体验不是很好;而whistle把对请求(响应)的操作分类,每一类对应一种协议(如:替换本地文件 file://),每个具体操作要用到的参数值放到协议后(如:file:///User/xxx/test.html中的/User/xxx/test.html),这样whistle把每个操作抽象成一个uri

问题来了,如果参数值有空格或需要多个参数(比如修改头部的一些字段),这个whistle是怎么处理的?

上述问题包含两个问题:

  1. 如何参数值有空格的问题?
    在单参数情形下,如果是文件路径或者是下面第二个问题要提到的请求参数类型(如:file://filepathresAppend://filepathreqHeaders://test=a%20b&referer=test等),可以用%20来代替,其它情形,可以用{key}的形式把参数值存到whistle的Values系统,如:ua://{chrome-ua},whistle会自动到Values里面找chrome-ua对应的值。

  2. 如何处理多个参数的情形?
    为方便用户使用,whistle尽可能把一些常用的操作精简到只有一个参数的情形(如请求头部字段,可以用协议req这种多参数的形式来新增或修改,对里面常用的字段,如refererua等也可以用独立的协议(refererua)来设置),但不可能把把所有操作都抽象成单个参数的情形。对于多参数情形,有如下3种方式:

    • 直接用请求参数的方式转成单参数的情形: reqHeaders://test=a%20b&referer=test,如果里面有空格要用%20替换

    • 按问题1的方式,用key代替,把value写到whistle的Values里面(reqHeaders://{test-reqHeaders}),而Values里面reqHeaders的值也可以有两种方式具体参考:JSON格式

    • 直接把值写到本地文件(reqHeaders:///User/xxxx/test.json),文件/User/xxxx/test.json里面reqHeaders的值也可以有两种方式具体参考:JSON格式

上面解决了把每个对web请求的操作用一个uri来表示的问题, 现在我们把对web请求的操作都可以抽象成一个operator-uri,如何用这些operator-uri来操作具体请求呢?

处理这个问题,whistle扩展了传统hosts配置方式,采用匹配请求url到operator-uri的映射:

pattern  operator-uri

pattern可以以下三种方式:

  • 域名匹配:www.example.com operator-uri

  • 路径匹配:www.example.com/... operator-uri

  • 正则匹配:/example/ operator-uri

为了兼容hosts的配置方式:

# 单个匹配
127.0.0.1 www.example.com

# 多个匹配
127.0.0.1 www.example1.com www.example2.com www.exampleN.com

whistle默认匹配顺序是从上到下,从左到右,多个匹配也可以写成

# 多个匹配
pattern operator-uri1 operator-uri2 operator-uriN

如果whistle可以判断operator-uri不可能为web请求的url,即不是如下uri形式:

# operator-uri协议为http, https, ws, wss之一
pattern http://xxxx

# operator-uri不包含协议,又不是ip
pattern xxxxx

或者pattern为正则表达式,满足这两种情况下,patternoperator-uri的顺序是可以调换的:

# 单个匹配
operator-uri pattern
# 多个匹配的情况
operator-uri pattern1 pattern2 patternN

上面讲了Rules的一些配置规则原理及Values的用途,包括:operator-uri设计原理,规则的匹配顺序(从上到下,从左到右),匹配方式,什么情况下可以对调等等。

一些例子

下面举一些例子,让大家快速上手whistle(规则中用#作为注释符号,如果要查看或修改HTTPS、Websocket的请求(响应)数据,要开启HTTPS拦截功能:启用HTTPS):

  1. host
    host的配置方式不仅完全兼容系统自带的配置方式,而且支持路径、正则匹配:

    # 传统的配置方式
    # 单个匹配
    127.0.0.1 www.ifeng.com
    # 多个匹配
    127.0.0.1 www.ifeng.com www.qq.com
        
    # 路径(顺序可以调换)
    # 单个匹配
    127.0.0.1 http://www.ifeng.com/xxx
    # 多个匹配
    127.0.0.1 http://www.ifeng.com www.qq.com/xxx
        
        
    #正则(顺序可以调换)
    # 单个匹配
    127.0.0.1 /ifeng/
    # 多个匹配
    127.0.0.1 /ifeng/ /qq/
        
        
    # whistle也支持如下方式,在ip前面加个协议(顺序可以调换)
    # 单个匹配
    host://127.0.0.1 www.ifeng.com
    # 多个匹配
    host://127.0.0.1 www.ifeng.com www.qq.com
  2. 端口映射(下面举得例子都是用域名匹配的方式,其它方式同理)

    # 本地调试
    www.example.com 127.0.0.1:8080
        
    # 映射到线上
    www.example.com www.qq.com
    www.example.com:8080  www.qq.com
  3. 本地替换

    # 下面用`|`来分隔目录或文件,从左到右依次找到存在的文件为止
    www.example.com file:///User/xxx/test|/User/xxx/test/index.html

上述配置,如果我们访问http://www.example.com/,则whistle会先找是否有/User/xxx/test这个文件,如果没有就会匹配/User/xxx/test/index.html;如果我们访问http://www.example.com/test.html,则whistle会先在找文件/User/xxx/test/test.html,再找/User/xxx/test/index.html/test.html,如果没就返回404

这里只是举了几个小例子,whistle还有非常多的功能,如:reqHeadersresHeaderslogdisablefilterweinre等等,而且还支持插件扩展,更多功能请参考:Wiki功能列表

后续规划

whistle是我业余时间在维护且会长期维护的项目,一般来说新版本(如果有)的发布放在周日晚上(修复影响功能的bug的版本不受时间限制,有新版本发布界面中的About菜单现会有红点提醒或者大版本发布则是直接弹框提醒,大家按提示更新就可以),下面是一些后面的规划内容:

  1. 插件whistle.websocket开发:用于查看websocket的请求内容

  2. 插件whistle.img开发:用于查看抓包到的图片内容

  3. 插件whistle.settings开发:用于一键安装系统代理及安装根证书(如果根证书不能一键安装,至少要做到点击能弹出根证书的安装对话框),也为后面的客户端版本做准备

  4. whistle的客户端开发:有打算把一些自动化测试的功能集成进来,设想的交互功能也会比Node强很多,因为可以直接操作本地文件

  5. 官网http://wproxy.org及文档建设:打算用Gitbook把文档重新整理,然后再i18n下及官网陆续建立起来

上面面规划的功能都只是在业余时间做,所以不能给出确切的完成时间,前面两个插件会尽快开发出来,想关注进度可以 Follow me:https://github.com/avwo

PS:为什么不把websocket、img、settings这几个插件功能集成到whistle里面,而要以插件的形式单独存在?主要是考虑到这几部分功能用到的时候相对比较少,及减少安装whistle过程中出现错误及运行过程中的内存问题,像处理websocket用到的中间件体积比较大且需要本地编译,img功能有比较耗内存和硬盘,而且把这部分功能放在独立的页面可能体验更好,综上考虑没有把这部分功能集成到whistle里面,但后续的客户端版本会直接集成进来。

说点什么

  1. 如果什么问题或建议可以给我提issue或加QQ群462558941

  2. 也欢迎大家直接PR

  3. 如果觉得whistle好用且对大家有帮助,帮忙分享给你所在的团队及别忘了给whistle加个Star

查看原文

赞 14 收藏 18 评论 1

三省吾身丶丶 赞了文章 · 2016-10-25

使用React + Redux + React-router构建可扩展的前端应用

现在是前端开发最好的时代,有太多很好的框架和工具帮你更好的实现复杂需求;同时又是最困难的时代,因为需要掌握太多的框架和工具。如何利用好各种框架来提高前端开发质量是大家都在探索的问题。本文就将介绍如何使用 React 及其相关技术,来进行实际前端项目的开发。因为主要介绍如何将技术用于实践,所以希望读者已经对相关概念已经有一定的了解。

本文最初来源于笔者在 StuQ 的一次同名课程直播,现在加以整理成文,希望能对更多的人有所启发。为了固化这种实践方式,当时还开发了一个名为 Rekit 的工具,用于确保项目能够始终遵循这种实践方式。现在工具也获得进一步完善,大家也可以结合 Rekit 来理解文中提到的实践方案。

Paste_Image.png

其实无论使用什么样的技术,一个理想中的 Web 项目大概都需要考虑以下几个方面:

Paste_Image.png

  1. 易于开发:在功能开发时,无需关注复杂的技术架构,能够直观的写功能相关的代码。

  2. 易于扩展:增加新功能时,无需对已有架构进行调整,新功能和已有功能具有很好的隔离性,并能很好的衔接。新功能的增加并不会带来显著的性能问题。

  3. 易于维护:代码直观易读易理解。即使是新加入的开发成员,也能够很快的理解技术架构和代码逻辑。

  4. 易于测试:代码单元性好,能够尽量使用纯函数。无需或很少需要 mock 即可完成单元测试。

  5. 易于构建:代码和静态资源结构符合主流模式,能够使用标准的构建工具进行构建。无需自己实现复杂的构建逻辑。

这些方面并不是互相独立,而是互相依赖互相制约。当某个方面做到极致,其它点就会受到影响。举例来说,写一个计数器功能,用jQuery一个页面内即可完成,但是易开发了,却不易扩展。因此我们通常都需要根据实际项目情况在这些点之间做一个权衡,达到适合项目的最佳状态。庆幸的是,现在的前端技术快速发展,不断出现的新技术帮助我们在各个方面都获得很大提升。

本文将要介绍的就是如何利用 React + Redux + React-router 来构建可扩展的前端应用。这里强调可扩展,因为传统前端实现方案通常在面对复杂应用时常常力不从心,代码结构容易混乱,性能问题难以解决。而可扩展则意味着能够从项目的初始阶段就具有了支持复杂项目的能力。首先我们看下涉及到的主要技术。

React

React 相信大家已经非常熟悉,其组件化的思想和虚拟 DOM 的实现都是颠覆性的变革,从而让前端开发可以在新的方向上不断提升。无论是 React-hot-loaderRedux 还是 React-router,都正是因为充分利用了 React 的这些特性,才能够提供如此强大的功能。笔者曾经写过《深入浅出React》 的系列文章,有需要的话可以进一步阅读。

Redux

Redux 是 JavaScript 程序状态管理框架。尽管是一个通用型的框架,但是和 React 在一起能够更好的工作,因为当状态变化时,React 可以不用关心变化的细节,由虚拟 DOM 机制完成优化过的UI更新逻辑。

Redux 也被认为整个 React 生态圈最难掌握的技术之一。其 action,reducer 和各种中间件虽然将代码逻辑充分隔离,即常说的 separation of concerns,但在一定程度上也给开发带来了不便。这也是上面提到的,在易维护、易扩展、易测试上得到了提升,那么易开发则受到了影响。

React-router

即使对于一个简单的应用,路由功能也是极其重要的。正如传统 Web 程序用页面来组织不同的功能模块,由不同的 URL 来区分和导航,单页应用使用 Router 来实现同样的功能,只是在前端进行渲染而不是服务器端。React 应用的“标准”路由方案就是使用 React-router。

路由功能不仅让用户更容易使用(例如刷新页面后维持 UI),也能够在开发时让我们思考如何更好组织功能单元,这也是功能复杂之后的必然需求。所以即使一开始的需求很简单,我们也应该引入 React-router 帮助我们以页面为单元进行功能的组织。

其它需要的技术

正如前面提到的,开发前端应用需要很多周边技术,这进一步增加了前端开发的门槛,例如:

这些工具提高了前端开发的能力和效率,但是了解并配置它们却并非易事,而事实上这些工具和需要开发的功能并没有直接的关系。使用工具来自动化这些配置是必然的发展方向,正如现在开发一个 C++ 应用,Visual Studio 会帮你完成所有的配置并搭建合适的项目结构,让你专注于功能逻辑的开发。无论是自己实现,还是利用第三方,我们都应该为自己的项目创建这样的工具链。

简单介绍了相关技术,下面我们来看如何去构建可扩展的 Web 项目。

按功能(feature)来组织文件夹结构

无论是 Flux 还是 Redux,提供的官方示例都是以技术逻辑来组织文件夹的,例如,下面是 Redux 的 Todo 示例应用的文件夹结构:

Paste_Image.png

虽然这种模式在技术上很清晰,在实际项目中却有很大的缺点:

  1. 难以扩展。当应用功能增加,规模变大时,一个 components 文件夹下可能会有几十上百个文件,组件间的关系极不直观。

  2. 难以开发。在开发某个功能时,通常需要同时开发组件,action,reducer 和样式。把它们分布在不同文件夹下严重影响开发效率。尤其是项目复杂之后,不同文件的切换会消耗大量时间。

因此,我们使用按功能来组织文件夹的方式,即功能相关的代码放到一个文件夹。例如,对于一个简单论坛程序,可能包含 user,topic,comment 这么几个核心功能。

Paste_Image.png

每个功能文件夹下包含自己的页面,组件,样式,action 和 reducer。

Paste_Image.png

这种文件夹结构在功能上而非技术上对代码逻辑进行区分,使得应用具有更好的扩展性,当增加新的功能时,只需增加一个新的文件夹即可;删除功能时同理。

使用页面(Page)的概念

前面提到了路由是当今前端应用的不可缺少的部分之一,那么对应到组件级别,就是页面组件。因此我们在开发的过程中,需要明确定义页面的概念:

  1. 一个页面拥有自己的 URL 地址。页面的展现和隐藏完全由 React-router 进行控制。当创建一个页面时,通常意味着在路由配置里增加一条新的规则。这和传统 Web 应用非常类似。

  2. 一个页面对应 Redux 的容器组件的概念。页面首先是一个标准的 React 组件,其次它通过 react-redux 封装成容器组件从而具备和 Redux 交互的能力。

页面是导航的基本模块单元,同时也是同一功能相关 UI 的容器,这种符合传统 Web 开发方式的概念有助于让项目结构更容易理解。

每个 action 一个独立文件

使用 Redux 来管理状态,就需要进行 action 和 reducer 的开发。在官方示例以及几乎所有的教程中,所有的 action 都放在一个文件,而所有的 reducer 则放在另外的文件。这种做法易于理解但是不具备很好的可扩展性,而且当项目复杂后,action 文件和 reducer 文件都会变得很冗长,不易开发和维护。

因此我们使用每个 action 一个独立文件的模式:每个 Redux 的 action 和对应的 reducer 放在同一个文件。使用这个做法的另一个原因是我们发现每次创建完 action 几乎都需要立刻创建 reducer 对其进行处理。把它们放在同一个文件有利于开发效率和维护。

以开发一个计数器组件为例:

Paste_Image.png

为实现点击“+”号增加1的功能,我们首先需要创建一个类型为 "COUNTER_PLUS_ONE" 的 action ,之后就立刻需要创建对应的 Reducer 来更新 store 的数据。官方示例的做法是分别在 actions.js 和 reducer.js 中分别加入相应的逻辑。而使用每个 action 独立文件的做法,则是创建一个名为 counterPlusOne.js 的文件,加入如下代码:

import {
  COUNTER_PLUS_ONE,
} from './constants';

export function counterPlusOne() {
  return {
    type: COUNTER_PLUS_ONE,
  };
}

export function reducer(state, action) {
  switch (action.type) {
    case COUNTER_PLUS_ONE:
      return {
        ...state,
        count: state.count + 1,
      };

    default:
      return state;
  }
}

按我们的经验,大部分的 reducer 都会对应到相应的 action,很少需要跨功能全局使用。因此,将它们放入一个文件是完全合理的,有助于提高开发效率。需要注意的是,这里定义的 reducer 并不是标准的 Redux reducer,因为它没有初始状态(initial state)。它仅仅是被功能文件夹下的根 reducer 调用。注意这个 reducer 固定命名为 "reducer",从而方便其被自动加载。

对于异步 action(通常是远程 API 请求),则需要对错误信息进行处理,因此在这个文件中有多个标准 action 存在。例如以保存文章为例,在 saveArticle.js 这个 action 文件中,同时存在 saveArticle 和 dismissSaveArticleError 这两个 action。

如何处理跨功能的 action?

尽管不是很常见,但是有些 action 是可能被多个 reducer 处理的。例如,对于站内聊天功能,当收到一条新消息时:

  1. 如果聊天框开着,那么直接显示新消息。

  2. 否则,显示一条通知提示有新的消息。

可见,NEW_MESSAGE 这个 action 类型需要被不同的 reducer 处理,从而能够在不同的 UI 组件做不同的展现。为了处理这类 action,每个功能文件夹下都有一个 reducer.js 文件,在里面可以处理跨功能的 action。

虽然不同 action 的 reducer 分布在不同的文件中,但它们和功能相关的 root reducer 共同操作同一个状态,即同一个 store 分支。因此 feature/reducer.js 具有如下的代码结构:

import initialState from './initialState';
import { reducer as counterPlusOne } from './counterPlusOne';
import { reducer as counterMinusOne } from './counterMinusOne';
import { reducer as resetCounter } from './resetCounter';

const reducers = [
  counterPlusOne,
  counterMinusOne,
  resetCounter,
];

export default function reducer(state = initialState, action) {
  let newState;
  switch (action.type) {
    // Put global reducers here
    default:
      newState = state;
      break;
  }
  return reducers.reduce((s, r) => r(s, action), newState);
}

它负责引入不同 action 的 reducer,当有 action 过来时,遍历所有的 reducer 并结合需要的全局 reducer 来实现对 store 的更新。所有功能相关的 root reducer 最终被组合到全局的 Redux root reducer 从而保证全局只有一个 store 的存在。

需要注意的是,每当创建一个新的 action 时,都需要在这个文件中注册。因为其模式非常固定,我们完全可以使用工具来自动注册相应的代码。Rekit 可以帮助做到这一点:当创建 action 时,它会自动在 reducer.js 中加入相应的代码,既减少了工作量,又可以避免出错。

使用单文件 action 的好处

使用这种方式,可以带来很多好处,比如:

  1. 易于开发:当创建 action 时,无需在多个文件中跳转;

  2. 易于维护:因为每个 action 在单独的文件,因此每个文件都很短小,通过文件名就可以定位到相应的功能逻辑;

  3. 易于测试:每个 action 都可以使用一个独立的测试文件进行覆盖,测试文件中也是同时包含对 action 和 reducer 的测试;

  4. 易于工具化:因为使用 Redux 的应用具有较为复杂的技术结构,我们可以使用工具来自动化一些逻辑。现在我们无需进行语法分析就可以自动生成代码。

  5. 易于静态分析:全局的 action 和 reducer 通常意味着模块间的依赖。这时我们只要分析功能文件夹下的 reducer.js,即可以找到所有这些依赖。

React-router 的规则定义

通常来说,我们会通过一个配置文件定义所有的路由规则。同样的,这种方式不具有扩展性,当项目变复杂之后,规则定义表会变得冗长而复杂。既然我们已经以功能为单位进行文件夹的组织,我们同样可以把功能相关的路由规则也放到对应文件夹下。因此,我们可以利用 React-router 的 JavaScript API 进行路由规则的定义,而不是用常见的 JSX 语法。

例如,对于一个简单论坛程序,主题功能对应的路由定义就放在 features/topic/route.js 中,内容如下:

import {
  EditPage,
  ListPage,
  ViewPage,
} from './index';

export default {
  path: '',
  name: '',
  childRoutes: [
    { path: '', component: ListPage, name: 'Topic List', isIndex: true },
    { path: 'topic/add', component: EditPage, name: 'New Topic' },
    { path: 'topic/:topicId', component: ViewPage },
  ],
};

所有功能相关的路由定义都被全局的根路由配置自动加载,因此,路由加载器具有如下的代码模式:

import topicRoute from '../features/topic/route';
import commentRoute from '../features/comment/route';

const routes = [{
  path: '/rekit-example',
  component: App,
  childRoutes: [
    topicRoute,
    commentRoute,
    { path: '*', name: 'Page not found', component: PageNotFound },
  ],
}];

可见,这个全局路由加载器负责加载所有 feature 的路由规则。类似 root reducer,这里的代码模式也是非常固定的,因此可以借助工具来维护这个文件。当使用 Rekit 创建页面时,就会自动在此加入路由规则。

使用工具辅助开发

由上面的介绍可以看到,开发一个 React 程序并不容易,即使一个简单的功能,也需要大量的琐碎的,但却非常重要的代码来确保一个良好的架构,从而让应用易于扩展和维护,虽然这些周边代码和你需要的功能并没有直接关系。

例如,对于一个论坛程序,需要一个列表界面展示最近发表的主题,为了做这样一个页面,我们通常都需要完成以下步骤:

  1. 创建一个名为 TopicList 的 React 组件;

  2. 为 TopicList 定义一条路由规则;

  3. 创建一个名为 TopicList.css 的样式文件,并在合适的位置引入;

  4. 使用 react-redux 将 TopicList 组件封装成容器组件,从而使其可以使用 Redux store;

  5. 创建4种不同的 action 类型:FETCH_BEGIN, FETCH_PENDING, FETCH_SUCCESS, FETCH_FAILURE,通常定义在 constants.js;

  6. 创建两个 action:fetchTopicList 和 dismissFetchTopicListError;

  7. 在 action 文件中引入类型常量;

  8. 在 reducer 中创建4个 swtich case 来处理不同的 action 类型;

  9. 在 reducer 文件中引入类型常量;

  10. 创建组件的测试文件及其代码结构;

  11. 创建 action 的测试文件及其代码结构;

  12. 创建 reducer 的测试文件及其代码结构。

天!在正式开始写论坛逻辑的第一行代码之前,竟然需要做这么多琐碎的事情。当这样的事情手动重复了多次之后,我们觉得应该有工具来自动化这样的事情。为此创建了 Rekit 工具包,可以帮助自动生成这些文件结构和代码。不同于其它的代码生成器,Rekit 基于一个相对固定的文件和代码结构,因此可以做更多的事情,例如:

  1. 它知道在哪里以及如何定义路由规则;

  2. 它知道如何生成 action 类型常量;

  3. 它知道如何根据 action 名字来生成类型常量;

  4. 它知道如何根据 action 类型来创建 reducer;

  5. 它知道如何创建有意义的测试案例。

借助于精心维护的工具,我们可以不必关注技术细节,而只需专注于功能相关的代码,提高了开发效率。不仅如此,工具也可以减少错误,并在代码结构,命名,配置等方面维持高度一致性,让代码更加容易理解和维护。

Rekit 针对本文提出的 React + Redux 开发实践提供了一套工具集,其本身也是可扩展的。你完全可以根据需要更改代码模板,或者提供自己的工具,针对自己的项目特性提供便捷的工具来提高开发效率。

小结

本文主要介绍了如何使用 React,Redux 以及 React-router 来开发可扩展的 Web 应用。其核心思路有两个,一是以功能(feature)为单位组件文件夹结构;二是采用每个 action 单独文件的模式。这样能够让代码更加模块化,增加和删除功能都不会对其它模块产生太大影响。同时使用 React-router 来帮助实现页面的概念,让单页应用(SPA)也拥有传统 Web 应用的 URL 导航功能,进一步降低了功能模块间的耦合行,让应用结构更加清晰直观。

为了支持这样的实践,文中还介绍了 Rekit 工具集,不仅可以帮助创建和配置初始的项目模板,而且还提供了大量实用的工具帮助以文中提到的方式自动生成技术结构,提高了开发效率。更多的工具介绍可以访问其官网:http://rekit.js.org

查看原文

赞 9 收藏 61 评论 0

三省吾身丶丶 发布了文章 · 2016-10-24

node.js http 模块学习笔记

一个网站的加载流程粗略的流程大概如下:

  1. 用户通过浏览器发送一个http的请求到指定的主机

  2. 服务器接收到该请求,对该请求进行分析和处理

  3. 服务器处理完成以后,返回对应的数据到用户机器

  4. 浏览器接收服务器返回的数据,并根据接收到的进行分析和处理

最简单的web服务器

我们需要搭建一个 http 的服务器,用于处理用户发送的 http 请求,在 node 中有 http 核心模块可以在很简单的几句话就帮我们启动一个服务器。

// 导入http模块:
var http = require('http');
// 创建http server,并传入回调函数:
var server = http.createServer(function (request, response) {
    // 回调函数接收request和response对象,
    console.log('有客户端请求了.....');
    // 将HTTP响应200写入response, 同时设置Content-Type: text/html:
    response.writeHead(200, {'Content-Type': 'text/html'});
    // 将HTTP响应的HTML内容写入response:
    response.write('<h1>hello World!</h1>');
    response.end();
});
// 让服务器监听8888端口:
server.listen(8888);
console.log('Server is running at http://127.0.0.1:8888/');

就这几行代码,我们就搭建了一个简单服务器,当我们将其运行,访问地址能在浏览器中显示我们熟悉的 hello World!,迈出了第一步。

下面我们来看看几个重要的地方:

核心方法

创建与监听

首先我们来看创建http server的代码:

  • var server = http.createServer([requestListener]) :创建并返回一个HTTP服务器对象

    • requestListener : 监听到客户端连接的回调函数
      在这里可以看到我们的回调函数是可选的,我们还可以使用事件监听器来进行,监听到客户端连接之后的操作,如:

server.on('request', function(req, res) {// do ....})
我们要在用户访问时做一些什么,都会在这里。

再看监听端口的代码,在这里我们只是简单的写了一下监听的端口,它的可选项都没有去设置

  • server.listen(port, [hostname], [backlog], [callback]) :监听客户端连接请求,只有当调用了 listen 方法以后,服务器才开始工作

    • port : 监听的端口

    • hostname : 主机名(IP/域名),可选

    • backlog : 连接等待队列的最大长度,可选

    • callback : 调用listen方法并成功开启监听以后,会触发一个 listening事件,callback将作为该事件的执行函数,可选

看完了创建与监听的方法,我们再看看看,我们在监听到客户端连接的回调函数 server.on('request', function(req, res) {// do ....}) 中看到有两个参数 request 和 response ,
在这两个参数中,我们可以去获得用户的当前请求一些信息,比如头信息,数据等待,还可以向该次请求的客户端输出返回响应,下面我们一起看看它里面的内容

request 对象

参数request对象是 http.IncomingMessage 的一个实例,通过它 ,我们可以获取到这次请求的一些信息,比如头信息,数据,url参数等等
这里简单的列一下最常见的:

  • httpVersion: 使用的http协议的版本

  • headers : 请求头信息中的数据

  • url : 请求的地址

  • method : 请求方式

response 对象

参数 response对象是 http.ServerResponse(这是一个由HTTP服务器内部创建的对象) 的一个实例,通过它 我们可以向该次请求的客户端输出返回响应。

  • response.writeHead(statusCode, [reasonPhrase], [headers]):向请求回复响应头,这个方法只能在当前请求中使用一次,并且必须在response.end()之前调用。

    • statusCode: 一个三位数的HTTP状态码, 例如 404

    • reasonPhrase:自行设置http响应状态码对应的原因短语

    • headers:响应头的内容

  • write(chunk, [encoding]) : 发送一个数据块到响应正文中 ,如果这个方法被调用但是 response.writeHead() 没有被调用,
    它将切换到默认header模式并更新默认的headers。chunk可以是字符串或者缓存。如果chunk 是一个字符串,
    第二个参数表明如何将这个字符串编码为一个比特流。默认的 encoding是'utf8'。

  • end([data], [encoding]): 当所有的正文和头信息发送完成以后,调用该方法告诉服务器数据已经全部发送完成了。
    这个方法在每次完成信息发送以后必须调用,并且是最后调用,如果指定了参数 data , 就相当于先调用 response.write(data, encoding) 之后再调用 response.end()

  • setHeader(name, value) : 为默认或者已存在的头设置一条单独的头信息:如果这个头已经存在于 将被送出的头中,将会覆盖原来的内容。如果我想设置更多的头, 就使用一个相同名字的字符串数组
    如:response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]);

看了那么多api,是时候实践一把了,我们再来对原来的代码进行一点改造~

// 导入http模块,url 模块
var http = require('http');
var url = require('url')
// 创建http server
var server = http.createServer();
server.on('request', function (req, res) {
    // 将HTTP响应200写入response, 同时设置Content-Type: text/html:
    res.writeHead(200, {
        'Content-Type': 'text/html'
    });
    var urlObj = url.parse(req.url);
    //根据用户访问的url不同展示不同的页面
    switch (urlObj.pathname){
        // 这是首页
        case '/':
            res.write('<h1>这是里首页</h1>');
            break;
        case '/user':
            res.write('<h1>这里是个人中心</h1>');
            break;
        default :
            res.write('<h1>你要找的页面不见了~</h1>');
            break;
    }
    // 将HTTP响应的HTML内容写入response:
    res.end();
});
server.listen(8888);
console.log('Server is running at http://127.0.0.1:8888/');

启动一下该js文件,并且通过不同的url不同的后缀,如 //user 去访问这个地址,看看浏览器的输出,应该已经变了。

这就是通过简单的的url处理,来实现的路由机制拉~

url 处理响应不同 html

当然这里我们再继续深入一下!结合 node 的文件系统(fs模块),使不同的url,直接读取不同的 html 文件,示例:

准备工作: 在当前文件目录下建立html文件夹, 并且新增文件 index.htmluser.html,内容自行发挥

var http = require('http');
var url = require('url');
var fs = require('fs');

var server = http.createServer();
// 读取我们当前文件所在的目录下的 html 文件夹
var HtmlDir = __dirname + '/html/';
server.on('request', function(req, res) {
    var urlObj = url.parse(req.url);
    switch (urlObj.pathname) {
        case '/':
            //首页
            sendData(HtmlDir + 'index.html', req, res);
            break;
        case '/user':
            //用户首页
            sendData(HtmlDir + 'user.html', req, res);
            break;
        default:
            //处理其他情况
            sendData(HtmlDir + 'err.html', req, res);
            break;
    }
});

/**
 * 读取html文件,响应数据,发送给浏览器
 * @param {String} file 文件路径
 * @param {Object} req request
 * @param {Object} res response 对象
 */
function sendData(file, req, res) {
    fs.readFile(file, function(err, data) {
        if (err) {
            res.writeHead(404, {
                'content-type': 'text/html;charset=utf-8'
            });
            res.end('<h1>你要找的页面不见了~</h1>');
        } else {
            res.writeHead(200, {
                'content-type': 'text/html;charset=utf-8'
            });
            res.end(data);
        }

    });
}
server.listen(8888);
console.log('Server is running at http://127.0.0.1:8888/');

运行文件,切换url,程序会将不同的页面返回。大家自己去试试吧!

这是对于 node 的 http模块 的学习,理解有限,如果有错误之处,请指出,谢谢!

本文地址:https://guowenfh.github.io/2016/10/15/node-http/

查看原文

赞 2 收藏 4 评论 0

三省吾身丶丶 发布了文章 · 2016-10-24

Node.js的process 对象与 Buffer 类

在上一篇博客中,我们一起对于 node 的模块机制有了一点点的了解,当时我们就知道了在 node 中的全局对象是 global 而不是浏览器中的 window 它们有一部分属性方法是相同的,
比如:clearInterval / clearTimeout / setInterval / setTimeout / console。他们的用法和在浏览器中的用法是一样的,接下来我们就来看看那些在 node 中所特有全局属性和方法。

我们先来稍微跑个题,看看与上一个篇博客的中介绍的 模块系统相关 的两个属性,还是可以模块的路径有关:

  • __filename : 返回 当前模块文件解析后的绝对路径

  • __dirname : 返回 当前模块文件所在目录 解析后的绝对路径

注意:它们可以直接使用,但是实际上并非全局的,而是在模块作用域下的

直接在文件内输入,运行即可输出:

console.log(__filename);
console.log(__dirname);

process 对象

process 对象的属性和方法

process对象是一个全局对象,可以在任何地方都能访问到他,通过这个对象提供的属性和方法,使我们可以对当前运行的程序的进程进行访问和控制

  • process.argv :一个包含命令行参数的数组。第一个元素会是 'node', 第二个元素将是 .Js 文件的名称。接下来的元素依次是命令行传入的参数。

  • process.execPath : 开启当前进程的绝对路径

  • process.env : 返回用户环境信息的对象

  • process.version : 返回node版本信息

  • process.versions : 返回node以及node依赖包版本信息

  • process.pid : 当前进程的pid

  • process.title : 当前进程的显示名称(Getter/Setter)

  • process.arch : 返回当前CPU处理器架构 arm/ia32/x64

  • process.platform : 返回当前操作系统平台

  • process.cwd() : 返回当前进程的工作目录

  • process.chdir(directory) : 改变进程的当前进程的工作目录,若操作失败则抛出异常。

  • process.memoryUsage() : 返回node进程的内存使用情况,单位是byte

  • process.exit(code) : 退出

  • process.kill(pid) : 向进程发送信息

  • 标准输入/输出流(IO):stdin 和stdout提供了操作输入数据和输出数据的方法,我们也通常称为IO操作

这里主要是对于标准输入/输出流(IO)的理解,放上一个中文维基百科的解释:标准输入/输出流,在这里关于 stdout就简单举例 console.log实现:

console.log = function(d) {
  process.stdout.write(d + '\n');
};

再看一个stdin的简单示例:

process.stdout.write('请输入内容:');
//默认情况下,输入流是关闭的,要监听处理输入流数据,首先要开启输入流
process.stdin.resume();
//用于监听用户的输入数据12
process.stdin.on('data', function(chunk) {
     console.log('用户输入了:' + chunk);
});

shell 执行该文件后,输入内容并回车,会把你输入的内容打印出来

Buffer 类

什么 buffer 类?

Buffer 类是一个全局变量类型,用于操作二进制数据流的类。我们在操作文件或者网络数据的时候,其实操作的就是二进制数据流,Buffer 类就是 Node 为了我们更好的操 作二进制数据而创建的类

  • new Buffer(size): Number 类型 配一个新的 buffer 大小是 size 的8位字节.

  • new Buffer(array): Array 类型分配一个新的 buffer 使用一个8位字节 array 数组.

  • new Buffer(str, [encoding]):分配一个新的 buffer ,其中包含着给定的 str 字符串. encoding 编码方式默认是:'utf8'.

    • str : String类型 - 需要存入buffer的string字符串.

    • encoding : String类型 - 使用什么编码方式,参数可选.

Buffer 方法学习

  1. buf.length:这个buffer的bytes大小。注意这未必是这buffer里面内容的大小。length 的依据是buffer对象所分配的内存数值,它不会随着这个buffer对象内容的改变而改变。

  2. buf.write(string, [offset], [length], [encoding]) : 根据参数 offset 偏移量和指定的encoding编码方式,length 长度是将要写入的字符串的bytes大小,将参数 string 数据写入buffer。

    • string : String类型 - 将要被写入 buffer 的数据

    • offset : Number类型, 可选参数, 默认: 0

    • length : Number类型, 可选参数, 默认: buffer.length - offset

    • encoding : String类型, 可选参数, 默认: 'utf8'

var str = 'buffer';
console.log(new Buffer(str));
var buf = new Buffer(6);
buf.write(str,1,3);
console.log(buf);
// <Buffer 62 75 66 66 65 72>
// <Buffer 62 62 75 66 01 00>

这里我们指定了 offset 和 length ,分别为1和3,所以可以看到两次在输出时,62,75,66,这部分是相同的,并且第二次输出它的位置偏移了1个位置。

  1. buf.toString([encoding], [start], [end]):根据 encoding参数(默认是'utf8')返回一个解码的 string 类型。还会根据传入的参数 start (默认是0)和 end (默认是 buffer.length)作为取值范围。

    • encoding: String类型, 可选参数, 默认: 'utf8'

    • start: Number类型, 可选参数, 默认: 0

    • end: Number类型, 可选参数, 默认: buffer.length

var str1 = 'buffer';
var bf1  = new Buffer(str1);
console.log(bf1.toString());
console.log(bf1.toString('utf8',1,4));

var str2 = '二进制';
var bf2  = new Buffer(str2);
console.log(bf2);
console.log(bf2.toString('utf8',1));

// buffer
// uff
// <Buffer e4 ba 8c e8 bf 9b e5 88 b6>
// ��进制

在这里通过例子和输出,就可以发现 toString() 方法看到在截取时 取左不取右,所以第一例子输出了 uff
第二例子则可以说明,中文使用3个字节来存储,所以在偏移量为一时读取到的 ba 8c被输出为乱码,当偏移量为3时,正常。

  1. buf.toJSON():返回一个 JSON表示的Buffer实例。JSON.stringify 将会默认调用来字符串序列化这个Buffer实例。如:{ type: 'Buffer', data: [ 98, 117, 102, 102, 101, 114 ] }

  2. buf.slice([start], [end]):返回一个新的buffer,这个 buffer 将会和老的 buffer 引用相同的内存地址,只是偏移和裁剪了索引,方法类似于数组。 负的索引是从 buffer 尾部开始计算的。

  3. buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd]):进行 buffer 的拷贝,源和目标可以是重叠的。 targetStart 目标开始偏移 和sourceStart源开始偏移 默认都是 0. sourceEnd 源结束位置偏移默认是源的长度 buffer.length.
    如果传递的值是undefined/NaN 或者是 out of bounds 超越边界的,就将设置为他们的默认值。(译者:这个默认值下面有的例子有说明)

    • targetBuffer: Buffer 类型对象 - 将要进行拷贝的Buffer

    • targetStart: Number类型, 可选参数, 默认: 0

    • sourceStart: Number类型, 可选参数, 默认: 0

    • sourceEnd: Number类型, 可选参数, 默认: buffer.length

类/静态方法

  1. Buffer.isEncoding(encoding):用来测试给定的编码字符串,如果给定的编码 encoding 是有效的,返回 true,否则返回 false :Buffer.isEncoding('utf8')

  2. Buffer.isBuffer(obj):测试这个 obj 是否是一个 Buffer.

  3. Buffer.byteLength(string, [encoding]):将会返回这个字符串真实 byte 长度。 encoding 编码默认是: 'utf8'. 这个和 String.prototype.length 是不一样的,因为那个方法返回这个字符串中有几个字符的数量。

  4. Buffer.concat(list, [totalLength]): 返回一个保存着将传入 buffer 数组中所有 buffer 对象拼接在一起的 buffer 对象。其实就是将数组中所有的 buffer 实例通过复制拼接在一起

    • list : {Array}数组类型,Buffer 数组,用于被连接。

    • totalLength : {Number}类型 上述 Buffer 数组的所有Buffer的总大小。(数组里 Buffer 实例的大小总和)

    这里我们再回头来看看,在上半部分中的介绍process 对象,说的标准输入输出流的时候,我们有这样一个例子:

process.stdout.write('请输入内容:');
//默认情况下,输入流是关闭的,要监听处理输入流数据,首先要开启输入流
process.stdin.resume();
//用于监听用户的输入数据12
process.stdin.on('data', function(chunk) {
     console.log('用户输入了:' + chunk);
});

我们运行这一段代码,在命令行中输入:process,可以看到有下面这样的结果:

请输入内容:process
用户输入了:process

在这里看来,我们接收到的用户输入 chunk ,就是一个字符串,实际上它是一个buffer对象,我们重写 porcess.stdin 方法,

process.stdin.on('data', function(chunk) {
     console.log(chunk);
});

输入同样的内容打印出来的是 <Buffer 70 72 6f 63 65 73 73 0a> ,可以知道在标准输入输出流中,实际上也是进行二进制的数据传输。
在第一个示例中 console.log('用户输入了:' + chunk); 因为 chunk与字符串相链接,所以 它实际上自动调用了 toString() 方法。

这是对于 process 对象与 Buffer 类的简单学习,理解较为粗浅,如有错误之处请指出,谢谢!

本文地址:https://guowenfh.github.io/2016/10/15/node-global-object/

查看原文

赞 0 收藏 0 评论 0

三省吾身丶丶 发布了文章 · 2016-10-22

Node.js 模块系统

什么是模块?

  • 在 node 中,文件和模块是一一对应的,也就是一个文件就是一个模块

  • 每个模块都有自己的作用域

  • 我们使用 var 来申明的一个变量,他并不是全局的,而是属于当前模块下

模块的加载与使用

模块的引入

在 node 中,我们使用 require('模块') 的形式将一个模块引入到我们的当前的文件中来。关于 require 路径的写法也有如下几种:

  • 模块路径可以是一个以 / 开头,表示一个 绝对路径

  • 模块路径以 ./ 开头,表示当前目录出发的 相对路径

  • 模块路径如果没有以 / 或者 ./ 开头,那么这个模块要么是 核心模块 要么是通过 npm 安装在 node_modules 文件夹下的。

看到上面的不同路径写法, 我们就想起来在 web 中引入当前文件夹中的文件时是不需要加上./的, 但在 node 中使用 .// 开头的路径和不使用时,有着很大的差别。

模块的加载机制

  1. 首先按照加载的模块的文件名称进行查找

  2. 如果没有找到,则会在模块文件名称后加上 .js 的后缀,进行查找

  3. 如果还没有找到,则会在文件名称后加上 .json 的后缀,进行查找

  4. 如果还没有,则会在文件名称后加上 .node 的后缀,进行查找

  5. 最终未找到,抛出异常

模块数据的使用

与浏览器中不同 , 在 node 中,每一个模块都有自己的作用域,在模块中使用 var 申明的变量的作用域范围是在该模块内,而不是 node 全局的。
但是你忘记了使用 var 进行变量声明,恭喜你这时和浏览器中忘记使用的效果是一致的。该变量也将挂在全局对象上。
只不过在 node中,全局对象是global,而不是浏览器中的window

下面看一个简单的例子:

我们从两个文件 a.jsb.js 就可以简单的看到 node 的声明变量的作用域。

a.js:

var a = '这是a声明的变量';
console.log('这是a.js模块');

b.js:

require('./2.js');
console.log('bbbb');
console.log(a);
console.log(global.a);

node b.js 输出结果:

这是2.js模块
bbbb
console.log(a); //ReferenceError: a is not defined

至于去掉 var 之后会是怎么样,大家自己试试吧 ^ ^

暴露模块API

module、module.exports、exports

如同上面代码中所展示的,在 nodejs 中不能直接去拿到另一个模块中的变量函数等等。要让模块暴露一个API成为 require调用的返回值,我们就需要通过 module.exports 或者 exports 对外提供模块内部变量的访问。

默认情况下,每个模块都会暴露处一个空对象,如果你想要在该对象上添加属性,那么简单的使用 exports 即可。

看这个例子:

module_a.js:

console.log(exports === module.exports)
exports.name = 'module_a.js'
exports.data = 'this is module_a.js data'
exports.getDate = function() {
    return new Date();
}

module_b.js:

var a = require('./module_a.js')
console.log(a.name)
console.log(a.data)
console.log(a.getDate())

node module_b.js输出:

true
module_a.js
this is module_a.js data
Thu Sep 22 2016 22:23:59 GMT+0800 (CST)

可以看到这里实际上在模块内部 exports 就是对于module.exports的引用,并且在使用require引入模块之后,a 是一个对象。可见外部模块通过 require(模块) 方法加载模块,该函数返回的就是被加载模块的 module.exports 对象

如果这样在 module.exports 对象上一个一个添加属性还满足不了你的需求,你还可以将 module.exports 彻底重写。我们将上面的两个文件改写:

module_a.js:

module.exports = function(text) {
    console.log('printf: ' + text);
}

module_b.js:

var a = require('./module_a.js')
a('ok');

node module_b.js输出:printf: ok

这样有一个需要注意的地方是:直接覆盖 exports 或者 module.exports让模块导出一个值,这样做会破坏 exportsmodule.exports 的引用关系。

这是对于 node 的模块系统的学习,理解有限,如果有错误之处,请指出,谢谢!

本文地址:https://guowenfh.github.io/2016/10/15/node-module-system/

查看原文

赞 2 收藏 9 评论 0

三省吾身丶丶 回答了问题 · 2016-08-13

我做的移动端页面为什么有这种问题

文字是不使用rem的,直接使用px

关注 3 回答 5

三省吾身丶丶 关注了问题 · 2016-08-13

我做的移动端页面为什么有这种问题

rem布局
百分百布局

为什么rem布局文字那么小,整个页面看起来不协调?

关注 3 回答 5

三省吾身丶丶 发布了文章 · 2016-08-07

详解 ESLint 规则,规范你的代码

因为前几天发现 CSDN上有直接把我文章 复制过去然后标原创的情况,以后会统一在博客头部加入原文链接~

本文个人博客原址

在很久之前就想通过工具来规范自己的代码风格,减少程序出错的概率,如果看过我的一个前端程序猿的Sublime Text3的自我修养,这篇博客的朋友,肯定知道在当时我使用SublimeLinter-jshint插件来规范风格,但是实际上一直懒癌发作也没去看它的文档,使用着它默认的规则。不过现在是时候切换到 ESLint 了!

作为一个有理想有抱负的前端工程师,只是使用默认规则,而不是看完文档了然于心,显然是不行滴 ^_^.. 团队协作时,若是团队的代码风格统一,能够大大减少沟通成本。(其实面试时和老大聊到代码规范,当时就说到用 JSHint ,或者 ESLint 等工具来统一的。。。这也算是我来填一个坑吧~)

好了,前情摘要就到这,我们开始吧!

什么是 ESLint ?

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。在许多方面,它和 JSLint、JSHint 相似,除了少数的例外:

  • ESLint 使用 Espree 解析 JavaScript。

  • ESLint 使用 AST 去分析代码中的模式

  • ESLint 是完全插件化的。每一个规则都是一个插件并且你可以在运行时添加更多的规则。

以上来自官网。不想再说下去,反正就是一个代码风格检测工具就对了

如何使用

  1. 安装

    npm install -g eslint
  2. 如果你第一次使用 ESLint,你必须使用 --init 命令新建一个配置文件:

    eslint --init
  3. 使用 ESLint 检测任何 JavaScript 文件:

    eslint test.js test2.js
  4. ESLint 中一些规则运行命令它可以帮你自动修复

    eslint test.js --fix

为了可以更直观的反馈,可能更多的会直接安装编辑器插件来进行错误提示,以Sublime 为例:

package control 中 ,先安装在 SublimeLinter ,再安装 SublimeLinter-contrib-eslint 在项目目录下新建 .eslintrc 文件,自定义规则。
重新载入文件应该就生效了(不生效的话 Ctrl+Shift+P 调用命令面板 找到sublimelinter: toggle linter 设置生效就好了),其他的编辑器异曲同工,就不再说了。

关于在如何在构建工具中使用,在这里不做说明(官网有)

规则定义

ESLint 支持几种格式的配置文件,如果同一个目录下有多个配置文件,ESLint 只会使用一个。优先级顺序如下:

  1. JavaScript - 使用 .eslintrc.js 然后输出一个配置对象。

  2. YAML - 使用 .eslintrc.yaml 或 .eslintrc.yml 去定义配置的结构。

  3. JSON -使用 .eslintrc.json 去定义配置的结构,ESLint 的 JSON 文件允许 JavaScript 风格的注释。

  4. Deprecated -使用 .eslintrc,可以使 JSON 也可以是 YAML。

  5. package.json - 在 package.json 里创建一个 eslintConfig属性,在那里定义你的配置。

下面就是规则啦,本人使用了.eslintrc格式,说明也在里面:

{
    // 环境定义了预定义的全局变量。
    "env": {
        //环境定义了预定义的全局变量。更多在官网查看
        "browser": true,
        "node": true,
        "commonjs": true,
        "amd": true,
        "es6":true,
        "mocha":true
    },
    // JavaScript 语言选项
    "parserOptions": {
        // ECMAScript 版本
        "ecmaVersion": 6,
        "sourceType": "script",//module
        // 想使用的额外的语言特性:
        "ecmaFeatures": {
            // 允许在全局作用域下使用 return 语句
            "globalReturn":true,
            // impliedStric
            "impliedStrict":true,
            // 启用 JSX
            "jsx":true
        }
    },
    /**
     *  "off" 或 0 - 关闭规则
     *  "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出),
     *  "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
     */
    "rules": {

        ////////////////
        // 可能的错误  //
        ////////////////

        // 禁止条件表达式中出现赋值操作符
        "no-cond-assign": 2,
        // 禁用 console
        "no-console": 0,
        // 禁止在条件中使用常量表达式
        // if (false) {
        //     doSomethingUnfinished();
        // } //cuowu
        "no-constant-condition": 2,
        // 禁止在正则表达式中使用控制字符 :new RegExp("\x1f")
        "no-control-regex": 2,
        // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
        // always-multiline:多行模式必须带逗号,单行模式不能带逗号
        "comma-dangle": [1, "always-multiline"],
        // 禁用 debugger
        "no-debugger": 2,
        // 禁止 function 定义中出现重名参数
        "no-dupe-args": 2,
        // 禁止对象字面量中出现重复的 key
        "no-dupe-keys": 2,
        // 禁止重复的 case 标签
        "no-duplicate-case": 2,
        // 禁止空语句块
        "no-empty": 2,
        // 禁止在正则表达式中使用空字符集 (/^abc[]/)
        "no-empty-character-class": 2,
        // 禁止对 catch 子句的参数重新赋值
        "no-ex-assign": 2,
        // 禁止不必要的布尔转换
        "no-extra-boolean-cast": 2,
        //  禁止不必要的括号 //(a * b) + c;//报错
        "no-extra-parens": 0,
        // 禁止不必要的分号
        "no-extra-semi": 2,
        // 禁止对 function 声明重新赋值
        "no-func-assign": 2,
        //  禁止在嵌套的块中出现 function 或 var 声明
        "no-inner-declarations": [2, "functions"],
        // 禁止 RegExp 构造函数中无效的正则表达式字符串
        "no-invalid-regexp": 2,
        // 禁止在字符串和注释之外不规则的空白
        "no-irregular-whitespace": 2,
        // 禁止在 in 表达式中出现否定的左操作数
        "no-negated-in-lhs": 2,
        //   禁止把全局对象 (Math 和 JSON) 作为函数调用  错误:var math = Math();
        "no-obj-calls": 2,
        // 禁止直接使用 Object.prototypes 的内置属性
        "no-prototype-builtins":0,
        // 禁止正则表达式字面量中出现多个空格
        "no-regex-spaces": 2,
        // 禁用稀疏数组
        "no-sparse-arrays": 2,
        // 禁止出现令人困惑的多行表达式
        "no-unexpected-multiline": 2,
        // 禁止在return、throw、continue 和 break语句之后出现不可达代码
        /*
            function foo() {
            return true;
            console.log("done");
            }//错误
        */
        "no-unreachable": 2,
        // 要求使用 isNaN() 检查 NaN
        "use-isnan": 2,
        // 强制使用有效的 JSDoc 注释
        "valid-jsdoc": 1,
        // 强制 typeof 表达式与有效的字符串进行比较
        // typeof foo === "undefimed" 错误
        "valid-typeof": 2,


        //////////////
        // 最佳实践 //
        //////////////

        // 定义对象的set存取器属性时,强制定义get
        "accessor-pairs": 2,
        // 强制数组方法的回调函数中有 return 语句
        "array-callback-return":0,
        // 强制把变量的使用限制在其定义的作用域范围内
        "block-scoped-var": 0,
        // 限制圈复杂度,也就是类似if else能连续接多少个
        "complexity": [2, 9],
        //  要求 return 语句要么总是指定返回的值,要么不指定
        "consistent-return": 0,
        // 强制所有控制语句使用一致的括号风格
        "curly": [2, "all"],
        // switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
        "default-case": 2,
        // 强制object.key 中 . 的位置,参数:
        //      property,'.'号应与属性在同一行
        //      object, '.' 号应与对象名在同一行
        "dot-location": [2, "property"],
        // 强制使用.号取属性
        //    参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
        //                          false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}]
        //           allowPattern:  当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}]
        "dot-notation": [2, { "allowKeywords": false }],
        // 使用 === 替代 == allow-null允许null和undefined==
        "eqeqeq": [2, "allow-null"],
        // 要求 for-in 循环中有一个 if 语句
        "guard-for-in": 2,
        // 禁用 alert、confirm 和 prompt
        "no-alert": 0,
        // 禁用 arguments.caller 或 arguments.callee
        "no-caller": 2,
        // 不允许在 case 子句中使用词法声明
        "no-case-declarations":2,
        // 禁止除法操作符显式的出现在正则表达式开始的位置
        "no-div-regex": 2,
        // 禁止 if 语句中有 return 之后有 else
        "no-else-return": 0,
        // 禁止出现空函数.如果一个函数包含了一条注释,它将不会被认为有问题。
        "no-empty-function":2,
        // 禁止使用空解构模式no-empty-pattern
        "no-empty-pattern":2,
        // 禁止在没有类型检查操作符的情况下与 null 进行比较
        "no-eq-null": 1,
        // 禁用 eval()
        "no-eval": 2,
        // 禁止扩展原生类型
        "no-extend-native": 2,
        // 禁止不必要的 .bind() 调用
        "no-extra-bind": 2,
        // 禁用不必要的标签
        "no-extra-label:":0,
        // 禁止 case 语句落空
        "no-fallthrough": 2,
        // 禁止数字字面量中使用前导和末尾小数点
        "no-floating-decimal": 2,
        // 禁止使用短符号进行类型转换(!!fOO)
        "no-implicit-coercion":0,
        // 禁止在全局范围内使用 var 和命名的 function 声明
        "no-implicit-globals":1,
        // 禁止使用类似 eval() 的方法
        "no-implied-eval": 2,
        // 禁止 this 关键字出现在类和类对象之外
        "no-invalid-this":0,
        // 禁用 __iterator__ 属性
        "no-iterator": 2,
        //  禁用标签语句
        "no-labels": 2,
        // 禁用不必要的嵌套块
        "no-lone-blocks": 2,
        // 禁止在循环中出现 function 声明和表达式
        "no-loop-func":1,
        // 禁用魔术数字(3.14什么的用常量代替)
        "no-magic-numbers":[1,{ "ignore": [0,-1,1] }],
        // 禁止使用多个空格
        "no-multi-spaces": 2,
        // 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
        "no-multi-str": 2,
        // 禁止对原生对象赋值
        "no-native-reassign": 2,
        // 禁止在非赋值或条件语句中使用 new 操作符
        "no-new": 2,
        // 禁止对 Function 对象使用 new 操作符
        "no-new-func": 0,
        // 禁止对 String,Number 和 Boolean 使用 new 操作符
        "no-new-wrappers": 2,
        // 禁用八进制字面量
        "no-octal": 2,
        // 禁止在字符串中使用八进制转义序列
        "no-octal-escape": 2,
        // 不允许对 function 的参数进行重新赋值
        "no-param-reassign": 0,
        // 禁用 __proto__ 属性
        "no-proto": 2,
        // 禁止使用 var 多次声明同一变量
        "no-redeclare": 2,
        // 禁用指定的通过 require 加载的模块
        "no-return-assign": 0,
        // 禁止使用 javascript: url
        "no-script-url": 0,
        // 禁止自我赋值
        "no-self-assign":2,
        // 禁止自身比较
        "no-self-compare": 2,
        // 禁用逗号操作符
        "no-sequences": 2,
        // 禁止抛出非异常字面量
        "no-throw-literal": 2,
        // 禁用一成不变的循环条件
        "no-unmodified-loop-condition":2,
        // 禁止出现未使用过的表达式
        "no-unused-expressions": 0,
        // 禁用未使用过的标签
        "no-unused-labels":2,
        // 禁止不必要的 .call() 和 .apply()
        "no-useless-call":2,
        // 禁止不必要的字符串字面量或模板字面量的连接
        "no-useless-concat":2,
        // 禁用不必要的转义字符
        "no-useless-escape":0,
        // 禁用 void 操作符
        "no-void": 0,
        // 禁止在注释中使用特定的警告术语
        "no-warning-comments": 0,
        // 禁用 with 语句
        "no-with": 2,
        // 强制在parseInt()使用基数参数
        "radix": 2,
        // 要求所有的 var 声明出现在它们所在的作用域顶部
        "vars-on-top": 0,
        // 要求 IIFE 使用括号括起来
        "wrap-iife": [2, "any"],
        // 要求或禁止 “Yoda” 条件
        "yoda": [2, "never"],
        // 要求或禁止使用严格模式指令
        "strict": 0,


        //////////////
        //  变量声明 //
        //////////////

        // 要求或禁止 var 声明中的初始化(初值)
        "init-declarations":0,
        // 不允许 catch 子句的参数与外层作用域中的变量同名
        "no-catch-shadow": 0,
        // 禁止删除变量
        "no-delete-var": 2,
        // 不允许标签与变量同名
        "no-label-var": 2,
        // 禁用特定的全局变量
        "no-restricted-globals":0,
        // 禁止 var 声明 与外层作用域的变量同名
        "no-shadow": 0,
        // 禁止覆盖受限制的标识符
        "no-shadow-restricted-names": 2,
        // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
        "no-undef": 2,
        // 禁止将变量初始化为 undefined
        "no-undef-init": 2,
        // 禁止将 undefined 作为标识符
        "no-undefined": 0,
        // 禁止出现未使用过的变量
        "no-unused-vars": [2, { "vars": "all", "args": "none" }],
        // 不允许在变量定义之前使用它们
        "no-use-before-define": 0,

        //////////////////////////
        // Node.js and CommonJS //
        //////////////////////////

        // require return statements after callbacks
        "callback-return":0,
        // 要求 require() 出现在顶层模块作用域中
        "global-require": 1,
        // 要求回调函数中有容错处理
        "handle-callback-err": [2, "^(err|error)$"],
        // 禁止混合常规 var 声明和 require 调用
        "no-mixed-requires": 0,
        // 禁止调用 require 时使用 new 操作符
        "no-new-require": 2,
        // 禁止对 __dirname 和 __filename进行字符串连接
        "no-path-concat": 0,
        // 禁用 process.env
        "no-process-env": 0,
        // 禁用 process.exit()
        "no-process-exit": 0,
        // 禁用同步方法
        "no-sync": 0,

        //////////////
        // 风格指南  //
        //////////////

        // 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
        "array-bracket-spacing": [2, "never"],
        // 禁止或强制在单行代码块中使用空格(禁用)
        "block-spacing":[1,"never"],
        //强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab,
        // if while function 后面的{必须与if在同一行,java风格。
        "brace-style": [2, "1tbs", { "allowSingleLine": true }],
        // 双峰驼命名格式
        "camelcase": 2,
        // 控制逗号前后的空格
        "comma-spacing": [2, { "before": false, "after": true }],
        // 控制逗号在行尾出现还是在行首出现 (默认行尾)
        // http://eslint.org/docs/rules/comma-style
        "comma-style": [2, "last"],
        //"SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平
        // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
        "computed-property-spacing": [2, "never"],
        // 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
        // e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值
        "consistent-this":  [1,"that"],
        // 强制使用命名的 function 表达式
        "func-names": 0,
        // 文件末尾强制换行
        "eol-last": 2,
        "indent": [2, 4, { "SwitchCase": 1 }],
        // 强制在对象字面量的属性中键和值之间使用一致的间距
        "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
        // 强制使用一致的换行风格
        "linebreak-style": [1,"unix"],
        // 要求在注释周围有空行      ( 要求在块级注释之前有一空行)
        "lines-around-comment": [1,{"beforeBlockComment":true}],
        //  强制一致地使用函数声明或函数表达式,方法定义风格,参数:
        //    declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"]
        //    expression:强制使用方法表达式的方式,var f = function() {}  e.g [2, "expression"]
        //    allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }]
        "func-style": 0,
        // 强制回调函数最大嵌套深度 5层
        "max-nested-callbacks": [1,5],
        // 禁止使用指定的标识符
        "id-blacklist":0,
        // 强制标识符的最新和最大长度
        "id-length":0,
        // 要求标识符匹配一个指定的正则表达式
        "id-match":0,
        // 强制在 JSX 属性中一致地使用双引号或单引号
        "jsx-quotes":0,
        // 强制在关键字前后使用一致的空格 (前后腰需要)
        "keyword-spacing":2,
        // 强制一行的最大长度
        "max-len":[1,200],
        // 强制最大行数
        "max-lines":0,
        // 强制 function 定义中最多允许的参数数量
        "max-params":[1,7],
        // 强制 function 块最多允许的的语句数量
        "max-statements":[1,200],
        // 强制每一行中所允许的最大语句数量
        "max-statements-per-line":0,
        // 要求构造函数首字母大写  (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
        "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
        // 要求调用无参构造函数时有圆括号
        "new-parens": 2,
        // 要求或禁止 var 声明语句后有一行空行
        "newline-after-var": 0,
        // 禁止使用 Array 构造函数
        "no-array-constructor": 2,
        // 禁用按位运算符
        "no-bitwise":0,
        // 要求 return 语句之前有一空行
        "newline-before-return":0,
        // 要求方法链中每个调用都有一个换行符
        "newline-per-chained-call":1,
        // 禁用 continue 语句
        "no-continue": 0,
        // 禁止在代码行后使用内联注释
        "no-inline-comments": 0,
        // 禁止 if 作为唯一的语句出现在 else 语句中
        "no-lonely-if": 0,
        // 禁止混合使用不同的操作符
        "no-mixed-operators":0,
        // 不允许空格和 tab 混合缩进
        "no-mixed-spaces-and-tabs": 2,
        // 不允许多个空行
        "no-multiple-empty-lines": [2, { "max": 2 }],
        // 不允许否定的表达式
        "no-negated-condition":0,
        // 不允许使用嵌套的三元表达式
        "no-nested-ternary": 0,
        // 禁止使用 Object 的构造函数
        "no-new-object": 2,
        // 禁止使用一元操作符 ++ 和 --
        "no-plusplus":0,
        // 禁止使用特定的语法
        "no-restricted-syntax":0,
        // 禁止 function 标识符和括号之间出现空格
        "no-spaced-func": 2,
        // 不允许使用三元操作符
        "no-ternary": 0,
        //  禁用行尾空格
        "no-trailing-spaces": 2,
        // 禁止标识符中有悬空下划线_bar
        "no-underscore-dangle": 0,
        // 禁止可以在有更简单的可替代的表达式时使用三元操作符
        "no-unneeded-ternary": 2,
        // 禁止属性前有空白
        "no-whitespace-before-property":0,
        // 强制花括号内换行符的一致性
        "object-curly-newline":0,
        // 强制在花括号中使用一致的空格
        "object-curly-spacing": 0,
        // 强制将对象的属性放在不同的行上
        "object-property-newline":0,
        // 强制函数中的变量要么一起声明要么分开声明
        "one-var": [2, { "initialized": "never" }],
        // 要求或禁止在 var 声明周围换行
        "one-var-declaration-per-line":0,
        // 要求或禁止在可能的情况下要求使用简化的赋值操作符
        "operator-assignment": 0,
        // 强制操作符使用一致的换行符
        "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
        // 要求或禁止块内填充
        "padded-blocks": 0,
        // 要求对象字面量属性名称用引号括起来
        "quote-props": 0,
        // 强制使用一致的反勾号、双引号或单引号
        "quotes": [2, "single", "avoid-escape"],
        // 要求使用 JSDoc 注释
        "require-jsdoc":1,
        // 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
        "semi": [2, "always"],
        // 强制分号之前和之后使用一致的空格
        "semi-spacing": 0,
        // 要求同一个声明块中的变量按顺序排列
        "sort-vars": 0,
        // 强制在块之前使用一致的空格
        "space-before-blocks": [2, "always"],
        // 强制在 function的左括号之前使用一致的空格
        "space-before-function-paren": [2, "always"],
        // 强制在圆括号内使用一致的空格
        "space-in-parens": [2, "never"],
        // 要求操作符周围有空格
        "space-infix-ops": 2,
        // 强制在一元操作符前后使用一致的空格
        "space-unary-ops": [2, { "words": true, "nonwords": false }],
        // 强制在注释中 // 或 /* 使用一致的空格
        "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
        // 要求或禁止 Unicode BOM
        "unicode-bom": 0,
        //  要求正则表达式被括号括起来
        "wrap-regex": 0,

        //////////////
        // ES6.相关 //
        //////////////

        // 要求箭头函数体使用大括号
        "arrow-body-style": 2,
        // 要求箭头函数的参数使用圆括号
        "arrow-parens": 2,
        "arrow-spacing":[2,{ "before": true, "after": true }],
        // 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
        "constructor-super": 0,
        // 强制 generator 函数中 * 号周围使用一致的空格
        "generator-star-spacing": [2, { "before": true, "after": true }],
        // 禁止修改类声明的变量
        "no-class-assign":2,
        // 不允许箭头功能,在那里他们可以混淆的比较
        "no-confusing-arrow":0,
        // 禁止修改 const 声明的变量
        "no-const-assign":2,
        // 禁止类成员中出现重复的名称
        "no-dupe-class-members":2,
        // 不允许复制模块的进口
        "no-duplicate-imports":0,
        // 禁止 Symbol  的构造函数
        "no-new-symbol":2,
        // 允许指定模块加载时的进口
        "no-restricted-imports":0,
        // 禁止在构造函数中,在调用 super() 之前使用 this 或 super
        "no-this-before-super": 2,
        // 禁止不必要的计算性能键对象的文字
        "no-useless-computed-key":0,
        // 要求使用 let 或 const 而不是 var
        "no-var": 0,
        // 要求或禁止对象字面量中方法和属性使用简写语法
        "object-shorthand": 0,
        // 要求使用箭头函数作为回调
        "prefer-arrow-callback":0,
        // 要求使用 const 声明那些声明后不再被修改的变量
        "prefer-const": 0,
        // 要求在合适的地方使用 Reflect 方法
        "prefer-reflect":0,
        // 要求使用扩展运算符而非 .apply()
        "prefer-spread":0,
        // 要求使用模板字面量而非字符串连接
        "prefer-template":0,
        // Suggest using the rest parameters instead of arguments
        "prefer-rest-params":0,
        // 要求generator 函数内有 yield
        "require-yield":0,
        // enforce spacing between rest and spread operators and their expressions
        "rest-spread-spacing":0,
        // 强制模块内的 import 排序
        "sort-imports":0,
        // 要求或禁止模板字符串中的嵌入表达式周围空格的使用
        "template-curly-spacing":1,
        // 强制在 yield* 表达式中 * 周围使用空格
        "yield-star-spacing":2
    }
}

更多内容,以及每一项的配置详情可以在 官网查看

查看原文

赞 13 收藏 65 评论 1

三省吾身丶丶 发布了文章 · 2016-08-03

Javascript中this与闭包学习笔记

博客原址

理解 Javascript中的this

基于不同的调用方式this的指向也会有所不同,调用方式大致有如下几种:

调用方式表达式
构造函数调用new Foo();
对象方法调用o.method();
函数直接调用foo();
call/apply/bindfunc.call(o);

现在就来看看这些不同的调用模式,this的指向会有怎么样的区别:

构造函数调用模式

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName  = function(){
        console.info(this.name);
    };
}
var allen = new Person("allen",12);
console.info(allen);//{name: "allen", age: 12};...

通过这样的代码可以很清楚的的看出,构造函数 Person 内部的this指向被创建的调用对象 allen

对象方法调用

通过上面的代码很明显我们创建了一个 allen 对象,其中有一个 sayName 方法, 直接打印 this.name ,现在我们就来看一下它会输出什么。

allen.sayName();//allen

很明显,这里函数中的this指向allen对象本身。

函数直接调用

先来看一段代码

function add(a, b) {
    return a + b;
}
var myNumber = {
    value: 1,
    double: function() {
        function handle() {
            this.value = add(this.value, this.value);
        }
        handle();
    }
};
console.info(myNumber.value);//1
myNumber.double();
console.info(myNumber.value);//1

解析: 首先我们定义了一个全局函数add用于加法运算,接着我们定义了一个对象,有一属性value为1,还有一个方法的目的是让value值乘以二。我们在函数内嵌套定义了一个函数handle,调用add方法并且执行。但是在调用函数值执行之后并没有达到我们想要的效果。这是为什么呢?
如何你打开chrome调试工具并打下断点会发现在handle函数内部的this会指向window!
由此可以发现,在函数内部创建的函数,在这个函数调用时,函数内部的this会指向window而不是外部的函数

下面就就可以看一下常见的两个方案:

// 取消 handle函数的定义,直接在对象的方法中使用this
double2: function() {
    this.value = add(this.value, this.value);
},
// 使用变量保存外部函数的this。
double3: function() {
    var that = this;
    function handle() {
        that.value = add(that.value, that.value);
    }
    handle();
}

使用 call/applybind 手动改变 this

先来看下面这样一段代码:

function Point(x, y){
    this.x = x;
    this.y = y;
}
Point.prototype.move = function(stepX, stepY){
    this.x += stepX;
    this.y += stepY;
};
var p = new Point(0, 0);
console.log(p);//{x: 0, y: 0}
p.move(2,2);
console.log(p);//{x: 2, y: 2}
var circle = {x:1,y:1,r:1};
p.move.apply(circle, [2,1]);
console.info(circle);//{x: 3, y: 2, r: 1}

我们使用Point构造函数可以创建出一个点,在其原型上有一个move方法可以使这个点坐标移动。
之后我们又创建circle对象,有x/y/r属性(把它想象成一个圆),之后我们的需求是:将这个圆的圆心移动,我们就使用了apply来借用move方法,最终将圆移动了位置,最终效果如下图:
apply使用圆示意图

  • function.prototype.apply/call

在上面我们可以看到能实现圆心移动的关键方法就是apply,大致解析如下,p.move是一个函数它的作用就是将一个点移动,然后我们通过apply方法把它借用给circle这个对象。将circle对象上的x/y属性进行变更,分别加2和1,实现了圆心的移动。很明显在这里 apply方法描述的就是一个借用的功能.

为什么会把apply/call放在一起说呢,因为他们的功能并没有实质性的区别。只是在传入参数的时候,apply需要将参数以数组的形式进行传递,而call是将需要传入的参数一个一个跟在借用的对象后。下面一个小例子足以说明:

function sum(a, b) {
    return a + b;
}
function call1(num1, num2) {
    return sum.call(this, num1, num2);
}
function apply1(num1, num2) {
    // return sum.apply(this,[num1,num2])
    return sum.apply(this, arguments);//利用函数的arguments对象

}
console.info(call1(10, 20));//30
console.info(call1(5, 10));//15

可以看到我们在后两个函数中,可以直接使用sum方法。

  • function.prototype.bind

这里来看看ES5引入的bind,又有什么不同,还是和上面类似的代码

function Point(x, y){
    this.x = x;
    this.y = y;
}
Point.prototype.move = function(stepX, stepY){
    this.x += stepX;
    this.y += stepY;
};
var p = new Point(0, 0);
var circle = {x:1,y:1,r:1};
var circleMove = p.move.bind(circle,2,2);
circleMove();
console.info(circle);//{x: 3, y: 3, r: 1}
circleMove(3,4);
console.info(circle);//{x: 5, y: 5, r: 1}

这里我使用了和 call 类似的调用方法,但是显然 bind 和 call 不一样,使用 bind 时,它会将我们绑定 this 后的函数引用返回,然后手动执行。可以看到的是,因为在这里我们绑定的对象的后面传入了x/y两个值,所以执行后坐标立即变化,并且在后来手动设置偏移量时也不再起到效果。
这样的相比于apply立即执行的好处时,我们可以使用定时器,例如:setTimeout(circleMove,1000),延迟一秒后移动。

当然,每次只能移动固定的值也不是一件很好的事情,所以我们在使用 bind 的时候常常不会设置其默认参数, var circleMove2 = p.move.bind(circle,);,之后在执行函数时,再将参数传入circleMove(3,4);,这样就可以实现每次自定义偏移量了

这又引出了call/applybind的作用的另外一种说法: 扩充作用域

var color = 'red';
var obj = {color:'blue'};
var obj1 = {color:'black'};
var obj2 = {color:'yellow'};

function showColor(){
    console.info(this.color);
}
showColor();//red
showColor.call(obj);//blue
showColor.apply(obj1);//black
showColor.bind(obj2)();//yellow

可以看到这里都实现了一样的效果。值得说的是使用callaplly()来扩充作用域的最大好处就是对象不需要与方法有任何耦合关系。

 闭包

简单定义

先来看这样的一段代码,在chrome中找到Scope列表,可以看到,在作用域链上我们已经创建了一个闭包作用域!

(function() {
    var a = 0;
    function b() {
        a = 1;
        debugger;
    }
    b();
})();

闭包一个最简单的定义就是:闭包就是说在函数内部定义了一个函数,然后这个函数调用到了父函数内的相关临时变量,这些相关的临时变量就会存入闭包作用域里面.这就是闭包最基础的定义

保存变量

下面就来看一下闭包的一个基本特性保存变量

function add(){
    var i = 0;
    return function(){
        console.info(i++);
    };
}
var f = add();
f();//1
f();//2

我们定义了一个 add 方法,执行完毕后会返回一个函数,接着我们就把这个函数赋值给了变量f,由于 add 函数也是返回一个函数,在我们每一次执行f()的时候,它引用了add内的变量i,并且保存在自己的闭包作用域内,所以一直输出执行的话,也会累加输出。

小tips

需要我们记住的是 每次函数调用的时候创建一个新的闭包

var fun = add();
fun();//1
fun();//2

我们再来通过简单的例子看看另一个注意的地方:

function test(){
    var a  = 0;
    var ff =  function(){
        console.info(a);
    };
    a = 1214;
    return ff;
}
var b = test();
b();//1214

执行的结果是1214,从这里我们可以看到 闭包中局部变量是引用而非拷贝,其实这样的改变发散开来我们就可以知道,即使在这里变量 a 未在函数 ff 之前定义,而是var a = 1214;我们同样会得到同样的结果

点击li显示对应编号案例解析

其实上面这些我是很晕的,来看一个我们实际在前端编程过程中经常遇到的问题。
我们有一个列表,分别为1/2/3,我们的需求是在点击不同的数字时,也能把它对应的编号弹出来。然后我们洋洋洒洒写下了这样的代码:

<ul id="#list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
(function() {
    var oLi = document.getElementById("#list").getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        oLi[i].onclick = function() {
            alert(i);
        };
    }
})();
</script>

一运行,发现懵了。怎么弹出来的都是3?不对啊,我不是用循环将值都传进去了吗?

如果你确实理解了上面的 闭包中局部变量是引用而非拷贝这一节中的两个案例的话,那么就应该能了解一些。

解析:在这里我们为每一个li的onclick事件 绑定了一个匿名函数,这个匿名函数就形成了一个闭包。这些匿名函数并不立即执行,而是在点击对应的li的时候才回去执行它。
而在这时就和上面的a = 1214;这个例子一样,此时的循环早已结束,i 就等于oLi.length,在我们点击不同的li时,闭包中引用的其实都是引用的同一个变量i自然弹出来的都是3,(这里不理解引用的都是用一个i的话,可以将alert(i);替换成alert(i++);,再到浏览器上去进行测试)

解决方案:

(function() {
    var oLi = document.getElementById("#list").getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        oLi[i].onclick = (function(j) {
            return function(){
                alert(j);
            };
        })(i);
    }
})();
/*
(function() {
    var oLi = document.getElementById("#list").getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        (function(j){
            oLi[i].onclick= function(){
                alert(j);
            };
        })(i);
    }
})();
*/

可以看到这里给出了两个简单的写法,但实际上除了写法不同之外、闭包包含范围、内容也不太一样(有兴趣的可以打开chrome调试工具看看),但是达到的效果是一样的。这样我们就为每个lionclick事件的匿名函数,都保存下了自己闭包变量。就可以实现在点击每个li的时候弹出对应的标号了。(还可以将alert(j);替换成alert(j++);欣赏一下点击不同li时的累加效果)

当然如果你只是想要记住一些标号这么简单的事情,其实还可以将变量保留于元素节点上,也能达到一样的效果,如下:

(function() {
    var oLi = document.getElementById("#list").getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        oLi[i].flag = i;
        oLi[i].onclick = function() {
            alert(this.flag);
        };
    }
})();

如果有错误之处,请指正。谢谢!

查看原文

赞 0 收藏 7 评论 0