分类
JavaScript

JavaScript常用工具方法封装

因为工作中经常用到这些方法,所有便把这些方法进行了总结。

JavaScript

1. type 类型判断

isString (o) { //是否字符串
    return Object.prototype.toString.call(o).slice(8, -1) === 'String'
}

isNumber (o) { //是否数字
    return Object.prototype.toString.call(o).slice(8, -1) === 'Number'
}

isBoolean (o) { //是否boolean
    return Object.prototype.toString.call(o).slice(8, -1) === 'Boolean'
}

isFunction (o) { //是否函数
    return Object.prototype.toString.call(o).slice(8, -1) === 'Function'
}

isNull (o) { //是否为null
    return Object.prototype.toString.call(o).slice(8, -1) === 'Null'
}

isUndefined (o) { //是否undefined
    return Object.prototype.toString.call(o).slice(8, -1) === 'Undefined'
}

isObj (o) { //是否对象
    return Object.prototype.toString.call(o).slice(8, -1) === 'Object'
}

isArray (o) { //是否数组
    return Object.prototype.toString.call(o).slice(8, -1) === 'Array'
}

isDate (o) { //是否时间
    return Object.prototype.toString.call(o).slice(8, -1) === 'Date'
}

isRegExp (o) { //是否正则
    return Object.prototype.toString.call(o).slice(8, -1) === 'RegExp'
}

isError (o) { //是否错误对象
    return Object.prototype.toString.call(o).slice(8, -1) === 'Error'
}

isSymbol (o) { //是否Symbol函数
    return Object.prototype.toString.call(o).slice(8, -1) === 'Symbol'
}

isPromise (o) { //是否Promise对象
    return Object.prototype.toString.call(o).slice(8, -1) === 'Promise'
}

isSet (o) { //是否Set对象
    return Object.prototype.toString.call(o).slice(8, -1) === 'Set'
}

isFalse (o) {
    if (!o || o === 'null' || o === 'undefined' || o === 'false' || o === 'NaN') return true
        return false
}

isTrue (o) {
    return !this.isFalse(o)
}

isIos () {
    var u = navigator.userAgent;
    if (u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) {//安卓手机
        // return "Android";
        return false
    } else if (u.indexOf('iPhone') > -1) {//苹果手机
        // return "iPhone";
        return true
    } else if (u.indexOf('iPad') > -1) {//iPad
        // return "iPad";
        return false
    } else if (u.indexOf('Windows Phone') > -1) {//winphone手机
        // return "Windows Phone";
        return false
    }else{
        return false
    }
}

isPC () { //是否为PC端
    var userAgentInfo = navigator.userAgent;
    var Agents = ["Android", "iPhone",
                "SymbianOS", "Windows Phone",
                "iPad", "iPod"];
    var flag = true;
    for (var v = 0; v < Agents.length; v++) {
        if (userAgentInfo.indexOf(Agents[v]) > 0) {
            flag = false;
            break;
        }
    }
    return flag;
}

browserType(){
    var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
    var isOpera = userAgent.indexOf("Opera") > -1; //判断是否Opera浏览器
    var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera; //判断是否IE浏览器
    var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
    var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器  
    var isFF = userAgent.indexOf("Firefox") > -1; //判断是否Firefox浏览器
    var isSafari = userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") == -1; //判断是否Safari浏览器
    var isChrome = userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Safari") > -1; //判断Chrome浏览器

    if (isIE) {
        var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
        reIE.test(userAgent);
        var fIEVersion = parseFloat(RegExp["$1"]);
        if(fIEVersion == 7) return "IE7"
        else if(fIEVersion == 8) return "IE8";
        else if(fIEVersion == 9) return "IE9";
        else if(fIEVersion == 10) return "IE10";
        else return "IE7以下"//IE版本过低
    }
    if (isIE11) return 'IE11';
    if (isEdge) return "Edge";
    if (isFF) return "FF";
    if (isOpera) return "Opera";
    if (isSafari) return "Safari";
    if (isChrome) return "Chrome";
}

checkStr (str, type) {
    switch (type) {
        case 'phone':   //手机号码
            return /^1[3|4|5|6|7|8|9][0-9]{9}$/.test(str);
        case 'tel':     //座机
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        case 'card':    //身份证
            return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str);
        case 'pwd':     //密码以字母开头,长度在6~18之间,只能包含字母、数字和下划线
            return /^[a-zA-Z]\w{5,17}$/.test(str)
        case 'postal':  //邮政编码
            return /[1-9]\d{5}(?!\d)/.test(str);
        case 'QQ':      //QQ号
            return /^[1-9][0-9]{4,9}$/.test(str);
        case 'email':   //邮箱
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'money':   //金额(小数点2位)
            return /^\d*(?:\.\d{0,2})?$/.test(str);
        case 'URL':     //网址
            return /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/.test(str)
        case 'IP':      //IP
            return /((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))/.test(str);
        case 'date':    //日期时间
            return /^(\d{4})\-(\d{2})\-(\d{2}) (\d{2})(?:\:\d{2}|:(\d{2}):(\d{2}))$/.test(str) || /^(\d{4})\-(\d{2})\-(\d{2})$/.test(str)
        case 'number':  //数字
            return /^[0-9]$/.test(str);
        case 'english': //英文
            return /^[a-zA-Z]+$/.test(str);
        case 'chinese': //中文
            return /^[\u4E00-\u9FA5]+$/.test(str);
        case 'lower':   //小写
            return /^[a-z]+$/.test(str);
        case 'upper':   //大写
            return /^[A-Z]+$/.test(str);
        case 'HTML':    //HTML标记
            return /<("[^"]*"|'[^']*'|[^'">])*>/.test(str);
        default:
            return true;
    }

    // 严格的身份证校验
    isCardID(sId) {
        if (!/(^\d{15}$)|(^\d{17}(\d|X|x)$)/.test(sId)) {
            alert('你输入的身份证长度或格式错误')
            return false
        }
        //身份证城市
        var aCity={11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外"};
        if(!aCity[parseInt(sId.substr(0,2))]) { 
            alert('你的身份证地区非法')
            return false
        }

        // 出生日期验证
        var sBirthday=(sId.substr(6,4)+"-"+Number(sId.substr(10,2))+"-"+Number(sId.substr(12,2))).replace(/-/g,"/"),
            d = new Date(sBirthday)
        if(sBirthday != (d.getFullYear()+"/"+ (d.getMonth()+1) + "/" + d.getDate())) {
            alert('身份证上的出生日期非法')
            return false
        }

        // 身份证号码校验
        var sum = 0,
            weights =  [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2],
            codes = "10X98765432"
        for (var i = 0; i < sId.length - 1; i++) {
            sum += sId[i] * weights[i];
        }
        var last = codes[sum % 11]; //计算出来的最后一位身份证号码
        if (sId[sId.length-1] != last) { 
            alert('你输入的身份证号非法')
            return false
        }

        return true
    }
}

2. Date

/**
 * 格式化时间
 * 
 * @param  {time} 时间
 * @param  {cFormat} 格式
 * @return {String} 字符串
 *
 * @example formatTime('2018-1-29', '{y}/{m}/{d} {h}:{i}:{s}') // -> 2018/01/29 00:00:00
 */
formatTime(time, cFormat) {
    if (arguments.length === 0) return null
    if ((time + '').length === 10) {
        time = +time * 1000
    }

    var format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}', date
    if (typeof time === 'object') {
        date = time
    } else {
        date = new Date(time)
    }

    var formatObj = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay()
    }
    var time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
        var value = formatObj[key]
        if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
        if (result.length > 0 && value < 10) {
            value = '0' + value
        }
        return value || 0
    })
    return time_str
}

/**
 * 返回指定长度的月份集合
 * 
 * @param  {time} 时间
 * @param  {len} 长度
 * @param  {direction} 方向:  1: 前几个月;  2: 后几个月;  3:前后几个月  默认 3
 * @return {Array} 数组
 * 
 * @example   getMonths('2018-1-29', 6, 1)  // ->  ["2018-1", "2017-12", "2017-11", "2017-10", "2017-9", "2017-8", "2017-7"]
 */
getMonths(time, len, direction) {
    var mm = new Date(time).getMonth(),
        yy = new Date(time).getFullYear(),
        direction = isNaN(direction) ? 3 : direction,
        index = mm;
    var cutMonth = function(index) {
        if ( index <= len && index >= -len) {
            return direction === 1 ? formatPre(index).concat(cutMonth(++index)):
                direction === 2 ? formatNext(index).concat(cutMonth(++index)):formatCurr(index).concat(cutMonth(++index))
        }
        return []
    }
    var formatNext = function(i) {
        var y = Math.floor(i/12),
            m = i%12
        return [yy+y + '-' + (m+1)]
    }
    var formatPre = function(i) {
        var y = Math.ceil(i/12),
            m = i%12
        m = m===0 ? 12 : m
        return [yy-y + '-' + (13 - m)]
    }
    var formatCurr = function(i) {
        var y = Math.floor(i/12),
            yNext = Math.ceil(i/12),
            m = i%12,
            mNext = m===0 ? 12 : m
        return [yy-yNext + '-' + (13 - mNext),yy+y + '-' + (m+1)]
    }
    // 数组去重
    var unique = function(arr) {
        if ( Array.hasOwnProperty('from') ) {
            return Array.from(new Set(arr));
        }else{
            var n = {},r=[]; 
            for(var i = 0; i < arr.length; i++){
                if (!n[arr[i]]){
                    n[arr[i]] = true; 
                    r.push(arr[i]);
                }
            }
            return r;
        }
    }
    return direction !== 3 ? cutMonth(index) : unique(cutMonth(index).sort(function(t1, t2){
        return new Date(t1).getTime() - new Date(t2).getTime()
    }))
}

/**
 * 返回指定长度的天数集合
 * 
 * @param  {time} 时间
 * @param  {len} 长度
 * @param  {direction} 方向: 1: 前几天;  2: 后几天;  3:前后几天  默认 3
 * @return {Array} 数组
 *
 * @example date.getDays('2018-1-29', 6) // -> ["2018-1-26", "2018-1-27", "2018-1-28", "2018-1-29", "2018-1-30", "2018-1-31", "2018-2-1"]
 */
getDays(time, len, diretion) {
    var tt = new Date(time)
    var getDay = function(day) {
        var t = new Date(time)
        t.setDate(t.getDate() + day)
        var m = t.getMonth()+1
        return t.getFullYear()+'-'+m+'-'+t.getDate()
    }
    var arr = []
    if (diretion === 1) {
        for (var i = 1; i <= len; i++) {
            arr.unshift(getDay(-i))
        }
    }else if(diretion === 2) {
        for (var i = 1; i <= len; i++) {
            arr.push(getDay(i))
        }
    }else {
        for (var i = 1; i <= len; i++) {
            arr.unshift(getDay(-i))
        }
        arr.push(tt.getFullYear()+'-'+(tt.getMonth()+1)+'-'+tt.getDate())
        for (var i = 1; i <= len; i++) {
            arr.push(getDay(i))
        }
    }
    return diretion === 1 ? arr.concat([tt.getFullYear()+'-'+(tt.getMonth()+1)+'-'+tt.getDate()]) : 
        diretion === 2 ? [tt.getFullYear()+'-'+(tt.getMonth()+1)+'-'+tt.getDate()].concat(arr) : arr
}

/**
 * @param  {s} 秒数
 * @return {String} 字符串 
 *
 * @example formatHMS(3610) // -> 1h0m10s
 */
formatHMS (s) {
    var str = ''
    if (s > 3600) {
        str = Math.floor(s/3600)+'h'+Math.floor(s%3600/60)+'m'+s%60+'s'
    }else if(s > 60) {
        str = Math.floor(s/60)+'m'+s%60+'s'
    }else{
        str = s%60+'s'
    }
    return str
}

/*获取某月有多少天*/
getMonthOfDay (time) {
    var date = new Date(time)
    var year = date.getFullYear()
    var mouth = date.getMonth() + 1
    var days

    //当月份为二月时,根据闰年还是非闰年判断天数
    if (mouth == 2) {
        days = (year%4==0 && year%100==0 && year%400==0) || (year%4==0 && year%100!=0) ? 28 : 29
    } else if (mouth == 1 || mouth == 3 || mouth == 5 || mouth == 7 || mouth == 8 || mouth == 10 || mouth == 12) {
        //月份为:1,3,5,7,8,10,12 时,为大月.则天数为31;
        days = 31
    } else {
        //其他月份,天数为:30.
        days = 30
    }
    return days
}

/*获取某年有多少天*/
getYearOfDay (time) {
    var firstDayYear = this.getFirstDayOfYear(time);
    var lastDayYear = this.getLastDayOfYear(time);
    var numSecond = (new Date(lastDayYear).getTime() - new Date(firstDayYear).getTime())/1000;
    return Math.ceil(numSecond/(24*3600));
}

/*获取某年的第一天*/
getFirstDayOfYear (time) {
    var year = new Date(time).getFullYear();
    return year + "-01-01 00:00:00";
}

/*获取某年最后一天*/
getLastDayOfYear (time) {
    var year = new Date(time).getFullYear();
    var dateString = year + "-12-01 00:00:00";
    var endDay = this.getMonthOfDay(dateString);
    return year + "-12-" + endDay + " 23:59:59";
}

/*获取某个日期是当年中的第几天*/
getDayOfYear (time) {
    var firstDayYear = this.getFirstDayOfYear(time);
    var numSecond = (new Date(time).getTime() - new Date(firstDayYear).getTime())/1000;
    return Math.ceil(numSecond/(24*3600));
}

/*获取某个日期在这一年的第几周*/
getDayOfYearWeek (time) {
    var numdays = this.getDayOfYear(time);
    return Math.ceil(numdays / 7);
}

3. Array

/*判断一个元素是否在数组中*/
contains (arr, val) {
    return arr.indexOf(val) != -1 ? true : false;
}

/**
 * @param  {arr} 数组
 * @param  {fn} 回调函数
 * @return {undefined}
 */
each (arr, fn) {
    fn = fn || Function;
    var a = [];
    var args = Array.prototype.slice.call(arguments, 1);
    for(var i = 0; i < arr.length; i++) {
        var res = fn.apply(arr, [arr[i], i].concat(args));
        if(res != null) a.push(res);
    }
}

/**
 * @param  {arr} 数组
 * @param  {fn} 回调函数
 * @param  {thisObj} this指向
 * @return {Array} 
 */
map (arr, fn, thisObj) {
    var scope = thisObj || window;
    var a = [];
    for(var i = 0, j = arr.length; i < j; ++i) {
        var res = fn.call(scope, arr[i], i, this);
        if(res != null) a.push(res);
    }
    return a;
}

/**
 * @param  {arr} 数组
 * @param  {type} 1:从小到大   2:从大到小   3:随机
 * @return {Array}
 */
sort (arr, type = 1) {
    return arr.sort( (a, b) => {
        switch(type) {
            case 1:
                return a - b;
            case 2:
                return b - a;
            case 3:
                return Math.random() - 0.5;
            default:
                return arr;
        }
    })
}

/*去重*/
unique (arr) {
    if ( Array.hasOwnProperty('from') ) {
        return Array.from(new Set(arr));
    }else{
        var n = {},r=[]; 
        for(var i = 0; i < arr.length; i++){
            if (!n[arr[i]]){
                n[arr[i]] = true; 
                r.push(arr[i]);
            }
        }
        return r;
    }
    // 注:上面 else 里面的排重并不能区分 2 和 '2',但能减少用indexOf带来的性能,暂时没找到替代的方法。。。
    /* 正确排重
    if ( Array.hasOwnProperty('from') ) {
        return Array.from(new Set(arr))
    }else{
        var r = [], NaNBol = true
        for(var i=0; i < arr.length; i++) {
            if (arr[i] !== arr[i]) {
                if (NaNBol && r.indexOf(arr[i]) === -1) {
                    r.push(arr[i])
                    NaNBol = false
                }
            }else{
                if(r.indexOf(arr[i]) === -1) r.push(arr[i])
            }
        }
        return r
    }

     */
}

/*求两个集合的并集*/
union (a, b) {
    var newArr = a.concat(b);
    return this.unique(newArr);
}

/*求两个集合的交集*/
intersect (a, b) {
    var _this = this;
    a = this.unique(a);
    return this.map(a, function(o) {
        return _this.contains(b, o) ? o : null;
    });
}

/*删除其中一个元素*/
remove (arr, ele) {
    var index = arr.indexOf(ele);
    if(index > -1) {
        arr.splice(index, 1);
    }
    return arr;
}

/*将类数组转换为数组的方法*/
formArray (ary) {
    var arr = [];
    if(Array.isArray(ary)) {
        arr = ary;
    } else {
        arr = Array.prototype.slice.call(ary);
    };
    return arr;
}

/*最大值*/
max (arr) {
    return Math.max.apply(null, arr);
}

/*最小值*/
min (arr) {
    return Math.min.apply(null, arr);
}

/*求和*/
sum (arr) {
    return arr.reduce( (pre, cur) => {
        return pre + cur
    })
}

/*平均值*/
average (arr) {
    return this.sum(arr)/arr.length
}

4. String 字符串操作

/**
 * 去除空格
 * @param  {str}
 * @param  {type} 
 *       type:  1-所有空格  2-前后空格  3-前空格 4-后空格
 * @return {String}
 */
trim (str, type) {
    type = type || 1
    switch (type) {
        case 1:
            return str.replace(/\s+/g, "");
        case 2:
            return str.replace(/(^\s*)|(\s*$)/g, "");
        case 3:
            return str.replace(/(^\s*)/g, "");
        case 4:
            return str.replace(/(\s*$)/g, "");
        default:
            return str;
    }
}

/**
 * @param  {str} 
 * @param  {type}
 *       type:  1:首字母大写  2:首页母小写  3:大小写转换  4:全部大写  5:全部小写
 * @return {String}
 */
changeCase (str, type) {
    type = type || 4
    switch (type) {
        case 1:
            return str.replace(/\b\w+\b/g, function (word) {
                return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();

            });
        case 2:
            return str.replace(/\b\w+\b/g, function (word) {
                return word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase();
            });
        case 3:
            return str.split('').map( function(word){
                if (/[a-z]/.test(word)) {
                    return word.toUpperCase();
                }else{
                    return word.toLowerCase()
                }
            }).join('')
        case 4:
            return str.toUpperCase();
        case 5:
            return str.toLowerCase();
        default:
            return str;
    }
}

/*
    检测密码强度
*/
checkPwd (str) {
    var Lv = 0;
    if (str.length < 6) {
        return Lv
    }
    if (/[0-9]/.test(str)) {
        Lv++
    }
    if (/[a-z]/.test(str)) {
        Lv++
    }
    if (/[A-Z]/.test(str)) {
        Lv++
    }
    if (/[\.|-|_]/.test(str)) {
        Lv++
    }
    return Lv;
}

/*过滤html代码(把<>转换)*/
filterTag (str) {
    str = str.replace(/&/ig, "&");
    str = str.replace(/</ig, "<");
    str = str.replace(/>/ig, ">");
    str = str.replace(" ", "&nbsp;");
    return str;
}

5. Number

/*随机数范围*/
random (min, max) {
    if (arguments.length === 2) {
        return Math.floor(min + Math.random() * ( (max+1) - min ))
    }else{
        return null;
    }

}

/*将阿拉伯数字翻译成中文的大写数字*/
numberToChinese (num) {
    var AA = new Array("零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十");
    var BB = new Array("", "十", "百", "仟", "萬", "億", "点", "");
    var a = ("" + num).replace(/(^0*)/g, "").split("."),
        k = 0,
        re = "";
    for(var i = a[0].length - 1; i >= 0; i--) {
        switch(k) {
            case 0:
                re = BB[7] + re;
                break;
            case 4:
                if(!new RegExp("0{4}//d{" + (a[0].length - i - 1) + "}$")
                    .test(a[0]))
                    re = BB[4] + re;
                break;
            case 8:
                re = BB[5] + re;
                BB[7] = BB[5];
                k = 0;
                break;
        }
        if(k % 4 == 2 && a[0].charAt(i + 2) != 0 && a[0].charAt(i + 1) == 0)
            re = AA[0] + re;
        if(a[0].charAt(i) != 0)
            re = AA[a[0].charAt(i)] + BB[k % 4] + re;
        k++;
    }

    if(a.length > 1) // 加上小数部分(如果有小数部分)
    {
        re += BB[6];
        for(var i = 0; i < a[1].length; i++)
            re += AA[a[1].charAt(i)];
    }
    if(re == '一十')
        re = "十";
    if(re.match(/^一/) && re.length == 3)
        re = re.replace("一", "");
    return re;
}

/*将数字转换为大写金额*/
changeToChinese (Num) {
        //判断如果传递进来的不是字符的话转换为字符
        if(typeof Num == "number") {
            Num = new String(Num);
        };
        Num = Num.replace(/,/g, "") //替换tomoney()中的“,”
        Num = Num.replace(/ /g, "") //替换tomoney()中的空格
        Num = Num.replace(/¥/g, "") //替换掉可能出现的¥字符
        if(isNaN(Num)) { //验证输入的字符是否为数字
            //alert("请检查小写金额是否正确");
            return "";
        };
        //字符处理完毕后开始转换,采用前后两部分分别转换
        var part = String(Num).split(".");
        var newchar = "";
        //小数点前进行转化
        for(var i = part[0].length - 1; i >= 0; i--) {
            if(part[0].length > 10) {
                return "";
                //若数量超过拾亿单位,提示
            }
            var tmpnewchar = ""
            var perchar = part[0].charAt(i);
            switch(perchar) {
                case "0":
                    tmpnewchar = "零" + tmpnewchar;
                    break;
                case "1":
                    tmpnewchar = "壹" + tmpnewchar;
                    break;
                case "2":
                    tmpnewchar = "贰" + tmpnewchar;
                    break;
                case "3":
                    tmpnewchar = "叁" + tmpnewchar;
                    break;
                case "4":
                    tmpnewchar = "肆" + tmpnewchar;
                    break;
                case "5":
                    tmpnewchar = "伍" + tmpnewchar;
                    break;
                case "6":
                    tmpnewchar = "陆" + tmpnewchar;
                    break;
                case "7":
                    tmpnewchar = "柒" + tmpnewchar;
                    break;
                case "8":
                    tmpnewchar = "捌" + tmpnewchar;
                    break;
                case "9":
                    tmpnewchar = "玖" + tmpnewchar;
                    break;
            }
            switch(part[0].length - i - 1) {
                case 0:
                    tmpnewchar = tmpnewchar + "元";
                    break;
                case 1:
                    if(perchar != 0) tmpnewchar = tmpnewchar + "拾";
                    break;
                case 2:
                    if(perchar != 0) tmpnewchar = tmpnewchar + "佰";
                    break;
                case 3:
                    if(perchar != 0) tmpnewchar = tmpnewchar + "仟";
                    break;
                case 4:
                    tmpnewchar = tmpnewchar + "万";
                    break;
                case 5:
                    if(perchar != 0) tmpnewchar = tmpnewchar + "拾";
                    break;
                case 6:
                    if(perchar != 0) tmpnewchar = tmpnewchar + "佰";
                    break;
                case 7:
                    if(perchar != 0) tmpnewchar = tmpnewchar + "仟";
                    break;
                case 8:
                    tmpnewchar = tmpnewchar + "亿";
                    break;
                case 9:
                    tmpnewchar = tmpnewchar + "拾";
                    break;
            }
            var newchar = tmpnewchar + newchar;
        }
        //小数点之后进行转化
        if(Num.indexOf(".") != -1) {
            if(part[1].length > 2) {
                // alert("小数点之后只能保留两位,系统将自动截断");
                part[1] = part[1].substr(0, 2)
            }
            for(i = 0; i < part[1].length; i++) {
                tmpnewchar = ""
                perchar = part[1].charAt(i)
                switch(perchar) {
                    case "0":
                        tmpnewchar = "零" + tmpnewchar;
                        break;
                    case "1":
                        tmpnewchar = "壹" + tmpnewchar;
                        break;
                    case "2":
                        tmpnewchar = "贰" + tmpnewchar;
                        break;
                    case "3":
                        tmpnewchar = "叁" + tmpnewchar;
                        break;
                    case "4":
                        tmpnewchar = "肆" + tmpnewchar;
                        break;
                    case "5":
                        tmpnewchar = "伍" + tmpnewchar;
                        break;
                    case "6":
                        tmpnewchar = "陆" + tmpnewchar;
                        break;
                    case "7":
                        tmpnewchar = "柒" + tmpnewchar;
                        break;
                    case "8":
                        tmpnewchar = "捌" + tmpnewchar;
                        break;
                    case "9":
                        tmpnewchar = "玖" + tmpnewchar;
                        break;
                }
                if(i == 0) tmpnewchar = tmpnewchar + "角";
                if(i == 1) tmpnewchar = tmpnewchar + "分";
                newchar = newchar + tmpnewchar;
            }
        }
        //替换所有无用汉字
        while(newchar.search("零零") != -1)
            newchar = newchar.replace("零零", "零");
        newchar = newchar.replace("零亿", "亿");
        newchar = newchar.replace("亿万", "亿");
        newchar = newchar.replace("零万", "万");
        newchar = newchar.replace("零元", "元");
        newchar = newchar.replace("零角", "");
        newchar = newchar.replace("零分", "");
        if(newchar.charAt(newchar.length - 1) == "元") {
            newchar = newchar + "整"
        }
        return newchar;
    }

6. Http

/**
 * @param  {setting}
 */
ajax(setting){
    //设置参数的初始值
    var opts={
        method: (setting.method || "GET").toUpperCase(), //请求方式
        url: setting.url || "", // 请求地址
        async: setting.async || true, // 是否异步
        dataType: setting.dataType || "json", // 解析方式
        data: setting.data || "", // 参数
        success: setting.success || function(){}, // 请求成功回调
        error: setting.error || function(){} // 请求失败回调
    }

    // 参数格式化
    function params_format (obj) {
        var str = ''
        for (var i in obj) {
            str += i + '=' + obj[i] + '&'
        }
        return str.split('').slice(0, -1).join('')
    }

    // 创建ajax对象
    var xhr=new XMLHttpRequest();

    // 连接服务器open(方法GET/POST,请求地址, 异步传输)
    if(opts.method == 'GET'){
        xhr.open(opts.method, opts.url + "?" + params_format(opts.data), opts.async);
        xhr.send();
    }else{
        xhr.open(opts.method, opts.url, opts.async);
        xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xhr.send(opts.data);
    }

    /*
    ** 每当readyState改变时,就会触发onreadystatechange事件
    ** readyState属性存储有XMLHttpRequest的状态信息
    ** 0 :请求未初始化
    ** 1 :服务器连接已建立
    ** 2 :请求已接受
    ** 3 : 请求处理中
    ** 4 :请求已完成,且相应就绪
    */
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
            switch(opts.dataType){
                case "json":
                    var json = JSON.parse(xhr.responseText);
                    opts.success(json);
                    break;
                case "xml":
                    opts.success(xhr.responseXML);
                    break;
                default:
                    opts.success(xhr.responseText);
                    break;
            }
        }
    }

    xhr.onerror = function(err) {
        opts.error(err);
    }
}

/**
 * @param  {url}
 * @param  {setting}
 * @return {Promise}
 */
fetch(url, setting) {
    //设置参数的初始值
    let opts={
        method: (setting.method || 'GET').toUpperCase(), //请求方式
        headers : setting.headers  || {}, // 请求头设置
        credentials : setting.credentials  || true, // 设置cookie是否一起发送
        body: setting.body || {},
        mode : setting.mode  || 'no-cors', // 可以设置 cors, no-cors, same-origin
        redirect : setting.redirect  || 'follow', // follow, error, manual
        cache : setting.cache  || 'default' // 设置 cache 模式 (default, reload, no-cache)
    }
    let dataType = setting.dataType || "json", // 解析方式  
        data = setting.data || "" // 参数

    // 参数格式化
    function params_format (obj) {
        var str = ''
        for (var i in obj) {
            str += `${i}=${obj[i]}&`
        }
        return str.split('').slice(0, -1).join('')
    }

    if (opts.method === 'GET') {
        url = url + (data?`?${params_format(data)}`:'')
    }else{
        setting.body = data || {}
    }

    return new Promise( (resolve, reject) => {
        fetch(url, opts).then( async res => {
            let data = dataType === 'text' ? await res.text() :
                dataType === 'blob' ? await res.blob() : await res.json() 
            resolve(data)
        }).catch( e => {
            reject(e)
        })
    })

}

7. DOM

$ (selector){ 
    var type = selector.substring(0, 1);
    if (type === '#') {
        if (document.querySelecotor) return document.querySelector(selector)
            return document.getElementById(selector.substring(1))

    }else if (type === '.') {
        if (document.querySelecotorAll) return document.querySelectorAll(selector)
            return document.getElementsByClassName(selector.substring(1))
    }else{
        return document['querySelectorAll' ? 'querySelectorAll':'getElementsByTagName'](selector)
    }
} 

/*检测类名*/
hasClass (ele, name) {
    return ele.className.match(new RegExp('(\\s|^)' + name + '(\\s|$)'));
}

/*添加类名*/
addClass (ele, name) {
    if (!this.hasClass(ele, name)) ele.className += " " + name;
}

/*删除类名*/
removeClass (ele, name) {
    if (this.hasClass(ele, name)) {
        var reg = new RegExp('(\\s|^)' + name + '(\\s|$)');
        ele.className = ele.className.replace(reg, '');
    }
}

/*替换类名*/
replaceClass (ele, newName, oldName) {
    this.removeClass(ele, oldName);
    this.addClass(ele, newName);
}

/*获取兄弟节点*/
siblings (ele) {
    console.log(ele.parentNode)
    var chid = ele.parentNode.children,eleMatch = []; 
    for(var i = 0, len = chid.length; i < len; i ++){ 
        if(chid[i] != ele){ 
            eleMatch.push(chid[i]); 
        } 
    } 
    return eleMatch;
}

/*获取行间样式属性*/
getByStyle (obj,name){
    if(obj.currentStyle){
        return  obj.currentStyle[name];
    }else{
        return  getComputedStyle(obj,false)[name];
    }
}

8. Storage 储存操作

class StorageFn {
    constructor () {
        this.ls = window.localStorage;
        this.ss = window.sessionStorage;
    }

    /*-----------------cookie---------------------*/
    /*设置cookie*/
    setCookie (name, value, day) {
        var setting = arguments[0];
        if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object'){
            for (var i in setting) {
                var oDate = new Date();
                oDate.setDate(oDate.getDate() + day);
                document.cookie = i + '=' + setting[i] + ';expires=' + oDate;
            }
        }else{
            var oDate = new Date();
            oDate.setDate(oDate.getDate() + day);
            document.cookie = name + '=' + value + ';expires=' + oDate;
        }

    }

    /*获取cookie*/
    getCookie (name) {
        var arr = document.cookie.split('; ');
        for (var i = 0; i < arr.length; i++) {
            var arr2 = arr[i].split('=');
            if (arr2[0] == name) {
                return arr2[1];
            }
        }
        return '';
    }

    /*删除cookie*/
    removeCookie (name) {
        this.setCookie(name, 1, -1);
    }

    /*-----------------localStorage---------------------*/
    /*设置localStorage*/
    setLocal(key, val) {
        var setting = arguments[0];
        if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object'){
            for(var i in setting){
                this.ls.setItem(i, JSON.stringify(setting[i]))
            }
        }else{
            this.ls.setItem(key, JSON.stringify(val))
        }

    }

    /*获取localStorage*/
    getLocal(key) {
        if (key) return JSON.parse(this.ls.getItem(key))
        return null;

    }

    /*移除localStorage*/
    removeLocal(key) {
        this.ls.removeItem(key)
    }

    /*移除所有localStorage*/
    clearLocal() {
        this.ls.clear()
    }

    /*-----------------sessionStorage---------------------*/
    /*设置sessionStorage*/
    setSession(key, val) {
        var setting = arguments[0];
        if (Object.prototype.toString.call(setting).slice(8, -1) === 'Object'){
            for(var i in setting){
                this.ss.setItem(i, JSON.stringify(setting[i]))
            }
        }else{
            this.ss.setItem(key, JSON.stringify(val))
        }

    }

    /*获取sessionStorage*/
    getSession(key) {
        if (key) return JSON.parse(this.ss.getItem(key))
        return null;

    }

    /*移除sessionStorage*/
    removeSession(key) {
        this.ss.removeItem(key)
    }

    /*移除所有sessionStorage*/
    clearSession() {
        this.ss.clear()
    }

}

9. Other 其它操作

/*获取网址参数*/
getURL(name){
    var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
    var r = decodeURI(window.location.search).substr(1).match(reg);
    if(r!=null) return  r[2]; return null;
}

/*获取全部url参数,并转换成json对象*/
getUrlAllParams (url) {
    var url = url ? url : window.location.href;
    var _pa = url.substring(url.indexOf('?') + 1),
        _arrS = _pa.split('&'),
        _rs = {};
    for (var i = 0, _len = _arrS.length; i < _len; i++) {
        var pos = _arrS[i].indexOf('=');
        if (pos == -1) {
            continue;
        }
        var name = _arrS[i].substring(0, pos),
            value = window.decodeURIComponent(_arrS[i].substring(pos + 1));
        _rs[name] = value;
    }
    return _rs;
}

/*删除url指定参数,返回url*/
delParamsUrl(url, name){
    var baseUrl = url.split('?')[0] + '?';
    var query = url.split('?')[1];
    if (query.indexOf(name)>-1) {
        var obj = {}
        var arr = query.split("&");
        for (var i = 0; i < arr.length; i++) {
            arr[i] = arr[i].split("=");
            obj[arr[i][0]] = arr[i][1];
        };
        delete obj[name];
        var url = baseUrl + JSON.stringify(obj).replace(/[\"\{\}]/g,"").replace(/\:/g,"=").replace(/\,/g,"&");
        return url
    }else{
        return url;
    }
}

/*获取十六进制随机颜色*/
getRandomColor () {
    return '#' + (function(h) {
        return new Array(7 - h.length).join("0") + h;
    })((Math.random() * 0x1000000 << 0).toString(16));
}

/*图片加载*/
imgLoadAll(arr,callback){
    var arrImg = []; 
    for (var i = 0; i < arr.length; i++) {
        var img = new Image();
        img.src = arr[i];
        img.onload = function(){
            arrImg.push(this);
            if (arrImg.length == arr.length) {
                callback && callback();
            }
        }
    }
}

/*音频加载*/
loadAudio(src, callback) {
    var audio = new Audio(src);
    audio.onloadedmetadata = callback;
    audio.src = src;
}

/*DOM转字符串*/
domToStirng(htmlDOM){
    var div= document.createElement("div");
    div.appendChild(htmlDOM);
    return div.innerHTML
}

/*字符串转DOM*/
stringToDom(htmlString){
    var div= document.createElement("div");
    div.innerHTML=htmlString;
    return div.children[0];
}

/**
 * 光标所在位置插入字符,并设置光标位置
 * 
 * @param {dom} 输入框
 * @param {val} 插入的值
 * @param {posLen} 光标位置处在 插入的值的哪个位置
 */
setCursorPosition (dom,val,posLen) {
    var cursorPosition = 0;
    if(dom.selectionStart){
        cursorPosition = dom.selectionStart;
    }
    this.insertAtCursor(dom,val);
    dom.focus();
    console.log(posLen)
    dom.setSelectionRange(dom.value.length,cursorPosition + (posLen || val.length));
}

/*光标所在位置插入字符*/
insertAtCursor(dom, val) {
    if (document.selection){
        dom.focus();
        sel = document.selection.createRange();
        sel.text = val;
        sel.select();
    }else if (dom.selectionStart || dom.selectionStart == '0'){
        let startPos = dom.selectionStart;
        let endPos = dom.selectionEnd;
        let restoreTop = dom.scrollTop;
        dom.value = dom.value.substring(0, startPos) + val + dom.value.substring(endPos, dom.value.length);
        if (restoreTop > 0){
            dom.scrollTop = restoreTop;
        }
        dom.focus();
        dom.selectionStart = startPos + val.length;
        dom.selectionEnd = startPos + val.length;
    } else {
        dom.value += val;
        dom.focus();
    }
}

CSS

1. pc-reset PC样式初始化

/* normalize.css */

html {
  line-height: 1.15;
  /* 1 */
  -ms-text-size-adjust: 100%;
  /* 2 */
  -webkit-text-size-adjust: 100%;
  /* 2 */
}

body {
  margin: 0;
}

article,
aside,
footer,
header,
nav,
section {
  display: block;
}

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

figcaption,
figure,
main {
  /* 1 */
  display: block;
}

figure {
  margin: 1em 40px;
}

hr {
  box-sizing: content-box;
  /* 1 */
  height: 0;
  /* 1 */
  overflow: visible;
  /* 2 */
}

pre {
  font-family: monospace, monospace;
  /* 1 */
  font-size: 1em;
  /* 2 */
}

a {
  background-color: transparent;
  /* 1 */
  -webkit-text-decoration-skip: objects;
  /* 2 */
}

abbr[title] {
  border-bottom: none;
  /* 1 */
  text-decoration: underline;
  /* 2 */
  text-decoration: underline dotted;
  /* 2 */
}

b,
strong {
  font-weight: inherit;
}

b,
strong {
  font-weight: bolder;
}

code,
kbd,
samp {
  font-family: monospace, monospace;
  /* 1 */
  font-size: 1em;
  /* 2 */
}

dfn {
  font-style: italic;
}

mark {
  background-color: #ff0;
  color: #000;
}

small {
  font-size: 80%;
}

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

audio,
video {
  display: inline-block;
}

audio:not([controls]) {
  display: none;
  height: 0;
}

img {
  border-style: none;
}

svg:not(:root) {
  overflow: hidden;
}

button,
input,
optgroup,
select,
textarea {
  font-family: sans-serif;
  /* 1 */
  font-size: 100%;
  /* 1 */
  line-height: 1.15;
  /* 1 */
  margin: 0;
  /* 2 */
}

button,
input {
  /* 1 */
  overflow: visible;
}

button,
select {
  /* 1 */
  text-transform: none;
}

button,
html [type="button"],

/* 1 */

[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
  /* 2 */
}

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

legend {
  box-sizing: border-box;
  /* 1 */
  color: inherit;
  /* 2 */
  display: table;
  /* 1 */
  max-width: 100%;
  /* 1 */
  padding: 0;
  /* 3 */
  white-space: normal;
  /* 1 */
}

progress {
  display: inline-block;
  /* 1 */
  vertical-align: baseline;
  /* 2 */
}

textarea {
  overflow: auto;
}

[type="checkbox"],
[type="radio"] {
  box-sizing: border-box;
  /* 1 */
  padding: 0;
  /* 2 */
}

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

[type="search"] {
  -webkit-appearance: textfield;
  /* 1 */
  outline-offset: -2px;
  /* 2 */
}

[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

 ::-webkit-file-upload-button {
  -webkit-appearance: button;
  /* 1 */
  font: inherit;
  /* 2 */
}

details,

/* 1 */

menu {
  display: block;
}

summary {
  display: list-item;
}

canvas {
  display: inline-block;
}

template {
  display: none;
}

[hidden] {
  display: none;
}

/* reset */

html,
body,
h1,
h2,
h3,
h4,
h5,
h6,
div,
dl,
dt,
dd,
ul,
ol,
li,
p,
blockquote,
pre,
hr,
figure,
table,
caption,
th,
td,
form,
fieldset,
legend,
input,
button,
textarea,
menu {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

2. Phone-reset

/* normalize.css */

html {
  line-height: 1.15;
  /* 1 */
  -ms-text-size-adjust: 100%;
  /* 2 */
  -webkit-text-size-adjust: 100%;
  /* 2 */
}

body {
  margin: 0;
}

article,
aside,
footer,
header,
nav,
section {
  display: block;
}

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

figcaption,
figure,
main {
  /* 1 */
  display: block;
}

figure {
  margin: 1em 40px;
}

hr {
  box-sizing: content-box;
  /* 1 */
  height: 0;
  /* 1 */
  overflow: visible;
  /* 2 */
}

pre {
  font-family: monospace, monospace;
  /* 1 */
  font-size: 1em;
  /* 2 */
}

a {
  background-color: transparent;
  /* 1 */
  -webkit-text-decoration-skip: objects;
  /* 2 */
}

abbr[title] {
  border-bottom: none;
  /* 1 */
  text-decoration: underline;
  /* 2 */
  text-decoration: underline dotted;
  /* 2 */
}

b,
strong {
  font-weight: inherit;
}

b,
strong {
  font-weight: bolder;
}

code,
kbd,
samp {
  font-family: monospace, monospace;
  /* 1 */
  font-size: 1em;
  /* 2 */
}

dfn {
  font-style: italic;
}

mark {
  background-color: #ff0;
  color: #000;
}

small {
  font-size: 80%;
}

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

audio,
video {
  display: inline-block;
}

audio:not([controls]) {
  display: none;
  height: 0;
}

img {
  border-style: none;
}

svg:not(:root) {
  overflow: hidden;
}

button,
input,
optgroup,
select,
textarea {
  font-family: sans-serif;
  /* 1 */
  font-size: 100%;
  /* 1 */
  line-height: 1.15;
  /* 1 */
  margin: 0;
  /* 2 */
}

button,
input {
  /* 1 */
  overflow: visible;
}

button,
select {
  /* 1 */
  text-transform: none;
}

button,
html [type="button"],

/* 1 */

[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
  /* 2 */
}

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

legend {
  box-sizing: border-box;
  /* 1 */
  color: inherit;
  /* 2 */
  display: table;
  /* 1 */
  max-width: 100%;
  /* 1 */
  padding: 0;
  /* 3 */
  white-space: normal;
  /* 1 */
}

progress {
  display: inline-block;
  /* 1 */
  vertical-align: baseline;
  /* 2 */
}

textarea {
  overflow: auto;
}

[type="checkbox"],
[type="radio"] {
  box-sizing: border-box;
  /* 1 */
  padding: 0;
  /* 2 */
}

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

[type="search"] {
  -webkit-appearance: textfield;
  /* 1 */
  outline-offset: -2px;
  /* 2 */
}

[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

 ::-webkit-file-upload-button {
  -webkit-appearance: button;
  /* 1 */
  font: inherit;
  /* 2 */
}

details,

/* 1 */

menu {
  display: block;
}

summary {
  display: list-item;
}

canvas {
  display: inline-block;
}

template {
  display: none;
}

[hidden] {
  display: none;
}

/* reset */

html,
body,
h1,
h2,
h3,
h4,
h5,
h6,
div,
dl,
dt,
dd,
ul,
ol,
li,
p,
blockquote,
pre,
hr,
figure,
table,
caption,
th,
td,
form,
fieldset,
legend,
input,
button,
textarea,
menu {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  /* 禁止选中文本 */
  -webkit-user-select: none;
  user-select: none;
  font: Oswald, 'Open Sans', Helvetica, Arial, sans-serif
}

/* 禁止长按链接与图片弹出菜单 */

a,
img {
  -webkit-touch-callout: none;
}

/*ios android去除自带阴影的样式*/

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

input[type="text"] {
  -webkit-appearance: none;
}

3. 公共样式提取

/* 禁止选中文本 */
.usn{
    -webkit-user-select:none;
    -moz-user-select:none;
    -ms-user-select:none;
    -o-user-select:none;
    user-select:none;
}
/* 浮动 */
.fl { float: left; }
.fr { float: right; }
.cf { zoom: 1; }
.cf:after {
    content:".";
    display:block;
    clear:both;
    visibility:hidden;
    height:0;
    overflow:hidden;
}

/* 元素类型 */
.db { display: block; }
.dn { display: none; }
.di { display: inline }
.dib {display: inline-block;}
.transparent { opacity: 0 }

/*文字排版、颜色*/
.f12 { font-size:12px }
.f14 { font-size:14px }
.f16 { font-size:16px }
.f18 { font-size:18px }
.f20 { font-size:20px }
.fb { font-weight:bold }
.fn { font-weight:normal }
.t2 { text-indent:2em }
.red,a.red { color:#cc0031 }
.darkblue,a.darkblue { color:#039 }
.gray,a.gray { color:#878787 }
.lh150 { line-height:150% }
.lh180 { line-height:180% }
.lh200 { line-height:200% }
.unl { text-decoration:underline; }
.no_unl { text-decoration:none; }
.tl { text-align: left; }
.tc { text-align: center; }
.tr { text-align: right; }
.tj { text-align: justify; text-justify: inter-ideograph; }
.wn { /* 强制不换行 */
    word-wrap:normal;
    white-space:nowrap;
}
.wb { /* 强制换行 */
    white-space:normal;
    word-wrap:break-word;
    word-break:break-all;
}
.wp { /* 保持空白序列*/
    overflow:hidden;text-align:left;white-space:pre-wrap;word-wrap:break-word;word-break:break-all;
}
.wes { /* 多出部分用省略号表示 , 用于一行 */
    overflow:hidden;
    word-wrap:normal;
    white-space:nowrap;
    text-overflow:ellipsis;
}
.wes-2 { /* 适用于webkit内核和移动端 */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
} 
.wes-3 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}
.wes-4 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    overflow: hidden;
}

/* 溢出样式 */
.ofh { overflow: hidden; }
.ofs {overflow: scroll; }
.ofa {overflow: auto; }
.ofv {overflow: visible; }

/* 定位方式 */
.ps {position: static; }
.pr {position: relative;zoom:1; }
.pa {position: absolute; }
.pf {position: fixed; }

/* 垂直对齐方式 */
.vt {vertical-align: top; }
.vm {vertical-align: middle; }
.vb {vertical-align: bottom; }

/* 鼠标样式 */
.csd {cursor: default; }
.csp {cursor: pointer; }
.csh {cursor: help; }
.csm {cursor: move; }

/* flex布局 */
.df-sb {
    display:flex;
    align-items: center;
    justify-content: space-between;
}
.df-sa {
    display:flex;
    align-items: center;
    justify-content: space-around;
}

/* 垂直居中 */
.df-c {
    display: flex;
    align-items: center;
    justify-content: center;
}
.tb-c {
    text-align:center;
    display:table-cell;
    vertical-align:middle;
}
.ts-c {
    position: absolute;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%);
}
.ts-mc {
    position: absolute;
    left: 0;right: 0;
    bottom: 0; top: 0;
    margin: auto;
}

/* 辅助 */
.mask-fixed-wrapper {
    width: 100%;
    height: 100%;
    position: fixed;
    left:0;top:0;
    background: rgba(0, 0, 0, 0.65);
    z-index: 999;
}
.bg-cover {
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center center;
}
.bg-cover-all {
    background-size: 100% 100%;
    background-repeat: no-repeat;
    background-position: center center;
}

分类
JavaScript

JavaScript加载时间线

JS执行是单线程,并不是说整个浏览器都是单线程的,姑且就成为单线程吧
JS单线程的原因是为了避免多线程操作dom,引发的并发问题,dom属于基础数据,从多线程上讲,对它的操作要加事物,而js的操作最初就是为了操作dom,嗯,幸好是单线程的,总之一句话,凡是能够修改dom的一定得同步

客户端JS时间线

  1. 创建document对象,开始解析web页面。创建HTMLHtmlElement对象,添加到document中。这个阶段document.readyState = 'loading'
  2. 遇到link外部css,创建线程加载,并继续解析文档。并发
  3. 遇到script外部js,并且没有设置async、defer,浏览器创建线程加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。js拥有修改dom的能力-->domcument.write
  4. 遇到script外部js,并且设置有async、defter,浏览器创建线程加载,并继续解析文档。async属性的脚本,脚本加载完成后立即执行。defter丢置尾部。document.createElement('script')的方式动态插入script元素来模拟async属性,实现脚本异步加载和执行。
  5. 遇到img等,浏览器创建线程加载,并继续解析文档。并发
  6. 当文档解析完成,document.readyState ='interactive'
  7. 文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意与async的不同)
  8. document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
  9. 当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = 'complete',window对象触发load事件。
  10. 从此,以异步响应方式处理用户输入、网络事件等。
分类
HTML/CSS

HTML5如何使用SVG

代码优化永远是程序员亘古不变的需求,而合理的利用SVG图片来代替部分PNG/JPG等格式的图片则是前端优化重要的一环,既然是优化,那我们先来看看SVG图片都有哪些优势:

  • SVG 可被非常多的工具读取和修改(比如记事本)
  • SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。
  • SVG 是可伸缩的
  • SVG 图像可在任何的分辨率下被高质量地打印
  • SVG 可在图像质量不下降的情况下被放大
  • SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)
  • SVG 可以与 Java 技术一起运行
  • SVG 是开放的标准
  • SVG 文件是纯粹的 XML

#####几个SVG图片小例子:

#####我们来看一下第三个分享图标的代码:

<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
  <g stroke="#AAB0BA" fill="none" fill-rule="evenodd">
    <path d="M10.524 3.413v8.235" stroke-linejoin="round"/>
    <path d="M13.027 7.508c.813 0 1.678-.01 1.678-.01.449 0 .812.376.812.826l-.005 6.36a.819.819 0 0 1-.811.826H6.31a.822.822 0 0 1-.811-.826l.005-6.36c0-.456.36-.825.812-.825l1.689.006M8.373 5.111l2.143-2.09 2.143 2.07"/>
  </g>
</svg>

不了解SVG的同学现在一定一脸问号,就跟我第一次见他们一样,别着急,我们从基础看起。

什么是SVG?

SVG 是一种基于 XML 语法的图像格式,全称是可缩放矢量图(Scalable Vector Graphics)。其他图像格式都是基于像素处理的,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。此外SVG 是万维网联盟的标准,SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体。

怎么使用?

在 HTML5 中,您能够将 SVG 元素直接嵌入 HTML 页面中,例如上面的那颗小红心:

<body>
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20">
    <defs>
      <rect id="a" y="54" width="60" height="25" rx="1"/>
      <mask id="b" x="0" y="0" width="60" height="25" fill="#fff">
        <use xlink:href="#a"/>
    </mask>
    </defs>
    <g transform="translate(-9 -56)" fill="none" fill-rule="evenodd">
      <use stroke="#EDEEEF" mask="url(#b)" stroke-width="2" xlink:href="#a"/>
      <path d="M19.05 62.797c-.208-.268-1.776-2.188-3.629-1.725-.662.165-1.439.44-2.009 1.463-2.18 3.913 4.965 8.983 5.615 9.433V72l.023-.016.023.016v-.032c.65-.45 7.795-5.52 5.615-9.433-.57-1.023-1.347-1.298-2.009-1.463-1.853-.463-3.42 1.457-3.629 1.725z" fill="red"/>
    </g>
  </svg>
</body>

SVG 代码也可以写在一个以.svg结尾的文件中,然后用<img><object><embed><iframe>等标签插入网页。

<img src="search.svg">
<object id="object" data="search.svg" type="image/svg+xml"></object>
<embed id="embed" src="search.svg" type="image/svg+xml">
<iframe id="iframe" src="search.svg"></iframe>

CSS也可以使用svg

.logo {
  background: url(logo.svg);
}

SVG 文件还可以转为 BASE64 编码,然后作为 Data URI 写入网页。

<img src="data:image/svg+xml;base64,[data]">

SVG的语法

1. <svg>标签
SVG 代码都放在顶层标签<svg>之中。下面是一个例子。

<svg width="100%" height="100%">
  <circle id="mycircle" cx="50" cy="50" r="50" />
</svg>

<svg>的width属性和height属性,指定了 SVG 图像在 HTML 元素中所占据的宽度和高度。除了相对单位,也可以采用绝对单位(单位:像素)。如果不指定这两个属性,SVG 图像默认大小是300像素(宽) x 150像素(高)。

如果只想展示 SVG 图像的一部分,就要指定viewBox属性。

<svg width="100" height="100" viewBox="50 50 50 50">
  <circle id="mycircle" cx="50" cy="50" r="50" />
</svg>

<viewBox>属性的值有四个数字,分别是左上角的横坐标和纵坐标、视口的宽度和高度。上面代码中,SVG 图像是100像素宽 x 100像素高,viewBox属性指定视口从(50, 50)这个点开始。所以,实际看到的是右下角的四分之一圆。

注意,视口必须适配所在的空间。上面代码中,视口的大小是 50 x 50,由于 SVG 图像的大小是 100 x 100,所以视口会放大去适配 SVG 图像的大小,即放大了四倍。

如果不指定width属性和height属性,只指定viewBox属性,则相当于只给定 SVG 图像的长宽比。这时,SVG 图像的默认大小将等于所在的 HTML 元素的大小。

2. <circle>标签
<circle>标签代表圆形。

<svg width="300" height="180">
  <circle cx="30"  cy="50" r="25" />
  <circle cx="90"  cy="50" r="25" class="red" />
  <circle cx="150" cy="50" r="25" class="fancy" />
</svg>

上面的代码定义了三个圆。<circle>标签的cx、cy、r属性分别为横坐标、纵坐标和半径,单位为像素。坐标都是相对于<svg>画布的左上角原点。

class属性用来指定对应的 CSS 类。

.red {
  fill: red;
}

.fancy {
  fill: none;
  stroke: black;
  stroke-width: 3pt;
}

SVG 的 CSS 属性与网页元素有所不同。

fill:填充色
stroke:描边色
stroke-width:边框宽度

3. <line>标签
<line>标签用来绘制直线。

<svg width="300" height="180">
  <line x1="0" y1="0" x2="200" y2="0" style="stroke:rgb(0,0,0);stroke-width:5" />
</svg>

上面代码中,标签的x1属性和y1属性,表示线段起点的横坐标和纵坐标;x2属性和y2属性,表示线段终点的横坐标和纵坐标;style属性表示线段的样式。

4. <polyline>标签
<polyline>标签用于绘制一根折线。

<svg width="300" height="180">
  <polyline points="3,3 30,28 3,53" fill="none" stroke="black" />
</svg>

<polyline>的points属性指定了每个端点的坐标,横坐标与纵坐标之间与逗号分隔,点与点之间用空格分隔。

5. <rect>标签
<rect>标签用于绘制矩形。

<svg width="300" height="180">
  <rect x="0" y="0" height="100" width="200" style="stroke: #70d5dd; fill: #dd524b" />
</svg>

<rect>的x属性和y属性,指定了矩形左上角端点的横坐标和纵坐标;width属性和height属性指定了矩形的宽度和高度(单位像素)。

6. <ellipse>标签
<ellipse>标签用于绘制椭圆。

<svg width="300" height="180">
  <ellipse cx="60" cy="60" ry="40" rx="20" stroke="black" stroke-width="5" fill="silver"/>
</svg>

<ellipse>的cx属性和cy属性,指定了椭圆中心的横坐标和纵坐标(单位像素);rx属性和ry属性,指定了椭圆横向轴和纵向轴的半径(单位像素)。

7. <polygon>标签
<polygon>标签用于绘制多边形。

<svg width="300" height="180">
  <polygon fill="green" stroke="orange" stroke-width="1" points="0,0 100,0 100,100 0,100 0,0"/>
</svg>

<polygon>的points属性指定了每个端点的坐标,横坐标与纵坐标之间与逗号分隔,点与点之间用空格分隔。

8. <path>标签
<path>标签用于制路径。

<svg width="300" height="180">
<path d="
  M 18,3
  L 46,3
  L 46,40
  L 61,40
  L 32,68
  L 3,40
  L 18,40
  Z
"></path>
</svg>

<path>的d属性表示绘制顺序,它的值是一个长字符串,每个字母表示一个绘制动作,后面跟着坐标。

M:移动到(moveto)
L:画直线到(lineto)
Z:闭合路径
9. <text>标签
<text>标签用于绘制文本。

<svg width="300" height="180">
  <text x="50" y="25">肆客足球</text>
</svg>

<text>的x属性和y属性,表示文本区块基线(baseline)起点的横坐标和纵坐标。文字的样式可以用class或style属性指定。

10. <use>标签
<use>标签用于复制一个形状。

<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
  <circle id="myCircle" cx="5" cy="5" r="4"/>

  <use href="#myCircle" x="10" y="0" fill="blue" />
  <use href="#myCircle" x="20" y="0" fill="white" stroke="blue" />
</svg>

<use>的href属性指定所要复制的节点,x属性和y属性是<use>左上角的坐标。另外,还可以指定width和height坐标。

11. <g>标签
<g>标签用于将多个形状组成一个组(group),方便复用。

<svg width="300" height="100">
  <g id="myCircle">
    <text x="25" y="20">圆形</text>
    <circle cx="50" cy="50" r="20"/>
  </g>

  <use href="#myCircle" x="100" y="0" fill="blue" />
  <use href="#myCircle" x="200" y="0" fill="white" stroke="blue" />
</svg>

12. <defs>标签
<defs>标签用于自定义形状,它内部的代码不会显示,仅供引用。

<svg width="300" height="100">
  <defs>
    <g id="myCircle">
      <text x="25" y="20">圆形</text>
      <circle cx="50" cy="50" r="20"/>
    </g>
  </defs>

  <use href="#myCircle" x="0" y="0" />
  <use href="#myCircle" x="100" y="0" fill="blue" />
  <use href="#myCircle" x="200" y="0" fill="white" stroke="blue" />
</svg>

13. <pattern>标签
<pattern>标签用于自定义一个形状,该形状可以被引用来平铺一个区域。

<svg width="500" height="500">
  <defs>
    <pattern id="dots" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
      <circle fill="#bee9e8" cx="50" cy="50" r="35" />
    </pattern>
  </defs>
  <rect x="0" y="0" width="100%" height="100%" fill="url(#dots)" />
</svg>

上面代码中,<pattern>标签将一个圆形定义为dots模式。patternUnits="userSpaceOnUse"表示<pattern>的宽度和长度是实际的像素值。然后,指定这个模式去填充下面的矩形。

14. <image>标签
<image>标签用于插入图片文件。

<svg viewBox="0 0 100 100" width="100" height="100">
  <image xlink:href="path/to/image.jpg"
    width="50%" height="50%"/>
</svg>

上面代码中,<image>xlink:href属性表示图像的来源。

15. <animate>标签
<animate>标签用于产生动画效果。

<svg width="500px" height="500px">
  <rect x="0" y="0" width="100" height="100" fill="#feac5e">
    <animate attributeName="x" from="0" to="500" dur="2s" repeatCount="indefinite" />
  </rect>
</svg>

上面代码中,矩形会不断移动,产生动画效果。

<animate>的属性含义如下。

attributeName:发生动画效果的属性名。
from:单次动画的初始值。
to:单次动画的结束值。
dur:单次动画的持续时间。
repeatCount:动画的循环模式。
可以在多个属性上面定义动画。

<animate attributeName="x" from="0" to="500" dur="2s" repeatCount="indefinite" />
<animate attributeName="width" to="500" dur="2s" repeatCount="indefinite" />

16. <animateTransform>标签
<animate>标签对 CSS 的transform属性不起作用,如果需要变形,就要使用标签。

<svg width="500px" height="500px">
  <rect x="250" y="250" width="50" height="50" fill="#4bc0c8">
    <animateTransform attributeName="transform" type="rotate" begin="0s" dur="10s" from="0 200 200" to="360 400 400" repeatCount="indefinite" />
  </rect>
</svg>

上面代码中,<animateTransform>的效果为旋转(rotate),这时from和to属性值有三个数字,第一个数字是角度值,第二个值和第三个值是旋转中心的坐标。from="0 200 200"表示开始时,角度为0,围绕(200, 200)开始旋转;to="360 400 400"表示结束时,角度为360,围绕(400, 400)旋转。

JavaScript 操作SVG

1. DOM操作
如果 SVG 代码直接写在 HTML 网页之中,它就成为网页 DOM 的一部分,可以直接用 DOM 操作。

<svg
  id="mysvg"
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 800 600"
  preserveAspectRatio="xMidYMid meet"
>
  <circle id="mycircle" cx="400" cy="300" r="50" />
<svg>

上面代码插入网页之后,就可以用 CSS 定制样式。

circle {
  stroke-width: 5;
  stroke: #f00;
  fill: #ff0;
}

circle:hover {
  stroke: #090;
  fill: #f8f8f8;
}

然后,可以用 JavaScript 代码操作 SVG。

var mycircle = document.getElementById('mycircle');

mycircle.addEventListener('click', function(e) {
  console.log('circle clicked - enlarging');
  mycircle.setAttribute('r', 60);
}, false);

上面代码指定,如果点击图形,就改写circle元素的r属性。

2. 获取 SVG DOM
使用<object><iframe><embed>标签插入 SVG 文件,可以获取 SVG DOM。

var svgObject = document.getElementById('object').contentDocument;
var svgIframe = document.getElementById('iframe').contentDocument;
var svgEmbed = document.getElementById('embed').getSVGDocument();

注意,如果使用标签插入 SVG 文件,就无法获取 SVG DOM。

3. 读取 SVG 源码
由于 SVG 文件就是一段 XML 文本,因此可以通过读取 XML 代码的方式,读取 SVG 源码。

<div id="svg-container">
  <svg
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xml:space="preserve" width="500" height="440"
  >
    <!-- svg code -->
  </svg>
</div>

使用XMLSerializer实例的serializeToString()方法,获取 SVG 元素的代码。

var svgString = new XMLSerializer()
  .serializeToString(document.querySelector('svg'));

4. SVG 图像转为 Canvas 图像
首先,需要新建一个Image对象,将 SVG 图像指定到该Image对象的src属性。

var img = new Image();
var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});

var DOMURL = self.URL || self.webkitURL || self;
var url = DOMURL.createObjectURL(svg);

img.src = url;

然后,当图像加载完成后,再将它绘制到<canvas>元素。

img.onload = function () {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
};

小结

SVG能做的远不止这些,利用SVG做的动画效果,文字效果我们以后给大家详细讲解,今天就先到这里吧。

console.log('右下角点好看呦')

###技术放肆聊QQ群:617413307 欢迎程序员朋友积极加群,共同进步
###技术放肆聊公众号,每日干货,最前沿的技术知识,扫描下方二维码关注:
技术放肆聊
####推一下自家APP
肆客足球 最新最全的足球资讯,最火爆的球迷社区,直播中超、欧洲赛事,全面丰富的数据统计,还有球星推特、Ins、社交动态,原汁原味,扫描下方二维码即可下载

分类
JavaScript

ES6十大常用特性

[toc]

以下是ES6排名前十的最佳特性列表(排名不分先后):

  • Default Parameters(默认参数) in ES6
  • Template Literals (模板文本)in ES6
  • Multi-line Strings (多行字符串)in ES6
  • Destructuring Assignment (解构赋值)in ES6
  • Enhanced Object Literals (增强的对象文本)in ES6
  • Arrow Functions (箭头函数)in ES6
  • Promises in ES6
  • Block-Scoped Constructs Let and Const(块作用域构造Let and Const)
  • Classes(类) in ES6
  • Modules(模块) in ES6

【备注 】这里只列出了10条比较常用的特性。并不是所有的浏览器都支持ES6模块,所以你需要使用一些像jspm去支持ES6模块。

1.Default Parameters(默认参数)

ES5:

var link = function (height, color, url) {  
    var height = height || 50;  
    var color = color || 'red';  
    var url = url || 'http://azat.co';  
    ...  
} 

ES6:直接写在参数里

var link = function(height = 50, color = 'red', url = 'http://azat.co') {  
  ...  
}

好处 节省了代码量。

2.Template Literals(模板对象)

在字符串里面输出变量

ES5:

var name = 'Your name is ' + first + ' ' + last + '.';  
var url = 'http://localhost:3000/api/messages/' + id;  

ES6:,使用新的语法 $ {NAME},并把它放在反引号里:

var name = 'Your name is ${first} ${last}.';
var url = 'http://loalhost:3000/api/messages/${id}';

好处: 这里的$ {NAME}直接当做字符串用,无需写加号

3.Multi-line Strings (多行字符串)

ES5:

var roadPoem = 'Then took the other, as just as fair,nt'  
    + 'And having perhaps the better claimnt'  
    + 'Because it was grassy and wanted wear,nt'  
    + 'Though as for that the passing therent'  
    + 'Had worn them really about the same,nt';  
var fourAgreements = 'You have the right to be you.n  
    You can only be you when you do your best.'; 

ES6: 反引号就可以啦!

var roadPoem = `Then took the other, as just as fair, 
    And having perhaps the better claim 
    Because it was grassy and wanted wear  
    Though as for that the passing theren 
    Had worn them really about the same,`;  
var fourAgreements = `You have the right to be you.n  
    You can only be you when you do your best.`; 

好处:直接一个反引号,将所有的字符串放进去即可,中介随意换行,好清爽!

4.Destructuring Assignment (解构赋值)

下边例子中,house 和 mouse是 key,同时 house 和 mouse 也是一个变量。

ES5:

var data = $('body').data(), // data has properties house and mouse  
    house = data.house,  
    mouse = data.mouse;  

以及在node.js中用ES5是这样:

var jsonMiddleware = require('body-parser').jsonMiddleware ;  
var body = req.body, // body has username and password  
username = body.username,  
password = body.password;  

ES6:

var {house,mouse} = $('body').data(); //we'll get house and mouse variables 
var {jsonMiddleware} = require('body-parser');
var {username,password} = req.body;

在数组中是这样的:

var [col1,col2] = $('.column'),
    [line1,line2,line3, ,line5] = file.split('n');

好处:使用{}省去了写对象的属性的步骤,当然这个{}中的变量是与对象的属性名字保持一致的情况下。

5.Enhanced Object Literals (增强的对象字面量)

使用对象文本可以做许多让人意想不到的事情!通过ES6,我们可以把ES5中的JSON变得更加接近于一个类。

下面是一个典型ES5对象文本,里面有一些方法和属性:

var serviceBase = {port: 3000, url: 'azat.co'},  
    getAccounts = function(){return [1,2,3]};  
var accountServiceES5 = {  
  port: serviceBase.port,  
  url: serviceBase.url,  
  getAccounts: getAccounts,  
  toString: function() {  
      return JSON.stringify(this.valueOf());  
  },  
  getUrl: function() {return "http://" + this.url + ':' + this.port},  
  valueOf_1_2_3: getAccounts()  
}  
如果我们想让它更有意思,我们可以用Object.create从serviceBase继承原型的方法:

var accountServiceES5ObjectCreate = Object.create(serviceBase)  
// Object.create() 方法创建一个拥有指定原型和若干个指定属性的对象。
var accountServiceES5ObjectCreate = {  
  getAccounts: getAccounts,  
  toString: function() {  
    return JSON.stringify(this.valueOf());  
  },  
  getUrl: function() {return "http://" + this.url + ':' + this.port},  
  valueOf_1_2_3: getAccounts()  
}  

ES6的对象文本中:既可以直接分配getAccounts: getAccounts,也可以只需用一个getAccounts

var serviceBase = {port: 3000, url: 'azat.co'},
getAccount = function(){return [1,2,3]};
var accountService = {
    __proto__: serviceBase, //通过proto设置属性
    getAccount, // 既可以直接分配getAccounts: getAccounts,也可以只需用一个getAccounts
    toString() { //这里将json形式改为函数形式 
        return JSON.stringify(super.valueOf()); 
        //调用super防范
    },  
    getUrl() {return "http://" + this.url + ':' + this.port},  
    [ 'valueOf_' + getAccounts().join('_') ]: getAccounts()  //使用动态key值(valueOf_1_2_3)此处将getAccounts()方法得到的数组[1,2,3]转化为字符串1_2_3
};
console.log(accountService);

好处:相当于直接将结果写进去,而不再必须 key:value

  • 将toString: function(){}这种json形式转变为 toString() {}这样的函数(类)的形式
  • 既可以直接分配getAccounts: getAccounts这样的json形式,也可以只需用一个getAccounts表达相同的意思

6.Arrow Functions in(箭头函数)

这些丰富的箭头是令人惊讶的因为它们将使许多操作变成现实,比如, 
以前我们使用闭包,this总是预期之外地产生改变,而箭头函数的迷人之处在于,现在你的this可以按照你的预期使用了,身处箭头函数里面,this还是原来的this。

ES5:

var _this = this;  
$('.btn').click(function(event){  
  _this.sendData();  
})  

ES6: 就不需要用 _this = this:

$('.btn').click((event) =>{  
  this.sendData();  
})  

再比如:

ES5:

var logUpperCase = function() {  
  var _this = this;   //this = Object {string: "ES6 ROCKS"}
  console.log('this指的是',this); //Object {string: "ES6 ROCKS"}
  console.log('_this指的是',_this);//Object {string: "ES6 ROCKS"}
  this.string = this.string.toUpperCase();  
  console.log(_this.string); //ES6 ROCKS  
  console.log(this.string);  //ES6 ROCKS
  return function () {  
    return console.log(_this.string); //ES6 ROCKS
    return console.log(_this.string); //如果return _this.string,将返回 undefined,因为

  }  
} 
logUpperCase.call({ string: 'ES6 rocks' })();

ES6:我们并不需要用_this浪费时间,现在你的this可以按照你的预期使用了,身处箭头函数里面,this还是原来的this

var logUpperCase = function() {  
  this.string = this.string.toUpperCase();//this还是原来的this  
  return () => console.log(this.string);  
}  
logUpperCase.call({ string: 'ES6 rocks' })();  

注意 只要你愿意,在ES6中=>可以混合和匹配老的函数一起使用。当在一行代码中用了箭头函数,它就变成了一个表达式。它将暗地里返回单个语句的结果。如果你超过了一行,将需要明确使用return。

ES5:

var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9'];  
var messages = ids.map(function (value) {  
  return "ID is " + value; // explicit return  
}); 

ES6:

var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9'];
var messages = ids.map((value)  => `ID is ${value}`); //implicit return 

好处: 
* 并不需要用_this浪费时间,现在你的this可以按照你的预期使用了,身处箭头函数里面,this还是原来的this。 
* => 可以代替function关键字,当在一行用了箭头函数,可以省去{},还可以省去return,它会暗地里返回的。

7.Promises

ES5:

setTimeout(function(){  
  console.log('Yay!');  
}, 1000);  

ES6: 我们可以用promise重写

var wait1000 = new Promise((resolve,reject)=> {
   setTimeout(resolve,1000);
}).then(()=> {
    console.log('Yay!'); 
});

如果我们有更多的嵌套逻辑在setTimeout()回调函数中,好处会明显一点: 
ES5:

setTimeout(function(){  
  console.log('Yay!');  
  setTimeout(function(){  
    console.log('Wheeyee!');  
  }, 1000)  
}, 1000);  

ES6: 我们可以用promise重写

var wait1000 = ()=> new Promise((resolve,reject)=>{ setTimeout(resolve,1000);});
wait1000()
    .then(function(){
        console.log('Yay!');  
        return wait1000()
    })
    .then(function(){
         console.log('Wheeyee!');  
    });

8 Block-Scoped(块作用域和构造let和const)

let是一种新的变量声明方式,它允许你把变量作用域控制在块级里面。我们用大括号定义代码块,在ES5中,块级作用域起不了任何作用:

function calculateTotalAmount (vip) {  
  var amount = 0;  
  if (vip) {  
    var amount = 1;  
  }  
  { // more crazy blocks!  
    var amount = 100;  
    {  
      var amount = 1000;  
    }  
  }    
  return amount;  
}  
console.log(calculateTotalAmount(true));  // 1000

ES6: 用let限制块级作用域

function calculateTotalAmount(vip){
    var amouont  = 0; // probably should also be let, but you can mix var and let
    if (vip) {  
        let amount = 1; // first amount is still 0  
    }   
    { // more crazy blocks!  
    let amount = 100; // first amount is still 0  
    {  
      let amount = 1000; // first amount is still 0  
    }  
  }    
  return amount;  
} 
console.log(calculateTotalAmount(true));  //0 因为块作用域中有了let。

谈到const,就更加容易了;它就是一个不变量,也是块级作用域就像let一样。

好处 : 我们用let限制块级作用域。而var是限制函数作用域。

9. Classes (类)

ES6没有用函数,而是使用原型实现类。我们创建一个类baseModel ,并且在这个类里定义了一个constructor 和一个 getName()方法:

class baseModel {  
    constructor(options, data) {// class constructor, 注意我们对options 和data使用了默认参数值。
        this.name = 'Base';  
        this.url = 'http://azat.co/api';  
        this.data = data;  
        this.options = options;  
   }  
    getName() { // class method  
        console.log(`Class name: ${this.name}`);  
    } 
    getUrl() { // class method  
         console.log(`Url: ${this.url}`);  
    }
}  

AccountModel 从类baseModel 中继承而来:

class AccountModel extends baseModel {  
    constructor(options, data) { 
    super({private: true}, ['32', '5242']); 
    this.url +='/accounts/';  
    }
    get accountsData() {
        return this.data;  
    }  
} 
// 调用
let accounts = new AccountModel(5);  
accounts.getName();  // Class name:  Base
console.log('Data is %s', accounts.accountsData); 
// Data is 32,5242 

//子类必须在constructor方法中调用super方法,否则新建实例时会报错。
//这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。
//如果不调用super方法,子类就得不到this对象。

【注意】: 
* 此处的继承中,子类必须在constructor方法中调用super方法,否则新建实例时会报错。 
* 此处的super方法继承了父类的所有方法,包括不在父类的constructor中的其他方法,当然可以改写父类方法,比如上述例子,继承了getName(),getUrl()方法,以及constructor()中的this.name等属性,同时改写了this.url,增加了accountsData,且新增的方法前边要加上get。 
* 子类调用super方法可以传入参数,对应constructor()函数的形参。

10. Modules (模块)

ES5导出:

module.exports = { port: 3000, getAccounts: function() { ... }}

ES6导出:

export var port = 3000;
export function getAccounts(url) { ...}

ES5导入:

var service = require('module.js');
console.log(service.port); // 3000

ES6导入:

我们需用import {name} from ‘my-module’语法

import {port, getAccounts} from 'module';
console.log(port); // 300

或者ES6整体导入:

import * as service from 'module';
console.log(service.port); // 3000

这里还有许多ES6的其它特性你可能会使用到,排名不分先后:

  • 全新的Math, Number, String, Array 和 Object 方法
  • 二进制和八进制数据类型
  • 默认参数不定参数扩展运算符
  • Symbols符号
  • tail调用
  • Generators (生成器)
  • New data structures like Map and Set(新的数据构造对像MAP和set)
分类
HTML/CSS

CSS 解析原理

>作为前端,我们每天都在与CSS打交道,那么CSS的原理是什么呢?

#### 一、浏览器渲染

开篇,我们还是不厌其烦的回顾一下浏览器的渲染过程,先上图:
![webkit render](https://media.feleti.cn/parse2.png-blog.png)
正如上图所展示的,我们浏览器渲染过程分为了两条主线:
其一,HTML Parser 生成的 DOM 树;
其二,CSS Parser 生成的 Style Rules ;

在这之后,DOM 树与 Style Rules 会生成一个新的对象,也就是我们常说的 Render Tree 渲染树,结合 Layout 绘制在屏幕上,从而展现出来。

>本文的重点也就集中在第二条分支上,我们来探究一下 CSS 解析原理。

#### 二、Webkit CSS 解析器

浏览器 CSS 模块负责 CSS 脚本解析,并为每个 Element 计算出样式。CSS 模块虽小,但是计算量大,设计不好往往成为浏览器性能的瓶颈。

CSS 模块在实现上有几个特点:CSS 对象众多(颗粒小而多),计算频繁(为每个 Element 计算样式)。这些特性决定了 webkit 在实现 CSS 引擎上采取的设计,算法。如何高效的计算样式是浏览器内核的重点也是难点。

先来看一张图:
![webkit css parse](https://media.feleti.cn/parse1.png-blog.png)

>Webkit 使用 Flex 和 Bison 解析生成器从 CSS 语法文件中自动生成解析器。

它们都是将每个 CSS 文件解析为样式表对象,每个对象包含 CSS 规则,CSS 规则对象包含选择器和声明对象,以及其他一些符合 CSS 语法的对象,下图可能会比较明了:

![css rule](https://media.feleti.cn/parse3.png-blog.png)

Webkit 使用了自动代码生成工具生成了相应的代码,也就是说`词法分析`和`语法分析`这部分代码是自动生成的,而 Webkit 中实现的 CallBack 函数就是在 CSSParser 中。

CSS 的一些解析功能的入口也在此处,它们会调用 lex , parse 等生成代码。相对的,生成代码中需要的 CallBack 也需要在这里实现。

举例来说,现在我们来看其中一个回调函数的实现,createStyleRule(),该函数将在一般性的规则需要被建立的时候调用,代码如下:

```css
CSSRule* CSSParser::createStyleRule(CSSSelector* selector)
{
CSSStyleRule* rule = 0;
if (selector) {
rule = new CSSStyleRule(styleElement);
m_parsedStyleObjects.append(rule);
rule->setSelector(sinkFloatingSelector(selector));
rule->setDeclaration(new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedProperties));
}
clearProperties();
return rule;
}
```

从该函数的实现可以很清楚的看到,解析器达到某条件需要创建一个 CSSStyleRule 的时候将调用该函数,该函数的功能是创建一个 CSSStyleRule ,并将其添加已解析的样式对象列表 `m_parsedStyleObjects` 中去,这里的对象就是指的 Rule 。

那么如此一来,经过这样一番解析后,作为输入的样式表中的所有 Style Rule 将被转化为 Webkit 的内部模型对象 CSSStyleRule 对象,存储在 `m_parsedStyleObjects` 中,它是一个 `Vector`。

>但是我们解析所要的结果是什么?

1.通过调用 CSSStyleSheet 的 parseString 函数,将上述 CSS 解析过程启动,解析完一遍后,把 Rule 都存储在对应的 CSSStyleSheet 对象中;

2.由于目前规则依然是不易于处理的,还需要将之转换成 CSSRuleSet。也就是将所有的纯样式规则存储在对应的集合当中,这种集合的抽象就是 CSSRuleSet;

3.CSSRuleSet 提供了一个 addRulesFromSheet 方法,能将 CSSStyleSheet 中的 rule 转换为 CSSRuleSet 中的 rule ;

4.基于这些个 CSSRuleSet 来决定每个页面中的元素的样式;

#### 三、CSS 选择器解析顺序

可能很多同学都知道排版引擎解析 CSS 选择器时是`从右往左`解析,这是为什么呢?

1.HTML 经过解析生成 DOM Tree(这个我们比较熟悉);而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。Render Tree 中的元素(WebKit 中称为「renderers」,Firefox 下为「frames」)与 DOM 元素相对应,但非一一对应:一个 DOM 元素可能会对应多个 renderer,如文本折行后,不同的「行」会成为 render tree 种不同的 renderer。也有的 DOM 元素被 Render Tree 完全无视,比如 display:none 的元素。

2.在建立 Render Tree 时(WebKit 中的「Attachment」过程),浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果(Style Rules)来确定生成怎样的 renderer。对于每个 DOM 元素,必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的,在遍历 DOM Tree 时,从 Style Rules 中去寻找对应的 selector。

3.因为所有样式规则可能数量很大,而且绝大多数不会匹配到当前的 DOM 元素(因为数量很大所以一般会建立规则索引树),所以有一个快速的方法来判断「这个 selector 不匹配当前元素」就是极其重要的。

4.如果正向解析,例如「div div p em」,我们首先就要检查当前元素到 html 的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第一个 div,回溯若干次才能确定匹配与否,效率很低。

对于上述描述,我们先有个大概的认知。接下来我们来看这样一个例子,[参考地址](http://www.imooc.com/code/4570):

```html

span> 111 span>

span> 222 span>

333

444

```

CSS 选择器:

```css
div > div.jartto p span.yellow{
color:yellow;
}
```

对于上述例子,如果按从左到右的方式进行查找:
1.先找到所有 div 节点;
2.在 div 节点内找到所有的子 div ,并且是 class = “jartto”;
3.然后再依次匹配 p span.yellow 等情况;
4.遇到不匹配的情况,就必须回溯到一开始搜索的 div 或者 p 节点,然后去搜索下个节点,重复这样的过程。

>这样的搜索过程对于一个只是匹配很少节点的选择器来说,效率是极低的,因为我们花费了大量的时间在回溯匹配不符合规则的节点。

如果换个思路,我们一开始过滤出跟目标节点最符合的集合出来,再在这个集合进行搜索,大大降低了搜索空间。来看看从右到左来解析选择器:
1.首先就查找到 的元素;
2.紧接着我们判断这些节点中的前兄弟节点是否符合 P 这个规则,这样就又减少了集合的元素,只有符合当前的子规则才会匹配再上一条子规则。

>结果显而易见了,众所周知,在 DOM 树中一个元素可能有若干子元素,如果每一个都去判断一下显然性能太差。而一个子元素只有一个父元素,所以找起来非常方便。

试想一下,如果采用`从左至右`的方式读取 CSS 规则,那么大多数规则读到最后(最右)才会发现是不匹配的,这样会做费时耗能,最后有很多都是无用的;而如果采取`从右向左`的方式,那么只要发现最右边选择器不匹配,就可以直接舍弃了,避免了许多无效匹配。

>浏览器 CSS 匹配核心算法的规则是以`从右向左`方式匹配节点的。这样做是为了减少无效匹配次数,从而匹配快、性能更优。

#### 四、CSS 语法解析过程

[CSS 样式表解析过程](http://blog.csdn.net/shuimuniao/article/details/8601588)中讲解的很细致,这里我们只看 CSS 语法解释器,大致过程如下:
1.先创建 CSSStyleSheet 对象。将 CSSStyleSheet 对象的指针存储到 CSSParser 对象中。
2.CSSParser 识别出一个 simple-selector ,形如 “div” 或者 “.class”。创建一个 CSSParserSelector 对象。
3.CSSParser 识别出一个关系符和另一个 simple-selecotr ,那么修改之前创建的 simple-selecotr, 创建组合关系符。
4.循环第3步直至碰到逗号或者左大括号。
5.如果碰到逗号,那么取出 CSSParser 的 reuse vector,然后将堆栈尾部的 CSSParserSelector 对象弹出存入 Vecotr 中,最后跳转至第2步。如果碰到左大括号,那么跳转至第6步。
6.识别属性名称,将属性名称的 hash 值压入解释器堆栈。
7.识别属性值,创建 CSSParserValue 对象,并将 CSSParserValue 对象存入解释器堆栈。
8.将属性名称和属性值弹出栈,创建 CSSProperty 对象。并将 CSSProperty 对象存入 CSSParser 成员变量m_parsedProperties 中。
9.如果识别处属性名称,那么转至第6步。如果识别右大括号,那么转至第10步。
10.将 reuse vector 从堆栈中弹出,并创建 CSSStyleRule 对象。CSSStyleRule 对象的选择符就是 reuse vector, 样式值就是 CSSParser 的成员变量 m_parsedProperties 。
11.把 CSSStyleRule 添加到 CSSStyleSheet 中。
12.清空 CSSParser 内部缓存结果。
13.如果没有内容了,那么结束。否则跳转值第2步。

#### 五、内联样式如何解析?

通过上文的了解,我们知道,当 CSS Parser 解析完 CSS 脚本后,会生成 CSSStyleSheetList ,他保存在Document 对象上。为了更快的计算样式,必须对这些 CSSStyleSheetList 进行重新组织。

计算样式就是从 CSSStyleSheetList 中找出所有匹配相应元素的 property-value 对。匹配会通过CSSSelector 来验证,同时需要满足层叠规则。

将所有的 declaration 中的 property 组织成一个大的数组。数组中的每一项纪录了这个 property 的selector,property 的值,权重(层叠规则)。

可能类似如下的表现:

```css
p > a {
color : red;
background-color:black;
}
a {
color : yellow
}
div {
margin : 1px;
}
```

重新组织之后的数组数据为(weight我只是表示了他们之间的相对大小,并非实际值。)

| selector | selector | weight |
| --- | --- | --- |
| a | color:yellow| 1 |
| p > a | color:red| 2 |
| p > a | background-color:black | 2 |
| div | margin:1px | 3 |

好了,到这里,我们来解决上述问题:
首先,要明确,内敛样式只是 CSS 三种加载方式之一;
其次,浏览器解析分为两个分支,HTML Parser 和 CSS Parser,两个 Parser 各司其职,各尽其责;
最后,不同的 CSS 加载方式产生的 Style rule ,通过权重来确定谁覆盖谁;

>到这里就不难理解了,对浏览器来说,內联样式与其他的加载样式方式唯一的区别就是权重不同。

深入了解,请阅读[Webkit CSS引擎分析](http://blog.csdn.net/scusyq/article/details/7059063)

#### 六、何谓 computedStyle ?

到这里,你以为完了?Too young too simple, sometimes naive!

浏览器还有一个非常棒的策略,在特定情况下,浏览器会共享 computedStyle,网页中能共享的标签非常多,所以能极大的提升执行效率!如果能共享,那就不需要执行匹配算法了,执行效率自然非常高。

也就是说:如果两个或多个 element 的 computedStyle 不通过计算可以确认他们相等,那么这些 computedStyle 相等的 elements 只会计算一次样式,其余的仅仅共享该 computedStyle 。

那么有哪些规则会共享 computedStyle 呢?

* 该共享的element不能有id属性且CSS中还有该id的StyleRule.哪怕该StyleRule与Element不匹配。
* tagName和class属性必须一样;
* mappedAttribute必须相等;
* 不能使用sibling selector,譬如:first-child, :last-selector, + selector;
* 不能有style属性。哪怕style属性相等,他们也不共享;

```css
span>p style="color:red">paragraph1span>p>
span>p style="color:red">paragraph2span>p>
```

当然,知道了共享 computedStyle 的规则,那么反面我们也就了解了:不会共享 computedStyle 的规则,这里就不展开讨论了。

深入了解,请参考:[Webkit CSS 引擎分析 - 高效执行的 CSS 脚本](http://blog.csdn.net/scusyq/article/details/7059063)

#### 七、眼见为实

![parse speed](https://media.feleti.cn/parse4.png-blog.png)
如上图,我们可以看到不同的 CSS 选择器的组合,解析速度也会受到不同的影响,你还会轻视 CSS 解析原理吗?

感兴趣的同学可以参考这里:[speed/validity selectors test for frameworks](http://test.veryos.com/selector/slickspeed/index.html)

#### 八、有何收获?

1.使用 id selector 非常的高效。在使用 id selector 的时候需要注意一点:因为 id 是唯一的,所以不需要既指定 id 又指定 tagName:

```css
Bad
p#id1 {color:red;}
Good
#id1 {color:red;}
```

>当然,你非要这么写也没有什么问题,但这会增加 CSS 编译与解析时间,实在是不值当。

2.避免深层次的 node ,譬如:

```css
Bad
div > div > div > p {color:red;}
Good
p-class{color:red;}
```

3.慎用 ChildSelector ;

4.不到万不得已,不要使用 attribute selector,如:p[att1=”val1”]。这样的匹配非常慢。更不要这样写:p[id=”id1”]。这样将 id selector 退化成 attribute selector。

```css

Bad

p[id="id1"]{color:red;}

p[class="class1"]{color:red;}

Good

#id1{color:red;}

.class1{color:red;}

```

5.理解依赖继承,如果某些属性可以继承,那么自然没有必要在写一遍;
6.规范真的很重要,不仅仅是可读性,也许会影响你的页面性能。这里推荐一个 [CSS 规范](http://nec.netease.com/standard/css-sort.html),可以参考一下。

#### 九、总结

>“学会使用”永远都是最基本的标准,但是懂得原理,你才能触类旁通,超越自我。

分类
读书笔记

流浪地球观后感

一部集齐吴京、吴孟达、雷佳音,改编自刘慈欣原著小说的中国科幻电影是怎样的?

临近下班,公司产品经理找到我,还以为他要改需求,四十米大刀都准备好了,结果他递过来的却是一张电影票,《流浪地球》?对于我这个好久都未关注电影的程序员来说,还真蛮陌生的。但当晚上走出电影院的时候才不得不说一句:真香。

IMG_0416

到了电影院,我才开始去了解这部电影。

《流浪地球》由导演郭帆执导,改编自刘慈欣的同名科幻小说。后者担任本片监制。

看过《三体》的人都应该知道刘慈欣的科幻小说质量上是有保障的,这也让我的期待值一下提高了很多。

再看一下演员阵容:

吴京、雷佳音、吴孟达这三位演技自不必说,都是票房的保证,但看过之后才发现,他们也的确是来保证票房的,因为主演的确是那些小肆之前不是很熟悉的演员。

但这并不妨碍它成为中国科幻电影的里程碑。

电影《流浪地球》延续了小说中的世界观,故事背景基本一致。

太阳正在急速老化,不断膨胀。未来,地球会被太阳吞噬,人类可居住的类地行星都会被毁灭。

无法在太阳系继续生存下去的人类,唯一的生路就是向外太空逃亡。

《流浪地球》便是讲述这样一场盛大的逃亡。在小说中,人类的逃亡分为五步,共计2500年,分别是刹车时代、逃逸时代、流浪时代(加速)、流浪时代(减速)、新太阳时代。

影片开始于刹车时代,在亚洲、美洲的大陆之上,已经布满了一万两千台行星发动机。

在赤道转向发动机的作用下,地球已经停止自传,地表环境已经达到零下七八十度,人类被迫迁移到地下城居住,此时地球总人口数已经锐减到35亿人。

但在地球接近木星时,由于木星引力影响,行星发动机组突然发生故障,地球面临撞击木星的危险。

屈楚萧饰演的刘洁一直想离开地下城去地表世界,于是在春节的这天带着15岁的妹妹坑了雷大头两套衣服被送到了地表--冰封后的北京。

之后意外参与进了运送火石重启行星发动机的任务。

为了不剧透,这里只说说电影观感吧。

首先,影片完成度很高,非常均衡,没有犯任何一个大错误——包括剧本、特效、剪辑、表演乃至美术设计、配乐等等!

作为填补近二十多年来科幻电影空白,或者说重新开创科幻类型片的先驱者来说,这是相当难得的。

影片开始的十五分钟左右还让人有点小尴尬,入戏不是那么快,毕竟科幻类型片的“水土不服”已经是多年的问题了。

但很快,这种略微的别扭感就消失了,你很容易投入到影片建构的环境和故事中去。

自然,故事已经是在刘慈欣《流浪地球》的小说基础上重新创作的了。

这种电影化的改编我认为一点问题都没有,很多小说都无法按照原来的故事呈现在大银幕上。

我认为,原著中的精神内核是体现在了电影当中的,包括理想主义、英雄主义、巨大灾难带来的生存困境、人文主义与生存间的矛盾等等。电影版把类型定位在了灾难/科幻方面,我觉得是种聪明的选择。

特效方面,第一感觉是特效镜头数量真多,从头到尾几乎都是蓝幕+CG或实景+CG。特效质量还是很OK的,达到了国内上乘水准,与当前北美的准一线商业大片不相上下,但尚不足以和北美超级大片媲美——能做到让观众全情投入,感同身受已经很棒了。短期内不能期望国内大制作拿出自己独特的特效技术来,毕竟在顶尖特效的研发方面我们还有不小的差距。

影片中出现了流浪太空期间破败的地面城市,包括北京和上海,期间各种地标建筑相当抢眼。这绝对是科幻电影/灾难片在国内的一次突破!以前认为绝对不能写、不能拍的禁忌被打破,这也是文化自信的一种表现嘛!

角色方面,几位年轻演员属于很好地完成了任务,不过不失已属难得。最出彩的还是吴孟达和吴京,表演有力度,无论任何情境都能让观众觉得真实可信。特别欣赏吴孟达在片中的演出,值得将来给一尊男配角奖!

主题方面野心不大,稳着来看来是导演的追求。我是不太容易被打动落泪的观众,到影片最后一幕时,确实也感到了影片的情感冲击力。以家庭和亲情为切入点,看来是最能打动当下国内观众的。

不要急着说和国外的科幻电影经典比,这首先是非常合格,比较优秀的科幻灾难片,一部工业水准、编剧、导演全部在线的华语电影——已经相当难得了!

说点不足之处吧,小问题其实还不少的。

剧本方面还是有很多可以打磨的地方,影片中大部分时候都是群戏,但演员们能出彩并让观众分清谁是谁的机会并不多(这点上跟红海行动有相似的问题)。

有个程序员的角色在结尾忽然大放异彩,但好几位戏份也不少的配角还是太功能性了。

我们产品经理还转头问了我一句:“你们程序员这么牛逼吗?”嗯,非常受用。

但是本片的“女主角”,全程几乎都在打酱油,只有结尾一场戏是给足她表演戏份的,这个设定还是让人觉得有点可惜——如果不是为了这一场戏,把她角色删掉一点不影响剧情……“男主角”刘启与父亲的戏,特别是他们之间的矛盾缺乏铺垫,导致结尾的感情戏有点不够给力,没烘托到顶点。

此外,结尾一幕似乎有点过长了,一个灾难接着一个灾难,特效方面又有些雷同,无外乎就是各种落石崩塌,稍微有点容易疲倦。

如果中间再加点抒情段落甚至一些空镜头,也许会让影片节奏更舒缓,情绪更饱满。

还有就是,吴京结尾的一个选择,从剧作角度讲没有问题,但从故事本身看,可能是有争议性的,这种争议在影片中没有做挖掘,也是有点遗憾的。

自然,作为一部科幻灾难片,求全责备本来就很难!只是希望能把这种宏大背景下的故事稍微表现出一点深度。

最后,我觉得最大最大的遗憾,是没有把《流浪地球》小说中那段憾人心弦的诗篇放在电影中——哪怕是字幕呈现一下呢。

我知道已被忘却
流浪的航程太长太长
但那一时刻要叫我一声啊
当东方再次出现霞光
我知道已被忘却
启航的时代太远太远
但那一时刻要叫我一声啊
当人类又看到了蓝天
我知道已被忘却
太阳系的往事太久太久
但那一时刻要叫我一声啊
当鲜花重新挂上枝头
……
每当听到这首歌,一股暖流就涌进我这年迈僵硬的身躯,我干涸的老眼又湿润了。
我好像看到半人马座三颗金色的太阳在地平线上依次升起,万物沐浴在它温暖的光芒中。
固态的空气融化了,变成了碧蓝的天。两千多年前的种子从解冻的土层中复苏,大地绿了。
我看到我的第一百代孙子孙女们在绿色的草原上欢笑,草原上有清澈的小溪,溪中有银色的小鱼……我看到了加代子,她从绿色的大地上向我跑来,年轻美丽,像个天使……
啊,地球,我的流浪地球……

最后,看完本片你一定会记住片里的这个梗:

北京交通委提醒您“道路千万条 ,安全第一条 ,行车不规范,亲人两行泪”

分类
读书笔记

《小萝莉的猴神大叔》观后感

阿米尔·汗曾这样评价《小萝莉的猴神大叔》:这部电影应该永远不下映,我还要再看至少十遍。

《小萝莉的猴神大叔》导演萨尔曼·汗是阿米尔·汗的好友,正是看到米叔的电影在中国受欢迎,才决定把《小萝莉的猴神大叔》带到中国。

豆瓣8.6分,好于96%的喜剧片,好于92%的剧情片。

电影讲述的印度男人帕万帮助巴基斯坦哑女沙希达与父母重聚的故事。这一路是历经坎坷,千辛万苦,但是帕万至始至终也没有放弃让沙希达与家人团聚的心愿。

萝莉和大叔是电影里常见的搭配,比如《这个杀手不太冷》《孤胆特工》《怒火救援》《大地惊雷》等等。

萝莉的稚嫩、大叔的成熟形成鲜明的对比,营造强烈的戏剧性。

稚嫩会创造趣味,成熟会带来温暖,因此这类电影通常是又有趣又温暖。

《小萝莉的猴神大叔》趣味十足,有萝莉的地方就有乐趣。

电影中反应的宗教之间的差异和冲突实际上有历史原因可寻,印度83%的人为印度教徒,13%则为穆斯林。印、穆不和,其源出于英国人“分而治之”的殖民政策逐步演变蔓延的后果。

1947年,印、巴分治,触发两大宗教的流血冲突。几十年间暴乱冲突不断,宗教冲突的残暴性和非人道性实在使人难以理解,原本引人向善的宗教却让“恨”的种子生根发芽。

宗教对立冲突是由于相互憎恨、抱怨和怀疑的情绪意识已深深埋根于印度教徒与穆斯林之中,对立宗教间的偏执顽固性和骄傲自大,自以为本身的宗教比另一种宗教更为优越,从而使教徒之间嫌隙隔阂非常深。

小萝莉被隔壁穆斯林邻居家煮肉的香味吸引过去大快朵颐的时候,大叔还在安慰自己,她不是婆罗门一定是刹帝利(印度教将人分为四个种姓:婆罗门、刹帝利、吠舍、首陀罗,婆罗门地位最高,其余种姓的社会地位依次降低,种姓制度在现今依旧盛行,人的高低贵贱就以种姓区分)。

当大叔怀着忐忑的心进入清真寺看到小萝莉虔诚的打坐念经的时候肯定差点晕过去,万万没想到萝莉根本不是印度教徒而是穆斯林。

电影中有两次“报地名”和“报站名”的桥段,一次是大巴乘客为了猜测沙希达的家乡,把自己知道的地名报出来;一次是巴基斯坦司机不断报站名,看有没有沙希达的家乡。

报地名和报站名的做法看似傻乎乎的,但就是这么傻乎乎的做法帮助沙希达找到了家乡。

这两个片段,第一次看只是觉得有趣,第二次看非常治愈。

帕万也是“傻乎乎”的,他“傻”到不会撒谎,他“傻”到试图用真诚打动世俗之人。

在这个说假话能骗取同情,发假新闻能获得流量的时代,诚实更显得难能可贵。

帕万懂世故而不世故,才是最善良的成熟。

帕万这种傻傻的信念,这才是真正的智慧。

因为罗曼·罗兰说过:世界上只有一种真正的英雄主义,那就是在认清生活真相之后依然热爱生活。

而帕万拥有的无所畏惧、勇往直前的执念,也使无数观众落泪。

其次,本片也非常温暖。

帕万身为印度人,亲自把身为巴基斯坦人的沙希达送回家,本身就是一件非常感动的事情。

当帕万把他送沙希达回家的故事告诉大巴售票员的时候,我们本以为售票员会报警,结果帕万得到了褒赞:我希望我们两国都能有更多像你这样的人。

就像电影中的记者说的:警察最多帮沙希达找一两天,然后他们就会把她送去孤儿院,只有你会倾尽全力地为沙希达找到家。

帕万送沙希达回家的做法,表面上不太现实,其实完全符合逻辑。

他把沙希达送到警察局,警察局不收留她;他去巴基斯坦大使馆求助,却遭到拒绝;他让旅行社送沙希达回家,旅行社把沙希达卖了……

他屡屡碰壁,经历重重困难,遭受万千磨难。

于是,亲自送沙希达回家,成了必然的选择。

即便是做了这样的决定,但这仍然是一个看似无法完成的任务。好在帕万拥有坚定的信念,和无疆的大爱。

跨越国境之后,帕万没有偷偷离开,而是征求边防军的同意。

他遭到拒绝,之后又遭到毒打,甚至边防军扬言要击毙他,帕万始终没有屈服。

正是他的诚实、真心、对沙希达的爱,感化了边防军。

无独有偶,记者与帕万化敌为友,大巴售票员说“希望有更多帕万这样的人”,清真寺阿訇(相当于神父)帮助帕万躲过警方追查,一方面是因为他们善良,是他们爱沙希达;另一方面是他们被帕万对沙希达的爱所感动。

换言之,原本帕万是没有丝毫可能将沙希达送回家的,是帕万对沙希达的爱,以及人们对沙希达的爱,克服了一切困难,排除了一切障碍。

当帕万要再次穿越国境、回到印度的时候,遭到巴基斯坦的百般阻挠。

在万千百姓的支持下,边防军大开国门,放帕万回家。

帕万能得到百姓和边防军的支持,是因为他得到了民心。

而他之所以得到民心,也是因为他真诚、博爱、拥有一颗赤子之心。他没有把沙希达当成巴基斯坦人,或者仇人,而是当做一个普通的小女孩。

看来,“只要人人都献出一点爱,世界将变成美好的人间”并非意淫,心诚则灵。

正所谓“精诚所至,金石为开”,是帕万心中的无疆大爱,和他的身体力行、不懈努力帮助他成功送沙希达回家。

就像记者说的:帕万来到这里的目的只有一个,那就是爱。我们两国的孩子,要在爱的滋润下长大,而不是仇恨。

爱能让军人违抗命令,能让阿訇、记者、售票员与巴基斯坦为敌……爱能战胜一切。

故事的最后,沙希达看着帕万的背影,终于开口喊出来“叔叔”,也是出于爱。

没错,妈妈带沙希达去了一次神殿,沙希达没能开口说话;帕万带沙希达第二次去神殿,沙希达也没能开口说话。

让沙希达开口说话的,不是神殿,不是神灵,而是爱。

听起来很假,但事实就是如此。

“用爱发电”的剧情看似老套,但你完整看完电影就会感动得热泪盈眶,毕竟电影是剧本、表演、剪辑、摄影等众多元素构成的,绝不是“用爱发电”四个字能概括的。

就像《泰坦尼克号》也绝不是“富家女和穷小子的爱情”所能概括的。

《小萝莉的猴神大叔》像一个童话故事,同时也有黑暗的桥段。

比如旅行社卖了沙希达,比如巴基斯坦将帕万关起来毒打。

但并不是所有人都那么坏。

岳父第一次看见帕万,本以为岳父会反对这门亲事,但他给帕万提供了机会。

帕万越过边境的时候,本以为边防军会枪毙他,边防军却放走了他;

本以为大巴售票员会报警,他却帮助帕万等人躲避搜查。

本以为军人会执行命令,他却打开了国门……

本以为清真寺阿訇会拒绝帕万进入,阿訇却说:这里欢迎所有人,所以我从不锁门。

我们总是看到一两件黑暗的事情,就以为全世界都是黑暗的。

其实,这个世界没你想得那么好,也没你想得那么坏。

这些意料之外的惊喜、情理之中的转折,非常治愈,给观众带来了美好和希望。

帕万坚持不撒谎,因为他觉得撒谎会让事情变得更坏。但正是他教记者撒谎,说他们正启程去戈杰拉,才给他们送沙希达回家提供了充足时间。

这个世界不一定是对的,撒谎不一定是对的,凡事都不一定是对的,只有“爱”永远都是对的。

我们被萝莉沙希达的可爱、机灵、乖巧、稚嫩所打动,又被大叔帕万的信念、毅力、勇气、坚持所折服。

萝莉的柔,可以把人萌哭;大叔的刚,让人感到无比贴心。刚柔并济,恰到好处。

《小萝莉的猴神大叔》充满趣味和幽默,又非常温暖和治愈,可以说是感人肺腑,笑中带泪。

看完电影,我感觉整个世界都变得美好了。

分类
JavaScript

深入理解JavaScript系列(6):S.O.L.I.D五大原则之单一职责SRP

# 前言

S.O.L.I.D五大原则,用来更好地进行面向对象编程,五大原则分别是:

>1. The Single Responsibility Principle(单一职责SRP)
2. The Open/Closed Principle(开闭原则OCP)
3. The Liskov Substitution Principle(里氏替换原则LSP)
4. The Interface Segregation Principle(接口分离原则ISP)
5. The Dependency Inversion Principle(依赖反转原则DIP)

五大原则,我相信已经被讨论烂了,尤其是C#的实现,但是相对于JavaScript这种以原型为base的动态类型语言来说还为数不多,该系列将分5篇文章以JavaScript编程语言为基础来展示五大原则的应用。 OK,开始我们的第一篇:单一职责。

>英文原文:http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/

# 单一职责

单一职责的描述如下:

>A class should have only one reason to change
类发生更改的原因应该只有一个

一个类(JavaScript下应该是一个对象)应该有一组紧密相关的行为的意思是什么?遵守单一职责的好处是可以让我们很容易地来维护这个对象,当一个对象封装了很多职责的话,一旦一个职责需要修改,势必会影响该对象想的其它职责代码。通过解耦可以让每个职责工更加有弹性地变化。

不过,我们如何知道一个对象的多个行为构造多个职责还是单个职责?我们可以通过参考[Object Design: Roles, Responsibilies, and Collaborations](http://www.amazon.com/Object-Design-Roles-Responsibilities-Collaborations/dp/0201379430)一书提出的Role Stereotypes概念来决定,该书提出了如下Role Stereotypes来区分职责:

>1. Information holder – 该对象设计为存储对象并提供对象信息给其它对象。
2. Structurer – 该对象设计为维护对象和信息之间的关系
3. Service provider – 该对象设计为处理工作并提供服务给其它对象
4. Controller – 该对象设计为控制决策一系列负责的任务处理
5. Coordinator – 该对象不做任何决策处理工作,只是delegate工作到其它对象上
6. Interfacer – 该对象设计为在系统的各个部分转化信息(或请求)

一旦你知道了这些概念,那就狠容易知道你的代码到底是多职责还是单一职责了。

# 实例代码

该实例代码演示的是将商品添加到购物车,代码非常糟糕,代码如下:

```js
function Product(id, description) {
this.getId = function () {
return id;
};
this.getDescription = function () {
return description;
};
}

function Cart(eventAggregator) {
var items = [];

this.addItem = function (item) {
items.push(item);
};
}

(function () {
var products = [new Product(1, "Star Wars Lego Ship"),
new Product(2, "Barbie Doll"),
new Product(3, "Remote Control Airplane")],
cart = new Cart();

function addToCart() {
var productId = $(this).attr('id');
var product = $.grep(products, function (x) {
return x.getId() == productId;
})[0];
cart.addItem(product);

var newItem = $('

  • ').html(product.getDescription()).attr('id-cart', product.getId()).appendTo("#cart");
    }

    products.forEach(function (product) {
    var newItem = $('

  • ').html(product.getDescription())
    .attr('id', product.getId())
    .dblclick(addToCart)
    .appendTo("#products");
    });
    })();
    ```

    该代码声明了2个function分别用来描述product和cart,而匿名函数的职责是更新屏幕和用户交互,这还不是一个很复杂的例子,但匿名函数里却包含了很多不相关的职责,让我们来看看到底有多少职责:

    1. 首先,有product的集合的声明
    2. 其次,有一个将product集合绑定到#product元素的代码,而且还附件了一个添加到购物车的事件处理
    3. 第三,有Cart购物车的展示功能
    4. 第四,有添加product item到购物车并显示的功能

    # 重构代码

    让我们来分解一下,以便代码各自存放到各自的对象里,为此,我们参考了martinfowler的事件聚合([Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html))理论在处理代码以便各对象之间进行通信。

    首先我们先来实现事件聚合的功能,该功能分为2部分,1个是Event,用于Handler回调的代码,1个是EventAggregator用来订阅和发布Event,代码如下:

    ```js
    function Event(name) {
    var handlers = [];

    this.getName = function () {
    return name;
    };

    this.addHandler = function (handler) {
    handlers.push(handler);
    };

    this.removeHandler = function (handler) {
    for (var i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { handlers.splice(i, 1); break; } } }; this.fire = function (eventArgs) { handlers.forEach(function (h) { h(eventArgs); }); }; } function EventAggregator() { var events = []; function getEvent(eventName) { return $.grep(events, function (event) { return event.getName() === eventName; })[0]; } this.publish = function (eventName, eventArgs) { var event = getEvent(eventName); if (!event) { event = new Event(eventName); events.push(event); } event.fire(eventArgs); }; this.subscribe = function (eventName, handler) { var event = getEvent(eventName); if (!event) { event = new Event(eventName); events.push(event); } event.addHandler(handler); }; } ``` 然后,我们来声明Product对象,代码如下: ```js function Product(id, description) { this.getId = function () { return id; }; this.getDescription = function () { return description; }; } ``` 接着来声明Cart对象,该对象的addItem的function里我们要触发发布一个事件itemAdded,然后将item作为参数传进去。 ```js function Cart(eventAggregator) { var items = []; this.addItem = function (item) { items.push(item); eventAggregator.publish("itemAdded", item); }; } ``` CartController主要是接受cart对象和事件聚合器,通过订阅itemAdded来增加一个li元素节点,通过订阅productSelected事件来添加*product。* ```js function CartController(cart, eventAggregator) { eventAggregator.subscribe("itemAdded", function (eventArgs) { var newItem = $('

  • ').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
    });

    eventAggregator.subscribe("productSelected", function (eventArgs) {
    cart.addItem(eventArgs.product);
    });
    }
    ```

    Repository的目的是为了获取数据(可以从ajax里获取),然后暴露get数据的方法。

    ```js
    function ProductRepository() {
    var products = [new Product(1, "Star Wars Lego Ship"),
    new Product(2, "Barbie Doll"),
    new Product(3, "Remote Control Airplane")];

    this.getProducts = function () {
    return products;
    }
    }
    ```

    ProductController里定义了一个onProductSelect方法,主要是发布触发productSelected事件,forEach主要是用于绑定数据到产品列表上,代码如下:

    ```js
    function ProductController(eventAggregator, productRepository) {
    var products = productRepository.getProducts();

    function onProductSelected() {
    var productId = $(this).attr('id');
    var product = $.grep(products, function (x) {
    return x.getId() == productId;
    })[0];
    eventAggregator.publish("productSelected", {
    product: product
    });
    }

    products.forEach(function (product) {
    var newItem = $('

  • ').html(product.getDescription())
    .attr('id', product.getId())
    .dblclick(onProductSelected)
    .appendTo("#products");
    });
    }
    ```

    最后声明匿名函数(需要确保HTML都加载完了才能执行这段代码,比如放在jQuery的ready方法里):

    ```js
    (function () {
    var eventAggregator = new EventAggregator(),
    cart = new Cart(eventAggregator),
    cartController = new CartController(cart, eventAggregator),
    productRepository = new ProductRepository(),
    productController = new ProductController(eventAggregator, productRepository);
    })();
    ```

    可以看到匿名函数的代码减少了很多,主要是一个对象的实例化代码,代码里我们介绍了Controller的概念,他接受信息然后传递到action,我们也介绍了Repository的概念,主要是用来处理product的展示,重构的结果就是写了一大堆的对象声明,但是好处是每个对象有了自己明确的职责,该展示数据的展示数据,改处理集合的处理集合,这样耦合度就非常低了。

    ```js
    //最终代码

    function Event(name) {
    var handlers = [];

    this.getName = function () {
    return name;
    };

    this.addHandler = function (handler) {
    handlers.push(handler);
    };

    this.removeHandler = function (handler) {
    for (var i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { handlers.splice(i, 1); break; } } }; this.fire = function (eventArgs) { handlers.forEach(function (h) { h(eventArgs); }); }; } function EventAggregator() { var events = []; function getEvent(eventName) { return $.grep(events, function (event) { return event.getName() === eventName; })[0]; } this.publish = function (eventName, eventArgs) { var event = getEvent(eventName); if (!event) { event = new Event(eventName); events.push(event); } event.fire(eventArgs); }; this.subscribe = function (eventName, handler) { var event = getEvent(eventName); if (!event) { event = new Event(eventName); events.push(event); } event.addHandler(handler); }; } function Product(id, description) { this.getId = function () { return id; }; this.getDescription = function () { return description; }; } function Cart(eventAggregator) { var items = []; this.addItem = function (item) { items.push(item); eventAggregator.publish("itemAdded", item); }; } function CartController(cart, eventAggregator) { eventAggregator.subscribe("itemAdded", function (eventArgs) { var newItem = $('

  • ').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
    });

    eventAggregator.subscribe("productSelected", function (eventArgs) {
    cart.addItem(eventArgs.product);
    });
    }

    function ProductRepository() {
    var products = [new Product(1, "Star Wars Lego Ship"),
    new Product(2, "Barbie Doll"),
    new Product(3, "Remote Control Airplane")];

    this.getProducts = function () {
    return products;
    }
    }

    function ProductController(eventAggregator, productRepository) {
    var products = productRepository.getProducts();

    function onProductSelected() {
    var productId = $(this).attr('id');
    var product = $.grep(products, function (x) {
    return x.getId() == productId;
    })[0];
    eventAggregator.publish("productSelected", {
    product: product
    });
    }

    products.forEach(function (product) {
    var newItem = $('

  • ').html(product.getDescription())
    .attr('id', product.getId())
    .dblclick(onProductSelected)
    .appendTo("#products");
    });
    }

    (function () {
    var eventAggregator = new EventAggregator(),
    cart = new Cart(eventAggregator),
    cartController = new CartController(cart, eventAggregator),
    productRepository = new ProductRepository(),
    productController = new ProductController(eventAggregator, productRepository);
    })();
    ```

    # 总结

    看到这个重构结果,有朋友可能要问了,真的有必要做这么复杂么?我只能说:要不要这么做取决于你项目的情况。

    如果你的项目是个是个非常小的项目,代码也不是很多,那其实是没有必要重构得这么复杂,但如果你的项目是个很复杂的大型项目,或者你的小项目将来可能增长得很快的话,那就在前期就得考虑SRP原则进行职责分离了,这样才有利于以后的维护。