Giter Site home page Giter Site logo

kenve.github.io's People

Contributors

kenve avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

mr3w

kenve.github.io's Issues

React Notes

React是什么

  1. facebook 2013 发布
  2. React 兼容IE8+

Html Template

React官网提供的Html模板,所有demo都是在这个Html模板内编写的

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="../build/react.js"></script>
    <script src="../build/react-dom.js"></script>
    <script src="../build/browser.min.js"></script>
</head>

<body>
    <div id="example"></div>
    <script type="text/babel">
        // ** Our code goes here! **
        // 在这里书写jsx代码
    </script>
</body>
</html>

上面的html模板有几个地方要注意:

  • 要在head便签中引入react.jsreact-dom.jsbrowser.min.js三个js库,而且要在jsx代码前引入,其中,react.js 是 React 的核心库,react-dom.js 是提供与 DOM 相关的功能,Browser.js 的作用是将 JSX 语法转为 JavaScript语法,这一步很消耗时间,实际上线的时候,应该使用构建工具构建完成后在放到服务器中。
  • 因为 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel",所以body中的 <script> 标签的 type 属性为 text/babel

HelloWord

官网提供了一个非常简单的HelloWord 的代码,将如下嵌入html模板中即可:

<script type="text/babel">
      ReactDOM.render(
        <h1>Hello, world!</h1>,
        document.getElementById('example')
      );
    </script>

上面代码是将一个h1便签,插入idexample的dom节点下(查看代码 helloworld

JSX

什么是JSX

  • JSX=JavaScript XML
  • 是基于ECMAScript的一种新特性
  • 一种定义带属性结构的语法
  • JSX不是XML或者HTML,而是一种机制

JSX的特点

  • 类XML语法容易接受
  • 结构清晰
  • 增强js语义
  • 抽象程度高(跨平台)
  • 代码模块化
  • 从左到右

JSX的语法:

jsx代码要使用<script type="text/babel"></script>标签包裹,JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。所以允许 HTML 与 JavaScript 的混写。

      //调用ReactDOM的render方法,把组件渲染到页面中
      //render接收两个参数,第一个是:一段jsx代码,第二个是渲染目标
      ReactDOM.render(
        <h1>Hello, world!</h1>,
        document.getElementById('example')
      );

以上代码定义了一个名为HelloWord的组件,然后调用ReactDOM.render();方法;该方法需要传递两个参数,第一个参数是一段JSX代码,第二参数是要渲染目标节点。

jsx组件

React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类。(查看代码jsx-component)。

     //创建名为HelloWord的组件
    var HelloWorld = React.createClass({
        render: function() {
            return ( < p >
                Hello, < input type = "text"
                placeholder = "Your name here" / > !It is { this.props.date.toTimeString() } < /p>
            );
        }
    });
  setInterval(function() {
      //调用React的render方法,把组件渲染到页面中
      //前面声明组件可以当作标签来使用,即<HelloWorld />
      //将名称为的属性date值,传入自定义组件中
      ReactDOM.render( < HelloWorld date = { new Date() }/>, document.getElementById('example') ); 
    }, 500);
  • 上面代码中,变量 HelloWord 就是一个组件类,模板插入 <HelloWord /> 时,会自动生成 HelloMessage 的一个实例(下文的"组件"都指组件类的实例)。所有组件类都必须有自己的 render 方法,用于输出组件。
  • 注意,组件类的首字母必须大写,使用驼峰命名法,否则会报错。另外,组件类只能包含一个顶层标签,否则也会报错。组件类可以任意加入属性像加HTML的属性一样,比如:< HelloWorld date = { new Date() }/> 就是在HelloWord组件中加入属性date,值为new Date(),因为是求值表达式所以要用{}包裹,也可以直接<HelloWord name="Tom">
  • 组件的属性可以在组件类的 this.props 对象上获取,比如 date 属性就可以通过 this.props.data 读取。
  • 添加组件属性,有一个地方需要注意,就是 class 属性需要写成 classNamefor 属性需要写成 htmlFor ,这是因为 classfor 是 JavaScript 的保留字。

JSX注释

多行注释使用/**/,单行注释使用 //

 var HelloWorld = React.createClass({
        render: function () {
          return (
              <p
                /*
                comment...
                */
                name="test" // set name to test
              >Hello, World {
                /*
                comment...
                comment
                */

                "hello, "

                // comment...
              }</p>
          )
        }
      });

JSX的设置CSS样式

var style={
    color:'red',
    border:'1px solid #000'
 };
var HelloWorld = React.createClass({
    render: function() {
        return ( 
          <div style={style}>
          < p >
            Hello, World < /p>
          </div>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 

条件判断语句

{}不能直接写条件判断(ifelse)

  • 使用三元表达式进行条件判断(查看Demo)
var HelloWorld = React.createClass({
    render: function() {
        return ( 
          <div>
          < p >
            Hello, {this.props.name?this.props.name:'World'} < /p>
          </div>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 
  • 使用自定义函数返回值,接受返回值进行条件判断(查看demo)
var HelloWorld = React.createClass({            
    getName:function () {
        if (this.props.name) {
            return this.props.name;
        } else {
            return 'World';
        }
    },
    render: function() {
        var name=this.getName();
        return ( 
          <div>
          < p >
            Hello, {name} < /p>
          </div>
        );
    }
    });
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 
  • 直接调用自定义函数进行条件判断(查看demo)
var HelloWorld = React.createClass({            
    getName:function () {
        if (this.props.name) {
            return this.props.name;
        } else {
            return 'World';
        }
    },
    render: function() {
        return ( 
          <div>
          < p >
            Hello, { this.getName()} < /p>
          </div>
        );
    }
    });
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 
  • 使用比较运算符,代替条件判断(查看demo)。
 var HelloWorld = React.createClass({
    render: function() {
        return ( 
          <div>
          < p >
            Hello, {this.props.name || 'World'} < /p>
          </div>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 
  • 使用万能的函数表达式 (一般不这样写):
 var HelloWorld = React.createClass({
    render: function() {
        return ( 
          <div>
          < p >
            Hello, {(function (obj) {
                if (obj.props.name) {
                    return obj.props.name;
                } else {
                    return 'World';
                }
            })(this)} < /p>
          </div>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') );

非dom 属性

非DOM属性:dangerouslySetInnerHTML、ref、key
节点相似尽量写成一个,因为react会重新生成; 使用列表展示元素,元素加上key
React Diff算法流程

  • dangerouslySetInnerHTML:在JSX中直接插入HTML代码 查看DEMO
var rawHTML={
    __html:"<h1>I'm inner HTML</h1>"
}
var HelloWorld = React.createClass({

    render: function() {
        return ( 
          <div dangerouslySetInnerHTML={rawHTML}>
          </div>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') );
  • ref:父组件引用子组件 查看DEMO
var HelloWorld = React.createClass({
    render: function() {
        return ( 
          <div>
          <p ref="childp">
               "Hello, World"
          </p>
          </div>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 
  • key:提高渲染性能,不能出现相同的key,key值必须是唯一的 查看DEMO
var HelloWorld = React.createClass({
    render: function() {
        return ( 
          <ul>
            <li key="1">1</li>
            <li key="2">2</li>
            <li key="3">3</li>
        </ul>
        );
    }
});
ReactDOM.render( < HelloWorld/>, document.getElementById('example') ); 

JSX源码

React组件生命周期详解

什么是组件的生命周期

  • 组件本质上是状态机,输入确定,输出一定确定。
  • 状态发生转换时会触发不同的钩子函数,从而让开发者有机会做出响应。
  • 可以用事件的思路来理解状态。
  • 不同生命周期内可以自定义的函数。
  • 组件生命周期可以分为:初始化 → 运行中 → 销毁,即:Mounting(已插入真实DOM)→ Updating(正在被重新渲染) → Unmounting(已移出真实DOM)

初始化阶段函数介绍

初始化阶段可以使用的函数:

  • getDefaultProps:只调用一次,实例之间共享引用
  • getInitialState:初始化每个实例特有的状态
  • componentWillMountrender之前最后一次修改状态的机会
  • render:只能访问this.propsthis.state,只有一个顶层组件,不允许修改状态和DOM输出。
  • componentDidMount:成功render并渲染完成真实DOM之后触发,可以修改DOM

使用实例:

  • 触发顺序getDefaultPropsgetInitialStatecomponentWillMountrendercomponentDidMount 。查看DEMO
var HelloWorld = React.createClass({ 
        //只调用一次,实例之间共享引用
        getDefaultProps:function () {
            console.log('getDefaultProps,1');
        },
        //初始化每个实例特有的状态
        getInitialState:function () {
            console.log('getInitialState,2');
            return null;
        },
        //render之前最后一次修改状态的机会
        componentWillMount:function () {
            console.log('componentWillMount,3');
        },
        //只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
        render: function() { 
            console.log('render,4');
            return (
               <div>
                   <p>
                     Hello, World!
                    </p>
               </div>
            ); 
       },
       //成功render并渲染完成真实DOM之后触发,可以修改DOM
        componentDidMount:function () {
            console.log('componentDidMount,5');
        }
   }); 
ReactDOM.render(<HelloWorld/>, document.getElementById('example') );
var count=0;
var HelloWorld = React.createClass({ 
    //只调用一次,实例之间共享引用
    getDefaultProps:function () {
        console.log('getDefaultProps,1');
        return {name:'Tom'};
    },
    //初始化每个实例特有的状态
    getInitialState:function () {
        console.log('getInitialState,2');
        return {
            myCount:count++,
            ready:false
        };
    },
    //render之前最后一次修改状态的机会
    componentWillMount:function () {
        console.log('componentWillMount,3');
        this.setState({
            ready:true
        })
    },
    //只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
    render: function() { 
        console.log('render,4');
        return (
               <p>
                 Hello, {this.props.name ? this.props.name : "World"}<br/>
                 {"ready:" + this.state.ready} <br/>
                 {"count:"+this.state.myCount}
                </p>
        ); 
   },
   //成功render并渲染完成真实DOM之后触发,可以修改DOM
    componentDidMount:function () {
        console.log('componentDidMount,5');
        $(ReactDOM.findDOMNode(this)).append('surprise!');
    }
}); 
ReactDOM.render(<div><HelloWorld/><br/><HelloWorld/></div>, document.getElementById('example') );

以上代码渲染两次HelloWorld组件,输出的页面及控制台信息如下:

页面显示结果 控制台信息

运行中阶段函数介绍

运行中阶段可以使用的函数:

  • componentWillReceiveProps:父组件修改属性触发,可以修改新属性、修改状态
  • shouldComponentUpdate:返回false会阻止render调用
  • componentWillUpdate:不能修改属性和状态
  • render:只能访问this.propsthis.state,只有一个顶层组件,不允许修改状态和DOM输出
  • componentDidUpdate:可以修改DOM

使用实例:

  • 触发顺序:componentWillReceivePropsshouldComponentUpdatecmponentWillUpdaterendercomponentDidUpdate.查看DEMO
 // HelloWorld组件
var HelloWorld = React.createClass({ 
    //父组件修改属性触发,可以修改新属性、修改状态
    componentWillReceiveProps:function () {
        console.log('componentWillReceiveProps,1');
    },
    //返回false会阻止render调用
    shouldComponentUpdate:function () {
        console.log('shouldComponentUpdate,2');
        return true;
    },
    //不能修改属性和状态
    componentWillUpdate:function () {
        console.log('componentWillUpdate,3');
    },
    //只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
    render: function() { 
        console.log('render,4');
        return (<p>Hello ,{this.props.name ? this.props.name : "World"}</p>); 
   },
   //可以修改DOM
    componentDidUpdate:function () {
        console.log('componentDidUpdate,5');
    }
}); 
//HelloUniverse组件
var HelloUniverse=React.createClass({
    getInitialState:function () {
        return {name:''};
    },
    //自定义事件处理方法
    handleChange:function (event) {
        this.setState({
            name:event.target.value
        });
    },
    render:function () {
        return (
            <div>
               <HelloWorld name={this.state.name}/>
               <br/>
               <input type="text" onChange={this.handleChange}/>
            </div>
        )
    }
});
ReactDOM.render(<HelloUniverse/>, document.getElementById('example') );

以上代码首先定义了一个HelloWorld组件,并定义了Updating各个函数的处理方法,然后定义了一个包含HelloWorld组件的HelloUniverse组件,并为input元素的onChange时间绑定handleChange方法,当input元素的值发生变化时,就会重新渲染页面,初始化阶段控制台输出:render,4,然后每次改变input元素的值,按componentWillReceivePropsshouldComponentUpdatecmponentWillUpdaterendercomponentDidUpdate顺序触发.

  • 用法:查看Demo
var HelloWorld = React.createClass({ 
    //父组件修改属性触发,可以修改新属性、修改状态
    componentWillReceiveProps:function (newProps) {
        console.log('componentWillReceiveProps,1');
         console.log(newProps);
    },
    //返回false会阻止render调用
    shouldComponentUpdate:function () {
        console.log('shouldComponentUpdate,2');
        return true;
    },
    //不能修改属性和状态
    componentWillUpdate:function () {
        console.log('componentWillUpdate,3');
    },
    //只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
    render: function() { 
        console.log('render,4');
        return (<p>Hello ,{this.props.name ? this.props.name : "World"}</p>); 
   },
   //可以修改DOM
    componentDidUpdate:function () {
        console.log('componentDidUpdate,5');
        $(ReactDOM.findDOMNode(this)).append("surprise!");
    }
}); 
var HelloUniverse=React.createClass({
    getInitialState:function () {
        return {name:''};
    },
    //自定义事件处理方法
    handleChange:function (event) {
        this.setState({
            name:event.target.value
        });
    },
    render:function () {
        return (
            <div>
               <HelloWorld name={this.state.name}/>
               <br/>
               <input type="text" onChange={this.handleChange}/>
            </div>
        )
    }
});
ReactDOM.render(<HelloUniverse/>, document.getElementById('example') );

以上代码在执行顺序的代码基础上,添加componentWillReceiveProps接收了newProps并在方法内输出该值,每次改变input元素的值,接收到参数值并输出,然后执行shouldComponentUpdate函数,若函数返回true程序继续执行componentWillUpdaterendercomponentDidUpdate,若返回false之停止渲染。

销毁阶段函数

销毁阶段可以使用的函数:

  • componentWillUnmount:在删除组件之前进行清理操作,比如计时器和事件监听器。 查看Demo
var HelloWorld = React.createClass({ 
    render: function() { 
        console.log('render,4');
        return (<p>Hello ,{this.props.name ? this.props.name : "World"}</p>); 
   },
   //在删除组件之前进行清理操作,比如计时器和事件监听器
    componentWillUnmount:function () {
        console.log('componentWillUnmount:BOOOOOOOOOOOOOOOOOM');
    }
}); 
var HelloUniverse=React.createClass({
    getInitialState:function () {
        return {name:''};
    },
    //自定义事件处理方法
    handleChange:function (event) {
        if (event.target.value == "123") {
            //移除dom中的内容
            ReactDOM.unmountComponentAtNode(document.getElementById('example'));
            return;
        }
        this.setState({
            name:event.target.value
        });
    },
    render:function () {
        return (
            <div>
               <HelloWorld name={this.state.name}/>
               <br/>
               <input type="text" onChange={this.handleChange}/>
            </div>
        )
    }
});
ReactDOM.render(<HelloUniverse/>, document.getElementById('example') );

以上代码,当用户输入123时触发 ReactDOMunmountComponentAtNode方法,在移除HelloWorld组件之前,调用HelloWorldcomponentWillUnmount方法。

React属性和状态详解

属性

  • 属性的含义:
    props = properties
    属性:一个事物的性质与关系,属性往往是与生俱来、无法自己改变的,
  • 用法:组件属性是有父组件传入,设置属性要使用父组件向子组件传值得方式来设置属性值。组件的属性可以接受任意值,字符串、对象、函数等等都可以。
  • 实例1:在调用ReactDOM.render方法是为HeloWorld组件传入分别传入字符串、对象、字符串对象及数组对象。 查看Demo
var HelloWorld = React.createClass({
    render: function() {
        return ( < p >
            Hello, {this.props.name ? this.props.name : "World"} < /p>
        );
    }
});
  ReactDOM.render(
  <div>
  <HelloWorld name = "Tom"/>
  <HelloWorld name = {123}/>
  <HelloWorld name = {"Tom"}/>
  <HelloWorld name = {[1,2,3]}/>
 </div>
  , document.getElementById('example')); 
var props = {
    one: "123",
    two: 321
}
<HelloWorld {...props}/>
  • 实例3:这种用法不常用,查看Demo
var HelloWorld = React.createClass({
    render: function () { 
        return <p>Hello, {this.props.name ? this.props.name : "World"}</p>;
    },
});
var instance = React.render(<HelloWorld></HelloWorld>, document.body);
instance.setProps({name: 'Tim'});

状态

  • 含义:
    state(状态):事物所处的状况,并且状态是由事物自行处理、不断变化的。就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI。
  • 用法:
    • getInitialState:初始化每个实例特有的状态
    • setState:更新组件状态 setStatediff算法 → 更新DOM
  • 实例:查看Demo
var HelloWorld = React.createClass({
    getInitialState: function () {
        return {
            name: 'Tom',
        };
    },
    handleChange: function (event) {                
        this.setState({name: event.target.value});
    }, 
    render: function () {
        return (<div>
        <p>Hello, {this.state.name}</p>
        <br/>
        <input type="text" onChange={this.handleChange} />
        </div>)
    },
});
ReactDOM.render(<HelloWorld/>, document.getElementById('example')); 

以上代码,定义HelloWorld组件的 getInitialState方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

属性和状态的对比

  • 相同点:
    • 都是纯JS对象
    • 都会触发render更新
    • 都具有确定性
  • 不同点:
    • 状态只和自己相关,由自己来维护
    • 组件不能修改自身属性,
    • 组件在运行时需要修改的数据就是状态
    • 属性(this.props) 表示那些一旦定义,就不再改变的特性,而 状态(this.state)是会随着用户互动而产生变化的特性。
  • 实例:查看Demo
var Content=React.createClass({
getInitialState:function () {
    return {
        inputText:''
    };
},
handleChange:function (event) {
    this.setState({
            inputText:event.target.value
    });
},
handleSubmit:function () {
     console.log("reply To: " + this.props.selectName + "\n" + this.state.inputText);
},
render:function () {
    return (
        <div>
        <textarea onChange={this.handleChange} placeholder="please enter something..."></textarea>
        <button onClick={this.handleSubmit}>submit</button>
        </div>
    )
}
});
var Comment = React.createClass({
    getInitialState: function () {
        return {
            names: ["Tim", "John", "Hank"],
            selectName: '',
        };
    },
    handleSelect: function (event) {
        this.setState({selectName: event.target.value});
    },
    render: function () {
        var options = [];
        for (var option in this.state.names) {
            options.push(<option value={this.state.names[option]} key={option}>{this.state.names[option]}</option>)
        };
        return <div>
        <select onChange={this.handleSelect}>
            {options}
        </select>
        <Content selectName={this.state.selectName}></Content>
        </div>;
    },
});
ReactDOM.render(<Comment/>, document.getElementById('example')); 

React中的事件

组件的事件处理函数包括:

  • React自有方法,如(rendercomponentWillUpdatecomponentDidMount等)
  • 用户自定义,如(handleClickhandleChangehandleMouseover等)

    编写

handleChange: function (event) {
   //response to change
},

绑定事件处理函数

绑定事件处理函数,使用onChange={this.handleChange}
其他可以绑定的事件处理函数:

  • 触摸:onTouchCancel onTouchEnd onTouchMove onTouchStart
  • Composition:onCompositionEnd onCompositionStart onCompositionUpdate
  • 键盘:onKeyDown onKeyPress onKeyUp
  • 剪切:onCopy onCut onPaste
  • 表单:onChange onInput onSubmit
  • 焦点:onFocus onBlur
  • UI元素:onScroll
  • 滚动:onWheel
  • 鼠标:onClick onContextMenu onDoubleClick onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp onDrop onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart
  • 媒体:onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting
  • 图片:onLoad onError
  • 更多

实例:(查看Demo)

var HelloWorld=React.createClass({
        handleChange:function(event){
          console.log(event.target.value);
        },
        render:function(){
          return (
              <div>
                <input type="text" onChange={this.handleChange}/>
              </div>
          )
        }
    });
ReactDOM.render( <HelloWorld/>, document.getElementById('example') ); 

以上代码定义了一个handleChange自的定义事件处理函数,接收名为event的事件对象,函数内部使用event.target.value打印事件对象的属性值

不同的事件对象

Properties:

  • 通用:
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type
  • 剪切
DOMDataTransfer clipboardData
  • Composition:
string data
  • 键盘
boolean altKey
number charCode
boolean ctrlKey
boolean getModifierState(key)
string key
number keyCode
string locale
number location
boolean metaKey
boolean repeat
boolean shiftKey
number which
  • 焦点
DOMEventTarget relatedTarget
  • 鼠标
boolean altKey
number button
number buttons
number clientX
number clientY
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
number pageX
number pageY
DOMEventTarget relatedTarget
number screenX
number screenY
boolean shiftKey
  • 触摸
boolean altKey
DOMTouchList changedTouches
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
boolean shiftKey
DOMTouchList targetTouches
DOMTouchList touches
  • UI元素
number detail
DOMAbstractView view
  • 滚动
number deltaMode
number deltaX
number deltaY
number deltaZ

实例:滚动事件 (查看Demo)

var HelloWorld=React.createClass({
  getInitialState:function () {
    return {
       backgroundColor:'#fffff'
    };
  },
    handleWheel:function(event){
      var newColor = (parseInt(this.state.backgroundColor.substr(1), 16) + event.deltaY * 997).toString(16);
        newColor = '#' + newColor.substr(newColor.length - 6).toUpperCase();
        this.setState({
            backgroundColor: newColor
        });
    },
    render:function(){
         console.log(this.state);
      return (
        <div onWheel={this.handleWheel} style={this.state}>
        <p>Hello, World</p>
        </div>
      )
    }
});
ReactDOM.render( <HelloWorld/>, document.getElementById('example') );

以上代码,将onWheel绑定到handleWheel中,当鼠标悬浮在绑定事件的div中,滚动鼠标滚轮改变该div的背景颜色。

实例:鼠标事件 (查看Demo)

var HelloWorld=React.createClass({
  getInitialState:function () {
    return {
      x:0,
      y:0
    };
  },
  handleMouseMove:function(event){
     this.setState({
            x: event.clientX,
            y: event.clientY
        });
   },
  render:function(){
         console.log(this.state);
      return (
        <div onMouseMove={this.handleMouseMove} style={{
            height: '1000px',
            width: '700px',
            backgroundColor: 'gray'
        }}>
        {this.state.x + ', ' + this.state.y}
        </div>
      )
    }
});
ReactDOM.render( <HelloWorld/>, document.getElementById('example') ); 

以上代码,实时获取鼠标的位置。

实例:键盘事件 (查看Demo)

var HelloWorld = React.createClass({
    getInitialState: function () {
        return {
            password: ''
        }
    },
    handleKeyPress: function (event) {
        this.setState({
            password: this.state.password + event.which
        });
        console.log(this.state)
    },
    render: function () {
        return (<div>
        <input onKeyPress={this.handleKeyPress} ></input>
        <p style={{
            'display': this.state.password.indexOf('495051') >= 0 ? 'inline' : 'none'
            }}>You got it!</p>
        </div>
        );
    },
});
ReactDOM.render( <HelloWorld/>, document.getElementById('example') ); 

以上代码,当用户在input输入带有123的字符时,显示You got it!

事件与状态关联

    handleChange:function(event){
    //设置状态
    this.setState({
       name:event.target.value;
    })
}

React组件的协同使用

组件的协同本质上是对组件的一种组织、管理方式。实现逻辑清晰、代码模块化、封装细节、代码可复用等目的。
组件嵌套更多是垂直方向的由下至上提取,实现代码的封装。
Mixin水平方向的抽离,实现代码的复用。

组件嵌套

  • 含义:组件嵌套的本质就是父子关系。
    组件嵌套
  • 优点:
    • 逻辑清晰:父子关系和人类社会的父子关系对应,易于理解
    • 代码模块化:每个模块对应一个功能,不同的模块可以同步开发
    • 封装细节:开发者只需要关注组件的功能,不用关心组件的实现细节
  • 缺点:
    • 编写难度高:父子关系的具体实现需要经过深思熟虑,贸然编写将导致关系混乱、代码难以维护
    • 无法掌握所有细节:使用者只知道组件用法,不知道实现细节,遇到问题难以修复
      *实例:父子关系 (查看Demo
var GenderSelect = React.createClass({
      render: function() {
          return <select onChange={this.props.handleSelect}>
          <option value="0"></option>
          <option value="1"></option>
          </select>
      }
  });
  var SignupForm = React.createClass({
      getInitialState: function() {
          return {
              name: '',
              password: '',
              gender: '',
          }
      },
      handleChange: function(name, event) {
          var newState = {}
          newState[name] = event.target.value
          this.setState(newState)
      },
      handleSelect: function(event) {
          this.setState({gender: event.target.value})
      },
      render: function() {
          console.log(this.state)
          return <form>
          <input type="text" placeholder="请输入用户名" onChange={this.handleChange.bind(this, 'name')} />
          <input type="password" placeholder="请输入密码" onChange={this.handleChange.bind(this, 'password')} />
          <GenderSelect handleSelect={this.handleSelect}></GenderSelect>
          </form>
      }
   });
ReactDOM.render( <SignupForm/>, document.getElementById('example')); 

以上代码,定义了GenderSelect性别选择组件也就是子组件, SignupForm 登录组件也就是父组件,最后把SignupForm渲染到页面中。SignupForm组件中有四个方法,第一个是getInitialState 返回一个对象。第二个是handleChange事件处理函数,该函数接收两个参数name event。在render页面绑定bind给函数加上默认参数,bind方法接收第一个参数是handleChange方法里面的this值的指向,从第二个参数开始每个参数都对应函数中的每个参数,确定change的目标,哪个节点触发的change事件。
父组件传递了一个函数handleSelect给子组件,当子组件触发onChange事件时调用的父组件的handleSelect处理函数。

Mixin

定义

Mixin = 一组方法
Mixin的目的是横向抽离出组件的相似代码
相似概念:面向切面编程、插件

优缺点

  • 优点:
    • 代码复用:抽离出通用代码,减少开发成本,提高开发效率
    • 即插即用:可以直接使用许多现有的Mixin来编写自己的组件
    • 适应性强:改动一次代码,影响多个组件
  • 缺点:
    • 编写难度高:Mixin可能被用在各种环境中,兼容多种环境就需要更多的逻辑和代码,通用的代价是提高复杂度
    • 降低代码可读性:组件的优势在于将逻辑和界面直接结合在一起,Mixin本质上会分散逻辑,理解起来难度更大

编写Mixin

查看Demo

var BindingMixin={
handleChange:function (key) {
  var that=this;
  var newState={};
  return function (event) {
    newState[key]=event.target.value;
    that.setState(newState);
  }
}
}
var BindExample=React.createClass({
mixins:[BindingMixin],
getInitialState: function() {
    return {
        text: '',
        comment: '',
    }
},
render: function() {
    return (<div>
        <input type="text" placeholder="请输入内容" onChange={this.handleChange('text')} />
        <textarea onChange={this.handleChange('comment')}></textarea>
        <p>{this.state.text}</p>
        <p>{this.state.comment}</p>
    </div>);
}
});
ReactDOM.render( <BindExample/>, document.getElementById('example')); 

以上代码,定义了一个名称为BindingMixin的Mixin,在里面定义了一个handleChange的处理函数,该处理函数接收一个参数key,处理函数里面定义了变量that存储this(因为在this值在函数的函数里面会丢失)。声明newState对象存储state的值,最后返回一个事件处理函数。
BindExample 组件中使用mixin的语法为mixins:[BindingMixin],在渲染页面时使用onChange={this.handleChange('comment')} 调用mixin中的方法。

addons中的mixin

使用addons中的mixin,首先在head引入react-with-addons.js文件
查看Demo

var BindingExample=React.createClass({
mixins:[React.addons.LinkedStateMixin],
getInitialState: function() {
    return {
        text: '',
        comment: '',
    }
},
render: function() {
    return (<div>
        <input type="text" placeholder="请输入内容" valueLink={this.linkState('text')} />
        <textarea valueLink={this.linkState('comment')}></textarea>
        <p>{this.state.text}</p>
        <p>{this.state.comment}</p>
    </div>);
}
});
ReactDOM.render( <BindingExample/>, document.getElementById('example')); 

使用mixin的语法mixins:[React.addons.LinkedStateMixin],页面渲染时调用的语法valueLink={this.linkState('comment')}

React表单

不可控组件

查看Demo

<input type="text" defaultValue="Hello World!" />
//获得上面的值,使用ref访问真实的`DOM`,然后获取值
var inputValue = React.findDOMNode(this.refs.input).value

数据被写死在input中而不是存储在state,使得组件中的数据和state中的数据不对应,数据不可控。

可控组件

查看Demo

<input type="text" defaultValue={this.state.value} />
//
var inputValue = this.state.value;

why controller

  • 组件可控的好处:
  • 符合React的数据流
  • 数据存储在state中,便于使用
  • 便于对数据进行处理

表单元素的使用

  • lable 标签
 <label htmlFor="name">Name</label>
  • input 标签
<input type="checkbox" value="A" 
 checked={this.state.checked} 
 onChange={this.handleChange} />
  • textarea 标签
<textarea value={this.state.helloTo} 
    onChange={this.handleChange} />
  • select 标签
<select value={this.state.helloTo} 
    onChange={this.handleChange}> 
    <option value="one"></option>
    <option value="two"></option>
</select>

实例:(查看Demo)

var MyForm = React.createClass({ 
   //初始化state
    getInitialState: function () {
        return {
            username: "",
            gender: "man",
            checked: true
        }; 
    },
    //更新状态
    handleUsernameChange: function (event) { 
        this.setState({
            username: event.target.value
        });
    },
    handleGenderChange: function (event) { 
        this.setState({
            gender: event.target.value
        });
    },
    handleCheckboxChange: function (event) { 
        this.setState({
            checked: event.target.checked
        });
    },
    submitHandler: function (event) {
        event.preventDefault();
        console.log(this.state); 
    },
    render: function () {
        return <form onSubmit={this.submitHandler}>
            <label htmlFor="username">请输入用户名:</label>
            <input id="username" type="text" value={this.state.username} onChange={this.handleUsernameChange} />
            <br />
            <select value={this.state.gender} onChange={this.handleGenderChange}> 
                <option value="man"></option>
                <option value="woman"></option>
            </select>
            <br />
            <label htmlFor="checkbox">同意用户协议</label>
            <input id="checkbox" type="checkbox" value="agree" checked={this.state.checked} onChange={this.handleCheckboxChange} />
            <button type="submit">注册</button>
        </form>;
        } 
    });
ReactDOM.render( <MyForm/>, document.getElementById('example')); 

注册表单。

复用事件处理函数

  • bind复用 (查看Demo)
handleChange: function(name, event) {
    ...
}
{this.handleChange.bind(this, 'input1')}
  • name复用 (查看Demo)
handleChange: function(event) {
    var name = event.target.name
}
{this.handleChange}

自定义表单组件

  • 原因:
    • 内因:表单本身具备特殊性:样式统一、信息内聚、行为固定
    • 外因:本质上是组件嵌套,组织和管理组件的一种方式
  • 方式
    • 不可控的自定义组件
    • 可控的自定义组件
  • 不可控的自定义组件实例:(查看Demo)
var MyForm = React.createClass({
    submitHandler: function (event) { 
        event.preventDefault(); 
        alert(this.refs.radio.state.value);
    },
    render: function () {
        return <form onSubmit={this.submitHandler}>
        <Radio ref="radio" name="my_radio" defaultValue="B">
            <option value="A">First Option</option>
            <option value="B">Second Option</option>
            <option value="C">Third Option</option>
        </Radio>
        <button type="submit">Speak</button>
        </form>;
    } 
});
  • 可控的自定义组件实例:(查看Demo)
var MyForm = React.createClass({
    getInitialState: function () { 
        return {my_radio: "B"};
    },
    handleChange: function (event) {
        this.setState({
            my_radio: event.target.value
        }); 
    },
    submitHandler: function (event) { 
        event.preventDefault(); 
        alert(this.state.my_radio);
    },
    render: function () {
        return <form onSubmit={this.submitHandler}> 
            <Radio name="my_radio" value={this.state.my_radio} onChange={this.handleChange}>
                <option value="A">First Option</option> 
                <option value="B">Second Option</option>
                <option value="C">Third Option</option>
            </Radio>
            <button type="submit">Speak</button>
        </form>;
    } 
});

React 动画效果

实现方法:

  • CSS3:scale transform rotate animation transition
  • JS模拟:$(...).animate() $(...).show() setInterval setTimeout
  • rAF(requestAnimationFrame):优化后的setIntervalsetTimeout
  • SVG:<animate>
名称 优点 缺点
CSS3动画 高性能、易于使用 兼容性差、无法实现复杂效果
JS模拟动画 兼容性强 性能差、难以调试
rAF动画 兼容性一般、性能较高 将被CSS3替代
SVG动画 高性能、能实现复杂效果 编写难度较大

React使用CSS3动画

传统的CSS3动画使用$(...).addClass(...)的方式控制class实现动画效果,而在React中这一步由React插件完成
React中CSS3动画用法

  • 实例 (查看Demo)
    head中引入react-with-addons.js,并添加组件的四个状态的CSS3动画样式:
.example-enter {
  opacity: 0.01;
}
.example-enter.example-enter-active {
  opacity: 1;
  transition: opacity 500ms ease-in;
}
.example-leave {
  opacity: 1;
}
.example-leave.example-leave-active {
  opacity: 0.01;
  transition: opacity 300ms ease-in;
}
var ReactCSSTransitionGroup= React.addons.CSSTransitionGroup;
  var TodoList = React.createClass({
    getInitialState: function() {
      return {items: ['hello', 'world', 'click', 'me']};
    },
    handleAdd: function() {
      var newItems =
        this.state.items.concat([prompt('Enter some text')]);
      this.setState({items: newItems});
    },
    handleRemove: function(i) {
      var newItems = this.state.items.slice();
      newItems.splice(i, 1);
      this.setState({items: newItems});
    },
    render: function() {
      var items = this.state.items.map(function(item, i) {
        return (
          <div key={item} onClick={this.handleRemove.bind(this, i)}>
            {item}
          </div>
        );
      }.bind(this));
      return (
        <div>
          <button onClick={this.handleAdd}>Add Item</button>
          <ReactCSSTransitionGroup transitionName="example" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
            {items}
          </ReactCSSTransitionGroup>
        </div>
      );
    }
  });

ReactDOM.render(<TodoList/>, document.getElementById('example'));

以上代码实现了添加元素到list的淡入淡出的效果,首先引入扩展组件ReactCSSTransitionGroup。定义了一个TodoList的组件实现添加项和点击移除项的功能。

React使用JS模拟动画

为了兼容不支持CSS3的浏览器,用JS来模拟CSS3动画的效果
动画 = 连续播放的静止页面 ← 帧
transition: left 100px ease-in; → 总时间1秒,每秒向右移动1/100px

  • 使用方法
function doMove() {
  foo.style.left=(foo.style.left+10)+'px';
  setTimeout(doMove,20);
  this.setState(...);
}
  • 实例 (查看Demo)
var Positioner = React.createClass({
      getInitialState: function() {
          return {
              position: 0
          };
      },
      resolveSetTimeout: function() {
          if (this.state.position < this.props.position) {
              this.setState({
                  position: this.state.position + 1
              });
          }
      },
      componentDidUpdate: function() {
          if (this.props.position) {
              setTimeout(this.resolveSetTimeout, this.props.timeoutMs);
          }
      },
      render: function() {
              var divStyle = {
                  marginLeft: this.state.position
              };
              return <div style={divStyle}> This will animate! </div> 
          } 
      }) 
 ReactDOM.render(<Positioner/>, document.getElementById('example'));
 ReactDOM.render(<Positioner position={100} timeoutMs={10} />, document.getElementById('example'));

以上代码使用setTimeout实现文字向右移动100px的功能。

React使用rAF动画

rAF = requestAnimationFrame 即优化过的JS模拟动画,兼容IE10+,普通JS动画改widthheight是有先后顺序的,而rAF则是同时改变widthheight

  • 实例: (查看Demo)
var Positioner = React.createClass({
    getInitialState: function() {
        return {
            position: 0
        };
    },
    resolveSetTimeout: function() {
        if (this.state.position < this.props.position) {
            this.setState({
                position: this.state.position + 1
            });
        }
    },
    componentDidUpdate: function() {
        if (this.props.position) {
            requestAnimationFrame(this.resolveSetTimeout);
        }
    },
    render: function() {
            var divStyle = {
                marginLeft: this.state.position
            };
            return <div style={divStyle}> This will animate! </div> 
        } 
    }) ;
ReactDOM.render(<Positioner/>, document.getElementById('example'));
ReactDOM.render(<Positioner position={100}  />, document.getElementById('example'));

React中使用 AJAX

组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法重新渲染 UI。
实例:代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 本身没有任何依赖。(查看Demo

 var UserGist = React.createClass({
   getInitialState: function() {
     return {
       username: '',
       lastGistUrl: ''
     };
   },
   componentDidMount: function() {
     $.get(this.props.source, function(result) {
       var lastGist = result[0];
       if (this.isMounted()) {
         this.setState({
           username: lastGist.owner.login,
           lastGistUrl: lastGist.html_url
         });
       }
     }.bind(this));
   },
   render: function() {
     return (
       <div>
         {this.state.username}'s last gist is
         <a href={this.state.lastGistUrl}>here</a>.
       </div>
     );
   }
 });
 ReactDOM.render(
   <UserGist source="https://api.github.com/users/octocat/gists" />,
   document.getElementById('example')
 );

实例:可以把一个Promise对象传入组件。(查看Demo

var RepoList = React.createClass({ 
    getInitialState: function() { 
       return { 
           loading: true,
            error: null, 
            data: null}; 
    },
     componentDidMount() { 
          this.props.promise.then( 
            value => this.setState({
              loading: false, 
              data: value
            }), 
            error => this.setState({
              loading: false,
               error: error
            }));
      }, 
      render: function() {
            if (this.state.loading) { 
                return <span>Loading...</span>;
            } else if (this.state.error !== null) {
                return <span>Error: {this.state.error.message}</span>; 
            } else { 
              var repos = this.state.data.items; 
              var repoList = repos.map(function (repo) { 
              return (<li>
            <a href={repo.html_url}>{repo.name}</a> 
            ({repo.stargazers_count} stars)
            <br/> 
            {repo.description}
           </li>
      ); }); return (
      <main>
          <h1>Most Popular JavaScript Projects in Github</h1>
          <ol>{repoList}</ol>
      </main>
      ); } } }); ReactDOM.render(
      <RepoList promise={
      $.getJSON( 'https://api.github.com/search/repositories?q=javascript&sort=stars')
      } />, document.getElementById('example'));

上面代码从Github的API抓取数据,然后将Promise对象作为属性,传给RepoList组件。
如果Promise对象正在抓取数据(pending状态),组件显示"正在加载";如果Promise对象报错(rejected状态),组件显示报错信息;如果Promise对象抓取数据成功(fulfilled状态),组件显示获取的数据。

React 组件性能调优

原因

React在开发的时候就非常注重性能,作为前端框架性能是极其重要的评判标准
React提高性能的方式:虚拟DOM,diff算法,将DOM操作减到最少
但是……

  • 父组件更新默认触发所有子组件更新
  • 列表类型的组件默认更新方式非常复杂

    调优原理

    性能问题:
  • 父组件更新默认触发所有子组件更新
  • 列表类型的组件默认更新方式非常复杂
    解决方法
  • 子组件覆盖shouldComponentUpdate方法,自行决定是否更新
  • 给列表中的组件添加key属性
  • React调优原理

覆盖了shouldComponentUpdate之后的更新流程,SCU红色表示false,绿色表示true,返回true的话就触发整个更新流程,false为跳过更新流程。vDOMEq表示虚拟dom节点是否相同

寻找性能热点

  • React性能分析工具

React调优原理

//一个动作超过200ms ,用户就有感知
React.addons.Perf.start();//开始记录时间
React.addons.Perf.stop();//停止记录时间
React.addons.Perf.printInclusive();//输出时间

调优前实例:(查看Demo)

React分析工具结果
调优点,父组件更新是每次都更新子组件,所以添加覆盖shouldComponentUpdate方法判断是否要更新子组件。

//调优点,每次都更新子组件,添加此方法判断是否更新子组件
shouldComponentUpdate: function (nextProps) {
    return nextProps.render
},
//...
//通知子组件是否要更新
render={i == this.state.inputNumber || i == this.state.lastNumber} 

调优后实例:(查看Demo)

React分析工具结果

解决性能问题

  • PureRenderMixin
    React官方提供的Mixin,用于“简单的”判断是否需要进入更新流程
    本质上就是判断状态和属性是否改变

  • Immutability
    Immutability Helpers,实现JS不可变对象
    修改不可变对象会返回一个新对象,之前的对象保持不变
    不同对象在JS引擎中的引用不同,因此直接比较引用即可确定是否发生改变
    基本语法:update(obj, cmd)

    执行前 执行命令 执行后
    a = [1, 2, 3] update(a, {$push: [4]}) [1, 2, 3, 4]
    a = [1, 2, 3] update(a, {$unshift: [3]}) [1, 2]
    a = [1, 2, 3] update(a, {$splice: [1, 1, 4]}) [4, 2, 3]
    a = [1, 2, 3] update(a, {$set: [1, 2, 3]}) [1, 2, 3]
    a = {i: 3} update(a, {$merge: {j: 4}}) {i: 3, j: 4}
    a = [1, 2, 3] update(a, {$apply: function(x) {return x * 2;}}) [2, 4, 6]

在React使用ES6 Classes

官方文档

class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>;
  }
}
ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);

The API is similar to React.createClass with the exception of getInitialState. Instead of providing a separate getInitialState method, you set up your own state property in the constructor.

Another difference is that propTypes and defaultProps are defined as properties on the constructor instead of in the class body.

export class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
    this.tick = this.tick.bind(this);
  }
  tick() {
    this.setState({count: this.state.count + 1});
  }
  render() {
    return (
      <div onClick={this.tick}>
        Clicks: {this.state.count}
      </div>
    );
  }
}
Counter.propTypes = { initialCount: React.PropTypes.number };
Counter.defaultProps = { initialCount: 0 };

No Autobinding,No Mixins

React开发工具

React的相关库

测试库 Jest (无法在windows中使用)

Jest是Facebook开发的一个JavaScript的单元测试工具,单元测试可以保证函数或者模块完成我们想要的功能,使用Jest需要配置NodeJS环境。
常见的测试:

  • 单元测试:测试具体功能是否正常
  • 集成测试:测试整个系统是否正常
  • 回归测试:确保改动没有破坏系统
  • 冒烟测试:快速发现问题
  • 压力测试:确定系统性能

安装:

$ npm install jest-cli --save-dev

使用(查看文档):
Let's get started by writing a test for a hypothetical sum.js file:

function sum(a, b) {
  return a + b;
}
module.exports = sum;

Create a directory __tests__/ with a file sum-test.js:

jest.unmock('../sum'); // unmock to use the actual implementation of sum

describe('sum', () => {
  it('adds 1 + 2 to equal 3', () => {
    const sum = require('../sum');
    expect(sum(1, 2)).toBe(3);
  });
});

ImmutableJS

Immutable-js是Facebook开发的一个JS库,能够在JS中实现不可变对象
不可变对象可以大大提高对象比较性能,用于状态和属性判断非常有效
实际上,提高比较性能的代价是降低修改性能,只不过收益更大。

原理:

Immutable-js原理

实例:(查看Demo)

Flux

Flux不是一个具体的框架,而是Facebook提出的一种代码架构
React只是一个视图库,Flux是在React基础上对于前端整体的组织方案
传统的MVC只适用于中小型系统,对大规模系统来说MVC复杂度过高
Flux的目的是保证逻辑清晰,数据流向清晰,依赖关系清晰
贯彻数据单向流动

Flux架构

官方事例(查看官方example):

React测试工具

TestUtils

React自带了测试工具:React.addons.TestUtils (查看官方文档
使用TestUtils可以:模拟操作、渲染组件、定位和操作子组件
TestUtils只是封装了和React相关的内容,还需要结合其他测试工具使用

用法:

TestUtils.renderIntoDocument(<helloworld></helloworld>)

利用renderIntoDocument方法把目标组件渲染到DOM节点中。

React.addons.TestUtils.Simulate.click(node)

使用Simulate模拟操作,如模拟点击Simulate.click(node),其中node为dom节点。

TestUtils.scryRenderedDOMComponentsWithTag(subject, "li")

以上代码功能是定位和寻找子组件返回一个集合,第一个参数是组件,第二个是标签。

Jasmine

Mocha

Enzyme

React项目实战

TTD

TDD:Test-driven development 测试驱动开发
先写测试,再进行开发,用测试驱动开发和重构。
各种驱动开发:

  • DDD,设计驱动开发
  • BDD,行为驱动开发
  • FDD,特性驱动开发
  • HDD,假设驱动开发

移动端自适应解决方案

rem

概念

REM是相对单位,是相对HTML根元素。

rem兼容性:

  • ios:6.1系统以上都支持
  • android:2.1系统以上都支持
  • IE8+

rem基准值计算

  1. 固定值:设置的基准值为1rem = 100px,当在写移动端布局的单位时,直接将设计稿的数值除以100在加上rem的单位就可以了。
  2. 动态设置:根据屏幕,,动态设置html的font-size
// 设置页面html元素的font-size值
!function(win) {
    var doc = win.document,
        docEle = doc.documentElement,
        num = 10,
        changeScreenSize = "orientationchange" in win ? "orientationchange" : "resize",
        setFontSize = function() {
            var clientWidth = docEle.clientWidth;
            docEle.style.fontSize = clientWidth / num + "px"
        };
    doc.addEventListener && (win.addEventListener(changeScreenSize, setFontSize, false), doc.addEventListener("DOMContentLoaded", setFontSize, false))
}(window);

DPR

众所周知移动设备,高清方案就是根据设备屏幕有DPR(device pixel ratio,设备像素比,又称DPPX,比如dpr=2时,表示1个CSS像素由4个物理像素点组成)的,dpr比较大,所以显示的像素较为清晰。

viewport

根据设备屏幕的DPR,自动设置viewport进行缩放,。

//通过添加meta元素,进行缩放设置
const dpr = window.devicePixelRatio;
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width, initial-scale=' + 1 / dpr + ', maximum-scale=' + 1 / dpr + ', minimum-scale=' + 1 / dpr + ', user-scalable=no');
document.getElementsByTagName('head')[0].appendChild(meta);

设计稿

一般来说设计稿都是以ipone6的分辨率进行设计的,所以设计给的稿子双倍的原因是iphone6这种屏幕属于高清屏,也就是2dpr。

  • sass
@function px2rem($px){
    $rem : 75px;
    @return ($px/$rem) + rem;
}
  • stylus
px2rem(px)
   $rem=75
   (px/$rem)rem

git 笔记

LearnGit(Git学习笔记)

learngit

配置

$ git config --global user.name "Your Name"
$ git config --global user.email "[email protected]"

因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。
自定义Git
让Git显示颜色,会让命令输出看起来更醒目:

$ git config --global color.ui true

更多配置查看Pro Git 自定义Git配置

创建版本库

什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
首先创建一个空文件夹,如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
第二步,通过git init命令把这个目录变成Git可以管理的仓库:

  $ git init

不要使用Windows自带的记事本编辑任何文本文件,因为编码问题。

小结:

  • 初始化一个Git仓库,使用git init命令。
  • 添加文件到Git仓库,分两步:
  • 第一步,使用命令git add <file>,注意,可反复多次使用,添加多个文件;
  • 第二步,使用命令git commit,完成。

查看状态

  • 要随时掌握工作区的状态,使用git status命令。
  • 如果 git status 告诉你有文件被修改过,用git diff可以查看修改内容。

查看log

git log命令显示从最近到最远的提交日志

git log

简化一行显示使用参数--pretty-oneline

git log --pretty-oneline

恢复指定版本

首先,Git必须知道当前版本是哪个版本,在Git中,用 HEAD 表示当前版本,也就是最新的提交3628164...882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

git reset --hard HEAD^

返回后面的版本,版本号没必要写全,前几位就可以了,405e7f6Git会自动去找。

$ git reset --hard 405e7f6

在Git中,总是有后悔药可以吃的。当你用$ git reset --hard HEAD^回退到以前时,再想恢复到当前版本时,就必须找到当前的commit id。Git提供了一个命令git reflog用来记录你的每一次命令:

$ git reflog

提交后,用git diff HEAD -- test.md命令可以查看工作区和版本库里面最新版本的区别:

$ git diff HEAD -- test.md

小结:

  • HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id
  • 穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
  • 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。

撤销修改

Git会告诉你,git checkout -- file可以丢弃工作区的修改:

$ git checkout -- test.md

就是让这个文件回到最近一次git commitgit add时的状态。
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file

场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了场景1,第二步按场景1操作。

场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。

管理修改

每次修改,如果不add到暂存区,那就不会加入到commit

删除文件

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了:

$ rm test.txt

这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了:

现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:

$ git checkout -- test.txt

命令git rm用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。

Github

在github新建仓库后,
新建仓库内容:

echo "# learngit" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/kenve/learngit.git
git push -u origin master

本地仓库的内容推送到GitHub仓库:

git remote add origin https://github.com/kenve/learngit.git
git push -u origin master

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

要关联一个远程库,使用命令git remote add origin https://github.com/kenve/learngit.git

关联后,使用命令git push -u origin master第一次推送master分支的所有内容;

此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改。

克隆项目

假设远程库已经准备好了,下一步是用命令git clone克隆一个本地库:

$ git clone [email protected]:kenve/gitskills.git
# $ git clone https://github.com/reactjs/redux.git 

分支管理

创建与合并分支

创建dev分支,然后切换到dev分支:

$ git checkout -b dev

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

$ git branch dev
$ git checkout dev

然后,用git branch命令查看当前分支:

$ git branch

git branch命令会列出所有分支,当前分支前面会标一个 * 号。
切换分支用$ git checkout name 命令。

git merge命令用于合并指定分支到当前分支,把 dev 分支的工作成果合并到 master 分支上:

$ git merge dev

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

  • 小结
    Git鼓励大量使用分支:

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>

解决冲突

Git用<<<<<<<=======>>>>>>> 标记出不同分支的内容。

当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。

git log --graph命令可以看到分支合并图。

$ git log --graph --pretty=oneline --abbrev-commit

分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
合并分支时,请注意--no-ff参数,表示禁用Fast forward

$ git merge --no-ff -m "merge with no-ff" dev

因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

BUG分支

创建一个分支issue-101来修复BUG,修复好后合并分支,但是,当前正在dev上进行的工作还没有提交(没有完成无法提交):
幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

$ git stash

现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:

$ git checkout master
$ git checkout -b issue-101

修复完成后,切换到master分支,并完成合并,最后删除issue-101分支.
git stash list命令查看隐藏的分支工作现场。

$ git stash list

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;

另一种方式是用git stash pop,恢复的同时把stash内容也删了:

$ git stash pop

你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:

$ git stash apply stash@{0}

Feature分支

添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。

但不需要这个新功能时,要将其销毁,当使用$ git branch -d <name> 进行销毁时,会提醒销毁失败。Git友情提醒,feature分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令git branch -D <name>

$ git branch -D feature-vulcan

多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin

要查看远程库的信息,用git remote,或者,用git remote -v显示更详细的信息:

$ git remote
$ git remote -v
origin  [email protected]:kenve/learngit.git (fetch)
origin  [email protected]:kenve/learngit.git (push)

推送分支:
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
推送分支:git push origin <name>

$ git push origin dev

但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要时刻与远程同步;
  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!

更新分支:
git fetch 命令与一个远程的仓库交互,并且将远程仓库中有但是在当前仓库的没有的所有信息拉取下来然后存储在你本地数据库中。
从远程端获取最新版到本地:

$ git fetch origin master 

比较本地仓库与远程参考区别:

$ git log -p master.. origin/master

把远程端下载下来的代码合并到本地仓库,远程和本地合并:

$ git merge origin/master

方式二:可以下载 master 为新分支 temp 然后在合并分支,然后删除 temp

#1
$ git fetch origin master:temp
#2
$ git diff temp
#3
$ git merge temp 
#4
$ git branch -d temp

抓取分支
git pull 命令基本上就是 git fetchgit merge 命令的组合体,Git 从你指定的远程仓库中抓取内容,然后马上尝试将其合并进你所在的分支中。
多人协作时,大家都会往masterdev分支上推送各自的修改。
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。
现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地:

$ git checkout -b dev origin/dev

他就可以在dev上继续修改,然后,时不时地把dev分支push到远程。而碰巧你也对同样的文件作了修改,并试图推送:$ git push origin dev。推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置devorigin/dev 的链接:

$ git branch --set-upstream dev origin/dev

这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push。
多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin branch-name推送自己的修改;
  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
  3. 如果合并有冲突,则解决冲突,并在本地提交;
  4. 没有冲突或者解决掉冲突后,再用 git push origin branch-name 推送就能成功!
  5. 从远程端获取最新版到本地 $ git fetch origin branch-name
    如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name

小结

  • 查看远程库信息,使用git remote -v
  • 本地新建的分支如果不推送到远程,对其他人就是不可见的;
  • 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
  • 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name ,本地和远程分支的名称最好一致;
  • 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name
  • 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

标签管理

发布一个版本时,我们通常先在版本库中打一个标签,这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。

创建标签

在Git中打标签非常简单,首先,切换到需要打标签的分支上$ git checkout master,然后,敲命令git tag <name>就可以打一个新标签:

$ git tag v1.0

可以用命令git tag查看所有标签。
默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是找到历史提交的commit id,使用$ git tag <tag-name> <commit-id>,然后打上就可以了:

$ git log --pretty=oneline --abbrev-commit

$ git tag v0.9 6224937

注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息:
还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:

$ git tag -a v0.1 -m "version 0.1 released" 3628164

还可以通过-s用私钥签名一个标签:

$ git tag -s v0.2 -m "signed version 0.2 released" fec145a

签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错:
如果报错,请参考GnuPG帮助文档配置Key,GitHub GPG key配置

小结:

  • 命令git tag 用于新建一个标签,默认为HEAD,也可以指定一个commit id;
  • git tag -a -m "blablabla..."可以指定标签信息;
  • git tag -s -m "blablabla..."可以用PGP签名标签;
  • 命令git tag可以查看所有标签。

操作标签

如果标签打错了,也可以使用$ git tag -d <name>删除:

$ git tag -d v0.1

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
如果要推送某个标签到远程,使用命令git push origin <tagname>

$ git push origin v1.0

或者,一次性推送全部尚未推送到远程的本地标签:

$ git push origin --tags

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:

$ git tag -d v0.9

然后,从远程删除。删除命令也是push,但是格式如下:

$ git push origin :refs/tags/v0.9

小结:

  • 命令git push origin <tagname>可以推送一个本地标签;
  • 命令git push origin --tags可以推送全部未推送过的本地标签;
  • 命令git tag -d <tagname>可以删除一个本地标签;
  • 命令git push origin :refs/tags/<tagname>可以删除一个远程标签。

自定义Git

忽略文件

忽略某些文件时,需要编写.gitignore,查看Github所有配置文件
.gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# Python:
*.py[cod]
*.so
*.egg
dist
build

忽略文件的原则是:

  • 忽略操作系统自动生成的文件,比如缩略图等;
  • 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
  • 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

配置别名

如果敲git st就表示git status那就简单多了。
我们只需要敲一行命令,告诉Git,以后st就表示status

$ git config --global alias.st status

当然还有别的命令可以简写,很多人都用co表示checkoutci表示commitbr表示branch

$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch

以后提交就可以简写成:

$ git ci -m "bala bala bala..."

--global参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。
我们知道,命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个unstage操作,就可以配置一个unstage别名:

$ git config --global alias.unstage 'reset HEAD'

配置一个git last,让其显示最后一次提交信息:

$ git config --global alias.last 'log -1'

甚至还有人丧心病狂地把lg配置成了:

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

配置文件
配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。

配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中。
别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。
而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中:
配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

搭建Git服务器

GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。

搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt命令就可以完成安装。

假设你已经有sudo权限的用户账号,下面,正式开始安装。

第一步,安装git:

$ sudo apt-get install git

第二步,创建一个git用户,用来运行git服务:

$ sudo adduser git

第三步,创建证书登录:
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。

第四步,初始化Git仓库:
先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:

$ sudo git init --bare sample.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后,把owner改为git

$ sudo chown -R git:git sample.git

第五步,禁用shell登录:
出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。

第六步,克隆远程仓库:
现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行:

$ git clone git@server:/srv/sample.git

管理公钥

如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys文件里就是可行的,如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。

管理权限

要方便管理公钥,用Gitosis
要像SVN那样变态地控制权限,用Gitolite

更多

Mastering Markdown

Headers

This is an h1> tag

This is an h2> tag

This is an h6> tag

Emphasis

This text will be italic
This will also be italic

This text will be bold
This will also be bold

_You _can* combine them*
Lists

Unordered

  • Item 1
  • Item 2
    • Item 2a
    • Item 2b
      Ordered
  1. Item 1
  2. Item 2
  3. Item 3
    • Item 3a
    • Item 3b
      Images

GitHub Logo
Format: Alt Text
Links

http://github.com - automatic!
GitHub
Blockquotes

As Kanye West said:

We're living the future so
the present is our past.
Inline code

I think you should use an

<addr> element here instead.

function fancyAlert(arg) {
  if(arg) {
    $.facebox({div:'#foo'})
  }
}

Task Lists

  • @mentions, #refs, links, formatting, and tags supported
  • list syntax required (any unordered or ordered list supported)
  • this is a complete item
  • this is an incomplete item
First Header Second Header
Content from cell 1 Content from cell 2
Content in the first column Content in the second column

SHA!:
16c999e8c71134401a78d4d46435517b2271d6ac
mojombo@16c999e8c71134401a78d4d46435517b2271d6ac
mojombo/github-flavored-markdown@16c999e

Issue references within a repository
Any number that refers to an Issue or Pull Request will be automatically converted into a link.
#1

mojombo#1
mojombo/github-flavored-markdown#1

Username @kenve

Automatic linking for URLs:http://www.github.com/

Strikethrough

Any word wrapped with two tildes (like this) will appear crossed out.

Emoji 💯 🔢

javascript工具函数

时间戳转换为YY-MM-DD hh:mm:ss 字符串格式

timeFormat(timestamp) {
    //timestamp是整数,否则要parseInt转换
    var time = new Date(timestamp);
    var y = time.getFullYear();
    var m = time.getMonth() + 1;
    var d = time.getDate();
    var h = time.getHours();
    var mm = time.getMinutes();
    var s = time.getSeconds();
    return y + '-' + add02Time(m) + '-' + add02Time(d) + ' ' +add02Time(h) + ':' +add02Time(mm) + ':' + add02Time(s);
};
add02Time(m) {
    return m < 10 ? '0' + m : m
},

把对象中的属性拼接到URL参数中(url?a=x&b=y)

joinParamsInGetReq(url, param) {
    if (param) {
        let paramsArray = []
        Object.keys(param).forEach(key => paramsArray.push(key + '=' + encodeURIComponent(param[key])))
        if (url.search(/\?/) === -1) {
            url += '?' + paramsArray.join('&')
        } else {
            url += '&' + paramsArray.join('&')
        }
    }
    return url;
},

对象或数组的深拷贝

deepCopyObj(obj) {
    var str, newobj = obj.constructor === Array ? [] : {};
    if (typeof obj !== 'object') {
        return;
    } else if (window.JSON) {
        str = JSON.stringify(obj), //系列化对象
            newobj = JSON.parse(str); //还原
    } else {
        for (var i in obj) {
            newobj[i] = typeof obj[i] === 'object' ? this.deepCopyObj(obj[i]) : obj[i];
        }
    }
    return newobj;
},

判断浏览器类型

/*
 * 智能机浏览器版本信息:
 *
 */
var browser = {
        versions: function() {
            var u = navigator.userAgent,
                app = navigator.appVersion;
            return { //移动终端浏览器版本信息 
                trident: u.indexOf('Trident') > -1, //IE内核
                presto: u.indexOf('Presto') > -1, //opera内核
                webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
                gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
                mobile: !!u.match(/AppleWebKit.*Mobile.*/) || !!u.match(/AppleWebKit/), //是否为移动终端
                ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
                android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器
                iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否为iPhone或者QQHD浏览器
                iPad: u.indexOf('iPad') > -1, //是否iPad
                webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
                wechat: u.toLowerCase().match(/MicroMessenger/i) == "micromessenger" //判断是否为微信浏览器
            };
        }(),
        language: (navigator.browserLanguage || navigator.language).toLowerCase() //语言版本
    } 

Cookie

    /**
     * 获取cookie
     * @param  {[type]} c_name [cookie名称]
     * @return {[type]}        [cookie的value]
     */
function getCookie(c_name) {
    if (document.cookie.length > 0) {
        c_start = document.cookie.indexOf(c_name + "=");
        if (c_start != -1) {
            c_start = c_start + c_name.length + 1;
            c_end = document.cookie.indexOf(";", c_start);
            if (c_end == -1) c_end = document.cookie.length;
            return unescape(document.cookie.substring(c_start, c_end));
        }
    }
    return "";
}
/**
 * 设置cookie的值和日期
 * @param {[type]} c_name     [cookie名称]
 * @param {[type]} value      [cookie的值]
 * @param {[type]} expiredays [cookie有效期]
 */
function setCookie(c_name, value, expiredays) {
    var exdate = new Date();
    exdate.setDate(exdate.getDate() + expiredays);
    document.cookie = c_name + "=" + escape(value) +
        ((expiredays == null) ? "" : ";expires=" + exdate.toGMTString());
}
/**
 * [删除 cookie]
 * @param  {[type]} name [description]
 * @return {[type]}      [description]
 */
function delCookie(name) {
    var exp = new Date();
    exp.setTime(exp.getTime() - 1);
    var cval = getCookie(name);
    if (cval != null) document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
}

Toast提示

function warn(msg, opt, left, top) {
    if (opt) {
        var obj = $("#" + opt);
    }
    new Toast({ context: $('body'), message: msg }, obj, left, top).show();
}
var Toast = function(config, obj, left, top) {
    this.context = config.context == null ? $('body') : config.context; //上下文
    this.message = config.message; //显示内容
    this.time = config.time == null ? 3000 : config.time; //持续时间
    this.left = config.left; //距容器左边的距离
    // this.top = (screen.availHeight / 4) * 2; //距容器上方的距离
    this.top = config.top;
    if (obj) {
        this.left = obj.offset().left + left;
        this.top = obj.offset().top + top;
    }
    this.init();

}
var msgEntity;
Toast.prototype = {
    //初始化显示的位置内容等
    init: function() {
        $("#toastMessage").remove();
        //设置消息体
        var msgDIV = new Array();
        msgDIV.push('<div id="toastMessage">');
        msgDIV.push('<span>' + this.message + '</span>');
        msgDIV.push('</div>');
        msgEntity = $(msgDIV.join('')).appendTo(this.context);
        //设置消息样式
        var left = this.left == null ? this.context.width() / 2 - msgEntity.find('span').width() / 2 - 20 : this.left; //padding 10+10
        var top = this.top == null ? '55%' : this.top;
        msgEntity.css({ position: 'fixed', top: top, 'z-index': '99', left: left, 'background': '#000', color: 'white', 'font-size': '.26rem', padding: '10px', 'border-radius': '4px', '-moz-border-radius': '4px', '-webkit-border-radius': '4px' });
        //msgEntity.addClass(".toast");
        msgEntity.hide();
    },
    //显示动画
    show: function() {
        msgEntity.show();
        setTimeout(function() {
            msgEntity.hide();
        }, this.time / 2);
        // msgEntity.fadeIn(this.time / 2);
        // msgEntity.fadeOut(this.time / 2);
    }
}

截取规定长度的字符串

/**
 * 截取字符串
 */
function cutString(str, len) {
    if (str.length * 2 <= len) {
        return str;
    }
    var strlen = 0;
    var s = "";
    for (var i = 0; i < str.length; i++) {
        s = s + str.charAt(i);
        if (str.charCodeAt(i) > 128) {
            strlen = strlen + 2;
            if (strlen >= len) {
                return s.substring(0, s.length - 1) + "...";
            }
        } else {
            strlen = strlen + 1;
            if (strlen >= len) {
                return s.substring(0, s.length - 2) + "...";
            }
        }
    }
    return s;
}

返回顶部

function backTop() {
    $('html,body').scrollTop(0);
}

function checkPosition(pos) {
    if ($(window).scrollTop() > pos) {
        $('#top').css('visibility', 'visible');
    } else {
        $('#top').css('visibility', 'hidden');
    }
}

根据分辨率设置html的font-size值

! function(win) {
    var doc = win.document,
        docEle = doc.documentElement,
        num = 10,
        changeScreenSize = "orientationchange" in win ? "orientationchange" : "resize",
        setFontSize = function() {
            var clientWidth = docEle.clientWidth;
            docEle.style.fontSize = clientWidth / num + "px"
        };
    doc.addEventListener && (win.addEventListener(changeScreenSize, setFontSize, !1), doc.addEventListener("DOMContentLoaded", setFontSize, !1))
}(window);

判断浏览设备竖屏或者横屏

// 判断横屏竖屏
(function(window) {
    var supportOrientation = (typeof window.orientation === 'number' &&
        typeof window.onorientationchange === 'object');
    var init = function() {
        var htmlNode = document.body.parentNode,
            orientation;

        var updateOrientation = function() {
            if (supportOrientation) {
                orientation = window.orientation;
                switch (orientation) {
                    case 90:
                    case -90:
                        orientation = 'landscape';
                        break;
                    default:
                        orientation = 'portrait';
                        break;
                }
            } else {
                orientation = (window.innerWidth > window.innerHeight) ? 'landscape' : 'portrait';
            }

            htmlNode.setAttribute('class', orientation);
            //
            if (orientation==='landscape') {
                console.log('横屏')
            } else if(orientation==='portrait'){
                console.log('竖屏')
            }
        };
        if (supportOrientation) {
            window.addEventListener('orientationchange', updateOrientation, false);
        } else {
            //监听resize事件
            window.addEventListener('resize', updateOrientation, false);
        }
        updateOrientation();
    };
    window.addEventListener('DOMContentLoaded', init, false);
})(window);

设置缩放比

//設置縮放 ipone6 2x 
const dpr = window.devicePixelRatio;
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width, initial-scale=' + 1 / dpr + ', maximum-scale=' + 1 / dpr + ', minimum-scale=' + 1 / dpr + ', user-scalable=no');
document.getElementsByTagName('head')[0].appendChild(meta);
/* ipone6 设计稿尺寸转rem(sass)*/
@function px2rem($px){
    $rem : 75px;
    @return ($px/$rem) + rem;
}
/* ipone6 设计稿尺寸转rem(stylus)*/
 px2rem(px)
    $rem=75
    (px/$rem)rem

js解析XML 字符串

var loadXML = function(xmlString){ //构建xmldoc对象
   
     var xmlDoc=null;
      
      if(window.DOMParser)  //IE9+,FF,webkit
      {
            try{
                
                domParser = new  DOMParser();
                xmlDoc = domParser.parseFromString(xmlString, 'text/xml');
            }catch(e){
            }
        }
        else if(!window.DOMParser && window.ActiveXObject)
        {   //window.DOMParser 判断是否是非ie浏览器
            var xmlDomVersions = ['MSXML2.DOMDocument','Microsoft.XMLDOM'];
            for(var i=0;i<xmlDomVersions.length;i++){
                try{
                    xmlDoc = new ActiveXObject(xmlDomVersions[i]);
                    xmlDoc.async = false;
                    xmlDoc.loadXML(xmlString); //loadXML方法载入xml字符串
                    break;
                }catch(e){
                    continue;
                }
            }
        }
        else{
            return null;
        }

        return xmlDoc;
    }

性能优化:使用Javascript原生的API

what

在做项目性能优化的时候,经常会看到一条原则就是:尽量使用JavaScript原生的API。

是否真的JS原生的API比框架执行效率高

下面使用js原生api和jq的api分别进行十万次的dom操作。

 function excutedTime(fn) {
            let preTime = new Date().getTime();
            if (!fn) {
                return;
            }
            fn();
            let endTime = new Date().getTime();
            let time = endTime - preTime;
            console.log(time);
        }
        // 
        for (let j = 0; j < 10; j++) {
            //jq
            excutedTime(function () {
                for (var i = 0; i < 100000; i++) {
                    var a = $('#id').val();
                }
            });
             //原生
             excutedTime(function () {
                for (var i = 0; i < 100000; i++) {
                    var a = document.getElementById('id').value;
                }
            });
        }

Win10 Chrome 62.0.3202.94(64)环境下执行10次得到如下数据:

jq框架 原生js
200 7
141 5
111 4
115 5
112 6
112 4
109 5
113 5
119 4
128 4

从以上执行结果可以看出执行同样的操作,使用jq框架的API操作时间大概是:120ms左右,而使用js原始API操作时间大概为:4ms左右,执行效率是jq框架30倍左右。所以在进行大量数据操作的时候,尽量使用js原生API

为什么JS原生API的执行效率高

  • 时间
    1.类似于Jquery这类框架的理念都是The Write Less, Do More,所以会额外执行解决API兼容性的通用操作,会带来额外的业务逻辑耗费额外的时间。
    2.浏览器内核能解析的只是js原生API,加了额外的框架相当于多了很多无关的调用。
    3.有些框架API定义在原型上,原型链查找耗时、额外的变量和函数查找耗时增加。
  • 空间
    1.栈:声明额外的变量,例如全局变量$,消耗栈空间,内存占用变大。
    2.堆:实例化对象数量的增加,存于堆空间中,内存占用变大。
    3.引用额外的库,使得js资源体积增加,消耗网络请求和存储空间。
  • 原生方法使用的是编译型语言。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.