用 Javascript 实现选中粘贴

为了对付单位愚蠢的每日工作总结的要求, 写了一个小脚本来导出任务管理器里的内容到剪贴板, 于是接触了一下纯 javascript 实现复制到剪贴板功能

代码先行贴出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(function() {
'use strict';
if ('your_url'){
var issueList = document.querySelectorAll('.issue-list li');
var taskList = [];
for(var i = 0, len = issueList.length; i < len; i++){
var key = issueList[i].getAttribute('data-key');
var title = issueList[i].getAttribute('title');
var task = '已完成: '+key+'-'+title;
taskList.push(task);
}
var text = taskList.join('\n');
var clipboard = document.createElement('textarea');
clipboard.style.width = '100%';
clipboard.style.height = '300px';
clipboard.value = text;
var listPanel = document.getElementsByClassName('list-panel')[0];
listPanel.appendChild(clipboard);
var today = document.createElement('button');
today.style.width = '100%';
today.innerText = 'Today';
listPanel.appendChild(today);
today.addEventListener('click', function () {
window.location.href = 'url_to_today';
});
var button = document.createElement('button');
button.style.width = '100%';
button.innerText = 'copy to clipboard';
listPanel.appendChild(button);
button.addEventListener('click', function() {
clipboard.select();
document.execCommand('copy');
clipboard.blur();
});
}
})()

Web 坐标整理

坐标分类

  1. 文档坐标

  2. 窗口坐标

scroll

Element.scrollTop: 设置或获得一个元素距离他容器顶部的像素距离, 当一个元素的容器没有产生垂直方向的滚动条, 则他的 scrollTop 的值默认为0

获得滚动距离
var intElementScrollTop = someElement.scrollTop;

scrollTop可以被设置为任何整数, 但是一下情况会报错:
1. 如果一个元素不能被滚动(没有溢出容器或本身不可滚动)的时候设置其 scrollTop 为 0;

  1. 设置 scrollTop 的值小于 0, scrollTop 被设为 0

  2. 如果设置了超出这个容器可以滚动的值, scrollTop 会被设为最大值

其实就是溢出距离

Element.scrollHeight: 计量元素内容高度的只读属性, 包括 overflow 样式属性导致的视图中不可见内容

没有垂直滚动条的情况下, scrollHeight 值与元素视图填充所有内容所需要的最小值 clientHeight 相同, 包括元素的 padding, 但不包括 margin

其实就是溢出元素的本身高度

!()[https://developer.mozilla.org/@api/deki/files/840/=ScrollHeight.png]

判断元素是否滚动到底部

element.scrollHeight - element.scrollTop === element.clientHeight

clientTop

Element.clientTop: 一个元素顶部边框的宽度, 不包括顶部外边距或内边距, 只读属性

clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度 来计算

其实就是可视区域高度

mouseEvent.clientX: 只读属性, 事件发生时应用客户端区域的水平坐标, 与页面无关

mouseEvent.x 是 mouseEvent.clientX 的别名

window

window.innerHeight: 浏览器窗口的视口( viewport ) 高度, 如果存在滚动条, 则要包括在内, 只读属性

window.outerHeight: 整个浏览器窗口的高度

offset

mouseEvent.offsetX: read-only property provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node

page

event.PageX: read-only property returns the horizontal coordinate of the event relative to the whole document

文档坐标

screen

mouseEvent.screenX: read-only property provides the horizontal coordinate of the mouse pointer in the global(screen) coordinate.

JavaScript 事件与性能

事件处理程序本质上是一种函数, 是一种对象, 存放在内存中, 设置大量的事件处理程序会使内存中的对象变多, Web 程序的性能会变得越来越差.

为了更好利用事件处理程序, 出现了事件委托, 用来提升性能

事件委托

Event Delegate: 把若干个子节点上的相同事件的处理函数绑定到父节点, 从父节点统一处理子节点冒泡上来的事件, 这种技术叫做事件委托.

举例:

&lt;ul id='parent-list'&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
    &lt;li id='list-1'&gt;List 1&lt;/li&gt;
&lt;/ul&gt;
`</pre>

通过父节点监听冒泡事件可以处理子节点上的对应事件

<pre>`var parentList = document.getElementById('parent-list');
parentList.addEventListener('click', function(){
  var target = event.target; // li
  if(target.nodeName.toLowerCase() === 'li'){
    alert(target.firstChild.nodeValue);
  }
}, false)

因为事件委托依赖事件冒泡机制, 所以并不是所有事件都可以委托

最适合的事件: click, mousedown, mouseup, keydown, keyup, keypress

移除事件处理程序

每当将事件处理程序制定给元素时, 运行中的浏览器代码与支持页面交互的 Js 代码之间就会建立一个连接, 这种连接越多, 页面执行越慢

如果使用完后不再使用的事件处理程序, 应当释放掉
`


var button = document.getElementById(‘button’);
button.onclick = function(){
button.onclick = null;
event.target.firstChild.nodeValue = “Submitting”
}

JavaScript 停止冒泡和阻止浏览器默认行为

事件兼容

function myFn(e){
  var evt = e ? e:window.event
}
`</pre>

### JS 停止冒泡

<pre>`function myFn(e){
  window.event ? window.event.cancelBubble = true : e.stopPropagation();
}
`</pre>

### JS 阻止默认行为

<pre>`function myFn(e){
  window.event ? window.event.returnValue = false : e.preventDefault();
}

补充

event 表示事件的状态, 例如, event.target 是触发事件的对象

JS 的 Date

&gt; var myDate = new Date()
undefined
&gt; myDate.getYear()
116
&gt; myDate.getFullYear()
2016
&gt; myDate.getMonth() // 0 - 11
7
&gt; myDate.getDate() // 1 - 31
28
&gt; myDate.getDay() // 0 - 6, 西方以周日为一周开始, 所以0是周日
0
&gt; myDate.getTime() // 从1970.1.1开始的毫秒数
1472313859531
&gt; myDate.getHours()
0
&gt; myDate.getMinutes()
4
&gt; myDate.getSeconds()
19
&gt; myDate.getMilliseconds()
531
&gt; myDate.toLocaleDateString()
'2016-08-28'
&gt; myDate.toLocaleString()
'2016-08-28 00:04:19'

JS 开发工具

开发工具是让开发人员工作更轻松的一些软件, 传统上包括集成开发环境, 代码检查工具, 编译器, 调试工具和性能测试工具

但是 JS 是一种动态语言, 伴随他动态本质而来的是对更多运行时开发者工具的需求.

Quick List

  • Atom & Atom-ternjs
  • Chrome DevTools
  • PageSpeed Insights
  • BrowserSycn
  • TraceGL
  • ironNode
  • ESLint
  • Babel
  • React
  • Webpack + Hot Module Replacement
  • Redux + Redux DevTools

关于工具, 主要是编辑器和运行时环境(比如浏览器)

主要用 Atom, 需要 atom-ternjs 来开启 JavaScript 智能识别

很多很棒的 Atom 插件

调试器: Chrome DevTools, 注意其 flame charts 和 dominators view 功能

性能审查: PageSpeed Insights

BrowserSync是一款测试响应式布局非常好用的工具, 可以一次模拟多种浏览器(电脑, 平板, 手机)
可以监视文件, 并在文件修改的时候自动重载同步刷新浏览器, 像滚动, 点击, 表单交互等动作也会跨设别同步.

视频链接

TraceGL 是一种运行时调试工具, 可以让你在软件中的方法实时调用时观察他们, 而不是手动逐步跟踪代码

视频链接

代码检查: ESLint 可配置性特别强, 每一个选项都可以设置开启或禁用, 甚至给他们添加参数. 可以创建自己的规则, 支持插件

Babel 是一种编译器, 可以让现在的 JS 代码使用 ES6+ 尚未支持的特性, JSX 等, 工作原理是讲代码转换为等价的 ES5.

文章链接

Webpack 会将模块和以来打包成针对浏览器的静态资源, 他支持大量有趣的特性, 比如模块热拔插, 可以让当前浏览器中的代码在你修改文件的时候自动更新, 而不用刷新页面. 模块热拔插

参考资料

React: 不算开发者工具

Promise in ES6

在 JavaScript 世界中, 所有代码都是单线程执行的

异步执行可以可以用回调函数实现

function callback() {
  console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000);
console.log('after setTimeout()');
`</pre>

先看一个简单的 Promise 例子: 生成0-2之间的随机数, 如果小于1, 则等待一段时间后返回成功, 否则返回失败

<pre>`function test(resolve, reject){
  var timeOut = Math.random() * 2;
  log('set timeout to: ' + timeOut + ' seconds.');
  setTimeout(function(){
    if(timeOut &lt; 1) {
      log('call resolve()...');
      resolve('200 ok');
    } else {
      log('call reject()...');
      reject('timeout in ' + timeOut + ' seconds');
    }
  }, timeOut * 1000)
}
`</pre>

这个`test()`函数有两个参数, 这两个参数都是函数, 如果执行成功, 我们将调用`resolve('200 ok)'`, 如果执行失败, 我们将调用`reject('timeout in ' + timeOut + ' seconds.')`. 可以看出, `test()`函数只关心自己的逻辑, 并不关心具体的`resolve` 和`reject` 将如何处理.

有了执行函数, 我们就可以用一个 Promise 对象来执行他, 并在将来某个时刻获得成功或失败的结果

<pre>`var p1 = new Promsie(test);
var p2 = p1.then(function(result){
  console.log('成功: ' + result);
});
var p3 = p2.catch(function(err){
  console.log('失败: ' + err);
});
`</pre>

变量`p1`是一个 Promise 对象, 他负责执行执行`test`函数, 由于`test`函数在内部是异步执行的, 当`test`函数执行到`resolve('ok 200')`时, 告诉 Promsie 对象执行

<pre>`.then(function(result){ // result 是通过 test 传递给 resolve 的参数
  console.log('成功: ' + result);
});
`</pre>

当`test`执行到`reject`的时候, 告诉 Promise 对象执行

<pre>`.catch(function(err){ // err 是 test 传递给 reject 的参数
  console.log('失败: '+ err);
});
`</pre>

Promise 对象可以串联起来(因为都返回一个 Promise 对象)

<pre>`new Promise(test).then(function(result){console.log(result)}).catch(function(err){console.log(err)});
`</pre>

可见, Promise 最大的好处, 是在异步执行的流程中, 把执行代码和处理结果代码清晰地分离了.

Promise 还可以做更多的事情, 比如有若干个异步任务, 需要先做任务1, 如果成功后再做任务2, 任何任务失败则不再继续并执行错误处理函数

要串行执行这样的异步任务, 只需要链式调用

<pre>`job1.then(job2).then(job3).catch(handleError);
`</pre>

其中 job1, job2, job3 都是(或返回) Promise 对象

<pre>`function job2(input){
  return new Promise(function(resolve, reject){
      ....
    })
}
`</pre>

### 并行执行异步任务

试想一个页面聊天任务, 我们需要从两个不同的 URL 分别获取用户的个人信息和好友列表, 这两个任务是可以并行执行的, 用`Promise.all()`实现如下:

<pre>`var p1 = new Promise(function(resolve, reject){
  setTimeout(resolve, 500, 'P1');
});

var p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 600, 'P2');
});

// 同时执行p1 和 p2, 并在他们都执行完毕后执行 then
Promise.all([p1,p2]).then(function(results){
  console.log(results); // 获得一个 Array: ['P1', 'P2']
});
`</pre>

有时多个异步任务是为了容错, 比如同时向两个 URL 读取, 只需要获得先返回的结果, 这种情况下用`Promise.race()`实现:

<pre>`var p1 = new Promise(function(resolve, reject){
  setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function(resolve, reject){
  setTimeout(resolve, 600, 'P2');
});

Promise.race([p1,p2]).then(function(result){
  console.log(result); // 'P1'
});

CO Module

Co v4

`co@4.0.0has been released, which now relies on promises. It is a stepping stone towards the [aysnc/await proposal](https://github.com/lukehoban/ecmascript-asyncawait). The primary API change is howco()is invoked. Before,coreturned a "thunk", which you then called with a callback and optional arguments. Now,co()` return s a promise.

co(function* (){
  var result = yield Promise.resolve(true);
  return result;
}).then(function (value) {
  console.log(value);
}, function(err) {
  console.error(err.stack);
});
`</pre>

If you want to convert a `co`-generator-function into a regular function that returns a promise, you now use `co.wrap(fn*)`.

<pre>`var fn = co.wrap(function*(val) {
  return yield Promise.resolve(val);
});

fn(true).then(function(val){
})
`</pre>

### Platform Compatibility

`co@4+` requires a `Promise` implementation. For version of node `&lt; 0.11` and for many older browsers, you should/must include your own `Promise` polyfill.

Node v4+ is supported out of the box, you can use `co` without flags or polyfills.

### Installation

<pre>`$ npm install co
`</pre>

### Examples

<pre>`var co = require('co');

co(function*(){
  // yield any promise
  var result = yield Promise.resolve(true);
}).catch(onerror);

co(function*(){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield[a, b, c];
  console.log(res);
  // =&gt; [1,2,3]
}).catch(onerror);

// errors can be try/catched
co(function*() {
  try{
    yield Promise.reject(new Error('boom'));
  } catch (err) {
    console.error(err.message); // 'boom'
  }
});

function onerror(err){
  // log any uncaught errors
  // co will not throw any errors you do not handle
  // HANDLE ALL YOUR ERRORS
  console.error(err.stack);
}

JavaScript Generator

作用迭代器

function* argumentsGenerator() {
  for (let i = 0; i &lt; arguments.length; i++) {
    yield arguments[i];
  }
}
`</pre>

我们希望迭代传入的每个实参

<pre>`var argumentsIterator = argumentsGenerator('a','b','c','d');

// Prints "a,b,c,d"
console.log(
  argumentsIterator.next().value,
  argumentsIterator.next().value,
  argumentsIterator.next().value,
  argumentsIterator.next().value
)
`</pre>

我们可以简单的理解
- Generator 其实是生成 Iterator 的方法, argumentsGenerator 被称为 GeneratorFunction, 也有一些人把 GeneratorFunction 的返回值称为一个 Generator
- yield 可以中断 GeneratorFunction 的运行, 而在 next() 时恢复运行
- 返回的 Iterator 上, 有 next 成员方法, 能够返回迭代值. 其中 value 属性包含了实际返回值, done 属性为布尔值, 标记迭代器是否完成迭代, 要注意的是, 在 done: true 后继续运行 next 方法会产生异常.

完整的 ES 实现中, for-of 循环正式为了快速迭代一个 Iterator

<pre>`for (let value of argumetnsIterator) {
  console.log(value);
}
`</pre>

可以利用 yield* 语法, 将 yield 操作代理到另一个 Generator

<pre>`let delegatedIterator = (function*() {
  yield 'Hello!';
  yield 'Bye!';
})();

let delegatingIterator = (function*() {
  yield 'Greetings!';
  yield* delegatedIterator;
  yield 'Ok, bye!';
})();

// Prints "Greetings!", "Hello", "Bye!", "Ok, bye!"
for (let value of delegatingIterator) {
  console.log(value);
}

用作流程控制

co 已经将此特性封装的非常完美

Generator 之所以可以用来控制代码流程, 就是通过 yield 来将两个或多个 Generator 的执行路径相互切换, 这种切换是语句级别的, 而不是函数级调用. 其本质是 CPS 变换.

这里补充 yield 的若干行为:

  • next 方法接收一个参数, 传入的参数是 yield 表达式的返回值: 即 yield 既可以产生数值, 也可以接收数值
  • throw 方法会抛出一个异常, 并终止迭代
  • GeneratorFunction 的 return 语句相当于一个 yield

将异步”变为”同步

假设我们希望有如下语法:

  • suspend 传入一个 GeneratorFunction
  • suspend 返回一个简单函数, 接收一个 node 风格的回调函数
  • 所有的异步调用都通过 yield, 看起来像同步调用
  • 给定一个特殊的回调, 让保证异步调用的返回值作为 yield 的返回值, 并让脚本继续
  • GeneratorFunction 的返回值和执行过程的错误都会传入全局的回调函数

Generator 101

function * HelloGen(){
  yield 100;
  yield 400;
}

var gen = HelloGen();

console.log(gen.next()); // {value: 100, done: false}
console.log(gen.next()); // {value: 400, done: false}
console.log(gen.next()); // {value: undefined, done: true}
`</pre>

<pre>`function * HelloGen2() {
  var a = yield 100;
  var b = yield a + 100;

  console.log(b);
}

var gen2 = HelloGen2();

console.log(gen2.next()); // {value: 100, done: false}
console.log(gen2.nect(500)); // {value: 600, done: false}
console.log(gen2.next(1000)); // {value: undefined, done: done}
// prints 1000
`</pre>

### Preventing Callback Hell

So, how can generators be used to avoid callback hell? First, you need to understand a simple technique that we will be using heavily with generators to write code without callbacks.

#### Understanding Thunks

A thunk is a partially evaluated function which accepts a single callback as the argument. Within generators, we'll be yielding thunks to write without callbacks. A simple thunk is shown below.

<pre>`function(callback) {
  fs.readFile('myfile.md', 'utf8', callback)
}
`</pre>

Thunks can also be created dynamically as shown below:

<pre>`function readFile(filename) {
  return function(callback) {
    fs.readFile(filename, 'utf8', callback);
  };
}
`</pre>

#### Using `co`

`co` is a nice module which helps to use thunks and generators together to create Node.js applications without callbacks.

A simple application which uses `co` and the `readFile()` thunk.

<pre>`var co = require('co');

co(function* (){
  var file1 = yield readFile('file1.md');
  var file2 = yield readFile('file2.md');

  console.log(file1);
  console.log(file2);
})();
`</pre>

As you can see, we are no longer using callbacks. This gives us a simple way to write large modular Node apps easily.

###### How `co` Works Internally
  1. First, it calls next(null) and gets a thunk.
  2. Then, it evaluate the thunk and saves the result.
  3. Then, it calls next(savedResult)(in this case, saveResult == readFile(‘file1.md’), and next(savedResult) pass the content to var file1)
  4. Repeat these steps until next() return {done: true}

    A minimal version of co written to show how it works internally.

    `
    function co(generator){
    var gen = generator();

    function nextItem(err, result) {

    var item = gen.next(result);
    

    `if (!item.done) {
    item.value(nextItem); // item.value is a thunk, because yield a thunk
    }

}

nextItem();
}

__Proto__与 Prototype

简而言之,

x.__proto__ === x.constructor.prototype
`</pre>

在 JS 世界里, 万物皆对象

Function 是对象, Function.prototype 也是对象, 因此他们具有对象的共性: **proto**属性

<pre>`Function.__proto__ === Function.prototype; // Function 的构造函数还是 Function

Function.prototype.__proto__ === Object.prototye; Function.prototype 的构造函数是 Object()
`</pre>

而 Object 的构造函数也是 Function, 所以

<pre>`Object.__proto__ === Function.prototype

彻底理解 Thunk 函数

生成器函数

生成器(Generator)函数写成:

function* func () {}
`</pre>

其本质也是一个函数, 所以他具备普通函数所具有的所有特性, 除此以外, 他还具有以下特性
  1. 执行生成器函数后返回一个迭代器(Iterator)
  2. 生成器函数内部可以使用 yield 或 yield*, 函数执行到 yield 的时候会暂停执行, 并返回 yield 后面表达式的值, 通过迭代器的 next() 方法返回一个对象, 该对象由 value 和 done 属性组成, 其中, value 属性值为 yield 后面表达式的值, done 属性表示生成器的执行状态
  3. 迭代器 next() 方法的参数是yield 表达式的值
  4. 当 next 指向最后一个 yield 的时候, 再次执行 next() 方法, 返回的对象中 done: true, 而 value 的值为 return 后面的表达式(默认为 undefined)

    例一

    `function test() {
      return 'b';
    }
    function* func() {
      var a = yield 'a';
    
      console.log('gen', a); // 'gen: undefined' - (如果 next 方法不带参数, yield 表达式返回 undefined)
      var b = yield test();
      console.log('gen': b); // 'gen: undefined'
    }
    
    var func1 = func();
    var a = func1.next();
    console.log('next: ' a); // 'next: {value: 'a', done: false}'
    var b = func1.next();
    console.log('next: ' b); // 'next: {value: 'b', done: false}'
    var c = func1.next();
    console.log('next: ' c); // 'next: {value: undefined, done: true}'
    `

    关于 yield*

    yield 暂停执行并返回后面表达式的值, 而 yield* 则将函数委托到另一个迭代器或可迭代对象

    arguments

    `> function* genFunc(){
    ... yield arguments;
    ... yield* arguments;
    ... }
    undefined
    > var gen = genFunc(1,2);
    undefined
    > gen.next()
    { value: { '0': 1, '1': 2 }, done: false }
    > gen.next()
    { value: 1, done: false }
    > gen.next()
    { value: 2, done: false }
    > gen.next()
    { value: undefined, done: true }
    `

    Generator

    `> function * gen1(){
    ... yield 2;
    ... yield 3;
    ... }
    undefined
    > function * gen2(){
    ... yield 1;
    ... yield* gen1();
    ... yield 4;
    ... }
    undefined
    > var g2 = gen2();
    undefined
    > g2.next()
    { value: 1, done: false }
    > g2.next()
    { value: 2, done: false }
    > g2.next()
    { value: 3, done: false }
    > g2.next()
    { value: 4, done: false }
    > g2.next()
    { value: undefined, done: true }
    `

    Thunk 函数

    在 co 的应用中, 为了能像同步代码那样书写异步代码, 比较多的使用方式是使用 thunk 函数(但不是唯一方式, 还可以是 Promise), 比如读取文件内容的异步函数 fs.readFile() 方法, 转化为 thunk 函数的方式如下

    `function readFile(path, encoding){
      return function(cb){
        fs.readFile(path, encoding, cb);
      }
    }
    `

    Thunk函数具备以下两个要素
    1. 有且只有一个参数是 callback 的函数
    2. callback 的第一个参数是 error

    使用 thunk 函数, 同时结合 co 我们就可以像书写同步代码一样书写异步代码

    `var co = require('co');
    var fs = require('fs');
    var Promise = require('es6-promise').Promise;
    
    function readFile(path, encoding){
      return function(cb) { // thunk function
        fs.readFile(path, encoding, cd);
      };
    }
    
    co(function* () { // 外部不可见, 但在 co 内部其实已经转化成promise.then().catch()...链式调用的行驶
      var a = yield readFile('a.txt', {encoding: 'utf-8'});
      console.log(a); // a
      var b = yield readFile('b.txt', {encoding: 'utf-8'});
      console.log(b); // b
      return yield Promise.resolve(a+b);
    }).then(function(val){
      console.log(val) // ab
    }).catch(function(error){
      console.log(error);
    })
    `

    有一个框架 thunkify 可以帮我们轻松实现以上步骤

    `var co = require('co');
    var thunkify = require('thunkify');
    var fs = requrie('fs');
    var Promise = require('es6-promise').Promise;
    
    var readFile = thunkify(fs.readFile);
    
    co(function* () { // 外部不可见, 但在 co 内部已经转化为 promise 的链式调用
      var a = yield readFile('a.txt', {encoding: 'utf-8'});
      console.log(a); // a
      var b = yield readFile('b.txt', {encoding: 'utf-8'});
      console.log(b); // b
      return yield Promise.resolve(a+b);
    }).then(function(val){
      console.log(val); // ab
    }).catch(function(err){
      console.log(err);
    })
    `

    对于 thunkify 框架的理解注释如下:

    `/**

    • Module dependencies
      */
      var assert = require(‘assert’);

    /**

    • Expose thunkify().
      */

    module.exports = thunkify;

    function thunkify(fn) {
    assert(‘function’ == typeof fn, ‘function required’);

    // 返回一个包含 thunk 函数的函数, 返回的 thunk 函数用于执行yield, 而外层的这个函数用于给 thunk 函数传递其他参数
    return function() {

    var args = new Array(arguments.length);
    // 缓存上下文环境, 给 fn 提供执行环境
    var ctx = this;
    
    // 将参数类数组对象转为数组(实现方式略臃肿, 可以直接用Array.from)
    for (var i = 0; i &lt; args.length; ++i){
      args[i] = argumetns[i];
    }
    
    // 真正的 thunk 函数(有且只有一个参数, 并且参数是第一个参数为 err 的 callback)
    return function(done) { // 返回 thunk 函数
      var called;
    
      // 将回调函数再包裹一层, 避免重复调用, 同时, 将包裹了的真正的回调函数 push 进参数数组(即将回调函数 done 作为最后一个参数传递给 fn)
      args.push(function(){  // 将回调函数 done 加到参数列表尾部, 形成 fn 需要的 [arg1, ..., done]
        if (called) return;
        called = true;
        done.apply(null, arguments); // 执行回调函数
      });
    
      try {
        // 在 ctx 环境执行 fn
        // 并将执行 thunkify 之后返回的函数的参数(含 done 回调) 传入, 类似于执行:
        // fs.readFile(path, {encoding: 'utf-8'}, done)
        fn.apply(ctx,args); // 类似 fn(arg1, .., done)
      } catch(err) {
        done(err);
      }
    }
    

    }
    }

生成器 Function*

function* 声明定义一个 generator( 生成器), 返回一个 Generator 对象

语法

function* name([param[, param[, ...param]]]){ statements }
`</pre>

### 描述

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

调用一个 Generator 并不马上执行它的主体, 而是返回一个 迭代器对象, 这个迭代器的 next() 方法被调用的时候, Generator 的主体会被执行至下一个 yield 表达式, 该表达式定义了迭代器的返回值

### 示例

<pre>`function* idMaker(){
  var index = 0;
  while(index &lt; 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

谨慎使用, 因为其内部变量保持变化, 要每次输出的相互影响

JavaScript 提高性能

针对 js 文件的加载位置

在 HTML 文件中, <script>标签是可以加载<head>或<body>区域的, 由于 JavaScript 执行和 UI 渲染的是单线程的, 如果加载不顺畅可能导致堵塞, 造成页面空白或卡顿.
1. 如果 js 文件没有特殊要求致命需要在页面渲染之前加载, 那么应当将 <script>放在</body>之前
2. 如果这些 js 文件有明确需求先于 body 执行, 那么就在第一个 js 或者页面上先放一个载入动画

针对 js 文件的合并

更快速的数据访问

对于浏览器而言, 一个标识符所处的位置约深, 去读写他的速度也越慢.

如果我们需要在当前函数内多次用到某一个值, 应当用一个局部变量将其保存

DOM 操作的优化

DOM 操作远比 JavaScript 的执行更耗性能, 应当尽量减少该操作对性能的消耗.

function innerLi_s(){
  var i = 0;
  for(;i&lt;20;i++){
    document.getElementById('Num').innerHTML += 'A'; //进行20此循环, 每次循环都进行2次DOM 元素的访问, 一次读取 innerHTML, 一次写入 innerHTML
  }
}

// 改写为

function innerLi_s(){
  var content = '';
  var i = 0;
  for(;i&lt;20;i++){
    content += 'A' //这里只对 js 的变量循环20次
  };
  document.getElementById('Num').innerHTML += content; //这里只进行2次 DOM 操作
}

减少 DOM 的重绘重排版

元素的布局的改变或内容的增删改或浏览器尺寸的变化都会导致重排, 而字体颜色或背景色的修改会导致重绘.

合并同一对象的操作, 比如 background 的各个属性的修改可以合并为一个

循环的优化

保存 arr.length, 避免多次访问

CommonJS 规范

概述
CommonJS 是服务器端模块的规范, Node.js 采用了这个规范

根据 CommonJS 规范, 一个单独的文件就是一个模块

加载模块使用 require 方法, 该方法读取一个文件并执行, 最后返回文件内部的 exports 对象.

// example.js

console.log('evaluating example.js');

var invisible = function(){
  console.log('invisible');
}

exports.message = 'h1';

exports.say = function(){
  console.log(message)
}
`</pre>

使用 require 方法, 加载 example.js 模块

<pre>`var example = require('./example.js');
`</pre>

这时变量 example 就对应模块中 exports 对象, 可以通过这个对象应用模块中提供的各种方法.

<pre>`{
  message: 'h1',
  say: [Function]
}
`</pre>

require 方法默认读取 js 文件, 所以可以省略 .js 扩展名

<pre>`var example = require('./example')
`</pre>

js 文件名前需要加上路径, 可以是相对路径, 也可以是绝对路径. 如果省略路径, node.js 会认为添加一个核心模块, 或者已经安装在本地 node_modules 目录中的模块. 如果加载的是一个目录, node.js 会首先寻找该目录中的 package.json 文件, 加载该文件 main 属性指定的模块, 否则就寻找该目录下的 index.js 文件

定义一个最简单的模块

<pre>`exports.add = function(a,b){return a+b};
`</pre>

//add.js
为 exports 对象添加一个 add 方法,

<pre>`var addition = require('./add');
addition.add(1,2); //3
`</pre>

稍复杂的例子

<pre>`//foobar.js
function foobar(){
  this.foo = function(){
    console.log('hello foo')
  };

  this.bar = function(){
    console.log('hello bar')
  };
}
exports.foobar = foobar;
`</pre>

调用

<pre>`var foobar = require('./foobar').foobar, test = new foobar();
test.bar(); //'hello bar'
`</pre>

有时不需要 exports 返回一个对象, 而仅需要返回一个函数, 就写成 module.exports

<pre>`module.exports = function(){
  console.log('hello world')
}
`</pre>

### AMD 规范与 CommonJS 规范的兼容性

CommonJS 规范加载模块是同步的, 也就是说, 只有加载完成, 才能执行后面的操作. AMD 规范则是非同步加载模块, 允许指定回调函数. 由于 Node.js 主要用于服务器编程, 模块文件一般都已经存在于本地硬盘, 所以加载起来比较快, 不需要考虑非同步加载的方式, 所以 CommonJS 的规范比较适用. 但是, 如果是浏览器环境, 要从服务器端加载模块, 这时就必须采用非同步模式, 因此浏览器端一般采用 AMD 规范.

AMD 规范适用 define 方法定义模块

<pre>`define(['package/lib', function(lib){
  function foo(){
    lib.log('hello world!');
  }

  return {
    foo:foo
  };
}])
`</pre>

AMD 规范允许输出的模块兼容 CommonJS 规范,在这时 define 方法需要写成如下格式

<pre>`define(function(require, exports, module)
  var someModule = require('someModule');
  var anotherModule = require('anotherModule');

  someModule.doTheAwesoem();
  anotherModule.doMoreAwesome();

  exports.asplode = function(){
    someModule.doTheAwesoem();
    anotherModule.doMoreAwesome();
  };
);

Array.from()

Array.from() 方法可以将一个类数组对象或可遍历对象转化为真正的数组.
在 ES6 中, Class 语法允许我们为内置类型(比如 Array) 和自定义类新建子类(比如叫 subArray), 这些子类也会继承父类的静态方法, 比如 subArray.from(), 调用该方法后会返回子类 subArray 一个实例, 而不是 Array 的实例.

语法

Array.from(arrayLike[, mapFn[, thisArg]])
`</pre>

### 参数

#### arrayLike

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

#### manFn

可选参数, 如果制定了该参数, 则最后生成的数组会经过 map 操作

#### this.Arg

可选参数, 执行 mapFn 时的 this 的值

### 描述

可以使用 Array.from()将下面的两种对象转换成数组
- 类数组对象(拥有 length 属性和若干索引属性的任意对象)
- 可遍历对象(可以迭代出其他对象, 比如有 Map 和 Set)

Array.from(obj,mapFn,thisArg) 相当于 Array.from(obj).map(mapFn,thisArg)

### 应用

<pre>`var obj = {length:5}; // return {length:5}
var arr =Array.from(obj); // namely Array.from({length:5}), return [undefined * 5]
Array.isArray(arr); //return true
var arr1 = Array.from(obj,(v,k) =&gt; k); //return [0,1,2,3,4]

JavaScript 的闭包

通常情况下, 求和函数的定义是这样的

function sum(arr){
  return arr.reduce(function(x,y){
    return x+y;
  });
}
sum([1,2,3,4,5]); //15
`</pre>

但是, 如果不需要立刻求和, 而是在后面的代码中根据需要进行计算, 可以选择返回一个保存了参数的求和函数

<pre>`function lazy_sum(arr){
  var sum = function(){
    return arr.reduce(function(x,y){
      return x+y;
    });
  }
  return sum;
}
`</pre>

当我们调用`f = lazy_sum()`时会返回保存了参数 arr 的 sum 函数, 直到通过 f()调用 sum 函数前都不会进行求和运算, 可以节约资源.

**需要注意的一点**是每一次调用 lazy_sum() 都会返回一个新的函数, 彼此相互独立, 各自占用内存空间
同时要注意, 尽管每次生成并返回的都是新的 sum 函数, 但是 lazy_sum() 的局部变量是通用的, 可参考**类方法**与**实例**的关系, 这是使用闭包的目的之一

**更要注意**:

<pre>`function count(){
  var arr = [];
  for(var i = 1; i&lt;=3; i++){
    arr.push(function(){
        return i*i;
      });
  }
  return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在这个例子中, f1(), f2(), f3()的返回值都是16, 原因在于:
每一次循环都向 arr 推入一个(function(){})(i), 但都没有计算, 三次循环后 i 变成了4, 所以f1, f2, f3的函数接受的局部变量的值都是4.
由此可知闭包保留的是变量, 而不是变量的值.
如果一定要引用循环变量, 记得创建一个变量用于保存现场

闭包可以用于实现装饰器

RegExp in JS

Literal

/patter/attributes

new RegExp

new RegExp(pattern, attribtues)

pattern

RegExp Attribtues

  • g: global
  • i: ignoreCase
  • m: multi-lines
  • source: literal text
  • lastIndex: mark the index where to start next match

Example

var re = new RegExp('^[0-9]+$','g');
console.log(re.source); //^[0-9]+$
`</pre>

<pre>`var re = new RegExp('\\d{3}','g');
var str = '123a456b';
console.log(re.lastIndex); // 0
re.exec(str); //return ['123']
console.log(re.lastIndex); // 3
re.exec(str); // ['456']
console.log(re.lastIndex); //7
re.exec(str);
console.log(re.lastIndex) // 0, reset to 0 if exceed.
re.lastIndex = 3; //Set Start Index
re.exec(str); // ['456']

RegExp Methods

re.compile(re) //execute RegExp in scripts
re.exec(str) //return an Array including String, or null.
re.test(str) => true/false

String Methods

str.search(re,subStr)
str.match(re) => Array including String
str.replace(re,str) => new String
str.split(re)

JS 获取 Input File Name

获得文件路径: var filePath = document.getElementById("file").value

截取文件名:

  • 保留后缀:

    *   `var fileName = filePath.split('\\').pop()`
    
    • var fileName = filePath.substring(filePath.lastIndexOf('\\')+1)
  • 不保留后缀:

    *   `var fileName = filePath.split(/\.|\\/).slice(-2,-1)[0]`
    
    • var fileName = filePath.substring(filePath.lastIndexOf("\\")+1,filePath.lastIndexPathOf("\."))
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×