stone的博客


  • 首页

  • 分类

  • 关于我

  • 归档

  • 标签

解决.gitignore无法忽略文件的办法

发表于 2017-04-26   |   分类于 other   |  

遇到的问题

在项目中使用了git作为版本控制工具,大家都知道在git中我们可以编写.gitignore文件来忽略我们不想提及的文件。但是在使用.gitignore的时候经常会遇到下面这两个问题:

1、当次编写的.gitignore文件无法忽视前一次已经提交掉的文件,换句话说,我在上一次提交的时候添加了sb.js文件,而我在当前提交的时候,在.gitignore忽略规则中编写了

1
2
# .gitignore 文件
sb.js

然而我先前提交掉的sb.js已经存在我的仓库里面了,而我的本意是想在本次提交之后清除仓库中的sb.js文件的,并没有达到我的预期。

2、 编写好.gitignore文件之后,规则中的文件并没有被正确忽略,或者说自己编写的规则没有让文件达到被忽略的效果。比如,我编写了这样的忽略规则

1
2
3
# .gitignore 文件
a.txt
b.txt

但是可能git只忽略掉了a.txt,而b.txt没被忽略掉,这种情况也同样违背了我的初衷。

解决方法

我们只需要在git项目的根目录执行

1
git rm -r --cached .

先把缓存清空掉,然后再重新

1
git add -A

把所有的文件再提交一次就ok了。这个时候应该就可以看到我们的.gitignore可以正常运作了。

Airbnb React/JSX 编码规范

发表于 2017-04-11   |   分类于 react   |  

算是最合理的React/JSX编码规范之一了

内容目录

  1. 基本规范
  2. Class vs React.createClass vs stateless
  3. Mixins
  4. 命名
  5. 声明模块
  6. 代码对齐
  7. 单引号还是双引号
  8. 空格
  9. 属性
  10. Refs引用
  11. 括号
  12. 标签
  13. 函数/方法
  14. 模块生命周期
  15. isMounted

Basic Rules 基本规范

  • 每个文件只写一个模块.
    • 但是多个无状态模块可以放在单个文件中. eslint: react/no-multi-comp.
  • 推荐使用JSX语法.
  • 不要使用 React.createElement,除非从一个非JSX的文件中初始化你的app.

创建模块

Class vs React.createClass vs stateless

  • 如果你的模块有内部状态或者是refs, 推荐使用 class extends React.Component 而不是 React.createClass.
    eslint: react/prefer-es6-class react/prefer-stateless-function

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // bad
    const Listing = React.createClass({
    // ...
    render() {
    return <div>{this.state.hello}</div>;
    }
    });
    // good
    class Listing extends React.Component {
    // ...
    render() {
    return <div>{this.state.hello}</div>;
    }
    }

    如果你的模块没有状态或是没有引用refs, 推荐使用普通函数(非箭头函数)而不是类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // bad
    class Listing extends React.Component {
    render() {
    return <div>{this.props.hello}</div>;
    }
    }
    // bad (relying on function name inference is discouraged)
    const Listing = ({ hello }) => (
    <div>{hello}</div>
    );
    // good
    function Listing({ hello }) {
    return <div>{hello}</div>;
    }

Mixins

  • 不要使用 mixins.

    为什么? Mixins 会增加隐式的依赖,导致命名冲突,并且会以雪球式增加复杂度。在大多数情况下Mixins可以被更好的方法替代,如:组件化,高阶组件,工具模块等。

Naming 命名

  • 扩展名: React模块使用 .jsx 扩展名.
  • 文件名: 文件名使用帕斯卡命名. 如, ReservationCard.jsx.
  • 引用命名: React模块名使用帕斯卡命名,实例使用骆驼式命名. eslint: react/jsx-pascal-case

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // bad
    import reservationCard from './ReservationCard';
    // good
    import ReservationCard from './ReservationCard';
    // bad
    const ReservationItem = <ReservationCard />;
    // good
    const reservationItem = <ReservationCard />;
  • 模块命名: 模块使用当前文件名一样的名称. 比如 ReservationCard.jsx 应该包含名为 ReservationCard的模块. 但是,如果整个文件夹是一个模块,使用 index.js作为入口文件,然后直接使用 index.js 或者文件夹名作为模块的名称:

    1
    2
    3
    4
    5
    6
    7
    8
    // bad
    import Footer from './Footer/Footer';
    // bad
    import Footer from './Footer/index';
    // good
    import Footer from './Footer';
  • 高阶模块命名: 对于生成一个新的模块,其中的模块名 displayName 应该为高阶模块名和传入模块名的组合. 例如, 高阶模块 withFoo(), 当传入一个 Bar 模块的时候, 生成的模块名 displayName 应该为 withFoo(Bar).

    为什么?一个模块的 displayName 可能会在开发者工具或者错误信息中使用到,因此有一个能清楚的表达这层关系的值能帮助我们更好的理解模块发生了什么,更好的Debug.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // bad
    export default function withFoo(WrappedComponent) {
    return function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
    }
    }
    // good
    export default function withFoo(WrappedComponent) {
    function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
    }
    const wrappedComponentName = WrappedComponent.displayName
    || WrappedComponent.name
    || 'Component';
    WithFoo.displayName = `withFoo(${wrappedComponentName})`;
    return WithFoo;
    }
  • 属性命名: 避免使用DOM相关的属性来用作其他的用途。

    为什么?对于style 和 className这样的属性名,我们都会默认它们代表一些特殊的含义,如元素的样式,CSS class的名称。在你的应用中使用这些属性来表示其他的含义会使你的代码更难阅读,更难维护,并且可能会引起bug。

    1
    2
    3
    4
    5
    // bad
    <MyComponent style="fancy" />
    // good
    <MyComponent variant="fancy" />

Declaration 声明模块

  • 不要使用 displayName 来命名React模块,而是使用引用来命名模块, 如 class 名称.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    export default React.createClass({
    displayName: 'ReservationCard',
    // stuff goes here
    });
    // good
    export default class ReservationCard extends React.Component {
    }

Alignment 代码对齐

  • 遵循以下的JSX语法缩进/格式. eslint: react/jsx-closing-bracket-location

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // bad
    <Foo superLongParam="bar"
    anotherSuperLongParam="baz" />
    // good, 有多行属性的话, 新建一行关闭标签
    <Foo
    superLongParam="bar"
    anotherSuperLongParam="baz"
    />
    // 若能在一行中显示, 直接写成一行
    <Foo bar="bar" />
    // 子元素按照常规方式缩进
    <Foo
    superLongParam="bar"
    anotherSuperLongParam="baz"
    >
    <Quux />
    </Foo>

Quotes 单引号还是双引号

  • 对于JSX属性值总是使用双引号("), 其他均使用单引号('). eslint: jsx-quotes

    为什么? HTML属性也是用双引号, 因此JSX的属性也遵循此约定.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // bad
    <Foo bar='bar' />
    // good
    <Foo bar="bar" />
    // bad
    <Foo style={{ left: "20px" }} />
    // good
    <Foo style={{ left: '20px' }} />

Spacing 空格

  • 总是在自动关闭的标签前加一个空格,正常情况下也不需要换行. eslint: no-multi-spaces, react/jsx-space-before-closing

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // bad
    <Foo/>
    // very bad
    <Foo />
    // bad
    <Foo
    />
    // good
    <Foo />
  • 不要在JSX {} 引用括号里两边加空格. eslint: react/jsx-curly-spacing

    1
    2
    3
    4
    5
    // bad
    <Foo bar={ baz } />
    // good
    <Foo bar={baz} />

Props 属性

  • JSX属性名使用骆驼式风格camelCase.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // bad
    <Foo
    UserName="hello"
    phone_number={12345678}
    />
    // good
    <Foo
    userName="hello"
    phoneNumber={12345678}
    />
  • 如果属性值为 true, 可以直接省略. eslint: react/jsx-boolean-value

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    <Foo
    hidden={true}
    />
    // good
    <Foo
    hidden
    />
  • <img> 标签总是添加 alt 属性. 如果图片以presentation(感觉是以类似PPT方式显示?)方式显示,alt 可为空, 或者<img> 要包含role="presentation". eslint: jsx-a11y/img-has-alt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // bad
    <img src="hello.jpg" />
    // good
    <img src="hello.jpg" alt="Me waving hello" />
    // good
    <img src="hello.jpg" alt="" />
    // good
    <img src="hello.jpg" role="presentation" />
  • 不要在 alt 值里使用如 “image”, “photo”, or “picture”包括图片含义这样的词, 中文也一样. eslint: jsx-a11y/img-redundant-alt

    为什么? 屏幕助读器已经把 img 标签标注为图片了, 所以没有必要再在 alt 里说明了.

    1
    2
    3
    4
    5
    // bad
    <img src="hello.jpg" alt="Picture of me waving hello" />
    // good
    <img src="hello.jpg" alt="Me waving hello" />
  • 使用有效正确的 aria role属性值 ARIA roles. eslint: jsx-a11y/aria-role

    1
    2
    3
    4
    5
    6
    7
    8
    // bad - not an ARIA role
    <div role="datepicker" />
    // bad - abstract ARIA role
    <div role="range" />
    // good
    <div role="button" />
  • 不要在标签上使用 accessKey 属性. eslint: jsx-a11y/no-access-key

    为什么? 屏幕助读器在键盘快捷键与键盘命令时造成的不统一性会导致阅读性更加复杂.

    1
    2
    3
    4
    5
    // bad
    <div accessKey="h" />
    // good
    <div />
  • 避免使用数组的index来作为属性key的值,推荐使用唯一ID. (为什么?)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // bad
    {todos.map((todo, index) =>
    <Todo
    {...todo}
    key={index}
    />
    )}
    // good
    {todos.map(todo => (
    <Todo
    {...todo}
    key={todo.id}
    />
    ))}
  • 对于所有非必须的属性,总是手动去定义defaultProps属性.

    为什么? propTypes 可以作为模块的文档说明, 并且声明 defaultProps 的话意味着阅读代码的人不需要去假设一些默认值。更重要的是, 显示的声明默认属性可以让你的模块跳过属性类型的检查.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // bad
    function SFC({ foo, bar, children }) {
    return <div>{foo}{bar}{children}</div>;
    }
    SFC.propTypes = {
    foo: PropTypes.number.isRequired,
    bar: PropTypes.string,
    children: PropTypes.node,
    };
    // good
    function SFC({ foo, bar }) {
    return <div>{foo}{bar}</div>;
    }
    SFC.propTypes = {
    foo: PropTypes.number.isRequired,
    bar: PropTypes.string,
    children: PropTypes.node,
    };
    SFC.defaultProps = {
    bar: '',
    children: null,
    };

Refs

  • 总是在Refs里使用回调函数. eslint: react/no-string-refs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    <Foo
    ref="myRef"
    />
    // good
    <Foo
    ref={(ref) => { this.myRef = ref; }}
    />

Parentheses 括号

  • 将多行的JSX标签写在 ()里. eslint: react/jsx-wrap-multilines

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // bad
    render() {
    return <MyComponent className="long body" foo="bar">
    <MyChild />
    </MyComponent>;
    }
    // good
    render() {
    return (
    <MyComponent className="long body" foo="bar">
    <MyChild />
    </MyComponent>
    );
    }
    // good, 单行可以不需要
    render() {
    const body = <div>hello</div>;
    return <MyComponent>{body}</MyComponent>;
    }

Tags 标签

  • 对于没有子元素的标签来说总是自己关闭标签. eslint: react/self-closing-comp

    1
    2
    3
    4
    5
    // bad
    <Foo className="stuff"></Foo>
    // good
    <Foo className="stuff" />
  • 如果模块有多行的属性, 关闭标签时新建一行. eslint: react/jsx-closing-bracket-location

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // bad
    <Foo
    bar="bar"
    baz="baz" />
    // good
    <Foo
    bar="bar"
    baz="baz"
    />

Methods 函数

  • 使用箭头函数来获取本地变量.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function ItemList(props) {
    return (
    <ul>
    {props.items.map((item, index) => (
    <Item
    key={item.key}
    onClick={() => doSomethingWith(item.name, index)}
    />
    ))}
    </ul>
    );
    }
  • 当在 render() 里使用事件处理方法时,提前在构造函数里把 this 绑定上去. eslint: react/jsx-no-bind

    为什么? 在每次 render 过程中, 再调用 bind 都会新建一个新的函数,浪费资源.

    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
    // bad
    class extends React.Component {
    onClickDiv() {
    // do stuff
    }
    render() {
    return <div onClick={this.onClickDiv.bind(this)} />
    }
    }
    // good
    class extends React.Component {
    constructor(props) {
    super(props);
    this.onClickDiv = this.onClickDiv.bind(this);
    }
    onClickDiv() {
    // do stuff
    }
    render() {
    return <div onClick={this.onClickDiv} />
    }
    }
  • 在React模块中,不要给所谓的私有函数添加 _ 前缀,本质上它并不是私有的.

    为什么?_ 下划线前缀在某些语言中通常被用来表示私有变量或者函数。但是不像其他的一些语言,在JS中没有原生支持所谓的私有变量,所有的变量函数都是共有的。尽管你的意图是使它私有化,在之前加上下划线并不会使这些变量私有化,并且所有的属性(包括有下划线前缀及没有前缀的)都应该被视为是共有的。了解更多详情请查看Issue #1024, 和 #490 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // bad
    React.createClass({
    _onClickSubmit() {
    // do stuff
    },
    // other stuff
    });
    // good
    class extends React.Component {
    onClickSubmit() {
    // do stuff
    }
    // other stuff
    }
  • 在 render 方法中总是确保 return 返回值. eslint: react/require-render-return

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    render() {
    (<div />);
    }
    // good
    render() {
    return (<div />);
    }

Ordering React 模块生命周期

  • class extends React.Component 的生命周期函数:
  1. 可选的 static 方法
  2. constructor 构造函数
  3. getChildContext 获取子元素内容
  4. componentWillMount 模块渲染前
  5. componentDidMount 模块渲染后
  6. componentWillReceiveProps 模块将接受新的数据
  7. shouldComponentUpdate 判断模块需不需要重新渲染
  8. componentWillUpdate 上面的方法返回 true, 模块将重新渲染
  9. componentDidUpdate 模块渲染结束
  10. componentWillUnmount 模块将从DOM中清除, 做一些清理任务
  11. 点击回调或者事件处理器 如 onClickSubmit() 或 onChangeDescription()
  12. render 里的 getter 方法 如 getSelectReason() 或 getFooterContent()
  13. 可选的 render 方法 如 renderNavigation() 或 renderProfilePicture()
  14. render render() 方法
  • 如何定义 propTypes, defaultProps, contextTypes, 等等其他属性…

    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
    import React, { PropTypes } from 'react';
    const propTypes = {
    id: PropTypes.number.isRequired,
    url: PropTypes.string.isRequired,
    text: PropTypes.string,
    };
    const defaultProps = {
    text: 'Hello World',
    };
    class Link extends React.Component {
    static methodsAreOk() {
    return true;
    }
    render() {
    return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>
    }
    }
    Link.propTypes = propTypes;
    Link.defaultProps = defaultProps;
    export default Link;
  • React.createClass 的生命周期函数,与使用class稍有不同: eslint: react/sort-comp

  1. displayName 设定模块名称
  2. propTypes 设置属性的类型
  3. contextTypes 设置上下文类型
  4. childContextTypes 设置子元素上下文类型
  5. mixins 添加一些mixins
  6. statics
  7. defaultProps 设置默认的属性值
  8. getDefaultProps 获取默认属性值
  9. getInitialState 或者初始状态
  10. getChildContext
  11. componentWillMount
  12. componentDidMount
  13. componentWillReceiveProps
  14. shouldComponentUpdate
  15. componentWillUpdate
  16. componentDidUpdate
  17. componentWillUnmount
  18. clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
  19. getter methods for render like getSelectReason() or getFooterContent()
  20. Optional render methods like renderNavigation() or renderProfilePicture()
  21. render

isMounted

  • 不要再使用 isMounted. eslint: react/no-is-mounted

    为什么? isMounted 反人类设计模式:(), 在 ES6 classes 中无法使用, 官方将在未来的版本里删除此方法.

javascript实用代码片段记录

发表于 2017-04-06   |   分类于 javascript   |  

前言

收集工作中实用的javascript相关代码片段,方便日后使用。

元素是否位于当前视窗

1
2
3
4
5
6
7
function isInViewport(el) {
var rect = el.getBoundingClientRect();
return rect.bottom > 0 &&
rect.right > 0 &&
rect.left < (window.innerWidth || document. documentElement.clientWidth) &&
rect.top < (window.innerHeight || document. documentElement.clientHeight);
}

转义html

1
2
3
4
5
6
7
function htmlEntities(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}

按字节截取字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function subSt(str, len){
var newLength = 0;
var newStr = "";
var chineseRegex = /[^\x00-\xff]/g;
var singleChar = "";
var strLength = str.replace(chineseRegex,"**").length;
for(var i = 0;i < strLength;i++) {
singleChar = str.charAt(i).toString();
if(singleChar.match(chineseRegex) != null) {
newLength += 2;
} else {
newLength++;
}
if(newLength > len) {
break;
}
newStr += singleChar;
}
return newStr;
}

删除数组中指定值的项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*获取index*/
function indexOfArray (val,arr){
for (var i=0,len = arr.length;i<len;i++){
if(arr[i] == val) return i;
}
return -1;
}
/*删除节点*/
function removeFromArray(val,arr){
var _index = indexOfArray(val,arr);
if(_index >-1){
arr.splice(_index,1);
}
return arr;
};

for循环简写

1
2
3
4
var array = [1,2,3,4,5];
for(var i=0,c;c=array[i++];){
document.write(c);//1,2,3,4,5
}

生成随机字母数字字符串

36进制是指26个字符加10个数字

1
2
3
4
5
function generateRandomAlphaNum(len) {
var rdmString = "";
for( ; rdmString.length < len; rdmString += Math.random().toString(36).substr(2));
return rdmString.substr(0, len);
}

获取图片宽高

两种方法都可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div>
<img id="j-img" src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png" alt="">
</div>
<script>
var img = document.getElementById('j-img');
//ie9以上
var _height = img.naturalHeight;
var _width = img.naturalWidth;
console.log('高度:'+_height+',宽度'+_width);//高度:38,宽度117
//兼容ie9以下
var imgObj = new Image();
imgObj.src = 'https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png';
img.onload = function(){
console.log('高度:'+this.height+',宽度'+this.width);//高度:38,宽度117
}
</script>

构造函数

定义在构造函数内部的方法和定义在原型上的方法是有区别的。
定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法. 如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的prototype属性上。

私有变量也是如此:

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
function Person(name, family) {
this.name = name;
this.family = family;
/*私有变量*/
var records = [{type: "in", amount: 0}];
/*私有方法,外部访问不到*/
function privateFun(){
//todo...
};
/*特权方法*/
this.addTransaction = function(trans) {
if(trans.hasOwnProperty("type") && trans.hasOwnProperty("amount")) {
records.push(trans);
}
};
this.balance = function() {
var total = 0;
records.forEach(function(record) {
if(record.type === "in") {
total += record.amount;
}
else {
total -= record.amount;
}
});
return total;
};
};
/*公共方法*/
Person.prototype.getFull = function() {
return this.name + " " + this.family;
};
Person.prototype.getProfile = function() {
return this.getFull() + ", total balance: " + this.balance();
};

面向模块

通过命名规范区分私有和公有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var _$$myModule = function(){
/*私有变量*/
var __privateCounter = 0;
/*私有方法*/
function __privateFunction(){
_privateCounter++;
}
/*公有方法*/
function _$addCounter(){
__privateFunction();
}
function _$getCounter(){
return __privateCounter;
}
/*暴露出去*/
return {
addCounter:_$addCounter,
getCounter:_$getCounter
};
}();
/*直接使用*/
_&&myModule.getCounter();

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var getSingle = function(fn){
var result;
return function(){
return result || (result = fn.apply(this,arguments));
}
};
// 使用
var createDiv = function(){
var div = document.createElement('div');
div.innerHTML= "i am div";
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
var createSingleDiv = getSingle(createDiv);
btn.onclick = function(){
var div = createSingleDiv();
div.style.display = 'block';
}

获取url中参数构建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var getUrlSearchObj = function(){
var _search = window.location.search;
if(_search.length > 1){
//用来保存的对象
var _objs = {};
_search = _search.substring(1);
var items = [];
items = _search.split('&');
for(var i=0,len=items.length;i<len;i++){
var _item = items[i].split('=');
var _name = decodeURIComponent(_item[0]);
var _value = decodeURIComponent(_item[1]);
_objs[_name] = _value;
}
return _objs;
}
}
var objs = getUrlSearchObj();
//http://localhost:4000/demo/getUrlSearchObj.html?a=123&b=456#hashisme
document.write(JSON.stringify(objs));//{"a":"123","b":"456"}

trim

删除两侧的空格

1
2
3
p._$trim =function(str){
return (str || '').replace(/(^\s*)|(\s*$)/g, "");
}

等待一段时间

模拟java中的sleep.

1
2
3
4
5
6
7
8
9
10
11
function sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
};
//使用,等待3s:
sleep(3000);

小数运算

JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,计算不精确.

所以需要先升幂再降幂:

1
2
3
4
5
6
7
8
9
10
function add(num1, num2){
let r1, r2, m;
r1 = (''+num1).split('.')[1].length;
r2 = (''+num2).split('.')[1].length;
m = Math.pow(10,Math.max(r1,r2));
return (num1 * m + num2 * m) / m;
}
console.log(add(0.1,0.2)); //0.3
console.log(add(0.15,0.2256)); //0.3756

动态加载css

非首屏的css文件采用动态加载的方式,代码取自seaJs:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/*
* @function 动态加载css文件
* @param {string} options.url -- css资源路径
* @param {function} options.callback -- 加载后回调函数
* @param {string} options.id -- link标签id
*/
function loadCss(options){
var url = options.url,
callback = typeof options.callback == "function" ? options.callback : function(){},
id = options.id,
node = document.createElement("link"),
supportOnload = "onload" in node,
isOldWebKit = +navigator.userAgent.replace(/.*(?:AppleWebKit|AndroidWebKit)\/?(\d+).*/i, "$1") < 536, // webkit旧内核做特殊处理
protectNum = 300000; // 阈值10分钟,一秒钟执行pollCss 500次
node.rel = "stylesheet";
node.type = "text/css";
node.href = url;
if( typeof id !== "undefined" ){
node.id = id;
}
document.getElementsByTagName("head")[0].appendChild(node);
// for Old WebKit and Old Firefox
if (isOldWebKit || !supportOnload) {
// Begin after node insertion
setTimeout(function() {
pollCss(node, callback, 0);
}, 1);
return;
}
if(supportOnload){
node.onload = onload;
node.onerror = function() {
// 加载失败(404)
onload();
}
}else{
node.onreadystatechange = function() {
if (/loaded|complete/.test(node.readyState)) {
onload();
}
}
}
function onload() {
// 确保只跑一次下载操作
node.onload = node.onerror = node.onreadystatechange = null;
// 清空node引用,在低版本IE,不清除会造成内存泄露
node = null;
callback();
}
// 循环判断css是否已加载成功
/*
* @param node -- link节点
* @param callback -- 回调函数
* @param step -- 计步器,避免无限循环
*/
function pollCss(node, callback, step){
var sheet = node.sheet,
isLoaded;
step += 1;
// 保护,大于10分钟,则不再轮询
if(step > protectNum){
isLoaded = true;
// 清空node引用
node = null;
callback();
return;
}
if(isOldWebKit){
// for WebKit < 536
if(sheet){
isLoaded = true;
}
}else if(sheet){
// for Firefox < 9.0
try{
if(sheet.cssRules){
isLoaded = true;
}
}catch(ex){
// 火狐特殊版本,通过特定值获知是否下载成功
// The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
// to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
// in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
if(ex.name === "NS_ERROR_DOM_SECURITY_ERR"){
isLoaded = true;
}
}
}
setTimeout(function() {
if(isLoaded){
// 延迟20ms是为了给下载的样式留够渲染的时间
callback();
}else{
pollCss(node, callback, step);
}
}, 20);
}
}

动态加载js

非首屏的js采用动态加载的方式:

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
/*
* @function 动态加载js文件
* @param {string} options.loadUrl -- js资源路径
* @param {function} options.callMyFun -- 加载后回调函数
* @param {string} options.argObj -- 传递参数
*/
function loadJs(loadUrl,callMyFun,argObj){
var loadScript=document.createElement('script');
loadScript.setAttribute("type","text/javascript");
loadScript.setAttribute('src',loadUrl);
console.log(loadUrl)
document.getElementsByTagName("head")[0].appendChild(loadScript);
//判断服务器
if(navigator.userAgent.indexOf("IE") >=0){
//IE下的事件
loadScript.onreadystatechange=function(){
if(loadScript && (loadScript.readyState == "loaded" || loadScript.readyState == "complete")){
//表示加载成功
loadScript.onreadystatechange=null;
callMyFun()//执行回调
}
}
}
else{
loadScript.onload=function(){
loadScript.onload=null;
callMyFun();
}
}
console.log(argObj);
}

移动端动态计算rem

网易考拉海购方案

详情介绍:http://brizer.top/2016/08/20/%E7%A7%BB%E5%8A%A8web%E9%80%82%E9%85%8D%E6%96%B9%E6%A1%88%E5%8A%A8%E6%80%81%E8%AE%A1%E7%AE%97%E7%89%88/

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
37
38
39
40
41
42
43
44
45
(function(win) {
var remCalc = {};
var docEl = win.document.documentElement,
tid;
/*根据设备屏幕计算rem*/
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
/*浏览器打开,已最大模式呈现*/
if (width > 640) { width = 640 }
var rem = width /640 * 100;
docEl.style.fontSize = rem + "px";
remCalc.rem = rem;
/*解决误差*/
var actualSize = parseFloat(window.getComputedStyle(document.documentElement)["font-size"]);
if (actualSize !== rem && actualSize > 0 && Math.abs(actualSize - rem) > 1) {
var remScaled = rem * rem / actualSize;
docEl.style.fontSize = remScaled + "px"
}
}
/*节流*/
function dbcRefresh() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 100)
}
win.addEventListener("resize", function() { dbcRefresh() }, false);
/*返回上一页到达该页后激活计算*/
win.addEventListener("pageshow", function(e) {
if (e.persisted) { dbcRefresh() }
}, false);
refreshRem();
remCalc.refreshRem = refreshRem;
/*转换方法,方便工程其他位置临时判断*/
remCalc.rem2px = function(d) {
var val = parseFloat(d) * this.rem;
if (typeof d === "string" && d.match(/rem$/)) { val += "px" }
return val
};
remCalc.px2rem = function(d) {
var val = parseFloat(d) / this.rem;
if (typeof d === "string" && d.match(/px$/)) { val += "rem" }
return val
};
win.remCalc = remCalc
})(window);

作用域与闭包:this,var,(function () {})》

发表于 2017-04-06   |   分类于 javascript   |  

知识点

  1. 理解 js 中 var 的作用域
  2. 了解闭包的概念
  3. 理解 this 的指向

课程内容

var 作用域

先来看个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var parent = function () {
var name = "parent_name";
var age = 13;
var child = function () {
var name = "child_name";
var childAge = 0.3;
// => child_name 13 0.3
console.log(name, age, childAge);
};
child();
// will throw Error
// ReferenceError: childAge is not defined
console.log(name, age, childAge);
};
parent();

直觉地,内部函数可以访问外部函数的变量,外部不能访问内部函数的变量。上面的例子中内部函数 child 可以访问变量 age,而外部函数 parent 不可以访问 child 中的变量 childAge,因此会抛出没有定义变量的异常。

有个重要的事,如果忘记var,那么变量就被声明为全局变量了。

1
2
3
4
5
6
function foo() {
value = "hello";
}
foo();
console.log(value); // 输出hello
console.log(global.value) // 输出hello

这个例子可以很正常的输出 hello,是因为 value 变量在定义时,没有使用 var 关键词,所以被定义成了全局变量。在 Node 中,全局变量会被定义在 global 对象下;在浏览器中,全局变量会被定义在 window 对象下。

如果你确实要定义一个全局变量的话,请显示地定义在 global 或者 window 对象上。

这类不小心定义全局变量的问题可以被 jshint 检测出来,如果你使用 sublime 编辑器的话,记得装一个 SublimeLinter 插件,这是插件支持多语言的语法错误检测,js 的检测是原生支持的。

JavaScript 中,变量的局部作用域是函数级别的。不同于 C 语言,在 C 语言中,作用域是块级别的。
JavaScript 中没有块级作用域。

js 中,函数中声明的变量在整个函数中都有定义。比如如下代码段,变量 i 和 value 虽然是在 for 循环代码块中被定义,但在代码块外仍可以访问 i 和 value。

1
2
3
4
5
6
7
8
function foo() {
for (var i = 0; i < 10; i++) {
var value = "hello world";
}
console.log(i); //输出10
console.log(value);//输出hello world
}
foo();

所以有种说法是:应该提前声明函数中需要用到的变量,即,在函数体的顶部声明可能用到的变量,这样就可以避免出现一些奇奇怪怪怪的 bug。

但我个人不喜欢遵守这一点,一般都是现用现声明的。这类错误的检测交给 jshint 来做就好了。

闭包

闭包这个概念,在函数式编程里很常见,简单的说,就是使内部函数可以访问定义在外部函数中的变量。

假如我们要实现一系列的函数:add10,add20,它们的定义是 int add10(int n)。

为此我们构造了一个名为 adder 的构造器,如下:

1
2
3
4
5
6
7
8
9
10
11
12
var adder = function (x) {
var base = x;
return function (n) {
return n + base;
};
};
var add10 = adder(10);
console.log(add10(5));
var add20 = adder(20);
console.log(add20(5));

每次调用 adder 时,adder 都会返回一个函数给我们。我们传给 adder 的值,会保存在一个名为 base 的变量中。由于返回的函数在其中引用了 base 的值,于是 base 的引用计数被 +1。当返回函数不被垃圾回收时,则 base 也会一直存在。

我暂时想不出什么实用的例子来,如果想深入理解这块,可以看看这篇 http://coolshell.cn/articles/6731.html

闭包的一个坑

1
2
3
4
5
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 5);
}

上面这个代码块会打印五个 5 出来,而我们预想的结果是打印 0 1 2 3 4。

之所以会这样,是因为 setTimeout 中的 i 是对外层 i 的引用。当 setTimeout 的代码被解释的时候,运行时只是记录了 i 的引用,而不是值。而当 setTimeout 被触发时,五个 setTimeout 中的 i 同时被取值,由于它们都指向了外层的同一个 i,而那个 i 的值在迭代完成时为 5,所以打印了五次 5。

为了得到我们预想的结果,我们可以把 i 赋值成一个局部的变量,从而摆脱外层迭代的影响。

1
2
3
4
5
6
7
for (var i = 0; i < 5; i++) {
(function (idx) {
setTimeout(function () {
console.log(idx);
}, 5);
})(i);
}

this

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。

在《javaScript语言精粹》这本书中,把 this 出现的场景分为四类,简单的说就是:

  • 有对象就指向调用对象
  • 没调用对象就指向全局对象
  • 用new构造就指向新对象
  • 通过 apply 或 call 或 bind 来改变 this 的所指。

1)函数有所属对象时:指向所属对象

函数有所属对象时,通常通过 . 表达式调用,这时 this 自然指向所属对象。比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
var myObject = {value: 100};
myObject.getValue = function () {
console.log(this.value); // 输出 100
// 输出 { value: 100, getValue: [Function] },
// 其实就是 myObject 对象本身
console.log(this);
return this.value;
};
console.log(myObject.getValue()); // => 100

getValue() 属于对象 myObject,并由 myOjbect 进行 . 调用,因此 this 指向对象 myObject。

2) 函数没有所属对象:指向全局对象

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = {value: 100};
myObject.getValue = function () {
var foo = function () {
console.log(this.value) // => undefined
console.log(this);// 输出全局对象 global
};
foo();
return this.value;
};
console.log(myObject.getValue()); // => 100

在上述代码块中,foo 函数虽然定义在 getValue 的函数体内,但实际上它既不属于 getValue 也不属于 myObject。foo 并没有被绑定在任何对象上,所以当调用时,它的 this 指针指向了全局对象 global。

据说这是个设计错误。

3)构造器中的 this:指向新对象

js 中,我们通过 new 关键词来调用构造函数,此时 this 会绑定在该新对象上。

1
2
3
4
5
6
7
8
var SomeClass = function(){
this.value = 100;
}
var myCreate = new SomeClass();
console.log(myCreate.value); // 输出100

顺便说一句,在 js 中,构造函数、普通函数、对象方法、闭包,这四者没有明确界线。界线都在人的心中。

4) apply 和 call 调用以及 bind 绑定:指向绑定的对象

apply() 方法接受两个参数第一个是函数运行的作用域,另外一个是一个参数数组(arguments)。

call() 方法第一个参数的意义与 apply() 方法相同,只是其他的参数需要一个个列举出来。

简单来说,call 的方式更接近我们平时调用函数,而 apply 需要我们传递 Array 形式的数组给它。它们是可以互相转换的。

1
2
3
4
5
6
7
8
9
10
11
12
var myObject = {value: 100};
var foo = function(){
console.log(this);
};
foo(); // 全局变量 global
foo.apply(myObject); // { value: 100 }
foo.call(myObject); // { value: 100 }
var newFoo = foo.bind(myObject);
newFoo(); // { value: 100 }

javascript中的数组(Array)对象的使用

发表于 2017-04-06   |   分类于 javascript   |  

Js Array的使用

今天系统性的重温下Array对象的知识

定义

数组的定义:一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量。即数组对象用来在单独的变量名中存储一系列的值。

使用数组

创建数组

  • 通过 var arr1 = [1,2,3]
  • 通过 new var arr2 = new Array(1,2,3) (注意:如果里面传入的是一个数字,比如10,是定义一个length为10的数组,每一个元素都是undefined)

读写数组

1
2
3
4
5
6
7
8
9
10
11
// 读取数组
var arr = [1,2,3,4];
for(var i=0;i<arr.length;i++){
console.log(arr[i]);
}
//改变数组值
var arr1 = [];
for(var i=0;i<10;i++){
arr1[i] = i;
}

我们可以用[]读取到索引的数组,同时也可以对他进行赋值改变的操作。

由字符串生成数组

调用字符串对象split()方法通过一个字符串生成一个数组

1
2
3
var str = 'hello world stone';
var arr = str.split(' ');
console.log(arr) //["hello", "world", "stone"]

对数组进行整体操作

把一个数组赋给另一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//浅复制 arr1 改变了 arr 也改变了
var arr = [1,2,3,4];
var arr1 = arr;
//深复制
function copyArr(arr) {
var _arr = [];
for(var i = 0; i < arr.length; i++) {
_arr[i] = arr[i];
}
return _arr;
}
var arr0 = [1,2,3,4];
var arr1 = copyArr(arr0);
console.log(arr1);
// [1, 2, 3, 4]
arr1[4] = 5;
console.log(arr0);
// [1, 2, 3, 4]
console.log(arr1);
// [1, 2, 3, 4, 5]

存取函数

查找元素

indexOf()是最常用的了,他授受一个参数,然后查找数组中元素等于这个参数值的第一个元素,返回对应的索引值,如果没有找到这个参数,返回-1,如果有多个,返回第一个索引值

1
2
3
4
5
var arr = [1,2,3,4,3,5];
console.log( arr.indexOf(2) );
// 1
console.log( arr.indexOf(6) );
// -1

还有一个对应的函数lastIndexOf(),他是从后面查起,对于多个,会返回查找到的第一个索引值。

1
2
3
var arr = [1,2,3,4,5];
console.log(arr.lastIndexOf(2));
// 1

数组的字符串表示

把数组转换成一个字符串有两个方法:toString()和join()前者是生成用,分割开每个元素的字符串,后者可以传入一个参数来指导用空上参数值来隔开。

join() 将数组中所有元素都转化为字符串并连接在一起,默认分隔符是’,’,可以指定分隔符

1
2
3
var arr = ['a','b','c'];
console.log(arr.toString()) //a,b,c
console.log(arr.join('-')) //a-b-c

由已有的数组创建新数组

方法有:concat()和splice()和slice()

  • concat() 接受多个数组作为参数,返回和他们合并的一个数组;
  • splice()截取一个数组的子集返回一个新数组;(会改变原数组)
  • slice()截取一个数组的子集:
1
2
3
4
5
6
var arr1 = [1,2,3,4];
var arr2 = [5,6];
var arr3 = [8];
var arr4 = arr1.concat(arr2, arr3);
console.log(arr4);
// [1, 2, 3, 4, 5, 6, 8]
1
2
3
4
5
6
7
var arr1 = [1,2,3,4,5,6,7,8];
var arr2 = arr1.splice(2,4); // 第一个参数是截取位置开始的索引值,第二个是截取的长度
console.log(arr2) //[3, 4, 5, 6]
var arr3 = [1,2,3,4];
var arr4 = arr3.slice(1,3);
console.log(arr4) //[2, 3]

数组方法

为数组增减元素

  • push()将一个元素添加到数组的末尾,返回被添加的元素;
  • unshift()在数组首部添加一个或者多个元素,添加的顺序从参数尾部向前添加
  • pop()删除数组末尾的一个元素,返回这个元素值;
  • shift()删除数组开头的一个元素,返回这个元素值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 从后面添加
var arr = [1,2,3,4];
arr.push(5); // 5
console.log(arr); //[1, 2, 3, 4, 5]
// 从前面添加
arr.unshift(0); //0
console.log(arr); // [0,1,2,3,4,5]
//删除未尾
var arr1 = [1,2,3,4];
arr1.pop(); // 4
console.log(arr1) // [1,2,3]
//删除开头
arr1.shift(); // 1
console.log(arr1) // [2,3]

在数组中间添加或者删除元素

还是splice()方法,他接受参数:

  • 启始索引
  • 需要删除的数组个数(添加元素的时候可以是0)
  • 需要添加进数组的元素
1
2
3
4
5
6
7
8
9
// 添加
var arr = [1,2,3,4];
arr.splice(2,0,10);
console.log(arr); //[1, 2, 10, 3, 4]
//删除
var arr1 = [1,2,3,4];
arr1.splice(2,1);
console.log(arr1); //[1, 2, 4]

为数组排序

  • reverse()反转数组。数组元素顺序反转,返回反转后的数组,此过程在原数组进行,不创建新数组
  • sort()排序,可以授受一个排序规则函数作为参数
1
2
3
4
5
6
7
8
9
var arr = [1,2,3,4];
arr.reverse();
console.log(arr) //[4, 3, 2, 1]
var arr1 = [23,13,8,60,20];
arr1.sort(function(a1,a2){
return a1 - a2
})
//[8, 13, 20, 23, 60]

迭代方法

不生成新的数组的迭代器方法

  • forEach(),他接受一个函数作为参数,函数接受两个参数:当前元素值、当前元素索引,单纯遍历数组。forEach无法用break中途停止遍历。
1
2
3
4
5
var arr = [1,2,3,4,5,6];
arr.forEach(function(item,index){
console.log(item);
console.log(index);
})
  • every()接受一个返回值为布尔类型的函数为参数,对数组中每个元素使用该函数,如果全都是返回true则该方法返回true,如果有一个以上返回false,那么该方法返回false;
1
2
3
4
5
var arr = [1,2,3,4,5];
var b1 = arr.every(function(item,index){
return item >=1;
});
console.log(b1) //true
  • some()方法,其实和every()方法很像,顾名思义,如果返回的有一个结果是true则返回true,如果全是false,则返回false;
1
2
3
4
5
var arr = [1,2,3,4,5];
var b1 = arr.some(function(item,index){
return item >=1;
});
console.log(b1) //true
  • reduce()方法,他接受两个参数:一个函数、一个初始值(可不传),函数接受4个参数:之前一次遍历的返回值(第一次的话就是初始值)、当前元素值、当前元素索引、被遍历的数组,函数返回一个值。他会从一个累加之开始不断对后面的元素调用该函数;
1
2
3
4
5
var arr = [1,2,3,4,5,6];
var sum = arr.reduce(function(runningTotal, currentValue) {
return runningTotal + currentValue;
}, 10);
console.log(sum); // 31
  • reduceRight()方法,它其实和reduce()方法一样,区别是他是从数组的右边开始累加到左边的。

生成新数组的方法

  • map()方法授受一个函数,这个函数处理每一个元素并返回一个新的值 ,然后方法会返回这些值组成的一个新数组;
1
2
3
4
5
var arr = [1,2,3,4,5];
var arr1 = arr.map(function(item,index){
return item + index
});
console.log(arr1); //[1, 3, 5, 7, 9]
  • filter()方法接受一个函数,函数返回一个布尔型的值,最后方法返回的数组是被函数返回true的元素的集合,顾名思义,就是过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var arr = [1,2,3,4,5,6,7,8,9];
var arr1 = arr.filter(function(item){
return item % 2 === 0
});
console.log(arr1) //[2, 4, 6, 8]
var a = [1,2,3,4,5];
var b = a.filter(function(x){
if(x>3) return true; //筛选大于3的元素
return false;
});
// b = [4,5];
//压缩稀疏数组
var v = a.filter(function(){ return true;});
//压缩并删除undefined 和 null 元素
var v = a.filter(function(x){ return x!==undefined && x!= null;});

二维数组和多维数组

创建二维数组

二维数组类似于一种行和列组成的数据表格。

在javascript里面定义二维数组,可以扩展javascript的数组对象:

1
2
3
4
5
6
7
8
9
10
11
12
Array.matrix = function(numrows, numcols, initial) {
var arr = [];
for(var i = 0; i < numrows; i++) {
var colnums = [];
for(var j = 0; j < numcols; j++) {
colnums[j] = initial;
}
arr[i] = colnums;
}
return arr;
}
var nums = Array.matrix(3,4,9);

处理二位数组中的元素

1
2
3
4
5
6
7
8
var arrs = [[1,2,3], [33,44,55], [12,23,34], [999, 000, 666]];
var total = 0;
for(var row = 0; row < arrs.length; row++) {
for(var col = 0; col < arrs[row].length; col++) {
total += arrs[row][col];
}
}
console.log(total); // 1872

是否改变原数组

  • 1.不改变原有数组:
    • indexOf
    • lastIndexOf
    • toString
    • join
    • concat
    • slice
    • some
    • forEach
    • every
    • filter
    • map
    • reduce
    • reduceRight

  • 2.改变数组:
    • splice
    • push
    • pop
    • shift
    • unshift
    • reverse
    • sort

es6中array的新特性

Array.from()

Array.from()方法可以将两类对象转为直接的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)
下面是一个类似数组的对象,Array.from将它转为真正的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
let arrayLike = {
'0':'a',
'1':'b',
'2':'c',
'3':'d',
'length': 4
}
//es5写法
var arr1 = [].slice.call(arrayLike); //["a", "b", "c", "d"]
// es6写法
let arr2 = Array.from(arrayLike); //["a", "b", "c", "d"]

Array.of()

Array.of()方法用于将一组值,转换为数组

这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

就是之前说的在用 new 构造一个数组的时候,如果传入的是一个数字,那么返回的是一个该长度的数组:

1
2
3
4
var arr0 = new Array(2);
console.log(arr0);
// []
arr0.length; // 2

如果使用 Array.of:

1
2
3
4
5
6
7
var arr0 = Array.of(2);
console.log(arr0);
// [2]
var arr1 = Array.of(1,2,3);
console.log(arr1);
// [1,2,3]

数组实例的copyWithin()

数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

它接受三个参数:

  • target(必需):从该位置开始替换数据。
  • start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
1
2
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

数组实例的find()和find

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

1
2
[1, 4, -5, 10].find((n) => n < 0)
// -5

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

1
2
[1, 4, -5, 10].find((n) => n < 0)
// 2

数组实例fill()

fill()方法使用给定值,填充一个数组

1
2
['a','b','c'].fill(8)
//[8, 8, 8]

fill()方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置

1
2
['a', 'b', 'c'].fill(8, 1, 2)
// ['a', 8, 'c']

数组实例的entries(),keys()和values()

ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

数组实例的includes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。该方法属于ES7,但Babel转码器已经支持。

1
2
3
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true

该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

1
2
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true

给自己的网站加上https:一个快速获取/更新 Let’s encrypt 证书的 shell script

发表于 2017-04-06   |   分类于 linux   |  

给自己的网站免费加上https,调用 acme_tiny.py 认证、获取、更新证书,不需要额外的依赖。

下载到本地

1
2
3
wget https://raw.githubusercontent.com/xdtianyu/scripts/master/lets-encrypt/letsencrypt.conf
wget https://raw.githubusercontent.com/xdtianyu/scripts/master/lets-encrypt/letsencrypt.sh
chmod +x letsencrypt.sh

配置文件

修改letsencrypt.conf文件,只需要修改 DOMAIN_KEY DOMAIN_DIR DOMAINS 为你自己的信息

1
2
3
4
5
6
ACCOUNT_KEY="letsencrypt-account.key"
DOMAIN_KEY="example.com.key"
DOMAIN_DIR="/var/www/example.com"
DOMAINS="DNS:example.com,DNS:whatever.example.com"
#ECC=TRUE
#LIGHTTPD=TRUE

执行过程中会自动生成需要的 key 文件。其中 ACCOUNT_KEY 为账户密钥, DOMAIN_KEY 为域名私钥, DOMAIN_DIR 为域名指向的目录,DOMAINS 为要签的域名列表, 需要 ECC 证书时取消 #ECC=TRUE 的注释,需要为 lighttpd 生成 pem 文件时,取消 #LIGHTTPD=TRUE 的注释。

运行

1
./letsencrypt.sh letsencrypt.conf

注意

需要已经绑定域名到 /var/www/example.com 目录,即通过 http://example.com http://whatever.example.com 可以访问到 /var/www/example.com 目录,用于域名的验证

将会生成如下几个文件
lets-encrypt-x1-cross-signed.pem #这个文件我的没有生成
example.chained.crt # 即网上搜索教程里常见的 fullchain.pem
example.com.key # 即网上搜索教程里常见的 privkey.pem
example.crt
example.csr

在 nginx 里添加 ssl 相关的配置

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
server {
listen 443;
server_name xxx.youmeixun.com;
#root /usr/local/web/xxx-server;
ssl on;
ssl_certificate /etc/nginx/certs/youmeixun.crt;
ssl_certificate_key /etc/nginx/certs/youmeixun.com.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_connect_timeout 1;
proxy_send_timeout 30;
proxy_read_timeout 60;
proxy_pass http://localhost:3001;
}
}

其实主要是这几行:

1
2
3
ssl on;
ssl_certificate /etc/nginx/certs/youmeixun.crt;
ssl_certificate_key /etc/nginx/certs/youmeixun.com.key;

cron 定时任务

1
0 0 1 * * /etc/nginx/certs/letsencrypt.sh /etc/nginx/certs/letsencrypt.conf >> /var/log/lets-encrypt.log 2>&1

定时任务也可以参考这里
https://my.oschina.net/tearlight/blog/738993

定时任务也可以这样写getcert.sh,记得要给执行权限

1
2
/etc/nginx/certs/letsencrypt.sh /etc/nginx/certs/letsencrypt.conf
/usr/sbin/nginx -s reload

编辑crontab

1
crontab -e

重启crond

1
service crond restart

查看是否运行

1
service crond status

node.js的path模块

发表于 2017-04-06   |   分类于 node   |  

node之path模块

在node API中的path对象使用频率很高,掌握了有助于我们处理好一些文件或文件夹的路径。以此记录,共勉。

1
2
//引用该模块
var path = require("path");

1、路径解析,得到规范化的路径格式

1
2
3
4
5
//对window系统,目录分隔为'\', 对于UNIX系统,分隔符为'/',针对'..'返回上一级;/与\\都被统一转换
//path.normalize(p);
var myPath = path.normalize(__dirname + '/test/a//b//../c/utilyou.mp3');
console.log(myPath); //windows: E:\workspace\NodeJS\app\fs\test\a\c\utilyou.mp3

2、路径结合、合并,路径最后不会带目录分隔符

1
2
3
4
5
6
7
8
9
10
11
//path.join([path1],[path2]..[pathn]);
/**
* [path1] 路径或表示目录的字符,
*/
var path1 = 'path1',
path2 = 'path2//pp\\',
path3 = '../path3';
var myPath = path.join(path1, path2, path3);
console.log(myPath); //path1\path2\path3

3、获取绝对路径

1
2
3
4
5
6
7
8
9
10
11
//path.resolve(path1, [path2]..[pathn]);
//以应用程序为起点,根据参数字符串解析出一个绝对路径
/**
* path 必须至少一个路径字符串值
* [pathn] 可选路径字符串
*/
var myPath = path.resolve('path1', 'path2', 'a/b\\c/');
console.log(myPath);//E:\workspace\NodeJS\path1\path2\a\b\c

4、获取相对路径

1
2
3
4
5
6
7
8
9
10
11
12
13
//path.relative(from, to);
//获取两路径之间的相对关系
/**
* from 当前路径,并且方法返回值是基于from指定到to的相对路径
* to 到哪路径,
*/
var from = 'c:\\from\\a\\',
to = 'c:/test/b';
var _path = path.relative(from, to);
console.log(_path); //..\..\test\b; 表示从from到to的相对路径

5、path.dirname(p)

1
2
3
4
// 获取路径中目录名
var myPath = path.dirname(__dirname + '/test/util you.mp3');
console.log(myPath);

6、path.basename(path, [ext])

1
2
3
4
// 获取路径中文件名,后缀是可选的,如果加,请使用'.ext'方式来匹配,则返回值中不包括后缀名;
var myPath = path.basename(__dirname + '/test/util you.mp3', '.mp3');
console.log(myPath);

6、path.basename(path, [ext])

1
2
3
4
// 获取路径中文件名,后缀是可选的,如果加,请使用'.ext'方式来匹配,则返回值中不包括后缀名;
var myPath = path.basename(__dirname + '/test/util you.mp3', '.mp3');
console.log(myPath);

7、path.extname(path)

1
2
3
// 返回路径文件中的扩展名
var myPath = path.extname('/foo/bar/baz/file.txt');
console.log(myPath); // .txt

8、path.sep

1
2
3
4
5
6
7
8
//返回对应平台下的文件夹分隔符,win下为'\',*nix下为'/':
var url1 = path.sep;
var url2 = 'foo\\bar\\baz'.split(path.sep);
var url3 = 'foo/bar/baz'.split(path.sep);
console.log('url1:',url1); // win下为\,*nix下为/
console.log('url2:',url2); // [ 'foo', 'bar', 'baz' ]
console.log('url3:',url3); // win下返回[ 'foo/bar/baz' ],但在*nix系统下会返回[ 'foo', 'bar', 'baz' ]

9、path.delimiter

1
2
3
4
5
6
7
8
9
//返回对应平台下的路径分隔符,win下为';',*nix下为':':
var path = require('path');
var env = process.env.PATH; //当前系统的环境变量PATH
var url1 = env.split(path.delimiter);
console.log(path.delimiter); //win下为“;”,*nix下为“:”
console.log('env:',env); // C:\ProgramData\Oracle\Java\javapath;C:\Program Files (x86)\Intel\iCLS Client\;
console.log('url1:',url1); // ['C:\ProgramData\Oracle\Java\javapath','C:\Program Files (x86)\Intel\iCLS Client\']

web移动端开发遇到的问题汇总

发表于 2017-04-06   |   分类于 web   |  

Q1: IOS中阻止当页面滚动到顶部或底部时页面整体滚动

IOS中浏览页面当页面滚动到顶部或底部时继续滚动会将整个页面滑动,阻止这种效果可以使用下面的函数,参数为最外层容器的选择器

preventPageScroll('.container');
function preventPageScroll(id){
    var el = document.querySelector(id);
    var sy = 0;
    el.addEventListener('touchstart', function (e) {
      sy = e.pageY;
    });
    el.addEventListener('touchmove', function (e) {
      var down = (e.pageY - sy > 0);
      //top
      if (down && el.scrollTop <= 0) {
        e.preventDefault();
      }
      //bottom
      if (!down && el.scrollTop >= el.scrollHeight - el.clientHeight) {
        e.preventDefault();
      }
    });
}

Q2: 使用zepto常见的点透问题

对于点透问题有以下方案可供解决参考

  1. 引入fastclick.js,源码地址,使用参考github

  2. 使用touchend代替tap,并阻止掉默认行为

$(dom).on('touchend',function(ev){
    // to doing
    ev.preventDefault();
});
  1. chrome Android 通过设置 viewport 的 user-scalable=no 可以阻止掉页面的300ms的延时

  2. 延迟一定时间(300ms+)处理

    $(dom).on('tap',function(){
       setTimeout(function(){
           // to doing
       },320)
    });
    

Q3:获取窗口宽度

获取窗口宽度有以下一些方法(这里假设已经将样式 reset):

  • window.innerWidth
  • document.body.clientWidth
  • document.body.offsetWidth

zepto中有

  • \$(window).width()
  • \$(document).width()

曾经遇到过在页面第一次打开时 window 和 document 获取的width 是相同的,但是当再次刷新页面时获取的两个宽度却不再相同,所以为了避免出现样式错乱,尽量使用 document 获取 width

Q4: Android部分机型中给img设置border-radius失效

radius

解决给img外嵌一个元素

//结构
<div>
    <img src="">
</div>
//样式
div{
    display: inline-block;
    border-radius: 50%;
    border: 4px solid #FF7000;
}
img{
    vertical-align: top;
}

Q5: 部分机型中圆角元素,背景颜色会溢出

border-radius

解决给该元素添加下面样式

//
{background-clip:padding-box;}

Q6:圆角使用Animation 做loading动画时,圆角背景溢出

radius-animation

解决使用一个同等大小的圆角图片做蒙版遮罩

//
{
    background-color: #F9CEAC;
    border-radius: 32px 0 0 32px;
    -webkit-mask-image: url(./image/btn_mask.png);
}

蒙版图片btn_mask

Q7: CSS 三角在 Android 上显示为方块

解决:可能是对这个三角使用了圆角,去掉 border-radius 即可

//
{
    border: 10px solid transparent;
    border-left-color: #000;
    /*border-radius: 2px;*/
}

Q8:Android 上使用 svg 作为 background-image 时显示模糊

解决:设置 background-size

//
{
    -webkit-background-size: 100%;
    background-size: 100%;
}

Q9: IOS中 :active 样式不生效

Safari 默认禁用了元素的 active 样式,我们通过声明 touchstart 来覆盖默认事件,就可以让 active 样式重新激活。

解决:

document.addEventListener("touchstart", function() {},false);
//或者 body标签添加 ontouchstart
<body ontouchstart="">

此外,默认点击按钮会有一个灰色的外框,通过这段 CSS 可以清除:

html {
    -webkit-tap-highlight-color: rgba(0,0,0,0);
}

Q10: 多行文字超出截断需要出现省略号方法

单行文本截断并末尾出现省略号一般写法是:

//
{
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

多行文版截断并出现省略号的纯CSS方法,其中的 -webkit-line-clamp: 2 即用来控制文本超出两行时截断并出现省略号。 在使用中如果出现第三行文字露一点头出来的问题,设置合理的 line-height 即可解决

//
{
    display: -webkit-box;
    overflow: hidden;
    text-overflow: ellipsis;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

Q11: 1px 线条、边框

在高版本设备中可以直接设置 0.5px 边框,但底版不能友好支持,一个比较合适的方法:原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。

  • 单条 border

    .hairlines li{
        position: relative;
        border:none;
    }
    .hairlines li:after{
        content: '';
        position: absolute;
        left: 0;
        background: #000;
        width: 100%;
        height: 1px;
        -webkit-transform: scaleY(0.5);
                transform: scaleY(0.5);
        -webkit-transform-origin: 0 0;
                transform-origin: 0 0;
    }
    
  • 四条 border

    .hairlines li{
        position: relative;
        margin-bottom: 20px;
        border:none;
    }
    .hairlines li:after{
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        border: 1px solid #000;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        width: 200%;
        height: 200%;
        -webkit-transform: scale(0.5);
        transform: scale(0.5);
        -webkit-transform-origin: left top;
        transform-origin: left top;
    }
    

样式使用的时候,也要结合 JS 代码,判断是否 Retina 屏

if(window.devicePixelRatio && devicePixelRatio >= 2){
    document.querySelector('ul').className = 'hairlines';
}

更多请参考这篇文章

Q12: 当模块使用系统的横向滚动时,不想显示出系统的滚动条样式

解决:

  • Android

    ::-webkit-scrollbar{ opacity: 0; }
    
  • Ios

    //html
    <div class="wrap">
        <div class="box"></div>
    </div>
    //css
    .wrap{ height: 100px; overflow: hidden; }
    .box{ width: 100%; height: -webkit-calc(100% + 5px); overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }
    

原理:.box 元素的横向滚动条通过其外层元素 .wrap 的 overflow:hiden 来隐藏。 (5px 是 iOS 上滚动条元素的高度)

Q13:横向滚动的元素,滑动时有时图片显示不出来/文字显示不出来

解决:给每个横滑的元素块使用硬件加速

li{ -webkit-transform: translateZ(0); }

Q14:使用 animation 动画后,页面上 overflow:auto 的元素滚动条不能滑动

解决:不使用 translate 方式的动画,换为使用 left/top 来实现元素移动的动画

Q15:上下滑动页面时候,页面元素消失

解决:检查是否使用了 fadeIn 的 animation,如有则 fill-mode 使用 backwards 模式

//
{ -webkit-animation: fadeIn 0.5s ease backwards; }

Q16: 页面上数字自动变成了可以点击的链接

解决:在页面 <head> 里添加

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

Q17:input 在 iOS 中圆角、内阴影去不掉

解决:

input{ -webkit-appearance: none; border-radius: 0; }

Q18:焦点在 input 时,placeholder 没有隐藏

解决:

input:focus::-webkit-input-placeholder{ opacity: 0;}

Q19:input 输入框调出数字输入键盘

解决:

<input type="number" />
<input type="number" pattern="[0-9]*" />
<input type="tel" />

分别对应下图的1、2、3
keyboard_number

需要注意的是,单独使用 type=”number” 时候, iOS 上出现并不是九宫格的数字键盘,如果需要九宫格的数字键盘,可选择使用 2、3 的方法。 1、2、3 在 Android 上均可以唤起九宫格的数字键盘

Q20: rem单位动态设置fontSize

(function (doc, win) {
    var _root = doc.documentElement,
        resizeEvent = 'orientationchange' in window ? 'orientationchange' : 'resize',
        resizeCallback = function () {
            var clientWidth = _root.clientWidth,
                fontSize = 50;
            if (!clientWidth) return;
            if(clientWidth < 750) {
                fontSize = 50 * (clientWidth / 375);
            } else {
                fontSize = 50 * (750 / 375);
            }
            _root.style.fontSize = fontSize + 'px';
        };
    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvent, resizeCallback, false);
    doc.addEventListener('DOMContentLoaded', resizeCallback, false);
})(document, window);

React实践记录

发表于 2017-04-06   |   分类于 react   |  

React写法用: ES5(createClass)还是ES6(class)?

React可以用ES5或ES6完美书写,使用JSX意味着你将通过Babel将JSX通过“build”转化为React.createElement调用。很多人利用这个特点在Babel的编译列表中添加一些es2015的语法以此来让ES6完全可用。

比较createClass和class

以下是使用React.createClass和ES6的class书写相同的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var inputControlES5 = React.creatClass({
getInitialState: function(){
return{
text: this.props.initialValue || 'placeholder'
};
},
handleChange: function(event){
this.setState({
text: event.target.value
})
},
return : function(){
return(
<div>
Type something:
<input onChange={this.handleChange}
value={this.state.text} />
</div>
);
)
}
})
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
class inputControlES6 extends React.Component{
constructor(props){
super(props);
this.state = {
text: this.props.initialValue || 'placeholder'
}
// es6函数要手动绑定
this.handleChange = this.handleChange.bind(this);
}
handleChange(event){
this.setState({
text: event.target.value
})
}
render(){
return(
<div>
Type something:
<input onChange={this.handleChange}
value={this.state.text} />
</div>
)
}
}

比较两种写法的不同点

函数的绑定

使用createClass,每个函数属性都会被React自动绑定,指的是this.eventFn这样的函数在任何你需要调用的时候都会自动为你绑定正确的this。

在ES6的class中,就不同了:函数需要我们去手动绑定它们

另一种方式就是在你使用的地方通过内联来绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .bind
render(){
return(
<input onChange={this.handleChange.bind(this)}
value={this.state.text}/>
)
}
// --- Or ---
render(){
return(
<input onChange={() => this.handleChange()}
value={this.state.text}/>
)
}

构造函数应该调用super

es6类的constructor需要接收props并且调用super(props)。这是createClass所没有的一点

class和createClass的比较

这个很明显,一个调用React.createClass并传入一个对象,另一个则是使用class继承React.Component。

小提示: 导入Component时如果在一个文件里包含多个组件,可以直接通过以下方式节省一些代码量:

1
import React, {Component} from 'react'

初始化State的设置

createClass 接受一个只会在组件被挂载时才会调用一次的initialState函数。

ES6 的class则使用构造函数。在调用super之后,可以直接设置state。

JSX中的if-else

你没法在JSX中使用 if-else 语句,因为 JSX 只是函数调用和对象创建的语法糖

用三元操作表达式

1
2
3
4
5
6
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name ? this.props.name : "World"}</div>;
}
});
ReactDOM.render(<HelloMessage name="xiaowang" />, document.body);

用比较运算符“ || ”

1
2
3
4
5
6
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name || "World"}</div>;
}
});
ReactDOM.render(<HelloMessage name="xiaowang" />, document.body);

用变量来书写

1
2
3
4
5
6
7
8
9
10
11
12
13
var loginButton;
if (loggedIn) {
loginButton = <LogoutButton />;
} else {
loginButton = <LoginButton />;
}
return (
<nav>
<Home />
{loginButton}
</nav>
)

内联判断,用立即调用函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ES5写法 函数的this调用需要重置this
{
(function(){
if(this.state.color){
return(
<div>this.state.color</div>
)
}
})()
}
//ES6写法
{
(()=>{
if(this.state.color){
return(
<div>this.state.color</div>
)
}
})()
}

React/JSX 编码规范

发表于 2016-07-22   |   分类于 react   |  

基本规范

  • 每个文件只包含的一个 React 组件(联系紧密的组件可以使用「命名空间的形式」)。
  • 始终使用 JSX 语法,不要使用 React.createElement 创建 ReactElement,以提高编写速度、可读性、可维护性(没有 JSX 转换的特殊场景例外,如在 console 中测试组件)。
  • 使用 ES6。

命名规范

  • 扩展名:使用 .js 作为 React 组件的扩展名;
  • 文件名:使用大驼峰命名法,如 MyComponent.js;
  • 组件命名:组件名称和文件名一致,如 MyComponent.js 里的组件名应该是 MyComponent;一个目录的根组件使用 index.js 命名,以目录名称作为组件名称;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // Use the filename as the component name
    // file contents
    const CheckBox = React.createClass({
    // ...
    })
    module.exports = CheckBox;
    // in some other file
    // bad
    const CheckBox = require('./checkBox');
    // bad
    const CheckBox = require('./check_box');
    // good
    const CheckBox = require('./CheckBox');
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // for root components of a directory,
    // use index.js as the filename and use the directory name as the component name
    // bad
    const Footer = require('./Footer/Footer.js')
    // bad
    const Footer = require('./Footer/index.js')
    // good
    const Footer = require('./Footer')
  • 引用命名:React 组件使用大驼峰命名法,HTML 标签、组件实例使用小驼峰命名法;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // bad
    const reservationCard = require('./ReservationCard');
    // good
    const ReservationCard = require('./ReservationCard');
    // bad
    const ReservationItem = <ReservationCard />;
    // good
    const reservationItem = <ReservationCard />;
    // HTML tag
    const myDivElement = <div className="foo" />;
    React.render(myDivElement, mountNode);

带命名空间的组件

  • 如果一个组件有许多关联子组件,可以以该组件作为命名空间编写、调用子组件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var MyFormComponent = React.createClass({ ... });
    MyFormComponent.Row = React.createClass({ ... });
    MyFormComponent.Label = React.createClass({ ... });
    MyFormComponent.Input = React.createClass({ ... });
    var Form = MyFormComponent;
    var App = (
    <Form>
    <Form.Row>
    <Form.Label />
    <Form.Input />
    </Form.Row>
    </Form>
    );

组件声明

  • 不要使用 displayName 来命名组件,通过引用来命名。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // bad
    export default React.createClass({
    displayName: 'ReservationCard',
    // stuff goes here
    });
    // good
    const ReservationCard = React.createClass({
    // stuff goes here
    });
    export default ReservationCard;

属性

属性命名

  • React 组件的属性使用小驼峰命名法;
  • 使用 className 代替 class 属性;
  • 使用 htmlFor 代替 for 属性。

传递给 HTML 的属性:

  • 传递给 HTML 元素的自定义属性,需要添加 data- 前缀,React 不会渲染非标准属性;
  • 无障碍属性 aria- 可以正常使用。

属性设置

  • 在组件行内设置属性(以便 propTypes 校验),不要在外部改变属性的值;
  • 属性较多使用 {...this.props} 语法;
  • 重复设置属性值时,前面的值会被后面的覆盖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var component = <Component />;
component.props.foo = x; // bad
component.props.bar = y; // also bad
// good
var component = <Component foo={x} bar={y} />;
// good
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'

属性对齐方式

  • 属性较少时可以行内排列;
  • 属性较多时每行一个属性,闭合标签单独成行
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
37
// bad - too long
<input type="text" value={this.state.newDinosaurName} onChange={this.inputHandler.bind(this, 'newDinosaurName')} />
// bad - aligning attributes after the tag
<input type="text"
value={this.state.newDinosaurName}
onChange={this.inputHandler.bind(this, 'newDinosaurName')} />
// good
<input
type="text"
value={this.state.newDinosaurName}
onChange={this.inputHandler.bind(this, 'newDinosaurName')}
/>
// if props fit in one line then keep it on the same line
<Foo bar="bar" />
// children get indented normally
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Spazz />
</Foo>
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>

行内迭代

  • 运算逻辑简单的直接使用行内迭代。
1
2
3
4
5
6
7
return (
<div>
{this.props.data.map(function(data, i) {
return (<Component data={data} key={i} />)
})}
</div>
);

其他代码格式

注释

  • 组件之间的注释需要用 {} 包裹。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var content = (
    <Nav>
    {/* child comment, put {} around */}
    <Person
    /* multi
    line
    comment */
    name={window.isLoggedIn ? window.name : ''} // end of line comment
    />
    </Nav>
    );

引号使用

  • HTML/JSX 属性使用双引号 ";
  • JS 使用单引号 ';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
// JavaScript Expression
const person = <Person name={window.isLoggedIn ? window.name : ''} />;
// HTML/JSX
const myDivElement = <div className="foo" />;
const app = <Nav color="blue" />;
const content = (
<Container>
{window.isLoggedIn ? <Nav /> : <Login />}
</Container>
);

条件 HTML

  • 简短的输出在行内直接三元运算符;

    1
    2
    {this.state.show && 'This is Shown'}
    {this.state.on ? 'On' : 'Off'}
  • 较复杂的结构可以在 .render() 方法内定义一个以 Html 结尾的变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var dinosaurHtml = '';
    if (this.state.showDinosaurs) {
    dinosaurHtml = (
    <section>
    <DinosaurTable />
    <DinosaurPager />
    </section>
    );
    }
    return (
    <div>
    ...
    {dinosaurHtml}
    ...
    </div>
    );

() 使用

  • 多行的 JSX 使用 () 包裹,有组件嵌套时使用多行模式;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // bad
    return (<div><ComponentOne /><ComponentTwo /></div>);
    // good
    var multilineJsx = (
    <header>
    <Logo />
    <Nav />
    </header>
    );
    // good
    return (
    <div>
    <ComponentOne />
    <ComponentTwo />
    </div>
    );
  • 单行 JSX 省略 ()。

    1
    2
    3
    4
    5
    6
    7
    var singleLineJsx = <h1>Simple JSX</h1>;
    // good, when single line
    render() {
    const body = <div>hello</div>;
    return <MyComponent>{body}</MyComponent>;
    }

自闭合标签

  • JSX 中所有标签必须闭合;
  • 没有子元素的组件使用自闭合语法,自闭合标签 / 前留一个空格。
1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
<Logo></Logo>
<Logo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Logo />

组件内部代码组织

  • 不要使用下划线前缀命名 React 组件的方法;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // bad
    React.createClass({
    _onClickSubmit() {
    // do stuff
    }
    // other stuff
    });
    // good
    React.createClass({
    onClickSubmit() {
    // do stuff
    }
    // other stuff
    });
  • 按照生命周期组顺序织组件的方法、属性;

  • 方法(属性)之间空一行;
  • .render() 方法始终放在最后;
  • 自定义方法 React API 方法之后、.render() 之前。
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// React 组件中按照以下顺序组织代码
React.createClass({
displayName: '',
mixins: [],
statics: {},
propTypes: {},
getDefaultProps() {
// ...
},
getInitialState() {
// do something
},
componentWillMount() {
// do something
},
componentDidMount() {
// do something: add DOM event listener, etc.
},
componentWillReceiveProps() {
},
shouldComponentUpdate() {},
componentWillUpdate() {},
componentDidUpdate() {},
componentWillUnmount() {
// do something: remove DOM event listener. etc.
},
// clickHandlers or eventHandlers like onClickSubmit() or onChangeDescription()
handleClick() {
// ...
},
// getter methods for render like getSelectReason() or getFooterContent()
// Optional render methods like renderNavigation() or renderProfilePicture()
render() {
// ...
}
});

代码校验工具

  • ESLint
  • ESLint React Plugin

参考资源

  • React DOM 术语表
  • Airbnb React/JSX Style Guide
  • 驼峰命名法
  • React 组件规范及生命周期
  • Airbnb JavaScript Style Guide
  • React 支持的 HTML 标签和属性
  • React 不自动添加 px 的 style 属性
12
lijiliang

lijiliang

实践,阅读,分享,学习,积累

11 日志
7 分类
11 标签
GitHub
© 2017 lijiliang
由 Hexo 强力驱动
主题 - NexT.Mist