Giter VIP home page Giter VIP logo

blog's People

Contributors

matthew-sun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

React漫谈

谈必及React。

React优势

  • 快。Virtual DOM的原理是做一份DOM树的拷贝在 javascript 的内存里,当DOM需要被改变的时候,通过比较虚拟DOM得到两者的不同,根据这个不同来更新真实的DOM。 快的原因是虚拟DOM只更新了两次DOM不同的地方,深层次的原因是javascript很快,而浏览器操作DOM很慢。
  • ‘learn once, write everywhere’。虽然讲ReactJs是js的产物,但是它已经基本跳出了web前端的范畴,可以是web, ios, android。重要的还有,它解决了困扰前端单页面应用的SEO问题,React在服务端可以通过renderToString方法把页面渲染出来。这样当爬虫爬到你的页面的时候,得到的将会是一个完整的页面,而不是一个需要执行的Js代码了。
  • 新概念。感受新技术的魅力,Web Components, ECMA 2015, webpack, data flow等等概念都可以在React上得到体验。

Virtual DOM的性能瓶颈

虚拟DOM很快,但他并不是没有性能瓶颈,因为React在render之前会做虚拟DOM树的比较操作,如果数据并没有发生改变,做这个额外的比较操作无疑是会增加开销的。好在React给出了自己的解决方案,那就是shouldComponentUpdate,shouldComponentUpdate的含义是这样的,在Component接受到新的props或者state,将要渲染之前调用,如果返回 false,那么组件不会更新。这里shouldComponentUpdate传入了两个参数,nextProps和 nextState,我们可以通过拿到的参数和当前的props和state进行比较,判断如果相等,那么返回false,告知组件不去更新。你可以使用PureRenderMixin插件,来帮你重复执行这个操作。

var PureRenderMixin = require('react').addons.PureRenderMixin;
React.createClass({
    mixins: [PureRenderMixin],

  render: function() {
    return <div className={this.props.className}>foo</div>;
    }
});

不过通过PurePenderMixin你只能进行浅层次的比较,如果想要使用深层比较的话,数据层你可以采用immutable-js,然后再使用上react-immutable-render-mixin就可以进行深层次比较了。

智慧组件和木偶组件

在智慧组件和木偶组件的概念中,可复用的组件是木偶组件,而起到控制展示作用的是智慧组件。

木偶组件
  • 不依赖任何Flux的actions和stores.
  • 通常允许包含在props.children里.
  • 接收数据和回调通过props.
  • 独立样式,包含有自己的css文件.
  • 通常自己没有state.
  • 很少用其他的木偶组件.
  • 比如:Page, Sidebar, Story, UserInfo, List.
智能组件
  • 包含木偶组件或其他的智慧组件.
  • 提供木偶组件Flux stores.
  • 提供木偶组件Flux actions.
  • 没有自己的css样式,或仅有布局的框架样式.
  • 没有自己的Dom结构,只包含木偶组件.
  • 比如:UserPage, FollowersSidebar, StoryContainer, FollowedUserList.
这样写的好处
  • 解耦,在写应用的时候更好的理解各个Component的职责。
  • 重用,木偶组件可以被不同的数据状态驱动。
  • 木偶组件就像是在以一种搭积木的方式构建应用,不涉及到任何的业务逻辑。
  • 强迫用这样一种形式把布局组件抽离出来,可以更好的重用布局结构。

路由

React经常被拿去做单页面应用,react-router是一个不错的React路由管理框架。

var routes = (
  <Route handler={App} path="/">
    <DefaultRoute handler={Home} />
    <Route name="about" handler={About} />
    <Route name="users" handler={Users}>
        <Route name="recent-users" path="recent" handler={RecentUsers} />
        <Route name="user" path="/user/:userId" handler={User} />
        <NotFoundRoute handler={UserRouteNotFound}/>
    </Route>
    <NotFoundRoute handler={NotFound}/>
    <Redirect from="company" to="about" />
  </Route>
);

Router.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

// Or, if you'd like to use the HTML5 history API for cleaner URLs:

Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});

移动端tap事件

做过移动端的同学都知道,ios在处理click事件的时候有300ms的延迟。在zepto的方案下我们会有一个tap事件去解决这个问题,而在React中我们可以使用react-tap-event-plugin

var React = require('react'),
injectTapEventPlugin = require("react-tap-event-plugin");
injectTapEventPlugin();

var Main = React.createClass({
render: function() {
    return <button onTouchTap={this._handleTouchTap}>Tap Me</button>
},

_handleTouchTap: function() {
    alert('Tap');
}
});

React.render(<Main />, document.body);

动画

React的动画库,react-tween-state.

var tweenState = require('react-tween-state');
var React = require('react');

var App = React.createClass({
    mixins: [tweenState.Mixin],
    getInitialState: function() {
        return {left: 0};
    },
    handleClick: function() {
        this.tweenState('left', {
            easing: tweenState.easingTypes.easeInOutQuad,
            duration: 500,
            endValue: this.state.left === 0 ? 400 : 0
        });
    },
    render: function() {
        var style = {
            position: 'absolute',
            width: 50,
            height: 50,
            backgroundColor: 'lightblue',
            left: this.getTweeningValue('left')
        };
        return <div style={style} onClick={this.handleClick} />;
    }
});

延伸阅读

WebIM技术---编写前端WebSocket组件

过去我们想要实现一个实时Web应用通常会考虑采用ajax轮循或者是long polling技术,但是因为频繁的建立http连接会带来多余的请求以及消息精准性的问题,让我们在实现实时Web应用时头疼不已。现在,Html5提出了WebSocket协议来规范解决了这个问题。

ajax轮询,long polling技术实现原理

ajax轮询

ajax轮询非常简单了,就是在客户端设置一个定时器,频繁的去请求接口,看有没有数据返回,但是这样很明显会有很多的多余请求,导致服务器压力巨大。。

long polling

long polling技术,其实是在ajax轮询的基础上再做了一些优化,客户端请求服务端看有没有返回,如果暂时没有,服务端会把请求短暂的挂起,当有数据的时候再把数据返回给客户端,这时候客户端再另起一个请求,询问服务器。
为了保证连接的通道不断,我们通常会设置一个timeout,当过了这个timeout时还没有数据,服务端会把挂起的服务关闭,返回空的数据,然后客户端再进行请求。
这样做虽然比ajax轮询好很多,但是当消息量大的时候,请求数还是很多。而且轮询还极有可能在传输的过程中遇到消息丢失的情况,这时候需要服务端做消息缓存等处理。

WebSocket协议

WebSocket协议本质上是一个基于TCP的协议,它由通信协议和编程API组成,WebSocket能够在浏览器和服务器之间建立双向连接,以基于事件的方式,赋予浏览器实时通信能力。既然是双向通信,就意味着服务器端和客户端可以同时发送并响应请求,而不再像HTTP的请求和响应。
简单来说,WebSocket就是一个长连接通道,在这个通道里客户端和服务端可以自由的发送消息给对方,而且不用管对方是否有返回,双方都有关闭这个通道的权利。

WebSocket通信场景

客户端:啦啦啦,我要建立Websocket协议,Websocket协议版本:17(HTTP Request)
服务端:ok,确认,已建立Websocket(HTTP Protocols Switched)
客户端:有消息的时候记得推给我哦。
服务端:ok,有的时候会告诉你的。
服务端:balabalabalabala
服务端:balabalabalabala
服务端:哈哈哈哈哈啊哈哈哈哈
客户端:哈哈哈哈哈哈哈,你可以不用返回
...

使用WebSocket有什么好处

  • WebSocket 能节约带宽、CPU 资源并减少延迟。
  • WebSocket 基于事件交流,通信简单。
  • WebSocket 可以跨域。

WebSocket API介绍

建立WebSocket连接

var ws = new WebSocket('ws://www.websocket.org')

为WebSocket 对象添加 4 个不同的事件:

  • open
  • message
  • error
  • close

代码示例:

// 当websocket连接建立成功时
ws.onopen = function() {
    console.log('websocket 打开成功');    
};

// 当收到服务端的消息时
ws.onmessage = function(e) {
    // e.data 是服务端发来的数据
    console.log(e.data);
};

// 当websocket关闭时
ws.onclose = function() {
    console.log("websocket 连接关闭");
};

// 当出现错误时
ws.onerror = function() {
    console.log("出现错误");
};

WebSocket对象方法

  • send
  • close

代码示例:

// 发送消息 
ws.send('blablabla')

// 关闭socket
ws.close()

定义一个前端Socket组件

为什么需要定义一个Socket组件

HTML5提供的SocketAPI太过于简陋,并不能满足复杂环境的socket交互需要,API的调用也不太方便。

定义一个什么样的Socket组件

  • 简单好用的客户端和服务端的双向通信API
  • 支持断线重连功能
  • 支持自定义事件
  • 能够自由感知socket状态信息

Socket组件示例

首先要和服务端约定互相可以识别的通信协议,假设我们约定的通信协议是

// 客户端发送给服务端
{
    method: 'xxx',
    request: {} 
}

// 服务端返回给客户端
{
    data: {},
    success: true,
    errorCode: 0,
    request: {} // 如果是服务端主动推消息给客户端,request会带有method参数
                // 如果是服务端返回客户端请求,request就是客户端之前请求的数据
}

然后我们上代码:

/*
 * @example
 *  var ws = new Socket('ws://www.websocket.org')
 *  ws.on('ready',function() {
 *      console.log('服务器连接成功');
 *      ws.on('message', function(json) {
 *          console.log('一条新消息:'+json.session);
 *      });
 *      ws.emit("send", {
 *          session: "一条新消息"
 *      })
 *  })
 *  ws.on("error",function(){
 *      console.log("连接报错")
 *  })
 *  ws.on("close",function(){
 *      console.log("连接关闭");
 *  })
 */

function Socket(url) {
    this.init(url)
}

Socket.prototype = {
    init: function(url) {
        this.initListeners()
        this.initSocket(url)
        this.bindSocketEvent()
    },

    initSocket: function(url) {
        this.url = url
        this.socket = new WebSocket(url)
        return this
    },

    initListeners: function() {
        this.listeners = {}
        return this
    },

    bindSocketEvent: function() {
        var me = this

        me.socket.onopen = function() {
            me.stopHeartBeat()
            me.startHeartBeat()
            me.clearAll()
            me.trigger('ready')
        }

        me.socket.onerror = function(e) {
            me.trigger('close', e)
            me.close()
        }

        me.socket.onmessage = function(e) {
            me.refreshServerTimer();
            var json = JSON.parse(e.data);
            me.trigger(json.request.method, json);
        }

        return this
    },

    reConnect: function() {
        this.initSocket(this.url).bindSocketEvent()
        this.trigger('reconnect')
    },

    isOffline: function() {
        return this.socket.readyState != WebSocket.OPEN
    },

    on: function(evt, fn) {
        var me = this

        if(me.listeners[evt] && me.listeners[evt].length) {
            if(me.listeners[evt].indexOf(fn) == -1){
                me.listeners[evt].push(fn)
            }
        }else {
            me.listeners[evt] = [fn]
        }

        return this
    },

    off: function(evt, fn) {
        var me = this

        if(me.listeners[evt] && me.listeners[evt].length){
            var index = me.listeners[evt].indexOf(fn)

            if(index != -1){
                me.listeners[evt].splice(index,1)
            }
        }

        return this
    },

    emit: function(method, info) {
        var me = this

        me.socket.send(JSON.stringify({
            method: method,
            request: info || ''
        }))

        return this
    },

    trigger: function(evt) {
        var me = this

        if(me.listeners[evt]) {
            for(var i=0; i<me.listeners[evt].length; i++) {
                me.listeners[evt][i].apply(me, [].slice.call(arguments,1))
            }
        }

        return this
    },

    startHeartBeat: function() {
        var me = this

        me.heartBeatTimer = setInterval(function() {
            me.emit("heartBeat")
        }, 5000)
    },

    stopHeartBeat: function() {
        clearInterval(this.heartBeatTimer)
    },

    //重新开始断线计时,20秒内没有收到任何正常消息或心跳就超时掉线
    refreshServerTimer: function() {
        var me = this

        clearTimeout(me.serverHeartBeatTimer)
        me.serverHeartBeatTimer = setTimeout(function() {
            me.trigger("disconnect")
            me.close()
            me.reConnect()
        }, 20000)
    },

    clearAll: function() {
        var tmp = this.listeners['ready']

        this.listeners = {}
        this.listeners['ready'] = tmp

        return this
    },

    close: function(options) {
        var me = this;

        clearTimeout(me.serverHeartBeatTimer);
        me.stopHeartBeat();
        me.socket.close();

        return this
    }
}

介绍一下组件里的心跳包机制:
因为一些原因,有这么一种情况,socket还在客户端连着,但是服务端和客户端之间却没有办法互相发送消息,我们称这种现象叫做WebSocket失活。

组件里采用的解决办法是,客户端每5秒钟向服务端发送心跳包,讲道理服务端会返回一个心跳包以保活。但是如果客户端检查1分钟内没有收到服务端的返回,客户端会自动重连WebSocket。

这里有个坑,请躲好。。

WebSocket在建立连接之前,会先发一个http协议询问服务端要不要建立WebSocket,因为http请求是会带上cookie的,这时候如果域名下的cookie太多,有可能会导致WebSocket建立连接失败。。

我这里的解决方案是,更换接口的域名地址,利用WebSocket可以跨域的特性绕过当前域的cookie建立连接。

前端MVC组件**

在经典的MVC架构模式中,前端的东西(html,css,javascript)都属于一个大view中,因此我们要更加深入的拆分javascript做的事情,使得js在各段位置中的职责更加清晰。
比如说数据校验,数据存储可以抽象成前端这个层面的model;
参数解析,数据和表现的组合,逻辑跳转等可以抽象成前端这个层面的controller;
css,html模板,事件绑定等可以抽象成前端这个层面的view。

传统组件开发模式:

我们往往会像下面这段代码一样去开发一个js组件

var newComponent = Class({
    init : function(cfg) {
        var defaults = {};
        cfg = $.extend({},defaults,cfg);
        this.renderUI();
        this.bindEvents();
    },
    // 绘制UI
    renderUI : function() {
        var htmlStr = itpl(tpl,data);
        $('body').append(htmlStr);
    },
    // 事件绑定
    bindEvents : function() {
        var handleEvent = this.handleEvent;
        cfg.el.on('click',handleEvent);
        cfg.el.on('mouseenter',handleEvent);
        ...
    },
    // 事件处理
    handeEvent : function(e) {
        switch (e.type) {
            case : 'click' : this._onClick();
                break;
            case : 'mouseenter' : this._onMouseEnter();
        }
    },
    _onClick : function() {
        ...
    },
    _onMouseEnter : function() {
        ...
    },
    // 销毁,释放内存
    destory : function() {
        ...
    }

})

大致上看我们每开发一个组件,都会有init,bindEvents,handleEvent,renderUI,destory这些方法,使用这种命名及代码结构,肯以让我们清晰的知道这个组件有哪些内容。当A写了这样一个组件,B去修改时,往往从bindEvents方法中去顺推着往下找就可以很轻松的找到需要修改的代码。这样写非常好阿,代码职责也很清晰,那又有什么问题呢?

1.依赖于JQUERY之类的框架操作DOM,以至于造成代码臃肿,不利于维护

2.renderUI中,数据往往和视图模板耦合在一起,代码职责不清晰。

3.类似于这样的命名方式,往往依赖于前端小组内的代码规范,不同团队,可能规范不一样,跨团队的前端代码维护起来也会非常的麻烦。即使是统一小组内,也有可能会有不遵守代码规范的情况,使用命名,约定,结构的方法去规范前端代码始终不是上乘之道。

MVC组件开发(解决方案):

jsMVC分解原则

数据校验,数据存储可以抽象成前端这个层面的model;
参数解析,数据和表现的组合,逻辑跳转等可以抽象成前端这个层面的controller;
css,html模板,事件绑定等可以抽象成前端这个层面的view。

依据上面的原则,我们来看angular.js是如何划分一个MVC的组件结构,前端MVC组件方案是否可行?


html:

<ul class="list-group">
  <li class="list-group-item repeat" ng-repeat="item in vm.items">
    {{item}}
    <button type="button" class="close" ng-click="vm.delItem($index)">&times;</button>
  </li>
</ul>
<button class="btn btn-default mmm" ng-click="vm.addItem()">添加一条</button>

css:

/*给repeat中的enter和leave事件添加基础动画*/
.repeat.ng-enter,
.repeat.ng-leave {
  -webkit-transition: 0.5s linear all;
  transition: 0.5s linear all;
}

.repeat.ng-enter,
.repeat.ng-leave.ng-leave-active {
  opacity: 0;
}

.repeat.ng-leave,
.repeat.ng-enter.ng-enter-active {
  opacity: 1;
}

js:

angular.module('ngShowcaseApp').controller('ctrl.animation.basic', function ($scope) {
  var vm = $scope.vm = {};

  vm.items = ['item1', 'item2'];
  vm.itemId = 3;

  vm.addItem = function() {
    vm.items.push('item' + vm.itemId);
    vm.itemId++;
  };

  vm.delItem = function(index) {
    vm.items.splice(index, 1);
  };
});

依据angular的这个实现Demo,我们可以看到,事件绑定,UI渲染被牢牢的控制在view层,数据和逻辑控制被解耦在了angular提供的controller里。模块与模块之间又被module所区分,这样的代码职责清晰,且利于维护。

当然了,在前端领域性能也是一个非常重要的考量维度,当页面的数据操作不多,就不建议使用angular这样的MVVM框架了,毕竟angular的代码已经上W行了。

希望这篇文章能对你有用,和我一起交流~

致程序设计的五大原则

OO设计的五大原则。1、单一职责原则;2、开放封闭原则;3、里氏替换原则;4、依赖倒置原则;5、接口隔离原则。因为js的对象大多是和页面中的dom元素相关的,我们有时会忽略这些设计原则,导致我们的js代码变得难以维护,不利于大型前端项目或者SPA的开发。本文着重介绍这五种设计原则,以及和前端相结合的点。

单一职责原则

概念解释:

就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个动机去改变一个类,那么这个类就具有多于一个类的职责。应该把多于的职责分离出去,分别再创建一些类来完成每一个职责。简而言之,一个类仅干一件事。

前端意义:

在前端的业务层中,我们有时会使用一个单例模式,创建一个对象,耦合页面中所有的业务操作,做到对命名空间的划分,和方便统一操作。但是,在这个中间,会遇到一个问题,从职责上划分,这个对象拥有多个职责,不符合单一职责原则。有什么坏处?第一点,单个程序文件的代码超过300行,我就认为这个代码是难以维护的,这里显然很容易超过这个量级。第二点,业务层的代码之后若是要抽离成组件,代价巨大,因为类的职责不够清晰。第三点,代码难以被其他业务复用,并且业务代码也很难被定制。

举个例子:

多职责:
var index = (function(){
    return {
        login : function() {
            ...
        },
        dialog : function() {
            ...
        },
        tab : function() {
            ...
        },
        play : function() {
            ...
        },
        ...
    }
})();

单一职责(利用seajs):    
define(function(require, exports, module){
    var $ = require("jquery");
    var login = require("./login");
    var dialog = require("./dialog");
    var tab = require("./tab");
    var play = require("./play");

    $(function(){
        login();
        dialog();
        tab();
        play();
    })
})

开放封闭原则

概念解释:

一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。简而言之,修改类只能在外部扩展,不能在内部修改。

前端意义:

这个场景在前端编写js类库的时候经常用到,大名鼎鼎的jquery使用的插件机制,就是符合了这一个原则,对内封闭,对外开放。对于开放封闭原则的代码实现,我们往往要用到的是自定义事件。在对象内部,派发事件,在对象外部,通过扩展订阅事件。

自定义事件代码:

/**
 * EVENT model
 */
var func = {};

;(function(func) {

    var _cache = {};

    /**
     * 广播事件
     * 目标: 为了尽可能的减少模块之间业务逻辑的耦合度, 而开发了这个eventbus, 主要用于业务逻辑的事件传递
     * 使用规范: 每个js模块尽可能通过事件去通信, 减少模块之间的直接调用和依赖(耦合)
     */

    /**
     * 派发
     * @param  {[type]} type 事件类型
     * @param  {[type]} data 回调数据
     * @return {[type]}      [description]
     */
    func.fire = function(type, data) {
        var listeners = _cache[type],
            len = 0;
        if (typeof listeners !== 'undefined') {
            var args = [].slice.call(arguments, 0);
            args = args.length > 2 ? args.splice(2, args.length - 1) : [];
            args = [data].concat(args);

            len = listeners.length;
            for (var i = 0; i < len; i++) {
                var listener = listeners[i];
                if (listener && listener.callback) {
                    args = args.concat(listener.args);
                    listener.callback.apply(listener.scope, args);
                }
            }
        }
        return this;
    }

    /**
     * 订阅广播事件
     * @param  {[type]}   types     事件类型,支持,分隔符
     * @param  {Function} callback 回调函数
     * @param  {[type]}   scope    回调函数上下文
     * @return {[type]}            this
     */

    func.on = function(types, callback, scope) {
        types = types || [];
        var args = [].slice.call(arguments);

        if (typeof types === 'string') { 
            types = types.split(',');
        }
        var len = types.length;
        if (len === 0) {
            return this;
        }
        args = args.length > 3 ? args.splice(3, args.length - 1) : [];
        for (var i = 0; i < len; i++) {
            var type = types[i];
            _cache[type] = _cache[type] || [];
            _cache[type].push({
                callback: callback,
                scope: scope,
                args: args
            });
        }
        return this;
    }

    /**
     * 退订
     * @param  {[type]}   type     [description]
     * @param  {Function} callback 假如传入则移出传入的监控事件,否则移出全部
     * @return {[type]}            [description]
     */

    func.un = function(type, callback, scope) {
        var listeners = _cache[type];
        if (!listeners) {
            return this;
        }
        if (callback) {
            var len = listeners.length,
                tmp = [];

            for (var i = 0; i < len; i++) {
                var listener = listeners[i];
                if (listener.callback == callback && listener.scope == scope) {} else {
                    tmp.push(listener);
                }
            }
            listeners = tmp;
        } else {
            listeners.length = 0;
        }
        return this;
    }

    func.removeAll = function() {
        _cache = {};
        return this;
    }

})(func)

里氏替换原则

概念解释:

子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

    前端意义:

    在前端中,比如弹出层,父类可以定义一些抽象方法,派发给子类去实现,多用于对子类代码的规范。

依赖倒置原则

概念解释:

具体依赖于抽象,抽象不依赖于具体。

前端意义:

这个很简单了,抽象的方法不应该是依赖于具体的实现的。

接口隔离原则

概念解释:

  • 一个类对另外一个类的依赖性应该建立在最小的接口上
  • 一个接口代表一个角色,不应该把不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染.
  • 不应该强迫客户依赖他们不用的方法。接口属于客户,不属于他所在的类的层次结构

前端意义:

当我们使用接口时在接口里面定义的方法要做要高类聚,作用要单一,不能把什么都放在里面,要用到多接口。在前端中,业务层代码,只需用一个像单一职责模式中的例子,很容易发展成为一个胖接口。所以,我引入了一个区域模块的概念,即一个页面的一块区域的代码接口耦合,这样也方便模块的整拿争取。

举个例子:

// 区域模块:
define(function(require, exports, module){
    var $ = require("jquery");
    var login = require("./login");
    var dialog = require("./dialog");

    function loginArea() {
        login();
        dialog();
    }
    return loginArea;
})

// 业务模块:  
define(function(require, exports, module){
    var $ = require("jquery");
    var loginArea = require("./loginArea");
    var tab = require("./tab");
    var play = require("./play");

    $(function(){
        loginArea();
        tab();
        play();
    })
})

制作运动卡路里消耗计算器

肥胖的根本原因是能量摄入超过能量消耗。正确的减重应该是减少脂肪组织内的脂肪,而每克脂肪会产生9大卡的热量,所以欲减少1公斤的脂肪,就医学观点来计算,就必须消耗7700大卡的热量。运动卡路里消耗计算器是运动减肥的必备工具,只要在填写您的体重和相应运动项目的时间,就可以算出今天活动消耗的卡路里。那么这个神奇的计算器是怎么制作的呢,一起来看看吧。

运动卡路里消耗计算器

卡路里消耗计算器是运动减肥的必备工具,只要在填写您的体重和相应运动项目的时间,就可以算出今天活动消耗的卡路里哦,赶紧试试吧!

点击demo查看:demo

手机扫描二维码查看:

二维码地址

核心算法

卡路里消耗公式:

消耗的能量(卡路里) = 体重(kg)* 运动时间(小时)*指数K

需要指出的是这个公式不是很精准,因为它忽视了年龄、性别、体质和基础代谢率等因素,只是给大家提供一个参考。

code

转换成code就是:

burnCalories = Math.floor(weight *minute/60*K);

/**
 * 指数K
 * 运动卡路里消耗数据
 * 单位(小时):卡路里/公斤
 */

K = {
    '仰卧起坐' : 9.38 ,
    '一般健美操' : 4.02 ,
    '减肥健美操,强度较大' : 6.7 ,
    '芭蕾,现代舞,扭摆舞,爵士舞,踢踏舞,吉特巴舞' : 5.09 ,
    '快速跳舞' : 6.03 ,
    '慢慢跳舞(华尔兹,狐步)' : 2.68 ,
    '爱尔兰舞,波尔卡' : 4.69 ,
    '一般舞蹈,草裙舞,希腊舞,佛拉明哥舞,摇摆舞' : 4.69 ,
    '慢速游泳,自由式,中低强度' : 8.04 ,
    '游泳(侧泳)' : 9.38 ,
    '游泳(仰式)' : 8.04 ,
    '蛙泳,一般' : 12.06 ,
    ... // 不一一列出了,感兴趣的同学可以去我的github上扒一下源码
}

移动端适配

PC端,有很多类似工具,但是在移动端,倒是没有见到类似的产品,所以我这个“卡路里消耗计算器”主要是针对移动端的客户群体。

Icon fonts

本项目的所使用的图标,都是字体图标,PC端不敢使用的东西,终于可以在移动浏览器上大胆使用啦,放心,大多数手机浏览器都是可以很好的支持的。我这里使用的Icon fonts是Font-Awesome,看到2W+stars你就放心使用吧,基于的开源协议是MIT。

Kontext

有的同学可能觉得,这个项目中在增加运动项目的时候的专场效果非常不错,是怎么做的呢?我使用的是Kontext这个开源项目,原理是让css3和js做一下配合就可以轻松做到啦,回头我会再写一篇博文来剖析Kontext的实现原理。

一点点不爽

虽然项目很小,但是还是有大量的操作针对着dom(获取,取值,插入,删除),未来想把项目用angularjs,backbonejs,vuejs等等的mv框架进行次改装,已增进自己对mv的认识,提升自己的技术能力。

ECMA5系列介绍---Array

ECMAScript 5规范在09年正式发布了,随着智能手机的普及和浏览器厂商的支持,无线前端开发者们也终于可以放心的在项目中实际使用了。本文是ECMA5系列介绍的一篇,主要讲解的是关于Array相关的API。

Array.isArray(object)

描述:

判断一个对象是否为数组。

参数:

object:必需。需要判定的对象。

返回:

true or false

示例:

var arr1 = []
console.log(Array.isArray(arr1))
// true

var arr2 = new Array()
console.log(Array.isArray(arr2))
// true

var arr3 = [1,2,3,4]
console.log(Array.isArray(arr3))
// true

var arr4 = 'is Array'
console.log(Array.isArray(arr4))
// false

array1.indexOf(searchElement[, fromIndex])

描述:返回某个值在数组中第一个匹配项的索引。

参数:

array1:必需。一个数组对象
searchElement:必需。搜索的值。
fromIndex:可选。用于开始搜索的数组索引。如果省略 fromIndex,则从索引 0 处开始搜索。

返回:返回搜索的值在数组中第一个匹配的索引值,找不到,则返回-1

示例:

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

向前兼容:

Array.prototype.indexOf = Array.prototype.indexOf || function(searchElement) {
    var len = this.length
    var i = +arguments[1] || 0 // fromIndex

    if(len === 0 || isNaN(i) || i>=len) {
        return -1
    }
    if(i<0) {
        i = len + i
        i<0 && i=0
    }
    for(; i<len; i++) {
        if( this.hasOwnProperty(String(i)) && this[i] ===  searchElement )
            return i
    }
    return -1
}

array1.lastIndexOf(searchElement[, fromIndex])

描述:

返回搜索值在数组中最后一个匹配项的索引。

参数:

array1:必需。一个数组对象。
searchElement:必需。搜索的值。
fromIndex:可选。用于开始搜索的数组索引。如果省略 fromIndex,则搜索将从数组中的最后一个索引处开始。

返回:

数组中的 searchElement 的最后一个匹配项的索引;如果未找到 searchElement,则为 -1。

示例:

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

向前兼容:

Array.prototype.lastIndexOf = Array.prototype.lastIndexOf || function(searchElement) {
    var len = this.length
    var i = +arguments[1] || len-1 // fromIndex

    if(len === 0 || isNaN(i) || i>=len) {
        return -1
    }
    if(i<0) {
        i = len + i
    }else if(i>=len){
        i = len - 1
    }

    for(; i>=0; i--) {
        if( this.hasOwnProperty(String(i)) && this[i] ===  searchElement )
            return i
    }
    return -1
}

array1.every(callbackfn[, thisArg])

描述:

确定数组成员是否满足指定测试。

参数:

array1:必需。一个数组对象。
callbackfn:必需。一个接受最多三个参数的函数。every 方法会为 array1 中的每个元素调用 callbackfn 函数,直到 callbackfn 返回 false,或直到到达数组的结尾。
thisArg:可选。可在 callbackfn 函数中为其引用 this 关键字的对象。如果省略 thisArg,则 undefined 将用作 this 值。

返回值:

如果 callbackfn 函数为所有数组元素返回 true,则为 true;否则为 false。如果数组没有元素,则 every 方法将返回 true。

备注:

every 方法会按升序顺序对每个数组元素调用一次 callbackfn 函数,直到 callbackfn 函数返回 false。 如果找到导致 callbackfn 返回 false 的元素,则 every 方法会立即返回 false。 否则,every 方法返回 true。

回调函数语法

回调函数的语法如下所示:
function callbackfn(value, index, array1)
可使用最多三个参数来声明回调函数。
value:数组元素的值。
index:数组元素的数字索引。
array1:包含该元素的数组对象。

示例:

var arr = [1,2,3,4]
var result = arr.every(function(value) {
    return value>0
})
console.log(result)
// true

下面例子解释thisArg的用法:

var arr = [1,2,3,4]
var result = arr.every(function(value) {
    return value>this.zero
},{
    zero: 0
})
console.log(result)
// true

向前兼容:

var __isCallable = (function(){ 

    var __sortCase = (function(){
            try {
                [].sort('abc');
                return false;
            } catch(ex) {
                return true;
            }
        })();

    return function(obj){
        if( typeof obj === 'function' )
            return true;
        if( typeof obj !== 'object' ) 
            return false;
        if( obj instanceof Function || obj instanceof RegExp )
            return true;
        if( __sortCase ) {
            try {
                [].sort(obj);
                return true;
            } catch(ex){ /* nothing to do */ }
        }
        return false;
    };
})();

Array.prototype.every = Array.prototype.every || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var thisArg = arguments[1]; 
    for(var i=0, len=this.length; i < len; i++) {
        if( this.hasOwnProperty(String(i)) ) {
            if( !callback.call(thisArg, this[i], i, this) )
                return false;
        }
    }

    return true;
};

array1.some(callbackfn[, thisArg])

描述:

确定数组成员是否满足指定测试。

参数:

array1:必需。一个数组对象。
callbackfn:必需。一个接受最多三个参数的函数。some 方法会为 array1 中的每个元素调用 callbackfn 函数,直到 callbackfn 返回 true,或直到到达数组的结尾。
thisArg:可选。可在 callbackfn 函数中为其引用 this 关键字的对象。如果省略 thisArg,则 undefined 将用作 this 值。

返回值:

如果 callbackfn 函数为任一数组元素返回 true,则为 true;否则为 false。

备注:

some 方法会按升序索引顺序对每个数组元素调用 callbackfn 函数,直到 callbackfn 函数返回 true。 如果找到导致 callbackfn 返回 true 的元素,则 some 方法会立即返回 true。 如果回调不对任何元素返回 true,则 some 方法会返回 false。

回调函数语法

回调函数的语法如下所示:
function callbackfn(value, index, array1)
可使用最多三个参数来声明回调函数。
value:数组元素的值。
index:数组元素的数字索引。
array1:包含该元素的数组对象。

示例:

var arr = [1,2,3,4]
var result = arr.some(function(value) {
    return value>2
})
console.log(result)
// true

下面例子解释thisArg的用法:

var arr = [1,2,3,4]
var result = arr.every(function(value) {
    return value>this.min
},{
    min: 2
})
console.log(result)
// true

向前兼容:

var __isCallable = (function(){ 

    var __sortCase = (function(){
            try {
                [].sort('abc');
                return false;
            } catch(ex) {
                return true;
            }
        })();

    return function(obj){
        if( typeof obj === 'function' )
            return true;
        if( typeof obj !== 'object' ) 
            return false;
        if( obj instanceof Function || obj instanceof RegExp )
            return true;
        if( __sortCase ) {
            try {
                [].sort(obj);
                return true;
            } catch(ex){ /* nothing to do */ }
        }
        return false;
    };
})();

Array.prototype.every = Array.prototype.every || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var thisArg = arguments[1]; 
    for(var i=0, len=this.length; i < len; i++) {
        if( this.hasOwnProperty(String(i)) ) {
            if( callback.call(thisArg, this[i], i, this) )
                return false;
        }
    }

    return true;
};

array1.forEach(callbackfn[, thisArg])

描述:

为数组中的每个元素执行指定操作。

参数:

array1:必需。一个数组对象。
callbackfn:必需。最多可接受三个参数,为数组中的每个元素都执行一次callbackfn函数。
thisArg:可选。callbackfn 函数中的 this 关键字可引用的对象。如果省略 thisArg,则 undefined 将用作 this 值。

示例:

var arr = [1,2,3,4]

arr.forEach(funcion(value, index, arr) {
    console.log('value:'+value+', index:'+index)
})

// output
// value:1, index:0
// value:2, index:1
// value:3, index:2
// value:4, index:3

向前兼容:

Array.prototype.forEach = Array.prototype.forEach || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var thisArg = arguments[1]; 
    for(var i=0, len=this.length; i < len; i++) {
        if( this.hasOwnProperty(String(i)) ) {
            callback.call(thisArg, this[i], i, this);
        }
    }       
}

array1.map(callbackfn[, thisArg])

描述:

对数组的每个元素调用定义的回调函数并返回包含结果的数组。

参数:

array1:必需。一个数组对象。
callbackfn:必需。最多可以接受三个参数的函数。 对于数组中的每个元素,map 方法都会调用 callbackfn 函数一次。
thisArg:可选。可在 callbackfn 函数中为其引用 this 关键字的对象。如果省略 thisArg,则 undefined 将用作 this 值。

返回值:

一个新数组,其中的每个元素均为关联的原始数组元素的回调函数返回值。

备注:

回调函数的语法如下所示:
function callbackfn(value, index, array1)
可使用最多三个参数来声明回调函数。
value:数组元素的值。
index:数组元素的数字索引。
array1:包含该元素的数组对象。

示例:

var arr = [1,2,3,4]
var result = arr.map(function(value) {
    return value*2
})
console.log(result)
// [2,4,6,8]

下面例子解释thisArg的用法:

var arr = [1,2,3,4]
var result = arr.map(function(value) {
    return value*this.number
},{
    min: 2
})
console.log(result)
// [2,4,6,8]

向前兼容:

Array.prototype.map = Array.prototype.map || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var thisArg = arguments[1],
        len = this.length,
        results = new Array(len);
    for(var i=0; i < len; ++i) {
        if( this.hasOwnProperty(String(i)) ) {
            results[i] = callback.call(thisArg, this[i], i, this);
        }
    }

    return results;
};

array1.filter(callbackfn[, thisArg])

描述:

对数组的每个元素调用定义的回调函数并返回包含结果的数组。

参数:

array1:必需。一个数组对象。
callbackfn:必需。最多可以接受三个参数的函数。 对于数组中的每个元素,filter 方法都会调用 callbackfn 函数一次。
thisArg:可选。可在 callbackfn 函数中为其引用 this 关键字的对象。如果省略 thisArg,则 undefined 将用作 this 值。

返回值:

一个包含回调函数为其返回 true 的所有值的新数组。 如果回调函数为 array1 的所有元素返回 false,则新数组的长度为 0。

备注:

回调函数的语法如下所示:
function callbackfn(value, index, array1)
可使用最多三个参数来声明回调函数。
value:数组元素的值。
index:数组元素的数字索引。
array1:包含该元素的数组对象。

示例:

var arr = [1,2,3,4]
var result = arr.filter(function(value) {
    return value>2
})
console.log(result)
// [3,4]

下面例子解释thisArg的用法:

var arr = [1,2,3,4]
var result = arr.filter(function(value) {
    return value>this.min
},{
    min: 2
})
console.log(result)
// [3,4]

向前兼容:

Array.prototype.filter = Array.prototype.filter || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var thisArg = arguments[1],
        len = this.length,
        results = [];
    for(var i=0; i < len; ++i) {
        if( this.hasOwnProperty(String(i)) ) {
            callback.call(thisArg, this[i], i, this) && results.push( this[i] );
            }
        }

    return results;
};

array1.reduce(callbackfn[, thisArg])

描述:

对数组中的所有元素调用指定的回调函数。该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。

参数:

array1:必需。一个数组对象。
callbackfn:必需。一个接受最多四个参数的函数。 对于数组中的每个元素,reduce 方法都会调用 callbackfn 函数一次。
thisArg:可选。如果指定 initialValue,则它将用作初始值来启动累积。 第一次调用 callbackfn 函数会将此值作为参数而非数组值提供。

返回值:

通过最后一次调用回调函数获得的累积结果。

备注:

回调函数的语法如下所示:

function callbackfn(value, index, array1)
可使用最多三个参数来声明回调函数。
previousValue:通过上一次调用回调函数获得的值。 如果向 reduce 方法提供 initialValue,则在首次调用函数时,previousValue 为 initialValue。
currentValue:当前数组元素的值。
currentIndex:当前数组元素的数字索引。
array1:包含该元素的数组对象。

第一次调用回调函数:

在第一次调用回调函数时,作为参数提供的值取决于 reduce 方法是否具有 initialValue 参数。
如果向 reduce 方法提供 initialValue:

  • previousValue 参数为 initialValue。
  • currentValue 参数是数组中的第一个元素的值。

如果未提供 initialValue:

  • previousValue 参数是数组中的第一个元素的值。
  • currentValue 参数是数组中的第二个元素的值。

示例:

var arr = [1,2,3,4]
var result = arr.reduce(function(previousValue, currentValue) {
    return previousValue + currentValue
})
console.log(result)
// 10

下面例子解释initialValue的用法:

var arr = [1,2,3,4]
var result = arr.reduce(function(previousValue, currentValue) {
    return previousValue + currentValue
},5)
console.log(result)
// 15

向前兼容:

Array.prototype.reduce = Array.prototype.reduce || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var len = this.length;
    if( len === 0 && arguments.length < 2 )
        throw new TypeError( "reduce of empty array with no initial value" );

    var initIdx = -1;
    if( arguments.length < 2 ) {
        if( (initIdx = __firstIndex(this)) === -1 )
            throw new TypeError( "reduce of empty array with no initial value" );               
    }

    var val = arguments.length > 1 ? arguments[1] : this[initIdx];

    for(var i=initIdx+1; i < len; i++) {
        if( this.hasOwnProperty(String(i)) ) {
            val = callback(val, this[i], i, this);
        }
    }

    return val;
};

array1.reduceRight(callbackfn[, thisArg])

描述:

按降序顺序对数组中的所有元素调用指定的回调函数。 该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。

参数:

array1:必需。一个数组对象。

callbackfn:必需。一个接受最多四个参数的函数。 对于数组中的每个元素,reduce 方法都会调用 callbackfn 函数一次。

thisArg:可选。如果指定 initialValue,则它将用作初始值来启动累积。 第一次调用 callbackfn 函数会将此值作为参数而非数组值提供。

返回值:

通过最后一次调用回调函数获得的累积结果。

备注:

回调函数的语法如下所示:

function callbackfn(value, index, array1)
可使用最多三个参数来声明回调函数。
previousValue:通过上一次调用回调函数获得的值。 如果向 reduce 方法提供 initialValue,则在首次调用函数时,previousValue 为 initialValue。
currentValue:当前数组元素的值。
currentIndex:当前数组元素的数字索引。
array1:包含该元素的数组对象。

第一次调用回调函数:

在第一次调用回调函数时,作为参数提供的值取决于 reduce 方法是否具有 initialValue 参数。
如果向 reduce 方法提供 initialValue:

  • previousValue 参数为 initialValue。
  • currentValue 参数是数组中的最后一个元素的值。

如果未提供 initialValue:

  • previousValue 参数是数组中的最后一个元素的值。
  • currentValue 参数是数组中的倒数第二个元素的值。

示例:

var arr = [1,2,3,4]
var result = arr.reduceRight(function(previousValue, currentValue) {
    return previousValue + currentValue
})
console.log(result)
// 10

下面例子解释initialValue的用法:

var arr = [1,2,3,4]
var result = arr.reduceRight(function(previousValue, currentValue) {
    return previousValue + currentValue
},5)
console.log(result)
// 15

向前兼容写法:

Array.prototype.reduceRight = Array.prototype.reduceRight || function(callback){
    if( !__isCallable(callback) )
        throw new TypeError( callback + " is not a callable object" );

    var len = this.length;
    if( len === 0 && arguments.length < 2 )
        throw new TypeError( "reduce of empty array with no initial value" );

    var initIdx = len;
    if( arguments.length < 2 ) {
        for( var k=len-1; k >=0; --k ) {
            if( this.hasOwnProperty(String(k)) ) {
                initIdx = k;
                break;
            }
        }
        if( initIdx === len )
            throw new TypeError( "reduce of empty array with no initial value" );               
    }       

    var val = arguments.length > 1 ? arguments[1] : this[initIdx];

    for(var i=initIdx-1; i >= 0; --i) {
        if( this.hasOwnProperty(String(i)) ) {
            val = callback(val, this[i], i, this);
        }
    }

    return val;
};

如果有任何问题都可以在下方给予我留言~

正则表达式使用API

常用字符串操作:

indexOf(searchvalue,fromindex):方法返回某个指定字符串在字符中首次出现的位置
    参数:1.必须,检索的字符串值
         2.可选,字符串开始检索的位置,合法取值是0-stringObject.length-1,不填,则从开始检索
substring(start,stop):提取字符串中介于两个指定下标之间的字符
    参数:1.必须,一个非负的整数,要提取的子串的第一个字符在stringObject中的位置
         2.可选,一个非负的整数,要比提取的子串的最后一个字符在stringObject中多1,若省略,则匹配到stringObject的结尾。
charAt(index):返回指定位置的字符
    参数:1.必须,字符在字符串中的下标
split(separator,howmany):把一个字符串分割成字符串数组
    参数:1.必须,字符串或正则表达式,从该参数指定的地方分割stringObject
    2.可选,指定返回数组的最大长度,如果没有设置参数,整个字符串都会被分割

正则表达式:

两个写法:

  • new RegExp(pattern,attributes) 在用于传参数时必须使用
  • /re/g

常用的四个方法:

RegExpObject.test(string):检测字符串是否匹配一个模式,返回布尔值
    参数:1.必须,要检测的字符串
RegExpObject.search(regexp):检索字符串中指定的子字符串,或检索与正则表达式想匹配的字符串
stringObject.match(regexp):正则去匹配字符串,如果匹配成功,就返回匹配成功的数组,如果匹配不成功,就返回null
    示例:
    var str="1 plus 2 equal 3"
    console.log(str.match(/\d+/g))==>[1,2,3]
stringObject.replace(regexp,replacement):正则去匹配字符串,匹配成功的字符去替换成新的字符串
    参数:replacement:可以使字符串,也可以是一个回调函数($0==>母亲,$1==>第一个子项,$2==>第二个子项..)

正则表达式的写法规则

转义字符:
\s:空格
\S:非空格
\d:数字
\D:非数字
\w:字符(字符,数字,下划线)
\W:非字符
\.:真正的点
\b:独立的部分 (起始,结束,空格)
\B:非独立的部分
\1:重复的子项

特殊字符:
$:匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n' 或 ‘\r'。要匹配 $ 字符本身,请使用 \$。
():分组操作,子项。要匹配这些字符,请使用 \( 和 \)。
*:匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+:匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
.:匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \.。
[ ]:标记一个中括号表达式的开始。要匹配 [,请使用 \[。
?:匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\:将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n' 匹配字符 ‘n'。'\n' 匹配换行符。序列 ‘\\' 匹配 “\”,而 ‘\(' 则匹配 “(”。
^:匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。
{ }:标记限定符表达式的开始。要匹配 {,请使用 \{。
|:指明两项之间的一个选择。要匹配 |,请使用 \|。

正则表达式常用判断集合

代码库:Source

1717wa移动端UI组件库---MUI

MUI是基于zepto的轻量级mobile UI组件库,因为1717wan移动站的业务需要而诞生。 MUI由matthewsun开发,基于开源MIT协议,支持商业和非商业用户的免费使用和任意修改,您可以在我的github上快速了解此项目。

MUI是什么?

MUI是基于zepto的轻量级mobile UI组件库,提供了最常用的UI功能,以及自己的js架构理念,还有一套基于Grunt的自动化打包工具。

MUI解决了什么问题?

  1. MUI提供了core,widget,config,view层的分层架构理念,解决了早期1717wan业务层代码过于臃肿的问题;
  2. 提供了一套常用的移动端UI组件库;
  3. 通过函数规范了组件层,业务层代码的编写形式;
  4. MUI提倡的代码编写规范是spec
  5. MUI提供的路由及模板预构建相结合的功能,通过url hash值的改变,实现了移动端页面的无刷新跳转;
  6. MUI提供的自动化工具包括:代码合并,代码压缩,自动css sprites,模板预构建和生成文档功能。

MUI怎么用?

这里有详细的API文档,也可以参考下github中examples文件夹下的例子。

自动化打包工具?

打包代码

grunt build-mui

自动css sprites

grunt build-sprite

预构建js模板

grunt build-tmod

生成文档

通过以下命令可以在doc目录下生成静态文档,也可以在线查看;
grunt build-doc

如果有任何问题都可以在下方给予我留言~

最后感谢伟大的开源社区给予本项目的支持~

校招面试中积累的前端问题

校招面试结束,最后拿到了三家公司的offer,同花顺、PPTV和高德地图,没有想象中的顺利,但也还算是老天眷顾,拿到了高德的special offer,不过最后还是选择离家近的PPTV。这篇文章,主要是记录我在面试中遇到的一些问题以及解决方案。

css问题:

ie6/7下块级元素如何模拟display:inline-block

众所周知,inline-block是一个很好用的属性。它可以将对象呈递为内联对象,但是对象的内容都作为块对象呈递。而旁边的内联对象会被呈递在同一行内,允许空格。

可惜的是,在IE6/7下是不支持这个属性的,这时我们该如何办呢?

这时我们可以考虑让块级元素设置为内联对象呈递(设置属性display:inline),然后触发块元素的hasLayout属性(如zoom:1)。代码如下:

//css
.ib { display:inline-block; *display: inline; *zoom:1; width: 60px; height: 60px; background: red;}
//html
<div class="ib">我是ie6/7下模拟的inline-block元素</div>
延伸上一个问题,实现两栏自适应布局的一个方案

只需要给左侧元素的布局浮动属性,并设置宽度,右侧的元素display:inline-block,ie6/7下使用兼容解决方案即可解决。当然两栏自适应布局的方法不止这一种,这里仅仅是做一个小小的延伸扩展。

css组件的命名规范

class命名一直是网页重构的一个重要的话题,而css组件的命名就更是重中之重。如何防止命名冲突,全站组件和单页面组件的命名怎么从普通class命名中间区分开来,以及全站的组件和单页面的组件之间又如何准确区分?

我这里给出的答案是在class的命名规范上下文章,全站组件的命名加上mod前缀以标示,例如:mod_xx。而单页面组件的命名加上单页的前缀和mod标示,如:xx_mod_xx。

css框架

Bootstrap的流行导致了越来越多的人去研究前端css框架,而在面试的时候面试官更多的是考察你对框架源码的剖析,以及知识的广度。比如说它的栅格布局,响应式布局以及各个css组件之间的联系。还有个css框架是YUI的pureCss,它可能没有bootstrap那么有名气,但恰恰是在面试的时候能够说出这个框架的名字以及你对于它的理解,相信是可以加分不少的。pure是一组轻量的响应式css模块,pure的所有模块都是在Normalize.css之上建立的。和传统的reset不同,它提供的是跨浏览器保持HTML元素默认样式的一致性。有兴趣的同学可以深入研究学习一下。

javascript问题:

事件绑定

js事件绑定,主要有三个问题:

1 事件绑定在标准浏览器和IE浏览器下的兼容性写法
2 事件绑定在标准浏览器下函数的第三个参数的含义

3 事件绑定在ie下,回调函数的this指向会被指向window

先说一下第二个问题,其它的问题可用代码示例。

obj.addEventListener(ev,fn,false);

这个参数的名字叫做useCapture,是一个布尔值,名为冒泡获取,false代表的含义是由里向外,true是由外向里。举个栗子:

<div id="outDiv">
  <div id="middleDiv">
    <div id="inDiv">请在此点击鼠标。</div>
  </div>
</div>
  • 全为 false 时,触发顺序为:inDiv、middleDiv、outDiv;
  • 全为 true 时,触发顺序为:outDiv、middleDiv、inDiv;
  • outDiv 为 true,其他为 false 时,触发顺序为:outDiv、inDiv、middleDiv;
  • middleDiv 为 true,其他为 false 时,触发顺序为:middleDiv、inDiv、outDiv;

使用匿名函数解决attachEvent回调函数的this指向问题:

function bindEvent(elem,type,fn){
    if(elem.attachEvent){
        elem.attachEvent("on"+type,function(){
            fn.apply(elem,arguments);
        });
    }else{
        elem.addEventListener(type,fn,false);
    }
}

js阻止默认事件和阻止冒泡的兼容写法

1.停止事件冒泡

//如果提供了事件对象,则这是一个非IE浏览器
if ( e && e.stopPropagation )
//因此它支持W3C的stopPropagation()方法
e.stopPropagation(); 
else
//否则,我们需要使用IE的方式来取消事件冒泡 
window.event.cancelBubble = true;
return false;

2.阻止浏览器的默认行为

//如果提供了事件对象,则这是一个非IE浏览器 
if ( e && e.preventDefault ) 
//阻止默认浏览器动作(W3C) 
e.preventDefault(); 
else
//IE中阻止函数器默认动作的方式 
window.event.returnValue = false; 
return false;

当然jquery中帮我集成了一个解决方案,那就是return false,已解决了兼容性问题。

js获取元素的位置

这道面试题考察的是,有没有获取父元素的位置,再加上自身的offset值,代码如下:

function getIE(e){
    var t=e.offsetTop;
    var l=e.offsetLeft;
    while(e=e.offsetParent){
        t+=e.offsetTop;
        l+=e.offsetLeft;
    }
}

hr问题:

你对加班的问题怎么看?

这个问题几乎是hr必问,但不要以为hr仅仅想听到你的答案是愿意加班哦。看看我是怎么回答的吧:

我不喜欢经常加班,因为我认为经常加班是工作低效率的一种表现,但是如果公司有紧急的需求需要我去加班,我愿意奉献自己的精力去完成,不会以不加班为借口,拖延紧急项目的进度。

关于面试的问题还有很多很多,比如说前端的性能优化,seaJs的模块化加载,前端MVC模式的应用,nodeJs等等,感兴趣的同学可以深入研究下。

前端代码架构

建造一座高楼不能从最高层开始建,要从打地基开始一层一层的往上建。代码架构就像是地基一样,它的稳定度决定了代码开发的质量,是不是方便维护,程序是否健壮,出bug的几率是不是小这些都与最初的代码架构息息相关。总之一句话,架构很重要。

前言

本文主要探讨的是js代码架构,关于html和css的设计可以参考我前面讲的两篇文章。

开光---js总架构

代码架构思维脑图

如图是js的代码架构图,core层为底层库,widgets层为组件层,views是业务层。

这里未标注的地方是整体结构采用seajs+grunt的方式进行模块化打包。seajs的学习可以直接去官方网站,grunt则推荐小钗同学写的这篇博文grunt打包详解

core层

zepto

Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。 如果你会用jquery,那么你也会用zepto。

整个网站采用zepto框架作为底层库,方便对底层js的操控。zepto解决了一些移动端的bug,但是也制造了移动端的一些bug,比如说臭名昭著的Zepto点透bug。

fastclick

干嘛用的?

百度搜一下,差不多就知道这货是解决移动端点透bug了哈

怎么用:

fastclick.attach(document.body);

func

func.js是在zepto的基础上抽象的一层与业务无关的工具库,实现了一些非常实用的功能,在之后的组件层以及业务层将会被高度复用。比如新建实例的Class方法,自定义事件模块,cookie相关,判断mobile浏览器方法等等。

itpl

什么东西?

itpl是我自己编写的一套模板引擎。

为什么要使用模板引擎?

for(; i<len ; i++) {
    pushHtml += '<a href="'+ d.info[i]['url'] +'" class="item">' ;
    pushHtml += '<p class="img_wrap">' ;
    pushHtml += '<span class="notes_wrap">' ;
    pushHtml += '<span class="img_time">'+ d.info[i]['durationSecond'] +'</span>' ;
    pushHtml += '<img class="lazy" src="http://static.vas.pptv.com/vas/assets/app/1717wan/wap/'+ vers +'/css/gb/lazypic.jpg" lazyimg="'+ d.info[i]['sloturl'] +'" />' ;
    pushHtml += '</span></p>' ;
    pushHtml += '<h5>'+ d.info[i]['title'] +'</h5>' ;
    pushHtml += '<p class="info">' ;
    pushHtml += '<span class="tit">'+ d.info[i]['author'] +'</span>' ;
    pushHtml += '<span class="views">'+ d.info[i]['pv'] +'</span>' ;
    pushHtml += '</p></a>'
}
$video.append(pushHtml);

向上面这样多的js代码出现多了,可能你就会感到恶心、痛苦,想迫切的把数据和html字符串给分离开来,这时候使用模板引擎是最好的办法。

怎么用?

itpl的项目地址上有使用方法。

widgets层(组件层)

common.js

在写业务层的时候发现,有一些js是很多业务层的代码所公用的,比如说登录相关,浏览器判断相关的等等功能。功能之间肯能彼此是没有任何关系的,所以抽离成一个组件,这不合理,但若是分散开来使用,这不方便。为此,我创建了common.js这个文件夹,在广义上的概念上看他不是任何一个功能抽离的组件,但是他聚合了很多需要跨页面使用的方法,为业务层提供方便调用的API。

widgets

组件js,没什么好说的,就是抽离的一些组件,比如说wipe,dialog,lazyload等等。但是在编写组件层的时候,对这里的组件进行了一些规范,由其是在业务层调用组件代码时,都是直接运行方法名就好了,方便业务层直观简单的调用组件。

组件js示例:
define(function(require, exports, module){
    var $ = require('../zepto/zepto');
    var func = require('../core/func');

    var defaults = {
        type : 'none',
        htype : 'hide'
    }

    var Widget = func.Class({
        // 初始化
        init : function(option) {
            var option = this.option = $.extend({},defaults,option);
            this.bindEvent();
        },
        bindEvent : function() {},
        handler : function() {},
        destory : function() {}
    })

    return Widget;

})

业务层代码调用:
define(function(require, exports, module){
    var widget = require('./widgets/widget');
    // 调用
    widget(option);
})

ajax_api.js

这个是把后台提供的接口聚合的js,特别方便对接口进行统一管理。

/**
 * @author : matthewsun
 * @mail : [email protected]
 * @description : API 聚合地址页 ( json 数据格式 )
 */
define(function(require, exports, module){
    var APIDOMAIN = apiUrl ;
    var API = { 
        loginPPTV : 'http://user.vas.pptv.com/api/ajax/loginCms.php?app=1717wan', // 登录检测接口 对 PPTV主站     
        indexVideoData : 'http://m.1717wan.cn/?ajax=1&page=', // 首页精彩视频数据      
        scheduleListData : 'http://m.1717wan.cn/match/?ajax=1&page=', // 赛程页赛程列表数据       
        gamesListLiveData : 'http://m.1717wan.cn/game/list/?ajax_1=1&', // 游戏列表页直播视频数据      
        gamesListVideoData : 'http://m.1717wan.cn/game/list/?ajax=1&', // 游戏列表页精彩视频数据      
        recordsListData : 'http://m.1717wan.cn/mylottery/order?ajax=1&p=', // 赛程页赛程列表数据      
        quizzeFuncData : APIDOMAIN + '/lottery/bet?&platform=2&', // 竞猜投注页     
        reserveData : APIDOMAIN + '/user/follow?uid=', // 订阅页 => 订阅      
        unReserveData : APIDOMAIN + '/user/unfollow?uid=', // 订阅页 => 取消订阅      
        exchangeMoneyData : APIDOMAIN + '/user/exchange?amount=', // 猜币兑换接口
        playerHomeList : 'http://m.1717wan.cn/player/detail/?ajax=1&room_id=', // 主播主页列表数据
        starHomeList : 'http://m.1717wan.cn/player/detail_star/?ajax=1&author=', // 伪主播列表数据
        WATCHPLAY : "http://apicdn.liveplatform.pptv.com/media/v3/1717wan/program/{pid}/watch" // 普通用户获取播放地址
    }

    return API ;

});

require_map.js

这个记录的是seajs的依赖关系,方便组件代码在重构时,api改变,可以不影响业务层。

var map = {
    libs : {
        'zepto' : [],
        'swipe' : []
    },
    utils : {
        'func' : ['zepto'],
        'ajax_api' : []
    },
    widget : {
        'common' : ['zepto','func','ajax_api'],
        'lazyload' : ['zepto'],
        'loader' : ['zepto'],
        'dialog' : ['zepto'],
        'share' : ['zepto'],
        'itpl' : []
    },
    ui : {
         'index' : ['zepto','common','swipe','ajax_api','loader','lazyload'],
         'details' : ['zepto','common'],
         'exchange' : ['zepto','common','ajax_api'],
         'forget' : ['zepto'],
         'games' : ['zepto','common'],
         'games_list' : ['zepto','common','ajax_api','loader','lazyload'],
         'home' : ['zepto','common','ajax_api','loader','lazyload'],
         'instruct' : ['zepto','common'],
         'live' : ['zepto','common','ajax_api','loader','lazyload'],
         'login' : ['zepto'],
         'personal' : ['zepto','common'],
         'players' : ['zepto','common'],
         'quizze' : ['zepto','common','ajax_api'],
         'records' : ['zepto','common','ajax_api','loader'],
         'reg' : ['zepto'],
         'reserve' : ['zepto','common','ajax_api'],
         'schedule' : ['zepto','common','ajax_api','loader'],
         'star_home' : ['zepto','common','ajax_api','loader','lazyload'],
         'strategy' : ['zepto','common'],
         'vlive' : ['zepto','lazyload'],
         'weixin_share' : ['zepto','share'],
         '404' : ['zepto','common']
    }
}

mock.js

Mock.js 是一款模拟数据生成器,旨在帮助前端攻城师独立于后端进行开发,帮助编写单元测试。提供了以下模拟功能:

  • 根据数据模板生成模拟数据
  • 模拟 Ajax 请求,生成并返回模拟数据
  • 基于 HTML 模板生成模拟数据

这款高云写的数据生成器工具在做测试的时候也是非常好用,推荐在项目中使用。

views

业务

业务层是一个非常麻烦的地方,推荐几种形式的写法,供参考:

单业务(不复杂):
var view = (function(){
    return {
        login : function() {
            ...
        },
        dialog : function() {
            ...
        },
        tab : function() {
            ...
        },
        play : function() {
            ...
        },
        ...
    }
})();

多业务(较复杂):
define(function(require, exports, module){
    var $ = require("jquery");
    var login = require("./login");
    var dialog = require("./dialog");
    var tab = require("./tab");
    var play = require("./play");

    $(function(){
        login();
        dialog();
        tab();
        play();
    })
})

多业务(复杂):
在业务粒子和业务模块之间架一层区域模块
区域模块:
define(function(require, exports, module){
    var $ = require("jquery");
    var login = require("./login");
    var dialog = require("./dialog");

    function loginArea() {
        login();
        dialog();
    }
    return loginArea;
})
业务模块:   
define(function(require, exports, module){
    var $ = require("jquery");
    var loginArea = require("./loginArea");
    var tab = require("./tab");
    var play = require("./play");

    $(function(){
        loginArea();
        tab();
        play();
    })
})

组合行为操作

class名应当只与样式有关,不应该和行为挂钩。本篇介绍了,利用html5 data-* 自定义属性的api来为一组元素绑定其所共通的行为,告别了以往依赖于class的行为操作方式。

过去是如何绑定一组元素的共通行为?

// html output:
<a href="javascript:;" class="J_action">btn call alert</a>
<a href="javascript:;" class="J_action">btn call alert</a>
<a href="javascript:;" class="J_action">btn call alert</a>
<a href="javascript:;" class="J_action">btn call alert</a>

// js output:
// require jquery
$('body').on('click', '.J_action', function() {
    alert('a')
})

如上面的代码所示,我为class名字是J_action的元素添加了一个alert的方法。

为什么使用data-*绑定元素共通行为?

在代码中,J_action的class并不控制任何css样式,只是共通的去使用了一组行为。
而在我的认知中,class名应该只与样式有关,和行为无关。以前一直觉得过去的用法有问题,但是说不上来问题在哪。
最近也是受到了高人指点,假如我把代码写成了下面这个样子,不就彻底把class名和行为分隔开了么?

// html output:
<a href="javascript:;" data-action="alert">btn call alert</a>
<a href="javascript:;" data-action="alert">btn call alert</a>
<a href="javascript:;" data-action="alert">btn call alert</a>
<a href="javascript:;" data-action="alert">btn call alert</a>

// js output:
// require jquery
$('body').on('click', '[data-action=alert]', function() {
    alert('a')
})

受到了什么开源项目的启发?

CMUI-ACTion
这个库里面有一些比较好的**,因为是别人的成果,我就不做过多的介绍了。

如果有任何问题都可以在下方给予我留言~

Kontext项目的技术分析

Kontext是国外大牛hakimel写的页面转场的动画效果,灵感来源于ios。我的卡路里消耗计算器项目中用到了这个效果,想在这篇博文中介绍Kontext实现原理,希望能帮助到大家对css3与Js相结合的动画有一个更好的理解。

Kontext是什么?

Kontext是国外大牛hakimel写的页面转场的动画效果,灵感来源于ios。

Kontext的项目展示demo

Kontext怎么用?

html代码:

<section class="kontext">
    <div class="layer show">...</div>
    <div class="layer">...</div>
    <div class="layer">...</div>
</section>

JS代码:

// 创建一个kontext实例
var k = kontext( document.querySelector( '.kontext' ) );

// API METHODS:
k.prev(); // 展示上一个场景
k.next(); // 展示下一个场景
k.show( 3 ); // 展示指定索引值的场景
k.getIndex(); // 现在展示的场景的索引值
k.getTotal(); // 场景的数量

Kontext的炫酷效果是怎么实现的?

css代码分析

// kontext对象全屏包裹
.kontext {
    width: 100%;
    height: 100%;
}

// 每一个场景绝对定位,并默认看不见
.kontext .layer {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    visibility: hidden;
}

// 设置show的场景显示出来  
.kontext .layer.show {
    visibility: visible;
}

// 如果当前浏览器支持css3的景深效果,则加上capable,这个会在js中操作实现
// 在ie6等等不支持css3景深效果的低级浏览器中,则表现为正常的场景切换,没有动画效果
.kontext.capable {
    -webkit-perspective: 1000px;
       -moz-perspective: 1000px;
            perspective: 1000px;

    -webkit-transform-style: preserve-3d;
       -moz-transform-style: preserve-3d;
            transform-style: preserve-3d;
}

// 场景延Z轴向里缩100px
.kontext.capable .layer {
    -webkit-transform: translateZ( -100px );
       -moz-transform: translateZ( -100px );
            transform: translateZ( -100px );
}

// 对于显示的场景保持原位置
.kontext.capable .layer.show {
    -webkit-transform: translateZ( 0px );
       -moz-transform: translateZ( 0px );
            transform: translateZ( 0px );
}

// 向右翻页时,需要显示的页面,用js加上show和right的class命,给予它名为show-right的动画
.kontext.capable.animate .layer.show.right {
    -webkit-animation: show-right 1s forwards ease;
       -moz-animation: show-right 1s forwards ease;
            animation: show-right 1s forwards ease;
}

// 向右翻页时,需要隐藏的页面,用js加上hide和right的class命,给予它名为hide-right的动画
.kontext.capable.animate .layer.hide.right {
    -webkit-animation: hide-right 1s forwards ease;
       -moz-animation: hide-right 1s forwards ease;
            animation: hide-right 1s forwards ease;
}

/* 这里即以下把left的代码给省略了,原理和right一样
/* CSS animation keyframes */

// show页面
// 0%时:延Z轴向里偏移200px
// 40%时:向右偏移40%,缩小至0.8,向y轴的负方向旋转20度
// 100%时:延Z轴方向恢复0px
@keyframes show-right {
    0%   { transform: translateZ( -200px ); }
    40%  { transform: translate( 40%, 0 ) scale( 0.8 ) rotateY( -20deg ); }
    100% { transform: translateZ( 0px ); }
}

// hide页面
// 0%时:延Z轴方向0px,显示效果为显示
// 40%时:向左偏移40%,缩小至0.8,向y轴的正方向旋转20度
// 100%时:延Z轴向里偏移200px,显示效果为隐藏
@keyframes hide-right {
    0%   { transform: translateZ( 0px ); visibility: visible; }
    40%  { transform: translate( -40%, 0 ) scale( 0.8 ) rotateY( 20deg ); }
    100% { transform: translateZ( -200px ); visibility: hidden; }
}

/* Dimmer */
.kontext .layer .dimmer {
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    visibility: hidden;
    background: transparent;
    z-index: 100;
}

.kontext.capable.animate .layer .dimmer {
    -webkit-transition: background 0.7s ease;
       -moz-transition: background 0.7s ease;
            transition: background 0.7s ease;
}

// 需要隐藏的场景,让遮罩显示出来,0.7s变化背景颜色
.kontext.capable.animate .layer.hide .dimmer {
    visibility: visible;
    background: rgba( 0, 0, 0, 0.8 );
}

js代码分析

// window上挂载kontext全局对象
window.kontext = function( container ) {

    // 这是一个观察者,也可称为自定义事件,用于观察场景是否发生了变化,实现方法在最下
    var changed = new kontext.Signal();

    //找到所有的场景
    var layers = Array.prototype.slice.call( container.querySelectorAll( '.layer' ) );

    // 判断浏览器是否支持3d景深
    var capable =   'WebkitPerspective' in document.body.style ||
                    'MozPerspective' in document.body.style ||
                    'msPerspective' in document.body.style ||
                    'OPerspective' in document.body.style ||
                    'perspective' in document.body.style;

    // 支持效果,则加上class名capable
    if( capable ) {
        container.classList.add( 'capable' );
    }

    // 给每一场景加上dimmer遮罩
    layers.forEach( function( el, i ) {
        if( !el.querySelector( '.dimmer' ) ) {
            var dimmer = document.createElement( 'div' );
            dimmer.className = 'dimmer';
            el.appendChild( dimmer );
        }
    } );

    // show(1,'left') || show(1,'right')
    function show( target, direction ) {

        layers = Array.prototype.slice.call( container.querySelectorAll( '.layer' ) );

        // 给kontext加上class名animate,用于准备动画
        container.classList.add( 'animate' );

        // 如果没有指定方向,则自行判断要运行的方向
        direction = direction || ( target > getIndex() ? 'right' : 'left' );

        if( typeof target === 'string' ) target = parseInt( target );
        if( typeof target !== 'number' ) target = getIndex( target );

        target = Math.max( Math.min( target, layers.length ), 0 );

        if( layers[ target ] && !layers[ target ].classList.contains( 'show' ) ) {

            layers.forEach( function( el, i ) {
                // 取消原先的动画
                el.classList.remove( 'left', 'right' );
                // 加上现在的动画方向
                el.classList.add( direction );
                if( el.classList.contains( 'show' ) ) {
                    // 给现在显示的场景加上class名hide
                    el.classList.remove( 'show' );
                    el.classList.add( 'hide' );
                }
                else {
                    // 除了有class名show之外的场景去掉class名hide
                    el.classList.remove( 'hide' );
                }
            } );

            // 给目标场景加上class名show
            layers[ target ].classList.add( 'show' );

            // 场景转换时派发事件
            changed.dispatch( layers[target], target );

        }

    }

    /**
     * 显示上一个场景
     */
    function prev() {

        var index = getIndex() - 1;
        show( index >= 0 ? index : layers.length + index, 'left' );

    }

    /**
     * 显示下一个场景
     */
    function next() {

        show( ( getIndex() + 1 ) % layers.length, 'right' );

    }

    /**
     * 返回现在显示场景的索引值
     */
    function getIndex( of ) {

        var index = 0;

        layers.forEach( function( layer, i ) {
            if( ( of && of == layer ) || ( !of && layer.classList.contains( 'show' ) ) ) {
                index = i;
                return;
            }
        } );

        return index;

    }

    /**
     * 返回场景的数量
     */
    function getTotal() {

        return layers.length;

    }

    // API
    return {

        show: show,
        prev: prev,
        next: next,

        getIndex: getIndex,
        getTotal: getTotal,

        changed: changed

    };

};

/**
 * 观察者
 */
kontext.Signal = function() {
    this.listeners = [];
}

kontext.Signal.prototype.add = function( callback ) {
    this.listeners.push( callback );
}

kontext.Signal.prototype.remove = function( callback ) {
    var i = this.listeners.indexOf( callback );

    if( i >= 0 ) this.listeners.splice( i, 1 );
}

kontext.Signal.prototype.dispatch = function() {
    var args = Array.prototype.slice.call( arguments );
    this.listeners.forEach( function( f, i ) {
        f.apply( null, args );
    } );
}

好简单!

怎么样,通过这个分析可以清楚的看到这个效果实现起来并不难,小伙伴们快来把css3和js联合起来使用吧。

那么,问题来了?

前端动画哪家强?

js事件委托

对“事件处理程序过多”问题的解决访问就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

最近在做的一个专题就应用到了这个效果:


example


如上图所示,这里需要对多个li里的a标签,添加点击事件。如果对每一个a标签都添加事件侦听,无疑会对页面的性能造成很大的影响。这个时候咱们就回到文章的标题,可以利用事件委托来解决这个问题。


具体实现方案:给父级元素添加绑定事件,当事件发生时,判断事件的活动源是否是需要绑定的元素,如果是则执行相关方法。

 $parent.on('click',function(event){ //给父元素添加click绑定事件
    var $this = $(event.target) ; // 活动响应源

    if($this.is($myTarget)) { // 事件发生,判断是否是确切要执行函数的绑定
        foo();// 需执行的事件函数       
    }
}

具体可查看 Demo ,或去我的github上扒取源码

希望这篇文章能对你有用,和我一起交流~

网页重构的css规范

css代码规范:

模块和组件

我们将具有一定公用性的DOM结构抽取成模块。包含全局模块(全站公用,比如翻页、按钮等),局部模块(部分页面用到)。

常规结构
  1. 最外层容器
  2. 内部容器,方便设置统一的padding值,也方便后期扩展样式。
  3. 模块头部
  4. 模块身体(可以出现多次)
  5. 模块尾部(可以没有)

    示例:
    <div class="mod_x">
        <div class="mod_x_inner">
            <div class="mod_x_hd"></div>
            <div class="mod_x_bd"></div>
            <div class="mod_x_ft"></div>
        </div>
    </div>

如果需要对该模块扩展个性化样式,可在该模块最外层新增个性化的className,针对新的className来扩展表现。

比如:<div class="mod_list hotsale_list">

命名规则

为什么命名很重要?
  1. 快速高效开发(命名费时);
  2. 安全(解耦无依赖);
  3. 方便维护(一目了然的命名,无耦合的模块化规范);
  4. 方便复用(高度抽象、细粒度、可拼装的组件)
模块命名
  • 公共模块可用mod作为前缀。比如:

    .mod_btn
    
  • 局部模块可用{页面名缩写}_mod为前缀。比如:

    .sy_mod_btn
    

PS: 对模块/组件重置样式时,需要自己加入新的样式名来进行差异化表现。

普通命名
  1. 大部分情况下使用长命名(继承父级的className),安全起见,外层布局模块必须使用长命名。比如:
    .hotlist > .hotlist_hd > .hotlist_hd_extra
  1. 也可以考虑使用祖先命名法,即子模块只集成祖先模块的前缀。比如:
    .hotlist > .hotlist_hd > .hotlist_extra
  1. 内部元素根据情况(比如可判定该结构中不会再嵌套其他元素时)可使用短单词命名。

    比如:tit、more。
常用命名词汇

word standard

status standard

编码规范

书写规范
  1. 选择符(selector)换行书写;属性(properties)和属性的取值(value)横排书写,不换行。
  2. 背景图URL引用时 属性值统一不加引号。
  3. margin,padding,border等属性可以简写的尽量简写,后续修改维护时,只需要改动单边属性值即可。
  4. CSS HACK使用标准形式放置于紧跟相同属性后面,并加以注释。
书写顺序
  1. 组合实现的功能放一起 如:截断width:100%; overflow:hidden;white-space:nowrap;text-overflow:ellipsis
  2. 长内容的放最后 如:图片:background:-ms-linear-gradient(top,#2372cf 0,#3064af 100%);
  3. 多个选择符:如果是选择符组,则这些选择符(selector)各占一行
  4. 推荐书写顺序:布局类 > 盒模型 > 表现类 (可选,不强制要求)

    布局:display,float,position,top,right,bottom,left,z-index,clear,visibility,table-layout

    盒模型:width,max-width,min-width,height,max-height,min-height,line-height,overflow,margin,padding,border

    表现:color,font家族,text家族,vertical-align,letter-spacing,white-space,word-spacing,content,list-style,filter,background

注释规范

  1. 普通注释:注释文字前后各加一个空格
    /* 注释 */
    /* 所在地注释 */
    .location_area { color:#f30; }

注意注释文字前后的空格很重要,没有空格可能会引起IE6下不识别的bug。

  1. 如果一系列页面共用一个样式,则需要在样式中用注释分割清楚。
    注释的形式:/* ==页面-{页面名称}== */
    /* ==页面-退货流程== */
    .tuihuo .hd {font:700 12px/21px Arial;}
    .tuihuo .bd {font:700 12px/21px Arial;}

统一Hack方法

如下代码所示:

统一使用“*”和“_”分别对IE7和6进行Hack。
/* IE7会显示灰色#888,IE6会显示白色#fff,其他浏览器显示黑色#000 */
.m-list{color:#000;*color:#888;_color:#fff;}

ie6/7定位元素导致相邻元素的margin-top失效

问题描述:

对于一个触发了haslayout的块级元素,且它的相邻元素是具有定位属性的,那么这个元素在IE6/7下的margin-top会失效。

html代码:
<div class="pos">定位元素</div>
<div class="bug">margin-top失效元素</div>
css代码:
.pos { position: fixed; left: 0; top: 0; width: 100%; background: blue; height: 40px; line-height: 40px; }
.bug { margin-top: 40px; background: red; height: 40px; line-height: 40px;  }

标准浏览器下输出的结果(正常):


normal

IE6/7下输出的产物(bug元素被遮住):


bug

具体可查看 Demo

WTF!

分析原因:

一个块级元素,触发了hasLayout(比如设置了宽度高度),并且其前面紧挨着的同级的节点如果为absolute绝对定位或者是固定定位,就会导致这个块级元素在IE6/IE7下面的margin-top失效,看起来就像margin-top:0一样。

关键词:自身触发haslayout,同级相邻节点定位

解决方案:

1.不使用margin属性:使用padding来代替margin,比如设置其父元素的padding-top,或者设置这个块元素的padding-top,不过要注意padding对其背景的影响。

.bug { padding-top: 40px; background: red; height: 40px; line-height: 40px;  }

2.避免两个元素相邻:在它们之间插入一个空div标签,或者交换这两个标签的前后位置。

<div class="pos">定位元素</div>
<div></div>
<div class="bug">margin-top失效元素</div>
或者(交换位置):
<div class="bug">margin-top失效元素</div>
<div class="pos">定位元素</div>

3.去掉失效元素的haslayout属性(特殊场景可使用,一般不推荐)

.bug { margin-top: 40px; background: red; line-height: 40px; }
希望这篇文章能对你有用,和我一起交流~

为什么使用mouseenter而不是使用mouseover

mouseover会触发冒泡事件,即不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件。不想让这种情况发生,怎么解决?
使用mouseenter/mouseleave事件。

对应mouseout,只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件。

对应mouseleave,mouseenter子元素不会反复触发事件,否则在IE中经常有闪烁情况发生。

下面不如来看一个例子来说明这种情况:Demo

可是mouseenter和mouseleave这么好的东西,只有在IE下是有onmouseenter的一系列事件支持的,在非IE浏览器如Chrome、FF下就没有这类事件。如果使用的是JQuery开发,则没有问题,因为mouseenter已经被封装成了一个JQuery事件,支持所有浏览器。

但是在使用原生JS时,应该如何模拟这个方法?


以下提供一个模拟的方案:

//ele为目标元素,type为事件类型不用'on',func为事件响应函数
var addEvent=function(ele,type,func){
    if(window.document.all) 
        ele.attachEvent('on'+type,func);//ie系列直接添加执行
    else{//ff
        if(type==='mouseenter')
            ele.addEventListener('mouseover',this.withoutChildFunction(func),false);
        else if(type==='mouseleave')
            ele.addEventListener('mouseout',this.withoutChildFunction(func),false);
        else
            ele.addEventListener(type,func,false);      
    }
}
var withoutChildFunction=function(func){
    return function(e){
        var parent=e.relatedTarget;//上一响应mouseover/mouseout事件的元素
        while(parent!=this&&parent){//假如存在这个元素并且这个元素不等于目标元素(被赋予mouseenter事件的元素)
            try{
                parent=parent.parentNode;}//上一响应的元素开始往上寻找目标元素
            catch(e){
                break;
            }
        }
        if(parent!=this)//以mouseenter为例,假如找不到,表明当前事件触发点不在目标元素内
        func(e);//运行目标方法,否则不运行
    }
}

网页重构的html规范

html代码规范:

html基础设施

  • 如我们所知不同的Doctype声明,将会触发浏览器不同的渲染模式,主要分为遵循W3C规范的标准模式和怪异模式。而因为html5的流行,我们不需要去管因为遗留下原因的不同dtd,主需要统一的在文件的顶格开始声明""。

  • 必须申明文档的编码charset,且与文件本身编码保持一致,推荐使用UTF-8编码。

  • 根据页面内容和需求填写适当的keywords和description。

  • 页面title是极为重要的不可缺少的一项。

  • 对于兼容性的处理可以考虑使用IE注释法加上class锚点

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8"/>
    <title>页面标题</title>
    <meta name="keywords" content=""/>
    <meta name="description" content=""/>
    <meta name="viewport" content="width=device-width"/>
    <link rel="stylesheet" href="css/style.css"/>
    <link rel="shortcut icon" href="img/favicon.ico"/>
    <link rel="apple-touch-icon" href="img/touchicon.png"/>
    </head>
    <body>
    
    </body>
    </html>
    

结构、表现、行为三者分离,避免内联

  • 使用link将css文件引入,并置于head中。

  • 使用script将js文件引入,并置于body底部。

    保持良好的简洁的树形结构

    1. 每一个块级元素都另起一行,每一行都使用Tab缩进对齐(head和body的子元素不需要缩进)。删除冗余的行尾的空格。
    2. 使用4个空格代替1个Tab(大多数编辑器中可设置)。
    3. 你也可以在大的模块之间用空行隔开,使模块更清晰。
    4. 模块首尾使用注释标示
    <body>
    <!--S 侧栏内容区 -->
    <div class="m-side">
        <div class="side">
            <div class="sidein">
                <!-- 热门标签 -->
                <div class="sideblk">
                    <div class="m-hd3"><h3 class="tit">热门标签</h3> </div>
                    ...
                </div>
    
                <!-- 最热TOP5 -->
                <div class="sideblk">
                    <div class="m-hd3"><h3 class="tit">最热TOP5</h3> <a href="#" class="s-fc02 f-fr">更多»</a></div>
                    ...
                </div>
            </div>
        </div>
    </div>
    <!--E 侧栏内容区 -->
    </body>
    

另外,请做到以下几点

  • 结构上如果可以并列书写,就不要嵌套。

    如果可以写成<div></div><div></div>那么就不要写成<div><div></div></div>
  • 如果结构已经可以满足视觉和语义的要求,那么就不要有额外的冗余的结构。

    比如<div><h2></h2></div>已经能满足要求,那么就不要再写成<div><div><h2></h2></div></div>
  • 一个标签上引用的className不要过多,越少越好。

    比如不要出现这种情况:<div class="class1 class2 class3 class4"></div>
  • 对于一个语义化的内部标签,应尽量避免使用className。

    比如在这样一个列表中,li标签中的itm应去除:<ul class="m-help"><li class="itm"></li><li class="itm"></li></ul>

严格的嵌套

  • 尽可能以最严格的xhtml strict标准来嵌套,比如内联元素不能包含块级元素等等。
  • 正确闭合标签且必须闭合。

严格的属性

  1. 属性和值全部小写,每个属性都必须有一个值,每个值必须加双引号。
  2. 没有值的属性必须使用自己的名称做为值(checked、disabled、readonly、selected等等)。
  3. 可以省略style标签和script标签的type属性。

内容类型决定使用的语义标签

  • 在网页中某种类型的内容必定需要某种特定的HTML标签来承载,也就是我们常常提到的根据你的内容语义化HTML结构。

加强“资源型”内容的可访问性和可用性

  • 在资源型的内容上加入描述文案,比如给img添加alt属性,在audio内加入文案和链接等等。

加强“不可见”内容的可访问性

  • 背景图上的文字应该同时写在html中,并使用css使其不可见,有利于搜索引擎抓取你的内容,也可以在css失效的情况下看到内容。

适当使用实体

  • 以实体代替与HTML语法相同的字符,避免浏览解析错误。
    常用HTML字符实体(建议使用实体):
    字符名称实体名实体数
    "双引号&quot;&#34;
    &&符&amp;&#38;
    <左尖括号(小于号)&lt;&#60;
    >右尖括号(大于号)&gt;&#62;
     空格&nbsp;&#160;
    常用特殊字符实体(不建议使用实体):
    字符名称实体名实体数
    ¥&yen;&#165;
    ¦断竖线&brvbar;&#166;
    ©版权&copy;&#169;
    ®注册商标R&reg;&#174;
    商标TM&trade;&#8482;
    ·间隔符&middot;&#183;
    «左双尖括号&laquo;&#171;
    »右双尖括号&raquo;&#187;
    °&deg;&#176;
    ×&times;&#215;
    ÷&divide;&#247;
    千分比&permil;&#8240;

使用Redux管理你的React应用

React是最好的前端库,因为其发源于世界上最好的后端语言框架。 ---信仰

4.0 will likely be the last major release. Use Redux instead. It's really great. —Flummox框架作者 acdliteAndrew Clark

为什么使用React还需要使用别的框架来搭配?

React的核心是使用组件定义界面的表现,是一个View层的前端库,那么在使用React的时候我们通常还需要一套机制去管理组件与组件之间,组件与数据模型之间的通信。

为什么使用Redux?

Facebook官方提出了FLUX**管理数据流,同时也给出了自己的实现来管理React应用。可是当我打开FLUX的文档时候,繁琐的实现,又臭又长的文档,实在难以让我有使用它的欲望。幸好,社区中和我有类似想法的不在少数,github上也涌现了一批关于实现FLUX的框架,比较出名的有Redux,Reflux,Flummox

其中Redux的简单和有趣的编程体验是最吸引我的地方。

  • 简单。和其它的FLUX实现不一样,Redux只有唯一的state树,不管项目变的有多复杂,我也仅仅只需要管理一个State树。可能你会有疑问,一个state树就够用了?这个state树该有多大?别着急,Redux中的Reducer机制可以解决这个问题。
  • 有趣。忙于迭代项目的你,体会编程带来的趣味是有多久没有体会到了?瞧下面这张图,右边那个调试工具是啥?整个应用的action和state都这么被轻松的管理了?行为还能被保存,删除,回滚,重置?修改了代码,页面不刷新也能产生变化?别开玩笑了,不行,世界那么大,让我去试试!

Redux DevTools

注:Redux开发调试工具:redux-devtools
React应用无刷新保存工具:react-transform

不明真相的群众,可能这里需要我来安利一下Flux数据流的**,看图:
  ╔═════════╗       ╔════════╗       ╔═════════════════╗
  ║ Actions ║──────>║ Stores ║──────>║ View Components ║
  ╚═════════╝       ╚════════╝       ╚═════════════════╝
       ^                                      │
       └──────────────────────────────────────┘

  注意:图片仅仅是FLUX**,而不是Facebook的实现。

大致的过程是这样的,View层不能直接对state进行操作,而需要依赖Actions派发指令来告知Store修改状态,Store接收Actions指令后发生相应的改变,View层同时跟着Store的变化而变化。

举个例子:A组件要使B组件发生变化。首先,A组件需要执行一个Action,告知绑定B组件的Store发生变化,Store接收到派发的指令后改变,那相应的B组件的视图也就发生了改变。假如C,D,E,F组件绑定了和B组件相同的Store,那么C,D,E,F也会跟着变化。

使用React和Redux开发一个小程序

为了更好的描述怎么样使用Redux管理React应用,我做了一个Manage Items的小例子。你可以在这里找到全部的源代码:https://github.com/matthew-sun/redux-example

Manage Items

快速查看

1.git clone [email protected]:matthew-sun/redux-example.git

2.npm install && npm start

3.open localhost:3000

目录结构

.
+-- app
|   +-- actions
|       +-- index.js
|   +-- components
|       +-- content.js
|       +-- footer.js
|       +-- searchBar.js
|   +-- constants
|       +-- ActionTypes.js
|   +-- containers
|       +-- App.js
|   +-- reducers
|       +-- index.js
|       +-- items.js
|       +-- filter.js
|   +-- utils
|   +-- configureStore.js
|   +-- index.js
+-- scss
|   +-- pure.scss
+-- index.html

Index.js

在入口文件中,我们需要把App和redux建立起联系。Provider是react-redux提供的组件,它的作用是把store和视图绑定在了一起,这里的Store就是那个唯一的State树。当Store发生改变的时候,整个App就可以作出对应的变化。这里的会传进Provider的props.children里。

/* app/index.js */

import React from 'react';
import { Provider } from 'react-redux';
import App from './containers/App';
import configureStore from './configureStore';

const store = configureStore();

React.render(
    <div>
        <Provider store={store}>
            <App />
        </Provider>
    </div>,
    document.getElementById('app'));

Constants

keyMirror这个方法非常的有用,它可以帮助我们轻松创建与键值key相等的常量。

/* app/constants/actionTypes.js */

import keyMirror from 'fbjs/lib/keyMirror';

export default keyMirror({
    ADD_ITEM: null,
    DELETE_ITEM: null,
    DELETE_ALL: null,
    FILTER_ITEM: null
});

// 等于
// export const ADD_ITEM = 'ADD_ITEM';
// export const DELETE_ITEM = 'DELETE_ITEM';
// export const DELETE_ALL = 'DELETE_ALL';
// export const FILTER_ITEM = 'FILTER_ITEM';

Actions

Action向store派发指令,action 函数会返回一个带有 type 属性的 Javascript Plain Object,store将会根据不同的action.type来执行相应的方法。addItem函数的异步操作我使用了一点小技巧,使用redux-thunk中间件去改变dispatch,dispatch是在View层中用bindActionCreators绑定的。使用这个改变的dispatch我们可以向store发送异步的指令。比如说,可以在action中放入向服务端的请求(ajax),也强烈推荐这样去做。

/* app/actions/index.js */

import { ADD_ITEM, DELETE_ITEM, DELETE_ALL, FILTER_ITEM } from '../constants/actionTypes';

export function addItem(item) {
    return dispatch => {
       setTimeout(() => dispatch({type: ADD_ITEM}), 1000)
    }
}
export function deleteItem(item, e) {
    return {
       type: DELETE_ITEM,
       item
    }
}
export function deleteAll() {
    return {
       type: DELETE_ALL
    }
}
export function filterItem(e) {
    let filterItem = e.target.value;
    return {
       type: FILTER_ITEM,
       filterItem
    }
}

Reducers

Redux有且只有一个State状态树,为了避免这个状态树变得越来越复杂,Redux通过 Reducers来负责管理整个应用的State树,而Reducers可以被分成一个个Reducer。

Reduce在javascript Array的方法中出现过,只是不太常用。简单快速的用代码样例来回顾一下:

  /* Array.prototype.reduce */

var arr = [1,2,3,4];
var initialValue = 5;
var result = arr.reduce(function(previousValue, currentValue) {
    return previousValue + currentValue
}, initialValue)
console.log(result)
// 15
// 该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。
// 整个函数执行的过程大致是这样 ((((5+1)+2)+3)+4)

回到Redux中来看,整个的状态就相当于从[初始状态]merge一个[action.state]从而得到一个新的状态,随着action的不断传入,不断的得到新的状态的过程。(previousState, action) => newState,注意:任何情况下都不要改变previousState,因为这样View层在比较State的改变时只需要简单比较即可,而避免了深度循环比较。Reducer的数据结构我们可以用immutable-js,这样我们在View层只需要react-immutable-render-mixin插件就可以轻松的跳过更新那些state没有发生改变的组件子树。

/* app/reducers/items.js */

import Immutable from 'immutable';
import { ADD_ITEM, DELETE_ITEM, DELETE_ALL } from '../constants/actionTypes';

const initialItems = Immutable.List([1,2,3]);

export default function items(state = initialItems, action) {
    switch(action.type) {
        case ADD_ITEM:
            return state.push( state.size !=0 ? state.get(-1)+1 : 1 );
        case DELETE_ITEM: 
            return state.delete( state.indexOf(action.item) );
        case DELETE_ALL:
            return state.clear();
        default:
            return state;
    }
}
连接reducers

Redux提供的combineReducers函数可以帮助我们把reducer组合在一起,这样我们就可以把Reducers拆分成一个个小的Reducer来管理Store了。

/* app/reducers/index.js */

import { combineReducers } from 'redux';
import items from './items';
import filter from './filter';

const rootReducer = combineReducers({
  items,
  filter
});

export default rootReducer;

Middleware

在Redux中,Middleware 主要是负责改变Store中的dispatch方法,从而能处理不同类型的 action 输入,得到最终的 Javascript Plain Object 形式的 action 对象。

redux-thunk为例子:

/* redux-thunk */  
export default function thunkMiddleware({ dispatch, getState }) {
  return next => 
     action => 
       typeof action === ‘function’ ? 
         action(dispatch, getState) : 
         next(action);
}

当ThunkMiddleware 判断action传入的是一个函数,就会为该thunk函数补齐dispatch和getState参数,否则,就调用next(action),给后续的Middleware(Middleware 插件可以被绑定多个)得到使用dispatch的机会。

 /* app/configureStore.js */

import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

var buildStore = compose(applyMiddleware(thunk))(createStore);
export default function configureStore(initialState) {
  const store = buildStore(rootReducer, initialState);
  return store;
}

UI

智能组件和木偶组件,因为本文主要是介绍Redux,对这个感兴趣的同学可以看一下这篇文章Smart and Dumb Components。本项目中在结构上会把智能组件放在containers中,木偶组件放于components中。

containers

智能组件,会通过react-redux函数提供的connect函数把state和actions转换为旗下木偶组件所需要的props。

/* app/containers/App.js */

import React from 'react';
import SearchBar from '../components/searchBar';
import Content from '../components/content';
import Footer from '../components/footer';
import { connect } from 'react-redux';
import ImmutableRenderMixin from 'react-immutable-render-mixin';
import * as ItemsActions from '../actions';
import { bindActionCreators } from 'redux';

let App = React.createClass({
     mixins: [ImmutableRenderMixin],
     propTypes: {
         items: React.PropTypes.object,
         filter: React.PropTypes.string
     },
     render() {
         let styles = {
             width: '200px',
             margin: '30px auto 0'
         }
         const actions = this.props.actions;
         return (
             <div style={styles}>
                 <h2>Manage Items</h2>
                 <SearchBar filterItem={actions.filterItem}/>
                 <Content items={this.props.items} filter={this.props.filter} deleteItem={actions.deleteItem}/>
                 <Footer addItem={actions.addItem} deleteAll={actions.deleteAll}/>
             </div>
         )
     }
 })

export default connect(state => ({
     items: state.items,
     filter: state.filter
}), dispatch => ({
     actions: bindActionCreators(ItemsActions, dispatch)
}))(App);
components

木偶组件,各司其职,没有什么关于actions和stores的依赖,拿出项目中也可独立使用,甚至可以和别的actions,stores进行绑定。

  • SearchBar:查找Item。
  • Content:控制Items的显示,删除一个Item。
  • Footer:新增Item,删除全部Item。

调试工具

使用redux-devtools调试,为你在开发过程中带来乐趣。

/* app/index.js */

function renderDevTools(store) {
  if (__DEBUG__) {
    let {DevTools, DebugPanel, LogMonitor} = require('redux-devtools/lib/react');
    return (
      <DebugPanel top right bottom>
        <DevTools store={store} monitor={LogMonitor} />
      </DebugPanel>
    );
  }else {
    return null;
  }
}

React.render(
    <div>
        <Provider store={store}>
            <App />
        </Provider>
        {renderDevTools(store)}
    </div>,
  document.getElementById('app'));
/* app/configureStore.js */

import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

var buildStore;

if(__DEBUG__) {
    buildStore = compose(
      applyMiddleware(thunk),
      require('redux-devtools').devTools(),
      require('redux-devtools').persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
    )(createStore)
}else {
    buildStore = compose(applyMiddleware(thunk))(createStore)
}

export default function configureStore(initialState) {
  const store = buildStore(rootReducer, initialState);

  if(module.hot) {
    module.hot.accept('./reducers', () => {
      store.replaceReducer(require('./reducers'));
    });
  }

  return store;
}

在你的代码中加上上面的两段代码,运行npm run debug命令,就可以用调试工具来管理你的项目了。

延伸阅读

写在最后

刚接触到Redux和React技术的时候,我几乎是夜夜难以入眠的,技术革新带来的新的**总是不断的刺激着我的大脑。非常建议你也能来试试Redux,体会我在开发中得到的这种幸福感。

如果有任何想要了解的,欢迎来我的github和我一起互动交流。​

ECMA5系列介绍---Date

ECMAScript 5规范在09年正式发布了,随着智能手机的普及和浏览器厂商的支持,无线前端开发者们也终于可以放心的在项目中实际使用了。本文是ECMA5系列介绍的一篇,主要讲解的是关于Date相关的API。

ECMA5规范对Date对象新增了两个方法,分别是Date.prototype.toISOString和Date.now

objDate.toISOString()

描述:以字符串值的形式返回采用 ISO 格式的日期。

示例

var dt = new Date("30 July 2010 15:05 UTC");
console.log(dt.toISOString());

// Output:
//  2010-07-30T15:05:00.000Z

Date.now()

描述:获取当前日期和时间。

返回值:1970 年 1 月 1日午夜与当前日期和时间之间的毫秒数。

示例:

console.log(Date.now())
// 1420015125590

过去我们通常是使用这样的方法来获取时间戳:

console.log(+new Date())
// 1420015125590
console.log( (Date.now() === +new Date()) )
// true

现在有了标准方法是不是变得很开心了呢

如果有任何问题都可以在下方给予我留言~

ECMA5系列介绍---Object

ECMAScript 5规范在09年正式发布了,随着智能手机的普及和浏览器厂商的支持,无线前端开发者们也终于可以放心的在项目中实际使用了。本文是ECMA5系列介绍的一篇,主要讲解的是关于Object相关的API。

Object.create(prototype[, descriptors])

描述:

创建一个具有指定原型且可选择性地包含指定属性的对象。

参数:

prototype: 必需。对象的原型链,可以为null

descriptors: 可选。包含一个或多个属性描述符的 JavaScript 对象。

属性描述一共有四个,分别是value,writable,enumerable和configurable,value是该属性的值,后面三个若未指定则默认为false,对应的中文解释分别为是否只读,是否可以被枚举(for in),是否可以被删除。

这里只需要知道即可,关于数据属性还有访问器属性将在下面的defineProperty中作详细解释。

示例:

创建一个普通对象模型

var o = Object.create({
    name: 'matthewsun',
    getName: function() {
        return this.name
    }
})

console.log(o.getName())
console.log(Object.getPrototypeOf(o)) // 获取原始对象的原型
console.log(Object.getOwnPropertyDescriptor(o)) // 获取对象的属性描述符

// output
// "matthewsun"
// [object Object]
// undefined

创建一个以null为对象原型,并添加一些属性描述。

var o = Object.create(null, {
  name: {
     value: 'matthewsun',
     writable: true
  }
})

console.log(o.name)
console.log(Object.getPrototypeOf(o)) 
console.log(Object.getOwnPropertyDescriptor(o, 'name'))

// output
// "matthewsun"
// null
// Object {value: "matthewsun", writable: true, enumerable: false, configurable: false} 

Object.defineProperty(object, propertyname, descriptor)

描述:

将属性添加到对象或修改现有属性的特性。

参数:

object: 必需。对其添加或修改属性的对象。这可以是本机 JavaScript 对象(即用户定义的对象或内置对象)或 DOM 对象。

propertyname:一个包含属性名称的字符串。

descriptor:必需。属性的描述符。它可以针对数据属性或访问器属性。

示例:

添加数据属性

var obj = {};

Object.defineProperty(obj, "newDataProperty", {
    value: 101,
    writable: true,
    enumerable: true,
    configurable: true
});

obj.newDataProperty = 102;
console.log( Object.getOwnPropertyDescriptor(obj, 'newDataProperty') )
console.log("Property value: " + obj.newDataProperty);

// output
// [object Object] {configurable: true,enumerable: true,value: 102,writable: true}
// "Property value: 102"

假如修改writable值为false,则输出的value为101

假如修改configurable值为false,则不能使用delete obj.newDataProperty

修改数据属性

Object.defineProperty(obj, "newDataProperty", { writable: false });

console.log( Object.getOwnPropertyDescriptor(obj, 'newDataProperty')['writable'] )

// output
// false

添加访问器属性

var obj = {};

Object.defineProperty(obj, "newAccessorProperty", {
    set: function (x) {
        document.write("in property set accessor" + newLine);
        this.newaccpropvalue = x;
    },
    get: function () {
        document.write("in property get accessor" + newLine);
        return this.newaccpropvalue;
    },
    enumerable: true,
    configurable: true
});

obj.newAccessorProperty = 30;
console.log("Property value: " + obj.newAccessorProperty);

// output
// Property value: 30

请注意这里并没有对newAccessorProperty属性设置value值和writable属性,全靠get/set对数据属性进行了访问,假如删去了get/set,将会返回undefined。

修改访问器属性

Object.defineProperty(obj, "newAccessorProperty", {
    get: function () {
        console.log('change.')
        return this.newaccpropvalue; 
    }
});

console.log("Property value: " + obj.newAccessorProperty);

// output
// change.
// Property value: 30

修改DOM元素上的属性

var descriptor = Object.getOwnPropertyDescriptor(Element.prototype, "querySelector");

descriptor.value = "query";
descriptor.writable = false;
Object.defineProperty(Element.prototype, "querySelector", descriptor);

var elem = document.getElementById("div");

elem.querySelector = "anotherQuery";
console.log(elem.querySelector);
// query

请注意此例子页面中必须包含id为div的元素。

Object.defineProperties(object, descriptors)

描述:

将一个或多个属性添加到对象,并/或修改现有属性的特性。

参数

object: 必需。对其添加或修改属性的对象。这可以是本机 JavaScript 对象(即用户定义的对象或内置对象)或 DOM 对象。

descriptors:必需。包含一个或多个描述符对象的 JavaScript 对象。 每个描述符对象描述一个数据属性或访问器属性。

示例

添加属性

var obj = {}

Object.defineProperties(obj, {
    newData: {
        value: 10,
        writable: true
    },
    newAccessor: {
        get: function() {
            console.log('get')
            return this.newAccessorValue
        },
        set: function(x) {
            console.log('set')
            this.newAccessorValue = x
        },
        enumerable: true
    }
})

obj.newData = 10
console.log( obj.newData )

// output
// set
// get
// 10

修改属性

Object.defineProperties(obj, {
    newData: {writable: false},
    newAccessor: {enumerable: false}
})

Object.getPrototypeOf(object)

描述:

返回对象的原型

参数:

object:必须。引用原型的对象。

示例:

function Person(name, age) {
  this.name = name
  this.age = age
}
var me = new Person('matthew', 22)
var proto = Object.getPrototypeOf(me)

proto.sex = 'male'
console.log(me.sex)
console.log(Person.prototype.sex)
console.log(proto === Person.prototype)
console.log(proto.isPrototypeOf(me))

// output
// male
// male
// true
// true

验证数据类型:

var arr = []
var result = (Object.getPrototypeOf(arr) === Array.prototype)
console.log(result)
// true

扩展,过去验证数据类型:

var arr = []
var result = (Object.prototype.toString.call(arr) === '[object Array]')
console.log(result)
// true

Object.keys(object)

描述:

返回对象的可枚举属性和方法的名称。

参数

object:必需。包含属性和方法的对象。这可以是您创建的对象或现有文档对象模型 (DOM) 对象。

示例

var o = {
    name: 'matthew',
    age: 22
}

console.log(Object.keys(o))
// ['name', 'age']

回顾一下知识,过去是如何获取键值?

function keys(obj) {
    var keys = [];
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) keys.push(key)
    }
    return keys;
}

Object.seal(object)

描述:

阻止修改现有属性的特性,并阻止添加新属性。

参数:

object:必需。在其上锁定特性的对象。

备注:

Object.seal 函数执行以下两项操作:

  • 使对象不可扩展,这样便无法向其添加新属性。
  • 为对象的所有属性将 configurable 特性设置为 false。

示例:

var obj = { pasta: "spaghetti", length: 10 };   
Object.defineProperty(obj, 't', {
  value: 't',
  writable: true
})
Object.seal(obj);
console.log(Object.isSealed(obj));

obj.newProp = 50;
console.log(obj.newProp);

delete obj.length;
console.log(obj.length);

obj.t = 'not t'
console.log(obj.t)

// Output:
// true
// undefined
// 10
// not t

Object.freeze(object)

描述:

阻止修改现有属性的特性和值,并阻止添加新属性。

参数:

object:必需。在其上锁定特性的对象。

备注

Object.freeze 函数执行下面的操作:

  • 使对象不可扩展,这样便无法向其添加新属性。
  • 为对象的所有属性将 configurable 特性设置为 false。在 configurable 为 false 时,无法更改属性的特性且无法删除属性。
  • 为对象的所有数据属性将 writable 特性设置为 false。当 writable 为 false 时,无法更改数据属性值。

示例:

var obj = { pasta: "spaghetti", length: 10 };

Object.defineProperty(obj, 't', {
  value: 't',
  writable: true
})
Object.freeze(obj);
// console.log(Object.isSealed(obj));

obj.newProp = 50;
console.log(obj.newProp);

delete obj.length;
console.log(obj.length);

obj.t = 'not t'
console.log(obj.t)

// Output:
// undefined
// 10
// t

Object.preventExtensions(object)

描述:

阻止向对象添加新属性。

参数:

object:必需。要成为不可扩展的对象的对象。

示例:

var obj = { pasta: "spaghetti", length: 10 };

Object.preventExtensions(obj);
console.log(Object.isExtensible(obj));

obj.newProp = 50;
document.write(obj.newProp);

// Output:
// false
// undefined

Object.isSealed(object)

描述:

如果无法在对象中修改现有属性的特性,且无法向对象添加新属性,则返回 true。

Object.isFrozen(object)

描述:

如果无法在对象中修改现有属性的特性和值,且无法向对象添加新属性,则返回 true。

Object.isExtensible(object)

描述:

返回一个值,该值指示是否可向对象添加新属性。

Object.getOwnPropertyDescriptor(object, propertyname)

描述:

获取指定对象自己的属性描述符。 自己的属性描述符是直接在对象上定义的描述符,而不是从对象的原型继承的描述符。

参数:

object:必需。包含该属性的对象。
propertyname:必需。属性的名称。

示例:

var obj = {};
obj.newDataProperty = "abc";

var descriptor = Object.getOwnPropertyDescriptor(obj, "newDataProperty");
console.log(descriptor)
// output
//[object Object] {
//  configurable: true,
//  enumerable: true,
//  value: "abc",
//  writable: true
}

Object.getOwnPropertyNames(object)

描述:

返回对象自己的属性的名称。一个对象的自己的属性是指直接对该对象定义的属性,而不是从该对象的原型继承的属性。对象的属性包括字段(对象)和函数。

参数

object:必需。包含自己的属性的对象。

返回值:

一个数组,其中包含对象自己的属性的名称。

示例:

function Pasta(grain, width, shape) {
    // Define properties.
    this.grain = grain;
    this.width = width;
    this.shape = shape;
    this.toString = function () {
        return (this.grain + ", " + this.width + ", " + this.shape);
    }
}

var spaghetti = new Pasta("wheat", 0.2, "circle");

var arr = Object.getOwnPropertyNames(spaghetti);
document.write (arr);

// Output:
// grain,width,shape,toString

关于ECMA5,object的介绍就到这么多了,下面会陆续写关于Date,Json,Function,String,Array等的介绍,还请大家感兴趣的多多关注。

如果有任何问题都可以在下方给予我留言~

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.