Natives

Array

1
2
3
new Array(1, 2, 3);// [1, 2, 3]
Array(1, 2, 3);// [1, 2, 3]
[1, 2, 3];//[1, 2, 3]

以上是3种, 初始化数组的方式

1
2
3
var arr = new Array(3);
arr.length;//3
arr;//[empty x 3]

以上是一个奇怪的现象, 当我们给数组初始化时, 只传递一个数值时, 它不是初始化一个数组包含这个值, 而是初始化了长度为这个值的数组

1
2
3
4
5
var a = new Array( 3 );//[empty x 3]
var b = [undefined, undefined, undefined ];//[undefined, undefined, undefined ]
var c = [];
c.length = 3; //[empty x 3]
var d = [, , ,]; //[empty x 3]

以上数组, 看似好像都是3个位置的数组, 但其实他们的值不全一样, 请看下面

1
2
a.map(function(v, key){ return key; }); // [ undefined x 3 ]
b.map(function(v, key){ return key; }); // [ 0, 1, 2 ]

以上, 我们发现a居然没有key值, 所以以后要undefined填充的数组使用b的方式声明

1
2
var a = Array.apply( null, { length: 3 } );
a; // [ undefined, undefined, undefined ]

以上是快速获取由undefined填充的数组

总结: 我们应该尽量避免使用对象形式创建数组, 应该使用 var arr = [] 这种字面量形式

Object(..), Function(..), and RegExp(..)

字面量的/正则表达式/比 new RegExp方式性能会好很多, 因为js引擎会对其进行提前编译和缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function isThisCool(vals,fn,rx) {
vals = vals || Array.prototype;
fn = fn || Function.prototype;
rx = rx || RegExp.prototype;
return rx.test(
vals.map( fn ).join( "" )
);
}

isThisCool(); // true

isThisCool(
["a","b","c"],
function(v){ return v.toUpperCase(); },
/D/
);// false

如上, 是一种初始化类似的方式, 比[], function(){}, /(?:)/在性能上会好一点, 但是尽量避免有修改属性的操作, 因为那样有可能影响到元素属性, 导致bug

Values

Arrays

1
2
3
4
var arr = [];
arr[0] = 0;
arr[2] = 2;
arr.length; //3

如上当我们跳过1索引位, 但是数组的长度还是变成了3而不是2, 索引1位置的值为undefined

1
2
3
4
5
var arr = [];
arr["hello"] = "world";
arr.length; //0
arr["2"] = 2;
arr.length; //3

如上当设置为字符串为key时, 不会算在数组长度里, 但是如果设置的是一个字符串的数值, 会为强转成数值

1
2
3
Array.prototype.slice.call(arrayLike);
//等价于es6
Array.from(arrayLike)

如上是将类数组(dom对象数组或者函数的arguments参数数组)的数组转换为数组

Strings

字符串, 有点像字符数组, 他们有一些公用方法, 但不完全一样, 比如字符串是无法改变自身位置, 但数组是可以的

1
2
3
4
5
var str = "hello";
str.concat("~~", "world");//hello~~world

str.join;//undefined
Array.prototype.join.call(str, '-');//h-e-l-l-o

如上我们发现我们能使用部分数组的方法, 但是不能使用有些数组方法(比如reverse), 这是为什么呢? 因为join方法是返回一个新数组的, 而reverse是改变数组本身, 又因为字符串是无法改变自身位置的, 所以reverse无法使用

##Numbers

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
var num = .42;
//等价于如下
num = 0.42

var num2 = 42.;
//等价于如下
num2 = 42.0;

var num3 = 4E2; //400

//.toFiex保留几位小数
var num4 = 42.59;
num4.toFixed(0)//43
num4.toFixed(1)//42.6
num4.toFixed(2)//42.59
num4.toFixed(4)//42.590
num4.toFixed(5)//42.5900

//.toPrecision保留几位数
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"

1
2
3
0x12//十六进制
0o12//八进制 在非严格模式下可以使用012, 但已废弃, 不建议再使用
0b11//二进制
1
2
3
4
5
6
7
8
0.1 + 0.2 == 0.30000000000000004

function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b );

如上得知, js的小数计算结果有偏差, 所以可以使用Number.EPSILON来计算是否相等

1
2
3
4
Number.MAX_VALUE;//1.798e+308
Number.MIN_VALUE;//5e-324
Number.MAX_SAFE_INTEGER;//9007199254740991= 2^53 - 1
Number.MIN_SAFE_INTEGER;//-9007199254740991

js整数最大用53位表示, 所以如果计算64位数时需要额外引入库

1
2
3
Number.isInteger( 42 );     // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false

undefined

undefined是一个标识符, 而null是关键字

1
2
3
4
5
6
function foo() {
"use strict";
var undefined = 2;
console.log( undefined ); // 2
}
foo()
1
2
var foo = void 0;
foo;//undefined

可以通过void获取undefined值, void _ 会一直返回undefined值

NaN - Not a Number

1
2
3
typeof NaN // number

NaN == NaN; //undefined
1
2
3
4
isNaN(NaN); //true
isNaN(1); //false
isNaN('1'); //false
isNaN('hello'); //true

以上得知, isNaN是用来判别是不是数字, 如果不是数字就返回true, 否则就是true

1
2
3
4
Number.isNaN(NaN); //true
Number.isNaN(1); //false
Number.isNaN('1'); //false
Number.isNaN('hello'); //false

es6提供的Number.isNaN修复了这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}

if (!Number.isNaN) {
Number.isNaN = function(n) {
//只有NaN不等于自身
return n !== n;
};
}

两种es6 isNaN的polyfill

Zeros

js里有0和-0, 它的用处不是很大, 但是在某些运动时刻, 可以通过正负值, 来判断运动方向

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 0 / -3; // -0
var b = 0 * -3; // -0

a.toString(); //"0"
a + ""; //"0"
String( a ); //"0"

JSON.stringify( a ); // "0"
JSON.parse( "-0" ); // -0

Number( "-0" ); // -0

-0 === 0;//true
1
2
3
4
5
6
7
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 );// true
isNegZero( 0 / -3 );// true
isNegZero( 0 );// false

可以通过上面的方法判断是否为负0

Special Equality

es6提供的提供了Object.is方法可以来判断两值是否相等

1
2
3
4
5
6
var a = 2 / "foo";
var b = -3 * 0;

Object.is( a, NaN ); //true
Object.is( b, -0 ); //true
Object.is( b, 0 ); // false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (!Object.is) {
Object.is = function(v1, v2) {
// test for `-0`
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// test for `NaN`
if (v1 !== v1) {
return v2 !== v2;
}
// everything else
return v1 === v2;
};
}

polyfill如上

漫谈React

事件系统

事件委派

它并不会把事件处理函数直接绑定到 真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监 听器上维持了一个映射来保存所有组件内部的事件监听和处理函数

在React中使用原生事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NativeEventDemo extends Component { 
componentDidMount() {
this.refs.button.addEventListener('click', e => { this.handleClick(e);});
}
handleClick(e) {
console.log(e);
}
componentWillUnmount() {
this.refs.button.removeEventListener('click');
}
render() {
return <button ref="button">Test</button>;
}
}

组件通信

跨级组件通信通信

父级组件申明getChildContext方法和childContextTypes对象属性, 子组件申明contextTypes再可以通过this.context跨级获取对应的属性, 类似全局变量的概念, 但是这样比较混乱, react不建议这样使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Parent extends Component {
static childContextTypes = {
color: PropTypes.string,
}
getChildContext() {
return {
color: 'red',
};
}

render() {
return <Son />
}
}

class Son extends Component {
static contextTypes = {
color: PropTypes.string,
};
render() {
return {this.context.color}
}
}

没有嵌套关系的组件通信

通过EventEmitter对象

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
import { EventEmitter } from 'events';
const emitter = new EventEmitter();
class App extends Component {
componentDidMount() {
this.btnClick = emitter.on('btnClick', (data) => {
console.log(data);
alert(data)
});
}

componentWillUnmount() {
emitter.removeListener(this.btnClick);
}
}

class OtherComp extends Component {
constructor(props) {
super(props);
}
onClickEvent() {
emitter.emit('btnClick', 'click...')
}
render() {

return <button onClick={this.onClickEvent.bind(this)}>按钮</button>
}
}

高阶组件

属性代理

1
2
3
4
5
6
7
8
9
10
import React, { Component } from 'React';
const MyContainer = (WrappedComponent) =>
class extends Component {
render() {
const newProps = {
text: newText,
};
return <WrappedComponent {...this.props} {...newProps} />;
}
}
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
import React, { Component } from 'React';
const MyContainer = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
this.onNameChange = this.onNameChange.bind(this);
}

onNameChange(event) {
this.setState({
name: event.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
},
}
return <WrappedComponent {...this.props} {...newProps} />;
}
}

@MyContainer
class MyComponent extends Component {
render() {
return <input name="name" {...this.props.name} />;
}
}

通过以上的封装,我们就得到了一个被控制的 input 组件。

反向代理

高阶组件集成传递进来的组件WrappedComponent, 然后通过super去反向调用

1
2
3
4
5
6
7
const MyContainer = (WrappedComponent) => 
class extends WrappedComponent {
render() {
return super.render();
}
}
}

组件性能优化

PureRender

1
2
3
4
5
6
<Account style={{ color: 'black' }} />

//优化为如下

const defaultStyle = {{color: 'black'}};
<Account style={this.props.style || defaultStyle} />

如上, 我们知道,每次调用 React 组件其实都会重新创建组件。就算传入的数组或对象的值没有改变, 但它引用的地址改变, 因此对象和数组类型的数据, 应该用一个变量传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class NameItem extends Component { 
render() {
//翻译成jsx为: <Item children={React.createElement('span', {}, 'Arcthur')}/>
return ( <Item>
<span>Arcthur</span> <Item/>
)
}
}

import PureRenderMixin from 'react-addons-pure-render-mixin';
class NameItem extends Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return ( <Item>
<span>Arcthur</span> </Item>
);
}
}

如上, Item 组件不论什么情况下都会重新渲染。那么,怎么避免 Item 组件的重复渲染呢? 很简单,我们给 NameItem 设置 PureRender

Immutable

Immutable提供了很多类似es6工具方法, 它最大的亮点是提供了Map(对象), List(数据)这2个方法, 它可以得到不会被改变的对象和数组, 就可以防止数据被在某些黑盒里修改

1
2
3
4
5
6
7
8
9
10
11
let a = Map({
select: 'users',
filter: Map({ name: 'Cam' }),
});
let b = a.set('select', 'people');
a === b // false

let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2; // => false
Immutable.is(map1, map2); // => true

以上为Immutable的map简单用法, Immutable.is 比较的是两个对象的 hashCode 或 valueOf(对于 JavaScript 对象)。由于 Immutable 内部使用了 trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。 这样的算法避免了深度遍历比较,因此性能非常好。

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
import React, { Component } from 'react'; 
import { is } from 'immutable';

class App extends Component {
shouldComponentUpdate(nextProps, nextState) {
const thisProps = this.props || {};
const thisState = this.state || {};

if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
}

for (const key in nextProps) {
if (nextProps.hasOwnProperty(key) && !is(thisProps[key], nextProps[key])) {
return true;
}
}

for (const key in nextState) {
if (nextState.hasOwnProperty(key) && !is(thisState[key], nextState[key])) {
return true;
}
}

return false;
}
}

如上优化shouldComponentUpdate, Immutable.js提供了简洁、高效的判断数据是否变化的方法,只需 === 和 is 比较就能知 道是否需要执行 render,而这个操作几乎零成本,所以可以极大提高性能

key

我们迭代渲染数组时, 如果不给组件设置key或是设置了相同的key会有警告, 但是设置的key, 尽量根据组件自身属性而产生的唯一性key, 比如id, 尽量避免使用数组的key, 这样效率会很低, 因为所有组件基本都会重新渲染

1
2
3
4
5
6
7
8
9
import React from 'react';
import createFragment from 'react-addons-create-fragment';
function Rank({ first, second }) {
const children = createFragment({
first: first,
second: second,
});
return ( <ul>{children} </ul>);
}

关于 key,我们还需要知道的一种情况是,有两个子组件需要渲染的时候,我们没法给它们
设 key。这时需要用到 React 插件 createFragment 来解决, 如上

react-addons-perf 性能检测工具

通过 Perf.start() 和 Perf.stop() 两个 API 设置 开始和结束的状态来作分析

  • Perf.printInclusive(measurements):所有阶段的时间。
  • Perf.printExclusive(measurements):不包含挂载组件的时间,即初始化 props、state,调用 componentWillMount 和 componentDidMount 方法的时间等。
  • Perf.printWasted(measurements):监测渲染的内容保持不变的组件(可以查看哪些组件
    没有被 shouldComponentUpdate 命中)。

初入React世界

React生命周期

  • constructor
  • static getDerivedStateFromProps
  • componentWillMount / UNSAFE_componentWillMount
  • render
  • componnetDidMount
  • componentWillReceiveProps / UNSAFE_componentWillReceiveProps
  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate / UNSAFE_componentWillUpdate
  • render
  • getSnapshotBeforeUpdate
  • componnetDidUpdate
  • componentWillUnmount
  • componentDidCatch

componnetDidMount

绑定事件, ajax请求尽量在这个阶段调用; 此阶段虽然在render之后被调用, 实质上仅仅生成了dom树, 还没有被渲染到页面上, 因此在调用setState, 页面上不会出现重复渲染

shouldComponentUpdate

此阶段控制页面是否重新渲染; 可以通过继承 React.PureComponent, 它实现了prop和state的浅比较; react不推荐我们在这阶段进行深度比较, 或是通过json.stringify进行比较, 这样很耗性能;

getSnapshotBeforeUpdate

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
class ScrollingList extends React.Component {
listRef = React.createRef();

getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the current height of the list so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
return this.listRef.current.scrollHeight;
}
return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
if (snapshot !== null) {
this.listRef.current.scrollTop +=
this.listRef.current.scrollHeight - snapshot;
}
}

render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}

componentDidCatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>

ReactDom

ReactDOMServer.renderToString(element)

在服务端渲染react的html, 加速首屏渲染和SEO优化

1
2
import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToString(element)

ReactDOM.hydrate(element, container[, callback])

可以对服务器渲染的容器挂载事件

React 与 DOM

  • findDOMNode 需要react节点
1
2
3
4
5
6
7
8
9
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentDidMount() {
// this 为当前组件的实例
const dom = ReactDOM.findDOMNode(this);
}
render() {}
}
  • unmountComponentAtNode 卸载react节点

ReactDOM 的不稳定方法

  • render: ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback)
  • unstable_renderSubtreeIntoContainer: ReactMount._renderSubtreeIntoContainer(parentComponent,
    nextElement, container, callback)

unstable_renderSubtreeIntoContainer 与 render 方法很相似,但 render 方法缺少一个插入某个节点的参数

ReactDOM.render 与 ReactDOM.createPortal 的区别

  1. createPortal 可以获取父级的context
  2. createPortal 的事件可以被传递给父级

api

Page Visibility

  • document.hidden 表示页面是否隐藏
  • document.visibilityState: 有4个状态的值
  • visibiliteChagne事件
1
2
3
4
5
document.addEventListener('visibiliteChagne', function(event) {
if (document.hidden) {
//do something
}
})

Geolocation API

获取用户地理位置

1
2
3
4
5
6
7
8
9
navigator.geolocation.getCurrentPosition(function(position) {
console.log(position.coords.latitude, position.coords.longitude);
}, function(error) {
console.log(error)
}, {
enableHighAccuracy: true, //尽可能使用精确的位置信息
timeout: 5000,//等待位置信息的最长时间
maximumAge: 25000//上一次获取的坐标信息的有效时间
})
  • watchPosition方法和getCurrentPosition类似, 但是它会定时调用getCurrentPosition的效果相同
  • clearWatch 取消监听
1
2
3
4
5
6
7
8
var watchId = navigator.geolocation.watchPosition方法和getCurrentPosition类似(function(position) {
console.log(position.coords.latitude, position.coords.longitude);
}, function(error) {
console.log(error)
})

//取消监听
clearWatch(watchId);

FileReader 类型

可以异步读取上传文件中的数据

  • readAsText(file, encoding) 以纯文本形式读取文件
  • readAsDataURL(file) 读取文件以URI的形式保存
  • readAsBinaryString(file) 获取字符串, 每个字符表示一个字节
  • readAsArrayBuffer(file) 获取ArrayBuffer类型的内容

应用

对象URL

可以不必将数据内容读取到JavaScript中而直接使用, 可以使用window.URL.createObjectURL()方法, 返回字符串, 指向内存地址

1
2
3
4
5
var url = window.URL.createObjectURL(files[0])
var output.innerHTML = '<img src="' + url + '">';

//释放内存
window.URL.revokeObjectURL(url);

读取拖放的文件

html5可以直接拖入文件, 可以通过files = event.dataTransfer.files;获取拖入文件并获取文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var droptarget = document.getElementById("droptarget");
function handleEvent(event) {
var info = "",
output = document.getElemntById("output"),
files, i, len;

EvenUtil.preventDefault(event);
if (event.type == drop) {
files = event.dataTransfer.files;
i = 0;
len = files.length;

while (i < len) {
//获取文件信息
}
}
}
EvenUtil.addHandle(droptarget, "dropenter", handleEvent);
EvenUtil.addHandle(droptarget, "dropover", handleEvent);
EvenUtil.addHandle(droptarget, "drop", handleEvent);

最佳实践

性能

避免全局查找

1
2
3
4
5
6
7
8
function updateUI() {
var imgs = document.getElementByTagName("img");
for (var i=0, len=imgs.length; i < len; i++) {
imgs[i].title = document.title + " images " + i;
}
var msg = document.getElementById("msg");
msg.innerHTML = "update complete.";
}

上面代码包含了3次对全局document对象的引用, 如果for循环多次就会有多次的document作用域链的查找. 我们可以通过创建一个指向document对象的局部变量, 这边就避免了每次全局作用域的查找, 修改如下

1
2
3
4
5
6
7
8
9
10
function updateUI() {
var doc = document;
var imgs = doc.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++) {
imgs[i].title = doc.title + " images " + i;
}

var msg = doc.getElmentById("msg");
msg.innerHTML = "update complete.";
}

避免with语句

1
2
3
4
5
function updateBody() {
with (document.body) {
innerHTML = "hello world";
}
}

with会创建自己的作用域, 由于额外的作用域查找, 在with里的代码肯定会比外面的代码执行的慢, 建议使用局部变量来完成相同的事情而不引入新的作用域.

1
2
3
4
function updateBody() {
var body = document.body;
body.innerHTML = "hello world"
}

离线应用与客户端存储

离线检测

navigator.onLine检测浏览器能否访问网络; 也可以监听online, offline事件来获取网络变化情况

1
2
3
4
5
6
window.online = function(event) {

}
window.offline = function(event) {

}

应用缓存

applicationCache专为离线web设计, applicationCache的status能够检测缓存情况

1
2
3
4
5
6
7
8
9
10
//将缓存与页面关联起来
<html manifest="/offline.manifest">

//缓存状况
console.log(applicationCache.status);

//监听缓存变化情况
applicationCache.updateready = function() {
applicationCache.swapCache();
}
1
2
3
4
5
6
7
8
//查看cookie
console.log(document.cookie)

//设置cookie
document.cookie = encodeURIComponent("test") + "=" + encodeURIComponent("value")

//删除cookie 其实就是让时间过期
document.cookie = encodeURIComponent("test") + "=" + encodeURIComponent("value") + "; expires=" + new Date(0)

子cookie

每个域设置的cookie是有上限的, 每条长度也有上限, 各浏览器各不一样; 我们可以设置子cookie增加cookie上限

1
2
//我们可以先获取parent, 先根据&split, 然后通过各sub获取对应的子cookie值
document.cookie = "parent=sub1=val1&sub2=val2"

IE用户数据

1
2
3
4
5
6
7
8
9
10
11
var dataStore = document.getElementById("dataStore");
dataStore.setAttribute("name", "test");
dataStore.setAttribute("book", "val");
dataStore.save("bookInfo");

//下一次加载页面 调用load 即可加载数据
dataStore.load("book")
//获取
dataStore.getAttribute("name");
//删除
dataStore.removeAttribute("name")

数据存储

浏览器对每个源存储的大小在 2.5M-5M之间

storage事件

setItem, clear, removeItem都会触发storage事件

1
2
3
window.addEventListener('storage', function(event) {
console.log(event.domain)
})

IndexedDB

数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//打开一个名为admin的数据库
var database;
var request = indexedDB.open("admin");
request.onsuccess = function(event) {
console.log("success")
database = event.target.result;
}
request.onerror = function(event) {
console.log(event.target.errorCode)
}

//给数据库设置版本号, 默认是没有版本号的
if (database.version != "1.0" && database.serVersion) {
request = database.serVersion("1.0");
request.onerror = function(event) {
console.log(event.target.errorCode);
}
request.onsuccess = function(event) {
console.log(database.version)
}
} else {
console.log(database.version)
}

对象存储空间

  • database.createObjectStore(表名, 指定主键)
  • store.add(obj) 添加数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var user = {
    username: "007",
    firstName: "James",
    lastName: "Bond",
    password: "foo"
    }

    var store = database.createObjectStore("users", {keypath: "username"})
    store.add(user)

事物

  • database.transaction([表名, …], 访问模式)
    1
    2
    3
    var transaction = database.transaction(["users", "anotherStore"], READ_WRITE)
    //获取相应的表 获取相应的数据
    transaction.objectStore("users").get("007")

使用游标查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var store = database.transaction(["users", "anotherStore"], READ_WRITE).objectStore("users"), value, updateRequest;
request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
if (cursor.key == "foo") {
value = cursor.value;
value.password = "magic"

updateRequest = cursor.update(value);
updateRequest.onsuccess = function() {}
updateRequest.onerror = function() {}
}
//游标移动到下一个
cursor.continue();
}
}

键范围

1
2
3
4
var IDBKeyRange = window.IDBKeyRange

//和上面的get("007") 功能类似
IDBKeyRange.only("007")

并发问题

当同一个来源的另一个标签页调用setVersion()时, 会触发onversionchange事件, 我们此时需要立即将数据库, 这样另一个页面的执行才能成功, 因为只有一个标签页使用数据库才能修改版本, 如果有其他tab开着数据库导致改变版本失败, 会触发onblock事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var database;
var request = indexedDB.open("admin");
request.onsuccess = function(event) {
console.log("success")
database = event.target.result;

database.onversionchange = function() {
database.close()
}

database.onblock = function() {
alert("请先关闭其他tab的数据库")
}
}

限制

数据库也是有大小限制的, 根据每个浏览器而不同, 大小在5mb-50mb之间

高级技巧

高级函数

安全的类型检测

使用typeof检测数组函数等返回都是object, 无法获取正确类型, 所以可以使用下面的方法获取正确的类型信息

1
2
var value = []
Object.prototype.toString.call(value)//"[object Array]"

安全构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Polygon(sides) {
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function() {
return 0;
}
} else {
return new Polygon(sides);
}
}

function Rectangle(width, height) {
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function() {
return this.width * this.height;
}
}

Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
rect.sides;//2

惰性载入函数

有时候函数的具体逻辑, 会根据环境不同而不同, 因此我们可以使用惰性载入函数, 在页面加载的时候就能自运行获取正确的函数, 而不用再调用时在去判断生成

1
2
3
4
5
6
7
8
9
10
11
var createXHR = (function() {
if (typeof XMLHttpRequest != "undefined") {
return function() {
return new XMLHttpRequest();
}
} else {
return function() {
//...
}
}
})()

函数绑定

this指向被改变

1
2
3
4
5
6
7
8
var handler = {
message: 'event handle',
handleClick: function(event) {
console.log(this.message);
}
}

EventUtil.addHandler(btn, "click", handler.handleClick); // undefined

使用bind纠正this

1
2
3
4
5
6
7
8
9
10
11
12
var handler = {
message: 'event handle',
handleClick: function(event) {
console.log(this.message);
}
}
function bind(fn, context) {
return function() {
fn.apply(context, arguments)
}
}
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler)); // event handle

函数柯里化

1
2
3
4
5
6
7
8
9
10
function add(num1, num2) {
return num1 + num2;
}

function curriedAdd(num2) {
return add(5, num2);
}

console.log(add((2, 3)))//5
console.log(curriedAdd(3));//8

柯里化

1
2
3
4
5
6
7
8
9
10
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
fn.apply(null, finalArgs);
}
}
var curriedAdd = curry(add, 5);
console.log(curriedAdd(3));//8

防篡改对象

不可扩展对象

Object.preventExtensions可以使对象不能再扩展属性, 但可以对已有属性进行修改删除

1
2
3
4
5
6
7
var obj = {a: 'a', b: 'b', c:'c'};
Object.isExtensible(obj);//true

Object.preventExtensions(obj);
Object.isExtensible(obj);//false
obj.d = 'd';
consolelog(obj);//{a: 'a', b: 'b', c:'c'}

密封对象

seal的对象, [[Configurable]]被设置为false, 因此不能删除属性和方法, 也不能新增属性和方法, 但可以修改属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
var person = {name: 'zhichen'};
Object.isExtensible(person);//true
Object.isSealed(person);//false

Object.seal(person);
Object.isExtensible(person);//false
Object.isSealed(person);//true

person.age = 18;
person.name = 'lc'
delete person.name;
console.log(person);//{name: 'lc'}

冻结的对象

freeze的对象, 不能进行扩展, [[writable]]特性会被设置为false, 如果定义[[Set]]函数, 访问器属性仍可以写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {name: 'zhichen'};
Object.isExtensible(person);//true
Object.isSealed(person);//false
Object.isFrozen(person);//false

Object.freeze(person);
Object.isExtensible(person);//false
Object.isSealed(person);//true
Object.isFrozen(person);//true


person.age = 18;
person.name = 'lc'
delete person.name;
console.log(person);//{name: 'zhichen'}

高级定时器

setTimeoutsetInterval 加入设置时间为150ms, 不表示在150ms后立即执行, 而是在150ms后被加入到执行队列, 如果队列没有其他任务, 就立即执行, 如果有其他任务, 等其他任务执行完毕后才能执行

重复的定时器

setInterval为了避免因为任务等待导致在同一时间重复执行, 因此setInterval被加入进程队列前会先检测是否有次任务, 如果有此任务, 就不会再被添加到进程队列中, 所以有些间隔会被跳过

Yielding Processes

如果要操作一个数组里的数据, 会导致阻塞, 如果可以改成异步的话, 可以使用如下方式

1
2
3
4
5
6
7
8
9
10
11
function chunk(array, process, context) {
setTimeout(function() {
var item = array.shift();
process.call(context, item);

//如果还有数据 就再调自身循环
if (array.length > 0) {
setTimeout(arguments.callee, 100)
}
}, 100)
}

JSON

解析与序列化

序列化选项

JSON.stringify第二个参数可以是个数组, 过滤掉其他参数, 只留下指定参数;

1
2
var obj = {a: 'a', b: 'b', c:'c'}
JSON.stringify(obj, ['a','c']) //"{"a":"a","c":"c"}"

第二个参数也可以是个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var book = {
"title": "Professional JavaScript",
"authors": ["Nicholas C. Zakas"],
edition: 3,
year: 2001
};
var jsonText = JSON.stringify(book, function(key, value) {
switch(key) {
case "authors":
return value.join(",");
case "year":
return 5000;
case "edition":
return undefined;
default:
return value;
}
});
jsonText; //"{"title":"Professional JavaScript","authors":"Nicholas C. Zakas","year":5000}"

第三个参数, 如果是数值可以指定缩进几个空格(最大缩进10), 也可以指定是字符串

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
var book = {
"title": "Professional JavaScript",
"authors": ["Nicholas C. Zakas"],
edition: 3,
year: 2001
};
var jsonText = JSON.stringify(book, null, 4);
/**
"{
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
"edition": 3,
"year": 2001
}"**/

JSON.stringify(book, null, '-');
/**
"{
-"title": "Professional JavaScript",
-"authors": [
--"Nicholas C. Zakas"
-],
-"edition": 3,
-"year": 2001
}"*/
  • toJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var book = {
"title": "Professional JavaScript",
"authors": ["Nicholas C. Zakas"],
edition: 3,
year: 2001,
toJSON: function() {
if (this.edition > 3) {
return 'big';
} else {
return 'small';
}
}
}

JSON.stringify(book) //"small"

解析选项

JSON.parse()